From 22f703cab05b7cd368f4de9e03991b7664dc5022 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Frings-F=C3=BCrst?= Date: Mon, 1 Sep 2014 13:56:46 +0200 Subject: Initial import of argyll version 1.5.1-8 --- target/ColorChecker.ti2 | 54 + target/ECI2002.ti2 | 1578 +++++++ target/ECI2002R.ti2 | 1517 +++++++ target/FograStrip2.ti1 | 120 + target/FograStrip2_2.ti2 | 75 + target/FograStrip3.ti1 | 146 + target/FograStrip3_3.ti2 | 101 + target/Jamfile | 74 + target/License.txt | 662 +++ target/Makefile.am | 28 + target/Readme.txt | 13 + target/afiles | 32 + target/alphix.c | 468 ++ target/alphix.h | 118 + target/filmtarg.c | 458 ++ target/i1_RGB_Scan_1.4.ti2 | 319 ++ target/ifarp.c | 946 ++++ target/ifarp.h | 67 + target/ofps.c | 10222 +++++++++++++++++++++++++++++++++++++++++++ target/ofps.h | 438 ++ target/ppoint.c | 1056 +++++ target/ppoint.h | 134 + target/prand.c | 604 +++ target/prand.h | 68 + target/printtarg.c | 4300 ++++++++++++++++++ target/randix.c | 166 + target/randix.h | 41 + target/simdlat.c | 1063 +++++ target/simdlat.h | 94 + target/simplat.c | 1095 +++++ target/simplat.h | 86 + target/targen.c | 2224 ++++++++++ target/targen.h | 30 + 33 files changed, 28397 insertions(+) create mode 100644 target/ColorChecker.ti2 create mode 100644 target/ECI2002.ti2 create mode 100644 target/ECI2002R.ti2 create mode 100644 target/FograStrip2.ti1 create mode 100644 target/FograStrip2_2.ti2 create mode 100644 target/FograStrip3.ti1 create mode 100644 target/FograStrip3_3.ti2 create mode 100644 target/Jamfile create mode 100644 target/License.txt create mode 100644 target/Makefile.am create mode 100644 target/Readme.txt create mode 100644 target/afiles create mode 100644 target/alphix.c create mode 100644 target/alphix.h create mode 100644 target/filmtarg.c create mode 100644 target/i1_RGB_Scan_1.4.ti2 create mode 100644 target/ifarp.c create mode 100644 target/ifarp.h create mode 100644 target/ofps.c create mode 100644 target/ofps.h create mode 100644 target/ppoint.c create mode 100644 target/ppoint.h create mode 100644 target/prand.c create mode 100644 target/prand.h create mode 100644 target/printtarg.c create mode 100644 target/randix.c create mode 100644 target/randix.h create mode 100644 target/simdlat.c create mode 100644 target/simdlat.h create mode 100644 target/simplat.c create mode 100644 target/simplat.h create mode 100644 target/targen.c create mode 100644 target/targen.h (limited to 'target') diff --git a/target/ColorChecker.ti2 b/target/ColorChecker.ti2 new file mode 100644 index 0000000..6ede47a --- /dev/null +++ b/target/ColorChecker.ti2 @@ -0,0 +1,54 @@ +CTI2 + +DESCRIPTOR "Argyll Calibration Target chart information 2" +# Standard Macbeth ColorChecker 6x4 chart, read patch by patch +ORIGINATOR "Argyll printtarg" +CREATED "Wed Apr 11 22:19:15 2007" +KEYWORD "TARGET_INSTRUMENT" +TARGET_INSTRUMENT "GretagMacbeth SpectroScan" +KEYWORD "APPROX_WHITE_POINT" +APPROX_WHITE_POINT "86.776 90.361 70.642" +KEYWORD "COLOR_REP" +COLOR_REP "RGB" +KEYWORD "STEPS_IN_PASS" +STEPS_IN_PASS "6" +KEYWORD "PASSES_IN_STRIPS2" +PASSES_IN_STRIPS2 "4" +KEYWORD "STRIP_INDEX_PATTERN" +STRIP_INDEX_PATTERN "A-Z, 2-9;A-X,2A-9Z" +KEYWORD "PATCH_INDEX_PATTERN" +PATCH_INDEX_PATTERN "0-9,@-9,@-9;1-999" + +KEYWORD "SAMPLE_LOC" +NUMBER_OF_FIELDS 9 +BEGIN_DATA_FORMAT +SAMPLE_ID SAMPLE_LOC RGB_R RGB_G RGB_B XYZ_X XYZ_Y XYZ_Z +END_DATA_FORMAT + +NUMBER_OF_SETS 24 +BEGIN_DATA +1 "A1" 0 0 0 11.773 10.213 4.9219 +2 "A2" 0 0 0 40.174 36.201 20.217 +3 "A3" 0 0 0 17.675 19.409 26.983 +4 "A4" 0 0 0 11.121 13.530 5.5615 +5 "A5" 0 0 0 25.551 24.404 34.847 +6 "A6" 0 0 0 31.744 43.164 35.249 +7 "B1" 0 0 0 40.056 30.947 4.8180 +8 "B2" 0 0 0 12.544 11.700 28.648 +9 "B3" 0 0 0 30.675 20.352 10.837 +10 "B4" 0 0 0 8.3961 6.5047 10.849 +11 "B5" 0 0 0 36.036 44.991 8.9494 +12 "B6" 0 0 0 50.203 44.570 6.2773 +13 "C1" 0 0 0 7.4590 6.0952 23.518 +14 "C2" 0 0 0 15.439 23.986 7.7482 +15 "C3" 0 0 0 22.850 13.022 4.1188 +16 "C4" 0 0 0 59.637 60.332 7.3520 +17 "C5" 0 0 0 30.450 20.015 22.947 +18 "C6" 0 0 0 13.591 19.466 30.479 +19 "D1" 0 0 0 86.776 90.361 70.642 +20 "D2" 0 0 0 56.865 59.038 48.218 +21 "D3" 0 0 0 34.763 36.036 29.378 +22 "D4" 0 0 0 18.884 19.603 16.309 +23 "D5" 0 0 0 8.4332 8.7464 7.1022 +24 "D6" 0 0 0 3.0110 3.0971 2.5475 +END_DATA diff --git a/target/ECI2002.ti2 b/target/ECI2002.ti2 new file mode 100644 index 0000000..0ab3095 --- /dev/null +++ b/target/ECI2002.ti2 @@ -0,0 +1,1578 @@ +CTI2 + +DESCRIPTOR "Argyll Calibration Target chart information 2" +ORIGINATOR "Argyll printtarg" +CREATED "Mon Oct 11 11:50:06 2010" +KEYWORD "TARGET_INSTRUMENT" +TARGET_INSTRUMENT "Xrite DTP41" +KEYWORD "APPROX_WHITE_POINT" +APPROX_WHITE_POINT "86.886812 89.830019 77.951670" +KEYWORD "COLOR_REP" +COLOR_REP "CMYK" +KEYWORD "RANDOM_START" +RANDOM_START "1251" +KEYWORD "PATCH_LENGTH" +PATCH_LENGTH "7.366000" +KEYWORD "GAP_LENGTH" +GAP_LENGTH "2.032000" +KEYWORD "TRAILER_LENGTH" +TRAILER_LENGTH "18.796000" +KEYWORD "STEPS_IN_PASS" +STEPS_IN_PASS "57" +KEYWORD "PASSES_IN_STRIPS2" +PASSES_IN_STRIPS2 "8,8,8,3" +KEYWORD "STRIP_INDEX_PATTERN" +STRIP_INDEX_PATTERN "A-Z, A-Z" +KEYWORD "PATCH_INDEX_PATTERN" +PATCH_INDEX_PATTERN "0-9,@-9,@-9;1-999" +KEYWORD "INDEX_ORDER" +INDEX_ORDER "STRIP_THEN_PATCH" + +KEYWORD "SAMPLE_LOC" +NUMBER_OF_FIELDS 9 +BEGIN_DATA_FORMAT +SAMPLE_ID SAMPLE_LOC CMYK_C CMYK_M CMYK_Y CMYK_K XYZ_X XYZ_Y XYZ_Z +END_DATA_FORMAT + +NUMBER_OF_SETS 1539 +BEGIN_DATA +1 "K18" 0.0000 0.0000 0.0000 0.0000 82.670 85.770 75.100 +2 "C42" 0.0000 10.196 0.0000 0.0000 75.440 75.210 67.360 +3 "H41" 0.0000 20.000 0.0000 0.0000 68.390 65.250 59.520 +4 "J28" 0.0000 29.804 0.0000 0.0000 61.730 55.910 51.990 +5 "P43" 0.0000 40.000 0.0000 0.0000 55.570 47.360 44.900 +6 "P52" 0.0000 54.902 0.0000 0.0000 47.430 36.190 34.970 +7 "Q3" 0.0000 70.196 0.0000 0.0000 40.640 27.330 26.760 +8 "C3" 0.0000 85.098 0.0000 0.0000 36.010 21.120 20.750 +9 "O41" 0.0000 100.00 0.0000 0.0000 32.280 16.480 15.950 +10 "R55" 10.196 0.0000 0.0000 0.0000 74.060 78.380 73.130 +11 "V5" 10.196 10.196 0.0000 0.0000 67.440 68.580 65.650 +12 "R5" 10.196 20.000 0.0000 0.0000 61.180 59.530 58.060 +13 "L34" 10.196 29.804 0.0000 0.0000 55.110 50.870 50.710 +14 "G34" 10.196 40.000 0.0000 0.0000 49.460 42.920 43.790 +15 "B38" 10.196 54.902 0.0000 0.0000 42.150 32.700 34.170 +16 "A44" 10.196 70.196 0.0000 0.0000 36.060 24.630 26.220 +17 "S11" 10.196 85.098 0.0000 0.0000 31.880 18.960 20.420 +18 "X39" 10.196 100.00 0.0000 0.0000 28.510 14.710 15.790 +19 "R40" 20.000 0.0000 0.0000 0.0000 65.080 70.510 71.060 +20 "R31" 20.000 10.196 0.0000 0.0000 59.420 61.860 63.940 +21 "S5" 20.000 20.000 0.0000 0.0000 54.000 53.760 56.770 +22 "N37" 20.000 29.804 0.0000 0.0000 48.570 45.890 49.620 +23 "X57" 20.000 40.000 0.0000 0.0000 43.490 38.650 42.860 +24 "Y52" 20.000 54.902 0.0000 0.0000 36.980 29.400 33.560 +25 "D13" 20.000 70.196 0.0000 0.0000 31.560 22.080 25.870 +26 "Q15" 20.000 85.098 0.0000 0.0000 27.790 16.870 20.230 +27 "A15" 20.000 100.00 0.0000 0.0000 24.730 12.950 15.700 +28 "W20" 29.804 0.0000 0.0000 0.0000 56.270 62.660 68.950 +29 "AA44" 29.804 10.196 0.0000 0.0000 51.330 54.930 61.980 +30 "A43" 29.804 20.000 0.0000 0.0000 46.640 47.710 55.080 +31 "C45" 29.804 29.804 0.0000 0.0000 41.990 40.770 48.310 +32 "Z39" 29.804 40.000 0.0000 0.0000 37.620 34.380 41.870 +33 "V30" 29.804 54.902 0.0000 0.0000 31.960 26.120 32.880 +34 "H33" 29.804 70.196 0.0000 0.0000 27.230 19.560 25.440 +35 "C23" 29.804 85.098 0.0000 0.0000 23.930 14.880 20.050 +36 "Y49" 29.804 100.00 0.0000 0.0000 21.240 11.340 15.730 +37 "Z50" 40.000 0.0000 0.0000 0.0000 48.240 55.380 66.850 +38 "L20" 40.000 10.196 0.0000 0.0000 43.880 48.440 60.010 +39 "K41" 40.000 20.000 0.0000 0.0000 39.840 42.020 53.330 +40 "F1" 40.000 29.804 0.0000 0.0000 35.910 35.950 46.930 +41 "S55" 40.000 40.000 0.0000 0.0000 32.220 30.360 40.840 +42 "B10" 40.000 54.902 0.0000 0.0000 27.350 23.050 32.190 +43 "N30" 40.000 70.196 0.0000 0.0000 23.270 17.220 25.020 +44 "T4" 40.000 85.098 0.0000 0.0000 20.410 13.050 19.880 +45 "I25" 40.000 100.00 0.0000 0.0000 18.080 9.9000 15.760 +46 "G17" 54.902 0.0000 0.0000 0.0000 38.030 45.930 63.790 +47 "D2" 54.902 10.196 0.0000 0.0000 34.510 40.110 57.320 +48 "B52" 54.902 20.000 0.0000 0.0000 31.280 34.770 51.060 +49 "K1" 54.902 29.804 0.0000 0.0000 28.140 29.700 44.980 +50 "O42" 54.902 40.000 0.0000 0.0000 25.220 25.070 39.230 +51 "Z36" 54.902 54.902 0.0000 0.0000 21.420 19.070 31.290 +52 "F13" 54.902 70.196 0.0000 0.0000 18.220 14.260 24.660 +53 "P32" 54.902 85.098 0.0000 0.0000 15.880 10.710 19.720 +54 "Y57" 54.902 100.00 0.0000 0.0000 13.940 7.9900 15.690 +55 "K51" 70.196 0.0000 0.0000 0.0000 29.450 37.760 60.910 +56 "T57" 70.196 10.196 0.0000 0.0000 26.690 32.940 54.840 +57 "L28" 70.196 20.000 0.0000 0.0000 24.150 28.550 48.990 +58 "Y13" 70.196 29.804 0.0000 0.0000 21.690 24.370 43.210 +59 "K47" 70.196 40.000 0.0000 0.0000 19.410 20.550 37.770 +60 "O23" 70.196 54.902 0.0000 0.0000 16.490 15.670 30.460 +61 "B18" 70.196 70.196 0.0000 0.0000 14.010 11.710 24.300 +62 "S3" 70.196 85.098 0.0000 0.0000 12.120 8.7200 19.550 +63 "A9" 70.196 100.00 0.0000 0.0000 10.540 6.3900 15.630 +64 "R7" 85.098 0.0000 0.0000 0.0000 22.360 31.030 58.510 +65 "P51" 85.098 10.196 0.0000 0.0000 20.260 27.090 52.840 +66 "G55" 85.098 20.000 0.0000 0.0000 18.350 23.520 47.310 +67 "F25" 85.098 29.804 0.0000 0.0000 16.510 20.140 41.930 +68 "X30" 85.098 40.000 0.0000 0.0000 14.790 17.020 36.840 +69 "AA48" 85.098 54.902 0.0000 0.0000 12.500 12.920 29.790 +70 "AA46" 85.098 70.196 0.0000 0.0000 10.560 9.6000 23.840 +71 "Z23" 85.098 85.098 0.0000 0.0000 9.0900 7.1100 19.350 +72 "I55" 85.098 100.00 0.0000 0.0000 7.8700 5.1700 15.670 +73 "M27" 100.00 0.0000 0.0000 0.0000 16.720 25.290 56.260 +74 "X34" 100.00 10.196 0.0000 0.0000 15.170 22.120 51.000 +75 "A3" 100.00 20.000 0.0000 0.0000 13.770 19.260 45.750 +76 "Y30" 100.00 29.804 0.0000 0.0000 12.430 16.590 40.800 +77 "X4" 100.00 40.000 0.0000 0.0000 11.160 14.070 36.080 +78 "A8" 100.00 54.902 0.0000 0.0000 9.3600 10.620 29.150 +79 "Y39" 100.00 70.196 0.0000 0.0000 7.8300 7.8300 23.340 +80 "L44" 100.00 85.098 0.0000 0.0000 6.7100 5.7800 19.130 +81 "D52" 100.00 100.00 0.0000 0.0000 5.7900 4.2100 15.750 +82 "D3" 0.0000 0.0000 10.196 0.0000 80.380 84.000 64.370 +83 "O17" 0.0000 10.196 10.196 0.0000 73.240 73.550 57.600 +84 "H11" 0.0000 20.000 10.196 0.0000 66.560 63.900 51.070 +85 "C48" 0.0000 29.804 10.196 0.0000 60.220 54.850 44.770 +86 "O25" 0.0000 40.000 10.196 0.0000 54.320 46.530 38.760 +87 "C56" 0.0000 54.902 10.196 0.0000 46.490 35.580 30.230 +88 "E57" 0.0000 70.196 10.196 0.0000 39.940 26.920 23.160 +89 "AA55" 0.0000 85.098 10.196 0.0000 35.480 20.900 18.000 +90 "Z21" 0.0000 100.00 10.196 0.0000 31.910 16.410 13.900 +91 "G7" 10.196 0.0000 10.196 0.0000 71.850 76.680 62.750 +92 "H25" 10.196 10.196 10.196 0.0000 65.560 67.260 56.410 +93 "W51" 10.196 20.000 10.196 0.0000 59.560 58.430 50.060 +94 "F6" 10.196 29.804 10.196 0.0000 53.740 50.010 43.840 +95 "R50" 10.196 40.000 10.196 0.0000 48.340 42.280 37.940 +96 "K45" 10.196 54.902 10.196 0.0000 41.320 32.260 29.640 +97 "G23" 10.196 70.196 10.196 0.0000 35.490 24.370 22.790 +98 "A55" 10.196 85.098 10.196 0.0000 31.500 18.860 17.820 +99 "D5" 10.196 100.00 10.196 0.0000 28.300 14.750 13.870 +100 "N50" 20.000 0.0000 10.196 0.0000 63.190 69.190 61.080 +101 "G47" 20.000 10.196 10.196 0.0000 57.650 60.620 54.910 +102 "T27" 20.000 20.000 10.196 0.0000 52.360 52.680 48.710 +103 "J23" 20.000 29.804 10.196 0.0000 47.190 45.010 42.630 +104 "N57" 20.000 40.000 10.196 0.0000 42.370 37.990 36.900 +105 "B21" 20.000 54.902 10.196 0.0000 36.180 28.990 28.990 +106 "E7" 20.000 70.196 10.196 0.0000 31.020 21.860 22.440 +107 "E12" 20.000 85.098 10.196 0.0000 27.440 16.800 17.600 +108 "B42" 20.000 100.00 10.196 0.0000 24.540 12.990 13.700 +109 "Q43" 29.804 0.0000 10.196 0.0000 54.560 61.520 59.050 +110 "O44" 29.804 10.196 10.196 0.0000 49.750 53.870 53.150 +111 "C52" 29.804 20.000 10.196 0.0000 45.200 46.820 47.200 +112 "V1" 29.804 29.804 10.196 0.0000 40.730 40.020 41.410 +113 "F26" 29.804 40.000 10.196 0.0000 36.560 33.770 35.950 +114 "J37" 29.804 54.902 10.196 0.0000 31.180 25.750 28.380 +115 "I38" 29.804 70.196 10.196 0.0000 26.680 19.370 22.070 +116 "F23" 29.804 85.098 10.196 0.0000 23.510 14.790 17.380 +117 "AA15" 29.804 100.00 10.196 0.0000 20.920 11.310 13.590 +118 "Y54" 40.000 0.0000 10.196 0.0000 46.600 54.310 57.010 +119 "G19" 40.000 10.196 10.196 0.0000 42.470 47.550 51.400 +120 "O47" 40.000 20.000 10.196 0.0000 38.590 41.330 45.720 +121 "U42" 40.000 29.804 10.196 0.0000 34.790 35.350 40.250 +122 "F39" 40.000 40.000 10.196 0.0000 31.230 29.850 35.070 +123 "K23" 40.000 54.902 10.196 0.0000 26.600 22.740 27.800 +124 "Z18" 40.000 70.196 10.196 0.0000 22.720 17.060 21.720 +125 "G49" 40.000 85.098 10.196 0.0000 19.950 12.940 17.190 +126 "V56" 40.000 100.00 10.196 0.0000 17.670 9.8000 13.520 +127 "L47" 54.902 0.0000 10.196 0.0000 36.470 44.940 54.360 +128 "AA56" 54.902 10.196 10.196 0.0000 33.150 39.300 49.040 +129 "N49" 54.902 20.000 10.196 0.0000 30.120 34.180 43.840 +130 "H23" 54.902 29.804 10.196 0.0000 27.160 29.250 38.710 +131 "G25" 54.902 40.000 10.196 0.0000 24.390 24.710 33.830 +132 "K4" 54.902 54.902 10.196 0.0000 20.750 18.820 27.020 +133 "N17" 54.902 70.196 10.196 0.0000 17.690 14.080 21.310 +134 "M43" 54.902 85.098 10.196 0.0000 15.470 10.610 17.020 +135 "Q42" 54.902 100.00 10.196 0.0000 13.640 7.9500 13.520 +136 "K55" 70.196 0.0000 10.196 0.0000 27.940 36.820 51.930 +137 "E45" 70.196 10.196 10.196 0.0000 25.360 32.200 46.890 +138 "J26" 70.196 20.000 10.196 0.0000 23.020 28.000 42.110 +139 "H45" 70.196 29.804 10.196 0.0000 20.770 23.990 37.280 +140 "Q41" 70.196 40.000 10.196 0.0000 18.660 20.280 32.670 +141 "C2" 70.196 54.902 10.196 0.0000 15.860 15.440 26.300 +142 "AA18" 70.196 70.196 10.196 0.0000 13.490 11.540 20.940 +143 "N2" 70.196 85.098 10.196 0.0000 11.760 8.6500 16.890 +144 "P16" 70.196 100.00 10.196 0.0000 10.320 6.4100 13.560 +145 "X52" 85.098 0.0000 10.196 0.0000 20.880 30.120 49.870 +146 "R42" 85.098 10.196 10.196 0.0000 19.000 26.420 45.220 +147 "Y2" 85.098 20.000 10.196 0.0000 17.220 22.970 40.630 +148 "H21" 85.098 29.804 10.196 0.0000 15.530 19.690 36.070 +149 "Q45" 85.098 40.000 10.196 0.0000 13.960 16.660 31.740 +150 "D22" 85.098 54.902 10.196 0.0000 11.860 12.690 25.740 +151 "C22" 85.098 70.196 10.196 0.0000 10.070 9.4700 20.670 +152 "U35" 85.098 85.098 10.196 0.0000 8.7400 7.0600 16.830 +153 "J53" 85.098 100.00 10.196 0.0000 7.6400 5.2000 13.660 +154 "L12" 100.00 0.0000 10.196 0.0000 15.280 24.400 47.930 +155 "T16" 100.00 10.196 10.196 0.0000 13.990 21.530 43.690 +156 "L23" 100.00 20.000 10.196 0.0000 12.640 18.700 39.220 +157 "C44" 100.00 29.804 10.196 0.0000 11.390 16.040 34.910 +158 "S44" 100.00 40.000 10.196 0.0000 10.230 13.600 30.870 +159 "T14" 100.00 54.902 10.196 0.0000 8.6900 10.360 25.210 +160 "S9" 100.00 70.196 10.196 0.0000 7.3700 7.7300 20.420 +161 "G24" 100.00 85.098 10.196 0.0000 6.3600 5.7600 16.780 +162 "B53" 100.00 100.00 10.196 0.0000 5.5300 4.2200 13.770 +163 "H37" 0.0000 0.0000 20.000 0.0000 78.300 82.380 54.640 +164 "R37" 0.0000 10.196 20.000 0.0000 71.360 72.100 48.880 +165 "AA22" 0.0000 20.000 20.000 0.0000 65.060 62.860 43.680 +166 "V10" 0.0000 29.804 20.000 0.0000 58.960 54.020 38.370 +167 "T22" 0.0000 40.000 20.000 0.0000 53.250 45.860 33.210 +168 "O39" 0.0000 54.902 20.000 0.0000 45.770 35.240 25.990 +169 "V49" 0.0000 70.196 20.000 0.0000 39.500 26.800 19.980 +170 "Z56" 0.0000 85.098 20.000 0.0000 35.160 20.850 15.510 +171 "B39" 0.0000 100.00 20.000 0.0000 31.650 16.380 11.930 +172 "A50" 10.196 0.0000 20.000 0.0000 69.820 75.120 53.140 +173 "M39" 10.196 10.196 20.000 0.0000 63.810 65.980 47.900 +174 "U2" 10.196 20.000 20.000 0.0000 58.190 57.500 42.860 +175 "K15" 10.196 29.804 20.000 0.0000 52.650 49.350 37.630 +176 "I1" 10.196 40.000 20.000 0.0000 47.450 41.810 32.570 +177 "C32" 10.196 54.902 20.000 0.0000 40.640 31.960 25.540 +178 "Q29" 10.196 70.196 20.000 0.0000 34.960 24.170 19.690 +179 "W19" 10.196 85.098 20.000 0.0000 31.090 18.740 15.360 +180 "P41" 10.196 100.00 20.000 0.0000 27.980 14.690 11.890 +181 "B51" 20.000 0.0000 20.000 0.0000 61.330 67.780 51.660 +182 "E37" 20.000 10.196 20.000 0.0000 56.030 59.460 46.530 +183 "B31" 20.000 20.000 20.000 0.0000 51.100 51.890 41.660 +184 "U39" 20.000 29.804 20.000 0.0000 46.130 44.410 36.510 +185 "T47" 20.000 40.000 20.000 0.0000 41.450 37.500 31.540 +186 "I29" 20.000 54.902 20.000 0.0000 35.490 28.670 24.840 +187 "Y40" 20.000 70.196 20.000 0.0000 30.520 21.670 19.280 +188 "N25" 20.000 85.098 20.000 0.0000 27.040 16.700 15.120 +189 "J11" 20.000 100.00 20.000 0.0000 24.220 12.950 11.750 +190 "S22" 29.804 0.0000 20.000 0.0000 52.750 60.170 49.770 +191 "V9" 29.804 10.196 20.000 0.0000 48.210 52.780 44.850 +192 "B45" 29.804 20.000 20.000 0.0000 43.950 46.030 40.170 +193 "I49" 29.804 29.804 20.000 0.0000 39.710 39.450 35.370 +194 "B4" 29.804 40.000 20.000 0.0000 35.720 33.350 30.730 +195 "S52" 29.804 54.902 20.000 0.0000 30.520 25.450 24.250 +196 "A36" 29.804 70.196 20.000 0.0000 26.170 19.160 18.850 +197 "A27" 29.804 85.098 20.000 0.0000 23.120 14.680 14.860 +198 "U32" 29.804 100.00 20.000 0.0000 20.640 11.290 11.640 +199 "A17" 40.000 0.0000 20.000 0.0000 44.870 53.030 47.880 +200 "K30" 40.000 10.196 20.000 0.0000 41.020 46.530 43.230 +201 "M48" 40.000 20.000 20.000 0.0000 37.380 40.550 38.740 +202 "W53" 40.000 29.804 20.000 0.0000 33.840 34.820 34.310 +203 "V54" 40.000 40.000 20.000 0.0000 30.490 29.510 30.000 +204 "M56" 40.000 54.902 20.000 0.0000 26.000 22.460 23.730 +205 "N4" 40.000 70.196 20.000 0.0000 22.210 16.840 18.480 +206 "M12" 40.000 85.098 20.000 0.0000 19.560 12.830 14.660 +207 "F18" 40.000 100.00 20.000 0.0000 17.420 9.7900 11.580 +208 "A22" 54.902 0.0000 20.000 0.0000 35.030 43.940 45.670 +209 "F46" 54.902 10.196 20.000 0.0000 31.970 38.520 41.340 +210 "G53" 54.902 20.000 20.000 0.0000 29.140 33.620 37.240 +211 "Y11" 54.902 29.804 20.000 0.0000 26.340 28.850 32.990 +212 "B49" 54.902 40.000 20.000 0.0000 23.700 24.410 28.850 +213 "Z11" 54.902 54.902 20.000 0.0000 20.220 18.600 23.080 +214 "N55" 54.902 70.196 20.000 0.0000 17.280 13.940 18.230 +215 "G38" 54.902 85.098 20.000 0.0000 15.130 10.540 14.610 +216 "K9" 54.902 100.00 20.000 0.0000 13.360 7.9400 11.670 +217 "H43" 70.196 0.0000 20.000 0.0000 26.700 36.030 43.670 +218 "H17" 70.196 10.196 20.000 0.0000 24.320 31.560 39.620 +219 "O30" 70.196 20.000 20.000 0.0000 22.190 27.590 35.870 +220 "O12" 70.196 29.804 20.000 0.0000 20.010 23.650 31.770 +221 "R46" 70.196 40.000 20.000 0.0000 17.960 19.980 27.790 +222 "AA13" 70.196 54.902 20.000 0.0000 15.350 15.250 22.490 +223 "W13" 70.196 70.196 20.000 0.0000 13.140 11.440 18.030 +224 "M7" 70.196 85.098 20.000 0.0000 11.450 8.6000 14.610 +225 "AA1" 70.196 100.00 20.000 0.0000 10.020 6.4000 11.780 +226 "V43" 85.098 0.0000 20.000 0.0000 19.660 29.370 41.960 +227 "D30" 85.098 10.196 20.000 0.0000 17.910 25.760 38.150 +228 "R53" 85.098 20.000 20.000 0.0000 16.330 22.510 34.520 +229 "D50" 85.098 29.804 20.000 0.0000 14.730 19.300 30.700 +230 "Z45" 85.098 40.000 20.000 0.0000 13.230 16.320 27.010 +231 "C20" 85.098 54.902 20.000 0.0000 11.300 12.470 22.050 +232 "F21" 85.098 70.196 20.000 0.0000 9.6700 9.3500 17.840 +233 "U45" 85.098 85.098 20.000 0.0000 8.4000 7.0000 14.550 +234 "Y10" 85.098 100.00 20.000 0.0000 7.3300 5.1800 11.820 +235 "T48" 100.00 0.0000 20.000 0.0000 14.050 23.630 40.330 +236 "D53" 100.00 10.196 20.000 0.0000 12.830 20.790 36.740 +237 "E20" 100.00 20.000 20.000 0.0000 11.670 18.130 33.170 +238 "M13" 100.00 29.804 20.000 0.0000 10.550 15.580 29.660 +239 "M40" 100.00 40.000 20.000 0.0000 9.5000 13.210 26.320 +240 "Z31" 100.00 54.902 20.000 0.0000 8.1000 10.100 21.640 +241 "L6" 100.00 70.196 20.000 0.0000 6.9000 7.5700 17.620 +242 "E16" 100.00 85.098 20.000 0.0000 5.9800 5.6700 14.470 +243 "X9" 100.00 100.00 20.000 0.0000 5.2200 4.2000 11.840 +244 "I19" 0.0000 0.0000 29.804 0.0000 76.290 80.780 45.490 +245 "U18" 0.0000 10.196 29.804 0.0000 69.640 70.770 40.820 +246 "A21" 0.0000 20.000 29.804 0.0000 63.470 61.670 36.480 +247 "E5" 0.0000 29.804 29.804 0.0000 57.610 53.040 32.110 +248 "E2" 0.0000 40.000 29.804 0.0000 52.150 45.090 27.870 +249 "X7" 0.0000 54.902 29.804 0.0000 44.940 34.710 21.850 +250 "A49" 0.0000 70.196 29.804 0.0000 38.900 26.490 16.830 +251 "T51" 0.0000 85.098 29.804 0.0000 34.770 20.740 13.120 +252 "B44" 0.0000 100.00 29.804 0.0000 31.460 16.430 10.140 +253 "Y21" 10.196 0.0000 29.804 0.0000 67.860 73.560 44.410 +254 "Y18" 10.196 10.196 29.804 0.0000 62.090 64.640 40.170 +255 "R57" 10.196 20.000 29.804 0.0000 56.730 56.440 35.980 +256 "Q4" 10.196 29.804 29.804 0.0000 51.390 48.450 31.590 +257 "O35" 10.196 40.000 29.804 0.0000 46.360 41.070 27.340 +258 "N51" 10.196 54.902 29.804 0.0000 39.850 31.530 21.510 +259 "T2" 10.196 70.196 29.804 0.0000 34.420 23.980 16.660 +260 "C28" 10.196 85.098 29.804 0.0000 30.730 18.690 13.060 +261 "D18" 10.196 100.00 29.804 0.0000 27.770 14.730 10.160 +262 "D45" 20.000 0.0000 29.804 0.0000 59.420 66.240 43.090 +263 "P26" 20.000 10.196 29.804 0.0000 54.410 58.230 39.030 +264 "M51" 20.000 20.000 29.804 0.0000 49.760 50.910 34.960 +265 "R43" 20.000 29.804 29.804 0.0000 44.940 43.580 30.610 +266 "E49" 20.000 40.000 29.804 0.0000 40.420 36.810 26.440 +267 "Q19" 20.000 54.902 29.804 0.0000 34.740 28.280 20.900 +268 "AA26" 20.000 70.196 29.804 0.0000 30.020 21.510 16.310 +269 "L50" 20.000 85.098 29.804 0.0000 26.700 16.660 12.870 +270 "L48" 20.000 100.00 29.804 0.0000 23.990 12.980 10.080 +271 "F22" 29.804 0.0000 29.804 0.0000 51.100 58.890 41.600 +272 "S31" 29.804 10.196 29.804 0.0000 46.760 51.710 37.680 +273 "AA4" 29.804 20.000 29.804 0.0000 42.790 45.230 33.780 +274 "Q50" 29.804 29.804 29.804 0.0000 38.690 38.760 29.720 +275 "I11" 29.804 40.000 29.804 0.0000 34.810 32.760 25.820 +276 "X24" 29.804 54.902 29.804 0.0000 29.830 25.090 20.450 +277 "R24" 29.804 70.196 29.804 0.0000 25.670 19.000 15.980 +278 "V15" 29.804 85.098 29.804 0.0000 22.780 14.630 12.670 +279 "M52" 29.804 100.00 29.804 0.0000 20.420 11.310 9.9900 +280 "U48" 40.000 0.0000 29.804 0.0000 43.450 52.000 40.160 +281 "O5" 40.000 10.196 29.804 0.0000 39.710 45.600 36.380 +282 "Y16" 40.000 20.000 29.804 0.0000 36.340 39.880 32.650 +283 "U57" 40.000 29.804 29.804 0.0000 32.920 34.240 28.910 +284 "I3" 40.000 40.000 29.804 0.0000 29.650 28.990 25.280 +285 "E47" 40.000 54.902 29.804 0.0000 25.340 22.140 20.070 +286 "V36" 40.000 70.196 29.804 0.0000 21.730 16.690 15.720 +287 "I54" 40.000 85.098 29.804 0.0000 19.230 12.780 12.520 +288 "O15" 40.000 100.00 29.804 0.0000 17.200 9.8200 9.9400 +289 "J51" 54.902 0.0000 29.804 0.0000 33.680 42.990 38.300 +290 "C39" 54.902 10.196 29.804 0.0000 30.740 37.650 34.740 +291 "D25" 54.902 20.000 29.804 0.0000 28.090 32.910 31.290 +292 "M36" 54.902 29.804 29.804 0.0000 25.370 28.210 27.700 +293 "H29" 54.902 40.000 29.804 0.0000 22.800 23.860 24.250 +294 "W44" 54.902 54.902 29.804 0.0000 19.570 18.300 19.520 +295 "M10" 54.902 70.196 29.804 0.0000 16.850 13.830 15.540 +296 "S7" 54.902 85.098 29.804 0.0000 14.830 10.510 12.520 +297 "T31" 54.902 100.00 29.804 0.0000 13.130 7.9500 10.040 +298 "O48" 70.196 0.0000 29.804 0.0000 25.400 35.120 36.620 +299 "W33" 70.196 10.196 29.804 0.0000 23.170 30.740 33.270 +300 "U22" 70.196 20.000 29.804 0.0000 21.120 26.850 30.070 +301 "Y24" 70.196 29.804 29.804 0.0000 19.020 22.990 26.610 +302 "U55" 70.196 40.000 29.804 0.0000 17.060 19.420 23.300 +303 "I21" 70.196 54.902 29.804 0.0000 14.730 14.970 19.020 +304 "R3" 70.196 70.196 29.804 0.0000 12.750 11.360 15.400 +305 "I16" 70.196 85.098 29.804 0.0000 11.160 8.5800 12.540 +306 "A34" 70.196 100.00 29.804 0.0000 9.7800 6.4000 10.150 +307 "B54" 85.098 0.0000 29.804 0.0000 18.470 28.540 35.270 +308 "A5" 85.098 10.196 29.804 0.0000 16.860 25.020 32.140 +309 "R14" 85.098 20.000 29.804 0.0000 15.370 21.850 29.080 +310 "C34" 85.098 29.804 29.804 0.0000 13.860 18.740 25.870 +311 "J31" 85.098 40.000 29.804 0.0000 12.450 15.870 22.800 +312 "N45" 85.098 54.902 29.804 0.0000 10.740 12.230 18.720 +313 "E41" 85.098 70.196 29.804 0.0000 9.2800 9.2700 15.250 +314 "I9" 85.098 85.098 29.804 0.0000 8.1000 6.9800 12.500 +315 "S39" 85.098 100.00 29.804 0.0000 7.0900 5.1700 10.190 +316 "P6" 100.00 0.0000 29.804 0.0000 12.990 22.890 34.020 +317 "N31" 100.00 10.196 29.804 0.0000 11.870 20.140 31.110 +318 "P4" 100.00 20.000 29.804 0.0000 10.830 17.600 28.160 +319 "T37" 100.00 29.804 29.804 0.0000 9.8200 15.150 25.230 +320 "S40" 100.00 40.000 29.804 0.0000 8.8600 12.890 22.430 +321 "K27" 100.00 54.902 29.804 0.0000 7.5900 9.9100 18.480 +322 "T26" 100.00 70.196 29.804 0.0000 6.5100 7.4800 15.080 +323 "H32" 100.00 85.098 29.804 0.0000 5.6700 5.6300 12.430 +324 "O46" 100.00 100.00 29.804 0.0000 4.9700 4.1800 10.210 +325 "P49" 0.0000 0.0000 40.000 0.0000 74.390 79.200 36.970 +326 "P5" 0.0000 10.196 40.000 0.0000 67.990 69.470 33.260 +327 "H20" 0.0000 20.000 40.000 0.0000 61.920 60.440 29.670 +328 "S24" 0.0000 29.804 40.000 0.0000 56.270 52.000 26.160 +329 "S21" 0.0000 40.000 40.000 0.0000 51.050 44.280 22.780 +330 "P44" 0.0000 54.902 40.000 0.0000 44.100 34.130 17.920 +331 "X56" 0.0000 70.196 40.000 0.0000 38.280 26.120 13.860 +332 "C4" 0.0000 85.098 40.000 0.0000 34.370 20.590 10.880 +333 "O34" 0.0000 100.00 40.000 0.0000 31.270 16.470 8.4900 +334 "E31" 10.196 0.0000 40.000 0.0000 66.000 72.030 36.270 +335 "H57" 10.196 10.196 40.000 0.0000 60.440 63.300 32.890 +336 "O20" 10.196 20.000 40.000 0.0000 55.270 55.310 29.430 +337 "B40" 10.196 29.804 40.000 0.0000 50.100 47.480 25.830 +338 "X49" 10.196 40.000 40.000 0.0000 45.250 40.270 22.370 +339 "V34" 10.196 54.902 40.000 0.0000 39.050 31.050 17.670 +340 "Q1" 10.196 70.196 40.000 0.0000 33.900 23.770 13.770 +341 "C6" 10.196 85.098 40.000 0.0000 30.390 18.640 10.880 +342 "A46" 10.196 100.00 40.000 0.0000 27.580 14.780 8.5600 +343 "Y15" 20.000 0.0000 40.000 0.0000 57.590 64.730 35.140 +344 "N53" 20.000 10.196 40.000 0.0000 52.850 56.990 32.000 +345 "AA23" 20.000 20.000 40.000 0.0000 48.380 49.850 28.590 +346 "Y35" 20.000 29.804 40.000 0.0000 43.740 42.660 25.010 +347 "C9" 20.000 40.000 40.000 0.0000 39.390 36.060 21.630 +348 "Y46" 20.000 54.902 40.000 0.0000 34.000 27.850 17.190 +349 "L22" 20.000 70.196 40.000 0.0000 29.520 21.330 13.510 +350 "C36" 20.000 85.098 40.000 0.0000 26.380 16.620 10.760 +351 "M34" 20.000 100.00 40.000 0.0000 23.800 13.020 8.5200 +352 "S4" 29.804 0.0000 40.000 0.0000 49.550 57.650 34.070 +353 "Z33" 29.804 10.196 40.000 0.0000 45.360 50.640 31.000 +354 "Q46" 29.804 20.000 40.000 0.0000 41.620 44.370 27.750 +355 "A18" 29.804 29.804 40.000 0.0000 37.650 38.000 24.370 +356 "M23" 29.804 40.000 40.000 0.0000 33.870 32.100 21.180 +357 "L43" 29.804 54.902 40.000 0.0000 29.130 24.710 16.880 +358 "T40" 29.804 70.196 40.000 0.0000 25.190 18.840 13.300 +359 "G3" 29.804 85.098 40.000 0.0000 22.450 14.590 10.630 +360 "P8" 29.804 100.00 40.000 0.0000 20.210 11.340 8.4500 +361 "K34" 40.000 0.0000 40.000 0.0000 42.140 51.000 33.060 +362 "P30" 40.000 10.196 40.000 0.0000 38.440 44.670 30.030 +363 "O45" 40.000 20.000 40.000 0.0000 35.330 39.180 26.960 +364 "R6" 40.000 29.804 40.000 0.0000 31.990 33.600 23.810 +365 "L41" 40.000 40.000 40.000 0.0000 28.770 28.400 20.790 +366 "M18" 40.000 54.902 40.000 0.0000 24.680 21.800 16.630 +367 "K32" 40.000 70.196 40.000 0.0000 21.270 16.540 13.140 +368 "S50" 40.000 85.098 40.000 0.0000 18.910 12.740 10.540 +369 "G39" 40.000 100.00 40.000 0.0000 16.990 9.8300 8.4100 +370 "G20" 54.902 0.0000 40.000 0.0000 32.390 42.020 31.550 +371 "P34" 54.902 10.196 40.000 0.0000 29.520 36.740 28.640 +372 "P50" 54.902 20.000 40.000 0.0000 27.010 32.130 25.760 +373 "H9" 54.902 29.804 40.000 0.0000 24.380 27.510 22.780 +374 "Z7" 54.902 40.000 40.000 0.0000 21.910 23.260 19.950 +375 "V32" 54.902 54.902 40.000 0.0000 18.920 17.960 16.180 +376 "S34" 54.902 70.196 40.000 0.0000 16.410 13.690 13.000 +377 "C29" 54.902 85.098 40.000 0.0000 14.520 10.470 10.540 +378 "X42" 54.902 100.00 40.000 0.0000 12.920 7.9500 8.4900 +379 "Y20" 70.196 0.0000 40.000 0.0000 24.130 34.160 30.170 +380 "A42" 70.196 10.196 40.000 0.0000 22.000 29.870 27.400 +381 "V6" 70.196 20.000 40.000 0.0000 20.030 26.030 24.680 +382 "A47" 70.196 29.804 40.000 0.0000 18.020 22.270 21.860 +383 "I57" 70.196 40.000 40.000 0.0000 16.190 18.840 19.200 +384 "C25" 70.196 54.902 40.000 0.0000 14.100 14.650 15.780 +385 "Q57" 70.196 70.196 40.000 0.0000 12.330 11.240 12.880 +386 "E25" 70.196 85.098 40.000 0.0000 10.860 8.5400 10.550 +387 "U36" 70.196 100.00 40.000 0.0000 9.5600 6.3900 8.5800 +388 "F17" 85.098 0.0000 40.000 0.0000 17.320 27.680 29.170 +389 "C8" 85.098 10.196 40.000 0.0000 15.830 24.270 26.620 +390 "F36" 85.098 20.000 40.000 0.0000 14.420 21.160 24.040 +391 "D7" 85.098 29.804 40.000 0.0000 13.000 18.150 21.410 +392 "AA27" 85.098 40.000 40.000 0.0000 11.710 15.410 18.910 +393 "T19" 85.098 54.902 40.000 0.0000 10.180 11.970 15.610 +394 "G56" 85.098 70.196 40.000 0.0000 8.8800 9.1500 12.790 +395 "Z19" 85.098 85.098 40.000 0.0000 7.8000 6.9300 10.540 +396 "J44" 85.098 100.00 40.000 0.0000 6.8600 5.1600 8.6400 +397 "E23" 100.00 0.0000 40.000 0.0000 11.990 22.160 28.290 +398 "X25" 100.00 10.196 40.000 0.0000 10.980 19.510 25.970 +399 "F4" 100.00 20.000 40.000 0.0000 10.040 17.070 23.530 +400 "V41" 100.00 29.804 40.000 0.0000 9.1100 14.730 21.100 +401 "X48" 100.00 40.000 40.000 0.0000 8.2400 12.570 18.770 +402 "W42" 100.00 54.902 40.000 0.0000 7.1100 9.7100 15.500 +403 "W9" 100.00 70.196 40.000 0.0000 6.1400 7.3700 12.690 +404 "M35" 100.00 85.098 40.000 0.0000 5.3800 5.5800 10.500 +405 "I4" 100.00 100.00 40.000 0.0000 4.7300 4.1600 8.6800 +406 "M15" 0.0000 0.0000 54.902 0.0000 71.850 76.950 25.370 +407 "L46" 0.0000 10.196 54.902 0.0000 65.680 67.470 22.770 +408 "D21" 0.0000 20.000 54.902 0.0000 59.880 58.740 20.360 +409 "AA6" 0.0000 29.804 54.902 0.0000 54.450 50.550 17.950 +410 "Z55" 0.0000 40.000 54.902 0.0000 49.460 43.080 15.630 +411 "L35" 0.0000 54.902 54.902 0.0000 42.980 33.400 12.470 +412 "O33" 0.0000 70.196 54.902 0.0000 37.560 25.750 9.8100 +413 "X31" 0.0000 85.098 54.902 0.0000 33.860 20.410 7.7800 +414 "Q8" 0.0000 100.00 54.902 0.0000 30.910 16.400 6.1200 +415 "U43" 10.196 0.0000 54.902 0.0000 63.580 69.930 24.890 +416 "C55" 10.196 10.196 54.902 0.0000 58.240 61.470 22.580 +417 "A48" 10.196 20.000 54.902 0.0000 53.250 53.660 20.240 +418 "R39" 10.196 29.804 54.902 0.0000 48.380 46.160 17.810 +419 "D49" 10.196 40.000 54.902 0.0000 43.840 39.280 15.490 +420 "J30" 10.196 54.902 54.902 0.0000 38.020 30.410 12.360 +421 "I48" 10.196 70.196 54.902 0.0000 33.160 23.390 9.7500 +422 "L31" 10.196 85.098 54.902 0.0000 29.860 18.460 7.7800 +423 "Q54" 10.196 100.00 54.902 0.0000 27.220 14.740 6.1800 +424 "I5" 20.000 0.0000 54.902 0.0000 55.290 62.740 24.130 +425 "Z30" 20.000 10.196 54.902 0.0000 50.730 55.220 22.030 +426 "W18" 20.000 20.000 54.902 0.0000 46.420 48.250 19.660 +427 "K29" 20.000 29.804 54.902 0.0000 42.090 41.390 17.270 +428 "S43" 20.000 40.000 54.902 0.0000 38.060 35.130 15.040 +429 "T6" 20.000 54.902 54.902 0.0000 33.020 27.230 12.030 +430 "F20" 20.000 70.196 54.902 0.0000 28.810 20.960 9.5300 +431 "P22" 20.000 85.098 54.902 0.0000 25.880 16.440 7.6700 +432 "F37" 20.000 100.00 54.902 0.0000 23.490 12.990 6.1500 +433 "E36" 29.804 0.0000 54.902 0.0000 47.380 55.770 23.500 +434 "J42" 29.804 10.196 54.902 0.0000 43.320 48.960 21.370 +435 "R16" 29.804 20.000 54.902 0.0000 39.750 42.870 19.180 +436 "D57" 29.804 29.804 54.902 0.0000 36.070 36.810 16.890 +437 "J17" 29.804 40.000 54.902 0.0000 32.590 31.210 14.710 +438 "A39" 29.804 54.902 54.902 0.0000 28.190 24.120 11.810 +439 "I23" 29.804 70.196 54.902 0.0000 24.530 18.480 9.3900 +440 "G31" 29.804 85.098 54.902 0.0000 22.000 14.420 7.5900 +441 "O18" 29.804 100.00 54.902 0.0000 19.930 11.320 6.1100 +442 "L19" 40.000 0.0000 54.902 0.0000 40.090 49.230 22.920 +443 "U13" 40.000 10.196 54.902 0.0000 36.510 43.100 20.740 +444 "U20" 40.000 20.000 54.902 0.0000 33.580 37.810 18.740 +445 "U26" 40.000 29.804 54.902 0.0000 30.510 32.510 16.560 +446 "AA51" 40.000 40.000 54.902 0.0000 27.550 27.560 14.450 +447 "Y3" 40.000 54.902 54.902 0.0000 23.780 21.240 11.650 +448 "E42" 40.000 70.196 54.902 0.0000 20.630 16.200 9.3100 +449 "F33" 40.000 85.098 54.902 0.0000 18.460 12.570 7.5500 +450 "U19" 40.000 100.00 54.902 0.0000 16.710 9.8100 6.1000 +451 "I45" 54.902 0.0000 54.902 0.0000 30.490 40.400 21.940 +452 "D44" 54.902 10.196 54.902 0.0000 27.770 35.360 19.870 +453 "W41" 54.902 20.000 54.902 0.0000 25.450 30.940 18.010 +454 "X14" 54.902 29.804 54.902 0.0000 23.100 26.600 16.000 +455 "S16" 54.902 40.000 54.902 0.0000 20.880 22.580 14.040 +456 "E21" 54.902 54.902 54.902 0.0000 18.070 17.450 11.410 +457 "R29" 54.902 70.196 54.902 0.0000 15.710 13.320 9.2100 +458 "D4" 54.902 85.098 54.902 0.0000 14.010 10.270 7.5500 +459 "N47" 54.902 100.00 54.902 0.0000 12.590 7.9000 6.1800 +460 "W49" 70.196 0.0000 54.902 0.0000 22.390 32.710 21.050 +461 "E50" 70.196 10.196 54.902 0.0000 20.430 28.660 19.130 +462 "P15" 70.196 20.000 54.902 0.0000 18.630 25.000 17.350 +463 "T34" 70.196 29.804 54.902 0.0000 16.890 21.490 15.480 +464 "G29" 70.196 40.000 54.902 0.0000 15.290 18.280 13.670 +465 "Q21" 70.196 54.902 54.902 0.0000 13.300 14.180 11.210 +466 "C12" 70.196 70.196 54.902 0.0000 11.600 10.850 9.1300 +467 "E44" 70.196 85.098 54.902 0.0000 10.300 8.3100 7.5600 +468 "G41" 70.196 100.00 54.902 0.0000 9.1900 6.3100 6.2600 +469 "S25" 85.098 0.0000 54.902 0.0000 15.750 26.400 20.560 +470 "N44" 85.098 10.196 54.902 0.0000 14.390 23.190 18.740 +471 "V48" 85.098 20.000 54.902 0.0000 13.140 20.250 16.990 +472 "R51" 85.098 29.804 54.902 0.0000 11.930 17.440 15.210 +473 "I47" 85.098 40.000 54.902 0.0000 10.800 14.850 13.490 +474 "I39" 85.098 54.902 54.902 0.0000 9.4100 11.510 11.140 +475 "I36" 85.098 70.196 54.902 0.0000 8.2100 8.8000 9.1400 +476 "L10" 85.098 85.098 54.902 0.0000 7.2700 6.7200 7.5900 +477 "O11" 85.098 100.00 54.902 0.0000 6.4700 5.0700 6.3100 +478 "T55" 100.00 0.0000 54.902 0.0000 10.570 21.060 20.180 +479 "Y53" 100.00 10.196 54.902 0.0000 9.6800 18.550 18.440 +480 "N16" 100.00 20.000 54.902 0.0000 8.8700 16.280 16.720 +481 "W55" 100.00 29.804 54.902 0.0000 8.0700 14.050 15.000 +482 "Q35" 100.00 40.000 54.902 0.0000 7.3200 11.980 13.370 +483 "S10" 100.00 54.902 54.902 0.0000 6.3600 9.2700 11.100 +484 "S53" 100.00 70.196 54.902 0.0000 5.5400 7.0700 9.1600 +485 "R15" 100.00 85.098 54.902 0.0000 4.8900 5.3900 7.6300 +486 "H44" 100.00 100.00 54.902 0.0000 4.3500 4.0800 6.3500 +487 "H14" 0.0000 0.0000 70.196 0.0000 69.800 74.880 16.470 +488 "B12" 0.0000 10.196 70.196 0.0000 63.780 65.610 14.710 +489 "R17" 0.0000 20.000 70.196 0.0000 58.230 57.180 13.230 +490 "T13" 0.0000 29.804 70.196 0.0000 52.990 49.220 11.670 +491 "AA21" 0.0000 40.000 70.196 0.0000 48.170 41.980 10.170 +492 "K46" 0.0000 54.902 70.196 0.0000 42.070 32.730 8.2600 +493 "S29" 0.0000 70.196 70.196 0.0000 36.970 25.400 6.6500 +494 "C40" 0.0000 85.098 70.196 0.0000 33.440 20.220 5.3500 +495 "Q6" 0.0000 100.00 70.196 0.0000 30.600 16.310 4.2700 +496 "F34" 10.196 0.0000 70.196 0.0000 61.650 68.010 16.120 +497 "I7" 10.196 10.196 70.196 0.0000 56.470 59.790 14.610 +498 "P27" 10.196 20.000 70.196 0.0000 51.630 52.160 13.150 +499 "Z2" 10.196 29.804 70.196 0.0000 47.010 44.970 11.630 +500 "AA33" 10.196 40.000 70.196 0.0000 42.730 38.390 10.180 +501 "C38" 10.196 54.902 70.196 0.0000 37.200 29.830 8.2300 +502 "J14" 10.196 70.196 70.196 0.0000 32.570 23.030 6.6000 +503 "S38" 10.196 85.098 70.196 0.0000 29.430 18.250 5.3400 +504 "E26" 10.196 100.00 70.196 0.0000 26.920 14.660 4.3100 +505 "G6" 20.000 0.0000 70.196 0.0000 53.480 60.950 15.650 +506 "E55" 20.000 10.196 70.196 0.0000 49.030 53.620 14.290 +507 "N14" 20.000 20.000 70.196 0.0000 44.850 46.800 12.780 +508 "P37" 20.000 29.804 70.196 0.0000 40.780 40.270 11.310 +509 "Z24" 20.000 40.000 70.196 0.0000 37.020 34.300 9.9300 +510 "J34" 20.000 54.902 70.196 0.0000 32.230 26.660 8.0300 +511 "W52" 20.000 70.196 70.196 0.0000 28.240 20.580 6.4300 +512 "I13" 20.000 85.098 70.196 0.0000 25.470 16.240 5.2400 +513 "V52" 20.000 100.00 70.196 0.0000 23.240 12.930 4.2800 +514 "J54" 29.804 0.0000 70.196 0.0000 45.620 54.040 15.320 +515 "D31" 29.804 10.196 70.196 0.0000 41.700 47.430 13.880 +516 "V16" 29.804 20.000 70.196 0.0000 38.240 41.500 12.520 +517 "J2" 29.804 29.804 70.196 0.0000 34.800 35.730 11.090 +518 "Z13" 29.804 40.000 70.196 0.0000 31.560 30.400 9.7100 +519 "B19" 29.804 54.902 70.196 0.0000 27.450 23.570 7.8800 +520 "G10" 29.804 70.196 70.196 0.0000 24.000 18.130 6.3400 +521 "X12" 29.804 85.098 70.196 0.0000 21.630 14.240 5.2000 +522 "U10" 29.804 100.00 70.196 0.0000 19.700 11.270 4.2700 +523 "Y14" 40.000 0.0000 70.196 0.0000 38.400 47.570 15.020 +524 "E24" 40.000 10.196 70.196 0.0000 34.980 41.670 13.500 +525 "S8" 40.000 20.000 70.196 0.0000 32.150 36.540 12.320 +526 "D36" 40.000 29.804 70.196 0.0000 29.300 31.490 10.920 +527 "P9" 40.000 40.000 70.196 0.0000 26.580 26.800 9.5400 +528 "T36" 40.000 54.902 70.196 0.0000 23.070 20.720 7.7800 +529 "C19" 40.000 70.196 70.196 0.0000 20.120 15.860 6.3000 +530 "J46" 40.000 85.098 70.196 0.0000 18.100 12.400 5.1900 +531 "Q44" 40.000 100.00 70.196 0.0000 16.480 9.7600 4.2800 +532 "J12" 54.902 0.0000 70.196 0.0000 28.990 38.930 14.450 +533 "V12" 54.902 10.196 70.196 0.0000 26.430 34.130 13.060 +534 "F7" 54.902 20.000 70.196 0.0000 24.220 29.870 11.960 +535 "R35" 54.902 29.804 70.196 0.0000 22.100 25.790 10.700 +536 "Q16" 54.902 40.000 70.196 0.0000 20.110 22.000 9.4300 +537 "D11" 54.902 54.902 70.196 0.0000 17.420 16.980 7.7000 +538 "L39" 54.902 70.196 70.196 0.0000 15.150 12.950 6.2500 +539 "Y44" 54.902 85.098 70.196 0.0000 13.580 10.050 5.2000 +540 "A4" 54.902 100.00 70.196 0.0000 12.310 7.8300 4.3500 +541 "E30" 70.196 0.0000 70.196 0.0000 21.040 31.420 13.950 +542 "O51" 70.196 10.196 70.196 0.0000 19.240 27.610 12.700 +543 "W30" 70.196 20.000 70.196 0.0000 17.560 24.110 11.650 +544 "V51" 70.196 29.804 70.196 0.0000 16.050 20.840 10.500 +545 "J39" 70.196 40.000 70.196 0.0000 14.640 17.820 9.3400 +546 "L29" 70.196 54.902 70.196 0.0000 12.690 13.760 7.6500 +547 "X1" 70.196 70.196 70.196 0.0000 11.020 10.470 6.2200 +548 "T7" 70.196 85.098 70.196 0.0000 9.8400 8.0700 5.2200 +549 "U46" 70.196 100.00 70.196 0.0000 8.8800 6.2200 4.4200 +550 "T12" 85.098 0.0000 70.196 0.0000 14.510 25.260 13.810 +551 "I6" 85.098 10.196 70.196 0.0000 13.280 22.230 12.580 +552 "S46" 85.098 20.000 70.196 0.0000 12.150 19.450 11.490 +553 "H55" 85.098 29.804 70.196 0.0000 11.100 16.810 10.370 +554 "B36" 85.098 40.000 70.196 0.0000 10.120 14.360 9.2600 +555 "E34" 85.098 54.902 70.196 0.0000 8.8000 11.100 7.6600 +556 "N1" 85.098 70.196 70.196 0.0000 7.6600 8.4500 6.3000 +557 "A12" 85.098 85.098 70.196 0.0000 6.8300 6.4900 5.2900 +558 "N5" 85.098 100.00 70.196 0.0000 6.1500 4.9800 4.4600 +559 "T25" 100.00 0.0000 70.196 0.0000 9.4400 20.050 13.750 +560 "R49" 100.00 10.196 70.196 0.0000 8.6300 17.670 12.510 +561 "K35" 100.00 20.000 70.196 0.0000 7.9400 15.540 11.370 +562 "W17" 100.00 29.804 70.196 0.0000 7.2400 13.420 10.240 +563 "M32" 100.00 40.000 70.196 0.0000 6.5800 11.440 9.1600 +564 "Z16" 100.00 54.902 70.196 0.0000 5.7500 8.8500 7.6900 +565 "O32" 100.00 70.196 70.196 0.0000 5.0400 6.7700 6.4100 +566 "E3" 100.00 85.098 70.196 0.0000 4.4900 5.2000 5.3600 +567 "H16" 100.00 100.00 70.196 0.0000 4.0200 3.9800 4.4800 +568 "J8" 0.0000 0.0000 85.098 0.0000 68.430 73.430 10.410 +569 "Q51" 0.0000 10.196 85.098 0.0000 62.490 64.260 9.2600 +570 "L51" 0.0000 20.000 85.098 0.0000 57.100 56.050 8.3900 +571 "Z26" 0.0000 29.804 85.098 0.0000 52.030 48.310 7.4600 +572 "G8" 0.0000 40.000 85.098 0.0000 47.370 41.250 6.5500 +573 "S56" 0.0000 54.902 85.098 0.0000 41.420 32.200 5.4000 +574 "AA30" 0.0000 70.196 85.098 0.0000 36.460 25.040 4.4200 +575 "E18" 0.0000 85.098 85.098 0.0000 33.070 20.020 3.6500 +576 "K48" 0.0000 100.00 85.098 0.0000 30.360 16.240 3.0200 +577 "B2" 10.196 0.0000 85.098 0.0000 60.350 66.640 10.190 +578 "T35" 10.196 10.196 85.098 0.0000 55.320 58.620 9.2000 +579 "V8" 10.196 20.000 85.098 0.0000 50.600 51.150 8.3400 +580 "T41" 10.196 29.804 85.098 0.0000 46.130 44.150 7.4400 +581 "H24" 10.196 40.000 85.098 0.0000 42.000 37.750 6.5500 +582 "C46" 10.196 54.902 85.098 0.0000 36.660 29.410 5.3900 +583 "C1" 10.196 70.196 85.098 0.0000 32.180 22.780 4.4100 +584 "D19" 10.196 85.098 85.098 0.0000 29.130 18.110 3.6700 +585 "P20" 10.196 100.00 85.098 0.0000 26.690 14.580 3.0600 +586 "U9" 20.000 0.0000 85.098 0.0000 52.310 59.720 9.9000 +587 "Y5" 20.000 10.196 85.098 0.0000 47.960 52.550 9.0200 +588 "C13" 20.000 20.000 85.098 0.0000 43.970 45.950 8.1400 +589 "Y51" 20.000 29.804 85.098 0.0000 40.050 39.610 7.2600 +590 "AA10" 20.000 40.000 85.098 0.0000 36.390 33.790 6.4300 +591 "A29" 20.000 54.902 85.098 0.0000 31.730 26.280 5.2800 +592 "AA19" 20.000 70.196 85.098 0.0000 27.830 20.300 4.3100 +593 "C53" 20.000 85.098 85.098 0.0000 25.180 16.080 3.6100 +594 "U28" 20.000 100.00 85.098 0.0000 23.040 12.880 3.0500 +595 "A13" 29.804 0.0000 85.098 0.0000 44.520 52.890 9.7300 +596 "U23" 29.804 10.196 85.098 0.0000 40.790 46.510 8.8100 +597 "U21" 29.804 20.000 85.098 0.0000 37.450 40.750 8.0000 +598 "I52" 29.804 29.804 85.098 0.0000 34.100 35.100 7.1400 +599 "M53" 29.804 40.000 85.098 0.0000 30.950 29.880 6.3100 +600 "T20" 29.804 54.902 85.098 0.0000 26.950 23.190 5.2100 +601 "G18" 29.804 70.196 85.098 0.0000 23.610 17.870 4.2800 +602 "E27" 29.804 85.098 85.098 0.0000 21.330 14.090 3.6000 +603 "M11" 29.804 100.00 85.098 0.0000 19.500 11.220 3.0500 +604 "A35" 40.000 0.0000 85.098 0.0000 37.360 46.510 9.5900 +605 "Q5" 40.000 10.196 85.098 0.0000 34.210 40.890 8.6200 +606 "N56" 40.000 20.000 85.098 0.0000 31.430 35.860 7.9100 +607 "E52" 40.000 29.804 85.098 0.0000 28.620 30.880 7.0700 +608 "D29" 40.000 40.000 85.098 0.0000 25.980 26.280 6.2400 +609 "R4" 40.000 54.902 85.098 0.0000 22.590 20.350 5.1700 +610 "J27" 40.000 70.196 85.098 0.0000 19.760 15.630 4.2800 +611 "K56" 40.000 85.098 85.098 0.0000 17.820 12.260 3.6200 +612 "D20" 40.000 100.00 85.098 0.0000 16.260 9.7000 3.0700 +613 "I14" 54.902 0.0000 85.098 0.0000 28.140 38.070 9.3200 +614 "R33" 54.902 10.196 85.098 0.0000 25.790 33.510 8.4400 +615 "I24" 54.902 20.000 85.098 0.0000 23.570 29.290 7.7800 +616 "Y29" 54.902 29.804 85.098 0.0000 21.530 25.290 7.0200 +617 "V3" 54.902 40.000 85.098 0.0000 19.630 21.590 6.2400 +618 "L26" 54.902 54.902 85.098 0.0000 17.020 16.690 5.1800 +619 "I53" 54.902 70.196 85.098 0.0000 14.810 12.750 4.2900 +620 "Z17" 54.902 85.098 85.098 0.0000 13.300 9.9200 3.6500 +621 "O10" 54.902 100.00 85.098 0.0000 12.080 7.7600 3.1300 +622 "A24" 70.196 0.0000 85.098 0.0000 20.310 30.710 9.0900 +623 "I33" 70.196 10.196 85.098 0.0000 18.650 27.080 8.3300 +624 "K49" 70.196 20.000 85.098 0.0000 16.970 23.600 7.6800 +625 "N8" 70.196 29.804 85.098 0.0000 15.540 20.420 6.9800 +626 "A10" 70.196 40.000 85.098 0.0000 14.240 17.500 6.2700 +627 "J4" 70.196 54.902 85.098 0.0000 12.330 13.510 5.2100 +628 "M29" 70.196 70.196 85.098 0.0000 10.690 10.280 4.3200 +629 "N34" 70.196 85.098 85.098 0.0000 9.5500 7.9500 3.6900 +630 "Q10" 70.196 100.00 85.098 0.0000 8.6400 6.1600 3.1800 +631 "O6" 85.098 0.0000 85.098 0.0000 13.780 24.590 9.1200 +632 "Q53" 85.098 10.196 85.098 0.0000 12.670 21.710 8.3700 +633 "AA24" 85.098 20.000 85.098 0.0000 11.550 18.950 7.7100 +634 "G43" 85.098 29.804 85.098 0.0000 10.580 16.400 7.0200 +635 "P38" 85.098 40.000 85.098 0.0000 9.6900 14.050 6.3400 +636 "D12" 85.098 54.902 85.098 0.0000 8.4200 10.860 5.3200 +637 "H2" 85.098 70.196 85.098 0.0000 7.3300 8.2700 4.4400 +638 "Q12" 85.098 85.098 85.098 0.0000 6.5500 6.3700 3.7700 +639 "E35" 85.098 100.00 85.098 0.0000 5.9100 4.9100 3.2100 +640 "U1" 100.00 0.0000 85.098 0.0000 8.6800 19.390 9.2000 +641 "F53" 100.00 10.196 85.098 0.0000 7.9800 17.120 8.4600 +642 "L25" 100.00 20.000 85.098 0.0000 7.3400 15.040 7.7500 +643 "U15" 100.00 29.804 85.098 0.0000 6.7100 13.000 7.0600 +644 "W11" 100.00 40.000 85.098 0.0000 6.1100 11.110 6.4000 +645 "X6" 100.00 54.902 85.098 0.0000 5.3500 8.6100 5.4400 +646 "L40" 100.00 70.196 85.098 0.0000 4.7100 6.5900 4.5900 +647 "M37" 100.00 85.098 85.098 0.0000 4.2100 5.0800 3.8600 +648 "S33" 100.00 100.00 85.098 0.0000 3.8000 3.9100 3.2300 +649 "A2" 0.0000 0.0000 100.00 0.0000 67.430 72.230 6.1400 +650 "H56" 0.0000 10.196 100.00 0.0000 61.540 63.120 5.4400 +651 "G2" 0.0000 20.000 100.00 0.0000 56.250 55.090 4.9800 +652 "O22" 0.0000 29.804 100.00 0.0000 51.340 47.560 4.5000 +653 "X37" 0.0000 40.000 100.00 0.0000 46.820 40.680 4.0200 +654 "M16" 0.0000 54.902 100.00 0.0000 40.930 31.740 3.3500 +655 "N24" 0.0000 70.196 100.00 0.0000 36.020 24.690 2.7900 +656 "P2" 0.0000 85.098 100.00 0.0000 32.750 19.830 2.4100 +657 "G42" 0.0000 100.00 100.00 0.0000 30.180 16.190 2.1100 +658 "B3" 10.196 0.0000 100.00 0.0000 59.400 65.500 6.0300 +659 "K14" 10.196 10.196 100.00 0.0000 54.490 57.660 5.4100 +660 "Z43" 10.196 20.000 100.00 0.0000 49.880 50.340 4.9600 +661 "Z48" 10.196 29.804 100.00 0.0000 45.510 43.480 4.4700 +662 "Q40" 10.196 40.000 100.00 0.0000 41.480 37.210 3.9800 +663 "D10" 10.196 54.902 100.00 0.0000 36.280 29.070 3.3500 +664 "B48" 10.196 70.196 100.00 0.0000 31.910 22.580 2.8200 +665 "Q20" 10.196 85.098 100.00 0.0000 28.920 17.990 2.4500 +666 "C31" 10.196 100.00 100.00 0.0000 26.500 14.500 2.1500 +667 "W14" 20.000 0.0000 100.00 0.0000 51.490 58.710 5.8500 +668 "A31" 20.000 10.196 100.00 0.0000 47.230 51.690 5.3000 +669 "C7" 20.000 20.000 100.00 0.0000 43.430 45.310 4.8600 +670 "X50" 20.000 29.804 100.00 0.0000 39.600 39.120 4.3900 +671 "A6" 20.000 40.000 100.00 0.0000 35.980 33.390 3.9300 +672 "L17" 20.000 54.902 100.00 0.0000 31.380 25.970 3.3100 +673 "U47" 20.000 70.196 100.00 0.0000 27.540 20.080 2.7900 +674 "N40" 20.000 85.098 100.00 0.0000 24.950 15.950 2.4400 +675 "S57" 20.000 100.00 100.00 0.0000 22.890 12.830 2.1600 +676 "T46" 29.804 0.0000 100.00 0.0000 43.750 51.970 5.7800 +677 "F56" 29.804 10.196 100.00 0.0000 40.230 45.810 5.2200 +678 "F15" 29.804 20.000 100.00 0.0000 37.010 40.220 4.8000 +679 "P29" 29.804 29.804 100.00 0.0000 33.680 34.640 4.3400 +680 "H40" 29.804 40.000 100.00 0.0000 30.540 29.480 3.9000 +681 "P57" 29.804 54.902 100.00 0.0000 26.590 22.880 3.3000 +682 "E39" 29.804 70.196 100.00 0.0000 23.310 17.660 2.8000 +683 "Z10" 29.804 85.098 100.00 0.0000 21.090 13.970 2.4500 +684 "Q32" 29.804 100.00 100.00 0.0000 19.320 11.170 2.1700 +685 "Q31" 40.000 0.0000 100.00 0.0000 36.650 45.670 5.7400 +686 "W47" 40.000 10.196 100.00 0.0000 33.800 40.340 5.1800 +687 "T39" 40.000 20.000 100.00 0.0000 31.030 35.390 4.7700 +688 "B24" 40.000 29.804 100.00 0.0000 28.210 30.420 4.3300 +689 "S36" 40.000 40.000 100.00 0.0000 25.560 25.850 3.8900 +690 "D23" 40.000 54.902 100.00 0.0000 22.240 20.050 3.3200 +691 "W1" 40.000 70.196 100.00 0.0000 19.480 15.450 2.8300 +692 "K52" 40.000 85.098 100.00 0.0000 17.580 12.160 2.4800 +693 "I37" 40.000 100.00 100.00 0.0000 16.060 9.6400 2.1900 +694 "G27" 54.902 0.0000 100.00 0.0000 27.620 37.430 5.6700 +695 "B9" 54.902 10.196 100.00 0.0000 25.500 33.120 5.1700 +696 "F40" 54.902 20.000 100.00 0.0000 23.220 28.890 4.7800 +697 "G16" 54.902 29.804 100.00 0.0000 21.180 24.900 4.3600 +698 "L9" 54.902 40.000 100.00 0.0000 19.320 21.260 3.9400 +699 "S37" 54.902 54.902 100.00 0.0000 16.760 16.460 3.3700 +700 "L56" 54.902 70.196 100.00 0.0000 14.590 12.610 2.8900 +701 "H47" 54.902 85.098 100.00 0.0000 13.090 9.8300 2.5300 +702 "Y56" 54.902 100.00 100.00 0.0000 11.880 7.7100 2.2400 +703 "S26" 70.196 0.0000 100.00 0.0000 19.890 30.210 5.6200 +704 "E9" 70.196 10.196 100.00 0.0000 18.370 26.750 5.2000 +705 "R11" 70.196 20.000 100.00 0.0000 16.620 23.220 4.8300 +706 "T30" 70.196 29.804 100.00 0.0000 15.220 20.080 4.4200 +707 "X40" 70.196 40.000 100.00 0.0000 13.980 17.240 4.0200 +708 "O31" 70.196 54.902 100.00 0.0000 12.100 13.330 3.4500 +709 "Y6" 70.196 70.196 100.00 0.0000 10.490 10.160 2.9600 +710 "G26" 70.196 85.098 100.00 0.0000 9.3600 7.8700 2.5900 +711 "A52" 70.196 100.00 100.00 0.0000 8.4500 6.1000 2.2800 +712 "B56" 85.098 0.0000 100.00 0.0000 13.310 24.080 5.7300 +713 "F48" 85.098 10.196 100.00 0.0000 12.310 21.330 5.3500 +714 "Y28" 85.098 20.000 100.00 0.0000 11.170 18.560 4.9800 +715 "K13" 85.098 29.804 100.00 0.0000 10.230 16.070 4.5900 +716 "U17" 85.098 40.000 100.00 0.0000 9.4000 13.810 4.2100 +717 "L55" 85.098 54.902 100.00 0.0000 8.1700 10.690 3.6300 +718 "F8" 85.098 70.196 100.00 0.0000 7.1000 8.1500 3.1100 +719 "D37" 85.098 85.098 100.00 0.0000 6.3500 6.2900 2.6800 +720 "K44" 85.098 100.00 100.00 0.0000 5.7300 4.8500 2.3000 +721 "D56" 100.00 0.0000 100.00 0.0000 8.1500 18.840 5.8600 +722 "J41" 100.00 10.196 100.00 0.0000 7.5400 16.690 5.5300 +723 "Z46" 100.00 20.000 100.00 0.0000 6.9300 14.620 5.1500 +724 "L36" 100.00 29.804 100.00 0.0000 6.3300 12.660 4.7900 +725 "M20" 100.00 40.000 100.00 0.0000 5.7800 10.860 4.4300 +726 "E1" 100.00 54.902 100.00 0.0000 5.0700 8.4400 3.8400 +727 "R1" 100.00 70.196 100.00 0.0000 4.4600 6.4700 3.2900 +728 "N48" 100.00 85.098 100.00 0.0000 4.0000 5.0000 2.7700 +729 "K6" 100.00 100.00 100.00 0.0000 3.6300 3.8400 2.3100 +730 "AA40" 0.0000 0.0000 0.0000 20.000 58.360 60.610 53.170 +731 "D43" 0.0000 10.196 0.0000 20.000 53.610 53.700 48.160 +732 "E29" 0.0000 20.000 0.0000 20.000 48.890 47.040 42.950 +733 "D54" 0.0000 40.000 0.0000 20.000 40.030 34.580 32.840 +734 "F49" 0.0000 70.196 0.0000 20.000 29.450 20.200 19.890 +735 "B5" 0.0000 100.00 0.0000 20.000 23.380 12.170 11.950 +736 "U31" 10.196 0.0000 0.0000 20.000 52.670 55.730 51.910 +737 "V38" 10.196 10.196 0.0000 20.000 48.300 49.300 47.050 +738 "P46" 10.196 20.000 0.0000 20.000 44.110 43.230 41.990 +739 "X29" 10.196 40.000 0.0000 20.000 36.000 31.630 32.080 +740 "J7" 10.196 70.196 0.0000 20.000 26.440 18.410 19.540 +741 "M31" 10.196 100.00 0.0000 20.000 20.820 10.960 11.830 +742 "P35" 20.000 0.0000 0.0000 20.000 46.630 50.470 50.570 +743 "B25" 20.000 10.196 0.0000 20.000 42.900 44.780 45.930 +744 "U25" 20.000 20.000 0.0000 20.000 39.260 39.330 41.130 +745 "N32" 20.000 40.000 0.0000 20.000 31.990 28.770 31.460 +746 "H39" 20.000 70.196 0.0000 20.000 23.430 16.720 19.310 +747 "Z22" 20.000 100.00 0.0000 20.000 18.200 9.7200 11.760 +748 "Q7" 40.000 0.0000 0.0000 20.000 34.990 40.080 47.760 +749 "L37" 40.000 10.196 0.0000 20.000 32.140 35.530 43.290 +750 "S28" 40.000 20.000 0.0000 20.000 29.450 31.220 38.810 +751 "T9" 40.000 40.000 0.0000 20.000 24.220 23.070 30.090 +752 "F16" 40.000 70.196 0.0000 20.000 17.690 13.370 18.770 +753 "M5" 40.000 100.00 0.0000 20.000 13.490 7.5300 11.800 +754 "S47" 70.196 0.0000 0.0000 20.000 21.560 27.570 43.680 +755 "M49" 70.196 10.196 0.0000 20.000 19.770 24.420 39.700 +756 "Z20" 70.196 20.000 0.0000 20.000 18.090 21.460 35.760 +757 "T23" 70.196 40.000 0.0000 20.000 14.830 15.810 27.910 +758 "H38" 70.196 70.196 0.0000 20.000 10.900 9.2900 18.290 +759 "X22" 70.196 100.00 0.0000 20.000 8.1500 5.0700 11.840 +760 "C5" 100.00 0.0000 0.0000 20.000 12.040 18.260 40.230 +761 "X23" 100.00 10.196 0.0000 20.000 11.030 16.160 36.800 +762 "F12" 100.00 20.000 0.0000 20.000 10.110 14.230 33.300 +763 "F2" 100.00 40.000 0.0000 20.000 8.3400 10.560 26.580 +764 "J38" 100.00 70.196 0.0000 20.000 6.0600 6.1100 17.550 +765 "P17" 100.00 100.00 0.0000 20.000 4.6900 3.5400 12.120 +766 "H19" 0.0000 0.0000 10.196 20.000 56.880 59.490 46.150 +767 "E33" 0.0000 10.196 10.196 20.000 52.190 52.650 41.710 +768 "P3" 0.0000 20.000 10.196 20.000 47.710 46.180 37.310 +769 "X18" 0.0000 40.000 10.196 20.000 39.230 34.050 28.660 +770 "M25" 0.0000 70.196 10.196 20.000 29.010 19.950 17.420 +771 "R52" 0.0000 100.00 10.196 20.000 23.130 12.140 10.560 +772 "Q47" 10.196 0.0000 10.196 20.000 51.250 54.670 45.100 +773 "H26" 10.196 10.196 10.196 20.000 47.100 48.480 40.930 +774 "K26" 10.196 20.000 10.196 20.000 43.060 42.530 36.640 +775 "AA42" 10.196 40.000 10.196 20.000 35.260 31.210 28.100 +776 "B6" 10.196 70.196 10.196 20.000 26.070 18.250 17.170 +777 "X33" 10.196 100.00 10.196 20.000 20.700 11.000 10.520 +778 "Q28" 20.000 0.0000 10.196 20.000 45.440 49.660 44.000 +779 "R13" 20.000 10.196 10.196 20.000 41.760 44.000 39.930 +780 "F35" 20.000 20.000 10.196 20.000 38.190 38.630 35.730 +781 "L4" 20.000 40.000 10.196 20.000 31.230 28.300 27.390 +782 "AA8" 20.000 70.196 10.196 20.000 23.050 16.570 16.940 +783 "S32" 20.000 100.00 10.196 20.000 18.110 9.7800 10.400 +784 "N6" 40.000 0.0000 10.196 20.000 34.000 39.460 41.260 +785 "J19" 40.000 10.196 10.196 20.000 31.250 34.980 37.540 +786 "T38" 40.000 20.000 10.196 20.000 28.630 30.760 33.670 +787 "X54" 40.000 40.000 10.196 20.000 23.490 22.650 26.130 +788 "W27" 40.000 70.196 10.196 20.000 17.270 13.220 16.460 +789 "V47" 40.000 100.00 10.196 20.000 13.270 7.5000 10.250 +790 "R36" 70.196 0.0000 10.196 20.000 20.640 27.060 37.720 +791 "E43" 70.196 10.196 10.196 20.000 18.930 23.970 34.350 +792 "B46" 70.196 20.000 10.196 20.000 17.350 21.100 31.070 +793 "G22" 70.196 40.000 10.196 20.000 14.290 15.580 24.370 +794 "T52" 70.196 70.196 10.196 20.000 10.520 9.1300 15.900 +795 "V17" 70.196 100.00 10.196 20.000 8.0400 5.1100 10.360 +796 "V55" 100.00 0.0000 10.196 20.000 11.150 17.780 34.660 +797 "I31" 100.00 10.196 10.196 20.000 10.300 15.850 31.840 +798 "Y27" 100.00 20.000 10.196 20.000 9.3900 13.900 28.800 +799 "C10" 100.00 40.000 10.196 20.000 7.7200 10.240 22.890 +800 "D17" 100.00 70.196 10.196 20.000 5.7400 6.0400 15.400 +801 "S2" 100.00 100.00 10.196 20.000 4.5000 3.5400 10.610 +802 "E10" 0.0000 0.0000 20.000 20.000 55.510 58.460 39.620 +803 "Z41" 0.0000 10.196 20.000 20.000 50.960 51.730 35.790 +804 "M33" 0.0000 20.000 20.000 20.000 46.740 45.520 32.240 +805 "H7" 0.0000 40.000 20.000 20.000 38.550 33.640 24.800 +806 "Z49" 0.0000 70.196 20.000 20.000 28.750 19.910 15.170 +807 "Y34" 0.0000 100.00 20.000 20.000 22.950 12.130 9.1700 +808 "F50" 10.196 0.0000 20.000 20.000 49.930 53.690 38.630 +809 "T50" 10.196 10.196 20.000 20.000 45.960 47.660 35.140 +810 "B15" 10.196 20.000 20.000 20.000 42.160 41.940 31.690 +811 "T15" 10.196 40.000 20.000 20.000 34.670 30.910 24.370 +812 "M14" 10.196 70.196 20.000 20.000 25.720 18.140 14.980 +813 "G28" 10.196 100.00 20.000 20.000 20.500 10.980 9.1400 +814 "F54" 20.000 0.0000 20.000 20.000 44.240 48.780 37.640 +815 "J57" 20.000 10.196 20.000 20.000 40.710 43.270 34.210 +816 "L45" 20.000 20.000 20.000 20.000 37.360 38.130 30.870 +817 "V19" 20.000 40.000 20.000 20.000 30.580 27.960 23.650 +818 "Y31" 20.000 70.196 20.000 20.000 22.700 16.440 14.690 +819 "D15" 20.000 100.00 20.000 20.000 17.930 9.7800 9.0200 +820 "I8" 40.000 0.0000 20.000 20.000 32.900 38.690 35.070 +821 "A30" 40.000 10.196 20.000 20.000 30.310 34.340 31.940 +822 "M46" 40.000 20.000 20.000 20.000 27.800 30.230 28.830 +823 "L14" 40.000 40.000 20.000 20.000 22.920 22.350 22.560 +824 "U16" 40.000 70.196 20.000 20.000 16.870 13.030 14.140 +825 "J9" 40.000 100.00 20.000 20.000 13.160 7.5500 8.8800 +826 "G40" 70.196 0.0000 20.000 20.000 19.890 26.630 32.070 +827 "B26" 70.196 10.196 20.000 20.000 18.280 23.600 29.330 +828 "Z34" 70.196 20.000 20.000 20.000 16.800 20.840 26.720 +829 "X2" 70.196 40.000 20.000 20.000 13.770 15.330 20.890 +830 "F52" 70.196 70.196 20.000 20.000 10.250 9.0300 13.770 +831 "C30" 70.196 100.00 20.000 20.000 7.8800 5.1400 9.0600 +832 "W6" 100.00 0.0000 20.000 20.000 10.390 17.380 29.450 +833 "I28" 100.00 10.196 20.000 20.000 9.5600 15.440 27.010 +834 "U40" 100.00 20.000 20.000 20.000 8.7700 13.580 24.530 +835 "N15" 100.00 40.000 20.000 20.000 7.2200 9.9900 19.600 +836 "T3" 100.00 70.196 20.000 20.000 5.4100 5.9200 13.300 +837 "Y4" 100.00 100.00 20.000 20.000 4.2800 3.5200 9.1000 +838 "I46" 0.0000 0.0000 40.000 20.000 52.880 56.400 27.260 +839 "D16" 0.0000 10.196 40.000 20.000 48.700 50.030 24.750 +840 "L1" 0.0000 20.000 40.000 20.000 44.640 43.970 22.250 +841 "AA38" 0.0000 40.000 40.000 20.000 37.100 32.640 17.260 +842 "U51" 0.0000 70.196 40.000 20.000 27.990 19.520 10.690 +843 "X15" 0.0000 100.00 40.000 20.000 22.690 12.240 6.6600 +844 "F44" 10.196 0.0000 40.000 20.000 47.390 51.700 26.810 +845 "L49" 10.196 10.196 40.000 20.000 43.700 45.940 24.510 +846 "G45" 10.196 20.000 40.000 20.000 40.200 40.510 22.100 +847 "D51" 10.196 40.000 40.000 20.000 33.150 29.870 16.970 +848 "B8" 10.196 70.196 40.000 20.000 25.010 17.910 10.620 +849 "Z51" 10.196 100.00 40.000 20.000 20.260 11.120 6.6900 +850 "H18" 20.000 0.0000 40.000 20.000 41.770 46.840 26.030 +851 "J52" 20.000 10.196 40.000 20.000 38.570 41.670 23.900 +852 "R48" 20.000 20.000 40.000 20.000 35.500 36.770 21.500 +853 "X47" 20.000 40.000 40.000 20.000 29.090 26.920 16.440 +854 "R22" 20.000 70.196 40.000 20.000 21.980 16.200 10.430 +855 "X3" 20.000 100.00 40.000 20.000 17.710 9.9100 6.6500 +856 "F42" 40.000 0.0000 40.000 20.000 31.120 37.440 24.580 +857 "M38" 40.000 10.196 40.000 20.000 28.550 33.110 22.510 +858 "E14" 40.000 20.000 40.000 20.000 26.350 29.270 20.340 +859 "O57" 40.000 40.000 40.000 20.000 21.530 21.410 15.830 +860 "K43" 40.000 70.196 40.000 20.000 16.100 12.750 10.160 +861 "Z29" 40.000 100.00 40.000 20.000 12.980 7.6800 6.5500 +862 "U12" 70.196 0.0000 40.000 20.000 18.220 25.500 22.460 +863 "X8" 70.196 10.196 40.000 20.000 16.710 22.500 20.530 +864 "C54" 70.196 20.000 40.000 20.000 15.280 19.750 18.590 +865 "A45" 70.196 40.000 40.000 20.000 12.400 14.380 14.520 +866 "D33" 70.196 70.196 40.000 20.000 9.6100 8.8200 9.8600 +867 "T11" 70.196 100.00 40.000 20.000 7.6300 5.2000 6.6300 +868 "R10" 100.00 0.0000 40.000 20.000 9.0700 16.580 20.850 +869 "N36" 100.00 10.196 40.000 20.000 8.3600 14.700 19.230 +870 "R23" 100.00 20.000 40.000 20.000 7.6900 12.940 17.480 +871 "E32" 100.00 40.000 40.000 20.000 6.3600 9.5600 13.950 +872 "K11" 100.00 70.196 40.000 20.000 4.8700 5.7800 9.4800 +873 "D9" 100.00 100.00 40.000 20.000 3.9100 3.4700 6.5600 +874 "P33" 0.0000 0.0000 70.196 20.000 49.570 53.360 12.660 +875 "AA2" 0.0000 10.196 70.196 20.000 45.630 47.300 11.410 +876 "A41" 0.0000 20.000 70.196 20.000 41.930 41.640 10.340 +877 "T29" 0.0000 40.000 70.196 20.000 34.960 30.980 8.0400 +878 "I26" 0.0000 70.196 70.196 20.000 26.980 19.010 5.3600 +879 "H54" 0.0000 100.00 70.196 20.000 22.180 12.160 3.5200 +880 "O49" 10.196 0.0000 70.196 20.000 44.230 48.860 12.400 +881 "B16" 10.196 10.196 70.196 20.000 40.810 43.440 11.330 +882 "E38" 10.196 20.000 70.196 20.000 37.540 38.270 10.270 +883 "I50" 10.196 40.000 70.196 20.000 31.320 28.550 8.0400 +884 "P10" 10.196 70.196 70.196 20.000 24.050 17.420 5.3200 +885 "I43" 10.196 100.00 70.196 20.000 19.800 11.070 3.5500 +886 "W57" 20.000 0.0000 70.196 20.000 38.780 44.170 12.050 +887 "W50" 20.000 10.196 70.196 20.000 35.800 39.280 11.080 +888 "AA57" 20.000 20.000 70.196 20.000 32.940 34.620 9.9900 +889 "N35" 20.000 40.000 70.196 20.000 27.400 25.720 7.8500 +890 "H15" 20.000 70.196 70.196 20.000 21.110 15.740 5.1900 +891 "F28" 20.000 100.00 70.196 20.000 17.340 9.9100 3.5300 +892 "W22" 40.000 0.0000 70.196 20.000 28.430 35.030 11.550 +893 "O29" 40.000 10.196 70.196 20.000 26.060 31.010 10.480 +894 "D55" 40.000 20.000 70.196 20.000 24.080 27.430 9.6200 +895 "F31" 40.000 40.000 70.196 20.000 20.060 20.410 7.5400 +896 "Y50" 40.000 70.196 70.196 20.000 15.440 12.420 5.1000 +897 "N20" 40.000 100.00 70.196 20.000 12.700 7.6900 3.5200 +898 "X19" 70.196 0.0000 70.196 20.000 16.050 23.620 10.700 +899 "Q48" 70.196 10.196 70.196 20.000 14.770 20.950 9.8000 +900 "M6" 70.196 20.000 70.196 20.000 13.560 18.450 9.0300 +901 "B27" 70.196 40.000 70.196 20.000 11.410 13.790 7.2800 +902 "H51" 70.196 70.196 70.196 20.000 8.8300 8.4300 4.9700 +903 "Y32" 70.196 100.00 70.196 20.000 7.2300 5.1400 3.6000 +904 "V20" 100.00 0.0000 70.196 20.000 7.3300 15.200 10.370 +905 "AA11" 100.00 10.196 70.196 20.000 6.7500 13.480 9.4800 +906 "J16" 100.00 20.000 70.196 20.000 6.2500 11.920 8.6400 +907 "P36" 100.00 40.000 70.196 20.000 5.2300 8.8200 6.9600 +908 "E6" 100.00 70.196 70.196 20.000 4.1700 5.4400 4.9700 +909 "S49" 100.00 100.00 70.196 20.000 3.4700 3.4000 3.6200 +910 "O27" 0.0000 0.0000 100.00 20.000 47.580 51.210 4.9300 +911 "V14" 0.0000 10.196 100.00 20.000 43.710 45.260 4.4100 +912 "X41" 0.0000 20.000 100.00 20.000 40.190 39.890 4.0700 +913 "M9" 0.0000 40.000 100.00 20.000 33.690 29.870 3.3200 +914 "D41" 0.0000 70.196 100.00 20.000 26.080 18.400 2.3900 +915 "Q17" 0.0000 100.00 100.00 20.000 21.830 12.070 1.8800 +916 "X35" 10.196 0.0000 100.00 20.000 42.290 46.780 4.8300 +917 "K40" 10.196 10.196 100.00 20.000 39.070 41.650 4.3700 +918 "W54" 10.196 20.000 100.00 20.000 36.010 36.770 4.0400 +919 "R12" 10.196 40.000 100.00 20.000 30.270 27.640 3.2800 +920 "Y22" 10.196 70.196 100.00 20.000 23.500 17.090 2.4000 +921 "AA17" 10.196 100.00 100.00 20.000 19.450 10.960 1.9100 +922 "I22" 20.000 0.0000 100.00 20.000 37.000 42.250 4.6800 +923 "L3" 20.000 10.196 100.00 20.000 34.220 37.660 4.2800 +924 "O54" 20.000 20.000 100.00 20.000 31.710 33.400 3.9500 +925 "K10" 20.000 40.000 100.00 20.000 26.660 25.130 3.2300 +926 "F11" 20.000 70.196 100.00 20.000 20.670 15.470 2.3800 +927 "S30" 20.000 100.00 100.00 20.000 17.060 9.8400 1.9200 +928 "B32" 40.000 0.0000 100.00 20.000 26.840 33.370 4.5700 +929 "P19" 40.000 10.196 100.00 20.000 25.020 29.910 4.1600 +930 "B28" 40.000 20.000 100.00 20.000 23.240 26.630 3.8600 +931 "D14" 40.000 40.000 100.00 20.000 19.630 20.050 3.1900 +932 "P55" 40.000 70.196 100.00 20.000 15.250 12.380 2.4100 +933 "Q25" 40.000 100.00 100.00 20.000 12.380 7.6200 1.9400 +934 "V29" 70.196 0.0000 100.00 20.000 15.020 22.560 4.4700 +935 "X36" 70.196 10.196 100.00 20.000 14.080 20.300 4.1600 +936 "C47" 70.196 20.000 100.00 20.000 12.930 17.920 3.8800 +937 "P40" 70.196 40.000 100.00 20.000 11.220 13.740 3.2600 +938 "K53" 70.196 70.196 100.00 20.000 8.6700 8.4500 2.4900 +939 "X20" 70.196 100.00 100.00 20.000 6.9400 5.0900 2.0100 +940 "H10" 100.00 0.0000 100.00 20.000 6.2800 14.140 4.6600 +941 "G33" 100.00 10.196 100.00 20.000 5.8900 12.680 4.4000 +942 "Y23" 100.00 20.000 100.00 20.000 5.4800 11.230 4.1000 +943 "X45" 100.00 40.000 100.00 20.000 4.6900 8.4700 3.5000 +944 "P24" 100.00 70.196 100.00 20.000 3.8000 5.3200 2.6800 +945 "O4" 100.00 100.00 100.00 20.000 3.2300 3.3700 2.0200 +946 "I18" 0.0000 0.0000 0.0000 40.000 35.870 37.330 32.950 +947 "Y26" 0.0000 20.000 0.0000 40.000 30.230 29.160 26.800 +948 "V25" 0.0000 40.000 0.0000 40.000 25.150 21.930 20.960 +949 "AA36" 0.0000 70.196 0.0000 40.000 18.700 13.060 12.860 +950 "U27" 0.0000 100.00 0.0000 40.000 14.740 7.8300 7.6100 +951 "D35" 20.000 0.0000 0.0000 40.000 28.670 31.130 31.220 +952 "V45" 20.000 20.000 0.0000 40.000 24.270 24.370 25.550 +953 "W35" 20.000 40.000 0.0000 40.000 20.090 18.170 19.960 +954 "O53" 20.000 70.196 0.0000 40.000 14.900 10.780 12.360 +955 "AA49" 20.000 100.00 0.0000 40.000 11.690 6.3800 7.4900 +956 "P42" 40.000 0.0000 0.0000 40.000 21.620 24.850 29.390 +957 "M1" 40.000 20.000 0.0000 40.000 18.280 19.370 23.990 +958 "K21" 40.000 40.000 0.0000 40.000 15.230 14.510 18.930 +959 "K7" 40.000 70.196 0.0000 40.000 11.320 8.6100 11.900 +960 "T24" 40.000 100.00 0.0000 40.000 8.9100 5.1000 7.5200 +961 "C16" 70.196 0.0000 0.0000 40.000 13.500 17.280 26.810 +962 "E56" 70.196 20.000 0.0000 40.000 11.420 13.450 22.040 +963 "D32" 70.196 40.000 0.0000 40.000 9.5400 10.080 17.500 +964 "L21" 70.196 70.196 0.0000 40.000 7.2400 6.1400 11.590 +965 "AA7" 70.196 100.00 0.0000 40.000 5.6800 3.6200 7.5800 +966 "M22" 100.00 0.0000 0.0000 40.000 7.6800 11.640 24.760 +967 "I2" 100.00 20.000 0.0000 40.000 6.5800 9.1700 20.590 +968 "N9" 100.00 40.000 0.0000 40.000 5.6100 7.0000 16.710 +969 "V24" 100.00 70.196 0.0000 40.000 4.3200 4.3200 11.260 +970 "Z9" 100.00 100.00 0.0000 40.000 3.4900 2.6700 7.8400 +971 "I32" 0.0000 0.0000 20.000 40.000 34.040 35.930 24.620 +972 "V40" 0.0000 20.000 20.000 40.000 28.830 28.170 20.180 +973 "V27" 0.0000 40.000 20.000 40.000 24.160 21.290 15.890 +974 "C43" 0.0000 70.196 20.000 40.000 18.230 12.870 9.8700 +975 "G21" 0.0000 100.00 20.000 40.000 14.540 7.8700 5.9600 +976 "K36" 20.000 0.0000 20.000 40.000 27.120 30.010 23.330 +977 "V23" 20.000 20.000 20.000 40.000 23.040 23.560 19.260 +978 "Z5" 20.000 40.000 20.000 40.000 19.150 17.630 15.090 +979 "Z44" 20.000 70.196 20.000 40.000 14.420 10.600 9.4800 +980 "Q27" 20.000 100.00 20.000 40.000 11.510 6.4500 5.8500 +981 "E4" 40.000 0.0000 20.000 40.000 20.240 23.890 21.670 +982 "H5" 40.000 20.000 20.000 40.000 17.200 18.700 17.940 +983 "R8" 40.000 40.000 20.000 40.000 14.380 14.040 14.320 +984 "G5" 40.000 70.196 20.000 40.000 10.790 8.4100 9.0500 +985 "R2" 40.000 100.00 20.000 40.000 8.6400 5.1000 5.7400 +986 "C49" 70.196 0.0000 20.000 40.000 12.450 16.670 19.850 +987 "W4" 70.196 20.000 20.000 40.000 10.620 13.080 16.640 +988 "U37" 70.196 40.000 20.000 40.000 8.8900 9.8000 13.270 +989 "S45" 70.196 70.196 20.000 40.000 6.8300 6.0200 8.8200 +990 "C27" 70.196 100.00 20.000 40.000 5.4700 3.6600 5.8600 +991 "L5" 100.00 0.0000 20.000 40.000 6.7400 11.210 18.420 +992 "P1" 100.00 20.000 20.000 40.000 5.8200 8.8600 15.430 +993 "O13" 100.00 40.000 20.000 40.000 4.9700 6.7300 12.540 +994 "W28" 100.00 70.196 20.000 40.000 3.9300 4.2600 8.6600 +995 "V28" 100.00 100.00 20.000 40.000 3.2600 2.7100 5.9900 +996 "M41" 0.0000 0.0000 40.000 40.000 32.280 34.540 16.750 +997 "P7" 0.0000 20.000 40.000 40.000 27.410 27.100 13.780 +998 "W3" 0.0000 40.000 40.000 40.000 23.150 20.570 10.940 +999 "W8" 0.0000 70.196 40.000 40.000 17.680 12.600 6.9200 +1000 "AA47" 0.0000 100.00 40.000 40.000 14.440 8.0200 4.3800 +1001 "M21" 20.000 0.0000 40.000 40.000 25.450 28.700 15.960 +1002 "S1" 20.000 20.000 40.000 40.000 21.770 22.640 13.300 +1003 "O2" 20.000 40.000 40.000 40.000 18.130 16.910 10.410 +1004 "Q11" 20.000 70.196 40.000 40.000 13.910 10.440 6.7100 +1005 "G46" 20.000 100.00 40.000 40.000 11.360 6.5700 4.3400 +1006 "N11" 40.000 0.0000 40.000 40.000 19.000 23.010 15.060 +1007 "K17" 40.000 20.000 40.000 40.000 16.200 18.030 12.580 +1008 "C14" 40.000 40.000 40.000 40.000 13.450 13.420 10.020 +1009 "F14" 40.000 70.196 40.000 40.000 10.260 8.2300 6.4900 +1010 "Z57" 40.000 100.00 40.000 40.000 8.4500 5.1800 4.2300 +1011 "R56" 70.196 0.0000 40.000 40.000 11.350 15.930 13.860 +1012 "M4" 70.196 20.000 40.000 40.000 9.6500 12.410 11.580 +1013 "W26" 70.196 40.000 40.000 40.000 8.0300 9.2500 9.2600 +1014 "B20" 70.196 70.196 40.000 40.000 6.4100 5.9200 6.3300 +1015 "E53" 70.196 100.00 40.000 40.000 5.2600 3.7100 4.2900 +1016 "G37" 100.00 0.0000 40.000 40.000 5.9700 10.810 13.150 +1017 "V22" 100.00 20.000 40.000 40.000 5.1800 8.5500 11.090 +1018 "I44" 100.00 40.000 40.000 40.000 4.4400 6.5300 9.0000 +1019 "L11" 100.00 70.196 40.000 40.000 3.6000 4.2200 6.2100 +1020 "T28" 100.00 100.00 40.000 40.000 3.0500 2.7500 4.3500 +1021 "Y17" 0.0000 0.0000 70.196 40.000 30.110 32.530 7.8400 +1022 "X43" 0.0000 20.000 70.196 40.000 25.640 25.540 6.4900 +1023 "G1" 0.0000 40.000 70.196 40.000 21.710 19.410 5.2000 +1024 "H34" 0.0000 70.196 70.196 40.000 17.020 12.270 3.6000 +1025 "S12" 0.0000 100.00 70.196 40.000 14.160 8.0600 2.4500 +1026 "Y37" 20.000 0.0000 70.196 40.000 23.490 26.940 7.4500 +1027 "W5" 20.000 20.000 70.196 40.000 20.010 21.120 6.2700 +1028 "P11" 20.000 40.000 70.196 40.000 16.810 15.890 5.0900 +1029 "B43" 20.000 70.196 70.196 40.000 13.190 10.030 3.4900 +1030 "K31" 20.000 100.00 70.196 40.000 11.110 6.6100 2.4300 +1031 "R20" 40.000 0.0000 70.196 40.000 17.210 21.410 7.1200 +1032 "F24" 40.000 20.000 70.196 40.000 14.550 16.640 6.0500 +1033 "Q9" 40.000 40.000 70.196 40.000 12.140 12.370 4.9300 +1034 "B11" 40.000 70.196 70.196 40.000 9.5800 7.8100 3.4200 +1035 "N3" 40.000 100.00 70.196 40.000 8.2200 5.2100 2.4000 +1036 "C21" 70.196 0.0000 70.196 40.000 9.9300 14.690 6.6700 +1037 "N18" 70.196 20.000 70.196 40.000 8.4300 11.430 5.7400 +1038 "Y7" 70.196 40.000 70.196 40.000 7.1600 8.5900 4.7900 +1039 "AA25" 70.196 70.196 70.196 40.000 5.7600 5.5200 3.3600 +1040 "J24" 70.196 100.00 70.196 40.000 4.9800 3.6900 2.4600 +1041 "L8" 100.00 0.0000 70.196 40.000 4.9200 10.010 6.6400 +1042 "J45" 100.00 20.000 70.196 40.000 4.2900 7.9400 5.6100 +1043 "AA12" 100.00 40.000 70.196 40.000 3.7200 6.0500 4.6300 +1044 "N21" 100.00 70.196 70.196 40.000 3.1500 3.9800 3.4100 +1045 "M17" 100.00 100.00 70.196 40.000 2.7800 2.7200 2.5600 +1046 "R44" 0.0000 0.0000 100.00 40.000 28.850 31.210 3.3700 +1047 "F45" 0.0000 20.000 100.00 40.000 24.550 24.460 2.8400 +1048 "S54" 0.0000 40.000 100.00 40.000 20.890 18.660 2.4100 +1049 "R26" 0.0000 70.196 100.00 40.000 16.460 11.890 1.8600 +1050 "K57" 0.0000 100.00 100.00 40.000 13.900 8.0200 1.5400 +1051 "L54" 20.000 0.0000 100.00 40.000 22.420 25.850 3.2100 +1052 "M30" 20.000 20.000 100.00 40.000 19.130 20.270 2.7800 +1053 "H49" 20.000 40.000 100.00 40.000 16.050 15.200 2.3700 +1054 "V57" 20.000 70.196 100.00 40.000 12.710 9.6900 1.8500 +1055 "J35" 20.000 100.00 100.00 40.000 10.910 6.5800 1.5400 +1056 "O14" 40.000 0.0000 100.00 40.000 16.250 20.490 3.1400 +1057 "S41" 40.000 20.000 100.00 40.000 13.770 15.900 2.7300 +1058 "U50" 40.000 40.000 100.00 40.000 11.340 11.590 2.3700 +1059 "J36" 40.000 70.196 100.00 40.000 9.1100 7.4700 1.8700 +1060 "M45" 40.000 100.00 100.00 40.000 8.0200 5.1700 1.5300 +1061 "K3" 70.196 0.0000 100.00 40.000 9.2500 13.990 3.1100 +1062 "D28" 70.196 20.000 100.00 40.000 7.8100 10.790 2.7700 +1063 "F38" 70.196 40.000 100.00 40.000 6.6300 8.0500 2.4100 +1064 "Y25" 70.196 70.196 100.00 40.000 5.4400 5.2900 1.9100 +1065 "H4" 70.196 100.00 100.00 40.000 4.8200 3.6700 1.5700 +1066 "C57" 100.00 0.0000 100.00 40.000 4.2300 9.1900 3.3200 +1067 "AA35" 100.00 20.000 100.00 40.000 3.7700 7.3600 2.9300 +1068 "X21" 100.00 40.000 100.00 40.000 3.3300 5.7000 2.5400 +1069 "S15" 100.00 70.196 100.00 40.000 2.8800 3.8500 2.0300 +1070 "P28" 100.00 100.00 100.00 40.000 2.6300 2.7000 1.6000 +1071 "P13" 0.0000 0.0000 0.0000 60.000 18.920 19.770 17.150 +1072 "A32" 0.0000 20.000 0.0000 60.000 16.340 15.770 14.120 +1073 "V42" 0.0000 40.000 0.0000 60.000 14.000 12.220 11.280 +1074 "P21" 0.0000 70.196 0.0000 60.000 10.700 7.6400 7.1400 +1075 "Y47" 0.0000 100.00 0.0000 60.000 8.5700 4.8000 4.3800 +1076 "U6" 20.000 0.0000 0.0000 60.000 15.460 16.840 16.420 +1077 "J33" 20.000 20.000 0.0000 60.000 13.250 13.290 13.530 +1078 "Q39" 20.000 40.000 0.0000 60.000 11.130 10.030 10.730 +1079 "T49" 20.000 70.196 0.0000 60.000 8.5400 6.2900 6.8500 +1080 "F27" 20.000 100.00 0.0000 60.000 7.0000 4.0600 4.3400 +1081 "B13" 40.000 0.0000 0.0000 60.000 11.910 13.740 15.620 +1082 "A40" 40.000 20.000 0.0000 60.000 10.010 10.570 12.760 +1083 "Q55" 40.000 40.000 0.0000 60.000 8.2800 7.8100 10.130 +1084 "N52" 40.000 70.196 0.0000 60.000 6.4600 4.9900 6.5800 +1085 "G35" 40.000 100.00 0.0000 60.000 5.5100 3.3800 4.3900 +1086 "K28" 70.196 0.0000 0.0000 60.000 7.7000 9.8600 14.380 +1087 "D27" 70.196 20.000 0.0000 60.000 6.4400 7.5200 11.770 +1088 "W7" 70.196 40.000 0.0000 60.000 5.3600 5.5600 9.3700 +1089 "X11" 70.196 70.196 0.0000 60.000 4.3900 3.7500 6.4300 +1090 "U56" 70.196 100.00 0.0000 60.000 3.8400 2.6400 4.4700 +1091 "M19" 100.00 0.0000 0.0000 60.000 4.5500 6.8200 13.210 +1092 "U53" 100.00 20.000 0.0000 60.000 3.9900 5.4500 11.020 +1093 "L24" 100.00 40.000 0.0000 60.000 3.5100 4.2900 9.0400 +1094 "V33" 100.00 70.196 0.0000 60.000 3.0100 3.0000 6.3500 +1095 "T1" 100.00 100.00 0.0000 60.000 2.7600 2.2300 4.6700 +1096 "Z52" 0.0000 0.0000 20.000 60.000 17.650 18.660 13.040 +1097 "A57" 0.0000 20.000 20.000 60.000 15.210 14.850 10.820 +1098 "B33" 0.0000 40.000 20.000 60.000 13.040 11.490 8.7000 +1099 "V4" 0.0000 70.196 20.000 60.000 10.150 7.3400 5.6000 +1100 "W34" 0.0000 100.00 20.000 60.000 8.3400 4.8000 3.5600 +1101 "X51" 20.000 0.0000 20.000 60.000 14.260 15.790 12.420 +1102 "AA20" 20.000 20.000 20.000 60.000 12.250 12.490 10.350 +1103 "B23" 20.000 40.000 20.000 60.000 10.350 9.4800 8.2400 +1104 "N46" 20.000 70.196 20.000 60.000 8.1000 6.0800 5.3800 +1105 "J10" 20.000 100.00 20.000 60.000 6.8100 4.0700 3.5100 +1106 "C18" 40.000 0.0000 20.000 60.000 10.760 12.700 11.610 +1107 "X26" 40.000 20.000 20.000 60.000 9.1600 9.9100 9.6500 +1108 "D1" 40.000 40.000 20.000 60.000 7.7100 7.4500 7.8000 +1109 "N29" 40.000 70.196 20.000 60.000 6.1100 4.8400 5.1200 +1110 "AA16" 40.000 100.00 20.000 60.000 5.2900 3.3500 3.4600 +1111 "M24" 70.196 0.0000 20.000 60.000 6.8300 9.0700 10.680 +1112 "V53" 70.196 20.000 20.000 60.000 5.8600 7.0900 8.9800 +1113 "O8" 70.196 40.000 20.000 60.000 4.9700 5.3400 7.2400 +1114 "J6" 70.196 70.196 20.000 60.000 4.1400 3.6600 5.0200 +1115 "N26" 70.196 100.00 20.000 60.000 3.6600 2.6300 3.5500 +1116 "K22" 100.00 0.0000 20.000 60.000 3.9700 6.3600 9.9000 +1117 "Z28" 100.00 20.000 20.000 60.000 3.5300 5.1400 8.3700 +1118 "V35" 100.00 40.000 20.000 60.000 3.1500 4.0600 6.9300 +1119 "E48" 100.00 70.196 20.000 60.000 2.7900 2.9300 5.0100 +1120 "E46" 100.00 100.00 20.000 60.000 2.5900 2.2200 3.6500 +1121 "O16" 0.0000 0.0000 40.000 60.000 16.280 17.460 8.9900 +1122 "D8" 0.0000 20.000 40.000 60.000 13.940 13.790 7.4700 +1123 "F32" 0.0000 40.000 40.000 60.000 11.950 10.650 6.0700 +1124 "T17" 0.0000 70.196 40.000 60.000 9.4900 6.9600 4.0200 +1125 "O52" 0.0000 100.00 40.000 60.000 8.1200 4.8400 2.7200 +1126 "B17" 20.000 0.0000 40.000 60.000 12.910 14.570 8.5600 +1127 "I27" 20.000 20.000 40.000 60.000 11.160 11.580 7.2100 +1128 "Y41" 20.000 40.000 40.000 60.000 9.4600 8.8000 5.7600 +1129 "Y19" 20.000 70.196 40.000 60.000 7.6000 5.8500 3.8800 +1130 "U38" 20.000 100.00 40.000 60.000 6.5900 4.1000 2.6900 +1131 "A53" 40.000 0.0000 40.000 60.000 9.6500 11.690 8.0600 +1132 "K33" 40.000 20.000 40.000 60.000 8.3300 9.2300 6.8000 +1133 "U44" 40.000 40.000 40.000 60.000 7.0700 6.9900 5.5300 +1134 "H35" 40.000 70.196 40.000 60.000 5.7300 4.6900 3.7500 +1135 "AA14" 40.000 100.00 40.000 60.000 5.0800 3.3600 2.6300 +1136 "S35" 70.196 0.0000 40.000 60.000 5.9400 8.2400 7.4200 +1137 "H8" 70.196 20.000 40.000 60.000 5.1800 6.5200 6.2800 +1138 "O1" 70.196 40.000 40.000 60.000 4.4900 5.0100 5.1400 +1139 "AA39" 70.196 70.196 40.000 60.000 3.8800 3.5800 3.6700 +1140 "AA54" 70.196 100.00 40.000 60.000 3.4600 2.6100 2.6500 +1141 "F30" 100.00 0.0000 40.000 60.000 3.4600 5.9400 7.0600 +1142 "S51" 100.00 20.000 40.000 60.000 3.1400 4.8400 6.0700 +1143 "T8" 100.00 40.000 40.000 60.000 2.8500 3.8800 5.0700 +1144 "L2" 100.00 70.196 40.000 60.000 2.5700 2.8600 3.6700 +1145 "L13" 100.00 100.00 40.000 60.000 2.4100 2.1900 2.7000 +1146 "Y43" 0.0000 0.0000 70.196 60.000 14.740 16.020 4.3100 +1147 "C41" 0.0000 20.000 70.196 60.000 12.580 12.610 3.6000 +1148 "O7" 0.0000 40.000 70.196 60.000 10.780 9.7300 2.9400 +1149 "K24" 0.0000 70.196 70.196 60.000 8.8400 6.6200 2.1600 +1150 "A51" 0.0000 100.00 70.196 60.000 7.7800 4.8100 1.6200 +1151 "P54" 20.000 0.0000 70.196 60.000 11.480 13.230 4.0400 +1152 "J15" 20.000 20.000 70.196 60.000 9.9000 10.490 3.4500 +1153 "A20" 20.000 40.000 70.196 60.000 8.5200 8.1000 2.8800 +1154 "K54" 20.000 70.196 70.196 60.000 7.0800 5.5800 2.1200 +1155 "E22" 20.000 100.00 70.196 60.000 6.3200 4.0900 1.6000 +1156 "Z37" 40.000 0.0000 70.196 60.000 8.3300 10.440 3.7900 +1157 "T21" 40.000 20.000 70.196 60.000 7.2400 8.3100 3.3000 +1158 "A28" 40.000 40.000 70.196 60.000 6.3100 6.4500 2.8100 +1159 "L27" 40.000 70.196 70.196 60.000 5.3400 4.5000 2.0900 +1160 "W29" 40.000 100.00 70.196 60.000 4.8800 3.3700 1.5800 +1161 "L30" 70.196 0.0000 70.196 60.000 4.9400 7.2800 3.5100 +1162 "J18" 70.196 20.000 70.196 60.000 4.4400 5.9200 3.1400 +1163 "F9" 70.196 40.000 70.196 60.000 4.0200 4.7200 2.7600 +1164 "P45" 70.196 70.196 70.196 60.000 3.5400 3.4200 2.0700 +1165 "D40" 70.196 100.00 70.196 60.000 3.2700 2.6000 1.6200 +1166 "J1" 100.00 0.0000 70.196 60.000 2.8100 5.3600 3.5000 +1167 "W12" 100.00 20.000 70.196 60.000 2.6300 4.4700 3.1000 +1168 "Q30" 100.00 40.000 70.196 60.000 2.4600 3.6400 2.7100 +1169 "X53" 100.00 70.196 70.196 60.000 2.3200 2.7500 2.1300 +1170 "F19" 100.00 100.00 70.196 60.000 2.2400 2.1800 1.7000 +1171 "R45" 0.0000 0.0000 100.00 60.000 14.580 15.940 2.3100 +1172 "I51" 0.0000 20.000 100.00 60.000 12.630 12.710 1.9300 +1173 "N7" 0.0000 40.000 100.00 60.000 10.990 9.9500 1.6300 +1174 "W48" 0.0000 70.196 100.00 60.000 8.9400 6.7100 1.3300 +1175 "P14" 0.0000 100.00 100.00 60.000 7.7500 4.7900 1.1900 +1176 "K8" 20.000 0.0000 100.00 60.000 11.610 13.460 2.1900 +1177 "H53" 20.000 20.000 100.00 60.000 10.060 10.730 1.9000 +1178 "T10" 20.000 40.000 100.00 60.000 8.6300 8.2800 1.6400 +1179 "T42" 20.000 70.196 100.00 60.000 7.1200 5.6500 1.3600 +1180 "F43" 20.000 100.00 100.00 60.000 6.3400 4.1200 1.2000 +1181 "X10" 40.000 0.0000 100.00 60.000 8.6200 10.880 2.1300 +1182 "U11" 40.000 20.000 100.00 60.000 7.4100 8.5900 1.8900 +1183 "N41" 40.000 40.000 100.00 60.000 6.2300 6.4500 1.6800 +1184 "O36" 40.000 70.196 100.00 60.000 5.3000 4.5300 1.4100 +1185 "Z6" 40.000 100.00 100.00 60.000 4.9100 3.4400 1.2200 +1186 "C26" 70.196 0.0000 100.00 60.000 5.1600 7.7100 2.1000 +1187 "W45" 70.196 20.000 100.00 60.000 4.4600 6.0900 1.9200 +1188 "Z8" 70.196 40.000 100.00 60.000 3.9000 4.7000 1.7400 +1189 "W23" 70.196 70.196 100.00 60.000 3.4800 3.4600 1.4500 +1190 "Q22" 70.196 100.00 100.00 60.000 3.3000 2.7100 1.2600 +1191 "C24" 100.00 0.0000 100.00 60.000 2.6900 5.4400 2.2300 +1192 "Z53" 100.00 20.000 100.00 60.000 2.5100 4.5200 2.0100 +1193 "H22" 100.00 40.000 100.00 60.000 2.3600 3.6900 1.7900 +1194 "W39" 100.00 70.196 100.00 60.000 2.2400 2.8000 1.5100 +1195 "O19" 100.00 100.00 100.00 60.000 2.2100 2.2400 1.2900 +1196 "L38" 0.0000 0.0000 0.0000 80.000 7.9300 8.2700 6.9600 +1197 "M44" 0.0000 40.000 0.0000 80.000 6.2500 5.4900 4.7900 +1198 "E13" 0.0000 70.196 0.0000 80.000 5.2700 3.9500 3.3700 +1199 "V7" 0.0000 100.00 0.0000 80.000 4.4200 2.7100 2.1800 +1200 "Y36" 40.000 0.0000 0.0000 80.000 5.3400 6.1300 6.6800 +1201 "X28" 40.000 40.000 0.0000 80.000 4.2900 4.1000 4.6400 +1202 "J21" 40.000 70.196 0.0000 80.000 3.7200 3.0200 3.3100 +1203 "F55" 40.000 100.00 0.0000 80.000 3.3100 2.2400 2.3200 +1204 "L42" 70.196 0.0000 0.0000 80.000 3.7700 4.7400 6.4100 +1205 "C15" 70.196 40.000 0.0000 80.000 3.1200 3.2300 4.4800 +1206 "B7" 70.196 70.196 0.0000 80.000 2.8100 2.4700 3.3100 +1207 "W43" 70.196 100.00 0.0000 80.000 2.5900 1.9300 2.4000 +1208 "N54" 100.00 0.0000 0.0000 80.000 2.4000 3.4600 6.0000 +1209 "D38" 100.00 40.000 0.0000 80.000 2.2300 2.5700 4.3900 +1210 "B1" 100.00 70.196 0.0000 80.000 2.1300 2.0600 3.2900 +1211 "R21" 100.00 100.00 0.0000 80.000 2.0000 1.6500 2.4400 +1212 "Z15" 0.0000 0.0000 40.000 80.000 7.3700 7.9100 4.1700 +1213 "I12" 0.0000 40.000 40.000 80.000 5.8100 5.2700 2.9500 +1214 "Z25" 0.0000 70.196 40.000 80.000 5.0300 3.9300 2.1900 +1215 "Z27" 0.0000 100.00 40.000 80.000 4.5400 3.0400 1.6100 +1216 "E11" 40.000 0.0000 40.000 80.000 4.7700 5.7300 3.9000 +1217 "G14" 40.000 40.000 40.000 80.000 3.9100 3.9200 2.8200 +1218 "G54" 40.000 70.196 40.000 80.000 3.4900 3.0100 2.1300 +1219 "F51" 40.000 100.00 40.000 80.000 3.2900 2.4500 1.6500 +1220 "Z4" 70.196 0.0000 40.000 80.000 3.2800 4.4100 3.7300 +1221 "AA5" 70.196 40.000 40.000 80.000 2.8200 3.1100 2.7300 +1222 "W38" 70.196 70.196 40.000 80.000 2.6600 2.5200 2.1400 +1223 "O56" 70.196 100.00 40.000 80.000 2.5900 2.1300 1.7100 +1224 "P39" 100.00 0.0000 40.000 80.000 2.1600 3.4200 3.6700 +1225 "J49" 100.00 40.000 40.000 80.000 2.0800 2.6000 2.8000 +1226 "V31" 100.00 70.196 40.000 80.000 2.0700 2.1800 2.2100 +1227 "F47" 100.00 100.00 40.000 80.000 2.0700 1.9000 1.7500 +1228 "D42" 0.0000 0.0000 70.196 80.000 7.0600 7.6700 2.5600 +1229 "R19" 0.0000 40.000 70.196 80.000 5.5800 5.1500 1.8900 +1230 "J32" 0.0000 70.196 70.196 80.000 4.9200 3.9400 1.5400 +1231 "I42" 0.0000 100.00 70.196 80.000 4.5200 3.1700 1.2700 +1232 "A11" 40.000 0.0000 70.196 80.000 4.4800 5.5000 2.4100 +1233 "M57" 40.000 40.000 70.196 80.000 3.7400 3.8500 1.8300 +1234 "D34" 40.000 70.196 70.196 80.000 3.4000 3.0300 1.5000 +1235 "J56" 40.000 100.00 70.196 80.000 3.2800 2.5500 1.2900 +1236 "R18" 70.196 0.0000 70.196 80.000 3.0400 4.2200 2.3300 +1237 "B14" 70.196 40.000 70.196 80.000 2.7000 3.0800 1.8300 +1238 "L7" 70.196 70.196 70.196 80.000 2.5600 2.5100 1.5000 +1239 "P56" 70.196 100.00 70.196 80.000 2.5600 2.2100 1.3100 +1240 "Y42" 100.00 0.0000 70.196 80.000 2.0000 3.3300 2.3600 +1241 "G30" 100.00 40.000 70.196 80.000 1.9700 2.5800 1.8800 +1242 "N23" 100.00 70.196 70.196 80.000 1.9900 2.1900 1.5500 +1243 "R30" 100.00 100.00 70.196 80.000 2.0100 1.9400 1.3000 +1244 "N39" 0.0000 0.0000 100.00 80.000 7.0200 7.6900 1.6400 +1245 "W40" 0.0000 40.000 100.00 80.000 5.6300 5.2600 1.3400 +1246 "L15" 0.0000 70.196 100.00 80.000 4.9500 4.0400 1.1900 +1247 "D47" 0.0000 100.00 100.00 80.000 4.4900 3.2300 1.1000 +1248 "O9" 40.000 0.0000 100.00 80.000 4.5100 5.6100 1.6000 +1249 "K2" 40.000 40.000 100.00 80.000 3.7700 3.9500 1.3300 +1250 "W25" 40.000 70.196 100.00 80.000 3.4400 3.1300 1.1900 +1251 "D39" 40.000 100.00 100.00 80.000 3.2800 2.6300 1.1300 +1252 "Q24" 70.196 0.0000 100.00 80.000 3.0600 4.3200 1.5900 +1253 "J3" 70.196 40.000 100.00 80.000 2.7200 3.1700 1.3400 +1254 "Q14" 70.196 70.196 100.00 80.000 2.5800 2.6000 1.2000 +1255 "R34" 70.196 100.00 100.00 80.000 2.5500 2.2800 1.1300 +1256 "G50" 100.00 0.0000 100.00 80.000 1.9100 3.3100 1.6000 +1257 "T43" 100.00 40.000 100.00 80.000 1.9300 2.6100 1.3900 +1258 "M50" 100.00 70.196 100.00 80.000 1.9600 2.2400 1.2300 +1259 "T18" 100.00 100.00 100.00 80.000 1.9600 1.9800 1.0600 +1260 "Q13" 0.0000 0.0000 0.0000 100.00 2.6900 2.8000 2.1600 +1261 "N33" 0.0000 40.000 0.0000 100.00 2.4700 2.3000 1.6700 +1262 "B50" 0.0000 100.00 0.0000 100.00 2.3200 1.6600 1.0100 +1263 "N43" 40.000 0.0000 0.0000 100.00 1.9700 2.2000 2.1200 +1264 "C11" 40.000 40.000 0.0000 100.00 1.9300 1.8900 1.6800 +1265 "Z35" 40.000 100.00 0.0000 100.00 1.9800 1.4700 1.0700 +1266 "K5" 100.00 0.0000 0.0000 100.00 1.2300 1.6300 2.2800 +1267 "AA52" 100.00 40.000 0.0000 100.00 1.3300 1.4900 1.8100 +1268 "S17" 100.00 100.00 0.0000 100.00 1.5800 1.3300 1.2300 +1269 "S18" 0.0000 0.0000 40.000 100.00 2.6600 2.7900 1.6200 +1270 "G52" 0.0000 40.000 40.000 100.00 2.4800 2.3600 1.3200 +1271 "O21" 0.0000 100.00 40.000 100.00 2.4000 1.8300 0.92000 +1272 "I30" 40.000 0.0000 40.000 100.00 1.9700 2.2300 1.5800 +1273 "O38" 40.000 40.000 40.000 100.00 1.9500 1.9600 1.3100 +1274 "Q18" 40.000 100.00 40.000 100.00 2.0500 1.6300 0.94000 +1275 "H28" 100.00 0.0000 40.000 100.00 1.2300 1.6600 1.6700 +1276 "O43" 100.00 40.000 40.000 100.00 1.3500 1.5600 1.3900 +1277 "A19" 100.00 100.00 40.000 100.00 1.6300 1.4600 1.0400 +1278 "S20" 0.0000 0.0000 100.00 100.00 2.5600 2.8300 0.98000 +1279 "A56" 0.0000 40.000 100.00 100.00 2.4600 2.4900 0.89000 +1280 "U5" 0.0000 100.00 100.00 100.00 2.4600 2.0800 0.85000 +1281 "AA28" 40.000 0.0000 100.00 100.00 1.9200 2.3000 0.93000 +1282 "H6" 40.000 40.000 100.00 100.00 1.9500 2.1100 0.85000 +1283 "S42" 40.000 100.00 100.00 100.00 2.1100 1.8700 0.81000 +1284 "P12" 100.00 0.0000 100.00 100.00 1.2000 1.7500 0.98000 +1285 "C35" 100.00 40.000 100.00 100.00 1.3600 1.6900 0.90000 +1286 "U3" 100.00 100.00 100.00 100.00 1.6800 1.6600 0.83000 +1287 "Y48" 100.00 0.0000 0.0000 0.0000 16.720 25.290 56.260 +1288 "J29" 98.039 0.0000 0.0000 0.0000 17.390 26.000 56.550 +1289 "A33" 94.902 0.0000 0.0000 0.0000 18.440 27.100 57.000 +1290 "V2" 90.196 0.0000 0.0000 0.0000 20.310 29.010 57.740 +1291 "A37" 85.098 0.0000 0.0000 0.0000 22.360 31.030 58.510 +1292 "N12" 80.000 0.0000 0.0000 0.0000 24.560 33.150 59.270 +1293 "J47" 74.902 0.0000 0.0000 0.0000 26.920 35.350 60.050 +1294 "O3" 70.196 0.0000 0.0000 0.0000 29.450 37.760 60.910 +1295 "N19" 60.000 0.0000 0.0000 0.0000 35.020 43.070 62.800 +1296 "H50" 50.196 0.0000 0.0000 0.0000 41.200 48.900 64.790 +1297 "D48" 40.000 0.0000 0.0000 0.0000 48.240 55.380 66.850 +1298 "K12" 29.804 0.0000 0.0000 0.0000 56.270 62.660 68.950 +1299 "H52" 25.098 0.0000 0.0000 0.0000 60.620 66.560 70.010 +1300 "H13" 20.000 0.0000 0.0000 0.0000 65.080 70.510 71.060 +1301 "Y8" 14.902 0.0000 0.0000 0.0000 69.620 74.500 72.110 +1302 "A54" 10.196 0.0000 0.0000 0.0000 74.060 78.380 73.130 +1303 "I17" 7.0588 0.0000 0.0000 0.0000 76.690 80.650 73.730 +1304 "O50" 5.0980 0.0000 0.0000 0.0000 78.370 82.090 74.120 +1305 "H30" 3.1373 0.0000 0.0000 0.0000 80.070 83.550 74.510 +1306 "Y45" 1.9608 0.0000 0.0000 0.0000 80.930 84.290 74.710 +1307 "U49" 0.0000 100.00 0.0000 0.0000 32.280 16.480 15.950 +1308 "U41" 0.0000 98.039 0.0000 0.0000 32.750 17.030 16.530 +1309 "Q56" 0.0000 94.902 0.0000 0.0000 33.460 17.900 17.440 +1310 "M3" 0.0000 90.196 0.0000 0.0000 34.690 19.430 19.040 +1311 "S27" 0.0000 85.098 0.0000 0.0000 36.010 21.120 20.750 +1312 "R27" 0.0000 80.000 0.0000 0.0000 37.400 22.980 22.580 +1313 "F41" 0.0000 74.902 0.0000 0.0000 38.840 24.970 24.520 +1314 "T44" 0.0000 70.196 0.0000 0.0000 40.640 27.330 26.760 +1315 "L18" 0.0000 60.000 0.0000 0.0000 44.970 32.940 32.000 +1316 "E17" 0.0000 50.196 0.0000 0.0000 50.010 39.680 38.140 +1317 "D6" 0.0000 40.000 0.0000 0.0000 55.570 47.360 44.900 +1318 "M47" 0.0000 29.804 0.0000 0.0000 61.730 55.910 51.990 +1319 "X27" 0.0000 25.098 0.0000 0.0000 64.990 60.480 55.690 +1320 "AA50" 0.0000 20.000 0.0000 0.0000 68.390 65.250 59.520 +1321 "Z47" 0.0000 14.902 0.0000 0.0000 71.880 70.190 63.430 +1322 "U24" 0.0000 10.196 0.0000 0.0000 75.440 75.210 67.360 +1323 "T5" 0.0000 7.0588 0.0000 0.0000 77.590 78.300 69.700 +1324 "Y55" 0.0000 5.0980 0.0000 0.0000 79.020 80.390 71.220 +1325 "L52" 0.0000 3.1373 0.0000 0.0000 80.470 82.520 72.750 +1326 "A26" 0.0000 1.9608 0.0000 0.0000 81.200 83.590 73.530 +1327 "Z40" 0.0000 0.0000 100.00 0.0000 67.430 72.230 6.1400 +1328 "A38" 0.0000 0.0000 98.039 0.0000 67.550 72.380 6.6300 +1329 "P53" 0.0000 0.0000 94.902 0.0000 67.740 72.610 7.4100 +1330 "K37" 0.0000 0.0000 90.196 0.0000 68.070 73.010 8.8300 +1331 "R54" 0.0000 0.0000 85.098 0.0000 68.430 73.430 10.410 +1332 "D24" 0.0000 0.0000 80.000 0.0000 68.840 73.870 12.190 +1333 "D26" 0.0000 0.0000 74.902 0.0000 69.260 74.310 14.170 +1334 "P47" 0.0000 0.0000 70.196 0.0000 69.800 74.880 16.470 +1335 "S13" 0.0000 0.0000 60.000 0.0000 71.090 76.210 22.050 +1336 "E15" 0.0000 0.0000 50.196 0.0000 72.640 77.690 29.020 +1337 "M2" 0.0000 0.0000 40.000 0.0000 74.390 79.200 36.970 +1338 "AA3" 0.0000 0.0000 29.804 0.0000 76.290 80.780 45.490 +1339 "R25" 0.0000 0.0000 25.098 0.0000 77.270 81.580 50.000 +1340 "Q49" 0.0000 0.0000 20.000 0.0000 78.300 82.380 54.640 +1341 "AA31" 0.0000 0.0000 14.902 0.0000 79.330 83.180 59.470 +1342 "B35" 0.0000 0.0000 10.196 0.0000 80.380 84.000 64.370 +1343 "K16" 0.0000 0.0000 7.0588 0.0000 81.040 84.510 67.460 +1344 "X17" 0.0000 0.0000 5.0980 0.0000 81.510 84.870 69.580 +1345 "AA53" 0.0000 0.0000 3.1373 0.0000 81.970 85.230 71.760 +1346 "X16" 0.0000 0.0000 1.9608 0.0000 82.200 85.410 72.860 +1347 "Z12" 0.0000 0.0000 0.0000 100.00 2.6900 2.8000 2.1600 +1348 "AA32" 0.0000 0.0000 0.0000 98.039 3.0500 3.1800 2.4900 +1349 "P25" 0.0000 0.0000 0.0000 94.902 3.6700 3.8200 3.0400 +1350 "I34" 0.0000 0.0000 0.0000 90.196 4.8400 5.0500 4.1200 +1351 "T53" 0.0000 0.0000 0.0000 85.098 6.2100 6.4700 5.3700 +1352 "R41" 0.0000 0.0000 0.0000 80.000 7.9300 8.2700 6.9600 +1353 "O55" 0.0000 0.0000 0.0000 74.902 10.010 10.450 8.8900 +1354 "Q2" 0.0000 0.0000 0.0000 70.196 12.600 13.150 11.280 +1355 "I41" 0.0000 0.0000 0.0000 60.000 18.920 19.770 17.150 +1356 "W16" 0.0000 0.0000 0.0000 50.196 26.590 27.730 24.310 +1357 "L32" 0.0000 0.0000 0.0000 40.000 35.870 37.330 32.950 +1358 "S19" 0.0000 0.0000 0.0000 29.804 46.510 48.340 42.550 +1359 "E40" 0.0000 0.0000 0.0000 25.098 52.470 54.520 47.890 +1360 "X13" 0.0000 0.0000 0.0000 20.000 58.360 60.610 53.170 +1361 "R47" 0.0000 0.0000 0.0000 14.902 64.370 66.830 58.570 +1362 "AA9" 0.0000 0.0000 0.0000 10.196 70.130 72.800 63.780 +1363 "AA37" 0.0000 0.0000 0.0000 7.0588 73.750 76.540 67.050 +1364 "I15" 0.0000 0.0000 0.0000 5.0980 76.230 79.110 69.290 +1365 "G32" 0.0000 0.0000 0.0000 3.1373 78.770 81.730 71.570 +1366 "E54" 0.0000 0.0000 0.0000 1.9608 80.050 83.060 72.740 +1367 "G48" 0.0000 0.0000 0.0000 0.0000 82.670 85.770 75.100 +1368 "C51" 100.00 85.098 85.098 0.0000 4.2100 5.0800 3.8600 +1369 "F29" 80.000 65.098 65.098 0.0000 9.2500 10.040 7.5600 +1370 "U4" 60.000 45.098 45.098 0.0000 18.590 19.740 16.510 +1371 "AA34" 40.000 27.059 27.059 0.0000 34.220 36.090 31.630 +1372 "E51" 20.000 12.157 12.157 0.0000 56.270 58.800 51.990 +1373 "J43" 10.196 5.8824 5.8824 0.0000 68.820 71.500 62.730 +1374 "N10" 5.0980 3.1373 3.1373 0.0000 75.560 78.420 68.630 +1375 "A25" 40.000 27.059 27.059 10.196 29.920 31.630 27.750 +1376 "G9" 20.000 12.157 12.157 10.196 48.460 50.700 44.880 +1377 "H42" 10.196 5.8824 5.8824 10.196 58.810 61.160 53.730 +1378 "J48" 60.000 45.098 45.098 20.000 14.020 14.930 12.540 +1379 "X5" 40.000 27.059 27.059 20.000 25.580 27.090 23.790 +1380 "Q36" 20.000 12.157 12.157 20.000 40.850 42.790 37.950 +1381 "W56" 10.196 5.8824 5.8824 20.000 49.240 51.260 45.120 +1382 "V46" 80.000 65.098 65.098 40.000 5.0200 5.3800 3.9000 +1383 "W2" 60.000 45.098 45.098 40.000 8.9700 9.5400 7.9900 +1384 "O37" 40.000 27.059 27.059 40.000 15.850 16.790 14.880 +1385 "W37" 20.000 12.157 12.157 40.000 25.140 26.400 23.570 +1386 "W10" 10.196 5.8824 5.8824 40.000 30.280 31.590 27.990 +1387 "C33" 100.00 85.098 85.098 60.000 2.2400 2.4600 1.6300 +1388 "H1" 80.000 65.098 65.098 60.000 3.1800 3.3500 2.3200 +1389 "AA41" 60.000 45.098 45.098 60.000 4.8500 5.0700 4.4500 +1390 "Y38" 40.000 27.059 27.059 60.000 8.3800 8.8100 8.0600 +1391 "F57" 20.000 12.157 12.157 60.000 13.460 14.110 12.550 +1392 "G51" 10.196 5.8824 5.8824 60.000 16.140 16.860 14.740 +1393 "K39" 100.00 85.098 85.098 80.000 1.9600 2.0600 1.2500 +1394 "X55" 80.000 65.098 65.098 80.000 2.3600 2.4500 1.6300 +1395 "W36" 60.000 45.098 45.098 80.000 3.0700 3.2100 2.4800 +1396 "V50" 40.000 27.059 27.059 80.000 4.2700 4.4900 3.7200 +1397 "K42" 20.000 12.157 12.157 80.000 5.9400 6.2300 5.2700 +1398 "H46" 10.196 5.8824 5.8824 80.000 6.8900 7.1900 6.0600 +1399 "K20" 100.00 85.098 85.098 100.00 1.6000 1.6200 0.88000 +1400 "O28" 80.000 65.098 65.098 100.00 1.6500 1.6700 1.0000 +1401 "M55" 60.000 45.098 45.098 100.00 1.7400 1.7900 1.2400 +1402 "Q33" 40.000 27.059 27.059 100.00 1.9500 2.0300 1.5400 +1403 "G11" 20.000 12.157 12.157 100.00 2.2800 2.3700 1.8500 +1404 "O24" 10.196 5.8824 5.8824 100.00 2.4700 2.5700 1.9900 +1405 "I40" 100.00 0.0000 0.0000 70.196 3.3500 4.9400 9.1000 +1406 "V26" 0.0000 100.00 0.0000 70.196 6.2200 3.6300 3.1400 +1407 "P23" 0.0000 0.0000 100.00 70.196 10.310 11.280 1.9600 +1408 "Z38" 100.00 100.00 0.0000 70.196 2.3500 1.9200 3.4300 +1409 "I20" 100.00 0.0000 100.00 70.196 2.2700 4.2800 1.9000 +1410 "V18" 0.0000 100.00 100.00 70.196 5.9200 3.9200 1.1400 +1411 "U33" 40.000 40.000 0.0000 70.196 6.0400 5.7300 7.0200 +1412 "V37" 40.000 0.0000 40.000 70.196 6.8800 8.3200 5.7100 +1413 "A23" 0.0000 40.000 40.000 70.196 8.4500 7.5800 4.3000 +1414 "U54" 3.1373 3.1373 0.0000 0.0000 77.910 80.350 72.190 +1415 "Y1" 3.1373 0.0000 3.1373 0.0000 79.370 83.010 71.210 +1416 "M8" 0.0000 3.1373 3.1373 0.0000 79.770 81.980 69.490 +1417 "J50" 3.1373 3.1373 3.1373 0.0000 77.230 79.830 68.970 +1418 "J55" 3.1373 0.0000 0.0000 3.1373 76.310 79.640 71.020 +1419 "M42" 0.0000 3.1373 0.0000 3.1373 76.690 78.660 69.370 +1420 "A14" 3.1373 3.1373 0.0000 3.1373 74.280 76.630 68.840 +1421 "Y9" 0.0000 0.0000 3.1373 3.1373 78.110 81.220 68.430 +1422 "S14" 3.1373 0.0000 3.1373 3.1373 75.660 79.130 67.910 +1423 "G15" 0.0000 3.1373 3.1373 3.1373 76.030 78.160 66.300 +1424 "H48" 3.1373 3.1373 3.1373 3.1373 73.640 76.140 65.810 +1425 "B30" 7.0588 7.0588 0.0000 0.0000 71.830 73.480 68.450 +1426 "G36" 7.0588 0.0000 7.0588 0.0000 75.070 79.390 66.280 +1427 "G44" 0.0000 7.0588 7.0588 0.0000 75.980 77.080 62.510 +1428 "Q52" 7.0588 7.0588 7.0588 0.0000 70.400 72.440 61.560 +1429 "C50" 7.0588 0.0000 0.0000 7.0588 68.550 72.090 65.870 +1430 "D46" 0.0000 7.0588 0.0000 7.0588 69.340 70.070 62.380 +1431 "M28" 7.0588 7.0588 0.0000 7.0588 64.320 65.870 61.300 +1432 "K19" 0.0000 0.0000 7.0588 7.0588 72.350 75.460 60.420 +1433 "I56" 7.0588 0.0000 7.0588 7.0588 67.150 71.010 59.410 +1434 "S23" 0.0000 7.0588 7.0588 7.0588 67.940 69.020 56.130 +1435 "I35" 7.0588 7.0588 7.0588 7.0588 63.090 64.980 55.310 +1436 "F10" 40.000 3.1373 0.0000 0.0000 46.900 53.230 64.760 +1437 "W32" 3.1373 40.000 0.0000 0.0000 53.700 46.000 44.550 +1438 "K38" 40.000 0.0000 3.1373 0.0000 47.760 55.080 63.820 +1439 "B41" 40.000 3.1373 3.1373 0.0000 46.430 52.940 61.850 +1440 "B55" 0.0000 40.000 3.1373 0.0000 55.180 47.100 42.990 +1441 "H3" 3.1373 40.000 3.1373 0.0000 53.320 45.760 42.680 +1442 "F3" 40.000 40.000 3.1373 0.0000 31.900 30.200 39.030 +1443 "N27" 3.1373 0.0000 40.000 0.0000 71.840 77.030 36.790 +1444 "U30" 0.0000 3.1373 40.000 0.0000 72.430 76.190 35.830 +1445 "T32" 3.1373 3.1373 40.000 0.0000 69.950 74.110 35.680 +1446 "A1" 40.000 3.1373 40.000 0.0000 40.970 49.010 32.140 +1447 "H12" 3.1373 40.000 40.000 0.0000 49.290 43.080 22.680 +1448 "A7" 40.000 0.0000 0.0000 3.1373 46.150 52.970 63.810 +1449 "N28" 40.000 3.1373 0.0000 3.1373 44.890 50.940 61.840 +1450 "T33" 0.0000 40.000 0.0000 3.1373 53.090 45.340 42.990 +1451 "G57" 3.1373 40.000 0.0000 3.1373 51.330 44.050 42.660 +1452 "AA29" 40.000 40.000 0.0000 3.1373 30.990 29.250 39.160 +1453 "Z54" 40.000 0.0000 3.1373 3.1373 45.700 52.680 60.950 +1454 "B57" 40.000 3.1373 3.1373 3.1373 44.460 50.670 59.100 +1455 "E19" 0.0000 40.000 3.1373 3.1373 52.730 45.090 41.180 +1456 "W24" 3.1373 40.000 3.1373 3.1373 50.980 43.830 40.890 +1457 "L16" 40.000 40.000 3.1373 3.1373 30.680 29.090 37.450 +1458 "Q38" 0.0000 0.0000 40.000 3.1373 70.950 75.570 35.460 +1459 "E28" 3.1373 0.0000 40.000 3.1373 68.550 73.520 35.290 +1460 "J25" 40.000 0.0000 40.000 3.1373 40.440 48.890 31.760 +1461 "Z42" 0.0000 3.1373 40.000 3.1373 69.110 72.730 34.390 +1462 "O40" 3.1373 3.1373 40.000 3.1373 66.780 70.770 34.250 +1463 "P48" 40.000 3.1373 40.000 3.1373 39.330 47.000 30.880 +1464 "S48" 0.0000 40.000 40.000 3.1373 48.850 42.450 21.930 +1465 "T54" 3.1373 40.000 40.000 3.1373 47.180 41.310 21.840 +1466 "V21" 40.000 40.000 40.000 3.1373 27.660 27.330 20.040 +1467 "X32" 3.1373 0.0000 0.0000 40.000 34.810 36.430 32.700 +1468 "M54" 0.0000 3.1373 0.0000 40.000 35.000 36.040 32.030 +1469 "P18" 3.1373 3.1373 0.0000 40.000 33.960 35.170 31.790 +1470 "B29" 40.000 3.1373 0.0000 40.000 21.090 23.970 28.560 +1471 "N42" 3.1373 40.000 0.0000 40.000 24.370 21.350 20.800 +1472 "H36" 0.0000 0.0000 3.1373 40.000 35.580 37.110 31.630 +1473 "Y33" 3.1373 0.0000 3.1373 40.000 34.530 36.220 31.390 +1474 "X38" 40.000 0.0000 3.1373 40.000 21.430 24.730 28.190 +1475 "W46" 0.0000 3.1373 3.1373 40.000 34.710 35.820 30.740 +1476 "Q34" 3.1373 3.1373 3.1373 40.000 33.680 34.950 30.510 +1477 "L53" 40.000 3.1373 3.1373 40.000 20.900 23.850 27.410 +1478 "Q23" 0.0000 40.000 3.1373 40.000 24.990 21.820 20.150 +1479 "N22" 3.1373 40.000 3.1373 40.000 24.220 21.240 20.010 +1480 "L57" 40.000 40.000 3.1373 40.000 15.080 14.420 18.190 +1481 "F5" 3.1373 0.0000 40.000 40.000 31.250 33.670 16.670 +1482 "J22" 0.0000 3.1373 40.000 40.000 31.520 33.360 16.290 +1483 "H27" 3.1373 3.1373 40.000 40.000 30.520 32.520 16.230 +1484 "U29" 40.000 3.1373 40.000 40.000 18.520 22.170 14.690 +1485 "U34" 3.1373 40.000 40.000 40.000 22.390 20.040 10.900 +0 "R9" 0.0000 0.0000 0.0000 0.0000 86.887 89.830 77.952 +0 "R38" 0.0000 0.0000 0.0000 0.0000 86.887 89.830 77.952 +0 "T45" 0.0000 0.0000 0.0000 0.0000 86.887 89.830 77.952 +0 "G12" 0.0000 0.0000 0.0000 0.0000 86.887 89.830 77.952 +0 "Q37" 0.0000 0.0000 0.0000 0.0000 86.887 89.830 77.952 +0 "G4" 0.0000 0.0000 0.0000 0.0000 86.887 89.830 77.952 +0 "Z32" 0.0000 0.0000 0.0000 0.0000 86.887 89.830 77.952 +0 "S6" 0.0000 0.0000 0.0000 0.0000 86.887 89.830 77.952 +0 "V39" 0.0000 0.0000 0.0000 0.0000 86.887 89.830 77.952 +0 "V44" 0.0000 0.0000 0.0000 0.0000 86.887 89.830 77.952 +0 "Q26" 0.0000 0.0000 0.0000 0.0000 86.887 89.830 77.952 +0 "Z3" 0.0000 0.0000 0.0000 0.0000 86.887 89.830 77.952 +0 "N38" 0.0000 0.0000 0.0000 0.0000 86.887 89.830 77.952 +0 "B22" 0.0000 0.0000 0.0000 0.0000 86.887 89.830 77.952 +0 "W21" 0.0000 0.0000 0.0000 0.0000 86.887 89.830 77.952 +0 "B47" 0.0000 0.0000 0.0000 0.0000 86.887 89.830 77.952 +0 "AA45" 0.0000 0.0000 0.0000 0.0000 86.887 89.830 77.952 +0 "Z1" 0.0000 0.0000 0.0000 0.0000 86.887 89.830 77.952 +0 "R32" 0.0000 0.0000 0.0000 0.0000 86.887 89.830 77.952 +0 "V11" 0.0000 0.0000 0.0000 0.0000 86.887 89.830 77.952 +0 "P31" 0.0000 0.0000 0.0000 0.0000 86.887 89.830 77.952 +0 "W31" 0.0000 0.0000 0.0000 0.0000 86.887 89.830 77.952 +0 "N13" 0.0000 0.0000 0.0000 0.0000 86.887 89.830 77.952 +0 "H31" 0.0000 0.0000 0.0000 0.0000 86.887 89.830 77.952 +0 "K50" 0.0000 0.0000 0.0000 0.0000 86.887 89.830 77.952 +0 "M26" 0.0000 0.0000 0.0000 0.0000 86.887 89.830 77.952 +0 "J40" 0.0000 0.0000 0.0000 0.0000 86.887 89.830 77.952 +0 "B34" 0.0000 0.0000 0.0000 0.0000 86.887 89.830 77.952 +0 "T56" 0.0000 0.0000 0.0000 0.0000 86.887 89.830 77.952 +0 "K25" 0.0000 0.0000 0.0000 0.0000 86.887 89.830 77.952 +0 "W15" 0.0000 0.0000 0.0000 0.0000 86.887 89.830 77.952 +0 "E8" 0.0000 0.0000 0.0000 0.0000 86.887 89.830 77.952 +0 "G13" 0.0000 0.0000 0.0000 0.0000 86.887 89.830 77.952 +0 "C17" 0.0000 0.0000 0.0000 0.0000 86.887 89.830 77.952 +0 "U14" 0.0000 0.0000 0.0000 0.0000 86.887 89.830 77.952 +0 "Y12" 0.0000 0.0000 0.0000 0.0000 86.887 89.830 77.952 +0 "J20" 0.0000 0.0000 0.0000 0.0000 86.887 89.830 77.952 +0 "A16" 0.0000 0.0000 0.0000 0.0000 86.887 89.830 77.952 +0 "B37" 0.0000 0.0000 0.0000 0.0000 86.887 89.830 77.952 +0 "U7" 0.0000 0.0000 0.0000 0.0000 86.887 89.830 77.952 +0 "X44" 0.0000 0.0000 0.0000 0.0000 86.887 89.830 77.952 +0 "U8" 0.0000 0.0000 0.0000 0.0000 86.887 89.830 77.952 +0 "AA43" 0.0000 0.0000 0.0000 0.0000 86.887 89.830 77.952 +0 "U52" 0.0000 0.0000 0.0000 0.0000 86.887 89.830 77.952 +0 "J13" 0.0000 0.0000 0.0000 0.0000 86.887 89.830 77.952 +0 "J5" 0.0000 0.0000 0.0000 0.0000 86.887 89.830 77.952 +0 "I10" 0.0000 0.0000 0.0000 0.0000 86.887 89.830 77.952 +0 "O26" 0.0000 0.0000 0.0000 0.0000 86.887 89.830 77.952 +0 "Z14" 0.0000 0.0000 0.0000 0.0000 86.887 89.830 77.952 +0 "V13" 0.0000 0.0000 0.0000 0.0000 86.887 89.830 77.952 +0 "C37" 0.0000 0.0000 0.0000 0.0000 86.887 89.830 77.952 +0 "X46" 0.0000 0.0000 0.0000 0.0000 86.887 89.830 77.952 +0 "L33" 0.0000 0.0000 0.0000 0.0000 86.887 89.830 77.952 +0 "R28" 0.0000 0.0000 0.0000 0.0000 86.887 89.830 77.952 +END_DATA diff --git a/target/ECI2002R.ti2 b/target/ECI2002R.ti2 new file mode 100644 index 0000000..d3f2d8b --- /dev/null +++ b/target/ECI2002R.ti2 @@ -0,0 +1,1517 @@ +CTI2 + +DESCRIPTOR "Argyll Calibration Target chart information 2" +# ECI2002 Random CMYK chart, 1485 patches. +ORIGINATOR "Argyll printtarg" +CREATED "Thu Aug 11 22:19:15 2005" +KEYWORD "TARGET_INSTRUMENT" +TARGET_INSTRUMENT "GretagMacbeth SpectroScan" +KEYWORD "MULTI_DIM_STEPS" +MULTI_DIM_STEPS "2" +KEYWORD "APPROX_WHITE_POINT" +APPROX_WHITE_POINT "96.420000 100.000000 82.490000" +KEYWORD "COLOR_REP" +COLOR_REP "CMYK" +KEYWORD "STEPS_IN_PASS" +STEPS_IN_PASS "33" +KEYWORD "PASSES_IN_STRIPS" +PASSES_IN_STRIPS "j" +KEYWORD "STRIP_INDEX_PATTERN" +STRIP_INDEX_PATTERN "A-Z, 2-9;A-X,2A-9Z" +KEYWORD "PATCH_INDEX_PATTERN" +PATCH_INDEX_PATTERN "0-9,@-9,@-9;1-999" + +KEYWORD "SAMPLE_LOC" +NUMBER_OF_FIELDS 9 +BEGIN_DATA_FORMAT +SAMPLE_ID SAMPLE_LOC CMYK_C CMYK_M CMYK_Y CMYK_K XYZ_X XYZ_Y XYZ_Z +END_DATA_FORMAT + +NUMBER_OF_SETS 1485 +BEGIN_DATA +810 "A1" 10 20 20 20 42.793 42.608 32.296 +1369 "A2" 80 65 65 0 9.5005 10.178 7.8087 +1393 "A3" 100 85 85 80 1.9086 2.003 1.1906 +1427 "A4" 0 7 7 0 76.857 77.978 63.869 +1387 "A5" 100 85 85 60 2.2422 2.4721 1.6188 +1377 "A6" 10 6 6 10 60.778 63.216 55.806 +558 "A7" 85 100 70 0 6.1175 5.0394 4.4819 +1381 "A8" 10 6 6 20 50.129 52.184 46.198 +1394 "A9" 80 65 65 80 2.3235 2.4083 1.599 +1434 "A10" 0 7 7 7 69.874 71.047 58.514 +1388 "A11" 80 65 65 60 3.437 3.6144 2.5883 +1392 "A12" 10 6 6 60 16.236 16.953 14.978 +1382 "A13" 80 65 65 40 5.2618 5.5902 4.1157 +93 "A14" 10 20 10 0 60.498 59.465 51.051 +230 "A15" 85 40 20 0 13.639 16.589 27.468 +1430 "A16" 0 7 0 7 71.224 72.097 64.03 +1395 "A17" 60 45 45 80 3.041 3.1902 2.4497 +1376 "A18" 20 12 12 10 49.823 52.06 45.983 +1389 "A19" 60 45 45 60 5.2975 5.5898 4.7403 +1380 "A20" 20 12 12 20 41.45 43.378 38.352 +1424 "A21" 3 3 3 3 75.838 78.392 68.023 +1385 "A22" 20 12 12 40 25.493 26.703 23.726 +1378 "A23" 60 45 45 20 14.211 15.169 12.762 +1391 "A24" 20 12 12 60 13.215 13.844 12.297 +1420 "A25" 3 3 0 3 76.251 78.631 70.15 +1397 "A26" 20 12 12 80 5.8519 6.1281 5.184 +210 "A27" 55 20 20 0 29.639 34.115 38.205 +101 "A28" 20 10 10 0 58.653 61.601 55.595 +1432 "A29" 0 0 7 7 74.374 77.494 62.933 +1375 "A30" 40 27 27 10 30.415 32.13 28.372 +1384 "A31" 40 27 27 40 16.266 17.237 15.144 +1379 "A32" 40 27 27 20 25.674 27.176 23.921 +1 "A33" 0 0 0 0 83.532 86.556 75.658 +970 "B1" 100 100 0 40 3.6335 2.7779 7.9686 +1049 "B2" 0 70 100 40 16.486 12.087 1.9347 +968 "B3" 100 40 0 40 5.7755 7.1079 16.701 +336 "B4" 10 20 40 0 55.595 55.81 29.655 +479 "B5" 100 10 55 0 9.9812 18.831 18.457 +560 "B6" 100 10 70 0 8.8992 17.985 12.749 +328 "B7" 0 30 40 0 56.678 52.472 26.359 +564 "B8" 100 55 70 0 5.8864 8.9485 7.8006 +974 "B9" 0 70 20 40 18.626 13.239 10.215 +1045 "B10" 100 100 70 40 2.8294 2.7651 2.6045 +234 "B11" 85 100 20 0 7.5253 5.3012 12.047 +999 "B12" 0 70 40 40 17.721 12.765 7.0851 +1018 "B13" 100 40 40 40 4.5218 6.511 8.8453 +997 "B14" 0 20 40 40 27.374 27.152 13.911 +641 "B15" 100 10 85 0 8.1959 17.374 8.6931 +849 "B16" 10 100 40 20 20.419 11.252 6.8398 +433 "B17" 30 0 55 0 47.637 56.166 23.691 +279 "B18" 30 100 30 0 20.49 11.372 10.058 +469 "B19" 85 0 55 0 16.124 26.789 20.762 +315 "B20" 85 100 30 0 7.2433 5.2818 10.282 +1046 "B21" 0 0 100 40 28.466 31.033 3.3861 +955 "B22" 20 100 0 40 11.794 6.4646 7.5603 +1056 "B23" 40 0 100 40 16.346 20.623 3.1537 +965 "B24" 70 100 0 40 5.7639 3.7213 7.6967 +1066 "B25" 100 0 100 40 4.1162 8.961 3.2855 +422 "B26" 10 85 55 0 30.135 18.78 7.901 +887 "B27" 20 10 70 20 36.357 39.95 11.445 +458 "B28" 55 85 55 0 14.013 10.389 7.6421 +899 "B29" 70 10 70 20 14.937 21.331 10.227 +740 "B30" 10 70 0 20 26.948 18.819 20.02 +911 "B31" 0 10 100 20 43.802 45.594 4.5306 +1265 "B32" 40 100 0 100 2.0055 1.5286 1.1038 +905 "B33" 100 10 70 20 6.9562 13.709 9.7649 +1061 "C1" 70 0 100 40 9.2036 14.115 3.1614 +952 "C2" 20 20 0 40 24.491 24.553 25.45 +1063 "C3" 70 40 100 40 6.7295 8.3357 2.4606 +358 "C4" 30 70 40 0 25.322 19.034 13.417 +720 "C5" 85 100 100 0 5.6081 4.8531 2.2834 +135 "C6" 55 100 10 0 13.763 8.0611 13.747 +350 "C7" 20 85 40 0 26.724 17.011 10.976 +384 "C8" 70 55 40 0 14.077 14.733 15.901 +1027 "C9" 20 20 70 40 20.476 21.648 6.6864 +986 "C10" 70 0 20 40 12.854 17.116 20.559 +389 "C11" 85 10 40 0 16.298 24.777 26.866 +1002 "C12" 20 20 40 40 21.604 22.456 13.304 +1013 "C13" 70 40 40 40 7.9577 9.151 9.2125 +1004 "C14" 20 70 40 40 13.987 10.47 6.8252 +216 "C15" 55 100 20 0 13.4 7.9938 11.912 +728 "C16" 100 85 100 0 4.0841 5.1036 2.7965 +471 "C17" 85 20 55 0 13.49 20.624 17.26 +457 "C18" 55 70 55 0 15.752 13.406 9.2268 +435 "C19" 30 20 55 0 40.149 43.322 19.482 +421 "C20" 10 70 55 0 33.517 23.678 9.8153 +400 "C21" 100 30 40 0 9.4072 14.991 21.358 +186 "C22" 20 55 20 0 35.935 29.1 25.365 +616 "C23" 55 30 85 0 21.553 25.496 7.2393 +222 "C24" 70 55 20 0 15.6 15.466 23.084 +580 "C25" 10 30 85 0 46.288 44.489 7.4927 +1267 "C26" 100 40 0 100 1.3818 1.5205 1.8283 +935 "C27" 70 10 100 20 13.917 20.374 4.265 +1261 "C28" 0 40 0 100 2.8301 2.5695 1.9105 +918 "C29" 10 20 100 20 36.334 37.277 4.1001 +474 "C30" 85 55 55 0 9.5771 11.612 11.315 +452 "C31" 55 10 55 0 28.107 35.803 20.187 +438 "C32" 30 55 55 0 28.453 24.407 11.952 +416 "C33" 10 10 55 0 58.641 61.983 22.575 +960 "D1" 40 100 0 40 8.982 5.1483 7.5676 +1059 "D2" 40 70 100 40 9.3594 7.7211 1.9254 +958 "D3" 40 40 0 40 15.606 14.83 19.238 +372 "D4" 55 20 40 0 27.182 32.439 26.236 +370 "D5" 55 0 40 0 32.564 42.302 31.907 +362 "D6" 40 10 40 0 39.04 45.288 30.422 +364 "D7" 40 30 40 0 32.681 34.373 24.474 +366 "D8" 40 55 40 0 24.752 21.975 16.766 +984 "D9" 40 70 20 40 11.209 8.7933 9.4358 +1035 "D10" 40 100 70 40 8.2908 5.2998 2.5581 +198 "D11" 30 100 20 0 20.728 11.36 11.887 +1009 "D12" 40 70 40 40 10.342 8.24 6.5236 +1008 "D13" 40 40 40 40 13.263 13.221 9.9237 +1007 "D14" 40 20 40 40 15.983 17.734 12.478 +371 "D15" 55 10 40 0 29.797 37.094 29.054 +419 "D16" 10 40 55 0 44.092 39.582 15.619 +437 "D17" 30 40 55 0 32.767 31.468 14.757 +455 "D18" 55 40 55 0 21.173 22.9 14.519 +473 "D19" 85 40 55 0 10.975 15.009 13.636 +150 "D20" 85 55 10 0 12.293 12.909 26.065 +1048 "D21" 0 40 100 40 20.719 18.667 2.3797 +953 "D22" 20 40 0 40 20.382 18.42 20.1 +1058 "D23" 40 40 100 40 11.607 11.948 2.4183 +963 "D24" 70 40 0 40 9.8391 10.374 17.803 +1068 "D25" 100 40 100 40 3.3945 5.7942 2.5531 +418 "D26" 10 30 55 0 48.669 46.539 17.988 +436 "D27" 30 30 55 0 36.428 37.272 17.106 +454 "D28" 55 30 55 0 23.62 27.183 16.565 +76 "D29" 100 30 0 0 13.039 16.997 41.354 +738 "D30" 10 20 0 20 44.794 43.834 42.663 +1278 "D31" 0 0 100 100 2.572 2.8437 0.98565 +1263 "D32" 40 0 0 100 3.6855 4.1078 4.037 +1284 "D33" 100 0 100 100 1.2048 1.7593 0.98423 +1051 "E1" 20 0 100 40 22.537 26.074 3.287 +962 "E2" 70 20 0 40 11.811 13.835 22.408 +1053 "E3" 20 40 100 40 16.09 15.286 2.4195 +394 "E4" 85 70 40 0 9.0571 9.2955 12.883 +684 "E5" 30 100 100 0 19.136 11.158 2.267 +99 "E6" 10 100 10 0 28.387 14.824 13.952 +386 "E7" 70 85 40 0 10.927 8.6924 10.65 +348 "E8" 20 55 40 0 34.295 28.185 17.407 +1037 "E9" 70 20 70 40 8.742 11.892 6.0601 +976 "E10" 20 0 20 40 27.708 30.572 23.903 +353 "E11" 30 10 40 0 45.851 51.24 31.303 +1012 "E12" 70 20 40 40 9.5758 12.338 11.494 +1003 "E13" 20 40 40 40 17.982 16.763 10.473 +1014 "E14" 70 70 40 40 6.3807 5.873 6.3043 +180 "E15" 10 100 20 0 28.092 14.8 12.13 +1042 "E16" 100 20 70 40 4.4208 8.0398 5.8433 +989 "E17" 70 70 20 40 7.153 6.3562 9.1678 +1032 "E18" 40 20 70 40 15.006 17.115 6.3506 +979 "E19" 20 70 20 40 14.885 11.021 9.8194 +1022 "E20" 0 20 70 40 26.023 26.124 6.893 +465 "E21" 70 55 55 0 13.34 14.247 11.521 +443 "E22" 40 10 55 0 37.025 43.64 20.961 +429 "E23" 20 55 55 0 33.356 27.593 12.197 +407 "E24" 0 10 55 0 66.105 68.086 22.708 +847 "E25" 10 40 40 20 33.266 30.041 17.13 +791 "E26" 70 10 10 20 19.449 24.461 34.957 +1282 "E27" 40 40 100 100 2.0252 2.1915 0.93621 +779 "E28" 20 10 10 20 42.453 44.687 40.368 +920 "E29" 10 70 100 20 23.705 17.325 2.4843 +470 "E30" 85 10 55 0 14.757 23.58 19.012 +456 "E31" 55 55 55 0 18.248 17.687 11.697 +434 "E32" 30 10 55 0 43.729 49.397 21.567 +420 "E33" 10 55 55 0 38.378 30.8 12.538 +950 "F1" 0 100 0 40 14.72 7.8407 7.6846 +1069 "F2" 100 70 100 40 2.9619 3.9055 2.0486 +948 "F3" 0 40 0 40 25.204 21.959 20.959 +481 "F4" 100 30 55 0 8.3475 14.293 15.071 +334 "F5" 10 0 40 0 66.412 72.64 36.369 +326 "F6" 0 10 40 0 68.338 69.993 33.426 +562 "F7" 100 30 70 0 7.442 13.62 10.455 +330 "F8" 0 55 40 0 44.42 34.552 18.22 +994 "F9" 100 70 20 40 4.1074 4.3786 8.8089 +1025 "F10" 0 100 70 40 14.075 8.072 2.5768 +813 "F11" 10 100 20 20 20.591 11.076 9.279 +1019 "F12" 100 70 40 40 3.6876 4.2309 6.2137 +998 "F13" 0 40 40 40 23.042 20.474 11.041 +1017 "F14" 100 20 40 40 5.3212 8.6332 11.014 +335 "F15" 10 10 40 0 61.004 64.009 33.147 +971 "F16" 0 0 20 40 34.702 36.66 25.315 +1030 "F17" 20 100 70 40 11.115 6.6577 2.5397 +981 "F18" 40 0 20 40 20.866 24.502 22.417 +1040 "F19" 70 100 70 40 5.0452 3.8003 2.5712 +991 "F20" 100 0 20 40 7.0357 11.405 18.83 +848 "F21" 10 70 40 20 25.346 18.217 10.84 +851 "F22" 20 10 40 20 39.003 42.17 24.128 +431 "F23" 20 85 55 0 26.099 16.712 7.7591 +863 "F24" 70 10 40 20 16.884 22.769 20.63 +467 "F25" 70 85 55 0 10.306 8.4104 7.6211 +875 "F26" 0 10 70 20 46.193 47.991 11.698 +440 "F27" 30 85 55 0 22.165 14.664 7.6754 +893 "F28" 40 10 70 20 26.604 31.587 10.802 +476 "F29" 85 85 55 0 7.4139 6.8546 7.6769 +731 "F30" 0 10 0 20 54.196 54.229 48.696 +1280 "F31" 0 100 100 100 2.4602 2.0877 0.85588 +749 "F32" 40 10 0 20 32.888 36.102 43.757 +1286 "F33" 100 100 100 100 1.6821 1.6669 0.84474 +91 "G1" 10 0 10 0 72.655 77.501 63.478 +151 "G2" 85 70 10 0 10.431 9.6758 21.099 +127 "G3" 55 0 10 0 36.695 45.049 54.358 +115 "G4" 30 70 10 0 27.027 19.626 22.285 +74 "G5" 100 10 0 0 15.768 22.473 51.333 +799 "G6" 100 40 10 20 8.0418 10.464 23.122 +901 "G7" 70 40 70 20 11.548 14.047 7.522 +787 "G8" 40 40 10 20 24.018 23.121 26.467 +889 "G9" 20 40 70 20 27.785 26.194 8.1213 +769 "G10" 0 40 10 20 39.7 34.449 29.069 +92 "G11" 10 10 10 0 66.392 68.104 57.186 +152 "G12" 85 85 10 0 9.0576 7.294 17.214 +128 "G13" 55 10 10 0 33.535 39.592 49.444 +116 "G14" 30 85 10 0 23.803 15.085 17.589 +236 "G15" 100 10 20 0 13.328 21.135 37.261 +323 "G16" 100 85 30 0 5.8909 5.7585 12.626 +587 "G17" 20 10 85 0 48.263 52.997 9.1566 +214 "G18" 55 70 20 0 17.587 14.191 18.765 +623 "G19" 70 10 85 0 18.622 27.328 8.567 +178 "G20" 10 70 20 0 35.423 24.586 20.135 +164 "G21" 0 10 20 0 72.096 72.884 50.177 +224 "G22" 70 85 20 0 11.621 8.7959 15.019 +200 "G23" 40 10 20 0 41.66 47.104 43.962 +188 "G24" 20 85 20 0 27.325 17.019 15.415 +398 "G25" 100 10 40 0 11.311 19.793 26.198 +1277 "G26" 100 100 40 100 1.6231 1.463 1.0683 +815 "G27" 20 10 20 20 41.466 44.041 34.957 +1271 "G28" 0 100 40 100 2.3835 1.8323 0.95284 +827 "G29" 70 10 20 20 18.625 23.963 30.049 +233 "G30" 85 85 20 0 8.7078 7.2232 14.921 +596 "G31" 30 10 85 0 41.074 46.949 8.98 +197 "G32" 30 85 20 0 23.387 14.972 15.204 +632 "G33" 85 10 85 0 12.726 21.931 8.5728 +873 "H1" 100 100 40 20 4.0344 3.542 6.6923 +826 "H2" 70 0 20 20 20.193 26.962 32.911 +861 "H3" 40 100 40 20 13.132 7.8145 6.7117 +814 "H4" 20 0 20 20 45.011 49.644 38.435 +843 "H5" 0 100 40 20 22.911 12.431 6.8012 +87 "H6" 0 55 10 0 46.808 35.876 30.627 +139 "H7" 70 30 10 0 21.203 24.321 37.669 +123 "H8" 40 55 10 0 27.068 23.111 28.104 +103 "H9" 20 30 10 0 47.872 45.713 43.191 +159 "H10" 100 55 10 0 9.06 10.557 25.56 +734 "H11" 0 70 0 20 30.09 20.627 20.392 +924 "H12" 20 20 100 20 31.964 33.847 4.0427 +752 "H13" 40 70 0 20 17.978 13.534 19.055 +936 "H14" 70 20 100 20 12.98 18.161 3.9702 +764 "H15" 100 70 0 20 6.3548 6.3278 17.789 +176 "H16" 10 40 20 0 47.705 42.092 33.062 +228 "H17" 85 20 20 0 16.859 22.879 35.148 +212 "H18" 55 40 20 0 23.936 24.63 29.499 +192 "H19" 30 20 20 0 44.961 47.088 41.589 +321 "H20" 100 55 30 0 7.8525 10.036 18.64 +967 "H21" 100 20 0 40 6.8416 9.3463 20.683 +1064 "H22" 70 70 100 40 5.5461 5.4696 1.964 +957 "H23" 40 20 0 40 18.62 19.639 24.192 +1054 "H24" 20 70 100 40 12.884 9.8577 1.9285 +947 "H25" 0 20 0 40 30.469 29.407 26.979 +177 "H26" 10 55 20 0 41.029 32.359 25.991 +211 "H27" 55 30 20 0 26.724 29.193 33.776 +213 "H28" 55 55 20 0 20.573 18.917 23.762 +175 "H29" 10 30 20 0 53.082 49.78 38.239 +775 "H30" 10 40 10 20 35.701 31.677 28.433 +1272 "H31" 40 0 40 100 3.1261 3.5912 2.1998 +1273 "H32" 40 40 40 100 1.9912 2.0029 1.3252 +774 "H33" 10 20 10 20 43.922 43.41 37.409 +95 "I1" 10 40 10 0 48.895 42.882 38.427 +147 "I2" 85 20 10 0 17.934 23.473 41.104 +131 "I3" 55 40 10 0 24.753 25.048 34.193 +111 "I4" 30 20 10 0 45.998 47.606 47.873 +78 "I5" 100 55 0 0 9.7782 10.862 29.546 +800 "I6" 100 70 10 20 6.0042 6.2242 15.688 +900 "I7" 70 20 70 20 13.87 18.947 9.4115 +788 "I8" 40 70 10 20 17.773 13.576 16.768 +888 "I9" 20 20 70 20 33.503 35.29 10.418 +770 "I10" 0 70 10 20 29.596 20.42 17.817 +96 "I11" 10 55 10 0 41.647 32.596 29.968 +148 "I12" 85 30 10 0 16.114 20.056 36.433 +132 "I13" 55 55 10 0 21.206 19.17 27.442 +112 "I14" 30 30 10 0 41.422 40.699 42.001 +240 "I15" 100 55 20 0 8.4259 10.278 22.043 +319 "I16" 100 30 30 0 10.129 15.397 25.408 +591 "I17" 20 55 85 0 32 26.649 5.4117 +1396 "I18" 40 27 27 80 4.0894 4.3142 3.5719 +627 "I19" 70 55 85 0 12.285 13.572 5.4121 +174 "I20" 10 20 20 0 58.796 58.174 43.686 +168 "I21" 0 55 20 0 46.124 35.582 26.484 +220 "I22" 70 30 20 0 20.264 23.866 32.415 +204 "I23" 40 55 20 0 26.384 22.796 24.217 +184 "I24" 20 30 20 0 46.704 44.962 37.252 +402 "I25" 100 55 40 0 7.331 9.8658 15.652 +1275 "I26" 100 0 40 100 1.2309 1.6899 1.6818 +1270 "I27" 0 40 40 100 3.2681 3.0735 1.5546 +1269 "I28" 0 0 40 100 2.9669 3.1625 1.83 +1276 "I29" 100 40 40 100 1.4023 1.624 1.4424 +229 "I30" 85 30 20 0 15.201 19.611 31.303 +600 "I31" 30 55 85 0 27.177 23.519 5.3512 +193 "I32" 30 30 20 0 40.3 40.044 36.324 +636 "I33" 85 55 85 0 8.5285 11.006 5.5658 +944 "J1" 100 70 100 20 3.8385 5.3703 2.7357 +756 "J2" 70 20 0 20 18.512 21.78 36.238 +932 "J3" 40 70 100 20 15.288 12.495 2.4705 +744 "J4" 20 20 0 20 39.781 39.757 41.498 +914 "J5" 0 70 100 20 26.393 18.799 2.4983 +83 "J6" 0 10 10 0 73.893 74.191 58.471 +143 "J7" 70 85 10 0 11.962 8.8492 17.294 +119 "J8" 40 10 10 0 43.033 47.993 51.622 +107 "J9" 20 85 10 0 27.765 17.167 17.844 +155 "J10" 100 10 10 0 14.44 21.781 43.785 +805 "J11" 0 40 20 20 38.936 33.996 25.233 +853 "J12" 20 40 40 20 29.539 27.398 16.776 +823 "J13" 40 40 20 20 23.556 22.971 23.03 +865 "J14" 70 40 40 20 12.596 14.604 14.68 +835 "J15" 100 40 20 20 7.5128 10.212 20.015 +172 "J16" 10 0 20 0 70.716 76.081 54.653 +232 "J17" 85 70 20 0 9.9958 9.5404 18.27 +208 "J18" 55 0 20 0 35.457 44.307 46.319 +196 "J19" 30 70 20 0 26.484 19.44 19.264 +317 "J20" 100 10 30 0 12.264 20.462 31.235 +404 "J21" 100 85 40 0 5.5846 5.711 10.662 +182 "J22" 20 10 20 0 56.852 60.272 47.551 +620 "J23" 55 85 85 0 13.193 10.033 3.8012 +218 "J24" 70 10 20 0 24.73 31.961 40.398 +584 "J25" 10 85 85 0 29.365 18.455 3.8402 +173 "J26" 10 10 20 0 64.663 66.846 49.307 +215 "J27" 55 85 20 0 15.327 10.768 15.072 +209 "J28" 55 10 20 0 32.457 38.971 42.15 +179 "J29" 10 85 20 0 31.401 19.157 15.756 +803 "J30" 0 10 20 20 51.767 52.562 36.611 +1274 "J31" 40 100 40 100 2.0691 1.6856 1.0061 +821 "J32" 40 10 20 20 31.147 35.136 32.826 +776 "J33" 10 70 10 20 26.657 18.779 17.602 +1120 "K1" 100 100 20 60 2.7153 2.3352 3.7231 +1161 "K2" 70 0 70 60 5.2448 7.7346 3.9283 +1110 "K3" 40 100 20 60 5.3374 3.4243 3.4798 +1151 "K4" 20 0 70 60 11.544 13.308 4.2438 +1100 "K5" 0 100 20 60 8.2906 4.8151 3.5443 +721 "K6" 100 0 100 0 8.1076 18.912 5.7582 +63 "K7" 70 100 0 0 10.674 6.4968 15.82 +685 "K8" 40 0 100 0 36.515 45.894 5.8715 +27 "K9" 20 100 0 0 25.023 13.172 15.983 +649 "K10" 0 0 100 0 67.831 73.183 6.3566 +397 "K11" 100 0 40 0 12.306 22.492 28.548 +387 "K12" 70 100 40 0 9.5791 6.4536 8.6392 +361 "K13" 40 0 40 0 42.364 51.362 33.35 +351 "K14" 20 100 40 0 23.924 13.131 8.6622 +325 "K15" 0 0 40 0 74.951 79.955 36.787 +73 "K16" 100 0 0 0 17.316 25.666 56.941 +711 "K17" 70 100 100 0 8.1065 6.0234 2.3028 +37 "K18" 40 0 0 0 48.74 55.767 67.464 +675 "K19" 20 100 100 0 22.791 12.85 2.2624 +1367 "K20" 0 0 0 0 83.532 86.556 75.658 +1170 "K21" 100 100 70 60 2.2415 2.1937 1.767 +1111 "K22" 70 0 20 60 6.7365 8.9202 10.488 +1160 "K23" 40 100 70 60 4.9604 3.4503 1.6823 +1101 "K24" 20 0 20 60 13.925 15.376 12.153 +1150 "K25" 0 100 70 60 7.7356 4.82 1.7333 +1268 "K26" 100 100 0 100 1.586 1.3361 1.2371 +929 "K27" 40 10 100 20 25.041 30.091 4.2447 +1262 "K28" 0 100 0 100 2.3217 1.6655 1.0172 +916 "K29" 10 0 100 20 42.296 47.086 4.8745 +54 "K30" 55 100 0 0 14.16 8.1322 15.9 +614 "K31" 55 10 85 0 25.842 33.805 8.7239 +18 "K32" 10 100 0 0 28.671 14.852 16.045 +578 "K33" 10 10 85 0 55.572 59.114 9.4388 +3 "L1" 0 20 0 0 69.597 66.565 60.838 +673 "L2" 20 70 100 0 27.825 20.432 2.9018 +39 "L3" 40 20 0 0 40.572 42.615 53.944 +709 "L4" 70 70 100 0 10.2 10.073 3.0376 +75 "L5" 100 20 0 0 14.395 19.681 46.296 +1149 "L6" 0 70 70 60 8.9749 6.7909 2.3081 +1102 "L7" 20 20 20 60 12.058 12.309 10.166 +1159 "L8" 40 70 70 60 5.4431 4.5543 2.1995 +1112 "L9" 70 20 20 60 5.7816 6.99 8.7996 +1169 "L10" 100 70 70 60 2.3761 2.8104 2.2458 +327 "L11" 0 20 40 0 62.366 61.062 29.931 +349 "L12" 20 70 40 0 29.877 21.69 13.737 +363 "L13" 40 20 40 0 35.967 39.85 27.525 +385 "L14" 70 70 40 0 12.394 11.372 12.969 +399 "L15" 100 20 40 0 10.365 17.364 23.838 +1099 "L16" 0 70 20 60 10.103 7.3489 5.5678 +1152 "L17" 20 20 70 60 10.044 10.668 3.7279 +1109 "L18" 40 70 20 60 6.1706 4.8349 5.1079 +1162 "L19" 70 20 70 60 4.6655 6.2353 3.4497 +1119 "L20" 100 70 20 60 2.8528 2.9433 4.9122 +651 "L21" 0 20 100 0 56.365 55.495 5.0428 +25 "L22" 20 70 0 0 31.967 22.387 26.188 +687 "L23" 40 20 100 0 31.007 35.545 4.8234 +61 "L24" 70 70 0 0 14.307 11.92 24.622 +723 "L25" 100 20 100 0 6.9585 14.721 5.1854 +170 "L26" 0 85 20 0 35.618 21.393 16.061 +598 "L27" 30 30 85 0 34.305 35.444 7.2538 +206 "L28" 40 85 20 0 19.826 13.094 15.005 +634 "L29" 85 30 85 0 10.637 16.574 7.1922 +739 "L30" 10 40 0 20 36.361 31.929 32.553 +923 "L31" 20 10 100 20 34.532 38.16 4.3913 +1264 "L32" 40 40 0 100 3.025 2.9233 2.7653 +941 "L33" 100 10 100 20 5.8796 12.705 4.4488 +725 "M1" 100 40 100 0 5.6657 10.768 4.437 +59 "M2" 70 40 0 0 19.82 20.835 38.153 +689 "M3" 40 40 100 0 25.449 25.81 3.9199 +23 "M4" 20 40 0 0 43.988 39.086 43.242 +653 "M5" 0 40 100 0 46.826 40.833 4.0776 +401 "M6" 100 40 40 0 8.508 12.792 19.04 +383 "M7" 70 40 40 0 15.911 18.715 19.337 +365 "M8" 40 40 40 0 28.9 28.624 21.058 +347 "M9" 20 40 40 0 39.477 36.198 21.787 +329 "M10" 0 40 40 0 51.464 44.787 23.357 +1118 "M11" 100 40 20 60 3.2583 4.1229 6.8222 +1163 "M12" 70 40 70 60 4.1138 4.892 2.905 +1108 "M13" 40 40 20 60 7.7936 7.5097 7.7076 +1153 "M14" 20 40 70 60 8.5393 8.172 3.0297 +1098 "M15" 0 40 20 60 12.826 11.415 8.5245 +77 "M16" 100 40 0 0 11.697 14.469 36.691 +707 "M17" 70 40 100 0 13.879 17.302 4.1153 +41 "M18" 40 40 0 0 32.737 30.791 41.385 +671 "M19" 20 40 100 0 36.014 33.591 3.9651 +5 "M20" 0 40 0 0 56.387 48.115 45.856 +1168 "M21" 100 40 70 60 2.5594 3.7583 2.8973 +1113 "M22" 70 40 20 60 4.8984 5.29 7.0265 +1158 "M23" 40 40 70 60 6.4157 6.5513 2.9637 +1103 "M24" 20 40 20 60 10.233 9.4169 8.1469 +1148 "M25" 0 40 70 60 10.845 9.911 3.1434 +1266 "M26" 100 0 0 100 1.2487 1.6429 2.2918 +1281 "M27" 40 0 100 100 1.9438 2.3714 0.98627 +1260 "M28" 0 0 0 100 2.6842 2.7971 2.1442 +919 "M29" 10 40 100 20 30.294 27.803 3.3279 +231 "M30" 85 55 20 0 11.711 12.702 22.558 +618 "M31" 55 55 85 0 17.046 16.86 5.3492 +195 "M32" 30 55 20 0 30.939 25.813 24.749 +582 "M33" 10 55 85 0 36.867 29.737 5.4828 +7 "N1" 0 70 0 0 41.064 27.7 27.285 +669 "N2" 20 20 100 0 43.633 45.789 4.9326 +43 "N3" 40 70 0 0 23.633 17.469 25.317 +705 "N4" 70 20 100 0 16.392 23.335 4.9379 +79 "N5" 100 70 0 0 8.126 7.9858 23.641 +1147 "N6" 0 20 70 60 12.587 12.713 3.8151 +1104 "N7" 20 70 20 60 8.0904 6.0557 5.3789 +1157 "N8" 40 20 70 60 7.5476 8.6054 3.5775 +1114 "N9" 70 70 20 60 4.1371 3.6437 4.9376 +1167 "N10" 100 20 70 60 2.7694 4.6386 3.3816 +331 "N11" 0 70 40 0 38.555 26.426 14.048 +345 "N12" 20 20 40 0 48.9 50.47 29.029 +367 "N13" 40 70 40 0 21.346 16.675 13.201 +381 "N14" 70 20 40 0 20.214 26.349 24.972 +403 "N15" 100 70 40 0 6.3393 7.4821 12.814 +1097 "N16" 0 20 20 60 15.268 14.982 10.814 +1154 "N17" 20 70 70 60 7.1701 5.6409 2.2541 +1107 "N18" 40 20 20 60 9.4122 10.149 9.7345 +1164 "N19" 70 70 70 60 3.633 3.517 2.2485 +1117 "N20" 100 20 20 60 3.6367 5.1854 8.293 +655 "N21" 0 70 100 0 36.241 25.005 2.8668 +21 "N22" 20 20 0 0 54.93 54.652 57.434 +691 "N23" 40 70 100 0 19.499 15.579 2.9145 +57 "N24" 70 20 0 0 24.704 28.996 49.5 +727 "N25" 100 70 100 0 4.5299 6.5549 3.3469 +166 "N26" 0 30 20 0 59.495 54.521 39.124 +602 "N27" 30 85 85 0 21.419 14.317 3.7354 +202 "N28" 40 30 20 0 34.441 35.391 35.145 +638 "N29" 85 85 85 0 6.544 6.5053 3.8482 +767 "N30" 0 10 10 20 52.858 53.287 42.421 +1279 "N31" 0 40 100 100 2.5356 2.5301 0.93376 +785 "N32" 40 10 10 20 32.161 35.818 38.145 +1285 "N33" 100 40 100 100 1.3834 1.7214 0.9272 +1116 "O1" 100 0 20 60 4.0233 6.3135 9.7349 +1165 "O2" 70 100 70 60 3.3702 2.7166 1.7203 +1106 "O3" 40 0 20 60 10.885 12.753 11.594 +1155 "O4" 20 100 70 60 6.3144 4.1045 1.6767 +1096 "O5" 0 0 20 60 17.552 18.567 13.037 +729 "O6" 100 100 100 0 3.664 3.8734 2.2561 +55 "O7" 70 0 0 0 30.004 38.132 61.496 +693 "O8" 40 100 100 0 15.807 9.6095 2.2784 +19 "O9" 20 0 0 0 66.155 71.475 71.889 +657 "O10" 0 100 100 0 30.384 16.417 2.1357 +1166 "O11" 100 0 70 60 2.9468 5.4992 3.8083 +1115 "O12" 70 100 20 60 3.6881 2.6925 3.5312 +1156 "O13" 40 0 70 60 8.7063 10.81 4.1087 +1105 "O14" 20 100 20 60 6.8185 4.1293 3.5251 +1146 "O15" 0 0 70 60 14.463 15.773 4.3803 +405 "O16" 100 100 40 0 4.8934 4.227 8.7463 +379 "O17" 70 0 40 0 24.306 34.442 30.471 +369 "O18" 40 100 40 0 17.036 9.9095 8.4951 +343 "O19" 20 0 40 0 57.937 65.282 35.342 +333 "O20" 0 100 40 0 31.525 16.713 8.6926 +81 "O21" 100 100 0 0 5.9847 4.2869 15.899 +703 "O22" 70 0 100 0 19.672 30.335 5.7155 +45 "O23" 40 100 0 0 18.459 10.135 16.046 +667 "O24" 20 0 100 0 51.529 59.137 6.0021 +9 "O25" 0 100 0 0 32.54 16.641 16.24 +755 "O26" 70 10 0 20 20.246 24.805 40.149 +1283 "O27" 40 100 100 100 2.1248 1.9121 0.8517 +743 "O28" 20 10 0 20 43.426 45.187 46.138 +921 "O29" 10 100 100 20 19.493 11.05 1.9553 +227 "O30" 85 10 20 0 18.576 26.246 38.785 +702 "O31" 55 100 100 0 11.506 7.6255 2.2956 +191 "O32" 30 10 20 0 49.042 53.595 46.035 +666 "O33" 10 100 100 0 26.519 14.59 2.2261 +72 "P1" 85 100 0 0 8.1029 5.2979 15.827 +340 "P2" 10 70 40 0 34.272 24.137 13.968 +483 "P3" 100 55 55 0 6.5506 9.3542 11.193 +1047 "P4" 0 20 100 40 24.607 24.742 2.8903 +966 "P5" 100 0 0 40 7.8864 11.734 24.621 +1041 "P6" 100 0 70 40 4.9885 9.9939 6.791 +972 "P7" 0 20 20 40 29.603 28.971 20.875 +1043 "P8" 100 40 70 40 3.8205 6.1544 4.8109 +332 "P9" 0 85 40 0 34.717 21.033 11.097 +153 "P10" 85 100 10 0 7.7939 5.3137 13.803 +1020 "P11" 100 100 40 40 3.1018 2.7833 4.3737 +341 "P12" 10 85 40 0 30.755 19.063 11.102 +645 "P13" 100 55 85 0 5.452 8.6712 5.5797 +337 "P14" 10 30 40 0 50.294 47.764 25.922 +1016 "P15" 100 0 40 40 6.031 10.716 12.899 +975 "P16" 0 100 20 40 14.692 8.0191 6.1097 +1026 "P17" 20 0 70 40 23.972 27.505 7.9233 +985 "P18" 40 100 20 40 8.8642 5.2767 5.9276 +1036 "P19" 70 0 70 40 10.213 15.128 7.1568 +995 "P20" 100 100 20 40 3.4196 2.8206 6.1531 +839 "P21" 0 10 40 20 48.941 50.379 24.983 +413 "P22" 0 85 55 0 34.221 20.855 7.9812 +857 "P23" 40 10 40 20 28.681 33.245 22.467 +449 "P24" 40 85 55 0 18.641 12.797 7.6507 +869 "P25" 100 10 40 20 8.6715 14.974 19.269 +504 "P26" 10 100 70 0 27.004 14.761 4.3751 +1403 "P27" 20 12 12 100 2.3544 2.4509 1.8872 +540 "P28" 55 100 70 0 12.158 7.8497 4.4409 +137 "P29" 70 10 10 0 25.806 32.587 47.472 +585 "P30" 10 100 85 0 26.788 14.692 3.1709 +190 "P31" 30 0 20 0 53.459 60.877 50.898 +621 "P32" 55 100 85 0 11.844 7.745 3.2349 +226 "P33" 85 0 20 0 20.45 29.973 42.588 +388 "Q1" 85 0 40 0 17.714 28.071 29.4 +354 "Q2" 30 20 40 0 42.187 45.057 28.305 +392 "Q3" 85 40 40 0 11.752 15.464 19.015 +954 "Q4" 20 70 0 40 15.189 11.028 12.597 +1065 "Q5" 70 100 100 40 4.8713 3.7963 1.602 +990 "Q6" 70 100 20 40 5.5746 3.7845 5.993 +1029 "Q7" 20 70 70 40 13.561 10.362 3.7644 +988 "Q8" 70 40 20 40 9.1547 10.107 13.621 +346 "Q9" 20 30 40 0 44.17 43.164 25.365 +380 "Q10" 70 10 40 0 22.221 30.224 27.687 +1011 "Q11" 70 0 40 40 11.281 15.853 13.82 +355 "Q12" 30 30 40 0 38.083 38.522 24.763 +393 "Q13" 85 55 40 0 10.347 12.121 15.787 +359 "Q14" 30 85 40 0 22.653 14.886 10.796 +1015 "Q15" 70 100 40 40 5.2597 3.798 4.3099 +1044 "Q16" 100 70 70 40 3.254 4.0928 3.564 +987 "Q17" 70 20 20 40 11.085 13.59 17.234 +1034 "Q18" 40 70 70 40 9.9768 8.179 3.6375 +977 "Q19" 20 20 20 40 23.514 24.033 19.687 +1024 "Q20" 0 70 70 40 17.151 12.502 3.8006 +461 "Q21" 70 10 55 0 20.669 29.041 19.486 +447 "Q22" 40 55 55 0 24.03 21.485 11.773 +425 "Q23" 20 10 55 0 51.206 55.742 22.11 +411 "Q24" 0 55 55 0 43.461 33.955 12.825 +844 "Q25" 10 0 40 20 47.596 52.102 27.007 +637 "Q26" 85 70 85 0 7.3644 8.3584 4.5689 +129 "Q27" 55 20 10 0 30.721 34.726 44.429 +601 "Q28" 30 70 85 0 23.755 18.069 4.3796 +1398 "Q29" 10 6 6 80 7.0105 7.296 6.1388 +629 "Q30" 70 85 85 0 9.3822 8 3.8025 +130 "Q31" 55 30 10 0 27.711 29.708 39.164 +593 "Q32" 20 85 85 0 25.359 16.384 3.7822 +94 "Q33" 10 30 10 0 54.468 50.787 44.521 +36 "R1" 30 100 0 0 21.556 11.576 16.017 +376 "R2" 55 70 40 0 16.504 13.853 13.09 +374 "R3" 55 40 40 0 22.024 23.501 20.533 +1057 "R4" 40 20 100 40 13.816 15.962 2.8108 +956 "R5" 40 0 0 40 22.126 25.279 29.614 +1031 "R6" 40 0 70 40 17.712 21.988 7.536 +982 "R7" 40 20 20 40 17.794 19.295 18.567 +1033 "R8" 40 40 70 40 12.562 12.837 5.1282 +368 "R9" 40 85 40 0 19.001 12.943 10.645 +603 "R10" 30 100 85 0 19.457 11.28 3.1632 +1010 "R11" 40 100 40 40 8.5279 5.2899 4.3262 +377 "R12" 55 85 40 0 14.612 10.65 10.619 +375 "R13" 55 55 40 0 18.962 18.108 16.309 +373 "R14" 55 30 40 0 24.694 27.954 23.328 +1006 "R15" 40 0 40 40 18.948 22.947 15.201 +973 "R16" 0 40 20 40 24.664 21.767 16.297 +1028 "R17" 20 40 70 40 17.171 16.287 5.3583 +983 "R18" 40 40 20 40 14.701 14.35 14.562 +1038 "R19" 70 40 70 40 7.3528 8.9015 4.9868 +993 "R20" 100 40 20 40 5.1831 6.9074 12.774 +846 "R21" 10 20 40 20 40.547 40.963 22.243 +409 "R22" 0 30 55 0 54.926 51.085 18.191 +427 "R23" 20 30 55 0 42.488 41.9 17.571 +445 "R24" 40 30 55 0 31.051 33.066 16.879 +463 "R25" 70 30 55 0 17.193 21.865 15.919 +581 "R26" 10 40 85 0 42.195 38.09 6.6443 +105 "R27" 20 55 10 0 36.68 29.43 29.303 +617 "R28" 55 40 85 0 19.79 21.875 6.4114 +141 "R29" 70 55 10 0 16.099 15.606 26.608 +573 "R30" 0 55 85 0 41.699 32.615 5.5737 +194 "R31" 30 40 20 0 36.038 33.681 31.325 +609 "R32" 40 55 85 0 22.77 20.607 5.3262 +1401 "R33" 60 45 45 100 1.7812 1.8318 1.268 +352 "S1" 30 0 40 0 49.662 57.958 34.147 +390 "S2" 85 20 40 0 14.827 21.597 24.334 +356 "S3" 30 40 40 0 33.95 32.236 21.274 +964 "S4" 70 70 0 40 7.5493 6.3917 11.819 +1055 "S5" 20 100 100 40 10.959 6.6601 1.5403 +980 "S6" 20 100 20 40 11.707 6.6171 6.0027 +1039 "S7" 70 70 70 40 6.0395 5.8289 3.6118 +978 "S8" 20 40 20 40 19.514 17.974 15.42 +382 "S9" 70 30 40 0 18.083 22.458 22.119 +344 "S10" 20 10 40 0 53.406 57.674 32.389 +1001 "S11" 20 0 40 40 25.382 28.644 16.04 +391 "S12" 85 30 40 0 13.302 18.459 21.66 +357 "S13" 30 55 40 0 29.224 24.877 17.005 +395 "S14" 85 85 40 0 7.9931 7.0878 10.595 +1005 "S15" 20 100 40 40 11.468 6.6943 4.4176 +724 "S16" 100 30 100 0 6.3165 12.701 4.8035 +475 "S17" 85 70 55 0 8.3754 8.8787 9.2435 +453 "S18" 55 20 55 0 25.734 31.381 18.381 +439 "S19" 30 70 55 0 24.743 18.69 9.4446 +417 "S20" 10 20 55 0 53.632 54.204 20.345 +969 "S21" 100 70 0 40 4.4711 4.4108 11.353 +1062 "S22" 70 20 100 40 7.8759 11.047 2.8479 +959 "S23" 40 70 0 40 11.581 8.7901 12.166 +1052 "S24" 20 20 100 40 19.245 20.452 2.8827 +949 "S25" 0 70 0 40 18.935 13.249 13.033 +633 "S26" 85 20 85 0 11.655 19.202 7.8973 +133 "S27" 55 70 10 0 17.99 14.262 21.622 +597 "S28" 30 20 85 0 37.726 41.19 8.1607 +97 "S29" 10 70 10 0 35.88 24.704 23.163 +625 "S30" 70 30 85 0 15.417 20.507 7.1937 +134 "S31" 55 85 10 0 15.742 10.852 17.356 +589 "S32" 20 30 85 0 40.348 40.08 7.3947 +98 "S33" 10 85 10 0 31.817 19.291 18.193 +741 "T1" 10 100 0 20 20.92 11.046 11.999 +485 "T2" 100 85 55 0 5.0324 5.4618 7.6477 +338 "T3" 10 40 40 0 45.243 40.342 22.51 +1067 "T4" 100 20 100 40 3.8495 7.4907 2.9603 +946 "T5" 0 0 0 40 36.398 37.862 33.394 +1021 "T6" 0 0 70 40 30.347 32.944 8.1709 +992 "T7" 100 20 20 40 6.1477 9.1347 15.967 +1023 "T8" 0 40 70 40 21.896 19.753 5.4679 +566 "T9" 100 85 70 0 4.5612 5.2604 5.3847 +777 "T10" 10 100 10 20 20.725 11.051 10.533 +1000 "T11" 0 100 40 40 14.375 8.0539 4.4273 +647 "T12" 100 85 85 0 4.2488 5.1601 3.9195 +339 "T13" 10 55 40 0 39.423 31.475 17.861 +643 "T14" 100 30 85 0 6.846 13.168 7.2542 +996 "T15" 0 0 40 40 31.718 34.029 16.657 +415 "T16" 10 0 55 0 63.997 70.524 24.637 +261 "T17" 10 100 30 0 27.879 14.844 10.292 +451 "T18" 55 0 55 0 30.669 40.701 22.033 +297 "T19" 55 100 30 0 13.148 8 10.121 +146 "T20" 85 10 10 0 19.783 26.989 45.684 +1050 "T21" 0 100 100 40 13.872 8.1063 1.6531 +951 "T22" 20 0 0 40 29.196 31.595 31.502 +1060 "T23" 40 100 100 40 8.0648 5.252 1.5254 +961 "T24" 70 0 0 40 13.926 17.677 27.023 +1070 "T25" 100 100 100 40 2.6364 2.7274 1.6146 +577 "T26" 10 0 85 0 60.79 67.419 10.52 +522 "T27" 30 100 70 0 19.696 11.34 4.3352 +613 "T28" 55 0 85 0 28.109 38.325 9.5404 +1400 "T29" 80 65 65 100 1.6643 1.6892 1.0143 +569 "T30" 0 10 85 0 62.99 65.129 9.7311 +117 "T31" 30 100 10 0 21.029 11.408 13.717 +605 "T32" 40 10 85 0 34.556 41.383 8.8728 +80 "T33" 100 85 0 0 6.9675 5.9349 19.448 +772 "U1" 10 0 10 20 52.057 55.554 45.831 +773 "U2" 10 10 10 20 47.759 49.162 41.538 +1399 "U3" 100 85 85 100 1.6187 1.6394 0.89845 +737 "U4" 10 10 0 20 48.784 49.713 47.296 +1408 "U5" 100 100 0 70 2.3691 1.9568 3.3718 +1409 "U6" 100 0 100 70 2.199 4.0799 1.855 +1410 "U7" 0 100 100 70 5.5848 3.7343 1.077 +1411 "U8" 40 40 0 70 5.7934 5.5192 6.6041 +1412 "U9" 40 0 40 70 6.7115 8.06 5.5183 +1413 "U10" 0 40 40 70 7.6432 6.9353 3.875 +940 "U11" 100 0 100 20 6.04 13.773 4.7479 +759 "U12" 70 100 0 20 8.2981 5.199 11.988 +928 "U13" 40 0 100 20 26.943 33.718 4.5748 +747 "U14" 20 100 0 20 18.477 9.8932 11.977 +910 "U15" 0 0 100 20 47.123 51.088 4.9814 +917 "U16" 10 10 100 20 39.259 42.042 4.485 +881 "U17" 10 10 70 20 41.368 44.128 11.608 +1211 "U18" 100 100 0 80 2.0214 1.6659 2.424 +672 "U19" 20 55 100 0 31.603 26.297 3.3949 +26 "U20" 20 85 0 0 28.234 17.308 20.611 +717 "U21" 85 55 100 0 8.0848 10.71 3.7004 +1227 "U22" 100 100 40 80 2.0471 1.8756 1.7224 +510 "U23" 20 55 70 0 32.554 27.054 8.1303 +269 "U24" 20 85 30 0 26.943 16.945 13.047 +555 "U25" 85 55 70 0 9.0366 11.316 7.9158 +396 "U26" 85 100 40 0 7.0104 5.2661 8.714 +290 "U27" 55 10 30 0 31.15 38.101 34.962 +360 "U28" 30 100 40 0 20.282 11.426 8.5811 +254 "U29" 10 10 30 0 62.704 65.312 40.658 +477 "U30" 85 100 55 0 6.5143 5.1384 6.2913 +47 "U31" 55 10 0 0 34.846 40.287 57.695 +441 "U32" 30 100 55 0 20.015 11.407 6.151 +11 "U33" 10 10 0 0 68.278 69.374 66.073 +1429 "V1" 7 0 0 7 70.46 73.92 67.261 +1404 "V2" 10 6 6 100 2.5919 2.6833 2.038 +1431 "V3" 7 7 0 7 66.447 68.034 62.702 +1390 "V4" 40 27 27 60 8.7749 9.2534 8.0212 +1433 "V5" 7 0 7 7 69.32 73.145 61.541 +1386 "V6" 10 6 6 40 30.82 32.145 28.568 +1435 "V7" 7 7 7 7 65.491 67.488 57.736 +1405 "V8" 100 0 0 70 3.3139 4.757 8.8718 +1406 "V9" 0 100 0 70 5.8208 3.4664 3.0148 +1407 "V10" 0 0 100 70 9.7971 10.768 1.8949 +732 "V11" 0 20 0 20 49.803 47.82 43.811 +926 "V12" 20 70 100 20 20.873 15.756 2.4785 +750 "V13" 40 20 0 20 30.145 31.768 39.381 +938 "V14" 70 70 100 20 8.5654 8.4502 2.5669 +762 "V15" 100 20 0 20 10.566 14.545 33.543 +736 "V16" 10 0 0 20 53.253 56.303 52.493 +811 "V17" 10 40 20 20 35.05 31.312 24.766 +14 "V18" 10 40 0 0 50.032 43.488 44.395 +688 "V19" 40 30 100 0 28.329 30.701 4.3862 +1206 "V20" 70 70 0 80 2.8994 2.5411 3.343 +1245 "V21" 0 40 100 80 5.6651 5.3425 1.396 +257 "V22" 10 40 30 0 46.555 41.32 27.521 +526 "V23" 40 30 70 0 29.655 31.955 11.194 +1222 "V24" 70 70 40 80 2.7614 2.6125 2.2024 +1229 "V25" 0 40 70 80 5.6798 5.2963 1.9558 +503 "V26" 10 85 70 0 29.757 18.637 5.4744 +274 "V27" 30 30 30 0 39.358 39.499 30.446 +1239 "V28" 70 100 70 80 2.5641 2.2416 1.3396 +1225 "V29" 100 40 40 80 2.1514 2.6607 2.8621 +665 "V30" 10 85 100 0 29.156 18.326 2.5466 +31 "V31" 30 30 0 0 42.828 41.521 48.982 +1255 "V32" 70 100 100 80 2.6193 2.3714 1.1818 +1209 "V33" 100 40 0 80 2.2087 2.5172 4.2884 +1480 "W1" 40 40 3 40 15.491 14.783 18.578 +1481 "W2" 3 0 40 40 30.835 33.282 16.572 +1482 "W3" 0 3 40 40 31.096 33.009 16.254 +1483 "W4" 3 3 40 40 30.215 32.277 16.188 +1484 "W5" 40 3 40 40 18.485 22.123 14.796 +1485 "W6" 3 40 40 40 22.306 19.937 10.964 +1425 "W7" 7 7 0 0 72.916 74.562 68.933 +1426 "W8" 7 0 7 0 76.125 80.385 67.437 +1368 "W9" 100 85 85 0 4.2488 5.1601 3.9195 +1428 "W10" 7 7 7 0 71.61 73.677 62.845 +943 "W11" 100 40 100 20 4.7403 8.5831 3.5312 +757 "W12" 70 40 0 20 15.154 16.041 28.325 +931 "W13" 40 40 100 20 19.538 20.131 3.252 +745 "W14" 20 40 0 20 32.397 29.093 31.919 +913 "W15" 0 40 100 20 33.852 30.15 3.3353 +882 "W16" 10 20 70 20 38.037 38.894 10.534 +880 "W17" 10 0 70 20 44.871 49.726 12.722 +67 "W18" 85 30 0 0 17.178 20.552 42.38 +1248 "W19" 40 0 100 80 4.5413 5.658 1.6254 +30 "W20" 30 20 0 0 47.48 48.506 55.784 +713 "W21" 85 10 100 0 12.311 21.57 5.442 +310 "W22" 85 30 30 0 14.221 19.029 26.141 +1232 "W23" 40 0 70 80 4.7088 5.7638 2.5301 +273 "W24" 30 20 30 0 43.523 46.036 34.48 +551 "W25" 85 10 70 0 13.518 22.62 12.816 +1226 "W26" 100 70 40 80 2.1369 2.2414 2.227 +517 "W27" 30 30 70 0 35.091 36.183 11.185 +276 "W28" 30 55 30 0 30.089 25.385 20.636 +1241 "W29" 100 40 70 80 2.0726 2.6889 1.9514 +1210 "W30" 100 70 0 80 2.1361 2.0453 3.268 +679 "W31" 30 30 100 0 33.743 34.869 4.3993 +33 "W32" 30 55 0 0 32.472 26.538 33.225 +1257 "W33" 100 40 100 80 2.0186 2.7006 1.4305 +1418 "X1" 3 0 0 3 78.087 81.304 72.192 +1471 "X2" 3 40 0 40 24.528 21.469 20.832 +1416 "X3" 0 3 3 0 80.718 82.931 70.76 +1473 "X4" 3 0 3 40 35.081 36.717 31.93 +1474 "X5" 40 0 3 40 21.975 25.198 28.589 +1450 "X6" 0 40 0 3 54.231 46.336 44.17 +1402 "X7" 40 27 27 100 1.9928 2.0843 1.578 +1451 "X8" 3 40 0 3 52.764 45.324 43.924 +1478 "X9" 0 40 3 40 25.167 21.969 20.329 +1479 "X10" 3 40 3 40 24.43 21.434 20.192 +238 "X11" 100 30 20 0 10.953 15.845 30.175 +114 "X12" 30 55 10 0 31.677 26.184 28.689 +607 "X13" 40 30 85 0 28.89 31.247 7.2553 +726 "X14" 100 55 100 0 5.0689 8.4682 3.8849 +571 "X15" 0 30 85 0 52.284 48.719 7.5835 +797 "X16" 100 10 10 20 10.649 16.035 32.114 +885 "X17" 10 100 70 20 19.827 11.129 3.648 +10 "X18" 10 0 0 0 75.117 79.325 73.888 +296 "X19" 55 85 30 0 14.958 10.704 12.697 +38 "X20" 40 10 0 0 44.525 48.85 60.426 +259 "X21" 10 70 30 0 34.846 24.357 16.865 +496 "X22" 10 0 70 0 61.985 68.656 16.253 +701 "X23" 55 85 100 0 12.855 9.8743 2.6087 +524 "X24" 40 10 70 0 35.387 42.195 13.842 +664 "X25" 10 70 100 0 32.058 22.77 2.9035 +499 "X26" 10 30 70 0 47.31 45.407 11.733 +278 "X27" 30 85 30 0 22.93 14.868 12.814 +535 "X28" 55 30 70 0 22.277 26.128 10.97 +314 "X29" 85 85 30 0 8.3629 7.174 12.667 +661 "X30" 10 30 100 0 45.626 43.799 4.5115 +35 "X31" 30 85 0 0 24.287 15.231 20.388 +697 "X32" 55 30 100 0 20.902 24.885 4.4299 +71 "X33" 85 85 0 0 9.4362 7.3338 19.684 +1455 "2A1" 0 40 3 3 53.993 46.223 42.661 +1461 "2A2" 0 3 40 3 70.383 74.295 34.724 +1448 "2A3" 40 0 0 3 47.195 53.953 64.998 +1449 "2A4" 40 3 0 3 46.112 52.161 63.209 +1464 "2A5" 0 40 40 3 49.506 43.168 22.613 +1465 "2A6" 3 40 40 3 48.143 42.244 22.502 +1419 "2A7" 0 3 0 3 78.35 80.439 70.76 +1467 "2A8" 3 0 0 40 35.38 36.979 33.117 +1468 "2A9" 0 3 0 40 35.504 36.593 32.45 +1469 "2A10" 3 3 0 40 34.505 35.714 32.02 +945 "2A11" 100 100 100 20 3.2509 3.4049 2.0486 +754 "2A12" 70 0 0 20 22.089 28.05 44.087 +933 "2A13" 40 100 100 20 12.377 7.6664 1.9719 +742 "2A14" 20 0 0 20 47.424 51.241 51.225 +915 "2A15" 0 100 100 20 21.691 12.124 1.9921 +884 "2A16" 10 70 70 20 24.384 17.733 5.538 +833 "2A17" 100 10 20 20 9.9756 15.72 27.732 +1259 "2A18" 100 100 100 80 2.113 2.1285 1.1794 +1200 "2A19" 40 0 0 80 5.5174 6.2971 6.7766 +674 "2A20" 20 85 100 0 25.196 16.32 2.5594 +65 "2A21" 85 10 0 0 21.358 27.932 53.463 +1243 "2A22" 100 100 70 80 2.0847 2.0389 1.3911 +1216 "2A23" 40 0 40 80 4.9677 5.9205 3.9901 +512 "2A24" 20 85 70 0 25.681 16.532 5.3409 +308 "2A25" 85 10 30 0 17.37 25.474 32.287 +1224 "2A26" 100 0 40 80 2.1836 3.3985 3.6401 +521 "2A27" 30 85 70 0 21.75 14.466 5.2726 +272 "2A28" 30 10 30 0 47.416 52.397 38.143 +557 "2A29" 85 85 70 0 6.8894 6.6312 5.3769 +1208 "2A30" 100 0 0 80 2.454 3.4793 5.9636 +683 "2A31" 30 85 100 0 21.18 14.225 2.5591 +29 "2A32" 30 10 0 0 52.057 55.456 62.416 +719 "2A33" 85 85 100 0 6.2798 6.3626 2.7033 +730 "2B1" 0 0 0 20 58.945 61.186 53.819 +927 "2B2" 20 100 100 20 17.147 9.9331 1.9505 +748 "2B3" 40 0 0 20 35.871 40.917 48.425 +939 "2B4" 70 100 100 20 6.9229 5.146 2.0433 +760 "2B5" 100 0 0 20 12.282 18.294 40.046 +879 "2B6" 0 100 70 20 22.293 12.288 3.6598 +778 "2B7" 20 0 10 20 46.281 50.535 44.514 +897 "2B8" 40 100 70 20 12.747 7.7894 3.6629 +790 "2B9" 70 0 10 20 21.117 27.534 38.328 +909 "2B10" 100 100 70 20 3.5374 3.4661 3.6518 +802 "2B11" 0 0 20 20 56.601 59.66 40.624 +855 "2B12" 20 100 40 20 17.914 10.071 6.8185 +820 "2B13" 40 0 20 20 33.673 39.499 36.055 +867 "2B14" 70 100 40 20 7.6254 5.2461 6.6314 +832 "2B15" 100 0 20 20 10.812 17.701 30.133 +808 "2B16" 10 0 20 20 50.801 54.679 39.587 +809 "2B17" 10 10 20 20 46.797 48.543 36.027 +662 "2B18" 10 40 100 0 41.429 37.335 4.0161 +40 "2B19" 40 30 0 0 36.619 36.559 47.537 +1254 "2B20" 70 70 100 80 2.5715 2.5962 1.2142 +1197 "2B21" 0 40 0 80 6.4426 5.6762 4.9924 +500 "2B22" 10 40 70 0 42.89 38.711 10.283 +283 "2B23" 40 30 30 0 33.8 35.223 30.04 +1238 "2B24" 70 70 70 80 2.634 2.5979 1.5788 +1213 "2B25" 0 40 40 80 5.8329 5.3275 2.9845 +494 "2B26" 0 85 70 0 33.767 20.676 5.531 +1220 "2B27" 70 0 40 80 3.3832 4.5369 3.8556 +530 "2B28" 40 85 70 0 18.281 12.652 5.3328 +245 "2B29" 0 10 30 0 70.09 71.322 41.218 +656 "2B30" 0 85 100 0 33.072 20.292 2.4698 +1204 "2B31" 70 0 0 80 3.7611 4.7312 6.4057 +692 "2B32" 40 85 100 0 17.532 12.31 2.5727 +2 "2B33" 0 10 0 0 76.386 76.234 68.218 +942 "2C1" 100 20 100 20 5.5861 11.454 4.1569 +758 "2C2" 70 70 0 20 11.215 9.5001 18.543 +930 "2C3" 40 20 100 20 23.258 26.76 3.9332 +746 "2C4" 20 70 0 20 23.84 17.025 19.697 +912 "2C5" 0 20 100 20 40.492 40.409 4.1293 +157 "2C6" 100 30 10 0 11.881 16.348 35.184 +599 "2C7" 30 40 85 0 31.083 30.156 6.447 +121 "2C8" 40 30 10 0 35.412 35.905 40.726 +635 "2C9" 85 40 85 0 9.8396 14.293 6.4971 +85 "2C10" 0 30 10 0 60.945 55.586 45.669 +870 "2C11" 100 20 40 20 7.9757 13.201 17.586 +830 "2C12" 70 70 20 20 10.44 9.2485 14.123 +858 "2C13" 40 20 40 20 26.608 29.542 20.401 +818 "2C14" 20 70 20 20 23.192 16.881 15.065 +840 "2C15" 0 20 40 20 44.975 44.4 22.596 +883 "2C16" 10 40 70 20 31.53 28.832 8.2102 +761 "2C17" 100 10 0 20 11.442 16.401 36.797 +715 "2C18" 85 30 100 0 10.092 16.111 4.6429 +24 "2C19" 20 55 0 0 37.446 29.749 33.815 +678 "2C20" 30 20 100 0 37.103 40.577 4.8694 +69 "2C21" 85 55 0 0 12.993 13.191 30.047 +553 "2C22" 85 30 70 0 11.284 17.084 10.6 +267 "2C23" 20 55 30 0 35.127 28.689 21.218 +516 "2C24" 30 20 70 0 38.702 42.133 12.782 +312 "2C25" 85 55 30 0 11.038 12.408 18.913 +303 "2C26" 70 55 30 0 14.846 15.086 19.26 +508 "2C27" 20 30 70 0 41.217 40.898 11.519 +1218 "2C28" 40 70 40 80 3.6659 3.1681 2.2246 +544 "2C29" 70 30 70 0 16.099 21.105 10.782 +60 "2C30" 70 55 0 0 16.76 15.817 30.664 +670 "2C31" 20 30 100 0 39.738 39.469 4.4475 +1202 "2C32" 40 70 0 80 3.7763 3.0477 3.2991 +706 "2C33" 70 30 100 0 14.854 19.997 4.5073 +733 "2D1" 0 40 0 20 40.233 34.647 33.129 +925 "2D2" 20 40 100 20 26.655 25.285 3.2995 +751 "2D3" 40 40 0 20 24.262 23.011 30.235 +937 "2D4" 70 40 100 20 11.121 13.778 3.3001 +763 "2D5" 100 40 0 20 8.6134 10.69 26.448 +877 "2D6" 0 40 70 20 35.215 31.352 8.2469 +781 "2D7" 20 40 10 20 31.798 28.893 27.848 +895 "2D8" 40 40 70 20 20.565 20.969 7.8714 +793 "2D9" 70 40 10 20 14.627 15.876 24.736 +907 "2D10" 100 40 70 20 5.376 8.9832 7.2014 +242 "2D11" 100 85 20 0 6.248 5.8322 14.826 +110 "2D12" 30 10 10 0 50.445 54.511 53.557 +611 "2D13" 40 85 85 0 17.915 12.48 3.772 +722 "2D14" 100 10 100 0 7.5407 16.73 5.5048 +575 "2D15" 0 85 85 0 33.454 20.538 3.8382 +845 "2D16" 10 10 40 20 44.143 46.488 24.678 +812 "2D17" 10 70 20 20 26.183 18.591 15.339 +658 "2D18" 10 0 100 0 59.555 66.059 6.1393 +539 "2D19" 55 85 70 0 13.564 10.195 5.3184 +686 "2D20" 40 10 100 0 33.715 40.512 5.2951 +502 "2D21" 10 70 70 0 32.955 23.401 6.7685 +253 "2D22" 10 0 30 0 68.467 74.301 44.913 +53 "2D23" 55 85 0 0 16.201 10.972 20.026 +281 "2D24" 40 10 30 0 40.297 46.19 36.753 +16 "2D25" 10 70 0 0 36.4 24.93 26.624 +490 "2D26" 0 30 70 0 53.301 49.669 11.829 +285 "2D27" 40 55 30 0 25.601 22.406 20.218 +1237 "2D28" 70 40 70 80 2.7354 3.1541 1.8793 +249 "2D29" 0 55 30 0 45.291 35.118 22.117 +652 "2D30" 0 30 100 0 51.46 47.868 4.5834 +42 "2D31" 40 55 0 0 27.794 23.398 32.575 +1253 "2D32" 70 40 100 80 2.6706 3.1448 1.3783 +6 "2D33" 0 55 0 0 47.429 36.04 35.201 +639 "2E1" 85 100 85 0 5.8308 4.9409 3.2424 +109 "2E2" 30 0 10 0 55.088 61.998 59.12 +619 "2E3" 55 70 85 0 14.749 12.807 4.4093 +145 "2E4" 85 0 10 0 21.862 30.879 50.22 +583 "2E5" 10 70 85 0 32.511 23.105 4.5305 +798 "2E6" 100 20 10 20 9.8389 14.217 29.27 +902 "2E7" 70 70 70 20 9.0284 8.6722 5.2487 +786 "2E8" 40 20 10 20 29.549 31.581 34.307 +890 "2E9" 20 70 70 20 21.516 16.127 5.5174 +768 "2E10" 0 20 10 20 48.723 47.131 38.268 +872 "2E11" 100 70 40 20 5.0542 5.875 9.601 +828 "2E12" 70 20 20 20 17.147 21.19 27.273 +860 "2E13" 40 70 40 20 16.588 13.074 10.285 +816 "2E14" 20 20 20 20 38.01 38.793 31.561 +842 "2E15" 0 70 40 20 28.249 19.899 10.893 +868 "2E16" 100 0 40 20 9.4045 16.878 20.882 +831 "2E17" 70 100 20 20 7.8989 5.178 9.1518 +856 "2E18" 40 0 40 20 30.615 36.969 24.493 +819 "2E19" 20 100 20 20 18.113 9.908 9.1732 +838 "2E20" 0 0 40 20 53.014 56.721 27.364 +801 "2E21" 100 100 10 20 4.5969 3.5817 10.626 +898 "2E22" 70 0 70 20 16.054 23.845 11.028 +789 "2E23" 40 100 10 20 13.563 7.6862 10.405 +886 "2E24" 20 0 70 20 39.398 45.005 12.506 +771 "2E25" 0 100 10 20 23.23 12.237 10.633 +299 "2E26" 70 10 30 0 23.504 31.165 33.504 +1235 "2E27" 40 100 70 80 3.3405 2.6353 1.3536 +263 "2E28" 20 10 30 0 55.035 58.89 39.483 +548 "2E29" 70 85 70 0 9.7363 8.1738 5.3342 +56 "2E30" 70 10 0 0 27.178 33.283 55.276 +1251 "2E31" 40 100 100 80 3.2688 2.647 1.118 +20 "2E32" 20 10 0 0 60.381 62.688 64.424 +710 "2E33" 70 85 100 0 9.063 7.8401 2.6433 +735 "2F1" 0 100 0 20 23.313 12.186 12.03 +922 "2F2" 20 0 100 20 37.207 42.764 4.7618 +753 "2F3" 40 100 0 20 13.706 7.6698 11.934 +934 "2F4" 70 0 100 20 14.805 22.592 4.5557 +765 "2F5" 100 100 0 20 4.7688 3.5633 11.941 +874 "2F6" 0 0 70 20 50.178 54.221 12.859 +783 "2F7" 20 100 10 20 18.29 9.8994 10.499 +892 "2F8" 40 0 70 20 28.936 35.683 11.763 +795 "2F9" 70 100 10 20 8.091 5.1913 10.527 +904 "2F10" 100 0 70 20 7.4692 15.345 10.452 +807 "2F11" 0 100 20 20 23.058 12.245 9.3329 +850 "2F12" 20 0 40 20 41.953 47.187 26.39 +825 "2F13" 40 100 20 20 13.401 7.6934 9.073 +862 "2F14" 70 0 40 20 18.366 25.704 22.551 +837 "2F15" 100 100 20 20 4.4115 3.5814 9.3364 +804 "2F16" 0 20 20 20 47.421 46.184 32.872 +854 "2F17" 20 70 40 20 22.348 16.488 10.687 +822 "2F18" 40 20 20 20 28.605 31.013 29.77 +866 "2F19" 70 70 40 20 9.6063 8.8285 9.7922 +834 "2F20" 100 20 20 20 9.1624 13.858 25.265 +878 "2F21" 0 70 70 20 27.083 19.177 5.4949 +780 "2F22" 20 20 10 20 39 39.419 36.308 +896 "2F23" 40 70 70 20 15.843 12.812 5.3519 +792 "2F24" 70 20 10 20 17.857 21.566 31.645 +908 "2F25" 100 70 70 20 4.2637 5.5352 5.0322 +1231 "2F26" 0 100 70 80 4.3827 3.0922 1.2778 +289 "2F27" 55 0 30 0 33.975 43.311 38.423 +538 "2F28" 55 70 70 0 15.19 13.094 6.4213 +1212 "2F29" 0 0 40 80 7.2398 7.7736 4.0923 +1247 "2F30" 0 100 100 80 4.4779 3.2461 1.1384 +46 "2F31" 55 0 0 0 37.906 45.689 64.134 +700 "2F32" 55 70 100 0 14.382 12.584 2.9632 +1196 "2F33" 0 0 0 80 7.5688 7.8937 6.6402 +495 "2G1" 0 100 70 0 30.696 16.461 4.3649 +241 "2G2" 100 70 20 0 7.204 7.7121 18.004 +491 "2G3" 0 40 70 0 48.486 42.462 10.329 +237 "2G4" 100 20 20 0 12.139 18.465 33.814 +487 "2G5" 0 0 70 0 70.296 75.801 16.829 +162 "2G6" 100 100 10 0 5.6757 4.2905 13.782 +622 "2G7" 70 0 85 0 20.256 30.935 9.2353 +126 "2G8" 40 100 10 0 17.864 9.9327 13.659 +586 "2G9" 20 0 85 0 52.677 60.378 10.16 +90 "2G10" 0 100 10 0 32.128 16.573 14.095 +1175 "2G11" 0 100 100 60 7.7461 4.8491 1.2043 +1094 "2G12" 100 70 0 60 3.1519 3.0872 6.637 +1173 "2G13" 0 40 100 60 11.279 10.35 1.7059 +1092 "2G14" 100 20 0 60 4.2429 5.682 11.587 +1171 "2G15" 0 0 100 60 14.598 16.021 2.3931 +871 "2G16" 100 40 40 20 6.5827 9.7411 14.016 +829 "2G17" 70 40 20 20 14.093 15.634 21.374 +859 "2G18" 40 40 40 20 21.865 21.791 15.941 +817 "2G19" 20 40 20 20 31.09 28.498 24.183 +841 "2G20" 0 40 40 20 36.833 32.49 17.453 +161 "2G21" 100 85 10 0 6.6047 5.9016 16.991 +595 "2G22" 30 0 85 0 44.655 53.316 9.9579 +125 "2G23" 40 85 10 0 20.256 13.207 17.412 +631 "2G24" 85 0 85 0 13.875 24.876 9.1828 +89 "2G25" 0 85 10 0 36.074 21.585 18.626 +311 "2G26" 85 40 30 0 12.707 16.067 22.975 +1233 "2G27" 40 40 70 80 3.8288 3.958 1.903 +275 "2G28" 30 40 30 0 34.956 32.941 26.009 +552 "2G29" 85 20 70 0 12.404 19.831 11.757 +68 "2G30" 85 40 0 0 15.401 17.409 37.286 +1249 "2G31" 40 40 100 80 3.7464 3.9131 1.3253 +32 "2G32" 30 40 0 0 38.119 34.792 42.301 +714 "2G33" 85 20 100 0 11.174 18.801 5.061 +181 "2H1" 20 0 20 0 62.098 68.564 52.696 +543 "2H2" 70 20 70 0 17.657 24.48 11.96 +185 "2H3" 20 40 20 0 41.807 37.848 32.114 +547 "2H4" 70 70 70 0 10.995 10.582 6.3901 +189 "2H5" 20 100 20 0 24.389 13.068 11.973 +574 "2H6" 0 70 85 0 36.78 25.432 4.5745 +102 "2H7" 20 20 10 0 53.225 53.555 49.466 +610 "2H8" 40 70 85 0 19.945 15.86 4.4212 +138 "2H9" 70 20 10 0 23.543 28.475 42.67 +646 "2H10" 100 70 85 0 4.7922 6.658 4.6939 +1076 "2H11" 20 0 0 60 16.12 17.453 17.095 +1187 "2H12" 70 20 100 60 4.5975 6.3578 1.9865 +1078 "2H13" 20 40 0 60 11.246 10.181 10.973 +1189 "2H14" 70 70 100 60 3.5687 3.5704 1.4992 +1080 "2H15" 20 100 0 60 7.0775 4.127 4.4583 +806 "2H16" 0 70 20 20 29.112 20.241 15.526 +852 "2H17" 20 20 40 20 35.941 37.258 21.792 +824 "2H18" 40 70 20 20 17.483 13.512 14.633 +864 "2H19" 70 20 40 20 15.463 19.996 18.676 +836 "2H20" 100 70 20 20 5.6528 6.1009 13.653 +876 "2H21" 0 20 70 20 42.359 42.186 10.58 +782 "2H22" 20 70 10 20 23.594 17.029 17.321 +894 "2H23" 40 20 70 20 24.554 27.936 9.8858 +794 "2H24" 70 70 10 20 10.82 9.3917 16.304 +906 "2H25" 100 20 70 20 6.4375 12.12 9.0207 +498 "2H26" 10 20 70 0 52.093 52.845 13.265 +293 "2H27" 55 40 30 0 23.034 24.103 24.689 +534 "2H28" 55 20 70 0 24.425 30.317 12.235 +1214 "2H29" 0 70 40 80 4.9538 3.8773 2.1287 +660 "2H30" 10 20 100 0 50.036 50.783 4.986 +50 "2H31" 55 40 0 0 25.599 25.355 39.62 +696 "2H32" 55 20 100 0 23.048 29.043 4.8772 +1198 "2H33" 0 70 0 80 5.2316 3.8927 3.3515 +531 "2I1" 40 100 70 0 16.461 9.8123 4.3821 +205 "2I2" 40 70 20 0 22.514 17.107 18.902 +527 "2I3" 40 40 70 0 26.802 27.163 9.6925 +201 "2I4" 40 20 20 0 38.27 41.405 39.909 +523 "2I5" 40 0 70 0 38.624 48.002 15.263 +158 "2I6" 100 40 10 0 10.662 13.88 31.163 +626 "2I7" 70 40 85 0 14.365 17.774 6.4865 +122 "2I8" 40 40 10 0 31.718 30.304 35.442 +590 "2I9" 20 40 85 0 36.646 34.206 6.581 +86 "2I10" 0 40 10 0 54.892 47.114 39.437 +1185 "2I11" 40 100 100 60 5.0017 3.5415 1.2204 +1084 "2I12" 40 70 0 60 6.8268 5.2535 6.9461 +1183 "2I13" 40 40 100 60 6.4525 6.7027 1.7087 +1082 "2I14" 40 20 0 60 10.544 11.081 13.353 +1181 "2I15" 40 0 100 60 8.7155 10.991 2.1626 +472 "2I16" 85 30 55 0 12.234 17.741 15.442 +113 "2I17" 30 40 10 0 37.004 34.223 36.327 +615 "2I18" 55 20 85 0 23.49 29.472 7.9889 +149 "2I19" 85 40 10 0 14.438 16.952 32.008 +579 "2I20" 10 20 85 0 50.831 51.658 8.4306 +796 "2I21" 100 0 10 20 11.565 18.075 34.978 +903 "2I22" 70 100 70 20 7.2197 5.2296 3.6755 +784 "2I23" 40 0 10 20 34.823 40.299 41.81 +891 "2I24" 20 100 70 20 17.351 9.9665 3.658 +766 "2I25" 0 0 10 20 57.939 60.619 47.246 +307 "2I26" 85 0 30 0 19.008 28.996 35.395 +520 "2I27" 30 70 70 0 24.172 18.332 6.4687 +271 "2I28" 30 0 30 0 51.632 59.517 42.064 +556 "2I29" 85 70 70 0 7.8131 8.5513 6.5836 +64 "2I30" 85 0 0 0 23.365 31.716 59.171 +682 "2I31" 30 70 100 0 23.425 17.869 2.8932 +28 "2I32" 30 0 0 0 56.939 63.231 69.655 +718 "2I33" 85 70 100 0 7.0792 8.2175 3.1644 +217 "2J1" 70 0 20 0 27.125 36.402 44.283 +507 "2J2" 20 20 70 0 45.39 47.549 12.999 +221 "2J3" 70 40 20 0 18.121 20.121 28.272 +511 "2J4" 20 70 70 0 28.512 20.854 6.5761 +225 "2J5" 70 100 20 0 10.071 6.4693 12.011 +570 "2J6" 0 20 85 0 57.496 56.756 8.6102 +106 "2J7" 20 70 10 0 31.445 22.187 22.731 +606 "2J8" 40 20 85 0 31.7 36.246 8.065 +142 "2J9" 70 70 10 0 13.821 11.77 21.436 +642 "2J10" 100 20 85 0 7.5282 15.276 7.9873 +1086 "2J11" 70 0 0 60 8.0007 10.124 14.828 +1177 "2J12" 20 20 100 60 10.17 10.9 1.9567 +1088 "2J13" 70 40 0 60 5.7691 6.0384 10.009 +1179 "2J14" 20 70 100 60 7.236 5.7348 1.4263 +1090 "2J15" 70 100 0 60 3.9422 2.7307 4.5882 +1441 "2J16" 3 40 3 0 54.293 46.676 43.807 +1442 "2J17" 40 40 3 0 32.465 30.677 39.82 +1445 "2J18" 3 3 40 0 70.851 75.213 35.788 +1452 "2J19" 40 40 0 3 31.687 29.834 39.954 +1475 "2J20" 0 3 3 40 35.382 36.528 31.537 +1460 "2J21" 40 0 40 3 40.881 49.515 32.217 +1439 "2J22" 40 3 3 0 47.181 53.597 62.936 +1470 "2J23" 40 3 0 40 21.586 24.396 28.773 +1458 "2J24" 0 0 40 3 71.989 76.822 35.593 +1459 "2J25" 3 0 40 3 69.942 75.071 35.529 +17 "2J26" 10 85 0 0 32.373 19.535 21.02 +695 "2J27" 55 10 100 0 25.223 33.197 5.3016 +1207 "2J28" 70 100 0 80 2.5582 1.9132 2.3762 +659 "2J29" 10 10 100 0 54.751 58.224 5.5591 +260 "2J30" 10 85 30 0 31 19.053 13.235 +533 "2J31" 55 10 70 0 26.597 34.52 13.332 +1223 "2J32" 70 100 40 80 2.5976 2.1507 1.738 +497 "2J33" 10 10 70 0 56.943 60.473 14.751 +567 "2K1" 100 100 70 0 4.0646 4.0207 4.4248 +169 "2K2" 0 70 20 0 39.926 27.209 20.466 +563 "2K3" 100 40 70 0 6.7195 11.55 9.3203 +165 "2K4" 0 20 20 0 65.698 63.533 44.561 +559 "2K5" 100 0 70 0 9.7118 20.377 13.922 +154 "2K6" 100 0 10 0 15.883 24.856 47.995 +630 "2K7" 70 100 85 0 8.3698 6.119 3.2589 +118 "2K8" 40 0 10 0 47.027 54.654 56.844 +594 "2K9" 20 100 85 0 23.133 12.999 3.1718 +82 "2K10" 0 0 10 0 81.546 85.184 66.064 +1195 "2K11" 100 100 100 60 2.1724 2.2263 1.2824 +1074 "2K12" 0 70 0 60 10.685 7.6357 7.2649 +1193 "2K13" 100 40 100 60 2.3928 3.7472 1.7985 +1072 "2K14" 0 20 0 60 16.905 16.358 14.823 +1191 "2K15" 100 0 100 60 2.6759 5.3706 2.2135 +1456 "2K16" 3 40 3 3 52.498 45.211 42.458 +1457 "2K17" 40 40 3 3 31.539 29.824 38.593 +1462 "2K18" 3 3 40 3 68.523 72.789 34.704 +1466 "2K19" 40 40 40 3 27.973 27.724 20.342 +1476 "2K20" 3 3 3 40 34.365 35.654 31.211 +1463 "2K21" 40 3 40 3 40.141 48.066 31.487 +1454 "2K22" 40 3 3 3 45.786 52.01 61.036 +1477 "2K23" 40 3 3 40 21.485 24.371 27.892 +1383 "2K24" 60 45 45 40 9.0665 9.6529 8.1931 +1421 "2K25" 0 0 3 3 79.789 82.854 70.456 +1258 "2K26" 100 70 100 80 2.0133 2.2902 1.2531 +51 "2K27" 55 55 0 0 21.878 19.422 31.73 +681 "2K28" 30 55 100 0 26.716 23.115 3.3808 +15 "2K29" 10 55 0 0 42.403 32.903 34.48 +1242 "2K30" 100 70 70 80 2.0233 2.2352 1.5758 +294 "2K31" 55 55 30 0 19.917 18.657 19.916 +519 "2K32" 30 55 70 0 27.687 23.883 8.0068 +258 "2K33" 10 55 30 0 40.235 31.957 21.726 +244 "2L1" 0 0 30 0 77.08 81.697 45.873 +432 "2L2" 20 100 55 0 23.639 13.11 6.1943 +280 "2L3" 40 0 30 0 43.854 52.447 40.404 +468 "2L4" 70 100 55 0 9.0996 6.3346 6.2835 +316 "2L5" 100 0 30 0 13.442 23.318 34.159 +1141 "2L6" 100 0 40 60 3.6644 6.2207 7.1392 +1122 "2L7" 0 20 40 60 14.806 14.748 7.7643 +1143 "2L8" 100 40 40 60 2.9983 4.0964 5.1669 +1124 "2L9" 0 70 40 60 10.133 7.5047 4.2491 +1145 "2L10" 100 100 40 60 2.4113 2.1836 2.7443 +406 "2L11" 0 0 55 0 72.272 77.656 24.988 +270 "2L12" 20 100 30 0 24.153 13.087 10.191 +442 "2L13" 40 0 55 0 40.203 49.451 22.945 +306 "2L14" 70 100 30 0 9.7975 6.4564 10.228 +478 "2L15" 100 0 55 0 10.877 21.35 20.18 +1316 "2L16" 0 50 0 0 50.062 39.535 38.4 +1317 "2L17" 0 40 0 0 56.387 48.115 45.856 +1336 "2L18" 0 0 50 0 73.074 78.372 28.447 +1337 "2L19" 0 0 40 0 74.951 79.955 36.787 +1356 "2L20" 0 0 0 50 27.113 28.23 24.861 +1357 "2L21" 0 0 0 40 36.398 37.862 33.394 +1296 "2L22" 50 0 0 0 41.037 48.639 65.176 +1297 "2L23" 40 0 0 0 48.74 55.767 67.464 +1423 "2L24" 0 3 3 3 77.783 79.99 68.416 +1446 "2L25" 40 3 40 0 41.382 49.573 32.515 +13 "2L26" 10 30 0 0 55.886 51.656 51.481 +699 "2L27" 55 55 100 0 16.597 16.464 3.4464 +49 "2L28" 55 30 0 0 28.708 30.127 45.424 +663 "2L29" 10 55 100 0 36.425 29.327 3.4137 +256 "2L30" 10 30 30 0 51.713 48.832 31.807 +537 "2L31" 55 55 70 0 17.549 17.239 7.7907 +292 "2L32" 55 30 30 0 25.976 28.883 28.527 +501 "2L33" 10 55 70 0 37.489 30.199 8.313 +480 "2M1" 100 20 55 0 9.1693 16.548 16.803 +304 "2M2" 70 70 30 0 12.946 11.538 15.631 +444 "2M3" 40 20 55 0 34.071 38.341 19.01 +268 "2M4" 20 70 30 0 30.376 21.828 16.549 +408 "2M5" 0 20 55 0 60.429 59.474 20.497 +1140 "2M6" 70 100 40 60 3.6536 2.8129 2.798 +1129 "2M7" 20 70 40 60 8.0524 6.1717 4.0654 +1138 "2M8" 70 40 40 60 4.681 5.3026 5.3278 +1127 "2M9" 20 20 40 60 11.795 12.27 7.4165 +1136 "2M10" 70 0 40 60 6.2776 8.7036 7.5706 +318 "2M11" 100 20 30 0 11.189 17.899 28.374 +466 "2M12" 70 70 55 0 11.666 10.941 9.1914 +282 "2M13" 40 20 30 0 37.116 40.692 33.418 +430 "2M14" 20 70 55 0 29.124 21.215 9.6145 +246 "2M15" 0 20 30 0 63.958 62.225 36.837 +1335 "2M16" 0 0 60 0 71.538 76.972 21.903 +1338 "2M17" 0 0 30 0 77.08 81.697 45.873 +1071 "2M18" 0 0 0 60 18.722 19.509 17.095 +1358 "2M19" 0 0 0 30 47.08 48.926 43.146 +1295 "2M20" 60 0 0 0 34.932 42.867 63.187 +1298 "2M21" 30 0 0 0 56.939 63.231 69.655 +1315 "2M22" 0 60 0 0 44.984 32.815 32.205 +4 "2M23" 0 30 0 0 62.701 56.901 53.098 +1443 "2M24" 3 0 40 0 72.542 77.891 36.699 +1444 "2M25" 0 3 40 0 73.042 77.009 35.813 +1256 "2M26" 100 0 100 80 1.893 3.2487 1.582 +459 "2M27" 55 100 55 0 12.532 7.9279 6.2308 +677 "2M28" 30 10 100 0 40.279 46.151 5.3443 +423 "2M29" 10 100 55 0 27.329 14.831 6.2413 +1240 "2M30" 100 0 70 80 2.053 3.3808 2.4352 +378 "2M31" 55 100 40 0 12.95 8.0267 8.5395 +515 "2M32" 30 10 70 0 42.101 47.999 14.089 +342 "2M33" 10 100 40 0 27.652 14.886 8.7057 +248 "2N1" 0 40 30 0 52.528 45.525 28.318 +428 "2N2" 20 40 55 0 38.41 35.546 15.198 +284 "2N3" 40 40 30 0 29.939 29.326 25.645 +464 "2N4" 70 40 55 0 15.489 18.523 14.18 +320 "2N5" 100 40 30 0 9.1277 13.089 22.599 +1131 "2N6" 40 0 40 60 10.19 12.289 8.225 +1132 "2N7" 40 20 40 60 8.8102 9.7595 6.9608 +1133 "2N8" 40 40 40 60 7.5029 7.4316 5.702 +1134 "2N9" 40 70 40 60 6.1031 4.9339 3.8811 +1135 "2N10" 40 100 40 60 5.4052 3.6322 2.7892 +410 "2N11" 0 40 55 0 49.848 43.529 15.896 +266 "2N12" 20 40 30 0 40.719 37.127 26.715 +446 "2N13" 40 40 55 0 27.771 27.816 14.545 +302 "2N14" 70 40 30 0 17.16 19.561 23.614 +482 "2N15" 100 40 55 0 7.5448 12.159 13.409 +1334 "2N16" 0 0 70 0 70.296 75.801 16.829 +1339 "2N17" 0 0 25 0 78.242 82.636 50.981 +1354 "2N18" 0 0 0 70 12.207 12.724 11 +1359 "2N19" 0 0 0 25 52.793 54.831 48.289 +1294 "2N20" 70 0 0 0 30.004 38.132 61.496 +1299 "2N21" 25 0 0 0 61.333 67.185 70.784 +1314 "2N22" 0 70 0 0 41.064 27.7 27.285 +1319 "2N23" 0 25 0 0 65.984 61.496 56.807 +1422 "2N24" 3 0 3 3 77.603 80.974 69.8 +1453 "2N25" 40 0 3 3 46.789 53.696 62.519 +8 "2N26" 0 85 0 0 36.321 21.541 21.27 +1252 "2N27" 70 0 100 80 3.0822 4.3835 1.6602 +44 "2N28" 40 85 0 0 20.804 13.409 20.271 +650 "2N29" 0 10 100 0 61.675 63.625 5.5889 +251 "2N30" 0 85 30 0 35.13 21.185 13.353 +1236 "2N31" 70 0 70 80 3.1049 4.3664 2.4244 +287 "2N32" 40 85 30 0 19.397 12.989 12.646 +488 "2N33" 0 10 70 0 64.187 66.282 14.983 +484 "2O1" 100 70 55 0 5.6991 7.1059 9.2396 +300 "2O2" 70 20 30 0 21.43 27.247 30.357 +448 "2O3" 40 70 55 0 20.835 16.388 9.392 +264 "2O4" 20 20 30 0 50.349 51.559 35.476 +412 "2O5" 0 70 55 0 37.905 26.101 9.9968 +1130 "2O6" 20 100 40 60 6.858 4.3108 2.799 +1139 "2O7" 70 70 40 60 3.9719 3.6823 3.7647 +1128 "2O8" 20 40 40 60 10.011 9.377 5.9896 +1137 "2O9" 70 20 40 60 5.3796 6.8306 6.3741 +1126 "2O10" 20 0 40 60 13.55 15.296 8.8402 +322 "2O11" 100 70 30 0 6.7492 7.5725 15.244 +462 "2O12" 70 20 55 0 18.9 25.419 17.746 +286 "2O13" 40 70 30 0 21.943 16.881 15.824 +426 "2O14" 20 20 55 0 46.818 48.738 19.917 +250 "2O15" 0 70 30 0 39.264 26.87 17.027 +1333 "2O16" 0 0 75 0 69.765 75.294 14.738 +163 "2O17" 0 0 20 0 79.484 83.639 56.44 +1353 "2O18" 0 0 0 75 9.6965 10.11 8.6337 +1360 "2O19" 0 0 0 20 58.945 61.186 53.819 +1293 "2O20" 75 0 0 0 27.91 36.096 60.764 +1300 "2O21" 20 0 0 0 66.155 71.475 71.889 +1313 "2O22" 0 75 0 0 39.448 25.605 25.236 +1320 "2O23" 0 20 0 0 69.597 66.565 60.838 +1447 "2O24" 3 40 40 0 49.709 43.549 23.144 +1440 "2O25" 0 40 3 0 56.018 47.878 44.066 +708 "2O26" 70 55 100 0 11.905 13.313 3.5428 +22 "2O27" 20 30 0 0 49.337 46.598 50.167 +1250 "2O28" 40 70 100 80 3.5096 3.1916 1.2656 +58 "2O29" 70 30 0 0 22.184 24.711 43.62 +546 "2O30" 70 55 70 0 12.724 13.885 7.8103 +265 "2O31" 20 30 30 0 45.493 44.156 31.065 +1234 "2O32" 40 70 70 80 3.5287 3.1386 1.5764 +301 "2O33" 70 30 30 0 19.263 23.285 27.099 +252 "2P1" 0 100 30 0 31.693 16.659 10.363 +424 "2P2" 20 0 55 0 55.813 63.395 24.202 +288 "2P3" 40 100 30 0 17.259 9.8599 10.01 +460 "2P4" 70 0 55 0 22.568 32.996 21.23 +324 "2P5" 100 100 30 0 5.115 4.2399 10.227 +1121 "2P6" 0 0 40 60 17.271 18.552 9.4611 +1142 "2P7" 100 20 40 60 3.3494 5.165 6.2004 +1123 "2P8" 0 40 40 60 12.404 11.185 6.1411 +1144 "2P9" 100 70 40 60 2.6547 2.9252 3.7357 +1125 "2P10" 0 100 40 60 8.4247 5.0487 2.8149 +414 "2P11" 0 100 55 0 31.062 16.556 6.234 +262 "2P12" 20 0 30 0 59.989 66.929 43.563 +450 "2P13" 40 100 55 0 16.719 9.8527 6.1702 +298 "2P14" 70 0 30 0 25.698 35.448 36.711 +486 "2P15" 100 100 55 0 4.4375 4.1148 6.2786 +1352 "2P16" 0 0 0 80 7.5688 7.8937 6.6402 +1361 "2P17" 0 0 0 15 64.939 67.375 59.178 +1292 "2P18" 80 0 0 0 25.861 34.122 60.042 +1301 "2P19" 15 0 0 0 70.678 75.488 73.034 +1312 "2P20" 0 80 0 0 37.947 23.655 23.333 +1321 "2P21" 0 15 0 0 73.058 71.373 64.646 +1332 "2P22" 0 0 80 0 69.318 74.851 12.828 +1341 "2P23" 0 0 15 0 80.55 84.499 61.058 +1472 "2P24" 0 0 3 40 36.184 37.715 32.352 +1417 "2P25" 3 3 3 0 78.484 81.076 70.262 +1318 "2P26" 0 30 0 0 62.701 56.901 53.098 +690 "2P27" 40 55 100 0 22.27 20.208 3.3847 +1205 "2P28" 70 40 0 80 3.1799 3.3106 4.5548 +654 "2P29" 0 55 100 0 41.058 32.033 3.4188 +247 "2P30" 0 30 30 0 58.153 53.602 32.559 +528 "2P31" 40 55 70 0 23.312 21.036 7.9337 +1221 "2P32" 70 40 40 80 2.8916 3.2153 2.8407 +492 "2P33" 0 55 70 0 42.45 33.253 8.4795 +1075 "2Q1" 0 100 0 60 8.4242 4.7581 4.4183 +1194 "2Q2" 100 70 100 60 2.2826 2.8028 1.5269 +1073 "2Q3" 0 40 0 60 14.133 12.396 11.625 +1192 "2Q4" 100 20 100 60 2.5815 4.6387 2.025 +1355 "2Q5" 0 0 0 60 18.722 19.509 17.095 +648 "2Q6" 100 100 85 0 3.8434 3.9547 3.2222 +136 "2Q7" 70 0 10 0 28.399 37.168 52.105 +612 "2Q8" 40 100 85 0 16.149 9.722 3.1901 +100 "2Q9" 20 0 10 0 64.122 70.064 61.644 +576 "2Q10" 0 100 85 0 30.497 16.411 3.101 +171 "2Q11" 0 100 20 0 31.855 16.576 12.252 +565 "2Q12" 100 70 70 0 5.126 6.7822 6.4772 +167 "2Q13" 0 40 20 0 53.653 46.279 33.894 +561 "2Q14" 100 20 70 0 8.1739 15.8 11.648 +1340 "2Q15" 0 0 20 0 79.484 83.639 56.44 +1351 "2Q16" 0 0 0 85 5.8783 6.1325 5.0453 +1362 "2Q17" 0 0 0 10 71.324 73.964 64.846 +1291 "2Q18" 85 0 0 0 23.365 31.716 59.171 +1302 "2Q19" 10 0 0 0 75.117 79.325 73.888 +1311 "2Q20" 0 85 0 0 36.321 21.541 21.27 +1322 "2Q21" 0 10 0 0 76.386 76.234 68.218 +568 "2Q22" 0 0 85 0 68.847 74.374 10.815 +1342 "2Q23" 0 0 10 0 81.546 85.184 66.064 +1374 "2Q24" 5 3 3 0 76.898 79.714 69.893 +1438 "2Q25" 40 0 3 0 48.364 55.548 64.6 +704 "2Q26" 70 10 100 0 17.965 26.694 5.3452 +1203 "2Q27" 40 100 0 80 3.2852 2.2379 2.2698 +668 "2Q28" 20 10 100 0 47.476 52.192 5.4187 +62 "2Q29" 70 85 0 0 12.341 8.8945 19.81 +542 "2Q30" 70 10 70 0 19.275 27.93 13.002 +1219 "2Q31" 40 100 40 80 3.3941 2.569 1.7401 +506 "2Q32" 20 10 70 0 49.495 54.282 14.405 +305 "2Q33" 70 85 30 0 11.307 8.7763 12.758 +1176 "2R1" 20 0 100 60 11.693 13.6 2.2243 +1087 "2R2" 70 20 0 60 6.8485 7.9614 12.462 +1178 "2R3" 20 40 100 60 8.7383 8.4322 1.6864 +1089 "2R4" 70 70 0 60 4.667 3.981 6.7619 +1180 "2R5" 20 100 100 60 6.4005 4.2183 1.2148 +88 "2R6" 0 70 10 0 40.436 27.379 23.635 +588 "2R7" 20 20 85 0 44.346 46.526 8.2869 +124 "2R8" 40 70 10 0 23.071 17.287 21.894 +624 "2R9" 70 20 85 0 16.961 23.857 7.9109 +160 "2R10" 100 70 10 0 7.664 7.8564 20.701 +505 "2R11" 20 0 70 0 53.84 61.587 15.853 +219 "2R12" 70 20 20 0 22.509 27.914 36.573 +509 "2R13" 20 40 70 0 37.308 34.783 10.114 +223 "2R14" 70 70 20 0 13.409 11.668 18.565 +513 "2R15" 20 100 70 0 23.329 13.052 4.3523 +1350 "2R16" 0 0 0 90 4.4602 4.6549 3.7277 +1363 "2R17" 0 0 0 7 75.254 78.018 68.33 +1290 "2R18" 90 0 0 0 21.073 29.468 58.368 +1303 "2R19" 7 0 0 0 77.791 81.624 74.41 +1310 "2R20" 0 90 0 0 34.862 19.646 19.365 +1323 "2R21" 0 7 0 0 78.538 79.375 70.488 +1330 "2R22" 0 0 90 0 68.433 73.929 9.0439 +1343 "2R23" 0 0 7 0 82.212 85.642 69.346 +1373 "2R24" 10 6 6 0 70.016 72.69 64.029 +1415 "2R25" 3 0 3 0 80.483 84.013 72.319 +1199 "2R26" 0 100 0 80 4.3725 2.7121 2.1754 +694 "2R27" 55 0 100 0 27.395 37.577 5.7703 +52 "2R28" 55 70 0 0 18.521 14.442 24.918 +1244 "2R29" 0 0 100 80 6.9069 7.584 1.6615 +1215 "2R30" 0 100 40 80 4.3481 2.9399 1.5795 +532 "2R31" 55 0 70 0 29.025 39.214 14.527 +295 "2R32" 55 70 30 0 17.073 14.022 15.756 +1228 "2R33" 0 0 70 80 7.1318 7.7751 2.585 +1085 "2S1" 40 100 0 60 5.6385 3.4701 4.5217 +1184 "2S2" 40 70 100 60 5.472 4.6439 1.431 +1083 "2S3" 40 40 0 60 8.6745 8.2231 10.527 +1182 "2S4" 40 20 100 60 7.5389 8.7392 1.9446 +1081 "2S5" 40 0 0 60 12.481 14.256 16.248 +644 "2S6" 100 40 85 0 6.1465 11.136 6.5358 +140 "2S7" 70 40 10 0 18.977 20.519 32.887 +608 "2S8" 40 40 85 0 26.102 26.494 6.3489 +104 "2S9" 20 40 10 0 42.831 38.473 37.277 +572 "2S10" 0 40 85 0 47.559 41.579 6.6855 +207 "2S11" 40 100 20 0 17.46 9.8331 11.75 +529 "2S12" 40 70 70 0 20.353 16.119 6.5305 +203 "2S13" 40 40 20 0 30.828 29.84 30.465 +525 "2S14" 40 20 70 0 32.496 37.003 12.595 +199 "2S15" 40 0 20 0 45.428 53.565 48.478 +1289 "2S16" 95 0 0 0 19.138 27.524 57.623 +1304 "2S17" 5 0 0 0 79.535 83.122 74.775 +1309 "2S18" 0 95 0 0 33.67 18.103 17.756 +1324 "2S19" 0 5 0 0 79.986 81.472 72 +1329 "2S20" 0 0 95 0 68.131 73.555 7.6466 +1344 "2S21" 0 0 5 0 82.632 85.932 71.387 +1349 "2S22" 0 0 0 95 3.4906 3.6405 2.8571 +1364 "2S23" 0 0 0 5 77.813 80.658 70.597 +1372 "2S24" 20 12 12 0 57.127 59.628 52.649 +1437 "2S25" 3 40 0 0 54.609 46.838 45.445 +716 "2S26" 85 40 100 0 9.3486 13.878 4.257 +1201 "2S27" 40 40 0 80 4.4886 4.319 4.7764 +680 "2S28" 30 40 100 0 30.501 29.579 3.9312 +66 "2S29" 85 20 0 0 19.214 24.141 47.91 +554 "2S30" 85 40 70 0 10.365 14.666 9.5341 +1217 "2S31" 40 40 40 80 4.1433 4.1667 2.9522 +518 "2S32" 30 40 70 0 31.79 30.79 9.8645 +309 "2S33" 85 20 30 0 15.812 22.227 29.309 +1186 "2T1" 70 0 100 60 5.2037 7.8436 2.1627 +1077 "2T2" 20 20 0 60 13.375 13.41 13.73 +1188 "2T3" 70 40 100 60 4.0032 4.9322 1.7678 +1079 "2T4" 20 70 0 60 8.7529 6.4523 7.1492 +1190 "2T5" 70 100 100 60 3.3628 2.8119 1.2912 +84 "2T6" 0 20 10 0 67.502 64.907 52.292 +592 "2T7" 20 70 85 0 28.108 20.596 4.4394 +120 "2T8" 40 20 10 0 39.229 41.854 46.166 +628 "2T9" 70 70 85 0 10.587 10.344 4.467 +156 "2T10" 100 20 10 0 13.142 19.002 39.45 +541 "2T11" 70 0 70 0 21.057 31.724 14.068 +183 "2T12" 20 20 20 0 51.957 52.761 42.696 +545 "2T13" 70 40 70 0 14.902 18.186 9.7369 +187 "2T14" 20 70 20 0 30.915 22.015 19.669 +549 "2T15" 70 100 70 0 8.6643 6.2153 4.4865 +1288 "2T16" 98 0 0 0 18.031 26.399 57.207 +1305 "2T17" 3 0 0 0 81.162 84.519 75.123 +1308 "2T18" 0 98 0 0 32.984 17.216 16.835 +1325 "2T19" 0 3 0 0 81.401 83.509 73.467 +1328 "2T20" 0 0 98 0 67.951 73.331 6.8597 +1345 "2T21" 0 0 3 0 83.005 86.19 73.174 +1348 "2T22" 0 0 0 98 2.99 3.117 2.4132 +1365 "2T23" 0 0 0 3 80.153 83.071 72.668 +1371 "2T24" 40 27 27 0 35.147 37.025 32.701 +1436 "2T25" 40 3 0 0 47.607 53.841 65.458 +12 "2T26" 10 20 0 0 62.169 60.553 58.992 +698 "2T27" 55 40 100 0 19.251 21.319 3.9971 +48 "2T28" 55 20 0 0 31.849 35.245 51.635 +1246 "2T29" 0 70 100 80 4.9126 4.0355 1.2344 +255 "2T30" 10 20 30 0 57.101 56.911 36.24 +536 "2T31" 55 40 70 0 20.38 22.356 9.6853 +291 "2T32" 55 20 30 0 28.488 33.387 31.708 +1230 "2T33" 0 70 70 80 4.9638 4.0166 1.6192 +1095 "2U1" 100 100 0 60 2.7618 2.2108 4.6888 +1174 "2U2" 0 70 100 60 9.0728 6.8758 1.3976 +1093 "2U3" 100 40 0 60 3.7545 4.5005 9.551 +1172 "2U4" 0 20 100 60 12.964 13.182 1.9982 +1091 "2U5" 100 0 0 60 4.728 6.906 13.457 +640 "2U6" 100 0 85 0 8.8403 19.595 9.2829 +144 "2U7" 70 100 10 0 10.353 6.489 13.819 +604 "2U8" 40 0 85 0 37.463 46.861 9.8419 +108 "2U9" 20 100 10 0 24.631 13.078 13.805 +1331 "2U10" 0 0 85 0 68.847 74.374 10.815 +243 "2U11" 100 100 20 0 5.3982 4.2754 11.983 +493 "2U12" 0 70 70 0 37.425 25.929 6.9599 +239 "2U13" 100 40 20 0 9.8374 13.434 26.709 +489 "2U14" 0 20 70 0 58.624 57.821 13.421 +235 "2U15" 100 0 20 0 14.646 24.111 40.809 +1287 "2U16" 100 0 0 0 17.316 25.666 56.941 +1306 "2U17" 2 0 0 0 81.949 85.195 75.296 +1307 "2U18" 0 100 0 0 32.54 16.641 16.24 +1326 "2U19" 0 2 0 0 82.103 84.515 74.191 +1327 "2U20" 0 0 100 0 67.831 73.183 6.3566 +1346 "2U21" 0 0 2 0 83.181 86.312 74.008 +1347 "2U22" 0 0 0 100 2.6842 2.7971 2.1442 +1366 "2U23" 0 0 0 2 81.269 84.221 73.655 +1370 "2U24" 60 45 45 0 18.648 19.88 16.953 +1414 "2U25" 3 3 0 0 79.032 81.452 72.778 +712 "2U26" 85 0 100 0 13.275 24.297 5.716 +34 "2U27" 30 70 0 0 27.569 19.811 25.728 +676 "2U28" 30 0 100 0 43.65 52.263 5.9337 +70 "2U29" 85 70 0 0 10.922 9.7865 24.082 +550 "2U30" 85 0 70 0 14.772 25.647 13.911 +277 "2U31" 30 70 30 0 25.866 19.213 16.143 +514 "2U32" 30 0 70 0 45.856 54.52 15.521 +313 "2U33" 85 70 30 0 9.539 9.4096 15.441 +END_DATA diff --git a/target/FograStrip2.ti1 b/target/FograStrip2.ti1 new file mode 100644 index 0000000..f8abae7 --- /dev/null +++ b/target/FograStrip2.ti1 @@ -0,0 +1,120 @@ +CTI1 + +DESCRIPTOR "Argyll Calibration Target chart information 1" +ORIGINATOR "Manualy created for FOGRA strip #2 " +CREATED "Wed Jun 1 15:06:24 2004" +KEYWORD "DEFAULT_EXPECTED_VALUES" +DEFAULT_EXPECTED_VALUES "true" +KEYWORD "APPROX_WHITE_POINT" +APPROX_WHITE_POINT "83.77 87.26 66.79" +KEYWORD "TOTAL_INK_LIMIT" +TOTAL_INK_LIMIT "270.0" +KEYWORD "COLOR_REP" +COLOR_REP "CMYK" + +KEYWORD "SAMPLE_LOC" +NUMBER_OF_FIELDS 9 +BEGIN_DATA_FORMAT +SAMPLE_ID CMYK_C CMYK_M CMYK_Y CMYK_K XYZ_X XYZ_Y XYZ_Z +END_DATA_FORMAT + +NUMBER_OF_SETS 46 +BEGIN_DATA +1 100 0 0 0 19.21 25.37 45.6 +2 70 0 0 0 28.25 35.4 51.26 +3 40 0 0 0 45.2 52.51 58.34 +4 0 100 0 0 35.66 21.75 16.18 +5 0 70 0 0 41.16 28.04 22.51 +6 0 40 0 0 54.44 44.85 37.56 +7 0 0 100 0 70.46 73.51 10.34 +8 0 0 70 0 72.54 76.67 16.6 +9 0 0 40 0 76.58 81.29 31.57 +10 20 70 70 0 26.84 20.24 9.68 +11 40 70 70 20 15.21 13.33 8.47 +12 40 100 100 20 13.26 10.8 6.56 +13 40 100 40 20 15.41 12.16 10.32 +14 40 40 100 20 17.64 18.07 7.7 +15 100 40 100 20 8.74 11.04 7.8 +16 100 40 40 20 10.64 12.84 14.75 +17 100 100 40 20 8.44 8.45 10.12 +18 0 0 0 10 69.21 72.04 55.31 +19 0 0 0 20 57.67 59.99 46.27 +20 0 0 0 40 37.79 39.2 30.25 +21 0 0 0 60 22.87 23.62 18.11 +22 0 0 0 80 12.52 12.83 9.74 +23 0 0 0 100 7.34 7.42 5.6 +24 100 100 0 0 10.64 10.02 15.7 +25 70 70 0 0 15.73 14.48 21.18 +26 40 40 0 0 31.46 29.92 34.72 +27 0 100 100 0 32.06 19.73 6.79 +28 0 70 70 0 37.86 26.24 10.23 +29 0 40 40 0 50.42 41.91 21.32 +30 100 0 100 0 11.84 18.76 9.74 +31 70 0 70 0 20.00 28.29 14.84 +32 40 0 40 0 37.71 46.37 28.51 +33 10 40 40 0 44.02 38.09 21.16 +34 0 40 100 0 46.00 37.51 8.7 +35 0 100 40 0 34.28 21.23 12.07 +36 40 100 0 0 20.58 15.09 16.73 +37 40 0 100 0 32.29 40.42 10.44 +38 100 0 40 0 15.35 22.58 24.53 +39 100 40 0 0 14.68 16.88 29.74 +40 0 0 0 0 83.77 87.26 66.79 +41 10 6 6 0 69.49 73.01 57.1 +42 20 12 12 0 53.95 56.73 46.26 +43 40 27 27 0 31.75 33.56 28.39 +44 60 45 45 0 18.83 20.03 17.45 +45 80 65 65 0 11.98 12.82 11.37 +46 100 85 85 0 8.30 8.90 8.39 +END_DATA +CTI1 + +DESCRIPTOR "Argyll Calibration Target chart information 1" +ORIGINATOR "Argyll targen" +KEYWORD "DENSITY_EXTREME_VALUES" +DENSITY_EXTREME_VALUES "8" +CREATED "April 22, 2008" + +KEYWORD "INDEX" +NUMBER_OF_FIELDS 8 +BEGIN_DATA_FORMAT +INDEX CMYK_C CMYK_M CMYK_Y CMYK_K XYZ_X XYZ_Y XYZ_Z +END_DATA_FORMAT + +NUMBER_OF_SETS 8 +BEGIN_DATA +0 0.0000 0.0000 0.0000 0.0000 96.420 100.00 82.490 +1 100.00 0.0000 0.0000 0.0000 12.000 18.000 48.000 +2 0.0000 100.00 0.0000 0.0000 38.000 19.000 20.000 +3 100.00 0.0000 0.0000 45.486 3.2503 4.8706 13.063 +4 0.0000 17.988 100.00 0.0000 59.720 57.803 8.0538 +5 100.00 0.0000 100.00 40.023 3.1381 4.8335 2.1311 +6 0.0000 100.00 100.00 18.089 19.420 9.9765 1.7309 +7 0.0000 31.327 0.0000 100.00 0.65916 0.54433 0.57384 +END_DATA +CTI1 + +DESCRIPTOR "Argyll Calibration Target chart information 1" +ORIGINATOR "Argyll targen" +KEYWORD "DEVICE_COMBINATION_VALUES" +DEVICE_COMBINATION_VALUES "9" +CREATED "April 22, 2008" + +KEYWORD "INDEX" +NUMBER_OF_FIELDS 8 +BEGIN_DATA_FORMAT +INDEX CMYK_C CMYK_M CMYK_Y CMYK_K XYZ_X XYZ_Y XYZ_Z +END_DATA_FORMAT + +NUMBER_OF_SETS 9 +BEGIN_DATA +0 0.0000 0.0000 0.0000 0.0000 96.420 100.00 82.490 +1 100.00 0.0000 0.0000 0.0000 12.000 18.000 48.000 +2 0.0000 100.00 0.0000 0.0000 38.000 19.000 20.000 +3 100.00 100.00 0.0000 0.0000 4.7293 3.4200 11.638 +4 0.0000 0.0000 100.00 0.0000 76.000 81.000 11.000 +5 100.00 0.0000 100.00 0.0000 9.4586 14.580 6.4008 +6 0.0000 100.00 100.00 0.0000 29.952 15.390 2.6670 +7 100.00 100.00 100.00 100.00 0.038661 0.027702 0.018813 +8 40.000 40.000 40.000 0.0000 19.978 17.645 12.009 +END_DATA diff --git a/target/FograStrip2_2.ti2 b/target/FograStrip2_2.ti2 new file mode 100644 index 0000000..b84cb9d --- /dev/null +++ b/target/FograStrip2_2.ti2 @@ -0,0 +1,75 @@ +CTI2 + +DESCRIPTOR "Argyll Calibration Target chart information 2" +ORIGINATOR "Manualy created for FOGRA strip #2 in 2 strip layout" +CREATED "Wed Jun 1 15:06:24 2004" +KEYWORD "TARGET_INSTRUMENT" +TARGET_INSTRUMENT "GretagMacbeth SpectroScan" +KEYWORD "APPROX_WHITE_POINT" +APPROX_WHITE_POINT "83.77 87.26 66.79" +KEYWORD "TOTAL_INK_LIMIT" +TOTAL_INK_LIMIT "270.0" +KEYWORD "COLOR_REP" +COLOR_REP "CMYK" +KEYWORD "STEPS_IN_PASS" +STEPS_IN_PASS "23" +KEYWORD "PASSES_IN_STRIPS" +PASSES_IN_STRIPS "2" + +KEYWORD "SAMPLE_LOC" +NUMBER_OF_FIELDS 9 +BEGIN_DATA_FORMAT +SAMPLE_ID SAMPLE_LOC CMYK_C CMYK_M CMYK_Y CMYK_K XYZ_X XYZ_Y XYZ_Z +END_DATA_FORMAT + +NUMBER_OF_SETS 46 +BEGIN_DATA +1 "A1" 100 0 0 0 19.21 25.37 45.6 +2 "A2" 70 0 0 0 28.25 35.4 51.26 +3 "A3" 40 0 0 0 45.2 52.51 58.34 +4 "A4" 0 100 0 0 35.66 21.75 16.18 +5 "A5" 0 70 0 0 41.16 28.04 22.51 +6 "A6" 0 40 0 0 54.44 44.85 37.56 +7 "A7" 0 0 100 0 70.46 73.51 10.34 +8 "A8" 0 0 70 0 72.54 76.67 16.6 +9 "A9" 0 0 40 0 76.58 81.29 31.57 +10 "A10" 20 70 70 0 26.84 20.24 9.68 +11 "A11" 40 70 70 20 15.21 13.33 8.47 +12 "A12" 40 100 100 20 13.26 10.8 6.56 +13 "A13" 40 100 40 20 15.41 12.16 10.32 +14 "A14" 40 40 100 20 17.64 18.07 7.7 +15 "A15" 100 40 100 20 8.74 11.04 7.8 +16 "A16" 100 40 40 20 10.64 12.84 14.75 +17 "A17" 100 100 40 20 8.44 8.45 10.12 +18 "A18" 0 0 0 10 69.21 72.04 55.31 +19 "A19" 0 0 0 20 57.67 59.99 46.27 +20 "A20" 0 0 0 40 37.79 39.2 30.25 +21 "A21" 0 0 0 60 22.87 23.62 18.11 +22 "A22" 0 0 0 80 12.52 12.83 9.74 +23 "A23" 0 0 0 100 7.34 7.42 5.6 +24 "B1" 100 100 0 0 10.64 10.02 15.7 +25 "B2" 70 70 0 0 15.73 14.48 21.18 +26 "B3" 40 40 0 0 31.46 29.92 34.72 +27 "B4" 0 100 100 0 32.06 19.73 6.79 +28 "B5" 0 70 70 0 37.86 26.24 10.23 +29 "B6" 0 40 40 0 50.42 41.91 21.32 +30 "B7" 100 0 100 0 11.84 18.76 9.74 +31 "B8" 70 0 70 0 20.00 28.29 14.84 +32 "B9" 40 0 40 0 37.71 46.37 28.51 +33 "B10" 10 40 40 0 44.02 38.09 21.16 +34 "B11" 0 40 100 0 46.00 37.51 8.7 +35 "B12" 0 100 40 0 34.28 21.23 12.07 +36 "B13" 40 100 0 0 20.58 15.09 16.73 +37 "B14" 40 0 100 0 32.29 40.42 10.44 +38 "B15" 100 0 40 0 15.35 22.58 24.53 +39 "B16" 100 40 0 0 14.68 16.88 29.74 +40 "B17" 0 0 0 0 83.77 87.26 66.79 +41 "B18" 10 6 6 0 69.49 73.01 57.1 +42 "B19" 20 12 12 0 53.95 56.73 46.26 +43 "B20" 40 27 27 0 31.75 33.56 28.39 +44 "B21" 60 45 45 0 18.83 20.03 17.45 +45 "B22" 80 65 65 0 11.98 12.82 11.37 +46 "B23" 100 85 85 0 8.30 8.90 8.39 +END_DATA + + diff --git a/target/FograStrip3.ti1 b/target/FograStrip3.ti1 new file mode 100644 index 0000000..4cf126f --- /dev/null +++ b/target/FograStrip3.ti1 @@ -0,0 +1,146 @@ +CTI1 + +DESCRIPTOR "Argyll Calibration Target chart information 1" +ORIGINATOR "Manualy created for FOGRA strip #3 " +CREATED "Thu Jan 12 15:06:24 2011" +KEYWORD "DEFAULT_EXPECTED_VALUES" +DEFAULT_EXPECTED_VALUES "true" +KEYWORD "APPROX_WHITE_POINT" +APPROX_WHITE_POINT "87.38 90.38 75.45" +KEYWORD "TOTAL_INK_LIMIT" +TOTAL_INK_LIMIT "270.0" +KEYWORD "COLOR_REP" +COLOR_REP "CMYK" + +KEYWORD "SAMPLE_LOC" +NUMBER_OF_FIELDS 9 +BEGIN_DATA_FORMAT +SAMPLE_ID CMYK_C CMYK_M CMYK_Y CMYK_K XYZ_X XYZ_Y XYZ_Z +END_DATA_FORMAT + +NUMBER_OF_SETS 72 +BEGIN_DATA +1 100 0 0 0 14.91 22.41 53.72 +2 70 0 0 0 24.6 32.11 56.56 +3 40 0 0 0 41.86 48.84 63.97 +4 20 0 0 0 59.49 65.47 69.4 +5 10 0 0 0 71.85 76.74 72.45 +6 0 100 0 0 35.87 18.39 16.7 +7 0 70 0 0 42.6 27.5 24.95 +8 0 40 0 0 56.48 44.98 40.26 +9 0 20 0 0 69.77 63.34 55.08 +10 0 10 0 0 79.71 77.36 66.28 +11 0 0 100 0 74.36 78.79 7.06 +12 0 0 70 0 75.77 80.62 16.06 +13 0 0 40 0 78.94 83.73 32.04 +14 0 0 20 0 82.9 87.23 49.82 +15 0 0 10 0 85.2 88.98 61.85 +16 0 0 0 10 67.91 70.15 58.39 +17 0 0 0 20 49.47 51.05 42.55 +18 0 0 0 40 30.07 30.99 26.02 +19 0 0 0 60 15.9 16.31 13.78 +20 0 0 0 80 7.04 7.13 6.22 +21 0 0 0 100 1.69 1.61 1.67 +22 0 100 0 100 1.41 1.14 1.48 +23 0 70 70 60 9.68 7.17 3.38 +24 0 0 70 80 7.27 7.64 3.38 +25 100 100 0 0 4.35 2.81 13.31 +26 70 70 0 0 12.04 10.07 21.64 +27 40 40 0 0 28.32 26.36 35.63 +28 20 20 0 0 47.53 46.5 50.03 +29 10 10 0 0 64.23 64.56 61.64 +30 0 100 100 0 32.69 17.25 2.74 +31 0 70 70 0 39.4 25.83 7.73 +32 0 40 40 0 52.14 42.32 20.67 +33 0 20 20 0 66.2 60.84 38.79 +34 0 10 10 0 77.01 75.27 54.63 +35 100 0 100 0 6.66 16.31 5.63 +36 70 0 70 0 16.97 26.86 13.73 +37 40 0 40 0 35.31 44.25 29.06 +38 20 0 20 0 55.17 62.38 46.73 +39 10 0 10 0 69.88 75.46 59.92 +40 10 6 6 0 66.27 68.73 60.0 +41 20 12 12 0 48.71 50.74 45.17 +42 40 27 27 0 28.15 29.54 26.91 +43 60 45 45 0 15.65 16.58 15.62 +44 80 65 65 0 7.41 7.92 7.94 +45 100 85 85 0 3.11 3.51 4.29 +46 100 0 0 100 1.00 1.07 1.42 +47 20 100 70 60 6.34 3.91 2.68 +48 70 0 70 80 3.22 4.15 3.04 +49 100 100 100 0 2.42 2.16 2.69 +50 70 70 70 0 8.92 8.36 6.83 +51 40 40 40 0 24.72 24.12 18.8 +52 20 20 20 0 44.17 44.18 35.62 +53 10 10 10 0 61.84 62.76 51.51 +54 20 70 70 0 26.95 19.18 7.57 +55 40 70 70 20 13.71 10.9 5.89 +56 40 100 100 20 10.62 6.33 2.41 +57 40 100 40 20 11.7 6.77 7.24 +58 40 40 100 20 16.45 16.81 3.73 +59 100 40 100 20 3.62 6.84 3.5 +60 100 40 40 20 5.33 8.00 12.8 +61 100 100 40 20 2.75 2.07 6.41 +62 10 40 40 0 43.46 36.9 20.39 +63 0 40 100 0 49.78 40.72 4.88 +64 0 100 40 0 34.02 17.62 9.73 +65 40 100 0 0 16.66 9.11 14.75 +66 40 0 100 0 31.1 40.61 6.39 +67 100 0 40 0 9.99 19.02 25.24 +68 100 40 0 0 9.18 11.79 30.55 +69 0 0 0 0 87.38 90.38 75.45 +70 0 0 100 100 1.57 1.54 1.33 +71 0 70 0 60 10.61 7.67 7.74 +72 70 0 0 80 3.84 4.59 6.23 +END_DATA +CTI1 + +DESCRIPTOR "Argyll Calibration Target chart information 1" +ORIGINATOR "Argyll targen" +KEYWORD "DENSITY_EXTREME_VALUES" +DENSITY_EXTREME_VALUES "8" +CREATED "April 22, 2008" + +KEYWORD "INDEX" +NUMBER_OF_FIELDS 8 +BEGIN_DATA_FORMAT +INDEX CMYK_C CMYK_M CMYK_Y CMYK_K XYZ_X XYZ_Y XYZ_Z +END_DATA_FORMAT + +NUMBER_OF_SETS 8 +BEGIN_DATA +0 0.0000 0.0000 0.0000 0.0000 96.420 100.00 82.490 +1 100.00 0.0000 0.0000 0.0000 12.000 18.000 48.000 +2 0.0000 100.00 0.0000 0.0000 38.000 19.000 20.000 +3 100.00 0.0000 0.0000 45.486 3.2503 4.8706 13.063 +4 0.0000 17.988 100.00 0.0000 59.720 57.803 8.0538 +5 100.00 0.0000 100.00 40.023 3.1381 4.8335 2.1311 +6 0.0000 100.00 100.00 18.089 19.420 9.9765 1.7309 +7 0.0000 31.327 0.0000 100.00 0.65916 0.54433 0.57384 +END_DATA +CTI1 + +DESCRIPTOR "Argyll Calibration Target chart information 1" +ORIGINATOR "Argyll targen" +KEYWORD "DEVICE_COMBINATION_VALUES" +DEVICE_COMBINATION_VALUES "9" +CREATED "April 22, 2008" + +KEYWORD "INDEX" +NUMBER_OF_FIELDS 8 +BEGIN_DATA_FORMAT +INDEX CMYK_C CMYK_M CMYK_Y CMYK_K XYZ_X XYZ_Y XYZ_Z +END_DATA_FORMAT + +NUMBER_OF_SETS 9 +BEGIN_DATA +0 0.0000 0.0000 0.0000 0.0000 96.420 100.00 82.490 +1 100.00 0.0000 0.0000 0.0000 12.000 18.000 48.000 +2 0.0000 100.00 0.0000 0.0000 38.000 19.000 20.000 +3 100.00 100.00 0.0000 0.0000 4.7293 3.4200 11.638 +4 0.0000 0.0000 100.00 0.0000 76.000 81.000 11.000 +5 100.00 0.0000 100.00 0.0000 9.4586 14.580 6.4008 +6 0.0000 100.00 100.00 0.0000 29.952 15.390 2.6670 +7 100.00 100.00 100.00 100.00 0.038661 0.027702 0.018813 +8 40.000 40.000 40.000 0.0000 19.978 17.645 12.009 +END_DATA diff --git a/target/FograStrip3_3.ti2 b/target/FograStrip3_3.ti2 new file mode 100644 index 0000000..8f9822e --- /dev/null +++ b/target/FograStrip3_3.ti2 @@ -0,0 +1,101 @@ +CTI2 + +DESCRIPTOR "Argyll Calibration Target chart information 2" +ORIGINATOR "Manualy created for FOGRA strip #3 in 3 strip layout (E3Z)" +CREATED "Thu Jan 12 15:06:24 2011" +KEYWORD "TARGET_INSTRUMENT" +TARGET_INSTRUMENT "GretagMacbeth SpectroScan" +KEYWORD "APPROX_WHITE_POINT" +APPROX_WHITE_POINT "83.77 87.26 66.79" +KEYWORD "TOTAL_INK_LIMIT" +TOTAL_INK_LIMIT "270.0" +KEYWORD "COLOR_REP" +COLOR_REP "CMYK" +KEYWORD "STEPS_IN_PASS" +STEPS_IN_PASS "24" +KEYWORD "PASSES_IN_STRIPS" +PASSES_IN_STRIPS "3" + +KEYWORD "SAMPLE_LOC" +NUMBER_OF_FIELDS 9 +BEGIN_DATA_FORMAT +SAMPLE_ID SAMPLE_LOC CMYK_C CMYK_M CMYK_Y CMYK_K XYZ_X XYZ_Y XYZ_Z +END_DATA_FORMAT + +NUMBER_OF_SETS 72 +BEGIN_DATA +1 "A1" 100 0 0 0 14.91 22.41 53.72 +2 "A2" 70 0 0 0 24.6 32.11 56.56 +3 "A3" 40 0 0 0 41.86 48.84 63.97 +4 "A4" 20 0 0 0 59.49 65.47 69.4 +5 "A5" 10 0 0 0 71.85 76.74 72.45 +6 "A6" 0 100 0 0 35.87 18.39 16.7 +7 "A7" 0 70 0 0 42.6 27.5 24.95 +8 "A8" 0 40 0 0 56.48 44.98 40.26 +9 "A9" 0 20 0 0 69.77 63.34 55.08 +10 "A10" 0 10 0 0 79.71 77.36 66.28 +11 "A11" 0 0 100 0 74.36 78.79 7.06 +12 "A12" 0 0 70 0 75.77 80.62 16.06 +13 "A13" 0 0 40 0 78.94 83.73 32.04 +14 "A14" 0 0 20 0 82.9 87.23 49.82 +15 "A15" 0 0 10 0 85.2 88.98 61.85 +16 "A16" 0 0 0 10 67.91 70.15 58.39 +17 "A17" 0 0 0 20 49.47 51.05 42.55 +18 "A18" 0 0 0 40 30.07 30.99 26.02 +19 "A19" 0 0 0 60 15.9 16.31 13.78 +20 "A20" 0 0 0 80 7.04 7.13 6.22 +21 "A21" 0 0 0 100 1.69 1.61 1.67 +22 "A22" 0 100 0 100 1.41 1.14 1.48 +23 "A23" 0 70 70 60 9.68 7.17 3.38 +24 "A24" 0 0 70 80 7.27 7.64 3.38 +25 "B1" 100 100 0 0 4.35 2.81 13.31 +26 "B2" 70 70 0 0 12.04 10.07 21.64 +27 "B3" 40 40 0 0 28.32 26.36 35.63 +28 "B4" 20 20 0 0 47.53 46.5 50.03 +29 "B5" 10 10 0 0 64.23 64.56 61.64 +30 "B6" 0 100 100 0 32.69 17.25 2.74 +31 "B7" 0 70 70 0 39.4 25.83 7.73 +32 "B8" 0 40 40 0 52.14 42.32 20.67 +33 "B9" 0 20 20 0 66.2 60.84 38.79 +34 "B10" 0 10 10 0 77.01 75.27 54.63 +35 "B11" 100 0 100 0 6.66 16.31 5.63 +36 "B12" 70 0 70 0 16.97 26.86 13.73 +37 "B13" 40 0 40 0 35.31 44.25 29.06 +38 "B14" 20 0 20 0 55.17 62.38 46.73 +39 "B15" 10 0 10 0 69.88 75.46 59.92 +40 "B16" 10 6 6 0 66.27 68.73 60.0 +41 "B17" 20 12 12 0 48.71 50.74 45.17 +42 "B18" 40 27 27 0 28.15 29.54 26.91 +43 "B19" 60 45 45 0 15.65 16.58 15.62 +44 "B20" 80 65 65 0 7.41 7.92 7.94 +45 "B21" 100 85 85 0 3.11 3.51 4.29 +46 "B22" 100 0 0 100 1.00 1.07 1.42 +47 "B23" 20 100 70 60 6.34 3.91 2.68 +48 "B24" 70 0 70 80 3.22 4.15 3.04 +49 "C1" 100 100 100 0 2.42 2.16 2.69 +50 "C2" 70 70 70 0 8.92 8.36 6.83 +51 "C3" 40 40 40 0 24.72 24.12 18.8 +52 "C4" 20 20 20 0 44.17 44.18 35.62 +53 "C5" 10 10 10 0 61.84 62.76 51.51 +54 "C6" 20 70 70 0 26.95 19.18 7.57 +55 "C7" 40 70 70 20 13.71 10.9 5.89 +56 "C8" 40 100 100 20 10.62 6.33 2.41 +57 "C9" 40 100 40 20 11.7 6.77 7.24 +58 "C10" 40 40 100 20 16.45 16.81 3.73 +59 "C11" 100 40 100 20 3.62 6.84 3.5 +60 "C12" 100 40 40 20 5.33 8.00 12.8 +61 "C13" 100 100 40 20 2.75 2.07 6.41 +62 "C14" 10 40 40 0 43.46 36.9 20.39 +63 "C15" 0 40 100 0 49.78 40.72 4.88 +64 "C16" 0 100 40 0 34.02 17.62 9.73 +65 "C17" 40 100 0 0 16.66 9.11 14.75 +66 "C18" 40 0 100 0 31.1 40.61 6.39 +67 "C19" 100 0 40 0 9.99 19.02 25.24 +68 "C20" 100 40 0 0 9.18 11.79 30.55 +69 "C21" 0 0 0 0 87.38 90.38 75.45 +70 "C22" 0 0 100 100 1.57 1.54 1.33 +71 "C23" 0 70 0 60 10.61 7.67 7.74 +72 "C24" 70 0 0 80 3.84 4.59 6.23 +END_DATA + + diff --git a/target/Jamfile b/target/Jamfile new file mode 100644 index 0000000..3a3ef28 --- /dev/null +++ b/target/Jamfile @@ -0,0 +1,74 @@ + +# Print Calibration Target Data File generator + +#PREF_CCFLAGS += $(CCOPTFLAG) ; # Turn optimisation on +PREF_CCFLAGS += $(CCDEBUGFLAG) ; # Debugging flags +#PREF_CCFLAGS += $(CCHEAPDEBUG) ; # Heap Debugging flags +PREF_LINKFLAGS += $(LINKDEBUGFLAG) ; # Link debugging flags +#PREF_CCFLAGS += $(CCPROFFLAG) ; # Profile flags +#PREF_LINKFLAGS += $(LINKPROFFLAG) ; # Profile link flags + +#Products +Executables = targen printtarg ; +Samples = ECI2002.ti2 + ECI2002R.ti2 + ColorChecker.ti2 + FograStrip2.ti1 FograStrip2_2.ti2 + FograStrip3.ti1 FograStrip3_3.ti2 + i1_RGB_Scan_1.4.ti2 ; + +#Install +InstallBin $(DESTDIR)$(PREFIX)/bin : $(Executables) ; +InstallFile $(DESTDIR)$(PREFIX)/$(REFSUBDIR) : $(Samples) ; + +HDRS = ../h ../numlib ; + +Objects alphix.c randix.c ; + +HDRS += ../plot ../rspl ../cgats ../icc ../gamut ../xicc ../spectro ../render $(TIFFINC) ; + +LINKLIBS = ../xicc/libxcolorants ../spectro/libconv ../xicc/libxicc ../spectro/libinsttypes + ../gamut/libgamut ../rspl/librspl ../render/librender ../cgats/libcgats + ../plot/libplot ../plot/libvrml ../icc/libicc ../numlib/libnum + $(TIFFLIB) $(JPEGLIB) $(LibWin) ; + + +#target generator +Main targen : targen.c ofps.c ifarp.c simplat.c simdlat.c prand.c ; + +# Film Calibration Target File generator +Main filmtarg : filmtarg.c : : : $(TIFFINC) : alphix randix : $(TIFFLIB) $(JPEGLIB) ; + +# Print Calibration Target Postscript File generator +Main printtarg : printtarg.c : : : : alphix randix : ; + +# Individual stand alone test of sample point classes + +# Percepttual point distribution +#MainVariant ppoint : ppoint.c : : STANDALONE_TEST ; + +# Optimised farthest point sampling class +MainVariant ofps : ofps.c : : STANDALONE_TEST ; + +# Incremental far point class +MainVariant ifarp : ifarp.c : : STANDALONE_TEST ; + +# Perceptual space simplex lattice +MainVariant ifarp : ifarp.c : : STANDALONE_TEST ; + +MainVariant simplat : simplat.c : : STANDALONE_TEST ; + +# Device space simplex lattice +MainVariant simdlat : simdlat.c : : STANDALONE_TEST ; + +if $(BUILD_JUNK) { + + # Test program + Main test : test.c : : : : alphix ; + + # Test program + Main temp : temp.c ; + + # Test lmean experiment + Main lmean : lmean.c ; +} diff --git a/target/License.txt b/target/License.txt new file mode 100644 index 0000000..a871fcf --- /dev/null +++ b/target/License.txt @@ -0,0 +1,662 @@ + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + 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. + + + Copyright (C) + + 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 . + +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 +. + diff --git a/target/Makefile.am b/target/Makefile.am new file mode 100644 index 0000000..d9f050b --- /dev/null +++ b/target/Makefile.am @@ -0,0 +1,28 @@ +include $(top_srcdir)/Makefile.shared + +CPPFLAGS+=-D_FORTIFY_SOURCE=2 + +privatelib_LTLIBRARIES = libtarget.la +privatelibdir = $(pkglibdir) + +libtarget_la_SOURCES = alphix.c alphix.h randix.c randix.h + +LDADD = ./libtarget.la ../rspl/librspl.la ../plot/libvrml.la \ + $(ICC_LIBS) ../render/librender.la ../cgats/libcgats.la \ + ../xicc/libxicc.la ../gamut/libgamut.la \ + ../spectro/libinsttypes.la ../spectro/libconv.la \ + ../numlib/libargyllnum.la ../libargyll.la $(TIFF_LIBS) + +bin_PROGRAMS = targen printtarg + +targen_DEPENDENCIES = ../spectro/libinsttypes.la +targen_SOURCES = targen.c targen.h ofps.c ofps.h ifarp.c ifarp.h \ + simplat.c simplat.h simdlat.c simdlat.h prand.c prand.h + +printtarg_SOURCES = printtarg.c + +refdir = $(datadir)/color/argyll/ref + +ref_DATA = $(wildcard *.ti2) + +EXTRA_DIST = License.txt Readme.txt diff --git a/target/Readme.txt b/target/Readme.txt new file mode 100644 index 0000000..99a8317 --- /dev/null +++ b/target/Readme.txt @@ -0,0 +1,13 @@ +This directory containts routines that generate print or +film calibration test charts, based on perceptually evenly +distributed, random sequence points, suitable for reading +using an Xrite DTP51 coloromiter or Xrite DTP41 spectromter +(print) or Gretag Spectrolino (film). The generated print +file is a PostScript file, while the film recorder file is +a TIFF file. + +targen Is used to generate the test point values. + +target Is used to generate a PostScript file from those values. + + diff --git a/target/afiles b/target/afiles new file mode 100644 index 0000000..4b06b9f --- /dev/null +++ b/target/afiles @@ -0,0 +1,32 @@ +Readme.txt +License.txt +afiles +Jamfile +randix.c +randix.h +alphix.c +alphix.h +targen.c +targen.h +printtarg.c +filmtarg.c +ppoint.c +ppoint.h +ofps.h +ofps.c +simplat.h +simplat.c +simdlat.h +simdlat.c +ifarp.h +ifarp.c +prand.h +prand.c +ColorChecker.ti2 +FograStrip2.ti1 +FograStrip2_2.ti2 +FograStrip3.ti1 +FograStrip3_3.ti2 +i1_RGB_Scan_1.4.ti2 +ECI2002.ti2 +ECI2002R.ti2 diff --git a/target/alphix.c b/target/alphix.c new file mode 100644 index 0000000..68dfd48 --- /dev/null +++ b/target/alphix.c @@ -0,0 +1,468 @@ + +/* + * Argyll Color Correction System + * + * Alphabetic index class. + * + * Author: Graeme W. Gill + * Date: 22/8/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. + */ + +/* TTBD: + It would be nice if it was possible to add a delimeter between + the X and Y identifiers so that number + number or alpha + alpha + identification was possible. + */ + +#include +#include +#include +#include "numsup.h" +#include "alphix.h" + +/* Return the alpha index for the given raw index number */ +/* Return NULL on error */ +static char *torawix(alphix *p, int ix) { + int i, j, n; + int z = 0; + char *rv, *v; + + if (ix < 0 || ix >= p->rmct) + return NULL; /* Index range error */ + + if ((rv = malloc((p->nd+1) * sizeof(char))) == NULL) + return NULL; + + for (v = rv, j = 0, i = p->nd-1; i >= 0; j++, i--) { + char c; + n = ix / p->ds[i].b; + ix -= n * p->ds[i].b; + c = p->ds[i].seq[n]; + if (p->ds[i].z && z == 0 && c == '0') + c = ' '; + if (z != 0 || c != ' ') { + *v++ = p->ds[i].seq[n]; + z = 1; + } + } + *v = '\000'; + + return rv; +} + +/* Return the the raw index number given the alpha index */ +/* return -1 on error */ +static int fromanat(alphix *p, char *ax) { + char *v, *tb, _tb[11]; + int cl; + int i, k, rv = 0; + + if (p->nd > 10) { + if ((tb = malloc((p->nd+1) * sizeof(char))) == NULL) + return -1; /* Malloc error */ + } else + tb = _tb; + + /* Pack the string out to the right number of digits with spaces. */ + cl = strlen(ax); + for (v = tb; cl < p->nd; v++, cl++) + *v = ' '; + strcpy(v, ax); + + /* For each digit, convert to numerical and accumulate */ + for (v = tb, i = p->nd-1; i >= 0; v++, i--) { + for (k = 0; k < p->ds[i].n; k++) { + if (*v == p->ds[i].seq[k] + || (p->ds[i].z && *v == ' ' && p->ds[i].seq[k] == '0')) { + rv += k * p->ds[i].b; + break; + } + } + if (k >= p->ds[i].n) { + if (tb != _tb) + free(tb); + return -1; /* Unknown digit */ + } + } + + if (tb != _tb) + free(tb); + + return rv; +} + +/* Working from the end of the string, */ +/* return a pointer to the maximum number of characters */ +/* that are legaly part of this alpha index. */ +static char *find_start(alphix *p, char *ax) { + int i, k; + char *v; + + v = ax + strlen(ax) - 1; /* start at last character */ + for (i = 0; v >= ax && i < p->nd; v--, i++) { /* working backwards */ + for (k = 0; k < p->ds[i].n; k++) { /* Locate */ + if (*v == p->ds[i].seq[k]) + break; /* Found */ + } + if (k >= p->ds[i].n) /* Not found */ + break; + } + return v+1; +} + +/* Convert from cooked index to raw index. */ +/* Return -1 if out of range */ +static int cookedtoraw(alphix *p, int ix) { + int i; + + if (p->nr == 0) + return ix; + + for (i = 0; i < p->nr; i++) { + if (ix >= p->rs[i].c0 && ix <= p->rs[i].c1) { + ix = ix - p->rs[i].c0 + p->rs[i].r0; + return ix; + } + } + return -1; +} + +/* Convert from raw index to cooked index. */ +/* Return -1 if out of range */ +static int rawtocooked(alphix *p, int ix) { + int i; + + if (p->nr == 0) + return ix; + + for (i = 0; i < p->nr; i++) { + if (ix >= p->rs[i].r0 && ix <= p->rs[i].r1) { + ix = ix - p->rs[i].r0 + p->rs[i].c0; + return ix; + } + } + return -1; +} + +/* Return the maximum possible length of count */ +static int alphix_maxlen(alphix *p) { + return p->cmct; +} + +/* Return the alpha index for the given index number (0 based) */ +/* Return NULL on error */ +/* (Free after use) */ +static char *alphix_aix(alphix *p, int ix) { + if ((ix = cookedtoraw(p, ix)) < 0) + return NULL; + return torawix(p, ix); +} + +/* Return the index number for the given alpha index */ +int alphix_nix(alphix *p, char *ax) { + int rv; + if ((rv = fromanat(p, ax)) < 0) + return -1; + return rawtocooked(p, rv); +} + +/* Destroy ourselves */ +static void +alphix_del(alphix *p) { + int i; + for (i = 0; i < p->nd; i++) + free(p->ds[i].seq); + free (p->ds); + free (p->rs); + free (p); +} + +/* Constructor: */ +alphix *new_alphix(char *pattern) { + alphix *p; + char *pp = pattern; + int i; + + if ((p = (alphix *)calloc(1,sizeof(alphix))) == NULL) + error("alphix: malloc failed"); + + p->maxlen = alphix_maxlen; + p->aix = alphix_aix; + p->nix = alphix_nix; + p->del = alphix_del; + + /* We now need to parse the pattern to setup our index sequence: */ + /* For all digits */ + for (p->nd = 0; ; p->nd++) { + + if (*pp == '\000' || *pp == ';') { + break; + } + + /* We are starting a new digit sequence */ + if (p->nd >= p->_nd) { /* Allocate space for current digit */ + p->_nd = 2 + p->_nd; + if ((p->ds = (dig *)realloc(p->ds, p->_nd * sizeof(dig))) == NULL) + error ("alphix: realloc failed"); + } + + p->ds[p->nd].n = 0; + p->ds[p->nd].seq = NULL; + p->ds[p->nd]._n = 0; + p->ds[p->nd].z = 0; + + /* For all symbols in this digit */ + for (p->ds[p->nd].n = 0; ;) { + char c, c1, c2; + + /* We are adding another character to the sequence */ + if (*pp == '\000' || *pp == ';' || *pp == ',') + break; /* Done this digit */ + if (pp[1] == '-' && pp[2] != '\000' && pp[2] != ';' && pp[2] != ',') { + c1 = pp[0]; + c2 = pp[2]; + pp += 3; + } else { + c1 = c2 = *pp; + pp++; + } + if (c1 == '@') { + c1 = '0'; + p->ds[p->nd].z = 1; + } + if (c2 == '@') { + c2 = '0'; + p->ds[p->nd].z = 1; + } + + /* Expand digit sequence */ + for (c = c1; c <= c2; c++) { + + if (p->ds[p->nd].n >= p->ds[p->nd]._n) { /* Allocate space for next character */ + p->ds[p->nd]._n = 20 + p->ds[p->nd]._n; + if ((p->ds[p->nd].seq = (char *)realloc(p->ds[p->nd].seq, p->ds[p->nd]._n * sizeof(char))) == NULL) + error ("alphix: realloc failed"); + } + p->ds[p->nd].seq[p->ds[p->nd].n++] = c; + } + } + if (*pp == '\000' || *pp == ';') + continue; + pp++; + + } + + /* Compute the native maximum index count */ + for (p->rmct = 1, i = 0; i < p->nd; i++) { + p->ds[i].b = p->rmct; + p->rmct *= p->ds[i].n; + } + + /* Search for valid ranges */ + if (*pp == ';') { + char *v, *tb, _tb[11]; + + pp++; + if (p->nd > 10) { + if ((tb = malloc((p->nd+1) * sizeof(char))) == NULL) + error ("alphix: malloc failed"); + } else + tb = _tb; + + /* For each range */ + for (p->nr = 0; ; p->nr++) { + + if (*pp == '\000' || *pp == ';') + break; + + /* We are adding a new range */ + if (p->nr >= p->_nr) { /* Allocate space for current range */ + p->_nr = 2 + p->_nr; + if ((p->rs = (rngsq *)realloc(p->rs, p->_nr * sizeof(rngsq))) == NULL) + error ("alphix: realloc failed"); + } + + /* Locate the end of the range start */ + for(v = tb; *pp != '\000' && *pp != '-' && *pp != ','; v++, pp++) + *v = *pp; + *v = '\000'; + p->rs[p->nr].r0 = p->rs[p->nr].r1 = fromanat(p, tb); + if (p->rs[p->nr].r0 < 0) + error("alphix: range start definition error on '%s'",tb); + + + if (*pp != '-') { /* oops - bad definition */ + error("alphix: range definition error - missing '-'"); + } + + /* Locate the end of the range end */ + for(v = tb, pp++; *pp != '\000' && *pp != ','; v++, pp++) + *v = *pp; + *v = '\000'; + p->rs[p->nr].r1 = fromanat(p, tb); + if (p->rs[p->nr].r1 < 0) + error("alphix: range end definition error on '%s'",tb); + + if (p->rs[p->nr].r1 < p->rs[p->nr].r0) /* Hmm */ + error("alphix: range definition error, end < start "); + + /* Compute cooked index range */ + p->rs[p->nr].c0 = 0; + p->rs[p->nr].c1 = p->rs[p->nr].r1 - p->rs[p->nr].r0; + if (p->nr > 0) { + int ofs = p->rs[p->nr-1].c1+1; + p->rs[p->nr].c0 += ofs; + p->rs[p->nr].c1 += ofs; + } + + if (*pp == '\000' || *pp == ';') + continue; + pp++; + } + + if (tb != _tb) + free(tb); + } + + /* Compute cooked actual range */ + p->cmct = p->rmct; + if (p->nr > 0) { + p->cmct = p->rs[p->nr-1].c1+1; + } + +#ifdef NEVER + // ########################################################### + /* Debug stuff */ + printf("~1 There are %d digits, %d ranges, raw max = %d, cooked max = %d\n", + p->nd,p->nr,p->rmct, p->maxlen(p)); + + /* Digit sequences */ + for (i = 0; i < p->nd; i++) { + printf(" ~1 Digit %d has %d symbols\n",i,p->ds[i].n); + for (j = 0; j < p->ds[i].n; j++) + printf(" ~1 symbol %d = '%c'\n",j,p->ds[i].seq[j]); + } + + /* Ranges */ + for (i = 0; i < p->nr; i++) { + printf(" ~1 Range %d is raw %d - %d, cooked %d - %d\n", + i,p->rs[i].r0, p->rs[i].r1, p->rs[i].c0, p->rs[i].c1); + } + + /* Itterate the sequence */ + for (i = 0; i < p->cmct; i++) { + char *v; + v = p->aix(p, i); + printf("ix %d -> '%s'\n",i,v); + free(v); + } + // ########################################################### +#endif /* NEVER */ + + return p; +} + + +/* ==================================================================== */ +/* Utility function: */ +/* Given the strip and patch alphix objects, and order flag, */ +/* Return a patch location. Free the returned string after use. */ +/* Return NULL on error */ +char *patch_location( + alphix *saix, /* Strip alpha index object */ + alphix *paix, /* Patch alpha index object */ + int ixord, /* Index order, 0 = strip then patch */ + int six, /* Strip index */ + int pix /* Patch index */ +) { + char *sl, *pl, *rv; + int ll; + + if ((sl = saix->aix(saix, six)) == NULL) + return NULL; + + if ((pl = paix->aix(paix, pix)) == NULL) { + free (sl); + return NULL; + } + ll = strlen(sl) + strlen(pl) + 1; + if ((rv = malloc(ll * sizeof(char))) == NULL) { + free (pl); + free (sl); + return NULL; + } + + if (ixord == 0) { + strcpy (rv, sl); + strcat (rv, pl); + } else { + strcpy (rv, pl); + strcat (rv, sl); + } + return rv; +} + +/* ==================================================================== */ +/* Utility function: */ +/* Given the strip and patch alphix objects, and order flag, */ +/* and a corresonding patch location string, return an index */ +/* number suitable for sorting location strings. */ +/* The sort order is in order of strips, then patches within each strip */ +/* Return -1 on error */ +int patch_location_order( + alphix *saix, /* Strip alpha index object */ + alphix *paix, /* Patch alpha index object */ + int ixord, /* Index order, 0 = strip then patch */ + char *_ax /* Patch location string */ +) { + char *ax; /* Copy of input string */ + char *v; + alphix *rh; /* Least significant, right hand alphix */ + alphix *lh; /* Most significant, left hand alphix */ + int ri, li; /* Right hand and left hand index numbers */ + + if ((ax = malloc(strlen(_ax)+1)) == NULL) + return -1; + strcpy(ax,_ax); + + if (ixord == 0) { + lh = saix; /* Strip is left hand */ + rh = paix; /* Patch is right hand */ + } else { + rh = saix; /* Strip is right hand */ + lh = paix; /* Patch is left hand */ + } + + /* We need to identify the boundary between */ + /* the right hand and left hand indexes. */ + /* We assume that the sequences are distinguishable ... */ + v = find_start(rh, ax); + + ri = rh->nix(rh, v); + *v = '\000'; + li = lh->nix(lh, ax); + free(ax); + if (ri < 0 || li < 0) + return -1; + + if (ixord == 0) /* Strip is left hand */ + return li * rh->cmct + ri; + else + return ri * lh->cmct + li; +} + + + + + + + + + + diff --git a/target/alphix.h b/target/alphix.h new file mode 100644 index 0000000..091c191 --- /dev/null +++ b/target/alphix.h @@ -0,0 +1,118 @@ + +#ifndef ALPHIX_H + +/* + * Argyll Color Correction System + * + * Alphabetic indexing class + * + * Author: Graeme W. Gill + * Date: 22/8/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. + */ + +/* + * + * Syntax of alphix pattern: + * + * First comes the definition of the symbols for each digit + * location, LS to MS. The number of definitions declares the + * maximum number of digits. For example, for a normal 2 digit numerical + * sequence: "0123456789, 123456789" (note the space is significant) + * would define 0..99 with the MS digit supressed when it is 0. + * Ranges can be used for brevity: * "0-9, 1-9". + * As a special case, the '@' character can be used + * instead of '0' to indicate suppression of the leading zero. + * Leading ' ' characters in a generated sequence are omitted. + * + * Optional, and delimited by a ';' character, valid segments of the + * index sequence can be defined. For instance, to define the index + * range to be 1..49 one could use the pattern "0-9, 1-9;1-49" + * + * Of course the main reason for using alphix is to allow letter index + * sequences. For a sequence A, B, C .. AA, AB, AC etc. (the default + * used in Argyll), the following pattern would be used: "A-Z, A-Z" + * + * For a some ECI2002R charts that skip columns Y and Z, the following + * might be used: "A-Z, 2-9;A-X,2A-9Z" + */ + +#define ALPHIX_MAX 4 /* Maximum digits allowed */ + +/* Definition for each digit sequence */ +typedef struct { + int n; /* Number of characters in sequence */ + char *seq; /* Sequence of characters */ + int _n; /* Allocation size of seq */ + int b; /* Base of this digit */ + int z; /* NZ if leading zero is to be supressed */ +} dig; + +/* Definition for each range sequence */ +typedef struct { + int r0,r1; /* Raw index start and end of range */ + int c0,c1; /* Cooked index start and end of range */ +} rngsq; + +struct _alphix { +/* private: */ + int nd; /* Number of digits */ + dig *ds; /* Digit sequences */ + int _nd; /* Allocation size of ds */ + int rmct; /* Raw maximum count */ + int cmct; /* Cooked maximum count */ + + int nr; /* Number of ranges */ + rngsq *rs; /* Digit sequences */ + int _nr; /* Allocation size of rs */ + +/* public: */ + /* Return the maximum possible length of count */ + int (*maxlen)(struct _alphix *p); + + /* Return the alpha index for the given index number (0 based) */ + /* Return NULL on error */ + char *(*aix)(struct _alphix *p, int ix); + + /* Return the index number for the given alpha index */ + /* Return -1 on error */ + int (*nix)(struct _alphix *p, char *ax); + + /* Destroy ourselves */ + void (*del)(struct _alphix *p); + +}; typedef struct _alphix alphix; + +/* Constructor: */ +extern alphix *new_alphix(char *pattern); + + +/* Utility function: */ +/* Given the strip and patch alhix objects, and order flag, */ +/* Return a patch location. */ +/* Return NULL on error */ +char *patch_location( + alphix *saix, /* Strip alpha index object */ + alphix *paix, /* Patch alpha index object */ + int ixord, /* Index order, 0 = strip then patch */ + int six, /* Strip index (from 0) */ + int pix); /* Patch index (from 0) */ + +/* Utility function: */ +/* Given the strip and patch alphix objects, and order flag, */ +/* and a coresonding patch location string, return an index */ +/* number suitable for sorting location strings. */ +/* Return -1 on error */ +int patch_location_order( + alphix *saix, /* Strip alpha index object */ + alphix *paix, /* Patch alpha index object */ + int ixord, /* Index order, 0 = strip then patch */ + char *_ax); /* Patch location string */ + +#define ALPHIX_H +#endif /* ALPHIX_H */ diff --git a/target/filmtarg.c b/target/filmtarg.c new file mode 100644 index 0000000..af341dd --- /dev/null +++ b/target/filmtarg.c @@ -0,0 +1,458 @@ +/* + * Argyll Color Correction System + * Film recorder chart generator module. + * + * Author: Neil Okamoto + * Date: 1/3/2001 + * + * Copyright 2001, DreamWorks LLC + * All rights reserved. + * + * This material is licenced under the GNU GENERAL PUBLIC LICENSE Version 2 :- + * see the License2.txt file for licencing details. + */ + +/* This program generates a set of TIFF images containing color test patches, + * given the .ti1 file specifying what the colors are. + * + * The output is designed to facilitate output on a 35mm motion picture film + * recorder such as those manufactured by Cineon, Celco, MGI, ARRI, etc. */ + +/* Description: + * + */ + +/* TTBD: + * + * - eliminate gamma hack -- do it correctly in targen. + * - write a proper description + * - multi-patch mosaics on a single tiff image + * - render text labels into images + */ + +#undef DEBUG + +#include +#include +#include +#if defined(__IBMC__) +#include +#endif +#include +#include +#include +#include +#include "copyright.h" +#include "aconfig.h" +#include "cgats.h" +#include "randix.h" +#include "tiffio.h" + +#include +void error(char *fmt, ...), warning(char *fmt, ...), verbose(int level, char *fmt, ...); + +/* A color structure */ +/* This can hold all representations simultaniously */ +typedef struct { + int t; /* Tag */ +#define T_W 0x1 +#define T_RGB 0x2 +#define T_CMYK 0x4 + double gy; + double r,g,b; + double c,m,y,k; + char *id; /* Id string */ + char loc[5]; /* Location ID string */ +} col; + + +void +generate_tiffs( +char *basename, /* Base file name for output tiffs */ +col *cols, /* Array of colors to be put on target chart */ +int npat, /* Number of test targets needed */ +char *label, /* Per set label */ +int pw, int ph, /* Patch width and height */ +int rand, /* Randomise flag */ +int rstart, /* Starting index for random */ +int verb) /* Verbose flag */ +{ + randix *r = NULL; /* Random index order object */ + int ix; /* Patch index in target list */ + int i; /* Logical patch in target list */ + char slab[6]; /* Strip label */ + char fname[200]; /* File name */ + TIFF *tiff; /* TIFF file handle */ + unsigned short* scanline; /* scanline buffer */ + int x,y; /* position in TIFF image */ + + if (rand) + r = new_randix(npat, rstart); + + i = 0; + while (i < npat) { + + if (rand) + ix = r->next(r); /* Next pseudo random index */ + else + ix = i; + + sprintf(slab, "P%04d",i+1); + strcpy(cols[ix].loc, slab); + sprintf(fname, "%s.%04d.tiff", basename, i+1); + + if ((tiff = TIFFOpen(fname, "w")) == NULL) { + fprintf(stderr,"Failed to open output %s\n", fname); + exit(-1); + } + + if (!(scanline = (unsigned short*)malloc(3*sizeof(short)*pw))) { + fprintf(stderr,"Failed to malloc a scanline buffer\n"); + exit(-1); + } + + TIFFSetField(tiff, TIFFTAG_IMAGEWIDTH, pw); + TIFFSetField(tiff, TIFFTAG_IMAGELENGTH, ph); + TIFFSetField(tiff, TIFFTAG_ORIENTATION, ORIENTATION_TOPLEFT); + TIFFSetField(tiff, TIFFTAG_SAMPLESPERPIXEL, 3); + TIFFSetField(tiff, TIFFTAG_BITSPERSAMPLE, 16); + TIFFSetField(tiff, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG); + TIFFSetField(tiff, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_RGB); + TIFFSetField(tiff, TIFFTAG_COMPRESSION, COMPRESSION_LZW); + + if (verb) { + printf("writing patch %4d (%4.2f %4.2f %4.2f) to %s\n", + ix, cols[ix].r, cols[ix].g, cols[ix].b, fname); + } + + /* fill scanline */ + for (x=0; xdel(r); + +} + +/* film size structure */ +typedef struct { + char *name; /* User name (lower case) */ + int w,h; /* Width and height in pixels */ +} film; + +static film filmsizes[] = { + { "Acad_Full", 3656, 2664 }, + { "Acad_Half", 1828, 1332 }, + { "Cscope_Full", 3656, 3112 }, + { "Cscope_Half", 1828, 1556 }, + { "FullAp_Full", 4096, 3112 }, + { "FullAp_Half", 2048, 1556 }, + { "Vista_Full", 4096, 6144 }, + { "Vista_Half", 2048, 3072 }, + { NULL, 0, 0 } +}; + +/* Case independent string compare */ +int +cistrcmp(char *s1, char *s2) { + for (;;s1++, s2++) { + if (tolower(*s1) != tolower(*s2)) + return 1; + if (*s1 == '\000') + return 0; + } +} + +void +usage(void) { + film *ff; + fprintf(stderr,"Generate Film Target image sequence, Version %s\n",ARGYLL_VERSION_STR); + fprintf(stderr,"Licensed under the GPL\n"); + fprintf(stderr,"usage: filmtarg [-v] [-r] [-g gamma] [-f format] basename\n"); + fprintf(stderr," -v Verbose mode\n"); + fprintf(stderr," -r Don't randomise patch location\n"); + fprintf(stderr," -g gamma Gamma correction / linearization\n"); + fprintf(stderr," -f format Select format from:\n"); + for (ff = filmsizes; ff->name != NULL; ff++) + fprintf(stderr," \t%s\t[%d x %d]\n", ff->name, ff->w, ff->h); + fprintf(stderr," basename Base name for input(.ti1)/output(.ti2)\n"); + exit(1); +} + + +int +main(argc,argv) +int argc; +char *argv[]; +{ + int fa,nfa; /* current argument we're looking at */ + int verb = 0; + int rand = 1; + double gamma = 1.0; + static char inname[200] = { 0 }; /* Input cgats file base name */ + static char outname[200] = { 0 }; /* Output cgats file base name */ + static char tiffname[200] = { 0 }; /* Output postscrip file base name */ + cgats *icg; /* input cgats structure */ + cgats *ocg; /* output cgats structure */ + int i; + int si, fi, wi; /* sample id index, field index, keyWord index */ + char *label = "Argyll Color Correction System - Development chart"; + film *flm = &filmsizes[1]; /* Default film format = Academy Half */ + col *cols; + int npat; /* Number of patches */ + time_t clk = time(0); + struct tm *tsp = localtime(&clk); + char *atm = asctime(tsp); /* Ascii time */ + char buf[100]; /* general sprintf buffer */ + int rstart = 0; /* Random sequence start value */ + +#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(); + + /* Verbosity */ + else if (argv[fa][1] == 'v' || argv[fa][1] == 'V') + verb = 1; + /* Randomisation */ + else if (argv[fa][1] == 'r' || argv[fa][1] == 'R') + rand = 0; + /* Gamma */ + else if (argv[fa][1] == 'g' || argv[fa][1] == 'G') { + fa = nfa; + if (na == NULL) usage(); + gamma = atof(na); + } + /* Image format */ + else if (argv[fa][1] == 'f' || argv[fa][1] == 'F') { + fa = nfa; + if (na == NULL) usage(); + for (flm = filmsizes; flm->name != NULL; flm++) { + if (cistrcmp(na, flm->name) == 0) + break; + } + if (flm->name == NULL) + usage(); + } else + usage(); + } + else + break; + } + + /* Get the file name argument */ + if (fa >= argc || argv[fa][0] == '-') usage(); + strcpy(inname,argv[fa]); + strcat(inname,".ti1"); + strcpy(outname,argv[fa]); + strcat(outname,".ti2"); + strcpy(tiffname,argv[fa]); + + icg = new_cgats(); /* Create a CGATS structure */ + icg->add_other(icg, "CTI1"); /* our special input type is Calibration Target Information 1 */ + + if (icg->read_name(icg, inname)) + error("CGATS file read error : %s",icg->err); + + if (icg->t[0].tt != tt_other || icg->t[0].oi != 0) + error ("Input file isn't a CTI1 format file"); + if (icg->ntables < 1) + error ("Input file doesn't contain at least one table"); + + if ((npat = icg->t[0].nsets) <= 0) + error ("No sets of data"); + + if ((cols = (col *)malloc(sizeof(col) * npat)) == NULL) + error("Malloc failed!"); + + /* Setup output cgats file */ + ocg = new_cgats(); /* Create a CGATS structure */ + ocg->add_other(ocg, "CTI2"); /* our special type is Calibration Target Information 2 */ + ocg->add_table(ocg, tt_other, 0); /* Start the first table */ + + ocg->add_kword(ocg, 0, "DESCRIPTOR", "Argyll Calibration Target chart information 2",NULL); + ocg->add_kword(ocg, 0, "ORIGINATOR", "Argyll filmtarg", NULL); + atm[strlen(atm)-1] = '\000'; /* Remove \n from end */ + ocg->add_kword(ocg, 0, "CREATED",atm, NULL); + + /* Note what instrument this chart is setup for */ + ocg->add_kword(ocg, 0, "TARGET_INSTRUMENT", "GretagMacbeth SpectroScanT", NULL); + + /* Copy various parameters through */ + if ((wi = icg->find_kword(icg, 0, "SINGLE_DIM_STEPS")) >= 0) + ocg->add_kword(ocg, 0, "SINGLE_DIM_STEPS",icg->t[0].kdata[wi], NULL); + + if ((wi = icg->find_kword(icg, 0, "COMP_GREY_STEPS")) >= 0) + ocg->add_kword(ocg, 0, "COMP_GREY_STEPS",icg->t[0].kdata[wi], NULL); + + if ((wi = icg->find_kword(icg, 0, "MULTI_DIM_STEPS")) >= 0) + ocg->add_kword(ocg, 0, "MULTI_DIM_STEPS",icg->t[0].kdata[wi], NULL); + + if ((wi = icg->find_kword(icg, 0, "FULL_SPREAD_PATCHES")) >= 0) + ocg->add_kword(ocg, 0, "FULL_SPREAD_PATCHES",icg->t[0].kdata[wi], NULL); + + + /* Fields we want */ + ocg->add_field(ocg, 0, "SAMPLE_ID", nqcs_t); + ocg->add_field(ocg, 0, "SAMPLE_LOC", cs_t); + + if ((si = icg->find_field(icg, 0, "SAMPLE_ID")) < 0) + error ("Input file doesn't contain field SAMPLE_ID"); + if (icg->t[0].ftype[si] != nqcs_t) + error ("Field SAMPLE_ID is wrong type"); + + /* Figure out the color space */ + if ((fi = icg->find_kword(icg, 0, "COLOR_REP")) < 0) + error ("Input file doesn't contain keyword COLOR_REPS"); + if (strcmp(icg->t[0].kdata[fi],"CMYK") == 0) { + error ("We don't support CMYK yet"); + } else if (strcmp(icg->t[0].kdata[fi],"RGB") == 0) { + int ri, gi, bi; + 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 RGB_R is wrong type"); + 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 RGB_G is wrong type"); + 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 RGB_B is wrong type"); + 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", NULL); + if (gamma != 1.0) { + sprintf(buf, "%6.4f", gamma); + ocg->add_kword(ocg, 0, "GAMMA", buf, NULL); + } + for (i = 0; i < npat; i++) { + cols[i].t = T_RGB; + cols[i].id = ((char *)icg->t[0].fdata[i][si]); + /* normalized and possibly gamma corrected */ + cols[i].r = pow(*((double *)icg->t[0].fdata[i][ri]) / 100.0, gamma); + cols[i].g = pow(*((double *)icg->t[0].fdata[i][gi]) / 100.0, gamma); + cols[i].b = pow(*((double *)icg->t[0].fdata[i][bi]) / 100.0, gamma); + } + } else if (strcmp(icg->t[0].kdata[fi],"W") == 0) { + error ("We don't support GRAY yet"); + } else + error ("Input file keyword COLOR_REPS has unknown value"); + + + if (verb) + printf("Film format chosen is %s [%d x %d]\n", flm->name, flm->w, flm->h); + + if (rand) { + rstart = clk % npat; + sprintf(buf,"%d",rstart); + ocg->add_kword(ocg, 0, "RANDOM_START", buf, NULL); + } + + generate_tiffs(tiffname, cols, npat, label, flm->w, flm->h, rand, rstart, verb); + + /* Write out the patch info to the output CGATS file */ + for (i = 0; i < npat; i++) { + if (cols[i].t & T_CMYK) + ocg->add_set(ocg, 0, cols[i].id, cols[i].loc, 100.0 * cols[i].c, 100.0 * cols[i].m, + 100.0 * cols[i].y, 100.0 * cols[i].k); + else if (cols[i].t & T_RGB) + ocg->add_set(ocg, 0, cols[i].id, cols[i].loc, 100.0 * cols[i].r, + 100.0 * cols[i].g, 100.0 * cols[i].b); + else if (cols[i].t & T_W) + ocg->add_set(ocg, 0, cols[i].id, cols[i].loc, 100.0 * cols[i].gy); + } + + if (ocg->write_name(ocg, outname)) + error("Write error : %s",ocg->err); + + free(cols); + ocg->del(ocg); /* Clean up */ + icg->del(icg); /* Clean up */ + + return 0; +} + +/******************************************************************/ +/* Error/debug output routines */ +/******************************************************************/ + + +/* Basic printf type error() and warning() routines */ + +void +error(char *fmt, ...) +{ + va_list args; + + fprintf(stderr,"filmtarg: Error - "); + va_start(args, fmt); + vfprintf(stderr, fmt, args); + va_end(args); + fprintf(stderr, "\n"); + exit (-1); +} + +void +warning(char *fmt, ...) +{ + va_list args; + + fprintf(stderr,"filmtarg: Warning - "); + va_start(args, fmt); + vfprintf(stderr, fmt, args); + va_end(args); + fprintf(stderr, "\n"); +} + + + + + + + diff --git a/target/i1_RGB_Scan_1.4.ti2 b/target/i1_RGB_Scan_1.4.ti2 new file mode 100644 index 0000000..8aaf081 --- /dev/null +++ b/target/i1_RGB_Scan_1.4.ti2 @@ -0,0 +1,319 @@ +CTI2 + +DESCRIPTOR "Argyll Calibration Target chart information 2 for Eye-One Scan Target 1.4" +ORIGINATOR "Argyll printtarg" +CREATED "Wed Sep 12 00:02:26 2007" +KEYWORD "TARGET_INSTRUMENT" +TARGET_INSTRUMENT "GretagMacbeth i1 Pro" +KEYWORD "ACCURATE_EXPECTED_VALUES" +ACCURATE_EXPECTED_VALUES "true" +KEYWORD "COLOR_REP" +COLOR_REP "RGB" +KEYWORD "STEPS_IN_PASS" +STEPS_IN_PASS "18" +KEYWORD "PASSES_IN_STRIPS2" +PASSES_IN_STRIPS2 "16" +KEYWORD "STRIP_INDEX_PATTERN" +STRIP_INDEX_PATTERN "0-9,@-9,@-9;1-999" +KEYWORD "PATCH_INDEX_PATTERN" +PATCH_INDEX_PATTERN "A-Z, A-Z" +KEYWORD "INDEX_ORDER" +INDEX_ORDER "PATCH_THEN_STRIP" + +KEYWORD "SAMPLE_LOC" +NUMBER_OF_FIELDS 8 +BEGIN_DATA_FORMAT +SAMPLE_ID SAMPLE_LOC RGB_R RGB_G RGB_B XYZ_X XYZ_Y XYZ_Z +END_DATA_FORMAT + +NUMBER_OF_SETS 288 +BEGIN_DATA +1 "A1" 0 0 0 0.52 0.59 0.56 +2 "A2" 20 40 100 10.27 10 32.62 +3 "A3" 80 0 20 13.89 7.3 2.84 +4 "A4" 100 40 20 33.34 23.69 4.75 +5 "A5" 69.8 69.8 69.8 31.79 33.22 28.61 +6 "A6" 46.67 46.67 46.67 13.85 14.51 12.72 +7 "A7" 100 40 40 35.52 25.53 10.91 +8 "A8" 66.67 60 66.67 27.16 27.04 25.26 +9 "A9" 29.8 29.8 29.8 6.12 6.42 5.76 +10 "A10" 26.67 33.33 33.33 6.04 6.8 6.75 +11 "A11" 0 20 100 4.4 3.47 20.55 +12 "A12" 40 0 60 6.34 3.41 11.34 +13 "A13" 40 20 60 9.03 7.05 14.88 +14 "A14" 40 20 40 7.91 6.52 8.41 +15 "A15" 60 20 80 16.18 10.77 24.37 +16 "A16" 0 0 60 1.97 1.1 8.66 +17 "B1" 60 100 20 27.55 34.83 6.23 +18 "B2" 80 20 0 17.94 11.4 1.02 +19 "B3" 80 80 20 34.87 35.19 5.79 +20 "B4" 20 80 80 13.05 19.54 29.87 +21 "B5" 0 60 100 6.88 9.15 27.74 +22 "B6" 100 100 80 70.59 73.69 44.02 +23 "B7" 13.33 13.33 6.67 2.04 2.19 1.41 +24 "B8" 0 40 0 1.35 2.84 0.94 +25 "B9" 100 60 80 49.51 40.62 36.32 +26 "B10" 100 60 40 43.26 35.61 12.01 +27 "B11" 13.33 20 20 2.65 3.13 3.25 +28 "B12" 40 80 60 17.77 24.45 21.08 +29 "B13" 0 100 100 8.51 14.13 31.85 +30 "B14" 0 80 100 7.84 11.93 30.12 +31 "B15" 0 100 60 5.81 12.04 16.38 +32 "B16" 60 20 0 11.49 8.06 0.99 +33 "C1" 100 20 100 34.5 19.81 37.1 +34 "C2" 80 80 73.33 42.27 43.99 33.47 +35 "C3" 0 60 60 4.36 7.54 14.12 +36 "C4" 100 100 20 58.73 57.76 6.5 +37 "C5" 60 0 80 11.22 5.5 17.82 +38 "C6" 6.67 13.33 13.33 1.55 1.89 2.09 +39 "C7" 74.9 74.9 74.9 37.5 39.03 33.61 +40 "C8" 80 40 20 23.67 18.54 4.44 +41 "C9" 20 80 20 8.12 14.07 4.81 +42 "C10" 0 100 0 2.9 7.38 1.41 +43 "C11" 73.33 73.33 66.67 34.09 35.82 26.91 +44 "C12" 40 33.33 40 9.45 9.34 9.05 +45 "C13" 100 60 20 41.11 33.34 5.29 +46 "C14" 80 40 60 27.38 21.65 19.2 +47 "C15" 64.71 64.71 64.71 27.38 28.66 24.61 +48 "C16" 80 80 100 48.58 49.16 58.53 +49 "D1" 20 80 0 7.47 12.02 1.86 +50 "D2" 80 0 80 17.27 8.47 18.91 +51 "D3" 100 20 0 24.86 14.78 0.99 +52 "D4" 60 0 20 8.61 4.81 2.74 +53 "D5" 80 60 20 28.85 26.03 5.07 +54 "D6" 40 80 80 20.34 26.75 33.64 +55 "D7" 40 0 40 5.39 3.09 6.34 +56 "D8" 40 0 80 7.42 3.7 17.12 +57 "D9" 86.67 100 100 60.57 66.88 64.61 +58 "D10" 26.67 20 26.67 4.42 4.33 4.38 +59 "D11" 0 20 60 2.63 2.73 10.91 +60 "D12" 80 100 60 46.48 54.19 25.5 +61 "D13" 0 20 20 1.24 2.02 2.92 +62 "D14" 0 60 20 2.43 5.58 3.81 +63 "D15" 40 0 0 4.46 2.69 0.72 +64 "D16" 100 0 80 23.58 11.3 19.67 +65 "E1" 60 40 60 18.33 16.26 18.07 +66 "E2" 40 80 0 13.25 17.2 1.73 +67 "E3" 40 80 40 16.1 22.52 11.75 +68 "E4" 86.67 86.67 80 50.7 52.96 40.49 +69 "E5" 4.71 4.71 4.71 0.93 1.03 0.98 +70 "E6" 0 0 80 2.58 1.28 12.24 +71 "E7" 93.33 93.33 86.67 61.11 63.67 49.46 +72 "E8" 20 80 40 9.69 16.36 11.19 +73 "E9" 60 60 20 19.64 20.4 5.14 +74 "E10" 100 100 69.8 68.63 71.55 34.47 +75 "E11" 73.33 66.67 73.33 33.86 33.75 30.83 +76 "E12" 100 0 0 18.65 9.44 0.83 +77 "E13" 20 100 60 12.88 22.5 20.49 +78 "E14" 80 60 100 40.57 35.97 51.8 +79 "E15" 60 60 80 25.89 26.22 33.39 +80 "E16" 60 100 40 30.29 39.21 13.78 +81 "F1" 0 80 20 3.2 7.65 4.38 +82 "F2" 0 20 80 3.71 3.35 16.24 +83 "F3" 80 20 80 23.31 14.61 25.55 +84 "F4" 80 20 40 20.14 13.3 9.05 +85 "F5" 53.33 60 60 19.65 21.55 20.22 +86 "F6" 6.67 6.67 0 1.21 1.33 0.75 +87 "F7" 0 60 80 5.8 8.65 21.51 +88 "F8" 100 0 40 20.82 10.37 7.25 +89 "F9" 60 0 100 12.98 6.05 24.77 +90 "F10" 60 40 20 15.96 14.23 4.62 +91 "F11" 100 100 0 54.03 50.45 2.31 +92 "F12" 60 60 60 23.38 24.4 20.86 +93 "F13" 20 40 0 4.42 5.8 1.14 +94 "F14" 40 100 20 17.42 25.87 5.81 +95 "F15" 0 40 20 1.8 3.63 3.4 +96 "F16" 60 20 20 12.33 9.08 3.65 +97 "G1" 100 40 80 41.01 28.98 32.18 +98 "G2" 20 100 80 14.7 24.18 31.92 +99 "G3" 0 40 80 4.63 5.68 19.04 +100 "G4" 100 100 93.33 73.8 76.63 58.3 +101 "G5" 100 0 100 25.16 11.63 27.13 +102 "G6" 60 80 20 23.64 27.52 5.74 +103 "G7" 80 80 60 40.74 42.65 24.74 +104 "G8" 20 40 20 5.04 6.8 4 +105 "G9" 20 80 100 15.6 21.51 42.53 +106 "G10" 80 86.67 86.67 47.5 50.89 46.6 +107 "G11" 40 46.67 46.67 11.95 13.23 12.63 +108 "G12" 0 0 20 0.74 0.68 2.21 +109 "G13" 60 40 80 20.56 17.42 28.91 +110 "G14" 20 20 13.33 3.34 3.61 2.24 +111 "G15" 80 0 60 15.88 7.93 12.58 +112 "G16" 60 100 60 32.76 42.79 24.95 +113 "H1" 13.33 6.67 13.33 1.99 1.85 1.96 +114 "H2" 20 20 60 5.23 4.73 13.17 +115 "H3" 60 100 80 35.1 45.5 39.54 +116 "H4" 49.8 49.8 49.8 15.44 16.22 14.02 +117 "H5" 54.9 54.9 54.9 18.85 19.72 16.96 +118 "H6" 100 0 20 19.88 10.01 3.06 +119 "H7" 40 20 100 12.31 8.36 31.05 +120 "H8" 80 60 80 37.11 33.66 35.77 +121 "H9" 80 60 40 31.4 29.01 12.25 +122 "H10" 20 40 40 5.92 7.65 8.81 +123 "H11" 94.9 94.9 94.9 65.82 68.12 58.73 +124 "H12" 40 60 60 15.16 18.34 19.38 +125 "H13" 60 40 0 14.99 12.73 1.65 +126 "H14" 86.67 93.33 93.33 56.71 60.58 54.75 +127 "H15" 20 60 60 9.34 13.22 17.86 +128 "H16" 80 0 40 14.91 7.72 6.98 +129 "I1" 80 40 40 25.37 20.12 10.52 +130 "I2" 84.71 84.71 84.71 49.34 51.3 44.11 +131 "I3" 33.33 26.67 33.33 6.63 6.47 6.58 +132 "I4" 6.67 0 6.67 1.07 0.94 1.24 +133 "I5" 60 0 0 8.34 4.69 0.92 +134 "I6" 60 40 100 23.16 18.65 41.02 +135 "I7" 20 60 20 6.72 10.56 4.53 +136 "I8" 80 60 0 27.97 23.98 1.71 +137 "I9" 0 0 100 3.27 1.46 15.75 +138 "I10" 100 80 60 55.74 51.88 24.53 +139 "I11" 20 60 40 7.7 11.77 9.88 +140 "I12" 9.8 9.8 9.8 1.57 1.7 1.49 +141 "I13" 60 60 100 28.92 28.09 47.66 +142 "I14" 100 20 40 28.27 17.22 9.16 +143 "I15" 100 100 86.67 72.13 75.17 50.99 +144 "I16" 0 40 60 3.58 5.09 12.93 +145 "J1" 20 60 100 13.14 15.8 38.03 +146 "J2" 20 100 20 9.81 17.93 5.25 +147 "J3" 100 86.67 100 68.6 65.82 63.75 +148 "J4" 100 40 100 43.83 30.19 45.87 +149 "J5" 80 0 100 18.39 8.6 26.07 +150 "J6" 0 80 60 5.36 10.17 15.84 +151 "J7" 86.67 86.67 100 55.67 57.15 61.71 +152 "J8" 40 60 80 17.35 19.86 30.55 +153 "J9" 60 0 40 9.34 5.12 6.56 +154 "J10" 20 26.67 26.67 4.06 4.65 4.46 +155 "J11" 100 80 80 59.67 55.56 40.66 +156 "J12" 86.67 80 86.67 49.63 49.42 45.4 +157 "J13" 80 100 20 41.62 45.81 6.35 +158 "J14" 20 20 40 4.44 4.57 7.21 +159 "J15" 46.67 40 46.67 12.73 12.57 11.83 +160 "J16" 80 60 60 33.9 31.33 21.78 +161 "K1" 80 100 0 39.02 40.41 2.14 +162 "K2" 20 20 80 6.59 5.47 20 +163 "K3" 20 60 80 11 14.42 27.43 +164 "K4" 0 20 40 1.86 2.4 6.45 +165 "K5" 40 100 40 18.68 28.5 12.59 +166 "K6" 100 100 100 75.76 78.23 67.4 +167 "K7" 33.33 33.33 26.67 7.32 7.87 5.36 +168 "K8" 60 60 0 18.49 18.14 1.64 +169 "K9" 80 80 40 37.54 38.95 13.07 +170 "K10" 80 100 100 55.35 62.8 63.8 +171 "K11" 33.33 40 40 8.76 9.83 9.34 +172 "K12" 34.9 34.9 34.9 8.38 8.86 7.56 +173 "K13" 80 20 60 21.69 14.04 16.27 +174 "K14" 40 100 0 16.19 22.43 2.19 +175 "K15" 100 100 40 62.66 63.7 14.84 +176 "K16" 0 100 80 7.22 13.33 24.18 +177 "L1" 40 0 20 4.75 2.89 2.62 +178 "L2" 60 100 100 38.93 48.68 58.9 +179 "L3" 93.33 100 93.33 65.92 71 57.4 +180 "L4" 100 100 60 66.52 69 26.91 +181 "L5" 40 40 100 15.55 13.99 36.92 +182 "L6" 60 40 40 17.18 15.48 10.42 +183 "L7" 80 100 80 51.65 59.75 43.16 +184 "L8" 60 20 100 18.25 11.8 33.47 +185 "L9" 20 20 20 3.47 3.71 3.23 +186 "L10" 60 20 40 12.9 9.35 8.48 +187 "L11" 100 80 100 64.65 59.55 61.46 +188 "L12" 100 80 0 45.13 38.69 1.96 +189 "L13" 0 80 80 6.43 10.92 22.75 +190 "L14" 60 53.33 60 21.35 21.35 19.83 +191 "L15" 80 40 100 33.05 24.65 43.94 +192 "L16" 60 60 53.33 22.66 23.84 17.58 +193 "M1" 100 86.67 86.67 65.11 63.01 48.58 +194 "M2" 20 0 0 1.94 1.46 0.65 +195 "M3" 80 40 0 22.58 16.86 1.28 +196 "M4" 60 0 60 10.28 5.41 11.55 +197 "M5" 20 100 40 11.02 20.07 11.6 +198 "M6" 93.33 86.67 93.33 59.78 59.84 54.76 +199 "M7" 0 40 40 2.71 4.6 7.83 +200 "M8" 40 100 80 23.91 34.53 36.93 +201 "M9" 100 20 60 30.27 18.26 16.91 +202 "M10" 40 100 100 26.5 36.45 52.65 +203 "M11" 40 60 40 13.33 16.63 10.94 +204 "M12" 0 80 40 4.04 8.83 9.08 +205 "M13" 26.67 26.67 26.67 5.17 5.59 5.05 +206 "M14" 0 100 20 3.61 9.2 4.51 +207 "M15" 40 40 33.33 10.14 10.8 7.78 +208 "M16" 20 20 0 3.13 3.26 0.84 +209 "N1" 0 60 40 3.29 6.6 8.25 +210 "N2" 0 100 40 4.65 10.78 9.31 +211 "N3" 66.67 73.33 73.33 30.72 33.49 30.48 +212 "N4" 40 60 0 10.72 12.58 1.45 +213 "N5" 40 20 80 10.17 7.54 21.82 +214 "N6" 20 80 60 11.18 18 19.3 +215 "N7" 100 80 40 53.3 49.34 13.79 +216 "N8" 40 20 20 7.14 6.18 3.7 +217 "N9" 20 40 60 7.18 8.54 15.43 +218 "N10" 40 40 40 10.26 10.86 9.44 +219 "N11" 93.33 93.33 100 64.17 66.15 63.94 +220 "N12" 80 40 80 29.82 22.94 30.75 +221 "N13" 80 73.33 80 41.27 41.14 38.01 +222 "N14" 40 40 80 13.72 13.09 26.73 +223 "N15" 93.33 100 100 67.54 72.16 65.85 +224 "N16" 100 69.8 100 59.57 51.66 57.98 +225 "O1" 80 0 0 13.5 7.07 0.76 +226 "O2" 100 80 20 49.21 44.05 5.87 +227 "O3" 80 20 20 18.71 12.3 3.74 +228 "O4" 73.33 80 80 38.42 41.38 37.84 +229 "O5" 40 80 20 14.29 19.83 5.34 +230 "O6" 100 40 60 38.12 27.36 19.84 +231 "O7" 0 20 0 0.82 1.5 0.8 +232 "O8" 86.67 100 86.67 58.19 65.09 49.46 +233 "O9" 60 80 0 22.03 24.06 1.89 +234 "O10" 100 60 100 54.38 44.04 54.64 +235 "O11" 53.33 53.33 46.67 17.33 18.31 13.39 +236 "O12" 20 13.33 20 3.07 2.92 2.94 +237 "O13" 40 40 0 8.57 8.65 1.33 +238 "O14" 0 60 0 2.04 4.54 1.45 +239 "O15" 20 0 100 5.58 2.53 20.51 +240 "O16" 40 40 20 9.55 10.01 4.38 +241 "P1" 60 80 60 27.28 32.46 22.7 +242 "P2" 0 0 40 1.26 0.88 4.93 +243 "P3" 60 80 100 33.17 37.11 53.34 +244 "P4" 40 0 100 8.31 3.84 22.57 +245 "P5" 0 6.67 6.67 0.65 0.9 1.06 +246 "P6" 20 0 60 3.58 1.97 10.4 +247 "P7" 20 100 100 17.53 26.57 45.79 +248 "P8" 100 60 0 38.54 30.11 1.74 +249 "P9" 100 93.33 93.33 70.01 70.01 56.41 +250 "P10" 69.8 100 100 45.98 54.96 61.34 +251 "P11" 0 40 100 5.76 6.4 24.4 +252 "P12" 66.67 66.67 60 27.4 28.95 21.64 +253 "P13" 20 40 80 8.91 9.56 24.29 +254 "P14" 60 20 60 14.32 10.16 15.55 +255 "P15" 80 100 40 44.13 50.38 14.14 +256 "P16" 80 20 100 25.91 15.77 35.63 +257 "Q1" 20 20 100 7.89 5.75 27.26 +258 "Q2" 40 60 100 19.45 21.15 43.11 +259 "Q3" 20 0 20 2.18 1.59 2.48 +260 "Q4" 14.9 14.9 14.9 2.47 2.57 2.36 +261 "Q5" 100 93.33 100 71.98 71.6 65.38 +262 "Q6" 60 66.67 66.67 25.4 27.72 25.57 +263 "Q7" 100 60 60 46.78 38.92 22.52 +264 "Q8" 60 80 80 30.85 35.91 37 +265 "Q9" 20 100 0 9.11 15.52 1.94 +266 "Q10" 40 40 60 11.65 11.86 16.65 +267 "Q11" 60 60 40 20.6 21.96 11.36 +268 "Q12" 0 80 0 2.49 6.09 1.38 +269 "Q13" 80 80 80 43.66 45.37 39.11 +270 "Q14" 89.8 89.8 89.8 56.73 58.92 50.71 +271 "Q15" 46.67 46.67 40 13.37 14.14 10.18 +272 "Q16" 60 80 40 25.67 30.42 12.63 +273 "R1" 40 60 20 11.96 14.98 5.06 +274 "R2" 60 100 0 25.55 30.36 2.16 +275 "R3" 40 100 60 20.96 31.53 22.46 +276 "R4" 100 20 20 26.63 16.16 3.75 +277 "R5" 46.67 53.33 53.33 15.45 17.15 15.99 +278 "R6" 20 0 40 2.87 1.83 5.87 +279 "R7" 20 60 0 5.94 8.9 1.32 +280 "R8" 100 0 60 22.2 10.8 12.85 +281 "R9" 100 20 80 32.45 19.2 26.87 +282 "R10" 100 40 0 31.17 21.44 1.48 +283 "R11" 20 0 80 4.71 2.36 15.65 +284 "R12" 53.33 46.67 53.33 16.81 16.68 15.67 +285 "R13" 80 80 0 33.39 31.69 1.87 +286 "R14" 40 20 0 6.67 5.46 0.93 +287 "R15" 40 80 100 23.87 29.65 49.1 +288 "R16" 26.67 26.67 20 5.1 5.43 3.45 +END_DATA diff --git a/target/ifarp.c b/target/ifarp.c new file mode 100644 index 0000000..7e1117e --- /dev/null +++ b/target/ifarp.c @@ -0,0 +1,946 @@ + +/* + * Argyll Color Correction System + * + * Incremental far point class + * + * Author: Graeme W. Gill + * Date: 6/11/2002 + * + * Copyright 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. + */ + +/* + Algorithm: + + Starting with a previous test point as a seed, use a random starte point + and minimisation algorithm to locate another point that is as far as + possible from the nearest existing test point in perceptual space, + while remaining in gamut at all tines. This means that ideally each + point "fills in" the gaps in the existing distribution, while starting + from an existing point. + + The performance is still not very good, as the inner loop involves + locating the nearest existing point, as well as converting from + device coordinates to perceptual space. If the powell search radius + is reduced too much the uniformity of the distribution suffers. + + */ + +/* TTBD: + + It would probably help the uniformity of distribution if we could + aproximately locate the next seed point as the one with the + biggest adjoing "gap", and this may speed things up by allowing us + to reduce the powel search radius. + + Perhaps switching to a balltree indexing structure would speed up + nearest ppoint finding as well as providing a mechanism to quickly + locate the nearest "void". + + Subsequent experience indicates that furthest distance in perceptual + space may not be the best strategy, but furthest distance in device + space may be. Add #define allowing this to be tested ?? + + */ + +#undef DEBUG +#define PERC_PLOT 1 /* Emit perceptive space plots (if DEBUG) */ +#define DO_WAIT 1 /* Wait for user key after each plot */ + +#define ASSERTS + +#include +#include +#include +#include +#if defined(__IBMC__) +#include +#endif +#ifdef DEBUG +#include "plot.h" +#endif +#include "numlib.h" +#include "sort.h" +#include "plot.h" +#include "icc.h" +#include "xcolorants.h" +#include "targen.h" +#include "ifarp.h" +#include "../h/sort.h" /* Heap sort */ + +#ifdef DEBUG +static void dump_image(ifarp *s, int pcp); +static void dump_image_final(ifarp *s, int pcp); +#endif + +#define MAX_TRIES 30 /* Maximum itterations */ + + +/* nn functions */ +static double nearest(ifarp *s, double *q); +static void init_nn(ifarp *s); +static void add_nn(ifarp *s); +static void del_nn(ifarp *s); + +/* ----------------------------------------------------- */ +/* Default convert the nodes device coordinates into approximate perceptual coordinates */ +static void +ifarp_to_percept(void *od, double *p, double *d) { + ifarp *s = (ifarp *)od; + int e; + + /* Do nothing - copy device to perceptual. */ + for (e = 0; e < s->di; e++) { + p[e] = d[e] * 100.0; + } +} + + +/* Return the largest distance of the point outside the device gamut. */ +/* This will be 0 if inside the gamut, and > 0 if outside. */ +static double +ifarp_in_dev_gamut(ifarp *s, double *d) { + int e; + int di = s->di; + double tt, dd = 0.0; + double ss = 0.0; + + for (e = 0; e < di; e++) { + ss += d[e]; + + tt = 0.0 - d[e]; + if (tt > 0.0) { + if (tt > dd) + dd = tt; + } + tt = d[e] - 1.0; + if (tt > 0.0) { + if (tt > dd) + dd = tt; + } + } + tt = ss - s->ilimit; + if (tt > 0.0) { + if (tt > dd) + dd = tt; + } + return dd; +} + +/* Snap a point to the device gamut boundary. */ +/* Return nz if it has been snapped. */ +static int snap_to_gamut(ifarp *s, double *d) { + int e; + int di = s->di; + double dd; /* Smallest distance */ + double ss; /* Sum */ + int rv = 0; + + /* Snap to ink limit first */ + for (ss = 0.0, e = 0; e < di; e++) + ss += d[e]; + dd = fabs(ss - s->ilimit); + + if (dd < 0.0) { + int j; + for (j = 0; j < di; j++) + d[j] *= s->ilimit/ss; /* Snap to ink limit */ + rv = 1; + } + + /* Now snap to any other dimension */ + for (e = 0; e < di; e++) { + + dd = fabs(d[e] - 0.0); + if (dd < 0.0) { + d[e] = 0.0; /* Snap to orthogonal boundary */ + rv = 1; + } + dd = fabs(1.0 - d[e]); + if (dd < 0.0) { + d[e] = 1.0; /* Snap to orthogonal boundary */ + rv = 1; + } + } + + return rv; +} + +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ +/* Reverse lookup function :- perceptual to device coordinates */ + +/* Definition of the optimization functions handed to powell() */ + +/* Return metric to be minimised, and */ +/* an error >= 50000 on being out of device gamut */ +static double efunc(void *edata, double p[]) { + ifarp *s = (ifarp *)edata; + double rv; + if ((rv = (ifarp_in_dev_gamut(s, p))) > 0.0) { + rv = rv * 500.0 + 500.0; /* Discourage being out of gamut */ + } else { + double v[MXTD]; + s->percept(s->od, v, p); + rv = 500.0 - nearest(s, v); + } +//printf("~1 rv = %f from %f %f\n",rv,p[0],p[1]); + return rv; +} + +/* Given a point in device space, optimise it to be */ +/* within the device gamut, as well as being as far as */ +/* possible from the nearest point in perceptual space. */ +/* return nz if powell failed */ +static int +optimise_point( +ifarp *s, +double *d /* starting and returned device position */ +) { + int e, di = s->di; + double sr[MXTD]; /* Search radius in each device dimension */ + double drad = 1.0; /* Search Radius (affects fill evenness) */ + double ptol = 0.001; /* Tolerance */ + double tt; + +// ~~99 + for (e = 0; e < di; e++) + sr[e] = drad; /* Device space search radius */ + if (powell(&tt, di, d, sr, ptol, 500, efunc, (void *)s, NULL, NULL) != 0 || tt >= 50000.0) { +#ifdef DEBUG + warning("ifarp: powell failed, tt = %f",tt); +#endif + return 1; + } + snap_to_gamut(s, d); + return 0; +} + +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +/* Create a new node. */ +/* Return current number of nodes */ +static int new_node( +ifarp *s, +int ix /* Index of point to start from */ +) { + int di = s->di; + int e; + +// ~~99 + /* Retry if powell failes */ + for (;;) { + + /* Create the new point by cloning the existing point */ + s->nodes[s->np].fx = 0; /* Not a fixed/pre-existing node */ + for (e = 0; e < di; e++) { + s->nodes[s->np].p[e] = s->nodes[ix].p[e]; + } + /* Compute new point location that is farthest from nearest existing point */ + if (optimise_point(s, s->nodes[s->np].p) == 0) + break; + } + + /* compute perceptual location */ + s->percept(s->od, s->nodes[s->np].v, s->nodes[s->np].p); + +#ifdef DEBUG +printf("Added node %d at perc %f %f, dev %f %f\n", +s->np, +s->nodes[s->np].v[0], +s->nodes[s->np].v[1], +s->nodes[s->np].p[0], +s->nodes[s->np].p[1]); +#endif + + /* Add the node to our current list */ + s->nodes[s->np].touch = s->tbase; + s->np++; + add_nn(s); + + return s->np; +} + +/* ============================================= */ +/* Main object functions */ + +/* Initialise, ready to read out all the points */ +static void ifarp_reset(ifarp *s) { + s->rix = 0; +} + +/* Read the next set of non-fixed points values */ +/* return non-zero when no more points */ +static int ifarp_read( +ifarp *s, +double *d, /* Device position */ +double *p /* Perceptual value */ +) { + int j; + + for (; s->rix < s->np; s->rix++) { + + if (s->nodes[s->rix].fx == 0) { + for (j = 0; j < s->di; j++) { + if (d != NULL) + d[j] = s->nodes[s->rix].p[j]; + if (p != NULL) + p[j] = s->nodes[s->rix].v[j]; + } + s->rix++; + return 0; + } + } + return 1; +} + +/* Destroy ourselves */ +static void +ifarp_del(ifarp *s) { + + if (s->nodes != NULL) + free(s->nodes); + + free (s); +} + +/* Constructor */ +ifarp *new_ifarp( +int di, /* Dimensionality of device space */ +double ilimit, /* Ink limit (sum of device coords max) */ +int inp, /* Number of points to generate */ +fxpos *fxlist, /* List of existing fixed points (may be NULL) */ +int fxno, /* Number of existing fixes points */ +void (*percept)(void *od, double *out, double *in), /* Perceptual lookup func. */ +void *od /* context for Perceptual function */ +) { + ifarp *s; + int e, i; + int verb = 1; + +#ifdef DEBUG + printf("new_ifarp called with di %d, inp %d, fxno = %d\n",di,inp,fxno); +#endif + + if ((s = (ifarp *)calloc(sizeof(ifarp), 1)) == NULL) + error ("ifarp: ifarp malloc failed"); + +#if defined(__IBMC__) + _control87(EM_UNDERFLOW, EM_UNDERFLOW); + _control87(EM_OVERFLOW, EM_OVERFLOW); +#endif + + s->reset = ifarp_reset; + s->read = ifarp_read; + s->del = ifarp_del; + + /* If no perceptual function given, use default */ + if (percept == NULL) { + s->percept = ifarp_to_percept; + s->od = s; + } else { + s->percept = percept; + s->od = od; + } + + s->ilimit = ilimit; + + s->inp = inp; /* Intended number of points */ + s->np = 0; + + if (di > MXTD) + error ("ifarp: Can't handle di %d",di); + s->di = di; + s->tbase = 0; + + /* Initial alloc of nodes */ + if ((s->nodes = (ifpnode *)malloc(s->inp * sizeof(ifpnode))) == NULL) + error ("ifarp: nodes malloc failed"); + + /* Copy fixed nodes */ + for (i = 0; (i < fxno) && (s->np < s->inp); i++) { + s->nodes[s->np].fx = 1; + for (e = 0; e < di; e++) + s->nodes[s->np].p[e] = fxlist[i].p[e]; + s->percept(s->od, s->nodes[i].v, s->nodes[i].p); + s->nodes[s->np].touch = s->tbase; + s->np++; + } + + /* Create at least one seed point */ + if (s->np == 0) { + s->nodes[s->np].fx = 0; + + for (e = 0; e < di; e++) + s->nodes[s->np].p[e] = 0.0; /* This is assumed to be in gamut */ + s->percept(s->od, s->nodes[i].v, s->nodes[i].p); + s->nodes[s->np].touch = s->tbase; + s->np++; + } + + /* Setup initial nearest point acceleration structure */ + init_nn(s); + + /* Create initial patches */ +// ~~99 + + if (verb) + printf("Full points:\n"); + + for (i = 0; s->np < s->inp; i += 17) { + i %= s->np; + new_node(s, i); + if (verb) { + int pc = (int)(100.0 * s->np/s->inp + 0.5); + printf(" % 3d%%%c",pc,cr_char); fflush(stdout); + } + } + + if (verb) + printf("\n"); + + /* We're done with acceleration structure */ + del_nn(s); + + return s; +} + +/* --------------------------------------------------- */ +/* (This code is has been copied from gamut/gamut.c) */ + +#ifdef DEBUG +#define NN_INF 100000.0 +#else +#define NN_INF 1e307 +#endif + +/* Given a point, */ +/* return the nearest existint test point. */ +static double +nearest( +ifarp *s, +double *q /* Target point location */ +) { + int e, i, k; + int di = s->di; + int wex[MXTD * 2]; /* Current window edge indexes */ + double wed[MXTD * 2]; /* Current window edge distances */ + /* Indexes are axis * 2 +0 for lower edge, */ + /* +1 for upper edge of search box. */ + /* We are comparing lower edge of search box */ + /* with upper edge of bounding box etc. */ + +//printf("~1 nearest called\n"); + + /* We have to find out which existing point the point will be nearest */ + + if ((s->tbase + di) < s->tbase) { /* Overflow of touch count */ + for (i = 0; i < s->np; i++) + s->sax[0][i]->touch = 0; /* reset it in all the objects */ + s->tbase = 0; + } + s->ttarget = s->tbase + di; /* Target touch value */ + +//printf("\n"); +//printf("Query point is %f %f\n",q[0], q[1]); + + /* Find starting indexes within axis arrays */ + for (e = 0; e < (2 * di); e++) { /* For all axes min & max */ + int f = e/2; /* Axis */ + int i0, i1, i2; /* Search indexes */ + double v0, v1, v2; /* Box */ + double qf, ww; + + /* Binary search this edge */ + qf = q[f]; /* strength reduced q[f] */ + +//printf("\n"); +//printf("isearching axis %d %s for %f\n",f, e & 1 ? "max" : "min", qf); + i0 = 0; + i2 = s->np - 1; + v0 = s->sax[f][i0]->v[f]; + v2 = s->sax[f][i2]->v[f]; +//printf("start points %d - %d, bound %f - %f\n",i0, i2, v0, v2); + + if (qf <= v0) { + i2 = i0; + v2 = v0; + } else if (qf >= v2) { + i0 = i2; + v0 = v2; + } else { + do { + i1 = (i2 + i0)/2; /* Trial point */ + v1 = s->sax[f][i1]->v[f]; /* Value at trial */ + if (v1 < qf) { + i0 = i1; /* Take top half */ + v0 = v1; + } else { + i2 = i1; /* Take bottom half */ + v2 = v1; + } +//printf("current point %d - %d, bound %f - %f\n",i0, i2, v0, v2); + } while ((i2 - i0) > 1); + } + + if (e & 1) { /* Max side of window */ + int tc; /* total object count */ + + ww = v2 - qf; + wed[e] = fabs(ww) * ww; + wex[e] = i2; + + /* Check that min and max together will cover at least s->np objects */ + tc = s->np - i2 + wex[e ^ 1] + 1; +//printf("got %d, expected %d\n",tc, s->np); + + /* (I don't really understand why this works!) */ + if (tc < s->np) { /* We haven't accounted for all the objects */ + int el = e ^ 1; /* Low side sax */ + int ti0, ti2; + double tv0, tv2; + + ti0 = wex[el]; + ti2 = i2; +//printf("We have straddling objects, initial indexes are %d - %d\n",ti0, ti2); + + /* While straddling objects remain undiscovered: */ + while (tc < s->np) { + tv0 = NN_INF; /* Guard values */ + tv2 = -NN_INF; + + /* Increment low side until we find a straddler */ + while (ti0 < (s->np-1)) { + ww = s->sax[f][++ti0]->v[f]; /* Position of the other end */ + if (ww < qf) { +//printf("found low object %d at index %d that straddles\n",s->sax[f][ti0]-s->nodes,ti0); + tv0 = qf - s->sax[f][ti0]->v[f]; + break; + } + } + + /* Decrement high side until we find a straddler */ + while (ti2 > 0) { + ww = s->sax[f][--ti2]->v[f]; /* Position of the other end */ + if (ww > qf) { +//printf("found high object %d at index %d that straddles\n",s->sax[f][ti2]-s->nodes,ti2); + tv2 = s->sax[f][ti2]->v[f] - qf; + break; + } + } + /* Choose the closest */ + if (tv0 > tv2) { + wed[el] = fabs(tv0) * tv0; + wex[el] = ti0; + tc++; + } else { + wed[e] = fabs(tv2) * tv2; + wex[e] = ti2; + tc++; + } + } +//printf("After correction we have %d - %d\n",wex[e^1], wex[e]); + } + } else { /* Min side of window */ + ww = q[f] - v0; + wed[e] = fabs(ww) * ww; + wex[e] = i0; + } + } + + /* Expand a di dimenstional cube centered on the target point, */ + /* jumping to the next nearest point on any axis, discovering */ + /* any bounding boxes that are within the expanding window */ + /* by checking their touch count. */ + + /* The first point found establishes the initial best distance. */ + /* When the window expands beyond the point where it can improve */ + /* the best distance, stop */ + + { + double bw = 0.0; /* Current window distance */ + double bdist = NN_INF; /* Best possible distance to an object outside the window */ + int bix; /* Index of best point */ + + /* Until we're done */ + for (;;) { + int ee; /* Axis & expanding box edge */ + int ff; /* Axis */ + int ii; /* Index of chosen point */ + ifpnode *ob; /* Current object */ + unsigned int ctv; /* Current touch value */ +//printf("\n"); +//printf("wwidth = %f, bdist = %f, window = %d-%d, %d-%d\n", +//bw, bdist, wex[0], wex[1], wex[2], wex[3]); +//printf("window edge distances are = %f-%f, %f-%f\n", +//wed[0], wed[1], wed[2], wed[3]); + + /* find next (smallest) window increment axis and direction */ + ee = 0; + ii = wex[ee]; + bw = wed[ee]; + for (e = 1; e < (2 * di); e++) { + if (wed[e] < bw) { + ee = e; + ii = wex[e]; + bw = wed[e]; + } + } +//printf("Next best is axisdir %d, object %d, axis index %d, best possible dist %f\n", +//ee, s->sax[ee/2][ii] - s->nodes, ii, bw); + + if (bw == NN_INF || bw > bdist) { + break; /* Can't go any further, or further points will be worse */ + } + +#ifdef ASSERTS +if (ii < 0 || ii >= s->np) { +printf("Assert: went out of bounds of sorted axis array\n"); +exit(0); +} +#endif + /* Chosen point on ee axis/direction, index ii */ + ff = ee / 2; /* Axis only */ + + ob = s->sax[ff][ii]; + + /* Touch value of current object */ + ctv = ob->touch; + + if (ctv < s->ttarget) { /* Not been dealt with before */ + + /* Touch this new window boundary point */ + ob->touch = ctv = ((ctv < s->tbase) ? s->tbase : ctv) + 1; + +//printf("New touch count on %d is %d, target %d\n", +//ob - s->nodes, s->sax[ff][ii]->touch, s->ttarget); + + /* Check the point out */ + if (ctv == (s->tbase + di)) { /* Is within window on all axes */ + double tdist = 0.0; + + /* Compute distance from query point to this object */ + for (k = 0; k < di; k++) { + double tt = ob->v[k] - q[k]; + tdist += tt * tt; + } + +//printf("Got new best point %d, dist %f\n",ob-s->nodes,sqrt(tdist)); + if (tdist < bdist) { /* New closest distance */ + bdist = tdist; + bix = ob - s->nodes; + } + } + } + + /* Increment next window edge candidate, and figure new edge distance */ + if (ee & 1) { /* Top */ + if (++wex[ee] >= s->np) { + wed[ee] = NN_INF; + wex[ee]--; + } else { + double ww = s->sax[ff][wex[ee]]->v[ff] - q[ff]; + wed[ee] = fabs(ww) * ww; + } + } else { + if (--wex[ee] < 0) { + wed[ee] = NN_INF; + wex[ee]++; + } else { + double ww = q[ff] - s->sax[ff][wex[ee]]->v[ff]; + wed[ee] = fabs(ww) * ww; + } + } + } + + s->tbase += di; /* Next touch */ + +//printf("~1 returning closest to node %d distance %f\n",bix,sqrt(bdist)); + return sqrt(bdist); /* Return nearest distance */ + } +} + + +/* Setup the nearest function acceleration structure */ +/* with the existing points */ +static void +init_nn( +ifarp *s +) { + int di = s->di; + int i, k; + int np = s->np; /* Existing number of points */ + +//printf("~9 init_nn called\n"); + + s->tbase = 0; /* Initialse touch flag */ + + /* Allocate the arrays spaces for intended number of points */ + for (k = 0; k < di; k++) { + if ((s->sax[k] = (ifpnode **)malloc(sizeof(ifpnode *) * s->inp)) == NULL) + error("Failed to allocate sorted index array"); + } + + /* Add each existing test point to the axis lists. */ + for (i = 0; i < np; i++) { + for (k = 0; k < di; k++) + s->sax[k][i] = &s->nodes[i]; + } + + /* Sort the axis arrays */ + for (k = 0; k < di; k++) { + /* Sort nodes edge list */ +#define HEAP_COMPARE(A,B) (A->v[k] < B->v[k]) + HEAPSORT(ifpnode *, &s->sax[k][0], np) +#undef HEAP_COMPARE + } +//printf("~9 init_nn done\n"); +} + + +#ifdef NEVER /* Slower but simpler version */ + +/* Add the last point to the acceleration structure */ +static void +add_nn( +ifarp *s +) { + int di = s->di; + int i, k; + int np = s->np; /* Existing number of points */ + int ap = np - 1; /* Index of point ot add */ + +//printf("~9 add_nn called with point ix %d, pos %f %f\n",ap, s->nodes[ap].v[0],s->nodes[ap].v[1]); + + for (k = 0; k < di; k++) { + s->sax[k][ap] = &s->nodes[ap]; + } + + /* Sort the axis arrays */ + for (k = 0; k < di; k++) { + /* Sort nodes edge list */ +#define HEAP_COMPARE(A,B) (A->v[k] < B->v[k]) + HEAPSORT(ifpnode *, &s->sax[k][0], np) +#undef HEAP_COMPARE + } +} + +#else + +/* Add the last point to the acceleration structure */ +static void +add_nn( +ifarp *s +) { + int di = s->di; + int e; + int np = s->np; /* Existing number of points */ + int ap = np - 1; /* Index of point to add */ + +//printf("~9 add_nn called with point ix %d, pos %f %f\n",ap, s->nodes[ap].v[0],s->nodes[ap].v[1]); + + for (e = 0; e < di; e++) { /* For all axes */ + int i0, i1, i2; /* Search indexes */ + double v0, v1, v2; /* Box */ + double qf; + + qf = s->nodes[ap].v[e]; /* value to be insertion sorted */ + +//printf("isearching axis %d for %f\n",e, qf); + + /* Find index of lowest value that is greater than target */ + i0 = 0; + i2 = ap - 1; + v0 = s->sax[e][i0]->v[e]; + v2 = s->sax[e][i2]->v[e]; +//printf("start points %d - %d, bound %f - %f\n",i0, i2, v0, v2); + + if (qf <= v0) { + i1 = i0; + } else if (qf >= v2) { + i1 = ap; + } else { + do { + i1 = (i2 + i0)/2; /* Trial point */ + v1 = s->sax[e][i1]->v[e]; /* Value at trial */ + + if (qf > v1) { + i0 = i1; /* Take top half */ + v0 = v1; + } else { /* qf <= v1 */ + i2 = i1; /* Take bottom half */ + v2 = v1; + } +//printf("current point %d - %d, bound %f - %f\n",i0, i2, v0, v2); + } while ((i2 - i0) > 1); + + i1 = i0; + v1 = s->sax[e][i1]->v[e]; + + /* Ensure we're > than target */ + while (v1 <= qf) { + i1++; + if (i1 < ap) + v1 = s->sax[e][i1]->v[e]; + else + break; + } + } + + /* Make room */ + if (i1 < ap) { + memmove((void *)&s->sax[e][i1+1], (void *)&s->sax[e][i1], (ap - i1) * sizeof(ifpnode *)); + } + /* Insert */ + s->sax[e][i1] = &s->nodes[ap]; + } +} + +#endif + +/* Free everything to do with the nn */ +static void del_nn(ifarp *s) { + int di = s->di; + int k; + + for (k = 0; k < di; k++) { + free (s->sax[k]); + } +} + +/* =================================================== */ + +#ifdef STANDALONE_TEST + +icxColorantLu *clu; + +void sa_percept(void *od, double *out, double *in) { + +#ifdef NEVER + double lab[3]; + clu->dev_to_rLab(clu, lab, in); + + out[0] = lab[0]; +// out[1] = (lab[1]+100.0)/2.0; + out[1] = (lab[2]+100.0)/2.0; +#else + + out[0] = in[0] * 100.0; + out[1] = in[1] * 100.0; + +#endif +} + +int +main(argc,argv) +int argc; +char *argv[]; +{ + int npoints = 500; + ifarp *s; + int mask = ICX_BLACK | ICX_GREEN; + int di = 2; + + error_program = argv[0]; + check_if_not_interactive(); + + if (argc > 1) + npoints = atoi(argv[1]); + + if ((clu = new_icxColorantLu(mask)) == NULL) + error ("Creation of xcolorant lu object failed"); + + /* Create the required points */ + s = new_ifarp(di, 1.5, npoints, NULL, 0, sa_percept, (void *)NULL); + +#ifdef DEBUG + /* Dump perceptual map */ + dump_image(s, PERC_PLOT); +#endif /* DEBUG */ + + s->del(s); + + return 0; +} + +#endif /* STANDALONE_TEST */ + + + +#ifdef DEBUG + +/* Dump the current point positions to a plot window file */ +static void +dump_image(ifarp *s, int pcp) { + double minx, miny, maxx, maxy; + double *x1a = NULL; + double *y1a = NULL; + double *x2a = NULL; + double *y2a = NULL; + double *x3a = NULL; + double *y3a = NULL; + + int i, nu; + ifpnode *p; + + if (s->np == 0) + return; + + if (pcp) { /* Perceptual range */ + minx = 0.0; /* Assume */ + miny = 0.0; + maxx = 100.0; + maxy = 100.0; + } else { + minx = 0.0; /* Assume */ + miny = 0.0; + maxx = 1.0; + maxy = 1.0; + } + + if ((x1a = (double *)malloc(s->np * sizeof(double))) == NULL) + error ("ifarp: plot malloc failed %d",s->np); + if ((y1a = (double *)malloc(s->np * sizeof(double))) == NULL) + error ("ifarp: plot malloc failed %d",s->np); + if ((x2a = (double *)malloc(s->np * sizeof(double))) == NULL) + error ("ifarp: plot malloc failed %d",s->np); + if ((y2a = (double *)malloc(s->np * sizeof(double))) == NULL) + error ("ifarp: plot malloc failed %d",s->np); + + for (nu = i = 0; i < s->np; i++) { + p = &s->nodes[i]; + + if (pcp) { + x1a[nu] = p->v[0]; + y1a[nu] = p->v[1]; + x2a[nu] = p->v[0]; + y2a[nu] = p->v[1]; + } else { + x1a[nu] = p->p[0]; + y1a[nu] = p->p[1]; + x2a[nu] = p->p[0]; + y2a[nu] = p->p[1]; + } + nu++; + } + + /* Plot the vectors */ + do_plot_vec(minx, maxx, miny, maxy, + x1a, y1a, x2a, y2a, nu, DO_WAIT, x3a, y3a, 0); + + free(x1a); + free(y1a); + free(x2a); + free(y2a); +} + +#endif /* DEBUG */ + + + + + diff --git a/target/ifarp.h b/target/ifarp.h new file mode 100644 index 0000000..083411b --- /dev/null +++ b/target/ifarp.h @@ -0,0 +1,67 @@ + +#ifndef IFARP_H + +/* + * Argyll Color Correction System + * + * Incremental far point class + * + * Author: Graeme W. Gill + * Date: 6/11/2002 + * + * Copyright 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. + */ + +/* A sample point node */ +struct _ifpnode { + int fx; /* nz if point is fixed (existing) */ + double p[MXTD]; /* Device coordinate position */ + double v[MXTD]; /* Subjective value (Labnnn..) */ + + unsigned int touch; /* nn: Per value touch count */ +}; typedef struct _ifpnode ifpnode; + + +/* Main simplex latice object */ +struct _ifarp { +/* private: */ + int di; /* Point dimensionality */ + double ilimit; /* Ink limit - limit on sum of p[] */ + int inp; /* Intended number of points in list */ + int np; /* Number of point nodes in list */ + ifpnode *nodes; /* Current array of nodes */ + int rix; /* Next read index */ + + /* Perceptual function */ + void (*percept)(void *od, double *out, double *in); + void *od; /* Opaque data for perceptual point */ + + /* nn support */ + ifpnode **sax[MXTD]; /* Sorted axis pointers, one for each direction */ + unsigned int tbase; /* Touch base value for this pass */ + unsigned int ttarget; /* Touch target value for this pass */ + +/* public: */ + /* Initialise, ready to read out all the points */ + void (*reset)(struct _ifarp *s); + + /* Read the next set of non-fixed points values */ + /* return non-zero when no more points */ + int (*read)(struct _ifarp *s, double *d, double *p); + + /* Destroy ourselves */ + void (*del)(struct _ifarp *s); + +}; typedef struct _ifarp ifarp; + +/* Constructor */ +extern ifarp *new_ifarp(int di, double ilimit, int npoints, + fxpos *fxlist, int fxno, + void (*percept)(void *od, double *out, double *in), void *od); + +#define IFARP_H +#endif /* IFARP_H */ diff --git a/target/ofps.c b/target/ofps.c new file mode 100644 index 0000000..fdea8cb --- /dev/null +++ b/target/ofps.c @@ -0,0 +1,10222 @@ + +/* + * ArgyllCMS Color Correction System + * + * Optimised Farthest Point Sampling - NN + * + * Author: Graeme W. Gill + * Date: 6/9/2004 + * + * Copyright 2004, 2009 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. + */ + +/* Latest version using vertex nets to reduce internal accounting overhead, */ +/* in an attempt to improve performance scaling with larger numbers of points. */ + +/* TTBD: + + This code shouldn't exit on an error - this causes an unnecessary failure + when ofps is used to evaluate the point distribution of other + distribution algorithms. + + There is a bug when the ink limit == dimensions-1 (200% for CMYY), and + the number of bit mask then exceeds > 32. This is not so +/- 0.2% either side + of 200%. + + One way of addressing the performance issues would be to use multiple + threads to call dnsq. A pool could be setup, one for each CPU. + + Some profiles are too rough, and slow/stall vertex placement. + Reducing the cache grid and/or smoothing the rspl values + ay mitigate this to some degree, and make this more robust ?? + + */ + +/* + Description: + + We create a function that estimates the sample positioning error at + any location based on a weighted combination of perceptual and device distance, + and perceptual function curvature. + + We then initially add sampling points at the largest estimated error + verticies of the veronoi natural neighbourhood. + This gives us an optimal distribution measuring in mestimated position + error within a tollerance of 2:1 + + We then iteratively improve the distribution of point nodes by + moving them in the direction of the adjacent vertex with the + largest estimated sampling error, so that the errors are equally + distributed. + + To ensure that there is a good distribution of sampling + points on the edges and faces of the gamut, the initial + points are given a slighte weighting towards these + elements, and then fastened to them. The points on + each lower dimensional element (edge, face) is then + optimized only amongst themselves, while higher + dimension points are aware of the lower dimension + ones. In this way the distribution of points on + lower dimensional surfaces is well spread, while + the higher dimension points take thier positions into account. + */ + +/* + Failings: + + The initial allocation of points to lower dimension surfaces + is a bit haphazard. It would be nice to have some mechanism + to add or subtract points to/from lower dimensional surfaces + if they were over or under sampled compared to everything else. + + While the current algorithm meets many goals, such as minimizing the + maximum estimated error from any point in the space to the nearest + node, and placing nodes on sub dimensional surfaces with distributions + optimal within that sub dimensions, it has one obvious failing, and + that is that it fails to stratify the samples. + + So if a device is dominantly channel indepenedent, it doesn't + take advantage of the number of samples used to fully explore + all possible device channel values. This also applies at + higher dimensions (ie. the CMYK values exploring response + to different K values doesn't spread the CMY values + evenly appart.) + + Stratification seems to be somewhat at odds with the primary goal + of minimizing the maximum estimated error from any point in the + space to the nearest node, but it would be good if there were + some way of setting this balance. + + How could stratification be added to the current approach ? + + In general there are many sub-dimensions views, not all of + which would probably be regarded as important. + + To measure spread, independent voronoi tesselations of + these sub dimensions would be neededi, and they could be + used partly driver optimization (??). + + For 1D device channels this wouldn't be so hard to + add, although it's hard to know how effective it would + be, or whether it would wreck the ND optimization. It + might also be unecessary if per channel calibration + has been applied. + + For CMY this would need a 3D shadow veronoi. + + */ + +#include +#include +#include +#include +#if defined(__IBMC__) +#include +#endif +#include "numlib.h" +#include "sort.h" +#include "counters.h" +#include "plot.h" +#include "icc.h" +#include "xicc.h" +#include "xcolorants.h" +#include "targen.h" +#include "rspl.h" +#include "conv.h" +#include "ofps.h" + +//#include + +#undef DEBUG +#undef WARNINGS /* Print warnings within DEBUG */ +#undef STATS /* Show function stats */ + + /* Optimal fully adapted weightings : */ +#define ADAPT_PERCWGHT 0.65 /* Degree of perceptual adaptation */ +#define ADAPT_CURVWGHT 1.0 /* Degree of curvature */ + +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ +#ifndef STANDALONE_TEST // targen settings + +# define DOOPT /* Do optimization */ +# define INDEP_SURFACE /* Make surface point distribution and optimization independent */ +# undef MAXINDEP_2D /* Limit independent surfaces to 2D */ + /* Seems to be best for ink limited devices to #undef ? */ +# define KEEP_SURFACE /* Keep surface points on the surface during opt. */ +# define INITIAL_SURFACE_PREF 1.50 /* Extra weighting for surface points at start of seeding */ +# define FINAL_SURFACE_PREF 0.80 /* Extra weighting for surface points by end of seeding */ + +# define SURFTOL 0.0001 /* Proportion of average spacing to force to gamut boundary */ +# define RANDOM_PERTERB /* Perpterb initial placement to break up patterns */ + +/* Good mode */ +# define PERTERB_AMOUNT 0.5 /* and to aid surface point distribution with INDEP_SURFACE */ +# define OPT_MAXITS 20 /* Number of optimisation itterations (0 to disable optimisation) */ +# define OPT_TRANS_ITTERS 18 /* Numbers of itterations to transition overshoot and sepw */ +# define OPT_TRANS_POW 1.6 /* Power curve to blend along */ +# define OPT_INITIAL_OVERSHOOT 1.9 /* Optimisation movement initial overshoot */ +# define OPT_FINAL_OVERSHOOT 0.1 /* Optimisation movement final overshoot */ +# define OPT_INITIAL_SEP_WEIGHT 0.7 /* Weight to give separation of nodes during opt */ +# define OPT_FINAL_SEP_WEIGHT 0.3 /* Weight to give separation of nodes during opt */ +# define OPT_STOP_TOL 0.0005 /* Stopping tollerance */ + +/* Fast mode */ +# define PERTERB_AMOUNT_2 0.1 /* and to aid surface point distribution with INDEP_SURFACE */ +# define OPT_MAXITS_2 6 /* Number of optimisation itterations (0 to disable optimisation) */ +# define OPT_TRANS_ITTERS_2 5 /* Numbers of itterations to transition overshoot and sepw */ +# define OPT_TRANS_POW_2 1.7 /* Power curve to blend along */ +# define OPT_INITIAL_OVERSHOOT_2 1.6 /* Optimisation movement initial overshoot */ +# define OPT_FINAL_OVERSHOOT_2 0.05 /* Optimisation movement final overshoot */ +# define OPT_INITIAL_SEP_WEIGHT_2 0.8 /* Weight to give separation of nodes during opt */ +# define OPT_FINAL_SEP_WEIGHT_2 0.3 /* Weight to give separation of nodes during opt */ +# define OPT_STOP_TOL_2 0.001 /* Stopping tollerance */ + +/* Diagnostic settings */ +# undef DUMP_STRUCTURE /* Dump internal node & vertex structure */ +# undef DUMP_PLOT_SEED /* Show on screen plot for each initial seed point */ +# undef DUMP_PLOT /* Show on screen plot after each itteration */ +# define DUMP_VTX 1 /* Display the vertex locations too */ +# define DUMP_PLA 1 /* Display the node planes too */ +# define PERC_PLOT 0 /* Emit perceptive space plots */ +# define DO_WAIT 1 /* Wait for user key after each plot */ + +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ +#else /* ofps standalone test settings */ + +# define DOOPT /* Do optimization */ +# define INDEP_SURFACE /* Make surface point distribution and optimization independent */ +# define MAXINDEP_2D /* Limit independent surfaces to 2D */ +# define KEEP_SURFACE /* Keep surface points on the surface during opt. */ +# define INITIAL_SURFACE_PREF 1.60 /* Extra weighting for surface points at start of seeding */ +# define FINAL_SURFACE_PREF 0.80 /* Extra weighting for surface points by end of seeding */ + +# define SURFTOL 0.0001 /* Proportion of averag spacing to force to gamut boundary */ +# define RANDOM_PERTERB /* Perpterb initial placement to break up patterns, */ +# define PERTERB_AMOUNT 0.5 + +# define OPT_MAXITS 20 /* Number of optimisation itterations (0 to disable optimisation) */ +# define OPT_TRANS_ITTERS 18 /* Numbers of itterations to transition overshoot and sepw */ +# define OPT_TRANS_POW 2.5 /* Power curve to blend along */ +# define OPT_INITIAL_OVERSHOOT 0.8 /* Optimisation movement initial overshoot */ +# define OPT_FINAL_OVERSHOOT 0.1 /* Optimisation movement final overshoot */ +# define OPT_INITIAL_SEP_WEIGHT 0.9 /* Weight to give separation of nodes during opt */ +# define OPT_FINAL_SEP_WEIGHT 0.3 /* Weight to give separation of nodes during opt */ +# define OPT_STOP_TOL 0.0005 /* Device stopping tollerance */ + +/* Diagnostic settings */ +# undef DUMP_STRUCTURE /* Dump internal node & vertex structure */ +# undef DUMP_PLOT_SEED /* Show on screen plot for each initial seed point */ +# undef DUMP_PLOT_NCOL /* Show on screen plot after adding neighbours, before collecting */ +# define DUMP_PLOT /* Show on screen plot after each itteration */ +# undef DUMP_PLOT_RESEED /* Show on screen plot for each re-seed point */ +# undef DUMP_OPT_PLOT /* Show on screen plot for each optimization pass */ +# undef DUMP_PLOT_BEFORFIXUP /* Show plot after reposition but before fixups */ +# undef DUMP_PLOT_EACHFIXUP /* Show each node fixup */ +# define DUMP_VTX 1 /* Display the vertex locations too */ +# define DUMP_PLA 1 /* Display the node planes too */ +# define PERC_PLOT 0 /* Emit perceptive space plots */ +# define DO_WAIT 1 /* Wait for user key after each plot */ +# undef DUMP_EPERR /* Create .tiff of eperr */ +# undef DUMP_FERR /* 10000 */ /* Create .tiff of function error >= 20 and stop. */ +//# define SA_ADAPT 0.001 /* Standalone test, adaptation level */ + +# define SA_ADAPT -1.0 /* Standalone test, adaptation level (< 0.0, use individual) */ +# define SA_DEVD_MULT 1.0 /* Delta E for each percent of device space distance */ +# define SA_PERC_MULT 0.0 /* Delta E for each delta E of perceptual space distance */ +# define SA_INTERP_MULT 0.0 /* Delta E for each delta E of estimated interpolation error */ + +#endif /* NEVER */ +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ +/* Overall algorithm */ + +#define NINSERTTRIES 100 /* Number of seedin insert tries befor failing with error() */ + +#define NUMTOL 1e-16 /* Numerical tollerance */ +#define FTOL 1e-8 /* dnsqe function tollerance */ +#define FGPMUL 5.0 /* Weighting of gamut plane error into dnsqe function */ +#define COINTOL 1e-8 /* Tollerance for point cooincidence */ +#define ILIMITEPS 1e-6 /* imin, imax and ilimit clip test margine */ + +#define FASTREJMULT1 20.5 /* Fast reject layer distance fudge factor */ +#define FASTREJECTMULT 0.08 /* Fast reject cell skip threshold fudge factor */ + +#define CELLMAXEPERRFF 2.2 /* Cell worst case eperr from center fudge factor */ + +#define TNPAGRID 0.8 /* Target nodes per accelleration & peceptual cache grid cell */ +#define TNPAGRIDMINRES 7 /* Perceptual cache grid min resolution */ +#define TNPAGRIDMAXRES 33 /* Perceptual cache grid max resolution */ +#undef FORCE_INCREMENTAL /* Force incremental update after itteration */ +#undef FORCE_RESEED /* Force reseed after itteration */ +#define MAXTRIES 41 /* Maximum dnsq tries before giving up */ +#define CACHE_PERCEPTUAL /* Cache the perceptual lookup function */ +#define USE_DISJOINT_SETMASKS /* Reduce INDEP_SURFACE setmask size */ + +/* Sanity checks (slow) */ +#undef SANITY_CHECK_SEED /* Sanity check the selection of the bigest eperr seed vertex */ +#undef SANITY_CHECK_HIT /* Sanity check the hit detection */ +#undef SANITY_CHECK_HIT_FATAL /* throw fatal error in sanity check */ +#undef SANITY_CHECK_FIXUP /* Check that fixup was sucessful */ +#undef SANITY_CHECK_FIXUP_FATAL /* throw fatal if it wasn't */ +#undef SANITY_CHECK_CLOSEST /* Check that ofps_findclosest_xx() returns correct result */ +#undef SANITY_CHECK_CLOSEST_FATAL /* throw fatal error on sanity fail */ +#undef SANITY_CHECK_CONSISTENCY /* Check internal consistency at each itteration */ +#undef SANITY_CHECK_CONSISTENCY_FATAL /* Throw a fatal if it wasn't */ + +#undef SANITY_RESEED_AFTER_FIXUPS /* Re-create veronoi from scratch after incremental update. */ +#undef SANITY_CHECK_EXAUSTIVE_SEARCH_FOR_VERTEXES /* Do very, vert slow search for all vertexes */ + +#define ALWAYS +#undef NEVER + +#ifdef STATS +# include "conv.h" /* System dependent convenience functions */ +#endif + +#if defined(DUMP_EPERR) || defined(DUMP_FERR) +#include "tiffio.h" +struct _vopt_cx; +static void dump_dnsqe(ofps *s, char *fname, int *nix, struct _vopt_cx *cx); +#endif + +#if defined(DEBUG) || defined(DUMP_PLOT_SEED) || defined(DUMP_PLOT) +static void dump_image(ofps *s, int pcp, int dwt, int vtx, int dpla, int ferr, int noi); +#endif +#if defined(DEBUG) || defined (SANITY_CHECK_CONSISTENCY) +static void sanity_check(ofps *s, int check_nodelists); +#endif +#if defined(DEBUG) || defined(DUMP_STRUCTURE) +static void dump_node_vtxs(ofps *s, int check_nodelists); +//static void dump_node_vtxs2(ofps *s, char *cc); +#endif + +static void ofps_binit(ofps *s); +static void ofps_stats(ofps *s); +static int ofps_point2cell(ofps *s, double *v, double *p); +static void ofps_gridcoords(ofps *s, int *c, double *v, double *p); +static void ofps_add_nacc(ofps *s, node *n); +static void ofps_rem_nacc(ofps *s, node *n); +static void ofps_add_vacc(ofps *s, vtx *vx); +static void ofps_rem_vacc(ofps *s, vtx *vx); +static void ofps_add_vseed(ofps *s, vtx *vx); +static void ofps_rem_vseed(ofps *s, vtx *vx); +static void ofps_re_create_node_node_vtx_lists(ofps *s); +static void do_batch_update1(ofps *s, int fixup); +static void do_batch_update2(ofps *s, int fixup); + +static node *ofps_findclosest_node(ofps *s, double *ceperr, vtx *vx); +//static vtx *ofps_findclosest_vtx(ofps *s, double *ceperr, node *nn); +static int ofps_findhit_vtxs(ofps *s, node *nn); + +static char *pco(int di, int *co); +static char *ppos(int di, double *p); +static char *pcomb(int di, int *n); +static char *peperr(double eperr); +static char *psm(ofps *s, setmask *sm); + +/* Check the incremental vertexes against the re-seeded vertexes */ +static void save_ivertexes(ofps *s); +static int check_vertexes(ofps *s); + +/* Check that no node is closer to a vertex than its parent */ +static int check_vertex_closest_node(ofps *s); + +/* Do an exaustive check for missing vertexes */ +static void check_for_missing_vertexes(ofps *s); + +/* --------------------------------------------------- */ +/* Setmask manipulation functions */ + +#ifdef USE_DISJOINT_SETMASKS + /* We assume the number of words is <= 1, */ + /* and we can use macros */ + +/* Signal this is a single word mask by using -ve no. of bits */ +#define sm_init(s, nbits) _sm_init(s, -(nbits)) + +#define sm_cp(s, sm_B, sm_A) \ + ((sm_B)->m[0] = (sm_A)->m[0]) + +#define sm_or(s, sm_C, sm_A, sm_B) \ + ((sm_C)->m[0] = (sm_A)->m[0] | (sm_B)->m[0]) + +#define sm_orand(s, sm_D, sm_A, sm_B, sm_C) \ + ((sm_D)->m[0] = (sm_A)->m[0] | ((sm_B)->m[0] & (sm_C)->m[0])) + +#define sm_and(s, sm_C, sm_A, sm_B) \ + ((sm_C)->m[0] = (sm_A)->m[0] & (sm_B)->m[0]) + +#define sm_andnot(s, sm_C, sm_A, sm_B) \ + ((sm_C)->m[0] = (sm_A)->m[0] & (s->lwmask ^ (sm_B)->m[0])) + +#define sm_andand(s, sm_D, sm_A, sm_B, sm_C) \ + ((sm_D)->m[0] = (sm_A)->m[0] & (sm_B)->m[0] & (sm_C)->m[0]) + +#define sm_test(s, sm_A) \ + ((sm_A)->m[0] & s->lwmask) + +#define sm_equal(s, sm_A, sm_B) \ + (((sm_A)->m[0] & s->lwmask) == ((sm_B)->m[0] & s->lwmask)) + +#define sm_andtest(s, sm_A, sm_B) \ + ((sm_A)->m[0] & (sm_B)->m[0]) + +#define sm_andnottest(s, sm_A, sm_B) \ + ((sm_A)->m[0] & (s->lwmask ^ (sm_B)->m[0])) + +#define sm_vtx_vtx(s, v1, v2) \ + ((v1)->vm.m[0] & (v2)->vm.m[0] & s->sc[(v1)->cmask & (v2)->cmask].a_sm.m[0]) + +#define sm_vtx_node(s, vx, nn) \ + ((vx)->vm.m[0] & s->sc[(nn)->pmask].a_sm.m[0] & s->sc[(vx)->cmask & (nn)->pmask].a_sm.m[0]) + +#else + +#define sm_init(s, nbits) _sm_init(s, nbits) +#define sm_cp(s, sm_B, sm_A) _sm_cp(s, sm_B, sm_A) +#define sm_or(s, sm_C, sm_A, sm_B) _sm_or(s, sm_C, sm_A, sm_B) +#define sm_orand(s, sm_D, sm_A, sm_B, sm_C) _sm_orand(s, sm_D, sm_A, sm_B, sm_C) +#define sm_and(s, sm_C, sm_A, sm_B) _sm_and(s, sm_C, sm_A, sm_B) +#define sm_andnot(s, sm_C, sm_A, sm_B) _sm_andnot(s, sm_C, sm_A, sm_B) +#define sm_andand(s, sm_D, sm_A, sm_B, sm_C) _sm_andand(s, sm_D, sm_A, sm_B, sm_C) +#define sm_test(s, sm_A) _sm_test(s, sm_A) +#define sm_equal(s, sm_A, sm_B) _sm_equal(s, sm_A, sm_B) +#define sm_andtest(s, sm_A, sm_B) _sm_andtest(s, sm_A, sm_B) +#define sm_andnottest(s, sm_A, sm_B) _sm_andnottest(s, sm_A, sm_B) +#define sm_vtx_node(s, vx, nn) _sm_vtx_node(s, vx, nn) +#define sm_vtx_vtx(s, v1, v2) _sm_vtx_vtx(s, v1, v2) + +#endif + +/* Compute set mask parameters */ +static void _sm_init(ofps *s, int nbits) { + +//printf("~1 _sm_init with %d bits\n",nbits); + s->bpsmw = sizeof(unsigned int) * 8; + + if (nbits < 0) { /* Macro initialisation */ +#ifdef DEBUG + printf("Disjoint sets being used\n"); +#endif + nbits = -nbits; + if (nbits > s->bpsmw) + error("Attempt to use macro setmasks when nbits %d > a words bits %d",nbits,s->bpsmw); + } + + s->smbits = nbits; + s->nsmw = (s->smbits + s->bpsmw - 1)/s->bpsmw; + s->lwmask = ~0; + s->lwmask >>= s->nsmw * s->bpsmw - s->smbits; /* Number of unused bits */ + if (s->nsmw > MXSMASKW) + error("Not enough words for %d setmask bits, got %d need %d\n",s->smbits,MXSMASKW,s->nsmw); +} + +/* Copy a setmask */ +static void _sm_cp(ofps *s, setmask *sm_B, setmask *sm_A) { + int i; + + for (i = 0; i < s->nsmw; i++) + sm_B->m[i] = sm_A->m[i]; +} + +/* Set the whole mask to zero or one */ +static void sm_set(ofps *s, setmask *sm, int val) { + int i; + unsigned int vv = 0; + + if (val & 1) + vv = ~0; + for (i = 0; i < s->nsmw; i++) + sm->m[i] = vv; + sm->m[i-1] &= s->lwmask; +} + +/* Set the given bit to zero or one */ +static void sm_setbit(ofps *s, setmask *sm, int bit, int val) { + int i; + unsigned int vv = 0; + + if (bit > s->smbits) + error("assert, trying to set bit %d outside setmask size %d",bit,s->smbits); + i = bit / s->bpsmw; + vv = 1 << bit % s->bpsmw; + if (val & 1) + sm->m[i] |= vv; + else + sm->m[i] &= ~vv; +} + +/* C = A | B */ +static void _sm_or(ofps *s, setmask *sm_C, setmask *sm_A, setmask *sm_B) { + int i; + + for (i = 0; i < s->nsmw; i++) + sm_C->m[i] = sm_A->m[i] | sm_B->m[i]; +} + +/* D = A | (B & C) */ +static void _sm_orand(ofps *s, setmask *sm_D, setmask *sm_A, setmask *sm_B, setmask *sm_C) { + int i; + + for (i = 0; i < s->nsmw; i++) + sm_D->m[i] = sm_A->m[i] | (sm_B->m[i] & sm_C->m[i]); +} + +/* C = A & B */ +/* Return zero if result is zero */ +static unsigned int _sm_and(ofps *s, setmask *sm_C, setmask *sm_A, setmask *sm_B) { + unsigned int vv = 0; + int i; + + for (i = 0; i < s->nsmw; i++) + vv |= sm_C->m[i] = sm_A->m[i] & sm_B->m[i]; + return vv; +} + +/* C = A & ~B */ +/* Return zero if result is zero */ +static unsigned int _sm_andnot(ofps *s, setmask *sm_C, setmask *sm_A, setmask *sm_B) { + unsigned int vv = 0; + int i; + + for (i = 0; i < s->nsmw; i++) { + if (i < (s->nsmw-1)) + vv |= sm_C->m[i] = sm_A->m[i] & ~sm_B->m[i]; + else + vv |= sm_C->m[i] = sm_A->m[i] & (s->lwmask ^ sm_B->m[i]); + } + return vv; +} + +/* D = A & B & C */ +/* Return zero if result is zero */ +static unsigned int _sm_andand(ofps *s, setmask *sm_D, setmask *sm_A, setmask *sm_B, setmask *sm_C) { + unsigned int vv = 0; + int i; + + for (i = 0; i < s->nsmw; i++) + vv |= sm_D->m[i] = sm_A->m[i] & sm_B->m[i] & sm_C->m[i]; + return vv; +} + +/* Return zero if result is zero */ +static unsigned int _sm_test(ofps *s, setmask *sm_A) { + unsigned int vv = 0; + int i; + + for (i = 0; i < s->nsmw; i++) { + if (i < (s->nsmw-1)) + vv |= sm_A->m[i]; + else + vv |= sm_A->m[i] & s->lwmask; + } + return vv; +} + +/* Return nz if the two are equal */ +static unsigned int _sm_equal(ofps *s, setmask *sm_A, setmask *sm_B) { + int i; + + for (i = 0; i < s->nsmw; i++) { + if (i < (s->nsmw-1)) { + if (sm_A->m[i] != sm_B->m[i]) + return 0; + } else { + if ((sm_A->m[i] & s->lwmask) != (sm_B->m[i] & s->lwmask)) + return 0; + } + } + return 1; +} + +/* A & B and return zero if the result was zero. */ +static unsigned int _sm_andtest(ofps *s, setmask *sm_A, setmask *sm_B) { + unsigned int vv = 0; + int i; + + for (i = 0; i < s->nsmw; i++) + vv |= sm_A->m[i] & sm_B->m[i]; + + return vv; +} + +/* A & ~B and return zero if result is zero */ +static unsigned int _sm_andnottest(ofps *s, setmask *sm_A, setmask *sm_B) { + unsigned int vv = 0; + int i; + + for (i = 0; i < s->nsmw; i++) { + if (i < (s->nsmw-1)) + vv |= sm_A->m[i] & ~sm_B->m[i]; + else + vv |= sm_A->m[i] & (s->lwmask ^ sm_B->m[i]); + } + return vv; +} + +/* Test if two vertexes interact */ +/* return nz if they do */ +static unsigned int _sm_vtx_vtx(ofps *s, vtx *v1, vtx *v2) { + unsigned int vv = 0; + int i; + +#ifdef USE_DISJOINT_SETMASKS + /* Because the mask bits are re-used across disjoint sets, */ + /* we have to discount any intersection that occurs where */ + /* the two items are disjoint, with the exception of the full-d set. */ + for (i = 0; i < s->nsmw; i++) + vv |= v1->vm.m[i] & v2->vm.m[i] & s->sc[v1->cmask & v2->cmask].a_sm.m[i]; +#else + for (i = 0; i < s->nsmw; i++) + vv |= v1->vm.m[i] & v2->vm.m[i]; +# endif + return vv; +} + +/* Test if a vertex and node interact */ +static unsigned int _sm_vtx_node(ofps *s, vtx *vx, node *nn) { + unsigned int vv = 0; + int i; + + /* Because the mask bits are re-used across disjoint sets, */ + /* we have to discount any intersection that occurs where */ + /* the two items are disjoint, with the exception of the full-d set. */ +#ifdef USE_DISJOINT_SETMASKS + for (i = 0; i < s->nsmw; i++) + vv |= vx->vm.m[i] & s->sc[nn->pmask].a_sm.m[i] & s->sc[vx->cmask & nn->pmask].a_sm.m[i]; +#else + for (i = 0; i < s->nsmw; i++) + vv |= vx->vm.m[i] & s->sc[nn->pmask].a_sm.m[i]; +# endif + return vv; +} + +/* Utility - return a string containing the mask in hex */ +static char *psm(ofps *s, setmask *sm) { + static char buf[5][200]; + static int ix = 0; + int e, f; + char *bp; + + if (++ix >= 5) + ix = 0; + bp = buf[ix]; + + sprintf(bp, "0x"); bp += strlen(bp); + for (f = 0, e = s->nsmw-1; e >= 0; e--) { + if (f || e == 0 || sm->m[e] != 0) { + if (f) { + sprintf(bp, "%08x", sm->m[e]); bp += strlen(bp); + } else { + sprintf(bp, "%x", sm->m[e]); bp += strlen(bp); + f = 1; + } + } + } + return buf[ix]; +} + +/* --------------------------------------------------- */ +/* Swap the location of a node in s->n[]. This is assumed to */ +/* be done _before_ a node is added to the veroni */ +static void swap_nodes(ofps *s, int i, int j) { + node *n; + int xx; + + n = s->n[i]; + s->n[i] = s->n[j]; + s->n[j] = n; + + /* fix index number */ + xx = s->n[i]->ix; + s->n[i]->ix = s->n[j]->ix; + s->n[j]->ix = xx; + + xx = s->n[i]->ixm; + s->n[i]->ixm = s->n[j]->ixm; + s->n[j]->ixm = xx; +} + +/* Shuffle all the nodes in the list along to put */ +/* the given node at the start. */ +static void move_node_to_front(ofps *s, int i) { + node *n; + int j; + + n = s->n[i]; + + for (j = 1; j <= i; j++) + s->n[j] = s->n[j-1]; + + s->n[0] = n; + + /* Fix ->ix and ixm */ + for (j = 0; j <= i; j++) { + int bitp; + s->n[j]->ix = i; + + bitp = 31 & (j + (j >> 4) + (j >> 8) + (j >> 12)); + s->n[j]->ixm = (1 << bitp); + } +} + +/* Randomly shuffle all the nodes */ +static void shuffle_node_order(ofps *s) { + int i; + + for (i = 0; i < s->tinp; i++) { + swap_nodes(s, i, i_rand(0, s->tinp-1)); + } +} + +/* Reverse the nodes order */ +static void reverse_node_order(ofps *s) { + int i, j; + + for (i = 0, j = s->tinp-1; i < j; i++, j--) { + swap_nodes(s, i, j); + } +} + +/* --------------------------------------------------- */ +/* Default convert the nodes device coordinates into approximate perceptual coordinates */ +/* (usually overriden by caller supplied function) */ +static void +default_ofps_to_percept(void *od, double *p, double *d) { + ofps *s = (ofps *)od; + int e; + + /* Default Do nothing - copy device to perceptual. */ + for (e = 0; e < s->di; e++) { + double tt = d[e]; + p[e] = tt * 100.0; + } +} + +/* Cached perceptual lookup */ +static void +ofps_cache_percept(void *od, double *p, double *d) { + int e; + co tp; + rspl *pc = (rspl *)od; + + for (e = 0; e < pc->di; e++) + tp.p[e] = d[e]; + pc->interp(pc, &tp); + for (e = 0; e < pc->fdi; e++) + p[e] = tp.v[e]; +} + +/* Return the distance of the device value from the device gamut */ +/* This will be -ve if the point is outside */ +/* If bvp is non-null, the index of the closest dim times 2 */ +/* will be returned for the 0.0 boundary, dim * 2 + 1 for the 1.0 */ +/* boundary, and di * 2 for the ink limit boundary. */ +static double +ofps_in_dev_gamut(ofps *s, double *d, int *bvp) { + int e, di = s->di; + double tt; + double dd = 100.0; /* Worst distance outside */ + double ss = 0.0; /* Sum of values */ + int bv = di; + for (e = 0; e < di; e++) { + tt = d[e] - s->imin[e]; + if (tt < dd) { + dd = tt; + bv = e * 2; + } + tt = s->imax[e] - d[e]; + if (tt < dd) { + dd = tt; + bv = e * 2 + 1; + } + ss += d[e]; /* Track sum */ + } + ss = (s->ilimit - ss)/di; /* Axis aligned distance to ink limit */ + tt = sqrt((double)di) * ss; /* Diagonal distance to ink limit */ + if (tt < dd) { + dd = tt; + bv = di * 2; + } + if (bvp != NULL) + *bvp = bv; + return dd; +} + +#ifdef NEVER /* Allow performance trace on ofps_clip_point usage */ +static int ofps_clip_point(ofps *s, double *cd, double *d); + +static int ofps_clip_point1(ofps *s, double *cd, double *d) { + return ofps_clip_point(s, cd, d); } +static int ofps_clip_point2(ofps *s, double *cd, double *d) { + return ofps_clip_point(s, cd, d); } +static int ofps_clip_point3(ofps *s, double *cd, double *d) { + return ofps_clip_point(s, cd, d); } +static int ofps_clip_point4(ofps *s, double *cd, double *d) { + return ofps_clip_point(s, cd, d); } +static int ofps_clip_point5(ofps *s, double *cd, double *d) { + return ofps_clip_point(s, cd, d); } +static int ofps_clip_point6(ofps *s, double *cd, double *d) { + return ofps_clip_point(s, cd, d); } +static int ofps_clip_point7(ofps *s, double *cd, double *d) { + return ofps_clip_point(s, cd, d); } +static int ofps_clip_point8(ofps *s, double *cd, double *d) { + return ofps_clip_point(s, cd, d); } +static int ofps_clip_point9(ofps *s, double *cd, double *d) { + return ofps_clip_point(s, cd, d); } +static int ofps_clip_point10(ofps *s, double *cd, double *d) { + return ofps_clip_point(s, cd, d); } + +#else /* Production code */ +#define ofps_clip_point1 ofps_clip_point +#define ofps_clip_point2 ofps_clip_point +#define ofps_clip_point3 ofps_clip_point +#define ofps_clip_point4 ofps_clip_point +#define ofps_clip_point5 ofps_clip_point +#define ofps_clip_point6 ofps_clip_point +#define ofps_clip_point7 ofps_clip_point +#define ofps_clip_point8 ofps_clip_point +#define ofps_clip_point9 ofps_clip_point +#define ofps_clip_point10 ofps_clip_point +#endif + +/* Given the new intended device coordinates, */ +/* clip the new position to the device gamut edge */ +/* return non-zero if the point was clipped */ +static int +ofps_clip_point(ofps *s, double *cd, double *d) { + int di = s->di; + double ss = 0.0; + int rv = 0; + +#define STEP(IX) \ + cd[IX] = d[IX]; \ + if (cd[IX] < s->imin[IX]) { \ + cd[IX] = s->imin[IX]; \ + if (cd[IX] < (s->imin[IX] - ILIMITEPS)) \ + rv |= 1; \ + } else if (cd[IX] > s->imax[IX]) { \ + cd[IX] = s->imax[IX]; \ + if (cd[IX] > (s->imax[IX] + ILIMITEPS)) \ + rv |= 1; \ + } \ + ss += cd[IX]; + + switch (di) { + case 4: + STEP(3) + case 3: + STEP(2) + case 2: + STEP(1) + case 1: + STEP(0) + } +#undef STEP + if (ss > s->ilimit) { + if (ss > (s->ilimit + ILIMITEPS)) + rv |= 1; + ss = (ss - s->ilimit)/s->di; + switch (di) { + case 4: + cd[3] -= ss; + case 3: + cd[2] -= ss; + case 2: + cd[1] -= ss; + case 1: + cd[0] -= ss; + } + } + return rv; +} + +/* Given the new intended device coordinates, */ +/* return non-zero if the point would be clipped. */ +static int +ofps_would_clip_point(ofps *s, double *d) { + int e; + double ss; + for (ss = 0.0, e = 0; e < s->di; e++) { + if (d[e] < (s->imin[e] - ILIMITEPS)) + return 1; + else if (d[e] > (s->imax[e] + ILIMITEPS)) + return 1; + ss += d[e]; + } + if (ss > (s->ilimit + ILIMITEPS)) + return 1; + return 0; +} + +/* Return a out of gamut value. */ +/* 0.0 is returned if the posistion is in gamut */ +static double ofps_oog(ofps *s, double *p) { + int e, di = s->di; + double ss, oog = 0.0; + + for (ss = 0.0, e = 0; e < di; e++) { + if (p[e] < (s->imin[e])) { + double tt = s->imin[e] - p[e]; + if (tt > oog) oog = tt; + } else if (p[e] > (s->imax[e])) { + double tt = p[e] - s->imax[e]; + if (tt > oog) oog = tt; + } + ss += p[e]; + } + if (ss > s->ilimit) { + double tt; + ss = (ss - s->ilimit)/di; /* Axis aligned distance to ink limit */ + tt = sqrt((double)di) * ss; /* Diagonal distance to ink limit */ + if (tt > oog) + oog = tt; + } + return oog; +} + +/* Unbounded perceptual lookup. */ +/* return nz if it was actually clipped and extended */ +static int ofps_cc_percept(ofps *s, double *v, double *p) { + co cp; + int clip; + + clip = ofps_clip_point(s, cp.p, p); + + if (s->pcache) { /* In line this for speed */ + int e, di = s->di; + + s->pcache->interp(s->pcache, &cp); + for (e = 0; e < di; e++) + v[e] = cp.v[e]; + + } else { + s->percept(s->od, v, cp.p); + } + + /* Extend perceptual value using matrix model */ + if (clip) { + int e, di = s->di; + double mcv[MXPD], zv[MXPD]; + +#ifdef DEBUG + if (s->pmod_init == 0) + error("ofps_cc_percept() called before pmod has been inited"); +#endif + /* Lookup matrix mode of perceptual at clipped device */ + icxCubeInterp(s->pmod, di, di, mcv, cp.p); + + /* Compute a correction factor to add to the matrix model to */ + /* give the actual perceptual value at the clipped location */ + for (e = 0; e < di; e++) + zv[e] = v[e] - mcv[e]; + + /* Compute the unclipped matrix model perceptual value */ + icxCubeInterp(s->pmod, di, di, v, p); + + /* Add the correction value to it */ + for (e = 0; e < di; e++) + v[e] += zv[e]; + } + return clip; +} + +/* --------------------------------------------------- */ +/* Vertex alloc/free support */ + +/* Check if a vertex is in the cache index, */ +/* and return it if it is. Return NULL otherwise */ +static vtx *vtx_cache_get(ofps *s, int *nix) { + int e, di = s->di; + unsigned int hash; + vtx *vx; + + hash = (unsigned int)nix[MXPD+1]; /* We assume it was put there by sort */ + + for (vx = s->vch[hash]; vx != NULL; vx = vx->chn) { + for (e = 0; e <= di; e++) { /* See if it is a match */ + if (nix[e] != vx->nix[e]) + break; + } + if (e > di) { /* It is */ + return vx; + } + } + return vx; +} + +/* Add a vertex to the cache index */ +static void vtx_cache_add(ofps *s, vtx *vv) { + int e, di = s->di; + unsigned int hash; + + hash = (unsigned int)vv->nix[MXPD+1]; + + /* Add it to the list */ + vv->chn = s->vch[hash]; + if (s->vch[hash] != NULL) + s->vch[hash]->pchn = &vv->chn; + s->vch[hash] = vv; + vv->pchn = &s->vch[hash]; +} + +/* Remove a vertex from the cache index */ +static void vtx_cache_rem(ofps *s, vtx *vv) { + int e, di = s->di; + unsigned int hash; + vtx *vx; + + hash = (unsigned int)vv->nix[MXPD+1]; + + for (vx = s->vch[hash]; vx != NULL; vx = vx->chn) { + if (vx == vv) { + if (vx->pchn != NULL) { + *vx->pchn = vx->chn; + if (vx->chn != NULL) + vx->chn->pchn = vx->pchn; + } + return; + } + } + /* Hmm. not in cache */ +} + +/* Each vtx returned gets a unique serial number */ +static vtx *new_vtx(ofps *s) { + vtx *vv; + + if (s->fvtx != NULL) { /* re-use one we've got */ + vv = s->fvtx; + s->fvtx = vv->link; + memset((void *)vv, 0, sizeof(vtx)); + + } else { + if ((vv = (vtx *)calloc(sizeof(vtx), 1)) == NULL) + error("ofps: malloc failed on new vertex"); + } + + /* Link vertex to currently used list */ + vv->link = s->uvtx; + if (s->uvtx != NULL) + s->uvtx->plp = &vv->link; + s->uvtx = vv; + vv->plp = &s->uvtx; + vv->no = s->nxvno++; + s->nv++; + + s->nvtxcreated++; + + vv->fuptol = NUMTOL; + + return vv; +} + +/* Remove a vertx from the used list, and put it on the hidden list. */ +/* (Used for making inside and outside vertexes unavailabe) */ +static void remu_vtx(ofps *s, vtx *v) { +//printf("~1 remu_vtx called on no %d\n",v->no); + + /* Remove it from the used list */ + if (v->plp != NULL) { /* If is on used list, remove it */ + *v->plp = v->link; + if (v->link != NULL) + v->link->plp = v->plp; + } + v->plp = NULL; + v->link = NULL; + + /* Add it to the hidden list */ + v->link = s->hvtx; + if (s->hvtx != NULL) + s->hvtx->plp = &v->link; + s->hvtx = v; + v->plp = &s->hvtx; + + s->nv--; /* Don't count hidden verts */ +} + +/* Remove a vertex from the cache and spatial accelleration grid, */ +/* and and the used list. */ +static void del_vtx1(ofps *s, vtx *vx) { + node *nn, *nnn; + + if (vx->plp != NULL) { /* If is on used list, remove it */ + *vx->plp = vx->link; + if (vx->link != NULL) + vx->link->plp = vx->plp; + s->nv--; + } + + if (vx->pfchl != NULL) { /* If is on fixup check list, remove it */ + *vx->pfchl = vx->fchl; + if (vx->fchl != NULL) + vx->fchl->pfchl = vx->pfchl; + } + vx->pfchl = NULL; + vx->fchl = NULL; + + if (vx->ofake == 0) { + + /* Remove it from cache */ + vtx_cache_rem(s, vx); + + /* Remove it from spatial accelleration grid */ + ofps_rem_vacc(s, vx); + + /* Remove it from seeding group */ + ofps_rem_vseed(s, vx); + } + + /* Remove vertex from the s->svtxs[] list */ + /* so that it doesn't get used in fixups. */ + if (vx->psvtxs != NULL) { + *vx->psvtxs = NULL; + vx->psvtxs = NULL; + } +} + +/* Delete a vertex by removing it from the cache and spatial accelleration grid, */ +/* and then moving it to the free list */ +/* (It's assumed that it's been removed from all other */ +/* structures by the caller) */ +static void del_vtx(ofps *s, vtx *vx) { + +//printf("~1 del_vtx called on no %d\n",vx->no); + + /* Remove it from various lists */ + del_vtx1(s, vx); + + /* Free vertex net neighbours list */ + if (vx->nv != NULL) { +/* !!!! if we need this, we're referencing deleted vertexes !!!! */ +/* vx->nnv = vx->_nnv = 0; */ + free(vx->nv); +/* vx->nv = NULL; */ + } + + /* Add to free list */ + vx->link = s->fvtx; + vx->plp = NULL; + s->fvtx = vx; + + s->nvtxdeleted++; +} + +/* Add a vertex to a vertex's net */ +static void vtx_add_vertex(ofps *s, vtx *vv, vtx *vx) { + int i; + +//printf("~1 Adding vertex no %d comb %s vm %s to vtx %d\n",vx->no,pcomb(s->di,vx->nix),psm(s,&vx->vm),vv->no); +//printf("~1 Adding vertex no %d to vtx no %d\n",vx->no,vv->no); +//printf("~1 Before add, no %d list is :",vv->no); for (i = 0; i < vv->nnv; i++) printf(" %d",vv->nv[i]->no); printf("\n"); + if (vv->_nnv == 0) { + vv->_nnv = 4; /* Initial allocation */ + if ((vv->nv = (vtx **)malloc(sizeof(vtx *) * vv->_nnv)) == NULL) + error("ofps: malloc failed on node vertex pointers"); + } else if (vv->nnv >= vv->_nnv) { + vv->_nnv *= 2; /* Double allocation */ + if ((vv->nv = (vtx **)realloc(vv->nv, sizeof(vtx *) * vv->_nnv)) == NULL) + error("ofps: realloc failed on node vertex pointers"); + } +#ifdef DEBUG +{ + int i; + + /* Check that we're not adding ourself */ + if (vx == vv) { + printf("Adding vtx no %d comb %s to itself!\n",vx->no,pcomb(s->di,vx->nix)); fflush(stdout); + error("Adding vtx no %d comb %s to itself!\n",vx->no,pcomb(s->di,vx->nix)); + } + + /* Check that the vertex is not already here */ + for (i = 0; i < vv->nnv; i++) { + if (vx == vv->nv[i]) { + printf("Adding vtx no %d comb %s to vtx %d when already there!\n",vx->no,pcomb(s->di,vx->nix),vv->no); fflush(stdout); + fprintf(stderr,"Adding vtx no %d comb %s to vtx %d when already there!\n",vx->no,pcomb(s->di,vx->nix),vv->no); fflush(stdout); +//*((char *)0) = 55; + return; + } + } +} +#endif /* DEBUG */ + + vv->nv[vv->nnv++] = vx; + +//printf("~1 After add, no %d list is :",vv->no); for (i = 0; i < vv->nnv; i++) printf(" %d",vv->nv[i]->no); printf("\n"); +} + +/* Delete a vertex to a vertex's net */ +static void vtx_rem_vertex(ofps *s, vtx *vv, vtx *vx) { + int i, j; + +//printf("~1 Removing vertex no %d comb %s vm %s from vtx %d\n",vx->no,pcomb(s->di,vx->nix),psm(s,&vx->vm),vv->no); +//printf("~1 Before delete, no %d list is :",vv->no); for (i = 0; i < vv->nnv; i++) printf(" %d",vv->nv[i]->no); printf("\n"); + + for (i = j = 0; i < vv->nnv; i++) { + if (vv->nv[i] != vx) { + vv->nv[j] = vv->nv[i]; + j++; + } + } + vv->nnv = j; +//printf("~1 After delete, no %d list is :",vv->no); for (i = 0; i < vv->nnv; i++) printf(" %d",vv->nv[i]->no); printf("\n"); +} + +/* Given two vertexes, check if they are neighbours, and if they are, */ +/* add them to each others neighbourhood. */ +/* If fixup is set, first check that they arn't already neighbours */ +/* Return nz if ther were added to each other */ +static int vtx_cnd_biadd_vtx(ofps *s, vtx *vx1, vtx *vx2, int fixup) { + int f, ff, e, di = s->di; + int aa, bb, cc; /* Probable hit check */ + int nnm, nmix; + + if (vx1 == vx2) + return 0; + +#ifdef NEVER /* vertex net needs all neighbours ? */ +#ifdef INDEP_SURFACE + /* Can only have a net between them if they are visible to each other */ + if (sm_vtx_vtx(s, vx1, vx2) == 0) + return 0; +#endif +#endif + + /* Use the nixm to quickly check if all but one parent node matches */ + aa = vx1->nix[MXPD+2]; /* nixm */ + bb = vx2->nix[MXPD+2]; /* nixm */ + if ((aa & bb) == 0 || (cc = aa & ~bb, (cc & (cc-1)) != 0)) { + return 0; /* It's certainly not */ + } + + /* Do an exact check of all except one node match */ + for (nnm = ff = e = 0; e <= di; e++) { + for (f = ff; f <= di; f++) { + if (vx1->nix[e] == vx2->nix[f]) { + ff = f; /* Start from here next time */ + break; + } + if (vx1->nix[e] > vx2->nix[f]) /* No point in looking further */ + f = di; + } + if (f > di) { /* Didn't match */ + if (++nnm > 1) + break; + nmix = e; + } + } + if (e <= di) { + return 0; /* No match */ + } + + if (nnm == 0) { + fflush(stdout); + error("ofps: two vertexes have the same nodes !\n" + "no %d at %s nix %s\nno %d at %s nix %s", + vx1->no,ppos(di,vx1->p),pcomb(di,vx1->nix), + vx2->no,ppos(di,vx2->p),pcomb(di,vx2->nix)); + } + + /* If fixup or not INDEP_SURFACE, check that the vertex */ + /* is not already here */ +#ifndef INDEP_SURFACE + if (fixup) +#endif + { + int i; + vtx *va = vx1, *vb = vx2; + + if (vx1->nnv > vx2->nnv) { /* Search the shortest list */ + va = vx2; + vb = vx1; + } + for (i = 0; i < va->nnv; i++) { + if (vb == va->nv[i]) { + return 0; + } + } + } + +//printf("~1 Adding net between vtx no %d and no %d\n",vx1->no,vx2->no); + /* vx2 is a neighbour, so add it to the vtx net */ + vtx_add_vertex(s, vx1, vx2); + + /* The reverse must apply too */ + vtx_add_vertex(s, vx2, vx1); + + return 1; +} + +#ifdef NEVER /* Not used */ + +/* Clear any veroinoi content of a vertex, but not the vertex itself. */ +static void vtx_clear(ofps *s, vtx *v) { + + /* Clear the list of vertex net neighbours */ + v->nnv = 0; +} + +/* Free any allocated content of a vertex, but not the vertex itself. */ +static void vtx_free(ofps *s, vtx *v) { + +//printf("~1 freeing node ix %d and all contents\n",v->ix); + + /* Free up list of Vertex net neighbours */ + if (v->nv != NULL) { + v->nnv = v->_nnv = 0; + free(v->nv); + v->nv = NULL; + } +} +#endif /* NEVER */ + +/* vertex binary tree support */ + +static int vtx_aat_cmp_eperr(const void *p1, const void *p2) { + return ((vtx *)p1)->eperr == ((vtx *)p2)->eperr ? 0 : + (((vtx *)p1)->eperr < ((vtx *)p2)->eperr ? -1 : 1); +} + +static int vtx_aat_cmp_eserr(const void *p1, const void *p2) { + return ((vtx *)p1)->eserr == ((vtx *)p2)->eserr ? 0 : + (((vtx *)p1)->eserr < ((vtx *)p2)->eserr ? -1 : 1); +} + + +/* --------------------------------------------------- */ +/* Midpoint alloc/free support */ + +/* Each mid returned gets a unique serial number */ +/* and a refc or 0 */ +static mid *new_mid(ofps *s) { + mid *p; + + if (s->fmid != NULL) { /* re-use one we've got */ + p = s->fmid; + s->fmid = p->link; + memset((void *)p, 0, sizeof(mid)); + + } else { + if ((p = (mid *)calloc(sizeof(mid), 1)) == NULL) + error("ofps: malloc failed on new midpoint"); + } + + /* Link midpoint to currently used list */ + p->link = s->umid; + if (s->umid != NULL) + s->umid->plp = &p->link; + s->umid = p; + p->plp = &s->umid; + p->no = s->nxmno++; + + return p; +} + +/* Decrement reference count, and midpoint to the free list */ +static void del_mid(ofps *s, mid *p) { +//printf("~1 del_mid called on no %d, refc = %d\n",p->no,p->refc); + if (--p->refc <= 0) { + + if (p->plp != NULL) { /* If is on used list, remove it */ + *p->plp = p->link; + if (p->link != NULL) + p->link->plp = p->plp; + } + + p->link = s->fmid; /* Add to free list */ + p->plp = NULL; + s->fmid = p; + p->refc = 0; + } +} + +/* --------------------------------------------------- */ +/* Node basic support functions */ + +/* Clear any veroinoi content of a node, but not the node itself. */ +static void node_clear(ofps *s, node *p) { + + /* Clear the list of Voronoi verticies */ + p->nvv = 0; + + /* Clear any midpoints and nodes */ + while (p->nvn > 0) { + if (p->mm[--p->nvn] != NULL) + del_mid(s, p->mm[p->nvn]); + } +} + +/* Free any allocated content of a node, but not the node itself. */ +static void node_free(ofps *s, node *p) { + +//printf("~1 freeing node ix %d and all contents\n",p->ix); + + /* Free up list of Voronoi verticies */ + if (p->vv != NULL) { + free(p->vv); + p->vv = NULL; + p->nvv = p->_nvv = 0; + } + + /* Free up list of voronoi node indexes */ + if (p->vn != NULL) { + while (p->nvn > 0) { + if (p->mm[--p->nvn] != NULL) + del_mid(s, p->mm[p->nvn]); + } + p->nvn = p->_nvn = 0; + free(p->vn); + free(p->mm); + p->vn = NULL; + p->mm = NULL; + } + + p->nsp = 0; /* No list of surface planes */ +} + +/* Add a vertex to the node vertex list */ +static void node_add_vertex(ofps *s, node *pp, vtx *vx) { + +//printf("~1 Adding vertex no %d comb %s vm %s to node %d\n",vx->no,pcomb(s->di,vx->nix),psm(s,&vx->vm),pp->ix); +#ifdef DEBUG + if (vx->del) + warning("!!!!! adding vertex no %d with delete flag set!!!",vx->no); +#endif + + if (pp->_nvv == 0) { + pp->_nvv = 4; /* Initial allocation */ + if ((pp->vv = (vtx **)malloc(sizeof(vtx *) * pp->_nvv)) == NULL) + error("ofps: malloc failed on node vertex pointers"); + } else if (pp->nvv >= pp->_nvv) { + pp->_nvv *= 2; /* Double allocation */ + if ((pp->vv = (vtx **)realloc(pp->vv, sizeof(vtx *) * pp->_nvv)) == NULL) + error("ofps: realloc failed on node vertex pointers"); + } + +#ifdef DEBUG +{ + int i; + + /* Check that the vertex is not already here */ + for (i = 0; i < pp->nvv; i++) { + if (vx == pp->vv[i]) { + printf("Adding vtx no %d comb %s when already there!\n",vx->no,pcomb(s->di,vx->nix)); fflush(stdout); + error("Adding vtx no %d comb %s when already there!",vx->no,pcomb(s->di,vx->nix)); fflush(stdout); + } + } +} +#endif /* DEBUG */ + + pp->vv[pp->nvv++] = vx; + +#ifdef NEVER +#ifdef DEBUG + { + int e, di = s->di; + printf("~1 +++ Node ix %d add vtx no %d pos %s err %f @ %d:",pp->ix,vx->no,ppos(di,vx->p),vx->eperr,pp->nvv); + for (e = 0; e < pp->nvv; e++) + printf("%d ",pp->vv[e]->no); + printf("\n"); + } +#endif +#endif +} + +/* Remove a vertex from the node vertex list */ +static void node_rem_vertex(ofps *s, node *pp, vtx *vx) { + int i, j; + +//printf("~1 Removing vertex no %d comb %s vm %s from node ix %d\n",vx->no,pcomb(s->di,vx->nix),psm(s,&vx->vm),pp->ix); +//printf("~1 Before delete, no %d list is :",vv->no); for (i = 0; i < vv->nnv; i++) printf(" %d",vv->nv[i]->no); printf("\n"); + + for (i = j = 0; i < pp->nvv; i++) { + if (pp->vv[i] != vx) { + pp->vv[j] = pp->vv[i]; + j++; + } + } + pp->nvv = j; +} + +/* Add a node index to the node */ +static void node_add_nix(ofps *s, node *pp, int ix) { + + if (pp->_nvn == 0) { + pp->_nvn = 4; /* Initial allocation */ + if ((pp->vn = (int *)malloc(sizeof(int) * pp->_nvn)) == NULL) + error("ofps: malloc failed on node index list"); + if ((pp->mm = (mid **)malloc(sizeof(mid *) * pp->_nvn)) == NULL) + error("ofps: malloc failed on midpoint pointer list"); + } else if (pp->nvn >= pp->_nvn) { + pp->_nvn *= 2; /* Double allocation */ + if ((pp->vn = (int *)realloc(pp->vn, sizeof(int) * pp->_nvn)) == NULL) + error("ofps: realloc failed on node index list"); + if ((pp->mm = (mid **)realloc(pp->mm, sizeof(mid *) * pp->_nvn)) == NULL) + error("ofps: realloc failed on midpoint pointer list"); + } + pp->vn[pp->nvn] = ix; + pp->mm[pp->nvn++] = NULL; + +#ifdef NEVER +#ifdef DEBUG + { + int e, di = s->di; + printf("~1 +++ Node ix %d add node ix %d at %s @ %d: ",pp->ix,ix,ppos(di,pp->p),pp->nvn); + for (e = 0; e < pp->nvn; e++) + printf("%d ",pp->vn[e]); + printf("\n"); + } +#endif +#endif +} + +/* Recompute a nodes neighborhood nodes. */ +/* (Invalidates and deletes any midpoints) */ +static void node_recomp_nvn( + ofps *s, + node *pp +) { + int e, di = s->di; + int i, j, k; + +#ifdef DEBUG + printf("node_recomp_nvn for node ix %d\n",pp->ix); +#endif + /* Clear any midpoints and nodes */ + while (pp->nvn > 0) { + if (pp->mm[--pp->nvn] != NULL) + del_mid(s, pp->mm[pp->nvn]); + } + s->nvnflag++; /* Make sure each node is only added once */ + pp->nvnflag = s->nvnflag; /* Don't put self in list */ + + for (i = 0; i < pp->nvv; i++) { /* For each vertex */ + double rads; + vtx *vv = pp->vv[i]; + + for (j = 0; j <= di; j++) { /* For each node in vertex */ + int ix = vv->nix[j]; + node *ap = s->n[ix]; + + if (ap->nvnflag == s->nvnflag) + continue; /* Already done that node */ + + node_add_nix(s, pp, ix); + ap->nvnflag = s->nvnflag; /* Don't worry about it again */ + } + } +} + +/* Sort a vertex node index array of di+1 nodes, */ +/* and add a hash at MXDP+1, and nixm at MXDP+2 */ +/* This is to speed up searching for a match */ +/* Sort largest to smallest (so fake gamut nodes are last) */ +static void sort_nix(ofps *s, int *nix) { + int i, j, t; + int di = s->di; /* There are di+1 nodes */ + unsigned int hash = 0; + int nixm = 0; + + /* Do a really simple exchange sort */ + for (i = 0; i < di; i++) { + for (j = i+1; j <= di; j++) { + if (nix[i] < nix[j]) { + t = nix[j]; + nix[j] = nix[i]; + nix[i] = t; + } + } + } + + /* And then compute the hash and nixm */ + for (i = 0; i <= di; i++) { + int bitp, ix = nix[i]; + hash = hash * 17 + nix[i]; + bitp = 31 & (ix + (ix >> 4) + (ix >> 8) + (ix >> 12)); + nixm |= (1 << bitp); + } + hash %= VTXCHSIZE; + + nix[MXPD+1] = (int)hash; + nix[MXPD+2] = nixm; +} + +/* Check if the given locate is on the gamut boundary surface, */ +/* and return the corresponding plane mask */ +static unsigned int check_pos_gsurf(ofps *s, double *p) { + int i, e, di = s->di; + unsigned int pmask = 0; + + /* For all the gamut boundary planes */ + for (i = 0; i < s->nbp; i++) { + pleq *vp = &s->gpeqs[i]; + double v; + + for (v = vp->pe[di], e = 0; e < di; e++) /* Compute relation to plane equation */ + v += vp->pe[e] * p[e]; + /* See if this location close to, or outside boundary plane */ + if (v > -s->surftol) + pmask |= (1 << i); + } +#ifdef MAXINDEP_2D + if (s->sc[pmask].valid == 0) + pmask = 0; +#endif + return pmask; +} + +/* Check if the given node is on the gamut boundary surface, */ +/* and record the number of surfaces it is on. */ +/* Return nz if the node is on one or more gamut boundaries. */ +/* Set the state of the node clip flag too. */ +static int det_node_gsurf(ofps *s, node *n, double *p) { + int i, e, di = s->di; + double ss; + + n->nsp = 0; + n->pmask = 0; + + /* For all the gamut boundary planes */ + for (i = 0; i < s->nbp; i++) { + pleq *vp = &s->gpeqs[i]; + double v; + + for (v = vp->pe[di], e = 0; e < di; e++) /* Compute relation to plane equation */ + v += vp->pe[e] * p[e]; + + /* See if this location close to, or outside boundary plane */ + if (v > -s->surftol) { + /* Add pointer to plane it falls on */ + n->sp[n->nsp++] = vp; + n->pmask |= (1 << i); + if (n->nsp > MXPD+1) + error("Assert in ofps det_node_gsurf : nsp %d > MXPD +1 %d",n->nsp, MXPD+1); + } + } + +#ifdef MAXINDEP_2D + if (s->sc[n->pmask].valid == 0) { + n->pmask = 0; + n->nsp = 0; + } +#endif +//printf("~1 node pmask = 0x%x\n",n->pmask); + + return (n->nsp > 0); +} + +/* Compute a cmask from an nix */ +static unsigned int comp_cmask(ofps *s, int *nix) { + unsigned int smask = 0, cmask = ~0; + int i, e, di = s->di; + + /* The composition mask indicates all the common surface planes */ + /* that a vertexes parent nodes lie on. Given this, one expects */ + /* the resulting location to be the same of within this. */ + for (e = 0; e <= di; e++) { + int ix = nix[e]; + if (ix < 0 && ix >= -s->nbp) { /* If fake surface node */ + smask |= 1 << -ix-1; + } else if (ix >= 0) { /* If real node */ + cmask &= s->n[ix]->pmask; + } + } + if (smask != 0) + cmask &= smask; + +#ifdef MAXINDEP_2D + if (s->sc[cmask].valid == 0) + cmask = 0; +#endif + + return cmask; +} + +/* Check if the given vertex is on the gamut boundary surface, */ +/* and record the number of surfaces it is on. */ +/* Also compute its cmask based on its parent nodes. */ +/* Set the state of the clip flag too. */ +static void det_vtx_gsurf(ofps *s, vtx *vx) { + int i, e, di = s->di; + unsigned int smask = 0; + + vx->nsp = 0; + + /* The composition mask indicates all the common surface planes */ + /* that a vertexes parent nodes lie on. Given this, one expects */ + /* the resulting location to be the same of within this. */ + vx->cmask = ~0; + for (e = 0; e <= di; e++) { + int ix = vx->nix[e]; + if (ix < 0 && ix >= -s->nbp) { /* If fake surface node */ + smask |= 1 << -ix-1; + } else if (ix >= 0) { + vx->cmask &= s->n[ix]->pmask; + } + } + if (smask != 0) + vx->cmask &= smask; + +#ifdef MAXINDEP_2D + if (s->sc[vx->cmask].valid == 0) + vx->cmask = 0; +#endif + + /* For all the gamut boundary planes */ + for (i = 0; i < s->nbp; i++) { + pleq *vp = &s->gpeqs[i]; + double v; + + for (v = vp->pe[di], e = 0; e < di; e++) /* Compute relation to plane equation */ + v += vp->pe[e] * vx->p[e]; + + /* See if this location close to, or outside boundary plane */ + if (v > -s->surftol) { + vx->sp[vx->nsp++] = vp; + vx->pmask |= (1 << i); + if (vx->nsp > di+1) + error("Assert in ofps det_vtx_gsurf : nsp %d > di+1 %d",vx->nsp, di+1); + } + } +#ifdef MAXINDEP_2D + if (s->sc[vx->pmask].valid == 0) { + vx->pmask = 0; + vx->nsp = 0; + } +#endif + +//printf("~1 vertex pmask = 0x%x, cmask = 0x%x\n",vx->pmask,vx->cmask); +} + +/* Given a device position and a list of surface planes, */ +/* move the position to lie on the closest location */ +/* on those planes. */ +static void confineto_gsurf(ofps *s, double *p, pleq **psp, int nsp) { + + if (nsp > 0) { /* It's a surface point, so keep it on the surface */ + int i, j, e, di = s->di; + double nn, np, q; + + /* Special case the common situation for speed. */ + if (nsp == 1) { + pleq *sp = psp[0]; + + /* Compute the dot product of the plane equation normal */ + for (nn = 0.0, e = 0; e < di; e++) + nn += sp->pe[e] * sp->pe[e]; + + /* Compute the dot product of the plane equation and the point */ + for (np = 0.0, e = 0; e < di; e++) + np += sp->pe[e] * p[e]; + + /* Compute the parameter */ + q = (sp->pe[di] + np)/nn; + + /* Compute the closest point */ + for (e = 0; e < di; e++) + p[e] -= q * sp->pe[e]; + + /* General case using matrix solution. */ + /* We compute the proportion of each plane normal vector to add to point */ + /* to map it onto all planes simultaniously (ie. to map the point to */ + /* the intersection of all the planes). */ + } else if (nsp > 1) { + double **ta, *TTA[MXPD + 1], TA[MXPD+1][MXPD + 1]; + double *tb, TB[MXPD + 1]; + + for (e = 0; e < nsp; e++) + TTA[e] = TA[e]; + ta = TTA; + tb = TB; + + /* For each combination of planes */ + for (i = 0; i < nsp; i++) { + pleq *spi = psp[i]; + for (j = i; j < nsp; j++) { + pleq *spj = psp[j]; + double vv; + + /* Compute dot product of the two normals */ + for (vv = 0.0, e = 0; e < di; e++) + vv += spi->pe[e] * spj->pe[e]; + ta[j][i] = ta[i][j] = vv; /* Use symetry too */ + } + + /* Compute right hand side */ + for (tb[i] = 0.0, e = 0; e < di; e++) + tb[i] += spi->pe[e] * p[e]; /* Dot prod of plane normal and point */ + tb[i] += spi->pe[di]; /* plus plane constant */ + } + /* Solve the simultaneous linear equations A.x = B */ + /* Return 1 if the matrix is singular, 0 if OK */ + if (solve_se(ta, tb, nsp) == 0) { + /* Compute the closest point */ + for (i = 0; i < nsp; i++) { + pleq *spi = psp[i]; + for (e = 0; e < di; e++) + p[e] -= tb[i] * spi->pe[e]; + } + } + } + /* The mapping may leave it out of gamut */ + ofps_clip_point2(s, p, p); + } +} + +/* Given a device position and a list of surface planes, */ +/* check that the point lies on all the planes. */ +/* Return NZ if it does, Z if it doesn't */ +static int checkon_gsurf(ofps *s, double *p, pleq **psp, int nsp) { + int i, e, di = s->di; + double nn, np, q; + + if (nsp == 0) + return 1; + + for (i = 0; i < nsp; i++) { + pleq *vp = psp[i]; + double v; + + for (v = vp->pe[di], e = 0; e < di; e++) /* Compute relation to plane equation */ + v += vp->pe[e] * p[e]; + + /* See if this location close to, or outside boundary plane */ + if (fabs(v) > s->surftol) { + return 0; + } + } + return 1; +} + +/* Compute the estimated positioning error given two locations. */ +/* [ This seems to be the critical inner loop in regard to */ +/* overall speed. The dominant callers are dnsq_solver() 10%, */ +/* followed by add_node2voronoi() 5%, others <= 1% ] */ + +#ifdef NEVER /* Allow performance trace on eperr usage */ +static double ofps_comp_eperr(ofps *s, double *pddist, double *v, double *p, double *nv, double *np); +static double ofps_comp_eperr1(ofps *s, double *pddist, double *v, double *p, double *nv, double *np) { + return ofps_comp_eperr(s, pddist, v, p, nv, np); } +static double ofps_comp_eperr2(ofps *s, double *pddist, double *v, double *p, double *nv, double *np) { + return ofps_comp_eperr(s, pddist, v, p, nv, np); } +static double ofps_comp_eperr3(ofps *s, double *pddist, double *v, double *p, double *nv, double *np) { + return ofps_comp_eperr(s, pddist, v, p, nv, np); } +static double ofps_comp_eperr4(ofps *s, double *pddist, double *v, double *p, double *nv, double *np) { + return ofps_comp_eperr(s, pddist, v, p, nv, np); } +static double ofps_comp_eperr5(ofps *s, double *pddist, double *v, double *p, double *nv, double *np) { + return ofps_comp_eperr(s, pddist, v, p, nv, np); } +static double ofps_comp_eperr6(ofps *s, double *pddist, double *v, double *p, double *nv, double *np) { + return ofps_comp_eperr(s, pddist, v, p, nv, np); } +static double ofps_comp_eperr7(ofps *s, double *pddist, double *v, double *p, double *nv, double *np) { + return ofps_comp_eperr(s, pddist, v, p, nv, np); } +static double ofps_comp_eperr8(ofps *s, double *pddist, double *v, double *p, double *nv, double *np) { + return ofps_comp_eperr(s, pddist, v, p, nv, np); } +static double ofps_comp_eperr9(ofps *s, double *pddist, double *v, double *p, double *nv, double *np) { + return ofps_comp_eperr(s, pddist, v, p, nv, np); } +#else /* Production code */ +#define ofps_comp_eperr1 ofps_comp_eperr +#define ofps_comp_eperr2 ofps_comp_eperr +#define ofps_comp_eperr3 ofps_comp_eperr +#define ofps_comp_eperr4 ofps_comp_eperr +#define ofps_comp_eperr5 ofps_comp_eperr +#define ofps_comp_eperr6 ofps_comp_eperr +#define ofps_comp_eperr7 ofps_comp_eperr +#define ofps_comp_eperr8 ofps_comp_eperr +#define ofps_comp_eperr9 ofps_comp_eperr +#endif + +static double ofps_comp_eperr( + ofps *s, + double *pddist, /* If not NULL, return the device distance */ + double *v, /* Device perceptual value */ + double *p, /* Device sample location to be evaluated */ + double *nv, /* Other perceptual value */ + double *np /* Other perceptual value */ +) { + int ii, e, f, di = s->di; + int isc; + double tt, ddist, pdist; + double eperr; + + /* Uncertaintly error computed from device and perceptual distance */ + +#ifndef NEVER /* unrolled code */ + +#if MXPD > 4 +# error "ofps.c: Need to expand switch code for MXPD > 4" +#endif + /* Unrole the loop */ + pdist = ddist = 0.0; + switch (di) { + case 4: + tt = (p[3] - np[3]); /* Device distance */ + ddist += tt * tt; + tt = (v[3] - nv[3]); /* Perceptual distance */ + pdist += tt * tt; + case 3: + tt = (p[2] - np[2]); /* Device distance */ + ddist += tt * tt; + tt = (v[2] - nv[2]); /* Perceptual distance */ + pdist += tt * tt; + case 2: + tt = (p[1] - np[1]); /* Device distance */ + ddist += tt * tt; + tt = (v[1] - nv[1]); /* Perceptual distance */ + pdist += tt * tt; + case 1: + tt = (p[0] - np[0]); /* Device distance */ + ddist += tt * tt; + tt = (v[0] - nv[0]); /* Perceptual distance */ + pdist += tt * tt; + } +#else + /* General code */ + for (pdist = ddist = 0.0, e = 0; e < di; e++) { + + /* Compute the device distance */ + tt = (p[e] - np[e]); + ddist += tt * tt; + + /* Compute the perceptual distance */ + tt = (v[e] - nv[e]); + pdist += tt * tt; + } +#endif + + if (pddist != NULL) + *pddist = ddist; + + ddist *= 100.0 * 100.0; + +//printf("~1 Device distance = %f, dev error = %f\n",ddist,s->devd_wght * ddist); + + ddist = sqrt(ddist); + pdist = sqrt(pdist); + eperr = s->devd_wght * ddist + s->perc_wght * pdist; + +//printf("~1 Percept distance = %f, perc error = %f\n",pdist,s->perc_wght * pdist); + return eperr; +} + +/* Compute the per node estimated position and interpolation errors */ +/* given a location and a list of up to di+1 neighborhood measurement nodes. */ +static void ofps_pn_eperr( + ofps *s, + double *ce, /* return the curvature/interpolation error for each node (may be NULL) */ + double *ee, /* return the uncertaintly error for each node */ + double *sv, /* Perceptual value if known, othewise NULL */ + double *sp, /* Device sample location to be evaluated */ + node **nds, /* Array of pointers to measurement nodes */ + int nnds /* Number of measurement nodes (>= 1) */ +) { + int ii, e, di = s->di; + node *np; + double _sv[MXPD]; /* Sample perceptual value */ + double iv[MXPD]; /* Interpolated perceptual value */ + + /* Lookup perceptual value at sample point location */ + if (sv == NULL) { + sv = _sv; + ofps_cc_percept(s, sv, sp); + } + + /* Uncertaintly error computed from device and perceptual distance */ + for (ii = 0; ii < nnds; ii++) { + ee[ii] = ofps_comp_eperr1(s, NULL, sv, sp, nds[ii]->v, nds[ii]->p); + } + + if (ce == NULL) + return; + + /* This could be made more efficient by only computing it for every */ + /* vertex during the initial seeding, and then on subsequent */ + /* passes only computing it once the re-seed/fixups are done. */ + if (s->curv_wght != 0.0) { /* Don't waste the time unless it's used */ + + /* Compute an error estimate that's related to curvature */ + for (ii = 0; ii < nnds; ii++) { + double cp[MXPD]; /* Midway points location */ + double civ[MXPD]; /* Midway points interpolated perceptual value */ + double cv[MXPD]; /* Midway points actual perceptual value */ + + /* Compute a point midway between the sample location and the node */ + for (e = 0; e < di; e++) { + cp[e] = 0.5 * (sp[e] + nds[ii]->p[e]); + civ[e] = 0.5 * (sv[e] + nds[ii]->v[e]); + } + + /* Look the actual perceptual value */ + /* (Computing this for each vertex consumes about 8% of execution time) */ + ofps_cc_percept(s, cv, cp); + + /* Compute the difference between the interpolated and actual perceptual values */ + for (ce[ii] = 0.0, e = 0; e < di; e++) { + double tt; + tt = civ[e] - cv[e]; + ce[ii] += tt * tt; + } + ce[ii] = s->curv_wght * sqrt(ce[ii]); + } + } else { + for (ii = 0; ii < nnds; ii++) + ce[ii] = 0.0; + } +} + +/* Compute the estimated position error given the results */ +/* of ofps_pn_eperr() */ +static double ofps_eperr2( + double *ee, /* Uncertainty error for each node */ + int nnds /* Number of measurement nodes */ +) { + double eperr; + int ii; + + for (eperr = 1e80, ii = 0; ii < nnds; ii++) { + if (ee[ii] < eperr) + eperr = ee[ii]; + } + +//printf("~1 ofps_eperr returning %f\n",eperr); + return eperr; +} + +/* Compute the estimated sampling error of a location given */ +/* the results of ofps_pn_eperr() */ +static double ofps_eserr2( + double *ce, /* The estimated curvature/interpolation error for each node */ + double *ee, /* Uncertainty error for each node */ + int nnds /* Number of measurement nodes */ +) { + int ii; + double eserr; + double mxce; + +#ifdef NEVER + /* We assume errors are inverse probabilities, */ + /* and sum inverse squares. */ + for (mxce = 0.0, eserr = 0.0, ii = 0; ii < nnds; ii++) { + double tt; + + tt = ee[ii] * ee[ii]; + if (tt > NUMTOL) + eserr += 1.0/tt; + else { /* One error is close to zero */ + eserr = 0.0; + break; + } + if (ce[ii] > mxce) + mxce = ce[ii]; + } + if (ii >= nnds) + eserr = 1.0/sqrt(eserr); + eserr += mxce; + +#else /* This seems best ? */ + /* Return nearest neighbor error metric for the moment, */ + /* with the ce being the maximum of the surrounders. */ + for (mxce = 0.0, eserr = 1e80, ii = 0; ii < nnds; ii++) { + if (ee[ii] < eserr) + eserr = ee[ii]; + if (ce[ii] > mxce) + mxce = ce[ii]; + } + eserr += mxce; +#endif + +//printf("~1 ofps_eserr returning %f\n",eserr); + return eserr; +} + +/* - - - - - - - - - -- - - - - - - - - - - - - - - - - - - - */ +/* Finding vertex location code using dnsqe() */ + +typedef struct { + double p[MXPD]; +} loc; + +/* Context for callback */ +struct _vopt_cx { + ofps *s; + node *nds[MXPD+1]; /* List of real nodes */ + int nn; /* Number of real nodes */ + int on; /* Index of odd node */ + + pleq *sp[MXPD+1]; /* List of touched gamut surface planes */ + int nsp; /* Number of touched gamut surface planes */ + + double srad; /* Search radius used */ + double stp[MXPD]; /* Starting point used */ + +#ifdef DUMP_FERR + /* Debug: */ + int debug; /* nz to trace search path */ + loc *clist; /* List of points sampled */ + int _nl; /* Allocated size */ + int nl; /* Number of points */ +#endif + +}; typedef struct _vopt_cx vopt_cx; + +/* calculate the functions at x[] */ +int dnsq_solver( /* Return < 0 on abort */ + void *fdata, /* Opaque data pointer */ + int n, /* Dimenstionality */ + double *x, /* Multivariate input values */ + double *fvec, /* Multivariate output values */ + int iflag /* Flag set to 0 to trigger debug output */ +) { + vopt_cx *cx = (vopt_cx *)fdata; + ofps *s = cx->s; + int k, e, di = s->di; + int nn_1 = cx->nn-1; + double sv[MXPD]; + double cee[MXPD+1], teperr; + +#ifdef DUMP_FERR + /* record the points we visited */ + if (cx->debug) { + if (cx->nl >= cx->_nl) { + cx->_nl = 2 * cx->_nl + 5; + if ((cx->clist = (loc *)realloc(cx->clist, sizeof(loc) * cx->_nl)) == NULL) + error("ofps: malloc failed on debug location array %d", cx->_nl); + } + for (e = 0; e < di; e++) + cx->clist[cx->nl].p[e] = x[e]; + cx->nl++; + } +#endif +//printf("~1 dnsq_solver got %d nodes and %d planes\n",cx->nn,cx->nsp); + + /* Get eperr at each real node */ + ofps_cc_percept(s, sv, x); /* We have to compute it */ + for (k = 0; k < cx->nn; k++) + cee[k] = ofps_comp_eperr2(s, NULL, sv, x, cx->nds[k]->v, cx->nds[k]->p); + +//fprintf(stderr,"~1 maxeperr = %f\n",cmax); + +//printf("~1 error ="); +//for (k = 0; k < cx->nn; k++) +// printf(" %f",cee[k]); +//printf("\n"); + + /* We need to create nn-1 output values from nn eperr's */ + /* cx->on is the odd one out. */ + /* Difference to average (best) */ + for (teperr = 0.0, k = 0; k < cx->nn; k++) + teperr += cee[k]; + teperr /= (double)cx->nn; + + for (k = e = 0; k < cx->nn; k++) { + if (k != cx->on) { + fvec[e++] = (teperr - cee[k]); + } + } + + /* Compute plane errors */ + for (k = 0; k < cx->nsp; k++) { + pleq *pl = cx->sp[k]; + double v; + + for (v = pl->pe[di], e = 0; e < di; e++) /* Compute relation to plane equation */ + v += pl->pe[e] * x[e]; + fvec[nn_1 + k] = FGPMUL * v; + } + + s->funccount++; + +//for (k = 0; k < nn_1; k++) +//printf("~1 fvec[%d] = %f\n",k,fvec[k]); +//printf("dnsq_solver returning %s from %s\n",ppos(di,fvec),ppos(di,x)); +//fprintf(stderr,"dnsq_solver returning %s from %s\n",ppos(di,fvec),ppos(di,x)); + + return 0; +} + +/* Locate a vertex position that has the eperr from all the real nodes */ +/* being equal. Set eperr, eserr and subjective value v[] too. */ +/* vv->ceperr contains the current eperr that must be bettered. */ +/* Return 0 if suceeded, 1 if best result is out of tollerance, 2 if failed. */ +static int position_vtx( + ofps *s, + nodecomb *vv, /* Return the location and its error */ + int startex, /* nz if current position is to be used as initial start point */ + int repos, /* nz after an itteration and we expect out of gamut */ + int fixup /* nz if doing fixups after itteration and expect out of gamut ??? */ +) { + int e, di = s->di; + int k, ii; + double tw; + double atp[MXPD], mct[MXPD]; /* Average node position, middle of closest 2 nodes */ + double osp[MXPD]; /* Original start position, start position */ + double cdist, fdist; /* Closest/furthest two points distance apart */ + double bsrad; /* Basic search radius, search radius */ + double ftol = FTOL; /* Final eperr tolerance */ + int tfev = 0, tcalls = 0; /* Track average successful fevs */ + int maxfev = 50; /* Maximum function evaluations */ + int tries, notries = MAXTRIES; /* Point to give up on. Giving up will error. */ + vopt_cx cx, pcx; /* dnsq context + previous context */ + +#ifdef DEBUG + printf("Position_vtx called for comb %s\n",pcomb(di,vv->nix)); +#endif + + s->positions++; + s->sob->reset(s->sob); + +#ifdef DUMP_FERR + cx.debug = 0; +#endif + + /* Setup for dnsq to optimize for equal eperr */ + cx.s = s; + + /* Pointers to real nodes. Although we allow for the */ + /* fake inner/outer nodes, eperr() will fail them later. */ + for (ii = e = 0; e <= di; e++) { + if (vv->nix[e] >= 0 || vv->nix[e] < -s->nbp) + cx.nds[ii++] = s->n[vv->nix[e]]; + } + cx.nn = ii; + + if (ii == 0) { + fflush(stdout); + error("ofps: unexpectedely got no real nodes in vertex position %s",pcomb(di,vv->nix)); + } + +#ifdef DEBUG + printf("%d real nodes\n",ii); + for (k = 0; k < ii; k++) + printf("Node ix %d at %s\n",cx.nds[k]->ix,ppos(di,cx.nds[k]->p)); +#endif + + /* Setup gamut suface planes */ + cx.nsp = 0; + if (ii < (di+1)) { + /* Create list of pointers to the gamut surface planes involved */ + for (e = 0, k = ii; k <= di; e++, k++) { +#ifdef DEBUG + printf("Adding plane for node ix %d\n",vv->nix[k]); +#endif + cx.sp[e] = &s->gpeqs[-1 - vv->nix[k]]; + } + cx.nsp = e; + } + + /* If there is only one node, map it to the planes */ + /* and we're done. */ + if (ii == 1) { + double ee[MXPD+1]; + + for (e = 0; e < di; e++) + vv->p[e] = cx.nds[0]->p[e]; + + confineto_gsurf(s, vv->p, cx.sp, cx.nsp); + + if (checkon_gsurf(s, vv->p, cx.sp, cx.nsp) == 0) { +#ifdef DEBUG + printf("Single node comb %s failed to confine to gamut surface\n",pcomb(di,vv->nix)); +#endif + return 2; + } + + /* Compute perceptual (can't clip because of confine) */ + s->percept(s->od, vv->v, vv->p); + + /* Compute the eperr's for each node. */ + ofps_pn_eperr(s, vv->ce, ee, vv->v, vv->p, cx.nds, cx.nn); + + /* Compute errors at returned location */ + vv->eperr = ofps_eperr2(ee, cx.nn); + vv->eserr = ofps_eserr2(vv->ce, ee, cx.nn); + +#ifdef DEBUG + printf("Single node, returning comb %s opt pos = %s, eperr = %f, eserr = %f\n",pcomb(di,vv->nix),ppos(di,vv->p),vv->eperr,vv->eserr); +#endif + return 0; + } + { + /* Compute average of real nodes */ + for (e = 0; e < di; e++) + atp[e] = 0.0; + for (tw = 0.0, k = 0; k < ii; k++) { + double w = 1.0; + + for (e = 0; e < di; e++) + atp[e] += cx.nds[k]->p[e]; + tw += w; + } + for (e = 0; e < di; e++) + atp[e] /= tw; + +#ifdef DEBUG + printf("Average of %d real node start pos = %s\n",ii,ppos(di,atp)); +#endif + } + + /* Locate the closest and furthest two nodes */ + { + double ceperr = 1e200; + int i, j, bi = 0, bj = 0; + + /* Find the two vectors that have the closest eperr. Brute force search */ + /* and track the device position for the two points involved. */ + /* Also locate the smallest device distance. */ + cdist = 1e200; + fdist = -1.0; + for (i = 0; i < (ii-1); i++) { + for (j = i+1; j < ii; j++) { + double dist; + dist = ofps_comp_eperr3(s, NULL, cx.nds[i]->v, cx.nds[i]->p, cx.nds[j]->v, cx.nds[j]->p); + if (dist < ceperr) { + ceperr = dist; + bi = i; + bj = j; + } + for (dist = 0.0, e = 0; e < di; e++) { + double tt = cx.nds[i]->p[e] - cx.nds[j]->p[e]; + dist += tt * tt; + } + if (dist < cdist) + cdist = dist; + if (dist > fdist) + fdist = dist; + } + } + + fdist = sqrt(fdist); + cdist = sqrt(cdist); + + /* Compute the middle of the two closest eperr nodes */ + for (e = 0; e < di; e++) + mct[e] = 0.5 * (cx.nds[bi]->p[e] + cx.nds[bj]->p[e]); + + /* Set a step/search radius based on the distance */ + /* between the two closest device distance nodes. */ + if (cdist < COINTOL) { +#ifdef DEBUG + printf("Two nodes are cooincident! - dnsq will fail!\n"); +#endif + if (s->verb > 1) + warning("Two nodes are cooincident! ix %d, pos %s and ix %d pos %s",cx.nds[bi]->ix,ppos(di,cx.nds[bi]->p),cx.nds[bj]->ix,ppos(di,cx.nds[bj]->p)); + } + bsrad = 0.2 * cdist; + if (bsrad < 1e-5) + bsrad = 1e-5; + } + + /* Set initial starting position */ +// if (startex && ! ofps_would_clip_point(s, vv->p)) { } + if (startex) { + + for (e = 0; e < di; e++) + osp[e] = vv->p[e]; + +// ofps_clip_point(s, vv->p, vv->p); +#ifdef DEBUG + printf("Startex startposition = %s\n",ppos(di,atp)); +#endif + } else { + double mwt = 0.3; + /* Start at equalateral point between two closest */ + /* nodes towards average. */ + for (e = 0; e < di; e++) { +// osp[e] = mct[e]; /* Best for 2D ? */ +// osp[e] = atp[e]; /* best for 3D/4D ? */ + osp[e] = mwt * mct[e] + (1.0 - mwt) * atp[e]; /* Good compromize */ + } + } + + /* Try our computed starting position first, and if that fails, */ + /* retry with a random offset starting location. */ + cx.srad = bsrad; + for (e = 0; e < di; e++) + cx.stp[e] = osp[e]; + cx.on = 0; + + for (tries = 0; tries < notries; tries++) { + int rv; + double fvec[MXPD]; /* Return function value at solution */ + int cfunccount; + + if (tries > 0) { /* Determine a starting point */ + + /* Try all possible odd one outs */ + cx.on++; + + /* On carry, use a random start offset */ + /* (Tried culling random starts and odd one outs */ + /* by picking one with a low norm, but */ + /* while this reduced the number of small */ + /* retries, it worsened the number of failures */ + /* and sucesses after a large number of retries.) */ + if (cx.on >= cx.nn) { + double rscale = 1.0; /* Random scale */ + double fval[MXPD]; + int nc; + + s->sob->next(s->sob, cx.stp); + + /* Scale random value around original starting point */ + for (e = 0; e < di; e++) { + cx.stp[e] = cx.stp[e] * 2.0 - 1.0; /* Make -1.0 to 1.0 range */ + if (cx.stp[e] < 0.0) { + cx.stp[e] *= rscale * (osp[e] - s->imin[e]); + } else { + cx.stp[e] *= rscale * (s->imax[e] - osp[e]); + } + cx.stp[e] += atp[e]; + } + ofps_clip_point4(s, cx.stp, cx.stp); + cx.on = 0; + } + } + + /* Set start position */ + for (e = 0; e < di; e++) + vv->p[e] = cx.stp[e]; + +#ifdef DEBUG + printf("Starting location = %s, srad = %f, on = %d\n",ppos(di,cx.stp),cx.srad,cx.on); +#endif + +//printf("\nStarting location = %s, srad = %f\n",ppos(di,cx.stp),cx.srad); + /* Locate vertex */ + cfunccount = s->funccount; + s->dnsqs++; + if (tcalls == 0) + maxfev = 500; + else + maxfev = 2 * tfev/tcalls; + rv = dnsqe((void *)&cx, dnsq_solver, NULL, di, vv->p, cx.srad, fvec, 0.0, ftol, maxfev, 0); + if ((s->funccount - cfunccount) > 20) { +//printf("More than 20: %d\n",s->funccount - cfunccount); + } + if ((s->funccount - cfunccount) > s->maxfunc) { + s->maxfunc = (s->funccount - cfunccount); +//printf("New maximum %d\n",s->maxfunc); + } + + if (rv != 1 && rv != 3) { + /* Fail to converge */ +#ifdef DEBUG + printf("dnsqe fail to converge, retuned %d\n",rv); +#endif + } else { + double ee[MXPD+1]; + double max, min; + double ple = 0.0; /* Gamut plane error */ + + /* Evaluate the result */ + + /* Update average function evaluations */ + tcalls++; + tfev += s->funccount - cfunccount; + +#ifdef DEBUG + printf("dnsq pos %s\n",ppos(di,vv->p)); + if (rv == 3) + printf("dnsq returned 3 - dtol too small\n"); +#endif + + /* Compute perceptual */ + ofps_cc_percept(s, vv->v, vv->p); + + /* Compute the eperr's for each node. */ + ofps_pn_eperr(s, vv->ce, ee, vv->v, vv->p, cx.nds, cx.nn); + + min = 1e80, max = -1e80; + for (e = 0; e < cx.nn; e++) { + if (min > ee[e]) + min = ee[e]; + if (max < ee[e]) + max = ee[e]; + } + + /* Compute the worst plane equation error */ + for (k = 0; k < cx.nsp; k++) { + double v; + for (v = cx.sp[k]->pe[di], e = 0; e < di; e++) /* Compute relation to plane equation */ + v += cx.sp[k]->pe[e] * vv->p[e]; + v = fabs(v * FGPMUL); +//printf("~1 gamut plane %d err = %f\n",k,v); + if (v > ple) + ple = v; + } + +#ifdef DEBUG + printf("new vertex pos %s has eperrs match by %f & gamut plane %f\n",ppos(di,vv->p),max-min,ple); +#endif + /* If not dtol too large, Check that the balance is acceptable */ + if (/* rv != 3 && */ + (((cx.nn > 1) && (max - min) > (ftol * 2.0)) + || ((cx.nsp > 0) && ple > (ftol * 2.0)))) { + /* Don't use this */ +#ifdef DEBUG + printf("new vertex pos %s doesn't have sufficient matching eperrs and on gamut plane\n",ppos(di,vv->p),max-min,ple); +#endif + } else { + /* eperr balance is acceptable, so further */ + /* evaluate the location found. */ + double ss; + + s->sucfunc += (s->funccount - cfunccount); + s->sucdnsq++; + + /* Compute how much the result is out of gamut */ + vv->oog = ofps_oog(s, vv->p); +#ifdef DEBUG + if (vv->oog > 0.01) + printf("dnsq returned out of gamut result by %e\n", vv->oog); +#endif + + /* Compute errors at returned location */ + vv->eperr = ofps_eperr2(ee, cx.nn); + vv->eserr = ofps_eserr2(vv->ce, ee, cx.nn); + + /* Decide whether a vertex location is acceptable */ + /* We accept a point that has an acceptable error balance */ + /* and improves the eperr, and is in gamut if this is not a repos. */ + /* (There's some mystery stuff in here for fixups) */ + if ((cx.nn <= 1) || ((max - min) <= (ftol * 2.0) + && ((!repos && vv->oog <= 0.01 && vv->eperr < (vv->ceperr + 0.1)) + || ( repos && vv->oog <= 0.01 && vv->eperr < (5.0 * vv->ceperr + 20.0)) + || ( repos && vv->oog > 0.0 && vv->eperr < 1000.0) + || ( fixup && vv->oog < 20.0 && vv->eperr < (vv->ceperr + 0.01)) + ))) { + + if (tries > s->maxretries) + s->maxretries = tries; +#ifdef DEBUG + printf(" - comb %s suceeded on retry %d (max %d)\n",pcomb(di,vv->nix),tries,s->maxretries); + printf(" oog = %f, eperr = %f, ceperr = %f\n",vv->oog,vv->eperr,vv->ceperr); +#endif +//if (tries > 10) +// printf(" - comb %s suceeded on retry %d (max %d)\n",pcomb(di,vv->nix),tries,s->maxretries); +// +//printf("Solution for comb %s has eperr %f < ceperr %f and not out of gamut by %f, retry %d\n",pcomb(di,vv->nix),vv->eperr,vv->ceperr,vv->oog,tries+1); +//printf("Solution is at %s (%s)\n",ppos(di,vv->p),ppos(di,vv->v)); + +// +//if (repos) printf("~1 vtx no %d dtav = %f, fdist = %f\n",vv->dtav,fdist); +//if (repos && vv->vv->no == 889) printf("~1 vtx no %d dtav = %f, fdist = %f\n",vv->vv->no,vv->dtav,fdist); + +#ifdef DUMP_FERR /* Create .tiff of dnsq function error */ + if (tries >= DUMP_FERR) { + printf("Suceeded on retry %d, dumping debug rasters\n",tries); + + /* Re-run the last unsucessful dnsq, to trace the path */ + pcx.debug = 1; + pcx.clist = NULL; + pcx._nl = 0; + pcx.nl = 0; + dnsqe((void *)&pcx, dnsq_solver, NULL, di, pcx.stp, pcx.srad, fvec, 0.0, ftol, maxfev, 0); + pcx.debug = 0; + dump_dnsqe(s, "dnsq_fail2.tif", vv->nix, &pcx); + free(pcx.clist); + + /* Re-run the first unsucessful dnsq, to trace the path */ + pcx.debug = 1; + pcx.clist = NULL; + pcx._nl = 0; + pcx.nl = 0; + pcx.on = 0; /* First odd one out */ + for (e = 0; e < di; e++) + pcx.stp[e] = atp[e]; /* best start ? */ + dnsqe((void *)&pcx, dnsq_solver, NULL, di, pcx.stp, pcx.srad, fvec, 0.0, ftol, maxfev, 0); + pcx.debug = 0; + dump_dnsqe(s, "dnsq_fail1.tif", vv->nix, &pcx); + free(pcx.clist); + + /* Re-run the sucessful dnsq, to trace the path */ + cx.debug = 1; + cx.clist = NULL; + cx._nl = 0; + cx.nl = 0; + dnsqe((void *)&cx, dnsq_solver, NULL, di, cx.stp, cx.srad, fvec, 0.0, ftol, maxfev, 0); + cx.debug = 0; + dump_dnsqe(s, "dnsq_suc.tif", vv->nix, &cx); + free(cx.clist); + exit(0); + } +#endif + + break; /* Use the result now */ + } +#ifdef DEBUG + printf("Solution for comb %s has eperr %f > ceperr %f or out of gamut by %f, retry %d\n",pcomb(di,vv->nix),vv->eperr,vv->ceperr,vv->oog,tries+1); +#endif + } + } + pcx = cx; /* Save unsucessful context for debug */ + } /* Retry */ + + /* If we've run out of tries, return the best solution we found */ + if (tries >= notries) { + + /* Show up if this ever gets used */ + for (e = 0; e < di; e++) + vv->p[e] = -0.1; + ofps_cc_percept(s, vv->v, vv->p); +#ifdef DEBUG + printf("vertex location solving failed after %d tries\n",tries); +#endif + if (s->verb > 1) + warning("vertex location solving failed after %d tries",tries); + return 2; /* Don't use this vertex */ + } + +#ifdef DEBUG + printf("Returning comb %s opt pos = %s val = %s, eperr = %f, eserr = %f\n",pcomb(di,vv->nix),ppos(di,vv->p),ppos(di,vv->v),vv->eperr,vv->eserr); +#endif + + return 0; +} + +/* --------------------------------------------------------- */ +/* Deal with creating a dummy vertex to represent one that */ +/* can't be positioned. We simply locate the best point we can. */ + +/* calculate the functions at x[] */ +double powell_solver( /* Return < 0 on abort */ + void *fdata, /* Opaque data pointer */ + double *x /* Multivariate input values */ +) { + vopt_cx *cx = (vopt_cx *)fdata; + ofps *s = cx->s; + int k, e, di = s->di; + int nn_1 = cx->nn-1; + double sv[MXPD]; + double cee[MXPD+1], teperr; + double ss, oog; + double rv = 0.0; + +//printf("~1 powell_solver got %d nodes and %d planes\n",cx->nn,cx->nsp); + + /* Get eperr at each real node */ + ofps_cc_percept(s, sv, x); /* We have to compute it */ + for (k = 0; k < cx->nn; k++) + cee[k] = ofps_comp_eperr2(s, NULL, sv, x, cx->nds[k]->v, cx->nds[k]->p); + +//fprintf(stderr,"~1 maxeperr = %f\n",cmax); + +//printf("~1 eprror ="); +//for (k = 0; k < cx->nn; k++) +// printf(" %f",cee[k]); +//printf("\n"); + + /* The error is zero if the input value */ + /* is within gamut, all the real node eperr's are */ + /* the same, and the ditance to gamut planes is zero. */ + + /* Compute average eperr */ + for (teperr = 0.0, k = 0; k < cx->nn; k++) + teperr += cee[k]; + teperr /= (double)cx->nn; + +//printf("~1 average = %f\n", teperr); + + /* Add diference to average */ + for (k = e = 0; k < cx->nn; k++) { + if (k != cx->on) { + double tt; + tt = teperr - cee[k]; + rv += tt * tt; + } + } +//printf("~1 after diff to avg rv = %f\n", rv); + + /* Compute distances to planes */ + for (k = 0; k < cx->nsp; k++) { + pleq *pl = cx->sp[k]; + double v; + + for (v = pl->pe[di], e = 0; e < di; e++) /* Compute relation to plane equation */ + v += pl->pe[e] * x[e]; + v *= FGPMUL; + rv += v * v; + } +//printf("~1 after diff to planes rv = %f\n", rv); + + /* Compute distance out of gamut */ + + for (ss = oog = 0.0, e = 0; e < di; e++) { + if (x[e] < (s->imin[e])) { + double tt = s->imin[e] - x[e]; + if (tt > oog) oog = tt; + } else if (x[e] > (s->imax[e])) { + double tt = x[e] - s->imax[e]; + if (tt > oog) oog = tt; + } + ss += x[e]; + } + if (ss > s->ilimit) { + double tt; + ss = (ss - s->ilimit)/di; /* Axis aligned distance to ink limit */ + tt = sqrt((double)di) * ss; /* Diagonal distance to ink limit */ + if (tt > oog) + oog = tt; + } + + rv += 1000.0 * oog * oog; +//printf("~1 after oog rv = %f\n", rv); + +//printf("powell_solver returning %f from %s\n",rv, ppos(di,x)); +//fprintf(stderr,"powell_solver returning %f from %s\n",rv, ppos(di,x)); + + return 0; +} + +/* Fake up a vertex position when position_vtx() has failed. */ +static void dummy_vtx_position( + ofps *s, + vtx *ev1, vtx *ev2, /* Deleted and non-deleted vertexes */ + nodecomb *vv /* Return the location and its error */ +) { + int e, di = s->di; + int k, ii; + vopt_cx cx; /* dnsq context */ + double ss[MXPD]; + double bl; /* Location on path between del and !del vertexes */ + double ee[MXPD+1]; + +#ifdef DEBUG + printf("dummy_vtx_position called for comb %s\n",pcomb(di,vv->nix)); +#endif + +#ifdef DUMP_FERR + cx.debug = 0; +#endif + + /* Setup for dnsq to optimize for equal eperr */ + cx.s = s; + + /* Pointers to real nodes. Although we allow for the */ + /* fake inner/outer nodes, eperr() will fail them later. */ + for (ii = e = 0; e <= di; e++) { + if (vv->nix[e] >= 0 || vv->nix[e] < -s->nbp) + cx.nds[ii++] = s->n[vv->nix[e]]; + } + cx.nn = ii; + + if (ii == 0) { + fflush(stdout); + error("ofps: unexpectedely got no real nodes in vertex position %s",pcomb(di,vv->nix)); + } + +#ifdef DEBUG + printf("%d real nodes\n",ii); + for (k = 0; k < ii; k++) + printf("Node ix %d at %s (%s)\n",cx.nds[k]->ix,ppos(di,cx.nds[k]->p),ppos(di,cx.nds[k]->v)); +#endif + + /* Setup gamut suface planes */ + cx.nsp = 0; + if (ii < (di+1)) { + /* Create list of pointers to the gamut surface planes involved */ + for (e = 0, k = ii; k <= di; e++, k++) { +#ifdef DEBUG + printf("Adding plane for node ix %d\n",vv->nix[k]); +#endif + cx.sp[e] = &s->gpeqs[-1 - vv->nix[k]]; + } + cx.nsp = e; + } + + /* Set search area */ + for (e = 0; e < di; e++) + ss[e] = 0.001; + + /* Compute a position on the locus between the del and !del vertexes */ + bl = (ev1->nba_eperr - ev2->eperr)/(ev1->eperr - ev2->eperr); + if (bl < 0.0) + bl = 0.0; + else if (bl > 1.0) + bl = 1.0; + for (e = 0; e < di; e++) { + vv->p[e] = bl * vv->v1[0]->p[e] + (1.0 - bl) * vv->v2[0]->p[e]; + } + + ofps_clip_point5(s, vv->p, vv->p); + +#ifndef NEVER + /* Seem to often fail due to pathalogical condition for max type eperr() */ + if (powell(NULL, di, vv->p, ss, 1e-5, 1000, powell_solver, &cx, NULL, NULL)) { + warning("dummy_vtx_position powell failed"); + } +#endif + + ofps_clip_point5(s, vv->p, vv->p); + + /* Compute perceptual (was clipped above) */ + s->percept(s->od, vv->v, vv->p); + + /* Compute the eperr's for each node. */ + ofps_pn_eperr(s, vv->ce, ee, vv->v, vv->p, cx.nds, cx.nn); + + /* Compute errors at returned location */ + vv->eperr = ofps_eperr2(ee, cx.nn); + vv->eserr = ofps_eserr2(vv->ce, ee, cx.nn); + +#ifdef DEBUG + printf("Returning comb %s opt pos = %s val = %s, eperr = %f, eserr = %f\n",pcomb(di,vv->nix),ppos(di,vv->p),ppos(di,vv->v),vv->eperr,vv->eserr); +#endif +} + +/* --------------------------------------------------- */ +/* Vertex add routines */ + +/* Comlete adding a node to a Voronoi surface. */ +/* It's assumed that the hit nodes have been added to the s->nxh list */ +/* and the s->nvcheckhits set to the number of hit vertexes. */ +/* The nodes may be fake gamut boundary nodes, but must have */ +/* real vertexes. Vertexes that are marked for deletion or new ones */ +/* will be added to the batch update list for later execution. */ +/* Nodes that have had vertexes added to them will added to the node 'to be updated' list */ +/* and then a batch update will be executed. */ +/* Return 0 if it wasn't added, */ +/* Return 1 if it was added */ +static int add_to_vsurf( +ofps *s, +node *nn, /* Node to add */ +int fixup, /* 0 = seed, 1 = fixup ?? */ +int abortonfail /* 0 = ignore position failures, 1 = abort add if there are any failures */ +) { + int e, ff, f, di = s->di; + int i, j, k, ndi; + vtx *tev; + vtx *ev1, *ev2; /* Deleted and non-deleted vertexes */ + int ndelvtx; /* Number of vertexes to delete */ + int nncombs; /* Number of node combinations generated, allocated. */ + +#ifdef DEBUG + printf("\nAdd_to_vsurf node ix %d (p %s), i_sm %s, a_sm %s\n",nn->ix, ppos(di,nn->p),psm(s,&s->sc[nn->pmask].i_sm),psm(s,&s->sc[nn->pmask].a_sm)); +#endif + +#ifdef DEBUG + if (nn->ix < -s->nbp) { + printf("Fake node involved\n"); + } +#endif + + /* Update stats for the hit vertexes */ + s->nsurfadds++; + s->nhitv += s->nvcheckhits; + if (s->nvcheckhits > s->maxhitv) + s->maxhitv = s->nvcheckhits; + + if (s->nvcheckhits == 0) { /* Node doesn't improve any vertex eperrs */ +#ifdef DEBUG + printf("Add_to_vsurf done - not better, not added\n"); +#endif + return 0; + } + +#ifdef DEBUG + printf("There are %d vertexes to replace, and %d potential replacement vertexes\n",s->nvcheckhits, di * s->nvcheckhits); + printf("Vertexes marked for deletion are:\n"); + for (ev1 = s->nxh; ev1 != NULL; ev1 = ev1->nxh) + printf(" vtx no %d nix %s\n",ev1->no,pcomb(di,ev1->nix)); +#endif + + /* Generate all the potential new node combinations/replacement verticies. */ + /* We check each deleted vertex against its non-deleted neighbours. */ + /* The same replacement combination may be generated more than once. */ + for (nncombs = 0, ev1 = s->nxh; ev1 != NULL; ev1 = ev1->nxh) { + + for (ndi = 0; ndi < ev1->nnv; ndi++) { + int nix[MXNIX]; +#ifdef INDEP_SURFACE + setmask cvm; /* visibility setmask for each vertex combination */ +#endif + ev2 = ev1->nv[ndi]; + + if (ev2->del != 0) + continue; /* Can't pair with another deleted vertex */ + +#ifdef DEBUG + printf("\nDealing with vertex pair del no %d nix %s, and !del no %d nix %s\n",ev1->no,pcomb(di,ev1->nix),ev2->no,pcomb(di,ev2->nix)); +#endif + +#ifdef DEBUG + { + int aa, bb, cc; /* Probable hit check */ + int nnm, nmix; + + /* Use the nixm to quickly check if all but one parent node matches */ + aa = ev1->nix[MXPD+2]; /* nixm */ + bb = ev2->nix[MXPD+2]; /* nixm */ + if ((aa & bb) == 0 || (cc = aa & ~bb, (cc & (cc-1)) != 0)) { + error("Vertexes %d comb %s and %d comb %s are vn neighbours that shouldn't be!", ev1->no,pcomb(di,ev1->nix),ev2->no,pcomb(di,ev2->nix)); + } + + /* Do an exact check of all except one node match */ + for (nnm = ff = e = 0; e <= di; e++) { + for (f = ff; f <= di; f++) { + if (ev1->nix[e] == ev2->nix[f]) { + ff = f; /* Start from here next time */ + break; + } + if (ev1->nix[e] > ev2->nix[f]) /* No point in looking further */ + f = di; + } + if (f > di) { /* Didn't match */ + if (++nnm > 1) + break; + nmix = e; + } + } + if (e <= di) { + error("Vertexes %d comb %s and %d comb %s are vn neighbours that shouldn't be!", ev1->no,pcomb(di,ev1->nix),ev2->no,pcomb(di,ev2->nix)); + } + } +#endif /* DEBUG */ + /* Create the node combination */ + for (e = 0; e <= di; e++) { + nix[e] = ev1->nix[e]; + for (f = 0; f <= di; f++) { + if (nix[e] == ev2->nix[f]) + break; + } + if (f > di) /* Found one different */ + nix[e] = nn->ix; + } + sort_nix(s, nix); + + /* Check that the same node doesn't appear twice */ + /* (~~99 Why do we need this - does it ever happen ??) */ + for (e = 0; e < di; e++) { + for (k = e+1; k <= di; k++) { + if (nix[e] == nix[k]) { +#ifdef DEBUG + printf("New vertex with duplicate nodes %s from vertexes %d comb %s and %d comb %s ignored\n",pcomb(di,nix),ev1->no,pcomb(di,ev1->nix),ev2->no,pcomb(di,ev2->nix)); + if (s->verb > 1) + warning("New vertex with duplicate nodes %s from vertexes %d comb %s and %d comb %s ignored",pcomb(di,nix),ev1->no,pcomb(di,ev1->nix),ev2->no,pcomb(di,ev2->nix)); +#endif + break; + } + } + if (k <= di) + break; + } + if (e < di) + continue; + + /* See if the combination has at least one real node, */ + /* and none of the inner or outer fake nodes. */ + /* (~~99 Do we need this - does it ever happen ??) */ + k = 0; + for (e = 0; e <= di; e++) { + if (nix[e] >= 0) + k |= 1; /* Found one real node */ + else if (nix[e] < -s->nbp) + break; /* There's a fake inner or outer node though */ + } + if (e <= di || k == 0) { +#ifdef DEBUG + printf("Combination ix: %s, skipped because it has no real nodes\n",pcomb(di,nix)); + if (s->verb > 1) + warning("Combination ix: %s, skipped because it has no real nodes",pcomb(di,nix)); +#endif + continue; + } + +#ifdef INDEP_SURFACE + /* Compute the pertinent visibility mask for this vertex creation. */ + /* Note that we keep pairs of vertexes that aren't visible to each other */ + /* so that we can add them to the vertex net. */ + sm_andand(s, &cvm, &ev1->vm, &ev2->vm, &s->sc[comp_cmask(s, nix)].a_sm); +#ifdef DEBUG + printf("Combination ix: %s, vm %s, eperrs %f to %f\n",pcomb(di,nix),psm(s,&cvm),ev1->eperr,ev2->eperr); +#endif +#else /* !INDEP_SURFACE */ +#ifdef DEBUG + printf("Combination ix: %s, eperrs %f to %f\n",pcomb(di,nix),ev1->eperr,ev2->eperr); +#endif +#endif /* !INDEP_SURFACE */ + + /* See if this combination is already in the list */ + /* due to a pair having the same common nodes. */ + for (k = 0; k < nncombs; k++) { + + if (s->combs[k].nix[MXPD+1] != nix[MXPD+1]) /* Hashes don't match */ + continue; + + for (e = 0; e <= di; e++) { /* Do full check */ + if (s->combs[k].nix[e] != nix[e]) { + break; /* No match */ + } + } + if (e > di) { /* Match */ + double ceperr; + if (s->combs[k].count >= s->combs[k]._count) { + s->combs[k]._count = 2 * s->combs[k]._count + 5; + if ((s->combs[k].v1 = (vtx **)realloc(s->combs[k].v1, + sizeof(vtx *) * s->combs[k]._count)) == NULL) + error ("ofps: malloc failed on node combination vertex list %d", + s->combs[k]._count); + if ((s->combs[k].v2 = (vtx **)realloc(s->combs[k].v2, + sizeof(vtx *) * s->combs[k]._count)) == NULL) + error ("ofps: malloc failed on node combination vertex list %d", + s->combs[k]._count); + } + s->combs[k].v1[s->combs[k].count] = ev1; + s->combs[k].v2[s->combs[k].count] = ev2; + s->combs[k].count++; + + /* Update ceperr if this is higher */ + ceperr = ev1->eperr > ev2->eperr ? ev1->eperr : ev2->eperr; + if (ceperr > s->combs[k].ceperr) + s->combs[k].ceperr = ceperr; +#ifdef DEBUG + printf("Vertex generation count now %d with ceperr %f\n",s->combs[k].count,s->combs[k].ceperr); +#endif +#ifdef INDEP_SURFACE + sm_or(s, &s->combs[k].vm, &s->combs[k].vm, &cvm); +#ifdef DEBUG + printf("Vertex combination vm now %s\n",psm(s,&s->combs[k].vm)); +#endif +#endif + break; + } + } + if (k < nncombs) + continue; /* Already on list */ + + /* Add this combination to the list as a new entry */ + if (nncombs >= s->_ncombs) { + int o_ncombs = s->_ncombs; + s->_ncombs = 2 * s->_ncombs + 5; + if ((s->combs = (nodecomb *)realloc(s->combs, sizeof(nodecomb) * s->_ncombs)) == NULL) + error ("ofps: malloc failed on node combination array length %d", s->_ncombs); + memset((void *)(s->combs + o_ncombs), 0, + (s->_ncombs - o_ncombs) * sizeof(nodecomb)); + } + + if (1 >= s->combs[nncombs]._count) { + s->combs[nncombs]._count = 2 * s->combs[nncombs]._count + 5; + if ((s->combs[nncombs].v1 = (vtx **)realloc(s->combs[nncombs].v1, + sizeof(vtx *) * s->combs[nncombs]._count)) == NULL) + error ("ofps: malloc failed on node combination vertex list %d", + s->combs[nncombs]._count); + if ((s->combs[nncombs].v2 = (vtx **)realloc(s->combs[nncombs].v2, + sizeof(vtx *) * s->combs[nncombs]._count)) == NULL) + error ("ofps: malloc failed on node combination vertex list %d", + s->combs[nncombs]._count); + } + s->combs[nncombs].v1[0] = ev1; + s->combs[nncombs].v2[0] = ev2; + s->combs[nncombs].count = 1; + for (e = 0; e <= di; e++) + s->combs[nncombs].nix[e] = nix[e]; + s->combs[nncombs].nix[MXPD+1] = nix[MXPD+1]; /* Copy Hash */ + s->combs[nncombs].nix[MXPD+2] = nix[MXPD+2]; /* Copy nixm */ + s->combs[nncombs].ceperr = ev1->eperr > ev2->eperr ? ev1->eperr : ev2->eperr; + s->combs[nncombs].startex = 0; + s->combs[nncombs].pvalid = 0; + s->combs[nncombs].vv = NULL; +#ifdef INDEP_SURFACE + sm_cp(s, &s->combs[nncombs].vm, &cvm); +#else /* !INDEP_SURFACE */ + sm_set(s, &s->combs[nncombs].vm, 0); /* Not used */ +#endif /* !INDEP_SURFACE */ + + nncombs++; + +#ifdef DEBUG + printf("Adding combination to list with ceperr %f, vm %s, list size %d\n",s->combs[nncombs-1].ceperr,psm(s,&s->combs[nncombs-1].vm), nncombs); +#endif + } + } + +#ifdef DEBUG + printf("\nThere are %d unique node combinations in list, locating combs. in list:\n",nncombs); +#endif + + /* Locate the replacement vertex positions */ + for (i = 0; i < nncombs; i++) { + + ev1 = s->combs[i].v1[0]; + ev2 = s->combs[i].v2[0]; + +#ifdef INDEP_SURFACE + /* Ignore pairs of vertexes that don't form a visible new combination */ + if (sm_test(s, &s->combs[i].vm) == 0) { +#ifdef DEBUG + printf("Combination ix: %s, skipped because vm %s == 0x0\n",pcomb(di,s->combs[i].nix),psm(s,&s->combs[i].vm)); +#endif + continue; + } +#endif /* INDEP_SURFACE */ + +#ifdef DEBUG + printf("\nNode combination ix: %s\n",pcomb(di,s->combs[i].nix)); +#endif + /* Try and locate existing vertex that is due to the same nodes */ + if ((s->combs[i].vv = vtx_cache_get(s, s->combs[i].nix)) != NULL) { +#ifdef DEBUG + printf("Vertex is same as existing no %d\n",s->combs[i].vv->no); +#endif + + s->combs[i].vv->add = 2; /* Updated vertex */ + + /* If a new vertex is not the same as the two nodes it's being created */ + /* from yet has been marked for deletion, reprieve it. */ + if (s->combs[i].vv->del) { + + s->combs[i].vv->del = 0; +#ifdef DEBUG + printf("New existing vertex no %d is deleted vertex - reprieve it\n",s->combs[i].vv->no); +#endif + } + } + + /* We need to create a replacement vertex, locate position for it */ + if (s->combs[i].vv == NULL) { +#ifdef DEBUG + printf("About to locate comb ix: %s, ceperr %f\n",pcomb(di,s->combs[i].nix),s->combs[i].ceperr); +#endif +//printf("~1 About to locate comb ix: %s, ceperr %f\n",pcomb(di,s->combs[i].nix),s->combs[i].ceperr); + /* Compute a starting position between the deleted/not deleted pair */ + /* This seems very slightly better than the default mct[] + atp[] scheme. */ + if (nn->ix >= 0) { /* If not boundary */ + double bl; + bl = (ev1->nba_eperr - ev2->eperr)/(ev1->eperr - ev2->eperr); + if (bl < 0.0) + bl = 0.0; + else if (bl > 1.0) + bl = 1.0; + for (e = 0; e < di; e++) { + s->combs[i].p[e] = bl * s->combs[i].v1[0]->p[e] + (1.0 - bl) * s->combs[i].v2[0]->p[e]; + } + ofps_clip_point5(s, s->combs[i].p, s->combs[i].p); +//printf("Startex is %s\n",ppos(di,s->combs[i].p)); + s->combs[i].startex = 1; + } + /* find vertex position of max eperr */ + if (position_vtx(s, &s->combs[i], s->combs[i].startex, 0, fixup) != 0) { + if (s->verb > 1) + warning("Unable to locate vertex at node comb %s\n",pcomb(di,s->combs[i].nix)); + s->posfails++; + s->posfailstp++; + if (abortonfail) + break; + + } else { + s->combs[i].pvalid = 1; + } + } + } /* Next replacement vertex */ + + /* If we aborted because abortonfail is set and we failed to place a new node, */ + /* erase our tracks and return failure. */ + if (i < nncombs) { + for (i = 0; i < nncombs; i++) { + if (s->combs[i].vv != NULL) { + s->combs[i].vv->add = 0; + s->combs[i].vv->del = 0; + } + } + return 0; + } + +#ifdef DEBUG + printf("\nNow converting positioned combinations to vertexes\n"); +#endif + /* Convert from computed position to vertexes */ + for (i = 0; i < nncombs; i++) { + +#ifdef INDEP_SURFACE + /* Ignore combo that doesn't form a visible new vertex */ + if (sm_test(s, &s->combs[i].vm) == 0) + continue; +#endif /* INDEP_SURFACE */ + + if (s->combs[i].vv == NULL) { /* Not an existing vertex */ + + if (s->combs[i].pvalid == 0) { /* No valid vertex found */ + + /* [ If a valid vertex location was not found we tried */ + /* using the deleted vertex's location instead, and */ + /* relying on it getting deleted at some later stage. */ + /* This seems to stuff the incremental fixup code up */ + /* completely, so we create a summy vertex position instead. ] */ + + /* Fake up a vertex position when position_vtx() has failed. */ + dummy_vtx_position(s, ev1, ev2, &s->combs[i]); + goto lnew_vtx; + + } else { + + lnew_vtx:; + /* Allocate space for new vertex, and create it from location */ + s->combs[i].vv = new_vtx(s); + +#ifdef DEBUG + printf("Converting comb %s to from del %d !del %d to vtx no %d vm %s\n",pcomb(di,s->combs[i].nix),s->combs[i].v1[0]->no, s->combs[i].v2[0]->no, s->combs[i].vv->no,psm(s,&s->combs[i].vm)); +#endif + + for (e = 0; e < di; e++) { + s->combs[i].vv->nix[e] = s->combs[i].nix[e]; + s->combs[i].vv->ce[e] = s->combs[i].ce[e]; + s->combs[i].vv->p[e] = s->combs[i].p[e]; + s->combs[i].vv->v[e] = s->combs[i].v[e]; + } + s->combs[i].vv->nix[e] = s->combs[i].nix[e]; + s->combs[i].vv->nix[MXPD+1] = s->combs[i].nix[MXPD+1]; /* Copy Hash */ + s->combs[i].vv->nix[MXPD+2] = s->combs[i].nix[MXPD+2]; /* Copy nixm */ + + s->combs[i].vv->eperr = s->combs[i].eperr; + s->combs[i].vv->eserr = s->combs[i].eserr; + + /* Count the number of gamut surfaces the vertex falls on */ + det_vtx_gsurf(s, s->combs[i].vv); + + /* Check if the node and vertex cooincide, and aren't going to move */ + if (nn->nsp == di && s->combs[i].vv->nsp == di + && nn->pmask == s->combs[i].vv->pmask) { +//printf("~1 Trapped node and vertex coincide - mark vertex as ghost\n"); + s->combs[i].vv->ghost = 1; + } + s->combs[i].vv->del = 0; + s->combs[i].vv->add = 1; /* New vertex */ + } + } + + if (s->combs[i].vv != NULL) { /* There is a new or updated vertex */ +#ifdef DEBUG + printf("Vertex no %d pmask 0x%x cmask 0x%x vm %s at %s being added to batch list\n",s->combs[i].vv->no, s->combs[i].vv->pmask, s->combs[i].vv->cmask, psm(s,&s->combs[i].vm),ppos(di,s->combs[i].vv->p)); +#endif + /* Add to batch update list if it is not already there */ + if (s->combs[i].vv->bch == 0) { +//printf("~1 adding vtx 0x%x no %d to batch list\n",s->combs[i].vv,s->combs[i].vv->no); + s->combs[i].vv->batch = s->batch; + s->batch = s->combs[i].vv; + s->combs[i].vv->bch = 1; + } + +#ifdef INDEP_SURFACE + /* Set or update the vm */ + sm_or(s, &s->combs[i].vv->buvm, &s->combs[i].vv->buvm, &s->combs[i].vm); +//printf("~1 Vertex no %d buvm set/update to %s\n",s->combs[i].vv->no,psm(s,&s->combs[i].vv->buvm)); +#endif /* INDEP_SURFACE */ + } + } + + /* Add all vtx marked for deletion to the batch update list. */ + for (ev1 = s->nxh; ev1 != NULL; ev1 = ev1->nxh) { + +#ifdef DEBUG + printf("Vertex no %d being added to pending delete batch list, bdvm %s\n",ev1->no,psm(s,&ev1->bdvm)); +#endif + + if (ev1->bch == 0) { +//printf("~1 adding vtx 0x%x no %d to batch list\n",ev1,ev1->no); + ev1->batch = s->batch; + s->batch = ev1; + ev1->bch = 1; + } + +#ifdef INDEP_SURFACE + /* Add node setmask to those that will be removed delete vertex visibility */ +# ifdef USE_DISJOINT_SETMASKS + sm_orand(s, &ev1->bdvm, &ev1->bdvm, &s->sc[nn->pmask].a_sm, &s->sc[ev1->cmask & nn->pmask].a_sm); +# else + sm_or(s, &ev1->bdvm, &ev1->bdvm, &s->sc[nn->pmask].a_sm); +# endif +#endif /* INDEP_SURFACE */ + } + + /* Do first part of batch update. */ + /* This will reset ->del on nodes that will be retained */ + do_batch_update1(s, fixup); + + /* Remove deleted vertex's from the vertex net, and their */ + /* parent nodes. */ + { + vtx *vx1, *vx2; + for (vx2 = s->nxh; vx2 != NULL; vx2 = vx2->nxh) { + int aa, bb, cc; /* Probable hit check */ + int nnm, nmix; + +//printf("~1 Removing deleted vertex no %d from net\n",vx2->no); + if (vx2->del == 0) { /* It's not really being deleted */ +//printf("~1 vtx no %d is being retained\n",vx2->no); + continue; + } + + /* Remove from vertex net */ + for (j = 0; j < vx2->nnv; j++) { + vx1 = vx2->nv[j]; + +//printf("~1 Removing vtx no %d from vtx no %d\n",vx2->no, vx1->no); +// if (vx1->del == 0) { } /* Speed optimization */ + { + vtx_rem_vertex(s, vx1, vx2); + } +//else printf("~1 Not removing from vtx no %d because it will be deleted anyway\n",vx1->no); + } + vx2->nnv = 0; + + /* Remove from parent nodes */ + for (e = 0; e <= di; e++) { + int ix = vx2->nix[e]; + node *pp = s->n[ix]; + node_rem_vertex(s, pp, vx2); + } + } + } + + /* Create/modify the vertex neighbour net lists for all the new vertexes. */ + /* Use a brute force search of local nodes to create the vertex net. */ + { + vtx *vx1, *vx2; + + /* For each new vertx */ + for (i = 0; i < nncombs; i++) { + vx1 = s->combs[i].vv; + + if (vx1 == NULL || vx1->del) + continue; + + /* Possibly add other new vertexes as neighbours */ + for (j = i+1; j < nncombs; j++) { + + vx2 = s->combs[j].vv; + + if (vx2 != NULL && vx2->del == 0) + vtx_cnd_biadd_vtx(s, vx1, vx2, fixup); + } + + /* Possibly add deleted and non-deleted vertexes */ + for (k = 0; k < s->combs[i].count; k++) { + +#ifdef INDEP_SURFACE + /* Add deleted vertex if it isn't going to be deleted */ + if (s->combs[i].v1[k]->del == 0) { + vtx_cnd_biadd_vtx(s, vx1, s->combs[i].v1[k], fixup); + } +#endif /* INDEP_SURFACE */ + + /* Add non-deleted vertex */ + if (s->combs[i].v2[k]->del == 0) + vtx_cnd_biadd_vtx(s, vx1, s->combs[i].v2[k], fixup); + } + + /* Add any existing vertexes of the node we're re-adding */ + if (fixup) { + for (j = 0; j < nn->nvv; j++) { + if (nn->vv[j]->del) + continue; + vtx_cnd_biadd_vtx(s, vx1, nn->vv[j], fixup); + } + } + } + } + + /* Do second part of batch update */ + do_batch_update2(s, fixup); + +#ifdef DEBUG + printf("Add_to_vsurf done - added node %d\n",nn->ix); +#endif + + /* If we want intermediate fixup state: */ + /* dump_node_vtxs(s, 0); */ + /* sanity_check(s, 0); */ + +#ifdef NEVER +{ + vtx *vx; + + /* Dump vertex and associated vertex information */ + for (vx = s->uvtx; vx != NULL; vx = vx->link) { + printf("Vertex no %d has Vtx net:",vx->no); + for (j = 0; j < vx->nnv; j++) { + vtx *vx2 = vx->nv[j]; + printf(" %d",vx2->no); + } + printf("\n"); + } + printf("\n"); + fflush(stdout); +} +#endif /* NEVER */ + + return 1; +} + +/* - - - - - - - - - - - */ +/* Deal with verticies marked for deletion or addition, */ +/* as well as updating the nodes consequently affects. */ +/* If fixup is set, add any new or updates vertexes to the s->fchl */ + +/* Do the first part of the batch update */ +static void do_batch_update1(ofps *s, int fixup) { + int e, di = s->di; + vtx *vv, *nvv; + node *pp; + +#ifdef DEBUG + printf("Doing batch update to add/delete vertexes - 1\n"); + +#endif + /* Update a vertexes vm, and decide whether it is going */ + /* to be deleted or just hidden. */ + for (vv = s->batch; vv != NULL; vv = vv->batch) { + +#ifdef DEBUG + printf("Pending vtx no %d del %d, add %d, vm %s |= %s &= %s\n",vv->no,vv->del,vv->add,psm(s,&vv->vm),psm(s,&vv->buvm),psm(s,&vv->bdvm)); + if (vv->ofake) + error("An ofake vertex no %d was hit!\n",vv->ofake); +#endif + + if (vv->add == 1) { /* New node */ + +#ifdef INDEP_SURFACE + sm_or(s, &vv->vm, &vv->vm, &vv->buvm); +#ifdef DEBUG + printf("Set vertex no %d vm to %s\n",vv->no,psm(s,&vv->vm)); +#endif +#endif /* INDEP_SURFACE */ + + /* Add it to the cache */ + vtx_cache_add(s, vv); + + /* Add it to the spatial accelleration grid */ + ofps_add_vacc(s, vv); + + /* Add to seeding lists */ + ofps_add_vseed(s, vv); + + } else if (vv->add == 2) { /* Update the visibility setmask */ + int was_inseed = 0, is_inseed = 0; + +#ifdef INDEP_SURFACE + if (sm_andtest(s, &s->sc[0].a_sm, &vv->vm) != 0) + was_inseed = 1; + + sm_or(s, &vv->vm, &vv->vm, &vv->buvm); +#ifdef DEBUG + printf("Updated vertex no %d vm to %s\n",vv->no,psm(s,&vv->vm)); +#endif + if (sm_andtest(s, &s->sc[0].a_sm, &vv->vm) != 0) + is_inseed = 1; + + if (vv->used == 0) { + /* Adjust presense in eserr tree if visibility has changed */ + if (was_inseed && !is_inseed) { +//printf("Removing (1) vtx no %d, used %d, eserr %f, vm %s nsp %d\n",vv->no,vv->used,vv->eserr,psm(s,&vv->vm),vv->nsp); + if ((aat_aerase(s->vtrees[vv->nsp], (void *)vv)) == 0) + error("aat_aerase vertex failed to find vertex no %d (1)", vv->no); + } else if (!was_inseed && is_inseed) { +//printf("Adding (1) vtx no %d, used %d, eserr %f, vm %s nsp %d\n",vv->no,vv->used,vv->eserr,psm(s,&vv->vm),vv->nsp); + if ((aat_ainsert(s->vtrees[vv->nsp], (void *)vv)) == 0) + error("aat_ainsert vertex malloc failed"); + } + } +#endif /* INDEP_SURFACE */ + + } else if (vv->del != 0) { + +#ifdef INDEP_SURFACE + int was_inseed = 0, is_inseed = 0; + + if (sm_andtest(s, &s->sc[0].a_sm, &vv->vm) != 0) + was_inseed = 1; +//printf("Checked was_inseed %d for vtx no %d, used %d, eserr %f, vm %s nsp %d\n",was_inseed,vv->no,vv->used,vv->eserr,psm(s,&vv->vm),vv->nsp); + + /* Remove visibility due to any deletes */ + sm_andnot(s, &vv->vm, &vv->vm, &vv->bdvm); + + if (sm_andtest(s, &s->sc[0].a_sm, &vv->vm) != 0) + is_inseed = 1; +//printf("Checking is_inseed %d for vtx no %d, used %d, eserr %f, vm %s nsp %d\n",is_inseed,vv->no,vv->used,vv->eserr,psm(s,&vv->vm),vv->nsp); + + /* Adjust presense in eserr tree if visibility has changed */ + if (vv->used == 0 && was_inseed && !is_inseed) { +//printf("Removing (2) vtx no %d, used %d, eserr %f, vm %s nsp %d\n",vv->no,vv->used,vv->eserr,psm(s,&vv->vm),vv->nsp); + if ((aat_aerase(s->vtrees[vv->nsp], (void *)vv)) == 0) + error("aat_aerase vertex failed to find vertex no %d (2)", vv->no); + } +#ifdef DEBUG + printf("Delete vertex no %d vm to %s\n",vv->no,psm(s,&vv->vm)); +#endif + /* Don't delete vertex if it remains visible to some sub-surfaces. */ + if (sm_test(s, &vv->vm) != 0) { + vv->del = 0; + vv->add = 2; /* Update it instead */ +#ifdef DEBUG + printf("Retaining vtx no %d marked for deletion because vm is %s\n",vv->no,psm(s,&vv->vm)); +#endif + } +#endif /* INDEP_SURFACE */ + } + } +} + +/* Do the second part of the batch update */ +static void do_batch_update2(ofps *s, int fixup) { + int e, di = s->di; + vtx *vv, *nvv; + node *pp; + +#ifdef DEBUG + printf("Doing batch update to add/delete vertexes - 2\n"); + +#endif + /* Add or delete a vertex */ + for (vv = s->batch; vv != NULL; vv = nvv) { + + nvv = vv->batch; + vv->batch = NULL; /* Ready for next time */ + vv->bch = 0; + + /* Setup vertex ready for another round */ + sm_set(s, &vv->buvm, 0); /* Ready to OR in new visibility next time */ + sm_set(s, &vv->bdvm, 0); /* Ready for OR in visibility to be remove next time */ + + if (vv->del) { /* delete vertex */ + +#ifdef DEBUG + printf("Deleting vertex no %d\n",vv->no); fflush(stdout); +#endif + /* Add all the parent nodes of this vertex to the update list */ + for (e = 0; e <= di; e++) { + int ix = vv->nix[e]; + node *pp = s->n[ix]; + + if (pp->upflag != s->flag) { + pp->nup = s->nup; + s->nup = pp; + pp->upflag = s->flag; + } + /* During fixups, maintain nodes vertexes lists */ + /* (During re-seeding we update it as a batch) */ + if (fixup) + node_rem_vertex(s, pp, vv); + } + del_vtx(s, vv); + +#ifdef DEBUG + { + vtx *vx; + int i, k; + + printf("~1 checking that no references to vertex remain after delete:\n"); + /* Check vertexes references to vertexes */ + for (vx = s->uvtx; vx != NULL; vx = vx->link) { + for (k = 0; k < vx->nnv; k++) { + if (vx->nv[k] == vv) { + printf("Vertex 0x%x no %d still in no %d nv list after deletion\n",vv,vv->no,vx->no); +#ifdef WARNINGS + warning("Vertex 0x%x no %d still in no %d nv list after deletion",vv,vv->no,vx->no); +#endif + } + } + } + /* Check nodes references to vertexes */ + for (i = 0; i < s->np; i++) { /* For all nodes */ + node *p1 = s->n[i]; + for (k = 0; k < p1->nvv; k++) { /* For all its vertexes */ + if (p1->vv[k] == vv) { + printf("Vertex 0x%x no %d still in ix %d vv list after deletion\n",vv,vv->no,p1->ix); +#ifdef WARNINGS + warning("Vertex 0x%x no %d still in ix %d vv list after deletion",vv,vv->no,p1->ix); +#endif + } + } + } + /* Fixup sorted list reference to vertex */ + for (i = 0; i < s->nsvtxs; i++) { + if (s->svtxs[i] == vv) { + printf("Vertex 0x%x no %d still in svtxs[%d] after deletion\n",vv,vv->no,i); +#ifdef WARNINGS + warning("Vertex 0x%x no %d still in svtxs[%d] after deletion",vv,vv->no,i); +#endif + } + } + } +#endif /* DEBUG */ + + } else if (vv->add != 0) { /* New or updated vertex, set updates & checks */ + +#ifdef DEBUG + printf("Adding vertex no %d\n",vv->no); +#endif + + /* Add all the parent nodes of this vertex to the update list */ + for (e = 0; e <= di; e++) { + node *pp = s->n[vv->nix[e]]; + + if (pp->upflag != s->flag) { + /* Add node to update list */ + pp->nup = s->nup; + s->nup = pp; + pp->upflag = s->flag; + } + + /* During fixups, maintain nodes vertexes lists. */ + /* (During re-seeding we update it as a batch) */ + if (fixup && vv->add == 1) + node_add_vertex(s, pp, vv); + } + + /* If this is a fixup and the vertex hasn't been added */ + /* to the "check" list, do so */ + if (fixup && vv->fflag != s->fflag) { + vv->fchl = s->fchl; /* Add vertex to the "to be checked" list */ + if (s->fchl != NULL) + s->fchl->pfchl = &vv->fchl; + s->fchl = vv; + vv->pfchl = &s->fchl; + vv->fflag = s->fflag; +#ifdef DEBUG + printf("Adding vtx no %d to check list due to addition\n",vv->no); +#endif + } + } + } + + s->batch = NULL; /* Nothing in pending delete list */ + s->nup = NULL; /* Nothing in nodes to be updated list */ +} + +/* ------------------------------------------------------------------------------- */ + +/* Do a quick count of the number of verticies hit by their */ +/* neighbour nodes. This is used during itteration to decide */ +/* whether to reseed or fixup. */ +/* Return the number of vertexes hit */ +static int +ofps_quick_check_hits(ofps *s) { + int i, j, k, e, di = s->di; + int nvxhits = 0; + + /* For all nodes */ + for (i = -s->nbp; i < s->np; i++) { + node *nn = s->n[i]; /* Node being considered */ + + s->flag++; /* Marker flag for testing this node */ + nn->flag = s->flag; + + /* Check all the neighbors nodes */ + for (j = 0; j < nn->nvn; j++) { + node *pp = s->n[nn->vn[j]]; + + /* Test nn against all of pp's vertexes */ + for (k = 0; k < pp->nvv; k++) { + vtx *vx = pp->vv[k]; + + if (vx->cflag == s->flag) + continue; /* Don't test same node twice */ + vx->cflag = s->flag; + + /* If node that we're testing against is in vertex */ + /* ignore it, we expect them to hit. */ + for (e = 0; e <= di; e++) { + if (nn->ix == vx->nix[e]) + break; + } + if (e <= di) { + continue; + } + +#ifdef INDEP_SURFACE + /* Check if this vertex is visible to this node */ + if (sm_vtx_node(s, vx, nn) == 0) { + continue; /* It's hidden */ + } +#endif /* INDEP_SURFACE */ + + if (nn->ix < 0) { + pleq *vp = &s->gpeqs[-1 - nn->ix]; + double v = 0.0; + + /* See if the vertex is on the wrong side of the plane */ + for (v = vp->pe[di], e = 0; e < di; e++) + v += vp->pe[e] * vx->p[e]; + + if (v > 0.0) { + nvxhits++; + } + } else { + double eperr = ofps_comp_eperr7(s, NULL, vx->v, vx->p, nn->v, nn->p); + + /* See if the vertex eperr will be improved */ + if (eperr < (vx->eperr + 0.0)) { + nvxhits++; + } + } + } + } + } + return nvxhits; +} + +/* ------------------------------------------------------------------------------- */ + +/* Recursive hit search routine: */ + +/* Test a vertex for a possible hit from a node. */ +/* Recursively search dorec recursions until there is at least */ +/* one hit, and stop after beyhit recursions beyond the hit region. */ +/* s->flag is assumed to be relevant for the given node. */ +/* Any hit vertexes are added to the s->nxh list. */ +/* s->vvchecks and s->nvcheckhits will be updated. */ +/* Return nz if vertex was hit */ + +/* Breadth first search. */ +/* (Assumes s->flag has been incremented) */ +/* [We're assuming that there is a single connected hit region, which */ +/* is not valid for SUBD. ] */ +static int +ofps_check_vtx(ofps *s, node *nn, vtx *vx, int dorec, int beyhit) { + int i, j, e, di = s->di; + vtx *slist = NULL; /* Next to search list */ + int dist; /* Distance from initial vertex */ + int hit = 0; + double tol = 0.0; /* Tollerance */ + +#ifdef DEBUG + printf("ofps_check_vtx() for node ix %d starting at vertex no %d, dorec %d, beyhit %d\n",nn->ix,vx->no,dorec,beyhit); +#endif + +#ifdef SANITY_CHECK_HIT + if (nn->ix < -s->nbp) + error("Calling ofps_check_node on fake outside node"); +#endif + + if (vx->cflag == s->flag) + return vx->del; /* Already been checked */ + + /* Put the starting node on the search list */ + vx->slist = slist; + slist = vx; + vx->sflag = s->flag; /* Mark as done for pre-hit search */ + + /* until we run out of vertexes, or we are done */ + for (dist = 0; slist != NULL && dist <= dorec; dist++) { + vtx *nvx; + + /* For each vertex in the search list, check it and recursion. */ + for (vx = slist, slist = NULL; vx != NULL; vx = nvx) { + nvx = vx->slist; + vx->opqsq = 0; /* Not on the list anymore */ + + if (vx->ofake) + continue; /* ofake vertexes can't be hit */ +#ifdef DEBUG + printf("%d: Checking vtx no %d %s\n",dist, vx->no,hit ? "Post-Hit" : "Pre-Hit"); +#endif +#ifdef INDEP_SURFACE + /* Only check for hit if the vertex is visible to the node */ + if (sm_vtx_node(s, vx, nn) == 0) { +# ifdef DEBUG + printf("%d: Vertex no %d xmask 0x%x vm %s isn't visible to ix %d pmask 0x%x a_sm %s\n",dist,vx->no,vx->cmask,psm(s,&vx->vm),nn->ix,nn->pmask,psm(s,&s->sc[nn->pmask].a_sm)); +# endif /* DEBUG */ + continue; + } +#endif /* INDEP_SURFACE */ + + /* If the vertex hasn't been checked yet: */ + if (vx->cflag != s->flag) { + vx->add = 0; + vx->del = 0; + vx->par = 0; + + s->vvchecks++; /* Checking a vertex */ + + /* Check if node is already parent to this vertex. */ + /* This only happens during fixups if the reposition fails and we */ + /* retain the vertex with the deleted vertex location (not currently */ + /* done), or by slim numerical margine, so ignore such hits. */ + /* We treat a parent as a hit node for the purposes of recursion, */ + /* and add it to a special list used to complete the vertex net. */ + if (nn->ixm & vx->nix[MXPD+2]) { /* Is in nixm */ + for (e = 0; e <= di; e++) { /* Do exact check */ + if (nn->ix == vx->nix[e]) + break; + } + if (e <= di) { +#ifdef DEBUG + printf("Vertex no %d has already got node ix %d\n",vx->no,nn->ix); +#endif + vx->par = 1; + } + } + + if (nn->ix < 0) { + pleq *vp = &s->gpeqs[-1 - nn->ix]; + double v = 0.0; + + /* See if the vertex is on the wrong side of the plane */ + for (v = vp->pe[di], e = 0; e < di; e++) + v += vp->pe[e] * vx->p[e]; + + if (!vx->par && v > tol) { + s->nvcheckhits++; + if (!hit) + slist = nvx = NULL; /* Abort pre-hit search */ + hit = 1; + vx->del = 1; /* Mark for deletion */ + vx->nxh = s->nxh; /* Add vertex to list */ + s->nxh = vx; + vx->disth = 0; /* This is a hit vertex */ + vx->hflag = s->flag; +#ifdef DEBUG + printf("%d: Gamut surface boundary plain hit by %f\n",dist,v); +#endif + } +#ifdef DEBUG + else { /* If worse */ + printf("%d: Gamut surface boundary plain miss by %f\n",dist,v); + } +#endif + } else { /* Node rather than boundary plane */ + + /* nba_eperr is assumed to be valid if vx->cflag == s->flag */ + vx->nba_eperr = ofps_comp_eperr7(s, NULL, vx->v, vx->p, nn->v, nn->p); +#ifdef DEBUG + printf("%d: Computing nba_eperr of %f for vtx no %d\n",dist, vx->nba_eperr, vx->no); +#endif + /* See if the vertex eperr will be improved */ + if (!vx->par && (vx->eperr - vx->nba_eperr) > tol) { + s->nvcheckhits++; + if (!hit) + slist = nvx = NULL; /* Abort pre-hit search */ + hit = 1; + vx->del = 1; /* Mark for deletion */ + vx->nxh = s->nxh; /* Add vertex to list */ + s->nxh = vx; + vx->disth = 0; /* This is a hit vertex */ + vx->hflag = s->flag; +#ifdef DEBUG + printf("%d: Vertex error improvement hit by %f (%f < %f)\n",dist, vx->eperr-vx->nba_eperr,vx->nba_eperr,vx->eperr); + + if (vx->par) { + printf("Vertex no %d hit by its own parent ix %d\n",vx->no, nn->ix); +#ifdef WARNINGS + warning("Vertex no %d hit by its own parent ix %d",vx->no, nn->ix); +#endif + } +#endif + } +#ifdef DEBUG + else { /* If worse */ + printf("%d: Vertex error not hit by %f (%f < %f)\n",dist, vx->eperr-vx->nba_eperr,vx->nba_eperr,vx->eperr); + } +#endif + } + vx->cflag = s->flag; +// ~~777 +//if (vx->del && i_rand(1,1000) == 15) { +// printf("~1 failing to check vertex no %d\n",vx->no); +// vx->cflag = s->flag -1; +// vx->del = 0; +//} + } + + /* Decide whether to recurse by adding vertexes to the new list */ + if (!hit) { + + /* Pre-hit recursion */ + if (dist < dorec) { /* Still within search radius */ + + /* Add all the unsearched vertexes neighbors to the next search list */ + for (j = 0; j < vx->nnv; j++) { + vtx *vx2 = vx->nv[j]; + + if (vx2->sflag == s->flag) /* Already been pre-hit searched */ + continue; +#ifdef DEBUG + printf("%d: Adding vtx no %d to next pre-hit search list\n",dist, vx2->no); +#endif + /* Put the neighbour node on the search list */ + vx2->slist = slist; + slist = vx2; + vx2->sflag = s->flag; + } + } + + } else { + + /* Post hit recursion */ + if (vx->disth <= beyhit) { /* Still within post-hit search radius */ + int disth = vx->disth + 1; /* Neighbours distance */ + + /* Add all the unsearched vertexes neighbors to the next search list */ + for (j = 0; j < vx->nnv; j++) { + vtx *vx2 = vx->nv[j]; + + if (vx2->hflag == s->flag) { /* Already been post-hit searched */ + if (disth >= vx2->disth) + continue; /* So skip it */ + + /* The already post-hit searched neighbour has an improved distance */ +#ifdef DEBUG + printf("%d: Improving ph searched vtx %d disth from %d to %d\n",dist, vx2->no,vx2->disth,disth); +#endif + vx2->disth = disth; /* Improved distance to hit though */ + if (vx2->disth > beyhit || vx2->opqsq) + continue; /* But it's still too far, or already on the list */ + /* Search this neighbour again now that it is within radius */ + } else { + vx2->disth = disth; /* Set hit distance */ + } +#ifdef DEBUG + printf("%d: Adding vtx no %d to next post-hit search list (disth = %d)\n",dist, vx2->no,vx2->disth); +#endif + /* Put the neighbour node on the search list */ + vx2->slist = slist; + slist = vx2; + vx2->sflag = vx2->hflag = s->flag; + vx2->opqsq = 1; /* On the list */ + } + } +#ifdef DEBUG + else + printf("%d: Vertex %d disth %d is > beyhit %d so not recursing\n",dist, vx->no,vx->disth,beyhit); +#endif + + } + } /* Next vertex in current list */ +#ifdef DEBUG + printf("Finished inner loop because vx 0x%x = NULL\n",vx); +#endif + } /* Next list */ +#ifdef DEBUG + printf("Finished outer loop because slist 0x%x = NULL, || dist %d > dorec %d\n",slist,dist,dorec); +#endif + + return hit; +} + +/* Non-recursive version of above used for sanity checking */ +/* that doesn't set any flags on vx */ +static int +ofps_check_vtx_sanity(ofps *s, node *nn, vtx *vx, int fixit) { + int i, j, e, di = s->di; + vtx *slist = NULL; /* Next to search list */ + int dist; /* Distance from initial vertex */ + double tol = 1e-6; + int hit = 0; + int par = 0; + +#ifdef DEBUG + printf("ofps_check_vtx_sanity() for node ix %d and vertex no %d\n",nn->ix,vx->no); +#endif + if (vx->cflag == s->flag) { +#ifdef DEBUG + printf("Returning alread calculated del = %d\n",vx->del); +#endif + return vx->del; /* Already been checked */ + } + + if (vx->ofake) { /* ofake nodes can't be hit */ +#ifdef DEBUG + printf("Returning ofake del = 0\n"); +#endif + return 0; + } + +#ifdef INDEP_SURFACE + /* Only check for hit if the vertex is visible to the node */ + if (sm_vtx_node(s, vx, nn) == 0) { +#ifdef DEBUG + printf("Returning non-visible del = 0\n"); +#endif + return 0; + } +#endif /* INDEP_SURFACE */ + + /* Check if node is already parent to this vertex. */ + /* This only happens during fixups if the reposition fails and we */ + /* retain the vertex with the deleted vertex location (not currently */ + /* done), or by slim numerical margine, so ignore such hits. */ + /* We treat a parent as a hit node for the purposes of recursion, */ + /* and add it to a special list used to complete the vertex net. */ + if (nn->ixm & vx->nix[MXPD+2]) { /* Is in nixm */ + for (e = 0; e <= di; e++) { /* Do exact check */ + if (nn->ix == vx->nix[e]) + break; + } + if (e <= di) + par = 1; + } + + if (nn->ix < 0) { + pleq *vp = &s->gpeqs[-1 - nn->ix]; + double v = 0.0; + + /* See if the vertex is on the wrong side of the plane */ + for (v = vp->pe[di], e = 0; e < di; e++) + v += vp->pe[e] * vx->p[e]; + + if (!par && v > tol) { + hit = 1; + + if (fixit) { + vx->slist = slist; + slist = vx; + vx->sflag = s->flag; + } + } + } else { /* Node rather than boundary plane */ + double nba_eperr; + + nba_eperr = ofps_comp_eperr7(s, NULL, vx->v, vx->p, nn->v, nn->p); + + /* See if the vertex eperr will be improved */ + if (!par && (vx->eperr - nba_eperr) > tol) { + hit = 1; + if (fixit) { + vx->slist = slist; + slist = vx; + vx->sflag = s->flag; + } + } + } + +#ifdef DEBUG + printf("Returning computed del = %d\n",hit); +#endif + return hit; +} + +/* - - - - - - - - - - - - - - - - - - - - - - - - */ + +/* Add a node to the currnent voronoi. */ +/* Return nz if the addition fails due to there being no vetex hits or a cooincince. */ +/* Return nz if abortonfail is set and we fail to position the node. */ +/* (This theoretically shouldn't happen, but does, due to the perceptual */ +/* geometry ?) */ +static int add_node2voronoi( +ofps *s, +int poi, /* Index of sample point to update/create Voronoi surface */ +int abortonfail /* 0 = ignore position failures, 1 = abort add if there are any failures */ +) { + node *nn = s->n[poi]; /* Node in question */ + int e, di = s->di; + int i, j; + vtx *vx = NULL; /* Closest vertex */ + +#ifdef DEBUG + printf("\nAdding Node ix %d pmask 0x%x at %s (perc %s) to Voronoi surface\n",poi,nn->pmask,ppos(di,nn->p),ppos(di,nn->v)); +#endif + + if (poi < 0) + error("Attempt to add fake point to veronoi surface"); + + if (nn->nvv > 0) + error("ofps: assert, node vertex info should be empty on add_node2voronoi() entry"); + + for (i = 0; i < 20; i++) { + int pci; /* Point cell list index */ + acell *cp; /* Acceleration cell */ + node *pp; + + /* Check if by some misfortune, this node colides with an existing node. */ + pci = ofps_point2cell(s, nn->v, nn->p); /* Grid index of cell of interest */ + cp = &s->grid[pci]; + for (pp = cp->head; pp != NULL; pp = pp->n) { + for (e = 0; e < di; e++) { + if (fabs(nn->v[e] - pp->v[e]) > (COINTOL * 100.0)) + break; /* Not cooincident */ + } + if (e >= di) { /* Cooincident */ +#ifdef DEBUG + printf("Node oint collides with existing - joggling it\n"); +// warning("Node oint collides with existing - joggling it"); +#endif + /* Joggle it's position */ + for (e = 0; e < di; e++) { + if (nn->p[e] < 0.5) + nn->p[e] += d_rand(0.0, 1e-4); + else + nn->p[e] -= d_rand(0.0, 1e-4); + } + /* Ignore confine planes. Next itter should fix it anyway ? */ + ofps_clip_point6(s, nn->p, nn->p); + s->percept(s->od, nn->v, nn->p); + break; + } + } + if (pp == NULL) + break; + } + if (i >= 20) { + if (s->verb > 1) + warning("add_node2voronoi: Assert, was unable to joggle cooincindent point"); + return 1; + } + +#ifdef DEBUG + printf("Locating all the hit vertexs\n"); +#endif + + s->nvcheckhits = 0; /* Count number of vertexes hit by recursive check. */ + s->batch = NULL; /* Nothing in pending delete list */ + s->nup = NULL; /* Nothing in nodes to be updated list */ + s->flag++; /* Marker flag for adding this node */ + s->nxh = NULL; /* Nothing in nodes hit list */ + +#ifdef DEBUG + printf("Done check of vertexes for hits\n"); +#endif + + /* Number of nodes that would be checked by exaustive search */ + s->vvpchecks += s->nv; + + ofps_findhit_vtxs(s, nn); + +#ifdef SANITY_CHECK_HIT +#ifdef DEBUG + printf("Doing sanity check of hits\n"); +#endif + for (vx = s->uvtx; vx != NULL; vx = vx->link) { /* Check all vertexes */ + + if (vx->cflag != s->flag && ofps_check_vtx_sanity(s, nn, vx, 0)) { + warning("!!!!!! Sanity: Add hit missed vertex no %d at %s !!!!!!",vx->no,ppos(di,vx->p)); + printf("!!!!!! Sanity: Add hit missed vertex no %d at %s !!!!!!\n",vx->no,ppos(di,vx->p)); + /* Don't stop for out of gamut vertexes that would have been hit */ + if (ofps_would_clip_point(s, vx->p)) + continue; + + /* Check if any of it's neighbours have been checked. */ + for (j = 0; j < vx->nnv; j++) { + vtx *vx2 = vx->nv[j]; + + if (vx2->cflag == s->flag) + break; /* Yes */ + } + if (j >= vx->nnv) { + warning("!!!!!! Sanity: Missed vertex was in isolated region"); + } else { + warning("!!!!!! Sanity: Missed vertex was adjacent to no %d", vx->nv[j]->no); + } +#ifdef SANITY_CHECK_HIT_FATAL + error("Failed to locate all hit vertexes"); +#endif /* SANITY_CHECK_HIT_FATAL */ + } + } +#endif /* SANITY_CHECK_HIT */ + +#ifdef DEBUG + printf("There were %d vertexes that will be hit by adding node\n",s->nvcheckhits); +#endif + + if (s->nvcheckhits == 0) { + if (s->verb > 1) + warning("Failed to get any vertex hits when adding a new node ix %d at %s",nn->ix,ppos(di,nn->p)); + return 1; + } + + /* Now turn all the hit vertexes into new vertexes. */ + if (add_to_vsurf(s, nn, 0, abortonfail) > 0) { + s->add_hit++; + } else { + if (abortonfail) + return 1; + s->add_mis++; + } + + ofps_add_nacc(s, nn); /* Add to spatial accelleration grid */ + + s->np++; + +#ifdef DEBUG + printf("Done add_node2voronoi()\n"); +#endif + + return 0; +} + +/* ------------------------------------------------------------------------------- */ + +/* Given a list of di plane equations, */ +/* compute the intersection point. */ +/* return nz if there is no intersection */ +static int comp_vtx(ofps *s, double *p, pleq **peqs) { + int i, e, di = s->di; + double **ta, *TTA[MXPD], TA[MXPD][MXPD]; + + for (e = 0; e < di; e++) + TTA[e] = TA[e]; + ta = TTA; + + for (i = 0; i < di; i++) { + for (e = 0; e < di; e++) + ta[i][e] = peqs[i]->pe[e]; /* Plane normal becomes row of matrix */ + p[i] = -peqs[i]->pe[di]; /* Plane constant becomes target */ + } + /* Solve the simultaneous linear equations A.x = B */ + /* Return 1 if the matrix is singular, 0 if OK */ + if (polished_solve_se(ta, p, di)) + return 1; + + return 0; +} + +/* --------------------------------------------------- */ + +/* Use a brute force search to (re-)create the vertex net. */ +/* This is used in initialization. */ +static void create_vtx_net(ofps *s) { + int ff, f, e, di = s->di; + vtx *vx1, *vx2; + +#ifdef DEBUG + printf("Doing create_vtx_net\n"); +#endif + + /* For each vertx */ + for (vx1 = s->uvtx; vx1 != NULL; vx1 = vx1->link) { + + vx1->nnv = 0; /* Clear the current list */ +#ifdef DEBUG + printf("Creating neighbourhood net for vtx no %d\n",vx1->no); +#endif + + /* Search all other vertexes for neighbours */ + for (vx2 = s->uvtx; vx2 != NULL; vx2 = vx2->link) { + int aa, bb, cc; /* Probable hit check */ + int nnm, nmix; + +//printf("~1 checking against vtx %d\n",vx2->no); + if (vx1 == vx2) { +//printf("~1 skip because it's the same\n"); + continue; + } + + + /* Use the nixm to quickly check if all but one parent node matches */ + aa = vx1->nix[MXPD+2]; /* nixm */ + bb = vx2->nix[MXPD+2]; /* nixm */ + if ((aa & bb) == 0 || (cc = aa & ~bb, (cc & (cc-1)) != 0)) { +//printf("~1 skip because nixm 0x%x and 0x%x don't match\n",aa,bb); + continue; /* It's certainly not */ + } + + /* Do an exact check of all except one node match */ + for (nnm = ff = e = 0; e <= di; e++) { + for (f = ff; f <= di; f++) { + if (vx1->nix[e] == vx2->nix[f]) { + ff = f; /* Start from here next time */ + break; + } + if (vx1->nix[e] > vx2->nix[f]) /* No point in looking further */ + f = di; + } + if (f > di) { /* Didn't match */ + if (++nnm > 1) + break; + nmix = e; + } + } + if (e <= di) { +//printf("~1 skip because nix %s and %s aren't one different\n",pcomb(di,vx1->nix),pcomb(di,vx2->nix)); + continue; /* No match */ + } + + if (nnm == 0) { + error("ofps: two vertexes have the same nodes !\n" + "no %d at %s nix %s\nno %d at %s nix %s", + vx1->no,ppos(di,vx1->p),pcomb(di,vx1->nix), + vx2->no,ppos(di,vx2->p),pcomb(di,vx2->nix)); + } + + /* vx2 is a neighbour, so add it to the vtx net */ + vtx_add_vertex(s, vx1, vx2); +//printf("~1 brute force: adding vtx %d as neighbour to %d\n",vx2->no,vx1->no); + } + } +} + +/* --------------------------------------------------- */ + +/* Use a brute force search to discover all the valid */ +/* sub-surface combinations. */ +static void discover_subsuf(ofps *s) { + int co; + double p[MXPD]; + pleq *peqs[MXPD]; + int i, j, k, e, di = s->di; + setmask acm; /* Accumulated mask for special last entry */ + + if (s->sminit) + return; /* Do this once */ + +#ifdef DEBUG + printf("Computing subd face combinations\n"); +#endif + + if ((s->sc = (surfcomb *)calloc(sizeof(surfcomb), (1 << s->nbp))) == NULL) + error ("ofps: malloc failed on sufcomb array"); + + for (co = 0; co < (1 << s->nbp); co++) { + + s->sc[co].co = co; + + /* Count number of planes */ + for (i = e = 0; e < s->nbp; e++) { + if (co & (1 << e)) + i++; + /* Skip combo if odd and even dimension planes are set */ + if ((e & 1) == 0 && e < (2 * di) && (co & (1 << e)) && (co & (1 << (e+1)))) + break; + } + s->sc[co].nos = i; + if (i > di || e < s->nbp) { + s->sc[co].valid = 0; + continue; + } + + /* Check that the combination results in a valid */ + if (i == di) { + for ( j = e = 0; e < s->nbp; e++) { + if (co & (1 << e)) + peqs[j++] = &s->gpeqs[e]; + } + if (comp_vtx(s, p, peqs) != 0 || ofps_would_clip_point(s, p)) { + s->sc[co].valid = 0; + } else { + s->sc[co].valid = 1; + } + } else { + if (co == 0 || i == 1) { + s->sc[co].valid = 1; + } else { + s->sc[co].valid = -1; + } + } +//printf("~1 val %s sc[%d].valid = %d\n",icmPdv(di, p), co,s->sc[co].valid); + } + /* Go through the unknown combinations, and see if there */ + /* is a valid lower dimensional combination that is valid. */ + for (co = 0; co < (1 << s->nbp); co++) { + if (s->sc[co].valid == -1) { + for (i = co+1; i < (1 << s->nbp); i++) { + if ((i & co) == co && s->sc[i].valid == 1) { + s->sc[co].valid = 1; + break; + } + } + if (i >= (1 << s->nbp)) /* Failed to find a valid combination */ + s->sc[co].valid = 0; + } + } +#ifdef USE_DISJOINT_SETMASKS + /* We can reduce the number of setmask bits by figuring out which */ + /* combinations are disjoint, and using the same setmask bits for disjoint */ + /* combinations. For CMYK, this reduces the setmask from 80-100 to less than 32 bits, */ + /* permiting faster mask manipulation. */ + { + surfcomb *scp, *zd = NULL; /* Zero Dimension combinations */ + surfcomb *sets = NULL; /* Sets at a given nos */ + int nsets = 0; /* Current number of sets */ + int _nsets = 0; /* Allocated array size */ + int nos; /* Number of surfaces */ + + /* init the circular lists, and add the 0D points to their list */ + for (k = co = 0; co < (1 << s->nbp); co++) { + s->sc[co].ds = &s->sc[co]; /* Init circular list to itself */ + if (s->sc[co].valid == 0) + continue; + /* Create a list of 0D points and count them */ + if (s->sc[co].nos == di) { + k++; + if (zd == NULL) + zd = &s->sc[co]; + else { + s->sc[co].ds = zd->ds; + zd->ds = &s->sc[co]; + } + } + } + + if (zd == NULL) + error("No zero-dim surface combinations (s->nbp = %d)",s->nbp); + +//printf("~1 total 0D points = %d\n",k); + + /* Temporarily use the setmask to track 0D hits */ + sm_init(s, k); + + k = 2; /* Count total disjoint sets, including 2 for di D and 0 D */ + + /* Locates sets for each dimension level */ + for (nos = 1; nos < di; nos++) { + nsets = 0; + +//printf("~1 doing nos = %d\n",nos); + /* Add the next combination to the sets */ + for (co = 0; co < (1 << s->nbp); co++) { + if (s->sc[co].valid == 0 || s->sc[co].nos != nos) + continue; + +//printf("~1 checking combo 0x%x\n",co); + /* Figure out 0D hits on this combo */ + i = 0; + scp = zd; + do { + if ((co & scp->co) == co) + sm_setbit(s, &s->sc[co].i_sm, i, 1); + i++; + scp = scp->ds; + } while(scp != zd); +//printf("~1 combo 0x%x has hits %s\n",co,psm(s,&s->sc[co].i_sm)); + + /* Search through the existing sets, and see */ + /* if this combo is disjoint */ + for (j = 0; j < nsets; j++) { + setmask tsm; + + if (sm_and(s, &tsm, &sets[j].i_sm, &s->sc[co].i_sm) == 0) { + /* Add this combo to the existing set */ + +//printf("~1 adding to set %d\n",j); + s->sc[co].ds = sets[j].ds->ds; + sets[j].ds->ds = &s->sc[co]; + sm_or(s, &sets[j].i_sm, &sets[j].i_sm, &s->sc[co].i_sm); + break; + } +//else printf("Miss on set %d hits %s, AND %s\n",j,psm(s,&sets[j].i_sm),psm(s,&tsm)); + } + /* If we can't use an existing set, create a new one */ + if (j >= nsets) { + if (nsets >= _nsets) { + _nsets = 2 * _nsets + 5; + if ((sets = (surfcomb *)realloc(sets, sizeof(surfcomb) * _nsets)) == NULL) + error("malloc failed on disjoint sets size %d", _nsets); + } + sm_cp(s, &sets[j].i_sm, &s->sc[co].i_sm); /* Hits to this set */ + sets[j].ds = &s->sc[co]; /* Only entry in circular list */ +//printf("New set %d hits %s\n",j,psm(s,&sets[j].i_sm)); + nsets++; + k++; + } + } + } + + if (sets != NULL) + free(sets); + +#ifdef DEBUG + printf("Total number of setmask disjoint sets = %d\n",k); +#endif + + /* Setup the setmask params */ + sm_init(s, k); + + /* Assign the individual setmask bits */ + for (i = co = 0; co < (1 << s->nbp); co++) { + if (s->sc[co].valid == 0 || s->sc[co].smset == 1) + continue; + +//printf("~1 setting mask bit on comb 0x%x and its set\n",co); + /* Assign setmask to all in this set */ + scp = &s->sc[co]; + do { +//printf("~1 setting mask bit %d on comb 0x%x\n",i,scp->co); + sm_set(s, &scp->i_sm, 0); /* Clear temporary hit mask */ + sm_setbit(s, &scp->i_sm, i, 1); + scp->smset = 1; + scp = scp->ds; + } while(scp != &s->sc[co]); + i++; + } + + } +#else /* !USE_DISJOINT_SETMASKS */ + + /* Count the number of valid combinations */ + for (i = co = 0; co < (1 << s->nbp); co++) { + if (s->sc[co].valid == 0) + continue; + i++; + } +#ifdef DEBUG + printf("Total number of setmask sets = %d\n",i); +#endif + + /* Setup the setmask params */ + sm_init(s, i); + + /* Assign the individual setmask bits */ + for (i = co = 0; co < (1 << s->nbp); co++) { + if (s->sc[co].valid == 0) + continue; + sm_setbit(s, &s->sc[co].i_sm, i, 1); + i++; + } +#endif /* !USE_DISJOINT_SETMASKS */ + + sm_set(s, &acm, 0); /* Init overall accumulated mask */ + + /* Compute the accumulated setmask bits */ + for (i = 0; i < (1 << s->nbp); i++) { + if (s->sc[i].valid == 0) + continue; + for (j = 0; j < (1 << s->nbp); j++) { + if ((i & j) != j || s->sc[j].valid == 0) + continue; + sm_or(s, &s->sc[i].a_sm, &s->sc[i].a_sm, &s->sc[j].i_sm); + } + sm_or(s, &acm, &acm, &s->sc[i].i_sm); + } + + /* Set special "all planes, all valid" combination as the last */ + /* entry for use by fake surface nodes. */ + s->sc[(1 << s->nbp)-1].valid = 1; + s->sc[(1 << s->nbp)-1].nos = s->nbp; + sm_cp(s, &s->sc[(1 << s->nbp)-1].i_sm, &acm); + sm_cp(s, &s->sc[(1 << s->nbp)-1].a_sm, &acm); + s->sc[(1 << s->nbp)-1].smset = 1; + s->sc[(1 << s->nbp)-1].ds = NULL; + +#ifdef MAXINDEP_2D + /* Go through the combinations and invalidate any */ + /* that are not full-d or more than 2D */ + for (co = 0; co < (1 << s->nbp); co++) { + if (s->sc[co].valid) { +// if (s->sc[co].nos != 0 && (di - s->sc[co].nos) > 1) // test in 3D + if (s->sc[co].nos != 0 && (di - s->sc[co].nos) > 2) + s->sc[co].valid = 0; + } + } +#endif /* MAXINDEP_2D */ + +#ifdef DEBUG + /* Print diagnostics */ + for (i = 0; i < (1 << s->nbp); i++) { + if (s->sc[i].valid == 0) + continue; + printf(" Mask 0x%x, setmasks i = %s, a = %s\n",i,psm(s,&s->sc[i].i_sm),psm(s,&s->sc[i].a_sm)); + } +#endif + + s->sminit = 1; +} +/* --------------------------------------------------- */ + +/* Compute a simple but unbounded model of the */ +/* perceptual function. We use the current vertex values */ +/* to setup the model */ +/* (It would be faster to do the optimization per output channel!) */ + +/* Matrix optimisation function handed to powell() */ +static double xfitfunc(void *edata, double *x) { + ofps *s = (ofps *)edata; + int e, di = s->di; + double rv = 0.0; + vtx *vx; + + /* For all the vertexes */ + for (vx = s->uvtx; vx != NULL; vx = vx->link) { + double v[MXPD], ev; + + /* Apply matrix cube interpolation */ + icxCubeInterp(x, di, di, v, vx->p); + + /* Evaluate the error */ + for (ev = 0.0, e = 0; e < di; e++) { + double tt; + tt = vx->v[e] - v[e]; + ev += tt * tt; + } + rv += ev; + } + +// printf("~1 rv = %f\n",rv); + + return rv; +} + +/* Fit the unbounded perceptual model to just the inside vertexes */ +static void init_pmod(ofps *s) { + int e, di = s->di; + double sa[MXPD * (1 << MXPD)]; + double rerr; + + /* Setup matrix to be closest values initially */ + for (e = 0; e < (1 << di); e++) { /* For each colorant combination */ + int j, f; + double bdif = 1e6; + double ov[MXPD]; + vtx *vx, *bvx = NULL; + + /* Search the vertex list to find the one closest to this input combination */ + for (vx = s->uvtx; vx != NULL; vx = vx->link) { + double dif = 0.0; + + if (vx->ofake) + continue; /* Ignore outside vertexes */ + + for (j = 0; j < di; j++) { + double tt; + if (e & (1 << j)) + tt = s->imax[j] - vx->p[j]; + else + tt = s->imin[j] - vx->p[j]; + dif += tt * tt; + } + if (dif < bdif) { /* best so far */ + bdif = dif; + bvx = vx; + if (dif < 0.001) + break; /* Don't bother looking further */ + } + } + for (f = 0; f < di; f++) + s->pmod[f * (1 << di) + e] = bvx->v[f]; + } + + for (e = 0; e < (di * (1 << di)); e++) + sa[e] = 10.0; + + if (powell(&rerr, di * (1 << di), s->pmod, sa, 0.001, 1000, + xfitfunc, (void *)s, NULL, NULL) != 0) { + if (s->verb > 1) + warning("Powell failed to converge, residual error = %f",rerr); + } + +#ifdef DEBUG + printf("Perceptual model fit residual = %f\n",sqrt(rerr)); +#endif + s->pmod_init = 1; +} + +/* --------------------------------------------------- */ +/* Init fake node contents, and setup the initial */ +/* voronoi surface with the first node. */ +static void ofps_binit(ofps *s) { + int e, di = s->di; + int doink = 0; + int i, j; + DCOUNT(co, MXPD, di, 0, 0, 2); /* Count through corner verticies */ + int iix = -2 * di - 2; /* Fake inside node index */ + node *inp = s->n[iix]; /* Fake inside node */ + int oix = -2 * di - 3; /* Fake outside node index */ + node *onp = s->n[oix]; /* Fake outside node */ + double ivtx_whts[] = { /* Initial vertex weightings */ + 3.7144267283692024e+165, + 1.3997102851752585e-152, + 6.1677886722367450e+223, + 1.7281009363426126e+097, + 2.0087766625640005e-139, + 4.9406564584124654e-323, + 7.7791723264315535e-260, + 8.5733372291341995e+170, + 6.0046007797559735e-067, + 2.8214561724952793e+243, + 5.0132438738338732e+262, + 1.6259745436952323e-260, + 7.9968034958246946e+001 + }; + double vtxwt; /* Combined initial vertexnode weighting */ + unsigned int fullmask = 0; + +#ifdef DEBUG + printf("Binit called\n"); +#endif + if (s->ilimit < (double)di) /* Ink limit is active */ + doink = 1; + + /* Init fake inside and outside node */ + inp->ix = iix; + inp->fx = 1; + inp->pmask = 0; + onp->ix = oix; + onp->fx = 1; + onp->pmask = 0; + + for (i = 0; i < (2 * di); i++) + fullmask |= 1 << i; + if (doink) + fullmask |= 1 << i; + + /* Init the axis aligned gamut surface plane equations */ + /* and also setup nodes that are indexes by the fake indexes */ + /* with just the information that will be used. */ + for (i = 0; i < (2 * di); i++) { /* unit cell at 0 */ + int ii = i >> 1; /* Dimension */ + int ix; /* Surface "node" index */ + pleq *vp; /* plane being initialized */ + node *np; + + ix = -i-1; /* -1 to -2di fake other nodes */ + vp = &s->gpeqs[-1-ix]; /* Pointer to plane associated with fake node */ + vp->ix = ix; + + for (e = 0; e < di; e++) + vp->pe[e] = 0.0; + vp->pe[ii] = i & 1 ? 1.0 : -1.0; /* Normal */ + vp->pe[di] = i & 1 ? -s->imax[ii] : s->imin[ii]; /* Constant */ + + np = s->n[ix]; + np->ix = ix; + np->fx = 1; /* They don't move */ + np->nsp = 1; + np->sp[0] = vp; + np->pmask = fullmask; +// np->pmask = 1 << i; /* fake surface node pmask is itself for cmask ?? */ +// onp->pmask = inp->pmask |= np->pmask; + } + s->nbp = 2 * di; /* Number of boundary planes */ + + /* Add ink limit surface plane and its fake node */ + if (doink) { /* Ink limit plane is orthogonal to diagonal */ + int ix; /* Surface "node" index */ + pleq *vp; /* plane being initialized */ + node *np; + double len; + + ix = -i-1; /* -1 to -2di fake other nodes */ + vp = &s->gpeqs[-1-ix]; /* Pointer to plane associated with fake node */ + vp->ix = ix; + len = 1.0/sqrt((double)di); /* Normalised length */ + for (e = 0; e < di; e++) + vp->pe[e] = len; + vp->pe[di] = -s->ilimit * len; + + np = s->n[ix]; + np->ix = ix; + np->fx = 1; /* They don't move */ + np->nsp = 1; + np->sp[0] = vp; + np->pmask = fullmask; +// np->pmask = 1 << i; /* fake surface node pmask is itself for cmask ?? */ +// onp->pmask = inp->pmask |= np->pmask; + s->nbp++; /* Number of boundary planes */ + } else { + s->n[-i-1]->ix = -i-1; /* Label unused node */ + } + +#ifdef DEBUG + printf("Number of boundary planes = %d\nDiscovering all valid veronoi sub-surfaces\n",s->nbp); +#endif + + discover_subsuf(s); + +#ifdef DEBUG + printf("Creating rectangular initial vertexes\n"); +#endif + + /* Compute initial node weighting */ + for (vtxwt = 0.0, i = 0; i < (sizeof(ivtx_whts)/sizeof(double)-1); i++) + vtxwt += log(ivtx_whts[i]); + vtxwt += ivtx_whts[i]; + + /* Create initial verticies, one for each di combination of planes, */ + /* and keep the ones that are in gamut. */ + DC_INIT(co); + while(!DC_DONE(co)) { + double p[MXPD]; + vtx *vi, *vo; /* Inside and outside vertex */ + + /* Compute vertex location */ + for (e = 0; e < di; e++) { + if (co[e] != 0) + p[e] = s->imax[e]; + else + p[e] = s->imin[e]; + } + + if (ofps_would_clip_point(s, p)) { +#ifdef DEBUG + printf("Position %s rejected, out of gamut\n",ppos(di,p)); +#endif + goto next_co; + } +#ifdef DEBUG + printf("Position %s accepted\n",ppos(di,p)); +#endif + + vi = new_vtx(s); + vo = new_vtx(s); + + for (e = 0; e < di; e++) + vi->p[e] = vtxwt * p[e]; + ofps_cc_percept(s, vi->v, vi->p); + + for (e = 0; e < di; e++) + vo->p[e] = (10.0 * (p[e] - 0.5)) + 0.5; +// ofps_cc_percept(s, vo->v, vo->p); + + /* Compute nodes involved */ + for (e = 0; e < di; e++) { + if (co[e] == 0) { + vo->nix[e] = vi->nix[e] = -1 - (2 * e + 0); + } else { + vo->nix[e] = vi->nix[e] = -1 - (2 * e + 1); + } + } + + vi->nix[di] = iix; /* First nodee */ +#ifdef DEBUG + printf("ivertex nix %s\n",pcomb(di,vi->nix)); +#endif + sort_nix(s, vi->nix); + vi->eperr = 10000.0; /* Very bad, so they get chosen first */ + vi->eserr = 10000.0; + + det_vtx_gsurf(s, vi); /* Set pmask & cmask */ + sm_cp(s, &vi->vm, &s->sc[vi->cmask].a_sm); /* Set visibility */ + + vi->ifake = 1; /* Inside fake */ + + vtx_cache_add(s, vi); /* Add it to the vertex cache and spatial accelleration grid */ + ofps_add_vacc(s, vi); + ofps_add_vseed(s, vi); + + vo->nix[di] = oix; /* Fake outside node */ +#ifdef DEBUG + printf("overtex nix %s\n",pcomb(di,vo->nix)); +#endif + sort_nix(s, vo->nix); + vo->eperr = vtxwt * -9.0; /* Better than zero error */ + vo->eserr = vtxwt * -9.0; + /* Leave pmask,cmask = 0 */ + vo->pmask = vi->pmask; /* Copy from inner vertexes */ + vo->cmask = vi->cmask; + sm_cp(s, &vo->vm, &s->sc[vo->cmask].a_sm); /* Set visibility */ + + vo->ofake = 1; /* Outside fake - don't plot vnets and don't use */ + /* for perceptual function extension. */ + vo->used = 1; /* Not a candidate for seeding */ + + next_co:; + DC_INC(co); + } + + /* Add ink limit vertexes */ + if (doink) { /* Ink limit plane is orthogonal to diagonal */ + COMBO(nco, MXPD, di-1, s->nbp-1); /* di-1 out of neighbor nodes combination counter */ + +#ifdef DEBUG + printf("Creating ink limit vertexes\n"); +#endif + /* Intersect the ink limit plane with each combination of */ + /* it and and di-1 of the existing planes, to generate */ + /* potential vertexes, and keep the ones that are in gamut. */ + CB_INIT(nco); + while (!CB_DONE(nco)) { + pleq *peqs[MXPD]; + double p[MXPD]; + + for (e = 0; e < (di-1); e++) { + peqs[e] = &s->gpeqs[nco[e]]; + } + peqs[e] = &s->gpeqs[2 * di]; + + /* Compute device location of intersection */ + if (comp_vtx(s, p, peqs) == 0) { + vtx *vi, *vo; /* Inside and outside vertex */ + + if (ofps_would_clip_point(s, p)) { +#ifdef DEBUG + printf("Position %s rejected, out of gamut\n",ppos(di,p)); +#endif + goto next_nco; + } +#ifdef DEBUG + printf("Position %s accepted\n",ppos(di,p)); +#endif + + vi = new_vtx(s); + vo = new_vtx(s); + + /* Device and perceptual */ + for (e = 0; e < di; e++) + vi->p[e] = vtxwt * p[e]; + ofps_cc_percept(s, vi->v, vi->p); + + for (e = 0; e < di; e++) + vo->p[e] = (10.0 * (p[e] - 0.5)) + 0.5; +// ofps_cc_percept(s, vo->v, vo->p); + + for (e = 0; e < (di-1); e++) + vo->nix[e] = vi->nix[e] = -1-nco[e]; /* Fake gamut surface plane nodes */ + vo->nix[e] = vi->nix[e] = -2 * di -1; /* Fake ink limit node */ + + vi->nix[di] = iix; /* First node */ +#ifdef DEBUG + printf("ivertex nix %s\n",pcomb(di,vi->nix)); +#endif + sort_nix(s, vi->nix); + vi->eperr = 10000.0; /* Very bad */ + vi->eserr = 10000.0; + + det_vtx_gsurf(s, vi); /* Set pmask & cmask */ + sm_cp(s, &vi->vm, &s->sc[vi->cmask].a_sm); /* Set visibility */ + + vi->ifake = 1; /* Inside fake */ + + vtx_cache_add(s, vi); /* Add to vertex cache and spatial accelleration grid */ + ofps_add_vacc(s, vi); + ofps_add_vseed(s, vi); + + vo->nix[di] = oix; /* Fake outside node */ +#ifdef DEBUG + printf("overtex nix %s\n",pcomb(di,vo->nix)); +#endif + sort_nix(s, vo->nix); + vo->eperr = vtxwt * -9.0; /* Better than zero error */ + vo->eserr = vtxwt * -9.0; + /* Leave pmask,cmask = 0 */ + vo->pmask = vi->pmask; /* Copy from inner vertexes */ + vo->cmask = vi->cmask; + sm_cp(s, &vo->vm, &s->sc[vo->cmask].a_sm); /* Set visibility */ + + vo->ofake = 1; /* Outside fake - don't plot vnets and don't use */ + /* for perceptual function extension. */ + vo->used = 1; /* Not a candidate for seeding */ + } + next_nco:; + CB_INC(nco); + } /* Next combination */ + } + + /* Create an initial vertex network */ + create_vtx_net(s); + + /* Fit the unbounded perceptual model to just the inside vertexes */ + if (s->pmod_init == 0) + init_pmod(s); + + /* Compute the nodes node and vertex lists */ + ofps_re_create_node_node_vtx_lists(s); + +#ifdef DUMP_STRUCTURE + printf("Done binit\n"); + dump_node_vtxs(s, 1); +// dump_node_vtxs2(s, "Done binit"); + printf("=========================================================================\n"); +#endif +#ifdef DUMP_PLOT_SEED + dump_image(s, PERC_PLOT, DO_WAIT, DUMP_VTX, DUMP_PLA, 0, -1); /* Device, No wait, verticies */ +#endif /* DUMP_PLOT_SEED */ +} + +/* --------------------------------------------------- */ +/* Setup the perceptual lookup cache */ +static void +ofps_init_pcache(ofps *s) { + int i, e; + int di = s->di; + int gr, gres[MXPD]; + int tinp = s->tinp; + +#ifdef DEBUG + printf("Initializing perceptual lookup cache\n"); +#endif + + /* Choose a grid resolution that aims for aproximately TNPAGRID nodes per grid */ + if (tinp > 10000) + tinp = 10000; + gr = (int)(pow(tinp/TNPAGRID, 1.0/di) + 0.5); + gr |= 1; /* make it odd */ + + if (gr < TNPAGRIDMINRES) + gr = TNPAGRIDMINRES; + if (gr > TNPAGRIDMAXRES) + gr = TNPAGRIDMAXRES; + + if (s->verb) + printf("Perceptual cache resolution = %d\n",gr); + +#ifdef DEBUG + printf("Perceptual cache resolution = %d\n",gr); +#endif + + /* Create a rspl to cache the perceptual lookup */ + + if ((s->pcache = new_rspl(RSPL_NOFLAGS, s->di, s->di)) == NULL) + error("new_rspl failed"); + + for (e = 0; e < di; e++) + gres[e] = gr; + + s->pcache->set_rspl(s->pcache, RSPL_SET_APXLS, s->od, s->percept, NULL, NULL, gres, NULL, NULL); + + /* Hmm. Should we store the underlying ->percept & ->od somewhere before we overwrite it ? */ + s->percept = ofps_cache_percept; + s->od = s->pcache; + +} + +/* --------------------------------------------------- */ +/* Setup the acceleration grid structure and perceptual cache. */ +/* The grid is in device space, although it is used to find the point */ +/* with the smallest eperr. */ +/* (Note that ofps_cc_percept() can't be called on clipped values yet) */ +static void +ofps_init_acc1(ofps *s) { + int i, e; + int di = s->di; + int gres[MXPD]; + int tinp = s->tinp; + +#ifdef DEBUG + printf("Initializing accelleration array (1)\n"); +#endif + + /* Create acceleration grid array */ + + /* Choose a grid resolution that aims for aproximately TNPAGRID nodes per grid */ + if (tinp > 10000) + tinp = 10000; + s->agres = (int)(pow(tinp/TNPAGRID, 1.0/di) + 0.5); + if (s->agres < 1) + s->agres = 1; + + if (s->verb) + printf("Acceleration grid res = %d\n",s->agres); + +#ifdef DEBUG + printf("Acceleration grid res = %d\n",s->agres); +#endif + + /* Cell width in grid units */ + s->gw = 1.0/s->agres; + + /* Compute grid index multipliers */ + /* (We allocate an two extra rows for boundary cells to be looked up.) */ + for (s->gim[0] = 1, e = 1; e < di; s->gim[e] = s->gim[e-1] * (s->agres+2), e++) + ; + + /* Compute cell diagonal distance */ + s->gcd = sqrt((double)di * s->gw * s->gw); + + /* Compute number of cells in grid (with two extra rows) */ + for (s->nig = 1, e = 0; e < di; e++) + s->nig *= (s->agres+2); + + /* Allocate grid (with two extra rows) */ + if ((s->_grid = (acell *)malloc(sizeof(acell) * s->nig)) == NULL) + error ("ofps: malloc failed for acceleration grid"); + + /* Set pointer to base of grid without extra row */ + for (s->grid = s->_grid, e = 0; e < di; e++) + s->grid += s->gim[e]; + + /* Initialise grid (including extra gruard rows) */ + { + DCOUNT(co, MXPD, di, -1, -1, (s->agres+1)); + + i = 0; + DC_INIT(co); + while (!DC_DONE(co)) { + acell *cp = &s->_grid[i]; + unsigned int gflag = 0; + + for (e = 0; e < di; e++) { + if (co[e] < 0 || co[e] >= s->agres) + gflag = BOUND_GFLAG; + cp->co[e] = co[e]; /* Grid coordinate of base of cell */ + cp->p[e] = co[e] * s->gw; /* Device coord of base of cell */ + cp->cp[e] = (co[e] + 0.5) * s->gw; /* Device coord of center of cell */ + } + + cp->gflag = gflag; + cp->head = NULL; + cp->vhead = NULL; + + DC_INC(co); + i++; + } + } + s->gflag = 0; + + /* Create the neighbour offset list */ + + /* There are 3^di -1 neighbours for each cell */ + for (s->nacnl = 1, e = 0; e < di; s->nacnl *= 3, e++) + ; + s->nacnl--; + + if ((s->acnl = (int *)malloc(sizeof(int) * s->nacnl)) == NULL) + error ("ofps: malloc failed on acnl list"); + + /* Initialise list from cube */ + { + DCOUNT(co, MXPD, di, -1, -1, 2); + + i = 0; + DC_INIT(co); + while (!DC_DONE(co)) { + + /* check we're not at the center cell */ + for (e = 0; e < di; e++) { + if (co[e] != 0) + break; + } + if (e < di) { /* Not center cell */ + /* Compute offset */ + for (s->acnl[i] = 0, e = 0; e < di; e++) { + s->acnl[i] += co[e] * s->gim[e]; + } +//printf("~1 acnl[%d] for co %s = %d\n",i,pco(di,co),s->acnl[i]); + i++; + } + DC_INC(co); + } + } +} + +/* Init the grid location p[] and v[] values */ +static void +ofps_init_acc2(ofps *s) { + int i, e, di = s->di; + int k; + DCOUNT(co, MXPD, di, 0, 0, 2); + double maxratio, avgratio, noratio; + double aitters = 0.0; + +#ifdef DEBUG + printf("Initializing accelleration array (2)\n"); +#endif + + for (i = 0; i < s->nig; i++) { + acell *cp = &s->_grid[i]; + + /* Lookup perceptual base and center values */ + ofps_cc_percept(s, cp->v, cp->p); + ofps_cc_percept(s, cp->cv, cp->cp); + } + + /* Compute the worst case eperr from a corner to the center */ + maxratio = -1.0; + avgratio = noratio = 0.0; + for (i = 0; i < s->nig; i++) { + acell *cp = &s->_grid[i]; + double ratio; + double mov[MXPD]; + + if (cp->gflag == BOUND_GFLAG) + continue; + +#define ACELITERS 20 + for (k = 0; ; k++) { + double eperr_avg, eperr_min, eperr_max, no; + cp->eperr = 0.0; + + DC_INIT(co); + eperr_avg = no = 0.0; + eperr_min = 1e300; + eperr_max = -1.0; + while (!DC_DONE(co)) { + acell *np = &s->_grid[i]; + double eperr; + int j; + + /* Locate cell corner */ + for (j = 0, e = 0; e < di; e++) + j += co[e] * s->gim[e]; + np = &s->_grid[i + j]; + + /* eperr from that corner to center of this cell */ + eperr = ofps_comp_eperr(s, NULL, cp->cv, cp->cp, np->v, np->p); + eperr_avg += eperr; + if (eperr > eperr_max) + eperr_max = eperr; + if (eperr < eperr_min) + eperr_min = eperr; + + no++; + + if (eperr > cp->eperr) + cp->eperr = eperr; + + DC_INC(co); + } + eperr_avg /= no; + + ratio = eperr_max/eperr_min; + + if (k >= ACELITERS || ratio < 1.2) { + avgratio += ratio; + noratio++; + if (ratio > maxratio) + maxratio = ratio; + + break; + + } else { + + /* Adjust the center position to minimuze range of eperr's */ + for (e = 0; e < di; e++) + mov[e] = 0.0; + +//printf("~1 cp was at %s\n",ppos(di,cp->cp)); + DC_INIT(co); + while (!DC_DONE(co)) { + acell *np = &s->_grid[i]; + double eperr, wf; + int j; + + /* Locate cell corner */ + for (j = 0, e = 0; e < di; e++) + j += co[e] * s->gim[e]; + np = &s->_grid[i + j]; + + /* Compose new center point from weighted corner points. */ + /* Weighting is proportional to eperr value */ + eperr = ofps_comp_eperr(s, NULL, cp->cv, cp->cp, np->v, np->p); + + if (eperr < eperr_avg) { + /* Move away from corner */ + wf = (eperr_avg - eperr)/eperr_avg; +//printf("~1 eperr %f, avg %f, min %f, wf %f\n",eperr,eperr_avg,eperr_min,wf); + for (e = 0; e < di; e++) + mov[e] += wf * (cp->cp[e] - np->p[e]); + } else { + /* Move towards corner */ + wf = (eperr - eperr_avg)/eperr_avg; +//printf("~1 eperr %f, avg %f, max %f, wf %f\n",eperr,eperr_avg,eperr_max,wf); + for (e = 0; e < di; e++) + mov[e] += wf * (np->p[e] - cp->cp[e]); + } + + DC_INC(co); + } + for (e = 0; e < di; e++) { + mov[e] = 1.2 * mov[e] / no; + cp->cp[e] += mov[e]; + } + ofps_cc_percept(s, cp->cv, cp->cp); +//printf("~1 moving by %s to %s\n",ppos(di,mov),ppos(di,cp->cp)); + } + } + aitters += k; + + cp->eperr *= CELLMAXEPERRFF; /* Times the fudge factor */ + } + aitters /= s->nig; + + avgratio /= noratio; +#ifdef DEBUG + printf("Average acell eperr ratio = %f, maximum = %f, avg itters %f\n",avgratio,maxratio,aitters); + +#endif + + s->agrid_init = 1; +} + +/* Convert a location into an acceleration cell index */ +static int +ofps_point2cell(ofps *s, double *v, double *p) { + int i, e, di = s->di; + int agres = s->agres; + double pp[MXPD]; + + ofps_clip_point(s, pp, p); + + for (i = e = 0; e < di; e++) { + int t; + t = (int)floor(agres * pp[e]); + if (t < 0) + t = 0; + else if (t >= agres) + t = (agres-1); + i += s->gim[e] * t; + } + return i; +} + +/* Diagnostic: Return the grid coordinates */ +static void ofps_gridcoords(ofps *s, int *c, double *v, double *p) { + int i, e, di = s->di; + int agres = s->agres; + double pp[MXPD]; + + ofps_clip_point(s, pp, p); + + for (i = e = 0; e < di; e++) { + int t; + c[e] = (int)floor(agres * pp[e]); + if (c[e] < 0) + c[e] = 0; + else if (c[e] >= agres) + c[e] = (agres-1); + } +} + +/* Add a node to the spatial acceleration grid */ +/* Note that little more than the node perceptual value */ +/* may be valid when this is called. */ +static void +ofps_add_nacc(ofps *s, node *n) { + int pci; + acell *cp; + + if (n->ix < 0) + return; + + pci = ofps_point2cell(s, n->v, n->p); + cp = &s->grid[pci]; + n->n = cp->head; + if (cp->head != NULL) + cp->head->pn = &n->n; + cp->head = n; + n->pn = &cp->head; + n->pci = pci; + n->cell = cp; + +#ifdef SANITY_CHECK_CLOSEST + if (s->agrid_init) { + double eperr; + /* Check that the eperr to the center of the cell */ + /* is less than the worst case for that cell */ + eperr = ofps_comp_eperr(s, NULL, cp->cv, cp->cp, n->v, n->p); + if (eperr > cp->eperr) { + warning("Sanity check ofps_add_nacc() node ix %d eperr %f > cell eperr %f",n->ix,eperr,cp->eperr); + printf("Sanity check ofps_add_nacc() node ix %d eperr %f > cell eperr %f\n",n->ix,eperr,cp->eperr); +#ifdef SANITY_CHECK_CLOSEST_FATAL + error("ofps_add_nacc cell eperr failed"); +#endif + } + } +#endif /* SANITY_CHECK_CLOSEST */ +} + +/* Remove a node from the spatial acceleration grid */ +static void +ofps_rem_nacc(ofps *s, node *n) { + if (n->ix < 0) + return; + if (n->pn != NULL) { /* If is on acceleration list, remove it */ + *n->pn = n->n; + if (n->n != NULL) + n->n->pn = n->pn; + } + n->pn = NULL; + n->n = NULL; +} + +/* Add a vertex to the spatial acceleration grid */ +static void +ofps_add_vacc(ofps *s, vtx *vx) { + int pci; + acell *cp; + + /* Normal spatial acceleration grid */ + pci = ofps_point2cell(s, vx->v, vx->p); + cp = &s->grid[pci]; + vx->n = cp->vhead; + if (cp->vhead != NULL) + cp->vhead->pn = &vx->n; + cp->vhead = vx; + vx->pn = &cp->vhead; + vx->pci = pci; + +#ifdef DEBUG + printf("Adding vertex no %d to spatial accelleration grid in cell %d\n",vx->no,pci); +#endif + +#ifdef SANITY_CHECK_CLOSEST + if (s->agrid_init) { + int e, di = s->di; + double eperr; + double p[MXPD], v[MXPD]; + + /* Check that the eperr to the center of the cell */ + /* is less than the worst case for that cell */ + + /* Clip point in case it lies outside the grid, */ + /* and would give an excessive eperr */ + for (e = 0; e < di; e++) { + p[e] = vx->p[e]; + if (p[e] < 0.0) + p[e] = 0.0; + else if (p[e] > 1.0) + p[e] = 1.0; + } + ofps_cc_percept(s, v, p); + eperr = ofps_comp_eperr(s, NULL, cp->cv, cp->cp, v, p); + + if (eperr > cp->eperr) { + +//printf("~1 Cell ix %d co %s center %s (%s), vtx at %s (%s) clipped to %s (%s)\n",pci,pco(s->di,cp->co),ppos(di,cp->cp),ppos(di,cp->cv),ppos(di,vx->p),ppos(di,vx->v),ppos(di,p),ppos(di,v)); + warning("Sanity check ofps_add_vacc() vtx no %d eperr %f > cell eperr %f",vx->no,eperr,cp->eperr); + printf("Sanity check ofps_add_vacc() vtx no %d eperr %f > cell eperr %f\n",vx->no,eperr,cp->eperr); + +#ifdef SANITY_CHECK_CLOSEST_FATAL + error("ofps_add_vacc cell eperr failed"); +#endif + } + } +#endif /* SANITY_CHECK_CLOSEST */ +} + +/* Add a vertex to the seeding groups */ +static void +ofps_add_vseed(ofps *s, vtx *vx) { + double oog; + int pci; + acell *cp; + +#ifdef DEBUG + printf("Adding vertex no %d to sorted binary tree\n",vx->no); +#endif + + /* Add the vertex to the sorted binary trees */ + if ((aat_ainsert(s->vtreep, (void *)vx)) == 0) + error("aat_ainsert vertex malloc failed"); + + /* Out of gamut vertexes are not candidates for seeds */ + if ((oog = ofps_oog(s, vx->p)) > COINTOL) { + vx->used = 1; +//printf("Setting used on vtx no %d, used %d, eserr %f, vm %s nsp %d oog by %e\n",vx->no,vx->used,vx->eserr,psm(s,&vx->vm),vx->nsp, oog); + } + + if (vx->used == 0) { +#ifdef INDEP_SURFACE + /* Only pick full dimensional visible vertexes for seeding group, */ + /* since only they have a full-d error value. */ + if (sm_andtest(s, &s->sc[0].a_sm, &vx->vm) != 0) { +#endif +//printf("Adding (3) vtx no %d, used %d, eserr %f, vm %s nsp %d\n",vx->no,vx->used,vx->eserr,psm(s,&vx->vm),vx->nsp); + if ((aat_ainsert(s->vtrees[vx->nsp], (void *)vx)) == 0) + error("aat_ainsert vertex malloc failed"); +#ifdef INDEP_SURFACE + } else { +//printf("Not adding (2) vtx no %d, used %d, eserr %f, vm %s nsp %d\n",vx->no,vx->used,vx->eserr,psm(s,&vx->vm),vx->nsp); + } +#endif + } +//else printf("Not adding (3) vtx no %d, used %d, eserr %f, vm %s nsp %d\n",vx->no,vx->used,vx->eserr,psm(s,&vx->vm),vx->nsp); +} + +/* Remove a vertex from the seeding groups */ +static void +ofps_rem_vseed(ofps *s, vtx *vx) { + +#ifdef DEBUG + printf("Removing vertex no %d from sorted binary tree\n",vx->no); +#endif + + /* Remove the vertex from the sorted binary tree */ + if ((aat_aerase(s->vtreep, (void *)vx)) == 0) + error("aat_aerase vertex failed to find vertex no %d (3)", vx->no); + + if (vx->used == 0) { +#ifdef INDEP_SURFACE + /* Only pick full dimensional visible vertexes for seeding group, */ + /* since only they have a full-d error value. */ + if (sm_andtest(s, &s->sc[0].a_sm, &vx->vm) != 0) { +#endif +//printf("Removing (4) vtx no %d, 0x%x, used %d, eserr %f, vm %s nsp %d\n",vx->no,vx, vx->used,vx->eserr,psm(s,&vx->vm),vx->nsp); + if ((aat_aerase(s->vtrees[vx->nsp], (void *)vx)) == 0) + error("aat_aerase vertex failed to find vertex no %d (4)", vx->no); +#ifdef INDEP_SURFACE + } else { +//printf("Not removing (1) vtx no %d, 0x%x, used %d, eserr %f, vm %s nsp %d\n",vx->no,vx,vx->used,vx->eserr,psm(s,&vx->vm),vx->nsp); + } +#endif + } +// else printf("Not removing (2) vtx no %d, 0x%x, used %d, eserr %f, vm %s nsp %d\n",vx->no,vx,vx->used,vx->eserr,psm(s,&vx->vm),vx->nsp); +} + +/* Remove a vertex from the spatial acceleration grid */ +static void +ofps_rem_vacc(ofps *s, vtx *vx) { + + /* Remove from spatial acceleration grid */ + if (vx->pn != NULL) { /* If is on acceleration list, remove it */ + *vx->pn = vx->n; + if (vx->n != NULL) + vx->n->pn = vx->pn; + } + vx->pn = NULL; + vx->n = NULL; +} + +/* Clear the spatial acceleration grid */ +static void +ofps_reset_acc(ofps *s) { + int i; + + for (i = 0; i < s->nig; i++) { + acell *cp = &s->_grid[i]; + if (cp->gflag != BOUND_GFLAG) + cp->gflag = 0; + cp->head = NULL; + cp->vhead = NULL; + } + s->gflag = 0; +} + +/* --------------------------------------------------- */ + +/* Creat a randomized order list of pointers to the fixed points */ +static void +ofps_setup_fixed( +ofps *s, +fxpos *fxlist, /* List of existing fixed points */ +int fxno /* Number in fixed list */ +) { + int e, di = s->di; + int i, j; + + s->fnp = 0; + if (fxno == 0) + return; + + /* Allocate a list of pointers sufficient for all the fixed points */ + if ((s->ufx = (fxpos **)calloc(sizeof(fxpos *), fxno)) == NULL) + error ("ofps: malloc failed on pointers to fixed list"); + + /* Add each fixed point to the list */ + for (i = 0; i < fxno; i++) { + + /* Clip the fixed point */ + ofps_clip_point7(s, fxlist[i].p, fxlist[i].p); + + /* Comute perceptual attributes */ + s->percept(s->od, fxlist[i].v, fxlist[i].p); + + /* Skip any duplicate points, or Voronoi will get confused.. */ + for (j = 0; j < s->fnp; j++) { + for (e = 0; e < di; e++) { + if (fabs(s->ufx[j]->p[e] - fxlist[i].p[e]) > 1e-5) + break; /* Not a match */ + } + if (e >= di) + break; /* Is a match */ + } + if (j < s->fnp) + continue; /* Skip adding this point */ + + s->ufx[s->fnp++] = &fxlist[i]; + } + + /* Randomly shuffle the fixed points */ + for (i = 0; i < s->fnp; i++) { + fxpos *tp; + + j = i_rand(0, s->fnp-1); + + /* Swap the pointers */ + tp = s->ufx[i]; + s->ufx[i] = s->ufx[j]; + s->ufx[j] = tp; + } + s->tinp -= (fxno - s->fnp); + +} + +/* Seed the object with any fixed points */ +/* (I think this is only used if ofps is used to check the stats */ +/* on all the points. ) */ +/* Return NZ on failure */ +static int +ofps_add_fixed( +ofps *s +) { + int e, di = s->di; + int i, j, ii; + + /* Add fixed points if there are any */ + if (s->fnp == 0) + return 0; + + if (s->verb) + printf("Adding %d unique fixed points\n",s->fnp); + + for (i = 0; i < s->fnp; i++) { + node *p = s->n[i]; /* Destination for point */ + + /* Make sure that fixed point is within our gamut */ + ofps_clip_point(s, s->ufx[i]->p, s->ufx[i]->p); + + for (e = 0; e < di; e++) { /* copy device and perceptual coords */ + p->op[e] = p->p[e] = s->ufx[i]->p[e]; + p->v[e] = s->ufx[i]->v[e]; + } + + /* Count gamut surface planes it lies on */ + det_node_gsurf(s, p, p->p); + + p->fx = 1; /* is a fixed point */ + + /* Compute the Voronoi for it, and inc s->np */ + if (add_node2voronoi(s, s->np, 1)) { + /* In theory we could try adding points in a different order, */ + /* by resetting the voronoi, shuffling all the fixedpoints */ + /* and re-adding them again. */ + warning("Adding a fixed point failed to hit any vertexes, and no points to swap with!"); + return 1; + } + + if (s->verb) + printf("%cAdded fixed %d/%d",cr_char,i,s->fnp); fflush(stdout); + +#ifdef DUMP_STRUCTURE + printf("Done node %d\n",s->np); + dump_node_vtxs(s, 0); + printf("=========================================================================\n"); +#endif +#ifdef DUMP_PLOT_SEED + dump_image(s, PERC_PLOT, DO_WAIT, DUMP_VTX, DUMP_PLA, 0, -1); /* Device, No wait, verticies */ +#endif /* DUMP_PLOT_SEED */ + } + + return 0; +} + +/* Seed the object with any fixed and movable incremental farthest points. */ +/* (We are only called if there is at leaset one movable point) */ +static void +ofps_seed(ofps *s) { + int e, di = s->di; + int ii, i, j, k, fc; + double rerr; + int needfirst = 0; /* Need a special first seed point (seems better without this ?) */ + int dofixed = 0; /* Do a fixed point next */ + int abortonfail = 0; /* Abort on failing to add fixed points (isn't always good ?) */ + int nsp = 0; /* Number of surface points */ + aat_atrav_t *aat_tr; + + if (s->verb) + printf("\n"); + + if ((aat_tr = aat_atnew()) == NULL) + error("aat_atnew returned NULL"); + + if (s->verb) { + printf("There are %d unique fixed points to add (%d total fixed points)\n",s->fnp, s->fxno); + printf("There are %d far spread points to add\n",s->tinp - s->fnp); + } + + if (!needfirst && s->fnp > 1) { /* There are fixed points to add */ + dofixed = s->fnp > 2 ? 2 : 1; + } + + /* Seed all the points. */ + /* (i is the node we're creating, j is the verbose interval count, */ + /* fc is the count of the fixed points added, ii is the movable count) */ + for (fc = j = i = ii = 0; i < s->tinp; i++, j++) { + node *p = s->n[i]; /* New node */ + double spref_mult; + + /* Compute current surface preference weighting */ + if (s->tinp - s->fnp <= 1) /* Prevent divide by zero */ + spref_mult = 0.0; + else + spref_mult = ii/(s->tinp - s->fnp - 1.0); + spref_mult = (1.0 - spref_mult) * s->ssurfpref + spref_mult * s->esurfpref; + + if (needfirst) { /* No initial fixed points, so seed the first */ + /* point as a special. */ + double min[MXPD], max[MXPD]; + + p->fx = 0; + + /* If there are no fixed points in the bulk, make the */ + /* first point such a point, to avoid pathology. */ + for (e = 0; e < di; e++) + p->p[e] = s->imin[e] + (s->imax[e] - s->imin[e]) * 1.0/4.141592654; + + /* Clip the new location */ + ofps_clip_point8(s, p->p, p->p); + + s->percept(s->od, p->v, p->p); +#ifdef DEBUG + printf("Creating first seed point (moveable %d out of %d)\n",i+1,s->tinp); +#endif + } else if (dofixed) { /* Setup to add a fixed point */ + +#ifdef DEBUG + printf("Adding fixed point %d out of %d\n",fc+1,s->fnp); +#endif + + /* (Fixed points are already clipped and have perceptual value) */ + for (e = 0; e < di; e++) { /* copy device and perceptual coords */ + p->p[e] = s->ufx[fc]->p[e]; + p->v[e] = s->ufx[fc]->v[e]; + } + + p->fx = 1; /* is a fixed point */ + + /* Count gamut surface planes it lies on */ + if (det_node_gsurf(s, p, p->p) != 0) + nsp++; + + } else { /* Setup to add a movable point */ + int k; + int sf = 0; + + double spref_mult; + double mx; + vtx *vx, *bvx; + double spweight[MXPD+1]; /* Surface preference weight table */ + double bspweight; /* Biggest weight */ + +#ifdef DEBUG + printf("Adding movable point %d out of %d\n",i+1,s->tinp); +#endif + + p->fx = 0; + + /* Compute current surface preference weighting */ + if (s->tinp - s->fnp <= 1) /* Prevent divide by zero */ + spref_mult = 0.0; + else + spref_mult = ii/(s->tinp - s->fnp - 1.0); + spref_mult = (1.0 - spref_mult) * s->ssurfpref + spref_mult * s->esurfpref; + + /* Until we use the next vertex, keep looking for a movable point */ + for (;;) { + +#ifdef NEVER /* DEBUG: Show the contents of each list */ + printf("All vertex list:\n"); + for (vx = s->uvtx; vx != NULL; vx = vx->link) { + printf(" Vtx %d, used %d, eserr %f, weserr %f, vm %s\n",vx->no,vx->used,vx->eserr,vx->eserr * spweight[vx->nsp],psm(s,&vx->vm)); + } + + for (e = 0; e <= (di+1); e++) { + printf("Sorted tree vertex list for nsp %d :\n",e); + + for (vx = aat_atlast(aat_tr, s->vtrees[e]); vx != NULL; vx = aat_atprev(aat_tr)) { + printf(" Vtx %d, used %d, eserr %f, weserr %f, vm %s\n",vx->no,vx->used,vx->eserr,vx->eserr * spweight[vx->nsp],psm(s,&vx->vm)); + } + } +#endif + /* Compute the surface weighting multiplier for */ + /* each possible nsp + 1 */ + spweight[0] = 1.0; + + for (e = 1; e <= di; e++) + spweight[e] = spweight[e-1] * spref_mult; + + /* Locate the Voronoi vertex with the greatest distance to a sampling points */ + for (mx = -1.0, bvx = NULL, e = 0; e <= (di+1); e++) { + double weserr; + + /* Get largest eserr vertex for this nsp */ + if ((vx = aat_atlast(aat_tr, s->vtrees[e])) == NULL) + continue; + + weserr = vx->eserr * spweight[vx->nsp]; + +//printf("~1 considering vertex no %d, eserr %f, weserr %f\n",vx->no,vx->eserr,weserr); + if (weserr > mx) { + mx = weserr; + bvx = vx; + } + } +//if (bvx != NULL) printf("~1 got vertex no %d, eserr %f, weserr %f\n",bvx->no,bvx->eserr,mx); + +#ifdef SANITY_CHECK_SEED + /* Do exaustive search of candidate vertexes */ + for (vx = s->uvtx; vx != NULL; vx = vx->link) { + double tweserr; + + tweserr = vx->eserr * spweight[vx->nsp]; + + if (vx->used == 0 && tweserr > (mx + 10.0 * NUMTOL) +#ifdef INDEP_SURFACE + /* Only pick full dimensional visible vertexes, */ + /* since only they have a full-d error value. */ + && sm_andtest(s, &s->sc[0].a_sm, &vx->vm) != 0 +#endif + ) { + warning("!!!!!! Sanity: Didn't pick largest eperr vtx no %d %f, picked %d %f instead !!!!!!",vx->no,tweserr,bvx->no,mx); + printf("!!!!!! Sanity: Didn't pick largest eperr vtx no %d %f, picked %d %f instead !!!!!!\n",vx->no,tweserr,bvx->no,mx); + mx = tweserr; + bvx = vx; + } + } +#endif /* SANITY_CHECK_SEED */ + + if (bvx == NULL) { /* We've failed to find a movable point */ + /* This could be because there are fixed points */ + /* at all candidate locations. */ + + if (fc < s->fnp) { /* Use a fixed point */ + /* (Fixed points are already clipped and have perceptual value) */ + for (e = 0; e < di; e++) { /* copy device and perceptual coords */ + p->p[e] = s->ufx[fc]->p[e]; + p->v[e] = s->ufx[fc]->v[e]; + } + + p->fx = 1; /* is a fixed point */ + + /* Count gamut surface planes it lies on */ + if (det_node_gsurf(s, p, p->p) != 0) + nsp++; + break; /* Go and use this point */ + } + error("ofps: assert, there are no vertexes to choose in initial seed\n"); + } + +#ifdef DEBUG + printf("Picking vertex no %d at %s with weserr %f, mask 0x%x\n",bvx->no,ppos(di,bvx->p),mx,bvx->pmask); +#endif + + /* Don't pick the vertex again */ + bvx->used = 1; +//printf("Removing (4) vtx no %d, used %d, eserr %f, vm %s nsp %d\n",bvx->no,bvx->used,bvx->eserr,psm(s,&bvx->vm),bvx->nsp); + if ((aat_aerase(s->vtrees[bvx->nsp], (void *)bvx)) == 0) + error("aat_aerase vertex failed to find vertex no %d (5)", bvx->no); + + /* Add the new node */ + for (e = 0; e < di; e++) + p->p[e] = bvx->p[e]; + + /* Count gamut surface planes it lies on */ + if (det_node_gsurf(s, p, p->p) != 0) + nsp++; + +#ifdef RANDOM_PERTERB + /* Compute radius of closest real node to vertex */ + rerr = 1.0; + for (k = 0; k <= di; k++) { + double rads; + int ix = bvx->nix[k]; + node *np; + if (ix < 0) + break; /* Done */ + np = s->n[ix]; + for (rads = 0.0, e = 0; e < di; e++) { + double tt = bvx->p[e] - np->p[e]; + rads += tt * tt; + } + if (rads < rerr) + rerr = rads; + } + rerr = s->lperterb * sqrt(rerr); + if (rerr < 0.001) + rerr = 0.001; + + /* Add a random offset to the position, and retry */ + /* if it collides with an existing node or future fixed point */ + for (k = 0; k < 20; k++) { + int pci; /* Point list index */ + acell *cp; /* Acceleration cell */ + node *p1; + + for (e = 0; e < di; e++) + p->p[e] = bvx->p[e] + d_rand(-rerr, rerr); + + /* Confine node to planes vertex was on, */ + /* bit not if we're having trouble avoiding collissions */ + if (bvx->nsp > 0) + confineto_gsurf(s, p->p, p->sp, p->nsp); + + ofps_clip_point9(s, p->p, p->p); + + s->percept(s->od, p->v, p->p); + + pci = ofps_point2cell(s, p->v, p->p); /* Grid index of cell of interest */ + if ((cp = &s->grid[pci]) == NULL) + break; /* Nothing in cell */ + for (p1 = cp->head; p1 != NULL; p1 = p1->n) { + for (e = 0; e < di; e++) { + if (fabs(p->v[e] - p1->v[e]) > COINTOL) + break; /* Not cooincident */ + } + if (e >= di) { /* Cooincident */ +#ifdef DEBUG + printf("Random offset node ix %d at %s collides with ix %d at %s - retry random %d\n",p->ix,ppos(di,p->p),p1->ix,ppos(di,p1->p),k); +#endif + break; /* Retry */ + } + } + if (p1 != NULL) /* Coincident */ + continue; + + /* Check movable point against fixed points that are yet to be added */ + /* (~~~ Ideally we should use an accelleration structure to check */ + /* for cooincidence rather than doing an exaustive search. ~~~) */ + if (fc < s->fnp) { /* There are more fixed points to add */ + int f; + + for (f = fc; f < s->fnp; f++) { + for (e = 0; e < di; e++) { + if (fabs(p->p[e] - s->ufx[f]->p[e]) > COINTOL) + break; /* Not cooincident */ + } + if (e >= di) { /* Cooincident */ +#ifdef DEBUG + printf("Movable node ix %d at %s collides with fixed point %d at %s - retry movable %d\n",p->ix,ppos(di,p->p),f,ppos(di,s->ufx[f]->p),k); +#endif + break; + } + } + if (f >= s->fnp) + break; /* movable point is not cooincident */ + } else { + break; /* Not cooincident, so OK */ + } + } + if (k >= 20) { + /* This can happen if we didn't pick the absolute largest weserr, */ + /* and the vertex we ended up with is being confined to the same */ + /* location as an existing vertex by the planes is on. */ + /* (Why does it have an weperr > 0.0 then ????) */ + /* Give up on this point and chose another one. */ + continue; + +// error("ofps_seed: Assert, was unable to joggle cooincindent point"); + } +#else /* !RANDOM_PERTERB */ + /* Confine node to planes vertex was on */ + if (bvx->nsp > 0) + confineto_gsurf(s, p->p, p->sp, p->nsp); + + ofps_clip_point9(s, p->p, p->p); + + s->percept(s->od, p->v, p->p); +#endif /* !RANDOM_PERTERB */ + + /* Added this movable point, so chosen the next point */ + break; + } /* keep looking for a movable point */ + } + + /* We now have a first/fixed/moevable point to add */ + +/* hack test */ +//p->p[0] = d_rand(0.0, 1.0); +//p->p[1] = d_rand(0.0, 1.0); +//ofps_cc_percept(s, p->v, p->p); + + /* Establish original position */ + for (e = 0; e < di; e++) + p->op[e] = p->p[e]; + + /* Compute the Voronoi for it, and inc s->np */ + /* Fail if we get a position fail */ + if (add_node2voronoi(s, i, dofixed && abortonfail)) { + if (dofixed) { + /* Pospone adding this vertex */ + if ((s->fnp - fc) >= (s->tinp - i - 1)) { /* No room for moveable points */ +// error("Adding fixed point failed to hit any vertexes or posn. failed"); + abortonfail = 0; + } else + dofixed = 0; + } + if (needfirst) { + /* Hmm. The first seed point has failed. What should we do ? */ + error("Adding first seed point failed to hit any vertexes or posn. failed"); + } + + /* Skip this point */ + --i; + --j; + continue; + } + + /* Suceeded in adding the point */ + if (p->fx) { /* Fixed point */ + fc++; + dofixed--; + if ((s->fnp - fc) >= (s->tinp - i - 1)) { /* No room for moveable points */ + dofixed = s->fnp - fc; /* Do all the fixed */ + } + } else { /* Movable point */ + ii++; + if (fc < s->fnp) { /* There are more fixed points to add */ + dofixed = s->fnp - fc; + /* Add fixed 2 at a time to try and minimize the disruption */ + /* of the movable point edge priority */ + if (dofixed > 2) + dofixed = 2; + } + } + + if (s->verb && (j == 11 || i == (s->tinp-1))) { + printf("%cAdded %d/%d",cr_char,s->np,s->tinp); fflush(stdout); + j = 0; + } +#ifdef DUMP_STRUCTURE + printf("Done node %d\n",i); + dump_node_vtxs(s, 0); + printf("=========================================================================\n"); +#endif +#ifdef DUMP_PLOT_SEED + dump_image(s, PERC_PLOT, DO_WAIT, DUMP_VTX, DUMP_PLA, 0, -1); /* Device, No wait, verticies */ +#endif /* DUMP_PLOT_SEED */ + + needfirst = 0; /* Must have done first */ + } +//printf("Number of gamut surface points = %d\n",nsp); + + aat_atdelete(aat_tr); + + if (s->verb) + printf("\n"); +} + +/* Recreate the Voronoi diagram with the current point positions */ +static void +ofps_redo_voronoi( +ofps *s +) { + vtx *vx, *nvx; + int i, j, k, e, di = s->di; + + /* Retry if we get a failure to add a point */ + for (k = 0; k < NINSERTTRIES; k++) { + + /* (~9 should think about smoothing the pre-conditioning lookup */ + /* if the number of tries is high. Add this to rspl.) */ + + /* Clear the voronoi nodes */ + node_clear(s, s->n[-2 * di - 2]); + for (j = -s->gnp; j < s->np; j++) + node_clear(s, s->n[j]); + + /* Delete the voronoi verticies */ + for (vx = s->uvtx; vx != NULL; vx = nvx) { + nvx = vx->link; + del_vtx(s, vx); + } + s->uvtx = NULL; + + /* Clear out the spatial acceleration grid */ + ofps_reset_acc(s); + + if (s->nv != 0) + warning("ofps: Assert, clear didn't leave us with 0 vertexes"); + + if (s->umid != NULL) + warning("ofps: Assert, clear didn't empty used midpoint list"); + + if (aat_asize(s->vtreep) != 0) + warning("ofps: Assert, clear didn't empty vertex tree"); + for (e = 0; e <= (di+1); e++) { + if (aat_asize(s->vtrees[e]) != 0) + warning("ofps: Assert, clear didn't empty vertex tree"); + } + + /* Set number of points in voronoi to zero */ + s->np = 0; + + /* Initialse the empty veronoi etc. */ + ofps_binit(s); + + s->posfailstp = 0; + + /* Add all points in again. */ + for (i = 0 ;i < s->tinp; i++) { /* Same order as before */ + + /* Compute the Voronoi for it (will add it to spatial accelleration grid) */ + /* and increment s->np */ + if (add_node2voronoi(s, i, 0)) { + + /* Hmm. Shuffle and retry the whole thing. */ + shuffle_node_order(s); + break; + } + + /* If it's not going well, re-shuffle and abort too */ + if (i > 10 && s->posfailstp/(1.0+i) > 0.2) { +//printf("~1 after node %d, posfailes = %d, prop %f\n",i,s->posfailstp, s->posfailstp/(1.0+i)); + /* Hmm. Shuffle and and retry the whole thing. */ + if (s->verb > 1) + warning("Too many nodes are failing to be inserted - reshuffling and re-starting\n"); + shuffle_node_order(s); + break; + } + +#ifdef DUMP_STRUCTURE + printf("Done node %d\n",i); + dump_node_vtxs(s, 0); +// ofps_re_create_node_node_vtx_lists(s); +// if ((s->optit+1) >= 4) +// { char buf[200]; sprintf(buf, "Itteration %d node ix %d",s->optit+1,s->np-1); dump_node_vtxs2(s, buf); } + printf("=========================================================================\n"); +#endif +#ifdef DUMP_PLOT_RESEED + dump_image(s, PERC_PLOT, DO_WAIT, DUMP_VTX, DUMP_PLA, 0, -1); /* Device, No wait, verticies */ +#endif /* DUMP_PLOT_RESEED */ + } + if (i >= s->tinp) { +#ifdef DEBUG + if (k > 1) printf("Took %d retries\n",k-1); +#endif /* DEBUG */ + break; + } + /* Retry the whole thing */ + } + if (k >= NINSERTTRIES) + error("Failed to re-seed the veronoi after %d tries - too many node insertion failures ?",NINSERTTRIES); +} + +/* ----------------------------------------------------------- */ +/* Ideas for improving the accelleration: + + When there is no SUBD, then it is possible that the node + neighbourhood net (if it is kept up to date during seeding) + could be used to locate the closest node and then vertex. + (It can't be used for fixup, because the voronoi properties + aren't true during fixup.) + Starting at at the first node found using the spiral structure, + check all it's neigbours and if it's neighbour is closer to + the target, switch to it. If no neighbour is closer, + then that is the closest node. + The closest vertex is then connected to the closest node ? + +*/ + +#undef DEBUG_FCLOSE + +/* Given a node, locate all vertexes that it hits. */ +/* s->flag is assumed to be relevant for the given node. */ +/* Any hit vertexes are added to the s->nxh list. */ +/* s->vvchecks and s->nvcheckhits will be updated. */ +/* Return nz if vertexs were hit */ +/* (This only returns visible vertexes.) */ +static int ofps_findhit_vtxs(ofps *s, node *nn) { + int e, di = s->di; + int i, j; + int pci; /* Point cell index */ + acell *cp; + vtx *vx; + double beperr, eperr; + acell *slist = NULL, *sliste = NULL; /* Next to search list */ + int hit = 0; + + if (nn->ix < 0) + error("ofps_findhit_vtxs given gamut boudary node ix %d",nn->ix); + +#ifdef DEBUG + if (s->agrid_init == 0) + error("ofps_findhit_vtxs() called before agrid_init"); +#endif + +#ifdef DEBUG_FCLOSE + printf("\nLocating a hit vtx to node at p = %s, v = %s\n", ppos(di,nn->p), ppos(di,nn->v)); +#endif + + /* Determine the largest eperr of any vertex */ + { + aat_atrav_t *aat_tr; + + beperr = 1e300; + + if ((aat_tr = aat_atnew()) == NULL) + error("aat_atnew returned NULL"); + + /* Find the largest vertex eperr visible to the node */ + for (vx = aat_atlast(aat_tr, s->vtreep); vx != NULL; vx = aat_atprev(aat_tr)) { +#ifdef INDEP_SURFACE + if (sm_vtx_node(s, vx, nn) == 0) + continue; +#endif /* INDEP_SURFACE */ + beperr = vx->eperr; + break; + } + aat_atdelete(aat_tr); + +#ifdef DEBUG_FCLOSE + printf("Largest eperr of any vertex = %f\n", beperr); +// fprintf(stderr,"Largest eperr of any vertex = %f\n", beperr); +#endif + } + + s->nvfschd += s->nv; /* Number of vertexes in a full search */ + s->naccsrch++; /* Number of searches */ + + /* Do a breadth first seed search for any hit vertexes, or until */ + /* we run out of cells that could possibly be hits. */ + + /* Locate a starting cell using the grid */ + pci = ofps_point2cell(s, nn->v, nn->p); /* Grid index of cell of interest */ + cp = &s->grid[pci]; + + s->gflag++; /* cell touched flag */ + + /* Put the starting cell on the search list */ +#ifdef DEBUG_FCLOSE + printf("Adding cell ix %d co %s to slist\n",cp - s->grid, pco(di,cp->co)); +#endif +#ifdef NEVER + if (sliste == NULL) { /* First in empty list */ + slist = cp; + } else { + sliste->slist = cp; /* Add to end of list */ + } + sliste = cp; + cp->slist = NULL; +#else + cp->slist = slist; + slist = cp; +#endif + cp->gflag = s->gflag; /* Cell is on list to be searched */ + + /* until we run out of cells to search */ + for (;slist != NULL;) { + acell *ncp; + + /* For each cell in the search list, check it and recursion. */ + for (cp = slist, slist = sliste = NULL; cp != NULL; cp = ncp) { + double ceperr; + ncp = cp->slist; + +#ifdef DEBUG_FCLOSE + printf("Checking cell ix %d co %s\n",cp - s->grid,pco(di,cp->co)); +#endif + + /* Compute the smallest eperr possible in this cell, by computing the */ + /* eperr of the cell center to the node minus the estimated */ + /* largest eperr of any point within the cell to the center. */ + ceperr = ofps_comp_eperr(s, NULL, cp->v, cp->p, nn->v, nn->p); + eperr = ceperr - cp->eperr; + +//printf("~1 ceperr %f, cp->eperr %f, eperr %f, beperr %f\n",ceperr,cp->eperr,eperr,beperr); + /* If smallest possible eperr is larger than largest vertexe eperr */ + if (eperr > beperr) { +//printf("~1 skipping cell\n"); +#ifdef SANITY_CHECK_CLOSEST + /* Check all nodees in the cell anyway */ + for (vx = cp->vhead; vx != NULL; vx = vx->n) { + int par = 0; + + if (vx->cflag == s->flag) + continue; +#ifdef INDEP_SURFACE + if (sm_vtx_node(s, vx, nn) == 0) + continue; +#endif /* INDEP_SURFACE */ + + if (nn->ixm & vx->nix[MXPD+2]) { /* Is in nixm */ + for (e = 0; e <= di; e++) { /* Do exact check */ + if (nn->ix == vx->nix[e]) + break; + } + if (e <= di) + par = 1; + } + + eperr = ofps_comp_eperr7(s, NULL, vx->v, vx->p, nn->v, nn->p); + + if (!par && (vx->eperr - eperr) > 0.0) { +//printf("~1 Node ix %d at %s (%s)\n Cell ix %d co %s center %s (%s),\n vtx no %d at %s (%s)\n",nn->ix, ppos(di,nn->p),ppos(di,nn->v),cp - s->grid,pco(s->di,cp->co),ppos(di,cp->cp),ppos(di,cp->cv),vx->no, ppos(di,vx->p),ppos(di,vx->v)); + warning("Sanity check ofps_findhit_vtxs() cell skip failed, hit on vtx no %d, eperr %f < vx->eperr %f, cell ix %d eperr %f, est min eperr %f",vx->no,eperr,vx->eperr,cp - s->grid,ceperr,ceperr - cp->eperr); + printf("Sanity check ofps_findhit_vtxs() cell skip failed, hit on vtx no %d, eperr %f < vx->eperr %f, cell ix %d eperr %f, est min eperr %f\n",vx->no,eperr,vx->eperr,cp - s->grid,ceperr,ceperr - cp->eperr); +#ifdef SANITY_CHECK_CLOSEST_FATAL + error("findclosest node cell skip failed"); +#endif + } + } +#endif /* SANITY_CHECK_CLOSEST */ + continue; /* Cell is not worth searching */ + } + + /* Search the cell */ + s->ncellssch++; + + /* For vertexes in this cell */ + for (vx = cp->vhead; vx != NULL; vx = vx->n) { +#ifdef DEBUG + printf("Checking vtx no %d\n",vx->no); +#endif + /* If the vertex has already been checked */ + if (vx->cflag == s->flag) + continue; + + if (vx->ofake) /* ofake vertexes can't be hit */ + continue; + +#ifdef INDEP_SURFACE + /* Only check for hit if the vertex is visible to the node */ + if (sm_vtx_node(s, vx, nn) == 0) { +# ifdef DEBUG + printf("Vertex no %d xmask 0x%x vm %s isn't visible to ix %d pmask 0x%x a_sm %s\n",vx->no,vx->cmask,psm(s,&vx->vm),nn->ix,nn->pmask,psm(s,&s->sc[nn->pmask].a_sm)); +# endif /* DEBUG */ + continue; + } +#endif /* INDEP_SURFACE */ + + vx->add = 0; + vx->del = 0; + vx->par = 0; + + s->vvchecks++; /* Checking a vertex */ + + /* Check if node is already parent to this vertex. */ + /* This only happens during fixups if the reposition fails and we */ + /* retain the vertex with the deleted vertex location (not currently */ + /* done), or by slim numerical margine, so ignore such hits. */ + /* We treat a parent as a hit node for the purposes of recursion, */ + /* and add it to a special list used to complete the vertex net. */ + if (nn->ixm & vx->nix[MXPD+2]) { /* Is in nixm */ + for (e = 0; e <= di; e++) { /* Do exact check */ + if (nn->ix == vx->nix[e]) + break; + } + if (e <= di) { +#ifdef DEBUG + printf("Vertex no %d has already got node ix %d\n",vx->no,nn->ix); +#endif + vx->par = 1; + } + } + + /* nba_eperr is assumed to be valid if vx->cflag == s->flag */ + vx->nba_eperr = ofps_comp_eperr7(s, NULL, vx->v, vx->p, nn->v, nn->p); +#ifdef DEBUG + printf("Computing nba_eperr of %f for vtx no %d\n",vx->nba_eperr, vx->no); +#endif + /* See if the vertex eperr will be improved */ + if (!vx->par && (vx->eperr - vx->nba_eperr) > 0.0) { + s->nvcheckhits++; + hit = 1; + vx->del = 1; /* Mark for deletion */ + vx->nxh = s->nxh; /* Add vertex to list */ + s->nxh = vx; + vx->hflag = s->flag; +#ifdef DEBUG + printf("Vertex error improvement hit by %f (%f < %f)\n",vx->eperr-vx->nba_eperr,vx->nba_eperr,vx->eperr); + + if (vx->par) { + printf("Vertex no %d hit by its own parent ix %d\n",vx->no, nn->ix); + warning("Vertex no %d hit by its own parent ix %d",vx->no, nn->ix); + } +#endif + } +#ifdef DEBUG + else { /* If worse */ + printf("Vertex error not hit by %f (%f < %f)\n",vx->eperr-vx->nba_eperr,vx->nba_eperr,vx->eperr); + } +#endif + vx->cflag = s->flag; + + } /* Next vertex in cell */ + + /* Put all this cells neighbours on the search list */ + /* (This is probably the critical inner loop. If ->acnl was */ + /* scaled by sizeof(acell), then the implicit multiply could */ + /* be avoided) */ + for (j = 0; j < s->nacnl; j++) { + acell *nc = cp + s->acnl[j]; + + if (nc->gflag >= s->gflag) + continue; + +#ifdef DEBUG_FCLOSE + printf("Adding cell ix %d co %s to slist\n",nc - s->grid, pco(di,nc->co)); +#endif +#ifdef NEVER + if (sliste == NULL) { /* First in empty list */ + slist = nc; + } else { + sliste->slist = nc; /* Add to end of list */ + } + sliste = nc; + nc->slist = NULL; +#else + nc->slist = slist; + slist = nc; +#endif + nc->gflag = s->gflag; /* Cell is on list to be searched */ + } + } /* Next cell in current list */ +//printf("~1 don that search list\n"); + } /* Next list */ +//printf("~1 no more search lists\n"); + + return hit; +} + +#ifdef DEBUG_FCLOSE +#undef DEBUG_FCLOSE +#endif + + +#undef DEBUG_FCLOSE + +/* Given a vertex, locate the smallest eperr node. */ +/* Return NULL if none, and if ceperr is not NULL, set it to the */ +/* eperr to the returned node. */ +/* (This only returns visible nodes.) */ +static node *ofps_findclosest_node(ofps *s, double *ceperr, vtx *vx) { + int e, di = s->di; + int i, j; + int pci; /* Point cell index */ + acell *cp; + double eperr, beperr = 1e300; /* eperr of closest node */ + node *bno = NULL; /* Closest node */ + acell *slist = NULL, *sliste = NULL; /* Next to search list */ + +#ifdef DEBUG + if (s->agrid_init == 0) + error("ofps_findclosest_node() called befor agrid_init"); +#endif + +#ifdef DEBUG_FCLOSE + printf("\nLocating closest node to vtx at p = %s, v = %s\n", ppos(di,vx->p), ppos(di,vx->v)); +#endif + + s->nnfschd += s->np; /* Number of nodes in a full search */ + s->naccsrch++; /* Number of searches */ + + /* Do a breadth first seed search for any better nodees, or until */ + /* we run out of cells that could improve on the current best. */ + + /* Locate a starting cell using the grid */ + pci = ofps_point2cell(s, vx->v, vx->p); /* Grid index of cell of interest */ + cp = &s->grid[pci]; + + s->gflag++; /* cell touched flag */ + + /* Put the starting cell on the search list */ +#ifdef DEBUG_FCLOSE + printf("Adding cell ix %d co %s to slist\n",cp - s->grid, pco(di,cp->co)); +#endif +#ifdef NEVER + if (sliste == NULL) { /* First in empty list */ + slist = cp; + } else { + sliste->slist = cp; /* Add to end of list */ + } + sliste = cp; + cp->slist = NULL; +#else + cp->slist = slist; /* Add it to start of list */ + slist = cp; +#endif + cp->gflag = s->gflag; /* Cell is on list to be searched */ + + /* until we run out of cells to search */ + for (;slist != NULL;) { + acell *ncp; + + /* For each cell in the search list, check it and recursion. */ + for (cp = slist, slist = sliste = NULL; cp != NULL; cp = ncp) { + double ceperr; + ncp = cp->slist; + +#ifdef DEBUG_FCLOSE + printf("Checking cell ix %d co %s\n",cp - s->grid,pco(di,cp->co)); +#endif + + /* Compute the eperr of the cell center to the vtx minus the estimated */ + /* largest eperr of any point within the cell to the center. */ + ceperr = ofps_comp_eperr(s, NULL, cp->v, cp->p, vx->v, vx->p); + eperr = ceperr - cp->eperr; + + /* If the cell is worth searching */ + if (eperr < beperr) { + node *no; + + /* Search the cell */ + s->ncellssch++; + + for (no = cp->head; no != NULL; no = no->n) { + +#ifdef INDEP_SURFACE + /* Check if this node is visible to this vtx */ + if (sm_vtx_node(s, vx, no) == 0) { + continue; /* It's hidden */ + } +#endif /* INDEP_SURFACE */ + + /* Compute the eperr between the node to the new vtx */ + eperr = ofps_comp_eperr(s, NULL, no->v, no->p, vx->v, vx->p); + if (eperr < beperr) { + bno = no; + beperr = eperr; +#ifdef DEBUG_FCLOSE + printf("Improved to node ix %d eperr\n",bno->ix,beperr); +#endif + } + } + + /* Put all this cells neighbours on the search list */ + /* (This is probably the critical ivxer loop. If ->acnl was */ + /* scaled by sizeof(acell), then the implicit multiply could */ + /* be avoided) */ + for (j = 0; j < s->nacnl; j++) { + acell *nc = cp + s->acnl[j]; + + if (nc->gflag >= s->gflag) + continue; + +#ifdef DEBUG_FCLOSE + printf("Adding cell ix %d co %s to slist\n",nc - s->grid, pco(di,nc->co)); +#endif +#ifdef NEVER + if (sliste == NULL) { /* First in empty list */ + slist = nc; + } else { + sliste->slist = nc; /* Add to end of list */ + } + sliste = nc; + nc->slist = NULL; +#else + nc->slist = slist; /* Add it to start of list */ + slist = nc; +#endif + nc->gflag = s->gflag; /* Cell is on list to be searched */ + } + } +#ifdef SANITY_CHECK_CLOSEST + /* Check all nodees in the cell anyway */ + else { + double teperr; + node *no; + + for (no = cp->head; no != NULL; no = no->n) { + +#ifdef INDEP_SURFACE + /* Check if this node is visible to this vtx */ + if (sm_vtx_node(s, vx, no) == 0) { + continue; /* It's hidden */ + } +#endif /* INDEP_SURFACE */ + + /* Compute the eperr between the node to the new vtx */ + teperr = ofps_comp_eperr(s, NULL, no->v, no->p, vx->v, vx->p); + if (teperr < beperr) { + warning("Sanity check ofps_findclosest_node() cell skip failed, estimated %f from cellc eperr %f - cell eperr %f, found %f from node ix %d",eperr,ceperr,cp->eperr,teperr,no->ix); + printf("Sanity check ofps_findclosest_node() cell skip failed, estimated %f from cellc eperr %f - cell eperr %f, found %f from node ix %d\n",eperr,ceperr,cp->eperr,teperr,no->ix); +#ifdef SANITY_CHECK_CLOSEST_FATAL + error("findclosest node cell skip failed"); +#endif + } + } + } +#endif /* SANITY_CHECK_CLOSEST */ + + } /* Next cell in current list */ +#ifdef DEBUG_FCLOSE + printf("Finished ivxer loop because p 0x%x = NULL\n",cp); +#endif + } /* Next list */ +#ifdef DEBUG_FCLOSE + printf("Finished outer loop because slist 0x%x = NULL\n",slist); +#endif + +#ifdef DEBUG_FCLOSE + if (bno == NULL) + printf("Failed to find a closest node"); + else + printf("Returning best node ix %d, eperr %f\n",bno->ix,beperr); +#endif + +#ifdef SANITY_CHECK_CLOSEST + /* Use exaustive search */ + { + double ch_beperr = 1e300; /* Device distance squared of closest vertex */ + node *ch_bno = NULL; + for (i = 0; i < (s->np-1); i++) { + node *nn = s->n[i]; + double eperr; + +#ifdef INDEP_SURFACE + /* Check if this vertex is visible to this node */ + if (sm_vtx_node(s, vx, nn) == 0) { + continue; /* It's hidden */ + } +#endif /* INDEP_SURFACE */ + + /* Compute the eperr between the node and the vertex */ + eperr = ofps_comp_eperr(s, NULL, nn->v, nn->p, vx->v, vx->p); + if (eperr < ch_beperr) { + ch_bno = nn; + ch_beperr = eperr; + } + } + + if (ch_bno != NULL && ch_beperr + 1e-3 < beperr) { + if (bno == NULL) { + warning("Sanity check ofps_findclosest_node() failed,\n found none, should be ix %d dist %f",ch_bno->ix,ch_beperr); + printf("Sanity check ofps_findclosest_node() failed,\n found none, should be ix %d dist %f\n",ch_bno->ix,ch_beperr); + } else { + warning("Sanity check ofps_findclosest_node() failed,\n found ix %d dist %f, should be ix %d dist %f",bno->ix,beperr,ch_bno->ix,ch_beperr); + printf("Sanity check ofps_findclosest_node() failed,\n found ix %d dist %f, should be ix %d dist %f\n",bno->ix,beperr,ch_bno->ix,ch_beperr); + } +#ifdef SANITY_CHECK_CLOSEST_FATAL + error("findclosest node failed"); +#endif + } + } +#endif + + if (bno != NULL && ceperr != NULL) + *ceperr = beperr; + + return bno; +} + +/* ----------------------------------------------------------- */ + +#ifdef NEVER /* No longer used */ + +/* Given a node, locate the smallest eperr vertex. */ +/* Return NULL if none, and if ceperr is not NULL, set it to the */ +/* eperr to the returned vertex. */ +/* (This only returns visible vertexes.) */ +static vtx *ofps_findclosest_vtx(ofps *s, double *ceperr, node *nn) { + int e, di = s->di; + int i, j; + int pci; /* Point cell index */ + acell *cp; + double eperr, beperr = 1e300; /* eperr of closest vertex */ + vtx *bvx = NULL; /* Closest vertex */ + acell *slist = NULL, *sliste = NULL; /* Next to search list */ + +#ifdef DEBUG + if (s->agrid_init == 0) + error("ofps_findclosest_vtx() called befor agrid_init"); +#endif + +#ifdef DEBUG_FCLOSE + printf("\nLocating closest vtx to node at p = %s, v = %s\n", ppos(di,nn->p), ppos(di,nn->v)); +#endif + + s->nvfschd += s->nv; /* Number of vertexes in a full search */ + s->naccsrch++; /* Number of searches */ + + /* Do a breadth first seed search for any better vertexes, or until */ + /* we run out of cells that could improve on the current best. */ + + /* Locate a starting cell using the grid */ + pci = ofps_point2cell(s, nn->v, nn->p); /* Grid index of cell of interest */ + cp = &s->grid[pci]; + + s->gflag++; /* cell touched flag */ + + /* Put the starting cell on the search list */ +#ifdef DEBUG_FCLOSE + printf("Adding cell ix %d co %s to slist\n",cp - s->grid, pco(di,cp->co)); +#endif + if (sliste == NULL) { /* First in empty list */ + slist = cp; + } else { + sliste->slist = cp; /* Add to end of list */ + } + sliste = cp; + cp->slist = NULL; + cp->gflag = s->gflag; /* Cell is on list to be searched */ + + /* until we run out of cells to search */ + for (;slist != NULL;) { + acell *ncp; + + /* For each cell in the search list, check it and recursion. */ + for (cp = slist, slist = sliste = NULL; cp != NULL; cp = ncp) { + double ceperr; + ncp = cp->slist; + +#ifdef DEBUG_FCLOSE + printf("Checking cell ix %d co %s\n",cp - s->grid,pco(di,cp->co)); +#endif + + /* Compute the eperr of the cell center to the node minus the estimated */ + /* largest eperr of any point within the cell to the center. */ + ceperr = ofps_comp_eperr(s, NULL, cp->v, cp->p, nn->v, nn->p); + eperr = ceperr - cp->eperr; + + /* If the cell is worth searching */ + if (eperr < beperr) { + vtx *vx; + + /* Search the cell */ + s->ncellssch++; + + for (vx = cp->vhead; vx != NULL; vx = vx->n) { + +#ifdef INDEP_SURFACE + /* Check if this vertex is visible to this node */ + if (sm_vtx_node(s, vx, nn) == 0) { + continue; /* It's hidden */ + } +#endif /* INDEP_SURFACE */ + + /* Compute the eperr between the vertex to the new node */ + eperr = ofps_comp_eperr(s, NULL, vx->v, vx->p, nn->v, nn->p); + if (eperr < beperr) { + bvx = vx; + beperr = eperr; +#ifdef DEBUG_FCLOSE + printf("Improved to vtx no %d eperr\n",bvx->no,beperr); +#endif + } + } + + /* Put all this cells neighbours on the search list */ + /* (This is probably the critical inner loop. If ->acnl was */ + /* scaled by sizeof(acell), then the implicit multiply could */ + /* be avoided) */ + for (j = 0; j < s->nacnl; j++) { + acell *nc = cp + s->acnl[j]; + + if (nc->gflag >= s->gflag) + continue; + +#ifdef DEBUG_FCLOSE + printf("Adding cell ix %d co %s to slist\n",nc - s->grid, pco(di,nc->co)); +#endif + if (sliste == NULL) { /* First in empty list */ + slist = nc; + } else { + sliste->slist = nc; /* Add to end of list */ + } + sliste = nc; + nc->slist = NULL; + nc->gflag = s->gflag; /* Cell is on list to be searched */ + } + } +#ifdef SANITY_CHECK_CLOSEST + /* Check all vertexes in the cell anyway */ + else { + double teperr; + vtx *vx; + + for (vx = cp->vhead; vx != NULL; vx = vx->n) { + +#ifdef INDEP_SURFACE + /* Check if this vertex is visible to this node */ + if (sm_vtx_node(s, vx, nn) == 0) { + continue; /* It's hidden */ + } +#endif /* INDEP_SURFACE */ + + /* Compute the eperr between the vertex to the new node */ + teperr = ofps_comp_eperr(s, NULL, vx->v, vx->p, nn->v, nn->p); + if (teperr < beperr) { + warning("Sanity check ofps_findclosest_vtx() cell skip failed, estimated %f from cellc eperr %f - cell eperr %f, found %f from vtx no %d",eperr,ceperr,cp->eperr,teperr,vx->no); + printf("Sanity check ofps_findclosest_vtx() cell skip failed, estimated %f from cellc eperr %f - cell eperr %f, found %f from vtx no %d\n",eperr,ceperr,cp->eperr,teperr,vx->no); +#ifdef SANITY_CHECK_CLOSEST_FATAL + error("findclosest vertex cell skip failed"); +#endif + } + } + } +#endif /* SANITY_CHECK_CLOSEST */ + + } /* Next cell in current list */ +#ifdef DEBUG_FCLOSE + printf("Finished inner loop because p 0x%x = NULL\n",cp); +#endif + } /* Next list */ +#ifdef DEBUG_FCLOSE + printf("Finished outer loop because slist 0x%x = NULL\n",slist); +#endif + +#ifdef DEBUG_FCLOSE + if (bvx == NULL) + printf("Failed to find a closest vertex"); + else + printf("Returning best vtx no %d, eperr %f\n",bvx->no,beperr); +#endif + +#ifdef SANITY_CHECK_CLOSEST + /* Use exaustive search */ + { + double ch_beperr = 1e300; /* Device distance squared of closest vertex */ + vtx *vx, *ch_bvx = NULL; + for (vx = s->uvtx; vx != NULL; vx = vx->link) { /* Check all vertexes */ + double eperr; + +#ifdef INDEP_SURFACE + /* Check if this vertex is visible to this node */ + if (sm_vtx_node(s, vx, nn) == 0) { + continue; /* It's hidden */ + } +#endif /* INDEP_SURFACE */ + + /* Compute the eperr between the vertex to the new node */ + eperr = ofps_comp_eperr(s, NULL, vx->v, vx->p, nn->v, nn->p); + if (eperr < ch_beperr) { + ch_bvx = vx; + ch_beperr = eperr; + } + } + + if (ch_bvx != NULL && ch_beperr + 1e-3 < beperr) { + if (bvx == NULL) { + warning("Sanity check ofps_findclosest_vtx() failed,\n found none, should be no %d dist %f",ch_bvx->no,ch_beperr); + printf("Sanity check ofps_findclosest_vtx() failed,\n found none, should be no %d dist %f\n",ch_bvx->no,ch_beperr); + } else { + warning("Sanity check ofps_findclosest_vtx() failed,\n found no %d dist %f, should be no %d dist %f",bvx->no,beperr,ch_bvx->no,ch_beperr); + printf("Sanity check ofps_findclosest_vtx() failed,\n found no %d dist %f, should be no %d dist %f\n",bvx->no,beperr,ch_bvx->no,ch_beperr); + } +#ifdef SANITY_CHECK_CLOSEST_FATAL + error("findclosest vertex failed"); +#endif + } + } +#endif + + if (bvx != NULL && ceperr != NULL) + *ceperr = beperr; + + return bvx; +} + +/* Given a node, locate a vertex that it hits. */ +/* Return NULL if none, and if ceperr is not NULL, set it to the */ +/* eperr to the returned vertex. */ +/* (This only returns visible vertexes.) */ +static vtx *ofps_findhit_vtx(ofps *s, double *ceperr, node *nn) { + int e, di = s->di; + int i, j; + int pci; /* Point cell index */ + acell *cp; + double eperr, beperr = 1e300; /* eperr of closest vertex */ + vtx *bvx = NULL; /* Closest vertex */ + acell *slist = NULL, *sliste = NULL; /* Next to search list */ + +#ifdef DEBUG + if (s->agrid_init == 0) + error("ofps_findhit_vtx() called befor agrid_init"); +#endif + +#ifdef DEBUG_FCLOSE + printf("\nLocating a hit vtx to node at p = %s, v = %s\n", ppos(di,nn->p), ppos(di,nn->v)); +#endif + + s->nvfschd += s->nv; /* Number of vertexes in a full search */ + s->naccsrch++; /* Number of searches */ + + /* Do a breadth first seed search for any hit vertexes, or until */ + /* we run out of cells that could improve on the current best. */ + + /* Locate a starting cell using the grid */ + pci = ofps_point2cell(s, nn->v, nn->p); /* Grid index of cell of interest */ + cp = &s->grid[pci]; + + s->gflag++; /* cell touched flag */ + + /* Put the starting cell on the search list */ + for (j = 0; j < s->nacnl; j++) { +#ifdef DEBUG_FCLOSE + printf("Adding cell ix %d co %s to slist\n",cp - s->grid, pco(di,cp->co)); +#endif + if (sliste == NULL) { /* First in empty list */ + slist = cp; + } else { + sliste->slist = cp; /* Add to end of list */ + } + sliste = cp; + cp->slist = NULL; + cp->gflag = s->gflag; /* Cell is on list to be searched */ + } + + /* until we run out of cells to search */ + for (;slist != NULL;) { + acell *ncp; + + /* For each cell in the search list, check it and recursion. */ + for (cp = slist, slist = sliste = NULL; cp != NULL; cp = ncp) { + ncp = cp->slist; + +#ifdef DEBUG_FCLOSE + printf("Checking cell ix %d co %s\n",cp - s->grid,pco(di,cp->co)); +#endif + + /* If the cell is worth searching */ + if (1) { + vtx *vx; + + /* Search the cell */ + s->ncellssch++; + + for (vx = cp->vhead; vx != NULL; vx = vx->n) { + +#ifdef INDEP_SURFACE + /* Check if this vertex is visible to this node */ + if (sm_vtx_node(s, vx, nn) == 0) { + continue; /* It's hidden */ + } +#endif /* INDEP_SURFACE */ + + /* Compute the eperr between the vertex to the new node */ + eperr = ofps_comp_eperr(s, NULL, vx->v, vx->p, nn->v, nn->p); + if (eperr < vx->eperr) { + bvx = vx; + beperr = eperr; +#ifdef DEBUG_FCLOSE + printf("Found hit vtx no %d eperr\n",bvx->no,beperr); +#endif + break; + } + } + if (vx != NULL) + break; + + /* Put all this cells neighbours on the search list */ + /* (This is probably the critical inner loop. If ->acnl was */ + /* scaled by sizeof(acell), then the implicit multiply could */ + /* be avoided) */ + for (j = 0; j < s->nacnl; j++) { + acell *nc = cp + s->acnl[j]; + + if (nc->gflag >= s->gflag) + continue; + +#ifdef DEBUG_FCLOSE + printf("Adding cell ix %d co %s to slist\n",nc - s->grid, pco(di,nc->co)); +#endif + if (sliste == NULL) { /* First in empty list */ + slist = nc; + } else { + sliste->slist = nc; /* Add to end of list */ + } + sliste = nc; + nc->slist = NULL; + nc->gflag = s->gflag; /* Cell is on list to be searched */ + } + } + } /* Next cell in current list */ + } /* Next list */ + +#ifdef DEBUG_FCLOSE + if (bvx == NULL) + printf("Failed to find a hit vertex"); + else + printf("Returning hit vtx no %d, eperr %f\n",bvx->no,beperr); +#endif + + if (bvx != NULL && ceperr != NULL) + *ceperr = beperr; + + return bvx; +} + +#ifdef DEBUG_FCLOSE +#undef DEBUG_FCLOSE +#endif + +#endif /* NEVER */ + +/* ----------------------------------------------------------- */ + +/* Re-position the vertexes given the current point positions, */ +/* and fixup the veronoi. */ +static void +ofps_repos_and_fix_voronoi( +ofps *s +) { + int e, di = s->di; + int i, j, k; + node *nds[MXPD+1]; /* Real nodes of vertex */ + int ii; /* Number of real nodes */ + double ee[MXPD+1]; /* Per node estimated error */ + vtx *vx; + node *nn, *pp; + int nfuxups, l_nfuxups; /* Count of fixups */ + int csllow; /* Count since last low */ + int mxcsllow = 5; /* Threshold to give up */ + +#ifdef DEBUG + printf("Repositioning vertexes\n"); +#endif + + /* Re-position the vertexes to match optimized node positions */ + s->fchl = NULL; + for (vx = s->uvtx; vx != NULL; vx = vx->link) { + nodecomb nc; + + if (vx->ifake || vx->ofake) + continue; + + vx->p_eperr = vx->eperr; + + /* Pointers to real nodes. */ + for (ii = e = 0; e <= di; e++) { + if (vx->nix[e] >= 0) + nds[ii++] = s->n[vx->nix[e]]; + if (vx->nix[e] < -s->nbp) + error("ofps_repos_and_fix_voronoi() got fake node no %d comb %s fake %d",vx->no,pcomb(di,vx->nix),vx->ofake); + } + + /* Compute the current eperr at the vertex given the repositioned nodes, */ + /* to set acceptance threshold for repositioned vertex. */ + ofps_pn_eperr(s, NULL, ee, vx->v, vx->p, nds, ii); + nc.ceperr = ofps_eperr2(ee, ii); + + /* Setup to re-position the vertex */ + memset((void *)&nc, 0, sizeof(nodecomb)); + for (e = 0; e < di; e++) { + nc.nix[e] = vx->nix[e]; + nc.p[e] = vx->p[e]; + nc.v[e] = vx->v[e]; + } + nc.nix[e] = vx->nix[e]; + +#ifdef DEBUG + printf("Repositioning vertex no %d nodes %s at %s, ceperr %f\n",vx->no,pcomb(di,vx->nix),ppos(di,vx->p),nc.ceperr); +#endif + + /* We're about to change the position and eperr: */ + ofps_rem_vacc(s, vx); + ofps_rem_vseed(s, vx); + + if (position_vtx(s, &nc, 1, 1, 0) == 2) { + /* Just leave it where it was. Perhaps fixups will delete it */ + if (s->verb > 1) + warning("re_position_vtx failed for vtx no %d at %s",vx->no,ppos(di,vx->p)); + } else { +//printf("~1 moved from %s to %s\n",ppos(di,vx->p),ppos(di,nc.p)); + + for (e = 0; e < di; e++) { + vx->p[e] = nc.p[e]; + vx->v[e] = nc.v[e]; + } + vx->eperr = nc.eperr; + vx->eserr = nc.eserr; + } + + /* Count the number of gamut surfaces the vertex falls on */ + det_vtx_gsurf(s, vx); + + /* We've changed the position and eperr: */ + ofps_add_vacc(s, vx); + ofps_add_vseed(s, vx); + + /* Add all vertexes to the "to be checked" list */ + vx->fchl = s->fchl; /* Add vertex to the "to be checked" list */ + if (s->fchl != NULL) + s->fchl->pfchl = &vx->fchl; + s->fchl = vx; + vx->pfchl = &s->fchl; + vx->fflag = s->fflag; + vx->fupcount = 0; + vx->fuptol = NUMTOL; + } + +#ifdef DUMP_PLOT_BEFORFIXUP + printf("Before applying fixups:\n"); + dump_image(s, PERC_PLOT, DO_WAIT, DUMP_VTX, DUMP_PLA, 0, -1); /* Device, No wait, verticies */ +#endif /* DUMP_PLOT_BEFORFIXUP */ + + /* Now fixup the veroni. */ +#ifdef DEBUG + printf("Doing fixups:\n"); +#endif + + /* We loop until the check list is empty */ + l_nfuxups = 1e9; + csllow = 0; + while (s->fchl != NULL && csllow < mxcsllow) { + vtx *nvx; + + s->fflag++; /* Fixup round flag */ + s->nsvtxs = 0; + nfuxups = 0; + +#ifdef DEBUG + printf("\nFixup round %d%s\n",s->fflag, s->fchl == NULL ? "" : " fchl != NULL"); +#endif + + /* out of gamut, or whether the closest node to it */ + /* is not one of its parent nodes. */ + for (vx = s->fchl; vx != NULL; vx = nvx) { + double ceperr; /* eperr to closest node */ + int hit = 0; + + /* For each vertex on the check list, check if it is */ + nvx = vx->fchl; +#ifdef DEBUG + printf("Checking vtx no %d, fuptol %e\n",vx->no,vx->fuptol); +#endif + + vx->hnode = NULL; + nn = NULL; + /* Check if the vertex position is clipped, */ + /* and add fake boundary node if it is */ + /* For all the gamut boundary planes: */ + for (i = 0; i < s->nbp; i++) { + pleq *vp = &s->gpeqs[i]; + double v; + + nn = s->n[-1-i]; + +#ifdef INDEP_SURFACE + /* Check if this vertex is visible to this node */ + if (sm_vtx_node(s, vx, nn) == 0) { + continue; + } +#endif /* INDEP_SURFACE */ + + for (v = vp->pe[di], e = 0; e < di; e++) + v += vp->pe[e] * vx->p[e]; + if (v > vx->fuptol) { + +#ifdef NEVER + /* This is an optimization: */ + /* Check whether nn is already a parent of the node */ + for (e = 0; e <= di; e++) { + if (nn->ix == vx->nix[e]) + break; + } + if (e <= di) { + continue; /* It is */ + } +#endif + + /* Add all the vertexes parent nodes to the nearest nodes "add" list */ +#ifdef DEBUG + printf("Vertex no %d hit by boundary node ix %d by %e\n",vx->no,nn->ix,v); +#endif + hit = 1; + if (vx->hnode == NULL) { + vx->hnode = nn; + vx->hitmarg = 50.0 * v; + + if (s->nsvtxs >= s->_nsvtxs) { + s->_nsvtxs = 2 * s->_nsvtxs + 5; + if ((s->svtxs = (vtx **)realloc(s->svtxs, sizeof(vtx *) * s->_nsvtxs)) == NULL) + error("ofps: malloc failed on svtxs%d", s->_nsvtxs); + } + s->svtxs[s->nsvtxs]= vx; + vx->psvtxs = &s->svtxs[s->nsvtxs]; + s->nsvtxs++; + + } else if (50.0 * v > vx->hitmarg) { + vx->hnode = nn; + vx->hitmarg = 50.0 * v; + } +#ifdef DEBUG + printf("Added vtx no %d to node %d for fixup\n",vx->no,nn->ix); +#endif + } + } + + /* Or locate the nearest node to the vertex. */ + /* (This only returns visible nodes) */ + if ((nn = ofps_findclosest_node(s, &ceperr, vx)) != NULL) { + double errimp = vx->eperr - ceperr; + + /* See if it is closer than the parent nodes */ + if (errimp >= vx->fuptol) { /* It is */ + + /* Add the vertexe to the "to be fixed" list */ +#ifdef DEBUG + printf("Vertex no %d hit by node ix %d by %e\n",vx->no,nn->ix,errimp); +#endif + hit = 1; + + if (vx->hnode == NULL) { + vx->hnode = nn; + vx->hitmarg = errimp; + + if (s->nsvtxs >= s->_nsvtxs) { + s->_nsvtxs = 2 * s->_nsvtxs + 5; + if ((s->svtxs = (vtx **)realloc(s->svtxs, sizeof(vtx *) * s->_nsvtxs)) == NULL) + error("ofps: malloc failed on svtxs%d", s->_nsvtxs); + } + s->svtxs[s->nsvtxs]= vx; + vx->psvtxs = &s->svtxs[s->nsvtxs]; + s->nsvtxs++; + + } else if (errimp > vx->hitmarg) { + vx->hnode = nn; + vx->hitmarg = errimp; + } +#ifdef DEBUG + printf("Added node %d to vtx no %d fixup\n",nn->ix, vx->no); +#endif + } + } + + next_vtx:; + if (hit) { + vx->fupcount++; + vx->fuptol *= 2.0; + nfuxups++; + } + + /* Remove this vertex from the check list */ + if (vx->pfchl != NULL) { /* If is on fixup check list, remove it */ + *vx->pfchl = vx->fchl; + if (vx->fchl != NULL) + vx->fchl->pfchl = vx->pfchl; + } + vx->pfchl = NULL; + vx->fchl = NULL; + } + if (s->fchl != NULL) + error("Check list should be empty!"); + + if (nfuxups < l_nfuxups) { + l_nfuxups = nfuxups; + csllow = 0; + } else { + csllow++; + } + +#ifdef DEBUG + printf("\nAbout to fixup %d marked vertexes\n",nfuxups); +#endif + /* Smallest error to largest seems best, */ + /* probably because the closer nodes cut off the */ + /* further ones, reducing the number of redundant create/deletes */ +#define HEAP_COMPARE(A,B) ((A)->hitmarg < (B)->hitmarg) + HEAPSORT(vtx *, s->svtxs, s->nsvtxs); +#undef HEAP_COMPARE + + /* Fixup the back references after the sort */ + for (i = 0; i < s->nsvtxs; i++) { + vx = s->svtxs[i]; + vx->psvtxs = &s->svtxs[i]; + } + + /* For each vertex on the "to be fixed" list, */ + /* search for hits by the node starting at that vertex, */ + /* and recursively locate all the hit vertexes. */ + for (i = 0; i < s->nsvtxs; i++) { + + if ((vx = s->svtxs[i]) == NULL) { + continue; /* Vertex got deleted by a previous fix */ + } + nn = vx->hnode; + s->svtxs[i] = NULL; + vx->psvtxs = NULL; + + s->nvcheckhits = 0; /* Count number of vertexes hit by recursive check. */ + s->batch = NULL; /* Nothing in pending delete list */ + s->nup = NULL; /* Nothing in nodes to be updated list */ + s->flag++; /* Marker flag for adding this node */ + s->nxh = NULL; /* Nothing in nodes hit list */ + +#ifdef DEBUG + printf("\nFixing up node ix %d starting at vx no %d\n",nn->ix,vx->no); +// fprintf(stderr,"Fixing up node ix %d starting at vx no %d\n",nn->ix,vx->no); +#endif + /* Recursively search for all vertexes hit by the new node */ + /* Note that we don't care that this only finds connected hits, */ + /* since there should be a separate s->svtxs[] entry for a hit by this */ + /* node on a disconnected region. */ + ofps_check_vtx(s, nn, vx, 100000, 0); + +#ifdef DEBUG + printf("Fixing up node ix %d, %d vertexes hit by it\n",nn->ix,s->nvcheckhits); +#endif + + /* Number of nodes that would be checked by exaustive search */ + s->vvpchecks += s->nv; + + /* Now re-add the node to the veronoi */ + if (add_to_vsurf(s, nn, 1, 0) > 0) { + s->add_hit++; +#ifdef DUMP_PLOT_EACHFIXUP + printf("After adding node ix %d at %s to vurf\n",nn->ix,ppos(di,nn->p)); + dump_image(s, PERC_PLOT, DO_WAIT, DUMP_VTX, DUMP_PLA, 0, -1); /* Device, No wait, verticies */ +#endif /* DUMP_PLOT_EACHFIXUP */ + } else { +#ifdef DUMP_PLOT_EACHFIXUP + printf("Adding node ix %d at %s to vurf was miss\n",nn->ix,ppos(di,nn->p)); +#endif /* DUMP_PLOT_EACHFIXUP */ + s->fadd_mis++; + } + } + s->nsvtxs = 0; + } /* Loop until there are no more vertexes to check */ + +#ifdef DEBUG + printf("Done fixups s->fchl 0x%x == NULL or csllow %d >= %d\n",s->fchl,csllow,mxcsllow); +#endif + +#ifdef SANITY_CHECK_FIXUP + /* Check that no node other than a parent is closer to any vertex */ + if (check_vertex_closest_node(s)) { +#ifdef SANITY_CHECK_FIXUP_FATAL + error("!!!!!! Sanity: Fixup didn't work"); +#endif /* SANITY_CHECK_FIXUP_FATAL */ + } +#endif /* SANITY_CHECK_FIXUP */ + +#ifdef DEBUG + printf("Applied fixups\n"); +#endif +} + +/* --------------------------------------------------- */ +/* After seeding or re-positioning, create the node */ +/* neighbour node and vertex lists. */ +/* (Invalidates and deletes any midpoints) */ +static void ofps_re_create_node_node_vtx_lists(ofps *s) { + int i, e, di = s->di; + vtx *vx; + + /* for each node, clear its vertex list */ + for (i = -s->gnp; i < s->np; i++) { + node *p = s->n[i]; + p->nvv = 0; + } + + /* For each vertex, add it to each of its parent nodes */ + for (vx = s->uvtx; vx != NULL; vx = vx->link) { + for (e = 0; e <= di; e++) { + node *p = s->n[vx->nix[e]]; + node_add_vertex(s, p, vx); + } + } + + /* For each node, recompute its neighbourhood nodes */ + for (i = -s->gnp; i < s->np; i++) { + node *p = s->n[i]; + node_recomp_nvn(s, p); /* Recompute the nodes associated vertex nodes */ + } +} + +/* --------------------------------------------------- */ +/* Midpoints */ + +/* Finding midpoint location code using dnsqe() */ + +/* Context for callback */ +typedef struct { + ofps *s; + node *nds[2]; /* List of nodes */ +} mopt_cx; + +/* calculate the functions at x[] */ +int dnsq_mid_solver( /* Return < 0 on abort */ + void *fdata, /* Opaque data pointer */ + int n, /* Dimenstionality */ + double *x, /* Multivariate input values */ + double *fvec, /* Multivariate output values */ + int iflag /* Flag set to 0 to trigger debug output */ +) { + mopt_cx *cx = (mopt_cx *)fdata; + ofps *s = cx->s; + int e, di = s->di; + double pos[MXPD], sv[MXPD]; + double cee[2], teperr; + +//printf("~1 dnsq_solver got %d nodes and %d planes\n",cx->nn,cx->nsp); + + /* Compute pos as interpolation between node 0 and 1 */ + for (e = 0; e < di; e++) + pos[e] = cx->nds[0]->p[e] * (1.0 - x[0]) + cx->nds[1]->p[e] * x[0]; + + ofps_cc_percept(s, sv, pos); + + /* Get eperr */ + cee[0] = ofps_comp_eperr8(s, NULL, sv, pos, cx->nds[0]->v, cx->nds[0]->p); + cee[1] = ofps_comp_eperr8(s, NULL, sv, pos, cx->nds[1]->v, cx->nds[1]->p); + +//printf("~1 error = %f, %f", cee[0], cee[1]); + + teperr = 0.5 * (cee[0] + cee[1]); + + fvec[0] = teperr - cee[0]; + +// printf("dnsq_mid_solver returning %f from %f\n",fvec[0],x[0]); + + return 0; +} + +/* Create or re-create all the midpoints, given the vertexes are done. */ +static void +ofps_create_mids(ofps *s) { + int e, di = s->di; + int i, j, k; + double rerr; + int nsp = 0; /* Number of surface points */ + double dnsqtol = 1e-6; /* Solution tollerance to aim for */ + vopt_cx cx; + double fvec[1]; + int rv; + + cx.s = s; + +//printf("~1 creating mid points\n"); + /* Clear any existing midpoints */ + for (i = 0; i < s->tinp; i++) { + node *p = s->n[i]; + + if (p->ix < 0) + break; /* Done when we get to gamut boundary nodes */ + + for (j = 0; j < p->nvn; j++) { + if (p->mm[j] != NULL) { + del_mid(s, p->mm[j]); + p->mm[j] = NULL; + } + } + } + + /* For each node, make sure it and each neighbor node have a shared midpoint */ + for (i = 0; i < s->tinp; i++) { + node *p = s->n[i]; + + if (p->ix < 0) + break; /* Done when we get to gamut boundary nodes */ + + /* For each neighbor node, create midpoint */ + for (j = 0; j < p->nvn; j++) { + mid *mp; + node *p2; + double ee[2]; + + if (p->vn[j] < 0 || p->mm[j] != NULL) + continue; /* Gamut boundary or already got a midpoint */ + + /* Create a midpoint between node p->ix and p->vn[j] */ + p2 = s->n[p->vn[j]]; + mp = new_mid(s); + mp->refc++; + + p->mm[j] = mp; + for (k = 0; k < p2->nvn; k++) { + if (p2->vn[k] == p->ix) { + p2->mm[k] = mp; + mp->refc++; + break; + } + } + + mp->nix[0] = p->ix; + mp->nix[1] = p->vn[j]; +//printf("~1 creating midpoint %d between nodes %d %d\n",mp->no,p->ix,p->vn[j]); + + cx.nds[0] = p; + cx.nds[1] = p2; + mp->np = 0.5; + + /* Locate mid point */ + if ((rv = dnsqe((void *)&cx, dnsq_mid_solver, NULL, 1, &mp->np, + 0.2, fvec, 0.0, dnsqtol, 0, 0)) != 1 && rv != 3) { + error("ofps: Locating midpoint failed with %d",rv); + } + + for (e = 0; e < di; e++) + mp->p[e] = p->p[e] * (1.0 - mp->np) + p2->p[e] * mp->np; + ofps_cc_percept(s, mp->v, mp->p); + + /* Compute the eperr's for midpoint */ + ofps_pn_eperr(s, mp->ce, ee, mp->v, mp->p, cx.nds, 2); + mp->eperr = ofps_eperr2(ee, 2); + mp->eserr = ofps_eserr2(mp->ce, ee, 2); +//printf("~1 location %s (%s) eperr %f\n",ppos(di,mp->p), ppos(di,mp->v), mp->eperr); + } + } +} + +/* --------------------------------------------------- */ +/* Statistics: Compute serr stats. */ + +static void ofps_stats(ofps *s) { + int e, di = s->di; + int i, j; + double acnt; + vtx *vx; + mid *mp; + +//printf("~1 stats called\n"); + s->mn = 1e80; + s->mx = -1e80; + s->av = 0.0; + acnt = 0.0; + + /* Vertex stats */ + for (vx = s->uvtx; vx != NULL; vx = vx->link) { + double es; + + if (vx->ghost) /* Skip a ghost (coincident) vertex */ + continue; + +#ifdef INDEP_SURFACE + /* Ignore vertexes that aren't full dimension. */ + if (sm_andtest(s, &s->sc[0].a_sm, &vx->vm) == 0) + continue; +#endif + es = vx->eserr; + if (es >= 0.0 && es < s->mn) + s->mn = es; + if (es > s->mx) + s->mx = es; + s->av += es; + acnt++; + } + + s->av /= acnt; + + /* Midpoint/node stats */ + for (s->smns = 1e6, mp = s->umid; mp != NULL; mp = mp->link) { + + if (mp->nix[0] < 0 || mp->nix[1] < 0 + || mp->eserr < 0.0) + continue; /* Skip fake points */ + + if (mp->eserr < s->smns) { + s->smns = mp->eserr; + } + } + s->smns *= 2.0; /* Error distance between nodes is double error to midpoint */ +} + +/* --------------------------------------------------- */ +/* Support accessing the list of generated sample points */ + +/* Reset the read index */ +static void +ofps_reset(ofps *s) { + s->rix = 0; +} + +/* Read the next non-fixed point value */ +/* Return nz if no more */ +static int +ofps_read(ofps *s, double *p, double *v) { + int e; + + /* Advance to next non-fixed point */ + while(s->rix < s->np && s->n[s->rix]->fx) + s->rix++; + + if (s->rix >= s->np) + return 1; + + /* Return point info to caller */ + for (e = 0; e < s->di; e++) { + if (p != NULL) + p[e] = s->n[s->rix]->p[e]; + if (v != NULL) + v[e] = s->n[s->rix]->v[e]; + } + s->rix++; + + return 0; +} + +/* --------------------------------------------------- */ + +/* Compute more optimum location for node amongst the surrounding */ +/* verticies. The result is put in ->np[] and ->nv[]. */ +/* The main aim is to minimize the maximum eserr of any vertex, */ +/* but moving away from low midpoint eserr's improves the */ +/* convergence rate and improves the eveness of the result. */ +static void comp_opt(ofps *s, int poi, double oshoot, double sep_weight) { + node *pp; /* Node in question */ + int e, di = s->di; + double radsq = -1.0; /* Span/radius squared */ + double rad; + double sum; + int i; + int bi = 0, bj = 0; + + pp = s->n[poi]; /* Node in question */ + + /* Move towards vertex with highest eserr approach */ + if (pp->nvv > 0) { + double aerr1, werr1, berr1, cnt1; /* Average, worst, best eserr from vertexes */ + int weix1, beix1; /* Worst, best error vertex index */ + double ov1[MXPD]; /* Optimization vector towards largest vertex error */ + double aerr2, werr2, berr2, cnt2; /* Average, worst, best eserr from midpoints */ + int weix2, beix2; /* Worst, best error midpoint index */ + double ov2[MXPD]; /* Optimization vector away from smallest midpoint error */ + + +//printf("\n --------------------------------\n"); +//printf("~1 Optimizing ix %d, %f %f\n",poi,pp->p[0],pp->p[1]); + + /* Compute the average and locate the largest error from verticies */ + for (aerr1 = cnt1 = 0.0, werr1 = -1.0, berr1 = 1e80, i = 0; i < pp->nvv; i++) { + vtx *vp = pp->vv[i]; + +#ifdef INDEP_SURFACE + /* Ingnore vertexes that are not visible to this node. */ + if (sm_vtx_node(s, vp, pp) == 0) { + continue; + } +#endif + aerr1 += vp->eserr; + cnt1++; + +//printf("~1 Vertex no %d at %f %f serr = %f\n",vp->no,vp->p[0],vp->p[1],vp->eserr); + if (vp->eserr > werr1) { + werr1 = vp->eserr; + weix1 = i; + } + if (vp->eserr < berr1) { + berr1 = vp->eserr; + beix1 = i; + } + } + + if (cnt1 > 0.0 && werr1 > NUMTOL && berr1 > NUMTOL) { + double wbf, bbf; + double towards = 0.8; /* Amount to weight vector towards from closest */ + + /* Compute a blend factor that takes the current */ + /* location towards the worst vertex error and */ + /* away from the best */ + aerr1 /= cnt1; + wbf = towards * (werr1 - aerr1)/werr1; + bbf = (1.0 - towards) * (aerr1 - berr1)/berr1; +// wbf = towards * (werr1 - aerr1)/aerr1; +// bbf = (1.0 - towards) * (aerr1 - berr1)/aerr1; + + for (e = 0; e < di; e++) + ov1[e] = wbf * (pp->vv[weix1]->p[e] - pp->p[e]) + + bbf * (pp->p[e] - pp->vv[beix1]->p[e]); +//printf("~1 moved %f %f towards vtx no %d at %f %f\n",ov1[0],ov1[2],pp->vv[weix1]->no,pp->vv[weix1]->p[0],pp->vv[weix1]->p[1]); + } else { + for (e = 0; e < di; e++) + ov1[e] = 0.0; + } + + /* Compute the average and locate the smallest error from midpoints */ + for (aerr2 = cnt2 = 0.0, werr2 = 1e80, berr2 = -1.0, i = 0; i < pp->nvn; i++) { + mid *mp = pp->mm[i]; + node *on = s->n[pp->vn[i]]; /* Other node involved */ + + if (mp == NULL || mp->nix[0] < 0 || mp->nix[1] < 0) + continue; /* Must be a fake gamut boundary node */ + +#ifdef INDEP_SURFACE + /* Ingnore nodes of higher dimension */ + if ((pp->pmask & on->pmask) != pp->pmask) { + continue; + } +#endif + aerr2 += mp->eserr; + cnt2++; +//printf("~1 plane no %d from node ix %d serr = %f\n",mp->no,on->ix,mp->eserr); + if (mp->eserr < werr2) { + werr2 = mp->eserr; + weix2 = i; + } + if (mp->eserr > berr2) { + berr2 = mp->eserr; + beix2 = i; + } + } + + if (cnt2 > 0.0 && werr2 > NUMTOL && berr2 > NUMTOL) { + double wbf, bbf; + double away = 0.8; /* Amount to weight vector away from closest */ + + /* Compute a blend factor that takes the current */ + /* location away from the worst plane error */ + aerr2 /= cnt2; + wbf = away * (aerr2 - werr2)/werr2; + bbf = (1.0 - away) * (berr2 - aerr2)/berr2; +// wbf = away * (aerr2 - werr2)/aerr2; +// bbf = (1.0 - away) * (berr2 - aerr2)/aerr2; + + for (e = 0; e < di; e++) + ov2[e] = wbf * (pp->p[e] - pp->mm[weix2]->p[e]) + + bbf * (pp->mm[beix2]->p[e] - pp->p[e]); +//printf("~1 moved %f %f away from node ix %d at %f %f\n",ov2[0],ov2[1],pp->vn[weix2],pp->mm[weix2]->p[0],pp->mm[weix2]->p[1]); + } else { + for (e = 0; e < di; e++) + ov2[e] = 0.0; + } +//printf("~1 ov1 = %f %f, ov2 = %f %f, sep weight %f\n",ov1[0], ov1[1], ov2[0], ov2[1], sep_weight); + + /* Move the node by the sum of the two vectors */ + for (e = 0; e < di; e++) + pp->np[e] = pp->p[e] + (1.0 - sep_weight) * ov1[e] + sep_weight * ov2[e]; +//printf("~1 moved node %d by %f %f\n",pp->ix, (1.0 - sep_weight) * ov1[0] + sep_weight * ov2[0],(1.0 - sep_weight) * ov1[1] + sep_weight * ov2[1]); + } + +//printf("~1 check moved by %f %f\n",pp->np[0] - pp->p[0], pp->np[1] - pp->p[1]); + /* Apply overshoot/damping */ + for (e = 0; e < di; e++) + pp->np[e] = pp->p[e] + (pp->np[e] - pp->p[e]) * oshoot; +//printf("~1 after overshoot of %f got %f %f\n",oshoot,pp->np[0],pp->np[1]); + + /* Clip the new location */ + ofps_clip_point10(s, pp->np, pp->np); + +#if defined(KEEP_SURFACE) || defined(INDEP_SURFACE) + if (pp->nsp > 0) { + confineto_gsurf(s, pp->np, pp->sp, pp->nsp); + } +#endif + /* Update perceptual */ + s->percept(s->od, pp->nv, pp->np); /* Was clipped above */ + + /* Compute how far the point has moved */ + /* (?? maybe should change this to change in average or max eserr ??) */ + for (sum = 0.0, e = 0; e < di; e++) { + double tt = pp->np[e] - pp->p[e]; + sum += tt * tt; +//printf("~1 total motion = %f\n",sqrt(sum)); + } + if (sum > s->mxmvsq) /* Track maximum movement */ + s->mxmvsq = sum; +} + +static void +ofps_optimize( +ofps *s +) { + int maxits; + int transitters; + double transpow; + double oshoot, ioshoot, foshoot; + double sepw, isepw, fsepw; + double stoptol; + int e, di = s->di; + int i, j; + + /* Default is "Good" */ + maxits = OPT_MAXITS; + transitters = OPT_TRANS_ITTERS; + transpow = OPT_TRANS_POW; + ioshoot = OPT_INITIAL_OVERSHOOT; + foshoot = OPT_FINAL_OVERSHOOT; + isepw = OPT_INITIAL_SEP_WEIGHT; + fsepw = OPT_FINAL_SEP_WEIGHT; + stoptol = OPT_STOP_TOL; + +#ifdef OPT_MAXITS_2 + /* Option is "Fast" */ + if (s->good == 0) { + maxits = OPT_MAXITS_2; + transitters = OPT_TRANS_ITTERS_2; + transpow = OPT_TRANS_POW_2; + ioshoot = OPT_INITIAL_OVERSHOOT_2; + foshoot = OPT_FINAL_OVERSHOOT_2; + isepw = OPT_INITIAL_SEP_WEIGHT_2; + fsepw = OPT_FINAL_SEP_WEIGHT_2; + stoptol = OPT_STOP_TOL_2; + } +#endif /* OPT_MAXITS_2 */ + + oshoot = ioshoot; + for (s->optit = 0; s->optit < maxits; s->optit++) { /* Up to maximum number of itterations */ + vtx *vx; + double bf = 1.0; + int nvxhits; + double hratio, thresh; + int doinc = 0; + + s->mxmvsq = 0.0; + if (s->optit < transitters) + bf = s->optit/(double)transitters; + bf = pow(bf, transpow); + oshoot = (1.0 - bf) * ioshoot + bf * foshoot; + sepw = (1.0 - bf) * isepw + bf * fsepw; + + /* Compute optimized node positions */ + for (i = 0; i < s->tinp; i++) { + + if (s->n[i]->fx) + continue; /* Ignore fixed points */ + + comp_opt(s, i, oshoot, sepw); + } + + /* Then update their positions to the optimized ones */ + for (i = 0; i < s->tinp; i++) { + node *pp = s->n[i]; + + if (pp->fx) + continue; /* Ignore fixed points */ + + ofps_rem_nacc(s, pp); /* Remove from spatial accelleration grid */ + + for (e = 0; e < di; e++) { + pp->op[e] = pp->p[e]; /* Record previous position */ + pp->p[e] = pp->np[e]; /* Move to optimized location */ + pp->v[e] = pp->nv[e]; + } + ofps_add_nacc(s, pp); /* Add to spatial acceleration grid */ + } + + /* Make sure that the optimized nodes don't accidentaly collide */ + for (i = 0; i < s->tinp; i++) { + node *pp = s->n[i]; + + if (pp->fx) + continue; /* Ignore fixed points */ + + for (j = 0; j < 20; j++) { /* Retry until not cooincident */ + int pci; /* Point list index */ + acell *cp; /* Acceleration cell */ + node *p1; + pci = ofps_point2cell(s, pp->v, pp->p); /* Grid index of cell of interest */ + + cp = &s->grid[pci]; + for (p1 = cp->head; p1 != NULL; p1 = p1->n) { + if (p1 == pp) + continue; + for (e = 0; e < di; e++) { + if (fabs(pp->p[e] - p1->p[e]) > COINTOL) + break; /* Not cooincident */ + } + if (e >= di) { /* Cooincident */ +#ifdef DEBUG + printf("Optimized node ix %d at %s collides with ix %d at %s - joggling it %d\n",pp->ix,ppos(di,pp->p),p1->ix,ppos(di,p1->p),i); + warning("Optimized node ix %d at %s collides with ix %d at %s - joggling it %d",pp->ix,ppos(di,pp->p),p1->ix,ppos(di,p1->p),i); +#endif + ofps_rem_nacc(s, pp); /* Remove from spatial accelleration grid */ + + /* Joggle it's position */ + for (e = 0; e < di; e++) { + if (pp->p[e] < 0.5) + pp->p[e] += d_rand(0.0, 1e-4); + else + pp->p[e] -= d_rand(0.0, 1e-4); + } + /* Ignore confine planes. Next itter should fix it anyway ? */ + ofps_clip_point10(s, pp->p, pp->p); + + /* Update perceptual (was clipped above) */ + s->percept(s->od, pp->v, pp->p); + + break; + } + } + if (p1 == NULL) + break; + } + if (j >= 20) + error("ofps_optimize: Assert, was unable to joggle cooincindent point"); + } + + /* Ideally the fixup method should create and delete fewer vertexes */ + /* than reseeding, hence always be faster, but in practice this doesn't */ + /* seem to be so. Perhaps this is because the fixups are being */ + /* done in a far from optimal order ? What this means is that often */ + /* for big movements reseeding will be faster. To get the best of both, */ + /* we try and estimate when the fixup method will break even with */ + /* reseeding, and switch over. */ + + /* Estimate how many vertexes will be hit by the move */ + nvxhits = ofps_quick_check_hits(s); + + /* Decide which way to go */ + thresh = 1.0/(di * di); + hratio = nvxhits/(double)s->nv; +//printf("~1 quick check of vertex hits = %d, ratio %f, threshold %f\n",nvxhits,hratio,thresh); + + /* Hmm. Re-seed seems to sometimes be slower than expected for > 3D, */ + /* so don't use it. */ + if (hratio < thresh && di < 4) { + doinc = 1; + } + +#ifdef FORCE_RESEED /* Force reseed after itteration */ + doinc = 0; +#else +# ifdef FORCE_INCREMENTAL /* Force incremental update after itteration */ + doinc = 1; +# endif +#endif + /* Incrementally update veronoi */ + if (doinc) { + + if (s->verb) + printf("Fixing up veronoi\n"); + + /* Re-position the vertexes, and fixup the veronoi */ + ofps_repos_and_fix_voronoi(s); + + /* Reseed the veronoi */ + } else { + + if (s->verb) + printf("Re-seeding\n"); + + /* remove nodes from the spatial acceleration grid. */ + for (i = 0; i < s->tinp; i++) { + node *pp = s->n[i]; + ofps_rem_nacc(s, pp); /* Remove from spatial accelleration grid */ + } + + /* And recompute veronoi, and add to spatial accelleration grid. */ + ofps_redo_voronoi(s); + } + ofps_re_create_node_node_vtx_lists(s); + ofps_create_mids(s); + + ofps_stats(s); + if (s->verb) { + printf("It %d: Maxmv = %f, MinPoint = %.3f, Min = %.3f, Avg. = %.3f, Max = %.3f, %.1f secs.\n",s->optit+1,sqrt(s->mxmvsq),s->smns,s->mn,s->av,s->mx,(msec_time() - s->l_mstime) / 1000.0); +#ifdef STATS + printf("Current vtx %d, created %d, deleted %d, positioned %d\n", s->nv,s->nvtxcreated - s->l_nvtxcreated,s->nvtxdeleted - s->l_nvtxdeleted, s->positions - s->l_positions); + s->l_positions = s->positions; + s->l_nvtxcreated = s->nvtxcreated; + s->l_nvtxdeleted = s->nvtxdeleted; +#endif + s->l_mstime = msec_time(); + } + +#ifdef DUMP_STRUCTURE + dump_node_vtxs(s, 1); +// { char buf[200]; sprintf(buf, "After itteration %d",s->optit+1); dump_node_vtxs2(s, buf); } + printf("=========================================================================\n"); +#endif +#ifdef DUMP_PLOT + dump_image(s, PERC_PLOT, DO_WAIT, DUMP_VTX, DUMP_PLA, 1, -1); /* Device, wait, verticies */ +#endif /* DUMP_PLOT */ + +#ifdef SANITY_RESEED_AFTER_FIXUPS + /* For debugging, replace the incremental fixed up veronoi with */ + /* a from scratch one. */ + + if (s->verb) + printf("Re-seeding after fixup:\n"); + + /* Save the current incremental vertexes */ + save_ivertexes(s); + + ofps_redo_voronoi(s); + ofps_re_create_node_node_vtx_lists(s); + ofps_create_mids(s); + + ofps_stats(s); + if (s->verb) { + printf("It %d: Maxmv = %f, MinPoint = %.3f, Min = %.3f, Avg. = %.3f, Max = %.3f, %.1f secs.\n",s->optit+1,sqrt(s->mxmvsq),s->smns,s->mn,s->av,s->mx,(msec_time() - s->l_mstime) / 1000.0); +#ifdef STATS + printf("Current vtx %d, created %d, deleted %d, positioned %d\n", s->nvtxcreated - s->l_nvtxcreated,s->nvtxdeleted - s->l_nvtxdeleted, s->positions - s->l_positions); + s->l_positions = s->positions; + s->l_nvtxcreated = s->nvtxcreated; + s->l_nvtxdeleted = s->nvtxdeleted; +#endif + s->l_mstime = msec_time(); + } + + /* Check that no node other than a parent is closer to any vertex */ + if (check_vertex_closest_node(s)) { + warning("Verify that re-seed leaves only parents closest to vertexes failed"); + } + + /* Check the incremental vertexes against the re-seeded vertexes */ + if (check_vertexes(s)) { + warning("Verify of incremental vertexes failed!"); + printf("Verify of incremental vertexes failed!\n"); + } else { + warning("Verify of incremental vertexes suceeded!"); + } +#ifdef DUMP_STRUCTURE + dump_node_vtxs(s, 1); +#endif +#ifdef DUMP_PLOT + dump_image(s, PERC_PLOT, DO_WAIT, DUMP_VTX, DUMP_PLA, 1, -1); /* Device, wait, verticies */ +#endif /* DUMP_PLOT */ +#endif /* SANITY_RESEED_AFTER_FIXUPS */ + + if (sqrt(s->mxmvsq) < stoptol) + break; + } +} + +/* ------------------------------------------------------------------------ */ +/* Main object creation/destruction */ + +/* Destroy ourselves */ +static void +ofps_del(ofps *s) { + int i, e, di = s->di; + + if (s->ufx != NULL) + free(s->ufx); + + /* Free our nodes */ + for (i = 0; i < s->np; i++) { + node_free(s, s->n[i]); + } + s->n -= s->gnp; /* Fixup offset */ + free(s->n); + free(s->_n); + + /* Any free vertexes */ + while (s->fvtx != NULL) { + vtx *p = s->fvtx; + s->fvtx = p->link; + free(p); + } + + /* Any other allocations */ + s->sob->del(s->sob); + if (s->combs != NULL) { + for (i = 0; i < s->_ncombs; i++) { + if (s->combs[i].v1 != NULL) + free(s->combs[i].v1); + if (s->combs[i].v2 != NULL) + free(s->combs[i].v2); + } + free(s->combs); + } + if (s->sc) + free(s->sc); + + if (s->svtxs != NULL) + free(s->svtxs); + + if (s->_grid != NULL) + free(s->_grid); + + if (s->acnl != NULL) + free(s->acnl); + + if (s->vtreep != NULL) + aat_adelete(s->vtreep); + + for (e = 0; e <= (di+1); e++) { + if (s->vtrees[e] != NULL) + aat_adelete(s->vtrees[e]); + } + + if (s->pcache != NULL) + s->pcache->del(s->pcache); + + free(s); +} + +/* Constructor */ +ofps *new_ofps( +int verb, /* Verbosity level, 1 = progress, 2 = warnings */ +int di, /* Dimensionality of device space */ +double ilimit, /* Ink limit (sum of device coords max) */ +int tinp, /* Total number of points to generate, including fixed */ +int good, /* 0 = fast, 1 = good */ +double dadaptation, /* Degree of adaptation to device characteristic 0.0 - 1.0 */ +double devd_wght, /* Device space weighting (if dad < 0) */ +double perc_wght, /* Perceptual space weighting (if dad < 0) */ +double curv_wght, /* Curvature weighting (if dad < 0) */ +fxpos *fxlist, /* List of existing fixed points (may be NULL) */ +int fxno, /* Number of existing fixes points */ +void (*percept)(void *od, double *out, double *in), /* Perceptual lookup func. */ +void *od /* context for Perceptual function */ +) { + return new_ofps_ex(verb, di, ilimit, NULL, NULL, tinp, good, + dadaptation, devd_wght, perc_wght, curv_wght, + fxlist, fxno, percept, od, 0, -1); +} + +/* Extended constructor */ +ofps *new_ofps_ex( +int verb, /* Verbosity level, 1 = progress, 2 = warnings */ +int di, /* Dimensionality of device space */ +double ilimit, /* Total ink limit (sum of device coords max) */ +double *imin, /* Ink limit - limit on min of p[], usually >= 0.0 (may be NULL) */ +double *imax, /* Ink limit - limit on min of p[], usually <= 1.0 (may be NULL) */ +int tinp, /* Total number of points to generate, including fixed */ +int good, /* 0 = fast, 1 = good */ +double dadaptation, /* Degree of adaptation to device characteristic 0.0 - 1.0 */ +double devd_wght, /* Device space weighting (if dad < 0) */ +double perc_wght, /* Perceptual space weighting (if dad < 0) */ +double curv_wght, /* Curvature weighting (if dad < 0) */ +fxpos *fxlist, /* List of existing fixed points (may be NULL) */ +int fxno, /* Number of existing fixes points */ +void (*percept)(void *od, double *out, double *in), /* Perceptual lookup func. */ +void *od, /* context for Perceptual function */ +int ntostop, /* Debug - number of points until diagnostic stop */ +int nopstop /* Debug - number of optimizations until diagnostic stop, -1 = not */ +) { + int i, e; + ofps *s; + long stime,ttime; + + stime = clock(); + + if ((s = (ofps *)calloc(sizeof(ofps), 1)) == NULL) + error ("ofps: malloc failed on new ofps"); + + if (di > MXPD) + error ("ofps: Can't handle di %d",di); + + s->verb = verb; + s->ntostop = ntostop; + s->nopstop = nopstop; + + if ((s->sob = new_sobol(di)) == NULL) + error ("ofps: new_sobol %d failed", di); + + if (s->verb) + printf("Degree of adaptation: %.3f\n", dadaptation); + + /* Set internal values explicitly */ + if (dadaptation < 0.0) { + s->devd_wght = devd_wght; + s->perc_wght = perc_wght; + s->curv_wght = curv_wght; + + /* Set values implicitly with adapation level */ + } else { + if (dadaptation > 1.0) + dadaptation = 1.0; + + /* Convert to internal numbers */ + s->perc_wght = ADAPT_PERCWGHT * dadaptation; + s->curv_wght = ADAPT_CURVWGHT * dadaptation * dadaptation; + s->devd_wght = 1.0 - s->perc_wght; + } + if (s->verb) + printf("Adaptation weights: Device = %.3f, Perceptual = %.3f, Curvature = %.3f\n", + s->devd_wght,s->perc_wght,s->curv_wght); + + s->di = di; + + if (tinp < fxno) /* Make sure we return at least the fixed points */ + tinp = fxno; + + s->fxno = fxno; /* Number of fixed points provided */ + s->tinp = tinp; /* Target total number of points */ + + /* Hack to workaround pathalogical case. At ilimit == di-2.0, we get > 32 bits */ + /* of mask for CMYK */ + if (di >= 3 + && ilimit >= (di-2.0 - 2 * ILIMITEPS) + && ilimit <= (di-2.0 + 2 * ILIMITEPS)) + ilimit = di-2.0 - 2 * ILIMITEPS; + + s->ilimit = ilimit; + + for (e = 0; e < di; e++) { + if (imin != NULL) + s->imin[e] = imin[e]; + else + s->imin[e] = 0.0; + + if (imax != NULL) + s->imax[e] = imax[e]; + else + s->imax[e] = 1.0; + } + + /* Compute an approximate half expected sample point spacing, */ + /* and setup seeding acceleration grid. */ + { + double vol = 1.0; + double eprange; + + for (e = 0; e < di; e++) + vol *= s->imax[e] - s->imin[e]; + + vol /= tinp; /* Approx vol per point */ + vol = pow(vol, 1.0/di); /* Distance per point */ + + s->surftol = SURFTOL * vol; +//printf("~1 surftol = %f\n",s->surftol); + } + +#ifdef STANDALONE_TEST + /* If no perceptual function given, use default */ + if (percept == NULL) { + s->percept = default_ofps_to_percept; + s->od = s; + } else { + s->percept = percept; + s->od = od; + } +#else + /* If no perceptual function given, use default */ +//warning("~1 new_ofps_ex() forcing default perceptual function"); + if (percept == NULL) { + s->percept = default_ofps_to_percept; + s->od = s; + } else { + s->percept = percept; + s->od = od; + } +#endif + + s->good = good; /* Fast/Good flag */ + s->lperterb = PERTERB_AMOUNT; +#ifdef OPT_MAXITS_2 + if (s->good == 0) + s->lperterb = PERTERB_AMOUNT_2; +#endif + s->ssurfpref = INITIAL_SURFACE_PREF; + s->esurfpref = FINAL_SURFACE_PREF; + + /* Init method pointers */ + s->reset = ofps_reset; + s->read = ofps_read; + s->stats = ofps_stats; + s->del = ofps_del; + + s->gnp = 2 * di + 1 + 2; /* Gamut boundary + inside/outside fake points */ + /* -1 to -2di-1 are fake boundary nodes indexes, */ + /* with -2di-1 being the ink limit boundary. */ + /* -2di-2 is the fake inside node. */ + /* -2di-3 is the fake outside node. */ + + /* Allocate the space for the target number of points */ + if ((s->_n = (node *)calloc(sizeof(node), s->gnp + s->tinp)) == NULL) + error ("ofps: malloc failed on sample nodes"); + if ((s->n = (node **)calloc(sizeof(node *), s->gnp + s->tinp)) == NULL) + error ("ofps: malloc failed on sample nodes"); + s->n += s->gnp; /* Allow -ve index for fake points */ + for (i = -s->gnp; i < s->tinp; i++) { + int bitp; + s->n[i] = &s->_n[i + s->gnp]; + s->n[i]->ix = i; + + bitp = 31 & (i + (i >> 4) + (i >> 8) + (i >> 12)); + s->n[i]->ixm = (1 << bitp); + } + + s->np = s->fnp = 0; + +#ifdef STATS + /* Save current counts to report stats after a pass */ + s->l_positions = s->positions; + s->l_nvtxcreated = s->nvtxcreated; + s->l_nvtxdeleted = s->nvtxdeleted; +#endif + s->l_mstime = msec_time(); + + /* Setup the eperr sorted trees */ + if ((s->vtreep = aat_anew(vtx_aat_cmp_eperr)) == NULL) + error("Allocating aat tree failed"); + + /* One sorted tree per number of surface planes */ + for (e = 0; e <= (di+1); e++) { + if ((s->vtrees[e] = aat_anew(vtx_aat_cmp_eserr)) == NULL) + error("Allocating aat tree failed"); + } + +#ifdef CACHE_PERCEPTUAL + ofps_init_pcache(s); +# endif /* CACHE_PERCEPTUAL */ + + /* Setup spatial acceleration grid */ + ofps_init_acc1(s); + + /* Initialse the empty veronoi etc. */ + ofps_binit(s); + + /* Setup spatial acceleration grid (2) */ + ofps_init_acc2(s); + + /* Setup the fixed points */ + ofps_setup_fixed(s, fxlist, fxno); + + if (fxno > 0 && tinp <= fxno) { /* There are no moveable points to create */ + + /* Add the fixed points */ + if (ofps_add_fixed(s)) { + s->del(s); + return NULL; + } + + if (s->verb && fxno > 0) { + ofps_stats(s); + printf("After fixed points: MinPoint = %.3f, Min = %.3f, Avg. = %.3f, Max = %.3f\n",s->smns,s->mn,s->av,s->mx); + } + } + + if (tinp > fxno) { /* There are movable points to create */ + + /* Add the fixed points and create the moveable points */ + ofps_seed(s); + ofps_re_create_node_node_vtx_lists(s); + ofps_create_mids(s); + + ofps_stats(s); + if (s->verb) { + printf("After seeding points: MinPoint = %.3f, Min = %.3f, Avg. = %.3f, Max = %.3f, %.1f secs\n",s->smns,s->mn,s->av,s->mx,(msec_time() - s->l_mstime) / 1000.0); + +#ifdef STATS + printf("Current vtx %d, created %d, deleted %d, positioned %d\n", s->nv,s->nvtxcreated - s->l_nvtxcreated,s->nvtxdeleted - s->l_nvtxdeleted, s->positions - s->l_positions); + s->l_positions = s->positions; + s->l_nvtxcreated = s->nvtxcreated; + s->l_nvtxdeleted = s->nvtxdeleted; +#endif + s->l_mstime = msec_time(); + } +# ifdef DUMP_STRUCTURE + printf("After seeding:\n"); + dump_node_vtxs(s, 1); +// dump_node_vtxs2(s, "After seeding"); +#else /* !DUMP_STRUCTURE */ +#ifdef SANITY_CHECK_CONSISTENCY + sanity_check(s, 1); +#endif +#endif /* !DUMP_STRUCTURE */ +#ifdef DUMP_PLOT + dump_image(s, PERC_PLOT, DO_WAIT, DUMP_VTX, DUMP_PLA, 1, -1); /* Device, No wait, no verticies */ +#endif /* DUMP_PLOT */ + +#ifdef DOOPT + /* Do the optimization */ + ofps_optimize(s); +#endif /* DOOPT */ + +# ifdef DUMP_STRUCTURE + printf("After optimization:\n"); + dump_node_vtxs(s, 1); +// dump_node_vtxs2(s, "After optimization"); +#else /* !DUMP_STRUCTURE */ +#ifdef SANITY_CHECK_CONSISTENCY + sanity_check(s, 1); +#endif +#endif /* !DUMP_STRUCTURE */ + ofps_stats(s); + if (s->verb) + printf("After optimization: MinPoint = %.3f, Min = %.3f, Avg. = %.3f, Max = %.3f\n",s->smns, s->mn,s->av,s->mx); +#ifdef DUMP_PLOT + dump_image(s, PERC_PLOT, DO_WAIT, DUMP_VTX, DUMP_PLA, 1, -1); /* Device, wait, verticies */ +#endif /* DUMP_PLOT */ + } + + ofps_reset(s); /* Reset read index */ + +#if defined(DEBUG) || defined(STATS) + { + vtx *vx; + int novtx = 0; + int totvtxverts = 0; + int maxvtxverts = 0; + vtx **svtxs; /* Sorted vertexes by number of vertexes */ + + ttime = clock() - stime; + printf("Execution time = %f seconds\n",ttime/(double)CLOCKS_PER_SEC); + + /* Look at the vertexes */ + for (novtx = 0, vx = s->uvtx; vx != NULL; vx = vx->link, novtx++) + ; + + if ((svtxs = (vtx **)malloc(sizeof(vtx *) * novtx)) == NULL) + error ("ofps: malloc failed on vertex pointer list"); + + /* Look at the vertexes */ + for (novtx = 0, vx = s->uvtx; vx != NULL; vx = vx->link, novtx++) { + + svtxs[novtx] = vx; + + totvtxverts += vx->nnv; + if (vx->nnv > maxvtxverts) + maxvtxverts = vx->nnv; + } + +#define HEAP_COMPARE(A,B) ((A)->nnv > (B)->nnv) + HEAPSORT(vtx *, svtxs, novtx); +#undef HEAP_COMPARE + +// printf("Top 20 vertexes per vertex:\n"); +// for (i = 0; i < 20 && i < novtx; i++) { +// printf(" Vtx no %d, no vtxs = %d\n",svtxs[i]->no,svtxs[i]->nnv); +// } + + fprintf(stderr,"Average vertexes per vertex %.1f, max %d\n",totvtxverts/(double)novtx,maxvtxverts); + fprintf(stderr,"Average hit vertexes per add %.1f\n",s->nhitv/(double)s->nsurfadds,s->maxhitv); + fprintf(stderr,"Total number of vertex = %d\n",novtx); + fprintf(stderr,"Total vertex positions = %d\n",s->positions); + fprintf(stderr,"Total dnsqs = %d\n",s->dnsqs); + fprintf(stderr,"Total function calls = %d\n",s->funccount); + fprintf(stderr,"Average dnsqs/position = %.2f\n",s->dnsqs/(double)s->positions); + fprintf(stderr,"Average function calls/dnsq = %.1f\n",s->funccount/(double)s->dnsqs); + fprintf(stderr,"Maximum function calls/dnsq = %d\n",s->maxfunc); + fprintf(stderr,"Average function calls/sucessful dnsq = %.2f\n",s->sucfunc/(double)s->sucdnsq); + fprintf(stderr,"Average function calls/position = %.1f\n",s->funccount/(double)s->positions); + fprintf(stderr,"Maximum tries for dnsq sucess %d\n",s->maxretries); + fprintf(stderr,"Number of position_vtx failures %d\n",s->posfails); + fprintf(stderr,"Vertex hit check efficiency = %.1f%%\n",100.0 * (1.0 - s->vvchecks/(double)s->vvpchecks)); + fprintf(stderr,"Average accell cells searched = %.2f\n",s->ncellssch/(double)s->naccsrch); + fprintf(stderr,"add_to_vsurf hit rate = %.1f%%\n",100.0 * s->add_hit/(s->add_hit + s->add_mis)); +#ifdef DOOPT + fprintf(stderr,"fixup add_to_vsurf hit rate = %.1f%%\n",100.0 * s->fadd_hit/(s->fadd_hit + s->fadd_mis)); + fprintf(stderr,"Vertex closest search efficiency = %.1f%%\n",100.0 * (1.0 - s->nvschd/(double)s->nvfschd)); + fprintf(stderr,"Node closest search efficiency = %.1f%%\n",100.0 * (1.0 - s->nnschd/(double)s->nnfschd)); +#endif + + free(svtxs); + } +#endif + + return s; +} + +/* =================================================== */ + +#ifdef STANDALONE_TEST + +/* Graphics Gems curve */ +static double gcurve(double vv, double g) { + if (g >= 0.0) { + vv = vv/(g - g * vv + 1.0); + } else { + vv = (vv - g * vv)/(1.0 - g * vv); + } + return vv; +} + + +static void sa_percept(void *od, double *p, double *d) { + double dd[2]; + + /* Default linear */ + p[0] = 100.0 * (dd[0] = d[0]); + p[1] = 100.0 * (dd[1] = d[1]); + + /* Normal non-linear test */ +// p[0] = 100.0 * gcurve(dd[0], -8.0); +// p[1] = 100.0 * gcurve(dd[1], 4.0); + + /* More extreme non-linear test */ + p[0] = 100.0 * gcurve(dd[0], -16.0); + p[1] = 100.0 * gcurve(dd[1], 8.0); + + /* An X break point to test curvature weighting */ +// if (dd[0] < 0.5) +// p[0] = 100.0 * 0.6 * dd[0]; +// else +// p[0] = 100.0 * (0.3 + 1.4 * (dd[0] - 0.5)); +// p[1] = 100.0 * dd[1]; + +// if (dd[0] < 0.0) +// dd[0] = 0.0; +// if (dd[1] < 0.0) +// dd[1] = 0.0; +// p[0] = 100.0 * pow(dd[0], 0.5); +// p[1] = 100.0 * pow(dd[1], 1.0); +// p[1] = 0.8 * p[1] + 0.2 * p[0]; + + /* One that causes dnsq failures due to ACCELL failure */ +// p[0] = gcurve(dd[0], -4.0); +// p[1] = gcurve(dd[1], 2.0); +// p[0] = 100.0 * gcurve(0.6 * p[0] + 0.4 * p[1], 2.0); +// p[1] = 100.0 * gcurve(0.1 * p[1] + 0.9 * p[1], -4.0); + +// p[0] = 100.0 * dd[0] * dd[0]; +// p[1] = 100.0 * dd[1] * dd[1]; +} + +int +main(argc,argv) +int argc; +char *argv[]; +{ + int npoints = 55; + int ntostop = 0; + int nopstop = 0; + ofps *s; + fxpos fx[4]; /* Any fixed points */ + int nfx = 0; + + error_program = argv[0]; + + printf("Standalone test of ofps, args are: no. of points, default %d, points to skip before diag. plots, optim passes to skip\n",npoints); + + if (argc > 1) + npoints = atoi(argv[1]); + + if (argc > 2) + ntostop = atoi(argv[2]); + + if (argc > 3) + nopstop = atoi(argv[3]); + + fx[0].p[0] = 0.5; + fx[0].p[1] = 0.5; + + fx[1].p[0] = 0.145722; + fx[1].p[1] = 0.0; + + fx[2].p[0] = 1.0; + fx[2].p[1] = 0.104414; + + nfx = 0; + + /* Create the required points */ + s = new_ofps_ex(1, 2, 1.5, NULL, NULL, npoints, 1, +// s = new_ofps_ex(1, 2, 2.5, NULL, NULL, npoints, 1, + SA_ADAPT, SA_DEVD_MULT, SA_PERC_MULT, SA_INTERP_MULT, + fx, nfx, sa_percept, (void *)NULL, ntostop, nopstop); + +#ifdef DUMP_PLOT + printf("Device plot (with verts):\n"); + dump_image(s, 0, DO_WAIT, 1, DUMP_PLA, 1, -1); + printf("Device plot:\n"); + dump_image(s, 0, DO_WAIT, 0, 0, 1, -1); + printf("Perceptual plot (with verts):\n"); + dump_image(s, 1, DO_WAIT, 1, DUMP_PLA, 1, -1); + printf("Perceptual plot:\n"); + dump_image(s, 1, DO_WAIT, 0, 0, 1, -1); +#endif /* DUMP_PLOT */ + + s->del(s); + + return 0; +} + +#endif /* STANDALONE_TEST */ + +#define WIDTH 400 /* Raster size for debug plots */ +#define HEIGHT 400 + +/* Utility - return a string containing the di coord */ +static char *pco(int di, int *co) { + static char buf[5][200]; + static int ix = 0; + int e; + char *bp; + + if (++ix >= 5) + ix = 0; + bp = buf[ix]; + + for (e = 0; e < di; e++) { + if (e > 0) + *bp++ = ' '; + sprintf(bp, "%d", co[e]); bp += strlen(bp); + } + return buf[ix]; +} + +/* Utility - return a string containing the di vector */ +static char *ppos(int di, double *p) { + static char buf[5][200]; + static int ix = 0; + int e; + char *bp; + + if (++ix >= 5) + ix = 0; + bp = buf[ix]; + + for (e = 0; e < di; e++) { + double val = p[e]; + /* Make -0.00000000 turn into 0.000 for cosmetics */ + if (val < 0.0 && val >-1e-9) + val = 0.0; + if (e > 0) + *bp++ = ' '; + sprintf(bp, "%f", val); bp += strlen(bp); + } + return buf[ix]; +} + +/* Utility - return a string containing the di+1 combination */ +static char *pcomb(int di, int *n) { + static char buf[5][200]; + static int ix = 0; + int e; + char *bp; + + if (++ix >= 5) + ix = 0; + bp = buf[ix]; + + for (e = 0; e <= di; e++) { + if (e > 0) + *bp++ = ' '; + sprintf(bp, "%d", n[e]); bp += strlen(bp); + } + return buf[ix]; +} + +/* Utility - return a string containing the eperr/eserr value */ +static char *peperr(double eperr) { + static char buf[5][200]; + static int ix = 0; + int e; + char *bp; + + if (++ix >= 5) + ix = 0; + bp = buf[ix]; + + if (eperr >= 1e50) + sprintf(bp,"%s", "Big"); + else + sprintf(bp,"%f",eperr); + return buf[ix]; +} + +/* --------------------------------------------------------------- */ +#if defined(DEBUG) || defined(DUMP_PLOT_SEED) || defined(DUMP_PLOT) + +/* Dump the current point positions to a plot window file */ +static void +dump_image( + ofps *s, + int pcp, /* Do perceptual plot */ + int dwt, /* Do wait for a key */ + int dvx, /* Dump voronoi verticies and mid points */ + int dpla, /* Dump node planes */ + int ferr, /* Show final error rather than seeding error */ + int noi /* -1 for general state, node of interest for particular */ +) { + int i, j, k, e, di = s->di; + double minx, miny, maxx, maxy; + static double *x1a = NULL; /* Previous sample locations */ + static double *y1a = NULL; + static double *x2a = NULL; /* Current sample locations */ + static double *y2a = NULL; + static char *_ntext, **ntext; + static int _n3 = 0; /* Current Voronoi verticies */ + static double *x3a = NULL; + static double *y3a = NULL; + static plot_col *mcols = NULL; + static char *_mtext, **mtext; + int n3; + static double *x4a = NULL; /* plane vectors */ + static double *y4a = NULL; + static double *x5a = NULL; + static double *y5a = NULL; + static plot_col *ocols = NULL; + static int _o4 = 0; + int o4; + + if (pcp != 0) { /* Perceptual range */ + vtx *vx; + minx = miny = 1e60; + maxx = maxy = -1e60; + for (vx = s->uvtx; vx != NULL; vx = vx->link) { + double v[MXPD]; + + if (vx->v[0] < minx) + minx = vx->v[0]; + if (vx->v[1] < miny) + miny = vx->v[1]; + if (vx->v[0] > maxx) + maxx = vx->v[0]; + if (vx->v[1] > maxy) + maxy = vx->v[1]; + } + } else { + minx = 0.0; /* Assume */ + miny = 0.0; + maxx = 1.0; + maxy = 1.0; + } + +#ifdef NEVER + /* Expand the range a little */ + minx -= 0.1 * (maxx - minx); + maxx += 0.1/1.1 * (maxx - minx); + miny -= 0.1 * (maxy - miny); + maxy += 0.1/1.1 * (maxy - miny); +#endif + + if (x1a == NULL) { + if ((x1a = (double *)malloc(s->tinp * sizeof(double))) == NULL) + error ("ofps: malloc failed x1a"); + if ((y1a = (double *)malloc(s->tinp * sizeof(double))) == NULL) + error ("ofps: malloc failed ya1"); + if ((x2a = (double *)malloc(s->tinp * sizeof(double))) == NULL) + error ("ofps: malloc failed x2a"); + if ((y2a = (double *)malloc(s->tinp * sizeof(double))) == NULL) + error ("ofps: malloc failed y2a"); + if ((_ntext = (char *)malloc(s->tinp * 10 * sizeof(char))) == NULL) + error ("ofps: malloc failed _ntext"); + if ((ntext = (char **)malloc(s->tinp * sizeof(char *))) == NULL) + error ("ofps: malloc failed ntext"); + for (i = 0; i < s->tinp; i++) + ntext[i] = _ntext + i * 10; + } + + /* Add sample node location */ + for (i = 0; i < s->np; i++) { + node *p = s->n[i]; + + if (pcp != 0) { + double ov[MXPD]; + ofps_cc_percept(s, ov, p->op); + x1a[i] = ov[0]; + y1a[i] = ov[1]; + x2a[i] = p->v[0]; + y2a[i] = p->v[1]; + } else { + x1a[i] = p->op[0]; + y1a[i] = p->op[1]; + x2a[i] = p->p[0]; + y2a[i] = p->p[1]; + } + sprintf(ntext[i],"%d",p->ix); +// sprintf(ntext[i],"",p->ix); + } + + if (dvx) { + vtx *vx; + mid *mp; + node *p = NULL; +// double rgb0[3] = { 0.0, 0.5, 0.5 }; /* "cool" */ +// double rgb1[3] = { 1.0, 0.5, 0.0 }; /* "warm" */ + double rgb0[3] = { 0.0, 1.0, 0.0 }; /* "cool" */ + double rgb1[3] = { 1.0, 0.0, 0.5 }; /* "warm" */ + double mine, maxe; /* Min and max vertex eserr */ + + if (noi >= 0) + p = s->n[noi]; + + if (x3a == NULL) { /* Initial allocation */ + _n3 = s->np * 4; + if ((x3a = (double *)malloc(_n3 * sizeof(double))) == NULL) + error ("ofps: malloc failed x3a"); + if ((y3a = (double *)malloc(_n3 * sizeof(double))) == NULL) + error ("ofps: malloc failed y3a"); + if ((mcols = (plot_col *)malloc(_n3 * sizeof(plot_col))) == NULL) + error ("ofps: malloc failed mcols"); + if ((_mtext = (char *)malloc(_n3 * 10 * sizeof(char))) == NULL) + error ("ofps: malloc failed _mtext"); + if ((mtext = (char **)malloc(_n3 * sizeof(char *))) == NULL) + error ("ofps: malloc failed mtext"); + for (i = 0; i < _n3; i++) + mtext[i] = _mtext + i * 10; + } + + /* Compute min & max serr for each vertex */ + mine = 1e6; + maxe = -1e6; + for (vx = s->uvtx; vx != NULL; vx = vx->link) { + if (vx->ghost) + continue; + if (vx->eserr > maxe) + maxe = vx->eserr; + if (vx->eserr > NUMTOL && vx->eserr < mine) + mine = vx->eserr; + } + if ((maxe - mine) < 10.0) + maxe = mine + 1.0; + + /* Add mid points */ + for (n3 = 0, mp = s->umid; mp != NULL; mp = mp->link, n3++) { + + if (n3 >= _n3) { /* need more space */ + _n3 = 2 * _n3 + 5; + if ((x3a = (double *)realloc(x3a, _n3 * sizeof(double))) == NULL) + error ("ofps: realloc failed x3a %d",_n3); + if ((y3a = (double *)realloc(y3a, _n3 * sizeof(double))) == NULL) + error ("ofps: realloc failed y3a"); + if ((mcols = (plot_col *)realloc(mcols, _n3 * sizeof(plot_col))) == NULL) + error ("ofps: realloc failed mcols"); + if ((_mtext = (char *)realloc(_mtext, _n3 * 10 * sizeof(char))) == NULL) + error ("ofps: realloc failed _mtext"); + if ((mtext = (char **)realloc(mtext, _n3 * sizeof(char *))) == NULL) + error ("ofps: realloc failed mtest"); + for (i = 0; i < _n3; i++) + mtext[i] = _mtext + i * 10; + } + if (pcp != 0) { + x3a[n3] = mp->v[0]; + y3a[n3] = mp->v[1]; + } else { + x3a[n3] = mp->p[0]; + y3a[n3] = mp->p[1]; + } + + /* Show mid points in grey */ + mcols[n3].rgb[0] = 0.85; + mcols[n3].rgb[1] = 0.85; + mcols[n3].rgb[2] = 0.85; + + sprintf(mtext[n3],""); + sprintf(mtext[n3],"%d",mp->no); +// sprintf(mtext[n3],"%d",(int)(mp->eserr + 0.5)); + } + + /* Add Voronoi verticies */ + for (vx = s->uvtx; vx != NULL; vx = vx->link, n3++) { + + if (n3 >= _n3) { /* need more space */ + _n3 = _n3 * 2 + 5; + if ((x3a = (double *)realloc(x3a, _n3 * sizeof(double))) == NULL) + error ("ofps: realloc failed x3a %d",_n3); + if ((y3a = (double *)realloc(y3a, _n3 * sizeof(double))) == NULL) + error ("ofps: realloc failed y3a"); + if ((mcols = (plot_col *)realloc(mcols, _n3 * sizeof(plot_col))) == NULL) + error ("ofps: realloc failed mcols"); + if ((_mtext = (char *)realloc(_mtext, _n3 * 10 * sizeof(char))) == NULL) + error ("ofps: realloc failed _mtext"); + if ((mtext = (char **)realloc(mtext, _n3 * sizeof(char *))) == NULL) + error ("ofps: realloc failed mtext"); + for (i = 0; i < _n3; i++) + mtext[i] = _mtext + i * 10; + } + if (pcp != 0) { + x3a[n3] = vx->v[0]; + y3a[n3] = vx->v[1]; + } else { + x3a[n3] = vx->p[0]; + y3a[n3] = vx->p[1]; + } + + /* Show the vertexes as warm to cold, depending on their eserr */ + if (p == NULL) { + double bf; + + bf = (vx->eserr - mine)/(maxe - mine); + if (bf < 0.0) + bf = 0.0; + if (bf > 1.0) + bf = 1.0; + + for (e = 0; e < 3; e++) + mcols[n3].rgb[e] = bf * rgb1[e] + (1.0 - bf) * rgb0[e]; + +//printf("~1 serr = %f, color = %f %f %f\n",vx->eserr, mcols[n3].rgb[0], mcols[n3].rgb[1], mcols[n3].rgb[2]); +// sprintf(mtext[n3],""); +// sprintf(mtext[n3],"%d",(int)(vx->eserr + 0.5)); + +#ifndef NEVER /* Vertex no */ + sprintf(mtext[n3],"%d",vx->no); +#endif + +#ifdef NEVER /* Vertex no and eserr */ + if (vx->eserr >= 1e50) + sprintf(mtext[n3],"%d:Big",vx->no); + else + sprintf(mtext[n3],"%d:%d",vx->no,(int)(vx->eserr + 0.5)); +#endif + +#ifdef NEVER /* eserr */ + if (vx->eserr >= 1e50) + sprintf(mtext[n3],"Big"); + else + sprintf(mtext[n3],"%d",(int)(vx->eserr + 0.5)); +#endif + + /* Highlight the vertcies of interest */ + } else { + for (j = 0; j < p->nvv; j++) { + if (p->vv[j] == vx) + break; + } + if (j < p->nvv) { /* Vertex associated with node of interest */ + mcols[n3].rgb[0] = 0.1; + mcols[n3].rgb[1] = 0.9; + mcols[n3].rgb[2] = 0.9; + + sprintf(mtext[n3],"%d",(int)(vx->eserr + 0.5)); + + } else { + mcols[n3].rgb[0] = 0.82; /* default color */ + mcols[n3].rgb[1] = 0.59; + mcols[n3].rgb[2] = 0.0; + + sprintf(mtext[n3],""); + } + } + } +#ifdef DUMP_EPERR /* Create .tiff of eperr */ + if (s->np >= s->ntostop) { + + unsigned char pa[WIDTH * 3]; + char *name = "ofps.tif"; + int width = WIDTH; + int height = HEIGHT; + int x, y; + TIFF *tif; + double pos[MXPD], vpos[MXPD]; + double rgb_low[3] = { 0.0, 1.0, 0.0 }; /* "low error" */ + double rgb_high[3] = { 1.0, 0.0, 0.0 }; /* "high error" */ + + if ((tif = TIFFOpen(name, "w")) == NULL) { + fprintf(stderr,"Failed to open output TIFF file '%s'\n",name); + exit (-1); + } + + TIFFSetField(tif, TIFFTAG_IMAGEWIDTH, width); + TIFFSetField(tif, TIFFTAG_IMAGELENGTH, height); + TIFFSetField(tif, TIFFTAG_ORIENTATION, ORIENTATION_TOPLEFT); + TIFFSetField(tif, TIFFTAG_SAMPLESPERPIXEL, 3); + TIFFSetField(tif, TIFFTAG_BITSPERSAMPLE, 8); + TIFFSetField(tif, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG); + TIFFSetField(tif, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_RGB); + TIFFSetField(tif, TIFFTAG_COMPRESSION, COMPRESSION_NONE); + + mine = 0.0; + + for (y = 0; y < height; y++) { + pos[1] = 1.0 - y/(height-1.0); + + /* Fill in pa[] with colors for this line */ + for (x = 0; x < width; x++) { + double ss; + unsigned char *dp; + double bf; + double beserr, eserr; + + dp = pa + x * 3; + pos[0] = x/(width-1.0); + dp[0] = dp[1] = dp[2] = 0; +//printf("~1 doing %d %d pos %f %f\n",x,y,pos[0],pos[1]); + + /* Lookup perceptual value at sample point location */ + ofps_cc_percept(s, vpos, pos); + + /* See if the sample is in gamut */ + for (ss = 0.0, e = 0; e < s->di; e++) { + if (pos[e] < s->imin[e] + || pos[e] > s->imax[e]) + break; + ss += pos[e]; + } + if (e < s->di || ss > (s->ilimit + ILIMITEPS)) { +//printf("~1 out of gamut\n"); + continue; + } + + /* We determine the eserr by evaluating eserr for */ + /* every node, and keeping the smallest. */ + /* (This could be speeded up by using nearest search function) */ + beserr = 1e80; + for (i = 0; i < s->np; i++) { + node *np = s->n[i]; + + eserr = ofps_comp_eperr9(s, NULL, vpos, pos, np->v, np->p); + if (eserr < beserr) + beserr = eserr; + } + bf = (beserr - mine)/(maxe - mine); +//printf("~1 beserr = %f, bf = %f\n",beserr,bf); + if (bf < 0.0) + bf = 0.0; + if (bf > 1.0) + bf = 1.0; + + for (e = 0; e < 3; e++) + dp[e] = (int)(255.0 * (bf * rgb_high[e] + (1.0 - bf) * rgb_low[e]) + 0.5); + + } + if (TIFFWriteScanline(tif, (tdata_t)pa, y, 0) < 0) { + fprintf(stderr,"WriteScanline Failed at line %d\n",y); + exit (-1); + } + } + (void) TIFFClose(tif); + } +#endif /* DUMP_EPERR */ + } + + /* Show veronoi planes by plotting the vertex network */ + if (dpla) { + vtx *vx1, *vx2; + + if (x4a == NULL) { + _o4 = s->tinp; + if ((x4a = (double *)malloc(_o4 * sizeof(double))) == NULL) + error ("ofps: malloc %d failed",_o4); + if ((y4a = (double *)malloc(_o4 * sizeof(double))) == NULL) + error ("ofps: malloc %d failed",_o4); + if ((x5a = (double *)malloc(_o4 * sizeof(double))) == NULL) + error ("ofps: malloc %d failed",_o4); + if ((y5a = (double *)malloc(_o4 * sizeof(double))) == NULL) + error ("ofps: malloc %d failed",_o4); + if ((ocols = (plot_col *)malloc(_o4 * sizeof(plot_col))) == NULL) + error ("ofps: malloc %d failed",_o4); + } + + /* Add normal planes then subd planes, so that subd are always on top */ + o4 = 0; +#ifdef INDEP_SURFACE + for (k = 0; k < 2; k++) /* Do two passes */ +#else + for (k = 0; k < 1; k++) +#endif + { + /* Add node planes */ + for (vx1 = s->uvtx; vx1 != NULL; vx1 = vx1->link) { + + /* Don't plot faces involving the fake inside or outside node */ + for (e = 0; e <= di; e++) { + if (vx1->nix[e] < -s->nbp) + break; + } + if (e <= di) + continue; + + for (j = 0; j < vx1->nnv; j++) { + vx2 = vx1->nv[j]; + + /* Don't plot faces involving the fake inside or outside node */ + for (e = 0; e <= di; e++) { + if (vx2->nix[e] < -s->nbp) + break; + } + if (e <= di) + continue; + +#ifdef INDEP_SURFACE + if (sm_andtest(s, &vx1->vm, &s->sc[0].a_sm) == 0 + || sm_andtest(s, &vx2->vm, &s->sc[0].a_sm) == 0) { /* Subd plane */ + if (k == 0) + continue; /* Doing non-zubd pass */ + } else { + if (k == 1) + continue; /* Doing subd pass */ + } +#endif + + if (o4 >= _o4) { /* need more space */ + _o4 *= 2; + if ((x4a = (double *)realloc(x4a, _o4 * sizeof(double))) == NULL) + error ("ofps: realloc x4a %d failed", _o4); + if ((y4a = (double *)realloc(y4a, _o4 * sizeof(double))) == NULL) + error ("ofps: realloc y4a %d failed", _o4); + if ((x5a = (double *)realloc(x5a, _o4 * sizeof(double))) == NULL) + error ("ofps: realloc x5a %d failed", _o4); + if ((y5a = (double *)realloc(y5a, _o4 * sizeof(double))) == NULL) + error ("ofps: realloc y5a %d failed", _o4); + if ((ocols = (plot_col *)realloc(ocols, _o4 * sizeof(plot_col))) == NULL) + error ("ofps: realloc y5a %d failed", _o4); + } + + if (pcp != 0) { + x4a[o4] = vx1->v[0]; + y4a[o4] = vx1->v[1]; + x5a[o4] = vx2->v[0]; + y5a[o4] = vx2->v[1]; + } else { + x4a[o4] = vx1->p[0]; + y4a[o4] = vx1->p[1]; + x5a[o4] = vx2->p[0]; + y5a[o4] = vx2->p[1]; + } + +#ifdef INDEP_SURFACE + /* Show the sub dimension outline in apricot */ + if (k == 1) { + ocols[o4].rgb[0] = 1.0; /* Apricot */ + ocols[o4].rgb[1] = 0.52; + ocols[o4].rgb[2] = 0.57; + } else +#endif + { + ocols[o4].rgb[0] = 0.5; /* Light Blue */ + ocols[o4].rgb[1] = 0.9; + ocols[o4].rgb[2] = 0.9; + } + o4++; + } + } + } + } + + if ((s->nopstop >= 0 && s->optit < s->nopstop) || s->np < s->ntostop) + dwt = 0; + + /* Plot the vectors */ + do_plot_vec2(minx, maxx, miny, maxy, + x1a, y1a, x2a, y2a, ntext, s->np, dwt, + x3a, y3a, mcols, mtext, dvx ? n3 : 0, + x4a, y4a, x5a, y5a, ocols, dpla ? o4 : 0); + +} + +#endif /* DEBUG || DUMP_PLOT */ + +/* ------------------------------------------------------------------- */ +#ifdef SANITY_RESEED_AFTER_FIXUPS + +/* Save the current used vertexes to the i_uvtx list, */ +/* so that they can be verified against the re-seeded vertexes */ +static void save_ivertexes(ofps *s) { + vtx *vx, *nvx; + + s->i_uvtx = NULL; + + for (vx = s->uvtx; vx != NULL; vx = nvx) { + nvx = vx->link; + + /* Remove the vertex from used and other lists */ + del_vtx1(s, vx); + + /* Add it to the i_uvtx list */ + vx->link = s->i_uvtx; + s->i_uvtx = vx; + } +} + +/* Check the incremental vertexes against the re-seeded vertexes */ +static int check_vertexes(ofps *s) { + int i, j, e, k, di = s->di; + vtx *v1, *v2; + int fail = 0; + + printf("Verifying incremental vertexes against re-seeded:\n"); + + /* For each reference (re-seeded) vertex */ + for (v1 = s->uvtx; v1 != NULL; v1 = v1->link) { + + /* Locate the equivalent incremental vertex */ + for (v2 = s->i_uvtx; v2 != NULL; v2 = v2->link) { + for (e = 0; e <= di; e++) { + if (v1->nix[e] != v2->nix[e]) + break; + } + if (e > di) + break; /* Found it */ + } + if (v2 == NULL) { + printf("Missing vertex no %d comb %s\n",v1->no,pcomb(di,v1->nix)); + fail = 1; + continue; + } + + /* Check the vertex location */ + for (e = 0; e < di; e++) { + if (fabs(v1->p[e] - v2->p[e]) > 1e-5) { + break; + } + } + if (e < di) { + printf("Vertex no %d (%d) comb %s in different location %s, should be %s\n",v1->no,v2->no,pcomb(di,v1->nix),ppos(di,v2->p),ppos(di,v1->p)); + fail = 1; + } + /* Check the eserr */ + if (fabs(v1->eserr - v2->eserr) > 1e-3) { + printf("Vertex no %d (%d) comb %s has different eserr %f, should be %f\n",v1->no,v2->no,pcomb(di,v1->nix),v2->eserr,v1->eserr); + fail = 1; + } + + /* Check setmask */ + if (!_sm_equal(s, &v1->vm, &v2->vm)) { + printf("Vertex no %d (%d) comb %s has different vm %s, should be %s\n",v1->no,v2->no,pcomb(di,v1->nix),psm(s,&v2->vm),psm(s,&v1->vm)); + fail = 1; + } + + /* Check that the vertex nets are the same */ + for (i = 0; i < v1->nnv; i++) { + vtx *vv1 = v1->nv[i]; + + for (j = 0; j < v2->nnv; j++) { + vtx *vv2 = v2->nv[j]; + + for (e = 0; e <= di; e++) { + if (vv1->nix[e] != vv2->nix[e]) + break; + } + if (e > di) + break; /* Found it */ + } + if (j >= v2->nnv) { + printf("Vertex no %d comb %s, i_ missing neighbour no %d comb %s\n",v1->no,pcomb(di,v1->nix),vv1->no,pcomb(di,vv1->nix)); + fail = 1; + } + } + for (j = 0; j < v2->nnv; j++) { + vtx *vv2 = v2->nv[j]; + + for (i = 0; i < v1->nnv; i++) { + vtx *vv1 = v1->nv[i]; + + for (e = 0; e <= di; e++) { + if (vv1->nix[e] != vv2->nix[e]) + break; + } + if (e > di) + break; /* Found it */ + } + if (i >= v1->nnv) { + printf("Vertex no %d comb %s, i_ extra neighbour no (%d) comb %s\n",v1->no,pcomb(di,v1->nix),vv2->no,pcomb(di,vv2->nix)); + fail = 1; + } + } + } + + /* For each incremental vertex, check that there is a corresponding re-seeded vertex */ + for (v2 = s->i_uvtx; v2 != NULL; v2 = v2->link) { + + for (v1 = s->uvtx; v1 != NULL; v1 = v1->link) { + for (e = 0; e <= di; e++) { + if (v1->nix[e] != v2->nix[e]) + break; + } + if (e > di) + break; /* Found it */ + } + if (v1 == NULL) { + printf("Extra vertex no (%d) comb %s\n",v2->no,pcomb(di,v2->nix)); + fail = 1; + } + } + + if (fail) + printf("Failed to verify incremental vertexes against re-seeded:\n"); + else + printf("Successfully verified incremental vertexes against re-seeded\n"); + + return fail; +} + +#endif /* SANITY_RESEED_AFTER_FIXUPS */ + +/* ------------------------------------------------------------------- */ +/* Do an exaustive, very slow check for missing vertexes */ +/* + This may be really, really, really slow. + + For every possible combination of di+1 nodes, + locate the corresponding vertex. If it is + locatable, check that no other node is closer to it. + If it meets these conditions, then check that it is in the veronoi surface. + */ +static void check_for_missing_vertexes(ofps *s) { + int e, di = s->di; + vtx *vx; + COMBO(co, MXPD+1, di+1, s->np + s->nbp); /* di-1 out of neighbor nodes combination counter */ + nodecomb vv; + int lsc = -100; + int isok = 1; + + printf("Doing exaustive check for missing vertexes:\n"); + + /* Mark all the vertexes so that we can tell if any are missed. */ + for (vx = s->uvtx; vx != NULL; vx = vx->link) { + vx->sch = 0; + } + + CB_INIT(co); + while (!CB_DONE(co)) { + int rl = 0; + + memset((void *)&vv, 0, sizeof(nodecomb)); + for (e = 0; e <= di; e++) { + vv.nix[e] = co[e] - s->nbp; + if (vv.nix[e] >= 0) + rl = 1; + } + if (rl == 0) + goto next_comb; /* No real nodes */ + + vv.vv = NULL; + vv.ceperr = 1e100; + + sort_nix(s, vv.nix); + + printf("Comb %s\n",pcomb(di, vv.nix)); + if (lsc != vv.nix[di]) { + fprintf(stderr,"digit %d\n",vv.nix[di]); + lsc = vv.nix[di]; + } + + if (position_vtx(s, &vv, 0, 0, 0) == 0) { + int ix; + double eperr; + node *nn; + + printf(" Located at %s (%s), eperr %f\n",ppos(di,vv.p),ppos(di,vv.v),vv.eperr); + + /* Check that the point is not out of gamut */ + if (ofps_in_dev_gamut(s, vv.p, NULL) < -s->surftol) { + printf(" vertex is out of gamut\n"); + goto not_valid; + } + + /* Check that no other vertex is closer */ + for (ix = 0; ix < s->np; ix++) { + for (e = 0; e <= di; e++) { + if (vv.nix[e] == ix) + break; + } + if (e <= di) + continue; /* Is a parent */ + + nn = s->n[ix]; + eperr = ofps_comp_eperr(s, NULL, nn->v, nn->p, vv.v, vv.p); + + printf(" eperr to ix %d is %f\n",nn->ix,eperr); + if (eperr < vv.eperr) { + printf("vertex is closer to node ix %d\n",nn->ix); + break; + } + } + if (ix >= s->np) { + + printf("Point %s is valid\n",pcomb(di,vv.nix)); + + /* see if we've created it */ + for (vx = s->uvtx; vx != NULL; vx = vx->link) { + + for (e = 0; e <= di; e++) { + if (vx->nix[e] != vv.nix[e]) + break; + } + if (e > di) + break; /* Found it */ + } + if (vx == NULL) { + printf("Can't find vertex %s at %s (%s)\n",pcomb(di,vv.nix),ppos(di,vv.p),ppos(di,vv.v)); + fprintf(stderr,"Can't find vertex %s at %s (%s)\n",pcomb(di,vv.nix),ppos(di,vv.p),ppos(di,vv.v)); + isok = 0; + } else { + vx->sch = 1; + printf("Found vertex no %d nix %s at %s (%s) OK\n",vx->no,pcomb(di,vv.nix),ppos(di,vv.p),ppos(di,vv.v)); + fprintf(stderr,"Found vertex no %d nix %s at %s (%s) OK\n",vx->no,pcomb(di,vv.nix),ppos(di,vv.p),ppos(di,vv.v)); + } + } + not_valid:; + } else { + printf(" Failed to locate %s\n",pcomb(di,vv.nix)); + } + next_comb:; + CB_INC(co); + } + for (vx = s->uvtx; vx != NULL; vx = vx->link) { + if (vx->sch == 0) { + for (e = 0; e <= di; e++) { + if (vx->nix[e] < -s->nbp) /* involves inside or outside fake point */ + break; + } + if (e <= di) + continue; /* Ignore */ + printf("Extra vertex no %d nix %s at %s (%s) OK\n",vx->no,pcomb(di,vx->nix),ppos(di,vx->p),ppos(di,vx->v)); + fprintf(stderr,"Extra vertex no %d nix %s at %s (%s) OK\n",vx->no,pcomb(di,vx->nix),ppos(di,vx->p),ppos(di,vx->v)); + isok = 0; + } + } + if (isok) { + printf("Check for missing veftexes is OK\n"); + fprintf(stderr,"Check for missing veftexes is OK\n"); + } else { + printf("Check for missing veftexes FAILED\n"); + fprintf(stderr,"Check for missing veftexes FAILED\n"); + } +} + +/* ------------------------------------------------------------------- */ +/* Check the veronoi to check that no node other than the parent */ +/* node is closer to any vertex. */ +/* return nz if there is a problem */ +static int check_vertex_closest_node(ofps *s) { + int i, e, di = s->di; + node *nn, *pp; + vtx *vx; + + /* Check that no node other than a parent is closer to any vertex */ + for (vx = s->uvtx; vx != NULL; vx = vx->link) { + double ceperr; + + if (vx->ofake) + continue; + + /* Check if the vertex position is clipped by a gamut boundary. */ + for (i = 0; i < s->nbp; i++) { + pleq *vp = &s->gpeqs[i]; + double v; + + pp = s->n[-1-i]; +#ifdef INDEP_SURFACE + /* Check if this vertex is visible to this node */ + if (sm_vtx_node(s, vx, pp) == 0) { + continue; /* It's hidden */ + } +#endif /* INDEP_SURFACE */ + + for (v = vp->pe[di], e = 0; e < di; e++) + v += vp->pe[e] * vx->p[e]; + if (v > 2.0 * NUMTOL) { + /* Check whether pp is already a parent of the node */ + for (e = 0; e <= di; e++) { + if (pp->ix == vx->nix[e]) + break; + } + if (e <= di) + continue; /* It is */ + +#ifdef DEBUG + printf("Vertex %d parents %s is clipped by boundary node %d by %e\n", vx->no,pcomb(di,vx->nix),pp->ix,v); +#endif + warning("Vertex %d parents %s is clipped by boundary node %d by %e", vx->no,pcomb(di,vx->nix),pp->ix,v); + return 1; + } + } + + /* locate the nearest node to the vertex */ + if ((nn = ofps_findclosest_node(s, &ceperr, vx)) == NULL) + continue; + + /* See if it is closer than the parent nodes */ + if ((vx->eperr - ceperr) < 2.0 * NUMTOL) + continue; /* No it's not */ + + /* Check whether nn is already a parent of the node */ + for (e = 0; e <= di; e++) { + if (nn->ix == vx->nix[e]) + break; + } + if (e <= di) + continue; /* A parent */ + +#ifdef DEBUG + printf("Vertex %d is closer to %d (%f) than parent nodes %s (%f) by %e\n",vx->no,nn->ix,ceperr,pcomb(di,vx->nix),vx->eperr, ceperr - vx->eperr); +#endif + warning("Vertex %d is closer to %d (%f) than parent nodes %s (%f) by %e",vx->no,nn->ix,ceperr,pcomb(di,vx->nix),vx->eperr, ceperr - vx->eperr); + return 1; + } + fflush(stdout); + + return 0; +} + +/* ------------------------------------------------------------------- */ + +#if defined(DEBUG) || defined(DUMP_PLOT) || defined (SANITY_CHECK_CONSISTENCY) || defined(DUMP_STRUCTURE) +/* Do some sanity checking on the points */ +static void +sanity_check( + ofps *s, + int check_nodelists /* nz to check node lists */ +) { + int i, j, k, e, di = s->di; + vtx *vx1, *vx2; + int fail = 0; /* 0 = pass, 1 = soft fail, 2 = hard fail */ + +#ifdef DEBUG + printf("Running sanity check...\n"); +#endif + + /* See if any of the sample nodes are near the same location */ + for (i = 0; i < (s->np-1); i++) { + node *p1 = s->n[i]; + for (j = i+1; j < s->np; j++) { + node *p2 = s->n[j]; + double rad; + for (rad = 0.0, e = 0; e < di; e++) { + double tt = p1->p[e] - p2->p[e]; + rad += tt * tt; + } + rad = sqrt(rad); + if (rad < 1e-5) { +#ifdef DEBUG + printf("Nodes ix %d and ix %d are at %s and %s\n", i,j,ppos(di,p1->p),ppos(di,p2->p)); +#endif + fail = 2; + } + } + } + + /* See if any of the vertexes have the same node combinations */ + for (vx1 = s->uvtx; vx1 != NULL; vx1 = vx1->link) + vx1->sch = 0; + + for (vx1 = s->uvtx; vx1 != NULL; vx1 = vx1->link) { + if (vx1->sch) + continue; + for (vx2 = vx1->link; vx2 != NULL; vx2 = vx2->link) { + if (vx2->sch) + continue; + for (e = 0; e <= di; e++) { + if (vx1->nix[e] != vx2->nix[e]) + break; + } + if (e > di) { + vx1->sch = vx2->sch = 1; /* Don't do these again */ +#ifdef DEBUG + printf("Vertex ix %d and ix %d have same nix %s\n", vx1->no,vx2->no,pcomb(di,vx1->nix)); +#endif + fail = 2; + } + } + } + + /* See if any of the vertexes are at the same location */ + for (vx1 = s->uvtx; vx1 != NULL; vx1 = vx1->link) + vx1->sch = 0; + + for (vx1 = s->uvtx; vx1 != NULL; vx1 = vx1->link) { + if (vx1->sch) + continue; + for (vx2 = vx1->link; vx2 != NULL; vx2 = vx2->link) { + double rad; + if (vx2->sch) + continue; + for (rad = 0.0, e = 0; e < di; e++) { + double tt = vx1->p[e] - vx2->p[e]; + rad += tt * tt; + } + rad = sqrt(rad); + if (rad < 1e-10) { + vx1->sch = vx2->sch = 1; /* Don't do these again */ +#ifdef DEBUG + printf("Vertex no %d nix %s vm %s and no %d nix %s vm %s are at %s and %s", vx1->no,pcomb(di,vx1->nix),psm(s,&vx1->vm),vx2->no,pcomb(di,vx2->nix),psm(s,&vx2->vm),ppos(di,vx1->p),ppos(di,vx2->p)); + if (fabs(vx1->eperr - vx2->eperr) > 1e-5) + printf(" and errs %f %f\n",vx1->eperr,vx2->eperr); + else + printf("\n"); +#endif + /* See if the two vertexes are both visible to each other */ + if (sm_vtx_vtx(s, vx1, vx2) != 0) { + fail = 2; + } + } + } + } + + /* See if any of the nodes and vertexes are near the same location */ + for (i = 0; i < s->np; i++) { + node *p1 = s->n[i]; + for (vx1 = s->uvtx; vx1 != NULL; vx1 = vx1->link) { + double rad; + for (rad = 0.0, e = 0; e < di; e++) { + double tt = p1->p[e] - vx1->p[e]; + rad += tt * tt; + } + rad = sqrt(rad); + if (rad < 1e-5) { +#ifdef DEBUG + printf("Node ix %d and Vertex no %d are at %s and %s%s", i,vx1->no,ppos(di,p1->p),ppos(di,vx1->p),vx1->ghost ? " (ghost)" : ""); + if (vx1->eperr > 1e-5) + printf(" and err %f\n",vx1->eperr); + else + printf("\n"); +#endif + if (vx1->ghost == 0) + fail = 1; + } + } + } + + /* Check every node appears in at least one vertex */ + for (i = 0; i < s->np; i++) { /* For all nodes */ + node *p1 = s->n[i]; + vtx *vx; + for (vx = s->uvtx; vx != NULL; vx = vx->link) { + for (e = 0; e <= di; e++) { + if (vx->nix[e] == p1->ix) + break; /* yes */ + } + if (e <= di) + break; /* yes */ + } + if (vx == NULL) { +#ifdef DEBUG + printf("Node ix %d has no vertexes that refer to it\n", p1->ix); +#endif + fail = 2; + } + } + + if (check_nodelists) { + /* See if any vertexes do not appear in their constituent nodes */ + /* vertex list, or whether verexes nodes don't appear in neighbour list. */ + for (vx1 = s->uvtx; vx1 != NULL; vx1 = vx1->link) { + for (e = 0; e <= di; e++) { + int ix = vx1->nix[e]; + node *pp = s->n[ix]; + + for (j = 0; j < pp->nvv; j++) { + if (pp->vv[j] == vx1) + break; + } + if (j >= pp->nvv) { +#ifdef DEBUG + printf("Vertex no %d nix %s doesn't appear in node ix %d\n", vx1->no,pcomb(di,vx1->nix),pp->ix); +#endif + fail = 2; + } + } + } + } + + /* Check that every vertex of a node contains that node. */ + for (i = 0; i < s->np; i++) { /* For all nodes */ + node *p1 = s->n[i]; + for (j = 0; j < p1->nvv; j++) { /* For all its vertexes */ + vtx *vx = p1->vv[j]; + + for (e = 0; e <= di; e++) { /* All vertexes parent nodes */ + int ix = vx->nix[e]; + node *pp = s->n[ix]; + + if (ix == p1->ix) + break; + } + if (e > di) { +#ifdef DEBUG + printf("Node ix %d has vtx no %d nix %s that doesn't contain node\n", p1->ix, vx->no,pcomb(di,vx->nix)); +#endif + fail = 2; + } + } + } + + if (check_nodelists) { + /* Check that a node contains as neighbours all the parent */ + /* nodes of its vertexes */ + for (i = 0; i < s->np; i++) { /* For all nodes */ + node *p1 = s->n[i]; + for (j = 0; j < p1->nvv; j++) { /* For all its vertexes */ + vtx *vx = p1->vv[j]; + + for (e = 0; e <= di; e++) { + int ix = vx->nix[e]; + node *pp = s->n[ix]; + + if (ix == p1->ix) + continue; /* Neighbours don't include self */ + for (k = 0; k < p1->nvn; k++) { + if (p1->vn[k] == ix) + break; + } + if (k >= p1->nvn) { +#ifdef DEBUG + printf("Node ix %d has vtx no %d nix %s where neighbour ix %d is missing\n", p1->ix, vx->no,pcomb(di,vx->nix),ix); +#endif + fail = 2; + } + } + } + } + } + + /* Check that the vertex net is correct */ + { + int ff, f, e, di = s->di; + vtx *vx1, *vx2; + int nnv = 0; + int _nnv = 0; + struct _vtx **nv = NULL; + + /* Do a brute force search to locate all this vertexes net neighbours */ + for (vx1 = s->uvtx; vx1 != NULL; vx1 = vx1->link) { + + nnv = 0; /* Clear the current list */ + + /* Search all other vertexes for neighbours */ + for (vx2 = s->uvtx; vx2 != NULL; vx2 = vx2->link) { + int aa, bb, cc; /* Probable hit check */ + int nnm, nmix; + + if (vx1 == vx2) + continue; + +#ifdef NEVER /* vertex net needs all neighbours ? */ +#ifdef INDEP_SURFACE + if (sm_vtx_vtx(s, vx1, vx2) == 0) + continue; +#endif /* INDEP_SURFACE */ +#endif + + /* Use the nixm to quickly check if all but one parent node matches */ + aa = vx1->nix[MXPD+2]; /* nixm */ + bb = vx2->nix[MXPD+2]; /* nixm */ + if ((aa & bb) == 0 || (cc = aa & ~bb, (cc & (cc-1)) != 0)) + continue; /* It's certainly not */ + + /* Do an exact check of all except one node match */ + for (nnm = ff = e = 0; e <= di; e++) { + for (f = ff; f <= di; f++) { + if (vx1->nix[e] == vx2->nix[f]) { + ff = f; /* Start from here next time */ + break; + } + if (vx1->nix[e] > vx2->nix[f]) /* No point in looking further */ + f = di; + } + if (f > di) { /* Didn't match */ + if (++nnm > 1) + break; + nmix = e; + } + } + if (e <= di) + continue; /* No match */ + + if (nnm == 0) { + error("ofps: two vertexes have the same nodes !\n" + "no %d at %s nix %s\nno %d at %s nix %s", + vx1->no,ppos(di,vx1->p),pcomb(di,vx1->nix), + vx2->no,ppos(di,vx2->p),pcomb(di,vx2->nix)); + } + if (nnv >= _nnv) { + _nnv = 2 * _nnv + 1; + if ((nv = (vtx **)realloc(nv, sizeof(vtx *) * _nnv)) == NULL) + error("ofps: realloc failed on node vertex pointers"); + } + nv[nnv++] = vx2; + } + + /* Now check that the vertex nets match */ + for (i = 0; i < nnv; i++) { + for (j = 0; j < vx1->nnv; j++) { + if (nv[i] == vx1->nv[j]) + break; + } + if (j >= vx1->nnv) { + printf("Vtx no %d is missing vtx no %d from net\n",vx1->no,nv[i]->no); + fail = 2; + } + } + for (j = 0; j < vx1->nnv; j++) { + for (i = 0; i < nnv; i++) { + if (nv[i] == vx1->nv[j]) + break; + } + if (i >= nnv) { + printf("Vtx no %d has extra vtx no %d in net\n",vx1->no,vx1->nv[j]->no); + fail = 2; + } + } + } + } + if (fail) { + if (fail == 1) + warning("Internal consistency check failed (soft)"); + else + warning("Internal consistency check failed"); +#ifdef DEBUG + if (fail == 1) + printf("Internal consistency check failed (soft)\n"); + else + printf("Internal consistency check failed\n"); + fflush(stdout); +#endif +#ifdef SANITY_CHECK_CONSISTENCY_FATAL + if (fail == 2) + error("Internal consistency check failed"); +#endif + } + +#ifdef SANITY_CHECK_EXAUSTIVE_SEARCH_FOR_VERTEXES + check_for_missing_vertexes(s); +#endif +} +#endif /* SANITY_CHECK_CONSISTENCY */ + +#if defined(DEBUG) || defined(DUMP_STRUCTURE) + +/* ------------------------------------------------------------------- */ + +/* Dump the node & vertex relationship */ +static void +dump_node_vtxs( + ofps *s, + int check_nodelists +) { + int i, j, e, di = s->di; + vtx *vx; + + printf("\n"); + printf("Dumping current state...\n"); + + /* Dump node information */ + for (i = -s->gnp; i < s->np; i++) { + node *p1 = s->n[i]; + printf("Node ix %d, pos %s, mask 0x%x, asm %s\n",p1->ix,ppos(di, p1->p),p1->pmask,psm(s,&s->sc[p1->pmask].a_sm)); + } + printf("\n"); + + /* Dump vertex information */ + for (vx = s->uvtx; vx != NULL; vx = vx->link) { + if (vx->ofake == 0) + printf("Vertex no %d, pmask 0x%x, cmask 0x%x, vm %s\n pos %s, nix %s, eperr = %s, eserr = %s\n",vx->no,vx->pmask,vx->cmask,psm(s,&vx->vm), ppos(di, vx->p), pcomb(di,vx->nix), peperr(vx->eperr), peperr(vx->eserr)); + else + printf("Vertex no %d, pmask 0x%x, cmask 0x%x, vm %s, nix %s, eperr = %s, eserr = %s (ofake)\n",vx->no,vx->pmask,vx->cmask,psm(s,&vx->vm), pcomb(di,vx->nix), peperr(vx->eperr), peperr(vx->eserr)); + } + printf("\n"); + + /* Dump vertex and associated vertex information */ + for (vx = s->uvtx; vx != NULL; vx = vx->link) { + printf("Vertex no %d has Vtx net:",vx->no); + for (j = 0; j < vx->nnv; j++) { + vtx *vx2 = vx->nv[j]; + printf(" %d",vx2->no); + } + printf("\n"); + } + printf("\n"); + + /* Dump node and associated vertex information */ + for (i = -s->nbp; i < s->np; i++) { + node *p1 = s->n[i]; + printf("Node ix %d, pos %s, mask 0x%x, a_sm %s:\n",p1->ix,ppos(di, p1->p),p1->pmask,psm(s,&s->sc[p1->pmask].a_sm)); + for (j = 0; j < p1->nvv; j++) { + vtx *vx = p1->vv[j]; + if (vx->ofake == 0) + printf(" Vtx no %d pmask 0x%x cmask 0x%x vm %s pos %s nix %s eserr %s\n",vx->no,vx->pmask,vx->cmask,psm(s,&vx->vm),ppos(di, vx->p), pcomb(di, vx->nix), peperr(vx->eserr)); + else + printf(" Vtx no %d pmask 0x%x cmask 0x%x vm %s nix %s eserr %s (ofake)\n",vx->no,vx->pmask,vx->cmask,psm(s,&vx->vm),pcomb(di,vx->nix), peperr(vx->eserr)); + } + for (j = 0; j < p1->nvn; j++) { + int ix = p1->vn[j]; + if (ix >= 0) { + node *n1 = s->n[ix]; + printf(" Assoc. node ix %d pos %s\n",ix,ppos(di, n1->p)); + } else { + printf(" Assoc. node ix %d\n",ix); + } + } + } + printf("\n"); + + sanity_check(s, check_nodelists); + + fflush(stdout); +} + +#ifdef NEVER +/* Special dump the node & vertex relationship, */ +/* for comparing with "good" output. */ +/* Deal with vertexe order and numbering. */ +/* Note that the "new" "bad" code needs ofake vertexes */ +/* to work, wheras the "old" "good" code doesn't, so */ +/* skip reporting ofake vetexes. */ +static void +dump_node_vtxs2( + ofps *s, + char *com +) { + int i, j, e, di = s->di; + vtx *vx; + FILE *fp; + static int cc = 0; + int showofake = 1; + vtx **vlist; + + if (cc == 0) { + if ((fp = fopen("bad.log","w")) == NULL) + error("Unable to open file '%s'\n","bad.log"); + cc = 1; + } else { + if ((fp = fopen("bad.log","a")) == NULL) + error("Unable to open file '%s'\n","bad.log"); + } + + fprintf(fp,"\n"); + fprintf(fp,"Dumping current state (%s) ...\n",com); + + /* Dump node information */ + for (i = -s->gnp; i < s->np; i++) { + node *p1 = s->n[i]; + fprintf(fp,"Node ix %d, pos %s, mask 0x%x, asm %s\n",p1->ix,ppos(di, p1->p),p1->pmask,psm(s,&s->sc[p1->pmask].a_sm)); + } + fprintf(fp,"\n"); + + /* Sort the vertexes by their nix */ + { + int scl = s->gnp + s->np; + int nv; + + int nused; + for (nused = 0, vx = s->uvtx; vx != NULL; vx = vx->link) + nused++; +if (nused != s->nv) error("s->nv %d doesn't match uvtx list %d",s->nv,nused); + +//printf("~1 number of vertexes = %d\n",s->nv); + if ((vlist = (vtx **)malloc(sizeof(vtx *) * s->nv)) == NULL) + error ("ofps: malloc failed on sorted vertex list"); + for (i = nv = 0, vx = s->uvtx; vx != NULL; vx = vx->link, i++) { + if (!showofake && vx->ofake) + continue; + vlist[nv++] = vx; + + /* Convert nix into sort index */ + vx->sch = 0; + for (e = 0; e <= di; e++) { + vx->sch = scl * vx->sch + (vx->nix[e] + s->gnp); + } +//printf("~1 nix %s ix %d\n",pcomb(di,vx->nix),vx->sch); +//fflush(stdout); + } + + /* Sort */ +#define HEAP_COMPARE(A,B) ((A)->sch < (B)->sch) + HEAPSORT(vtx *, vlist, nv); +#undef HEAP_COMPARE + + for (i = 0; i < nv; i++) { + vx = vlist[i]; + vx->sch = i; /* Sorted index */ + } + + /* Dump vertex information */ + for (i = 0; i < nv; i++) { + vx = vlist[i]; + fprintf(fp,"Vertex no %d, pmask 0x%x, cmask 0x%x, vm %s\n pos %s, nix %s, eserr = %s\n",vx->sch,vx->pmask,vx->cmask,psm(s,&vx->vm), ppos(di, vx->p), pcomb(di,vx->nix), peperr(vx->eserr)); + } + fprintf(fp,"\n"); + free(vlist); + } + + /* Dump node and associated vertex information */ + for (i = -s->nbp; i < s->np; i++) { + node *p1 = s->n[i]; + vtx **vv; + int nvv; + int *vn; + + fprintf(fp,"Node ix %d, pos %s, mask 0x%x, a_sm %s\n",p1->ix,ppos(di, p1->p),p1->pmask,psm(s,&s->sc[p1->pmask].a_sm)); + + /* Display the vertexes in order */ + if ((vv = (vtx **)malloc(sizeof(vtx *) * p1->nvv)) == NULL) + error ("ofps: malloc failed on sorted vertex list"); + for (nvv = j = 0; j < p1->nvv; j++) { + if (!showofake && p1->vv[j]->ofake) + continue; + vv[nvv++] = p1->vv[j]; + } +#define HEAP_COMPARE(A,B) ((A)->sch < (B)->sch) + HEAPSORT(vtx *, vv, nvv); +#undef HEAP_COMPARE + for (j = 0; j < nvv; j++) { + vtx *vx = vv[j]; + fprintf(fp," Vtx no %d pmask 0x%x cmask 0x%x vm %s pos %s nix %s eserr %s\n",vx->sch,vx->pmask,vx->cmask,psm(s,&vx->vm),ppos(di, vx->p), pcomb(di, vx->nix), peperr(vx->eserr)); + } + free(vv); + + /* Sort the nodes to be in order */ + if ((vn = (int *)malloc(sizeof(int) * p1->nvn)) == NULL) + error ("ofps: malloc failed on sorted vertex list"); + for (j = 0; j < p1->nvn; j++) + vn[j] = p1->vn[j]; +#define HEAP_COMPARE(A,B) ((A) < (B)) + HEAPSORT(int, vn, p1->nvn); +#undef HEAP_COMPARE + for (j = 0; j < p1->nvn; j++) { + int ix = vn[j]; + if (ix >= 0) { + node *n1 = s->n[ix]; + fprintf(fp," Assoc. node ix %d pos %s\n",ix,ppos(di, n1->p)); + } else { + fprintf(fp," Assoc. node ix %d\n",ix); + } + } + free(vn); + } + printf("\n"); + fflush(fp); + fclose(fp); +} +#endif /* NEVER */ +#endif /* DEBUG || DUMP_PLOT || DUMP_STRUCTURE */ + + +/* --------------------------------------------------------------- */ +#ifdef DUMP_FERR /* Create .tiff of dnsq function error */ + +/* Draw a line in the output diagnostic raster */ +static int +show_line( +ofps *s, /* ofps object */ +int x1, int y1, int x2, int y2, /* line start and end points */ +unsigned char rgb[3], /* Color */ +unsigned char *base, /* Raster base of line */ +int pitch, +int width, +int height +) { + unsigned char *pp; + int ow = width, oh = height; /* width and height of raster for clipping */ + int dx, dy; /* Line deltas */ + int adx, ady; /* Absolute deltas */ + + int e, k1, k2; /* Error and axial/diagonal error change values */ + int m1,m2; /* axial/diagonal coordinate change values */ + + int ll; /* Line length */ + + /* Do a crude clip */ + if (x1 < 0) + x1 = 0; + if (x1 >= ow) + x1 = ow-1; + if (x2 < 0) + x2 = 0; + if (x2 >= ow) + x2 = ow-1; + if (y1 < 0) + y1 = 0; + if (y1 >= oh) + y1 = oh-1; + if (y2 < 0) + y2 = 0; + if (y2 >= oh) + y2 = oh-1; + + /* calculate the standard constants */ + dx = x2 - x1; + dy = y2 - y1; + + if(dx < 0) { + m1 = -3; /* x is going backwards */ + adx = -dx; /* make this absolute */ + } else { + m1 = 3; /* x is going forwards */ + adx = dx; + } + + e = 0; + if(dy < 0) { + m2 = -pitch; /* y is going upwards (decreasing) */ + ady = -dy; /* make this absolute */ + e = -1; /* make lines retraceable */ + } else { + m2 = pitch; /* y is going downwards (increasing) */ + ady = dy; + } + + /* m1 has been set to x increment, m2 to y increment */ + + m2 += m1; /* make m2 the diagonal address increment */ + /* and m1 the x axial inrement */ + if(adx > ady) { /* x is driven */ + ll = adx; + k1 = 2 * ady; + k2 = 2 * (ady - adx); + e += k1 - adx; + } else { + ll = ady; + k1 = 2 * adx; + k2 = 2 * (adx - ady); + e += k1 - ady; + m1 = m2 - m1; /* Make m1 the y increment */ + } + + /* Start pixel of line */ + pp = base + y1 * pitch + 3 * x1; + + ll++; /* Draw start and end point */ + + while( ll > 0) { + while(e < 0 && ll > 0) { + pp[0] = rgb[0]; + pp[1] = rgb[1]; + pp[2] = rgb[2]; + pp += m1; + e += k1; + ll--; + } + while(e >= 0 && ll > 0) { + pp[0] = rgb[0]; + pp[1] = rgb[1]; + pp[2] = rgb[2]; + pp += m2; + e += k2; + ll--; + } + } + return 0; +} + +/* Dump a TIFF of the dnsq function values for a given point/plane set. */ +static void +dump_dnsqe( + ofps *s, + char *fname, + int *nix, + vopt_cx *cx +) { + int i, j, e, di = s->di; + unsigned char *base, *pa, col[2][3]; + int width = WIDTH; + int height = HEIGHT; + int pitch = width * 3; + int x, y; + TIFF *tif; + double pos[MXPD], fval[MXPD]; + double angle, mag; + + printf("Dumping dnsqe error for combination %s\n",pcomb(di,nix)); + + if ((tif = TIFFOpen(fname, "w")) == NULL) { + fprintf(stderr,"Failed to open output TIFF file '%s'\n",fname); + exit (-1); + } + + TIFFSetField(tif, TIFFTAG_IMAGEWIDTH, width); + TIFFSetField(tif, TIFFTAG_IMAGELENGTH, height); + TIFFSetField(tif, TIFFTAG_ORIENTATION, ORIENTATION_TOPLEFT); + TIFFSetField(tif, TIFFTAG_SAMPLESPERPIXEL, 3); + TIFFSetField(tif, TIFFTAG_BITSPERSAMPLE, 8); + TIFFSetField(tif, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG); + TIFFSetField(tif, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_RGB); + TIFFSetField(tif, TIFFTAG_COMPRESSION, COMPRESSION_NONE); + + /* allocate a raster */ + if ((base = (unsigned char *)malloc(sizeof(unsigned char) * height * pitch)) == NULL) + error ("ofps: malloc failed on diagnostic raster"); + + for (y = 0; y < height; y++) { + pos[1] = 1.0 - y/(height-1.0); + pos[1] = 1.4 * pos[1] - 0.2; + pa = base + y * pitch; + + /* Fill in pa[] with colors for this line */ + for (x = 0; x < width; x++) { + double ss; + unsigned char *dp; + double beserr, eserr; + double bf, rgb[3]; + double oog = 1.0; + double escale = 10.0; /* Error value scaling */ + + dp = pa + x * 3; + pos[0] = x/(width-1.0); + pos[0] = 1.4 * pos[0] - 0.2; + dp[0] = dp[1] = dp[2] = 255; +//printf("~1 doing %d %d pos %f %f\n",x,y,pos[0],pos[1]); + + /* Se if the sample is in gamut */ + for (ss = 0.0, e = 0; e < s->di; e++) { + if (pos[e] < s->imin[e] + || pos[e] > s->imax[e]) + break; + ss += pos[e]; + } + if (e < s->di || ss > (s->ilimit + ILIMITEPS)) { + oog = 0.7; /* Show gamut boundary */ + } + +#ifdef NEVER /* Test colors out */ + fval[0] = pos[0] * 2.0 * escale - escale; + fval[1] = pos[1] * 2.0 * escale - escale; +#else + /* Lookup the function value here */ + dnsq_solver(cx, di, pos, fval, 0); +#endif + + /* Turn the two values into colors. */ + for (e = 0; e < di; e++) { + fval[e] = (fval[e] / escale); + if (fval[e] >= 0.0) + fval[e] = pow(fval[e], 0.5); + else + fval[e] = -pow(-fval[e], 0.5); + } + + /* Convert to angle and magnitude */ + angle = 180.0/3.1415926 * atan2(fval[0], fval[1]); + if (angle < 0.0) + angle += 360.0; + else if (angle > 360.0) + angle -= 360.0; + mag = sqrt(fval[0] * fval[0] + fval[1] * fval[1]); + if (mag > 1.0) + mag = 1.0; + + rgb[0] = rgb[1] = rgb[1] = 0.0; + if (angle < 120.0) { /* red to green */ + bf = angle / 120.0; + rgb[0] = 1.0 - bf; + rgb[1] = bf; + rgb[2] = 0.0; + } else if (angle < 240.0) { /* green to blue */ + bf = (angle - 120.0) / 120.0; + rgb[0] = 0.0; + rgb[1] = 1.0 - bf; + rgb[2] = bf; + } else { /* blue to red */ + bf = (angle - 240.0) / 120.0; + rgb[0] = bf; + rgb[1] = 0.0; + rgb[2] = 1.0 - bf; + } + + /* Scale to black with magnitude */ + for (e = 0; e < 3; e++) { + rgb[e] = 1.0 - rgb[e]; + rgb[e] = (1.0 - mag) * 0.0 + mag * rgb[e]; + } + + for (e = 0; e < 3; e++) + dp[e] = (int)(255.0 * oog * rgb[e] + 0.5); + } + } + + + /* Show the path the dnsq sampled */ + col[0][0] = col[0][1] = 255, col[0][2] = 128; + col[1][0] = 128, col[1][1] = col[1][2] = 255; + + for (i = 0; i < (cx->nl-1); i++) { +//printf("~1 line %d: %f %f -> %f %f\n",i, cx->clist[i].p[0], cx->clist[i].p[1], cx->clist[i+1].p[0], cx->clist[i+1].p[1]); + show_line(s, + (int)(((cx->clist[i].p[0] + 0.2) / 1.4) * (width - 1.0) + 0.5), + (int)((1.0 - ((cx->clist[i].p[1] + 0.2) / 1.4)) * (height - 1.0) + 0.5), + (int)(((cx->clist[i+1].p[0] + 0.2) / 1.4) * (width - 1.0) + 0.5), + (int)((1.0 - ((cx->clist[i+1].p[1] + 0.2) / 1.4)) * (height - 1.0) + 0.5), + col[i & 1], base, pitch, width, height); + } + + /* Write the raster out */ + for (y = 0; y < height; y++) { + pa = base + y * pitch; + + if (TIFFWriteScanline(tif, (tdata_t)pa, y, 0) < 0) { + fprintf(stderr,"WriteScanline Failed at line %d\n",y); + exit (-1); + } + } + (void) TIFFClose(tif); + free(base); +} +#endif /* DUMP_FERR */ + +/* --------------------------------------------------------------- */ + +#ifdef NEVER + + /* Compute an aproximate bounding shere, and use */ + /* the center of it as the start point. */ + + double radsq = -1.0; /* Span/radius squared */ + double rad; + double sum; + int i, j; + int bi = 0, bj = 0; + + /* Find the two vectors that are farthest apart. Brute force search */ + /* Also track the device position for the points used to define the shere */ + for (i = 0; i < (ii-1); i++) { + for (j = i+1; j < ii; j++) { + for (sum = 0.0, e = 0; e < di; e++) { + double tt = cx.nds[i]->p[e] - cx.nds[j]->p[e]; + sum += tt * tt; + } + if (sum > radsq) { + radsq = sum; + bi = i; + bj = j; + } + } + } + + /* Set initial bounding sphere */ + for (e = 0; e < di; e++) + atp[e] = 0.5 * (cx.nds[bi]->p[e] + cx.nds[bj]->p[e]); + radsq /= 4.0; /* diam^2 -> rad^2 */ + rad = sqrt(radsq); + + /* Go though all the points again, expanding sphere if necessary */ + for (i = 0; i < ii; i++) { + + if (i == bi || i == bj) + continue; + + /* Compute distance squared of vertex to bounding sphere center */ + for (sum = 0.0, e = 0; e < di; e++) { + double tt = cx.nds[i]->p[e] - atp[e]; + sum += tt * tt; + } + if (sum > radsq) { + double tt; + + sum = sqrt(sum) + 1e-10; /* Radius to point */ + rad = 0.5 * (rad + sum); + radsq = rad * rad; + tt = sum - rad; + for (e = 0; e < di; e++) + atp[e] = (rad * atp[e] + tt * cx.nds[i]->p[e])/sum; + } + } + +/* Given two sample point indexes, compute the plane between them. */ +/* (This will fail with a divide by zero error if two points are coincident) */ +static void comp_pleq(ofps *s, pleq *vp, int ix1, int ix2) { + node *p0 = s->n[ix1], *p1 = s->n[ix2]; + int e, di = s->di; + double cp[MXPD]; + double sum = 0.0; + + /* Compute plane normal from ix1 to ix2 */ + for (e = 0; e < di; e++) { + double tt = p1->p[e] - p0->p[e]; + vp->pe[e] = tt; + sum += tt * tt; + } + sum = sqrt(sum); + + /* Normalise it */ + for (e = 0; e < di; e++) + vp->pe[e] /= sum; + + /* Compute mid point */ + for (e = 0; e < di; e++) + cp[e] = 0.5 * (p1->p[e] + p0->p[e]); + + /* Compute the plane equation constant */ + for (vp->pe[di] = 0.0, e = 0; e < di; e++) + vp->pe[di] -= vp->pe[e] * cp[e]; +} + +#endif // NEVER + diff --git a/target/ofps.h b/target/ofps.h new file mode 100644 index 0000000..e2b1d55 --- /dev/null +++ b/target/ofps.h @@ -0,0 +1,438 @@ + +#ifndef OFPS_H + +/* + * Argyll Color Correction System + * + * Optimised Farthest Point Sampling + * + * Author: Graeme W. Gill + * Date: 6/9/2004 + * + * Copyright 2004 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. + */ + +#ifndef MXPD +#define MXPD 4 /* Maximum ofps dimentionality */ +#endif + +#define MXNIX (MXPD+3) /* Maximum vertex node indexes + hash + ixm */ + +struct _acell; + +/* A gamut surface plane equation. */ +struct _pleq { + double pe[MXPD+1]; /* Vertex plane equation. First di elements are normalized */ + /* outward pointing normal (from first sample point), last element */ + /* is constant. If point . pe > 0, then point is outside surface */ + int ix; /* Index of fake node associated with gamut surface (-ve) */ +}; typedef struct _pleq pleq; + +/* A sub-surface set mask */ +#define MXSMASKW 6 /* Maximum number of setmask words */ +typedef struct { + unsigned int m[MXSMASKW]; +} setmask; + +/* A vertex. This is a point that has the highest eserr within */ +/* the local region of di+1 nodes. It is therefore a candidate for a new */ +/* sample node during seeding, or a point at which the sampling */ +/* error of the current node locations can be estimated. */ +/* Vertexes are the vericies of the Voronoi polyhedra. */ +/* Because these are based on the natural eserr neighborhood, */ +/* they are not necessarily exactly poyhedra. */ +/* (Non gamut boundary verticies are shared) */ +struct _vtx { + int no; /* Serial number for id */ + int nix[MXNIX]; /* di+1 Sample point node indexes involved in vertex */ + /* Index is -ve if it is a fake gamut boundary node, */ + /* sorted largest to smallest (so fake gamut nodes are last) */ + /* [MXPD+1] is hash of all the node indexes, */ + /* [MXPD+2] is the OR of all the node ixm's */ + double ce[MXPD+1]; /* Estimated curvature error from mid point to each real node */ + /* (ce's are compacted, skipping fake boundary nodes) */ + int nnv; /* Number of neighbour verticies (vertex net) */ + int _nnv; /* Number allocated */ + struct _vtx **nv; /* List of neighbour verticies. This includes hidden verts. */ + /* (If we didn't have to support INDEP_SURFACE, then there would */ + /* be exactly di neighbour verticies.) */ + + double p[MXPD]; /* Vertex location */ + double v[MXPD]; /* Subjective value at vertex (Labj) ? */ + double eperr; /* Estimated position error */ + double eserr; /* Estimated sampling error */ + double p_eperr; /* Previous estimated position error */ + char ghost; /* Don't use for optimization or stats. */ + char ifake; /* A fake inside node */ + char ofake; /* A fake outside node */ + char used; /* Set to nz if already used for seeding */ + char bch; /* nz if added to batch update list */ + char del; /* Marked for deletion (used by add_to_vsurf()) */ + char add; /* 1 = add to node, 2 = update hm (used by add_to_vsurf()) */ + char par; /* Marked for deletion because it's already a parent node */ + int sch; /* Sanity check houskeeping */ + setmask buvm; /* Batch update to sub-surface hidden setmask */ + setmask bdvm; /* Batch delete change to sub-surface hidden setmask */ + struct _vtx *batch; /* Batch update list */ + int nsp; /* Number of gamut surface planes it touches */ + pleq *sp[MXPD+1]; /* List of gamut surface planes */ + unsigned int pmask; /* Gamut surface plane mask, from its location */ + unsigned int cmask; /* Gamut surface composition mask, from it's parent nodes */ + setmask vm; /* Sub-surface visiblility setmask */ + struct _vtx *link; /* Linked list of free/used vtx's */ + struct _vtx **plp; /* Pointer to link pointer in used list */ + + struct _vtx *n; /* Next in acceleration list */ + struct _vtx **pn; /* Pointer to link pointer in acceleration list */ + int pci; /* Accelleration grid index */ + + struct _vtx *chn; /* Next in cache index list */ + struct _vtx **pchn; /* Pointer to link pointer in cache index list */ + + int fflag; /* fchl set flag set from s->fflag */ + struct _vtx *fchl; /* Next in fixup check list */ + struct _vtx **pfchl;/* Pointer to link pointer */ + int fupcount; /* Number of times vertex has fixed in a round */ + double fuptol; /* Tollerance for fixing this vertex */ + struct _node *hnode;/* Hit node */ + double hitmarg; /* Hit margine to node */ + struct _vtx **psvtxs; /* Pointer to entry in s->svtxs[] */ + + int cflag; /* Vertex checked for hit flag, set from s->flag */ + int sflag; /* Vertex search before flag, set from s->flag */ + int hflag; /* Vertex search after hit flag, set from s->flag */ + char opqsq; /* flag, on post hit search queue */ + struct _vtx *slist; /* Breadth first search list link */ + int disth; /* Search distance from hit vertex, valid when hflag == s->flag */ + struct _vtx *nxh; /* Vtxs hit by node (to be deleted) list set by ofps_check_vtx_vn() */ + double nba_eperr; /* node being added eperr, valid if cflag == s->flag */ + + struct _vtx *dell; /* Deleted/Not Deleted list */ +}; typedef struct _vtx vtx; + +/* A mid point. This is a point that has the highest eserr directly */ +/* between two neighboring nodes. It is used during optimization */ +/* to try and encourage even spacing between nodes. */ +struct _mid { + int no; /* Serial number for id */ + double p[MXPD]; /* Midpoint location */ + int nix[2]; /* The two sample point node indexes involved in midpoint */ + double ce[2]; /* Estimated curvature error from mid point to two nodes */ + double np; /* Interpolation point between nux[0] and nix[1] */ + double v[MXPD]; /* Subjective value at midpoint (Labj) ? */ + double eperr; /* Estimated position error */ + double eserr; /* Estimated sampling error */ + int refc; /* Reference count */ + struct _mid *link; /* Linked list of free/used mid's */ + struct _mid **plp; /* Pointer to link pointer in used list */ +}; typedef struct _mid mid; + + +/* A measurement sample point node. */ +/* Sample points are the points around which the Voronoi polyhedra */ +/* are constructed. If a network is constructed between nodes that */ +/* form a vertex, the netork will be the Delaunay tesselation. */ +struct _node { + int ix; /* Index of node in s->n[] */ + int ixm; /* Hash mask of node ix */ + int fx; /* nz if point is fixed */ + double p[MXPD]; /* Device coordinate position */ + double v[MXPD]; /* Subjective value (Labk) ? */ + + double np[MXPD]; /* Next device coordinates during opt */ + double nv[MXPD]; /* Next subjective coordinates during opt */ + + double op[MXPD]; /* Previous device coordinates during opt */ + + int nvv; /* Number of Voronoi surface verticies */ + int _nvv; /* Number allocated */ + vtx **vv; /* List of Voronoi surface verticies */ + + int nvn; /* Number of Voronoi nodes & midpoints */ + int _nvn; /* Number allocated */ + int *vn; /* List of Voronoi nodes indexes. Doesn't include this node. */ + /* Index is -ve if it is a fake gamut boundary node. */ + mid **mm; /* List of midpoints. Midpoints will be NULL if not created yet. */ + + int nsp; /* Number of touched gamut surface planes */ + pleq *sp[MXPD+1]; /* List of touched gamut surface planes */ + unsigned int pmask; /* Gamut surface plane mask */ + + struct _acell *cell;/* Pointer to cell vertex is in */ + struct _node *n; /* Next in acceleration list */ + struct _node **pn; /* Pointer to link pointer in acceleration list */ + int pci; /* Accelleration grid index */ + + int flag; /* Node being added access flag, set from s->flag */ + int nvnflag; /* node_recomp_nvn_dmxs access flag */ + struct _node *na; /* Next in 'to be added' list */ + int upflag; /* Set to s->flag if node has been added to ->nup list */ + struct _node *nup; /* Next node in 'recomp_nvn' list */ + +}; typedef struct _node node; + +#define BOUND_GFLAG ((unsigned int)-1) /* Boundary cell gflag */ + +/* An acceleration structure cube */ +struct _acell { + unsigned int gflag; /* Acceleration grid search touched & boundary flag */ + node *head; /* List of nodes inside acceleration cell */ + vtx *vhead; /* List of verticies with all real nodes inside acceleration cell */ + int co[MXPD]; /* coordinate of cell */ + double p[MXPD]; /* Device position of base of cell */ + double v[MXPD]; /* Corresponfing perceptual value of base of cell */ + double cp[MXPD]; /* Device position of center of cell */ + double cv[MXPD]; /* Corresponfing perceptual value of center of cell */ + double eperr; /* Worst case eperr from a corner to the center */ + struct _acell *slist; /* Search list */ +}; typedef struct _acell acell; + +/* Storage for a node combination/new position */ +struct _nodecomb { + int nix[MXNIX]; /* di+1 Sample point node indexes involved in vertex */ + double ce[MXPD+1]; /* Estimated curvature error from mid point to two nodes */ + vtx **v1, **v2; /* Deleted and non-deleted vertex involved */ + int _count; /* v1/v2 allocation */ + int count; /* Number of times this is generated, used of v1/v2 */ + double p[MXPD]; /* Position of resulting vertex or */ + double v[MXPD]; /* Value of resulting vertex or */ + double eperr; + double eserr; + double weserr; + int pvalid; /* Valid new position, else use deleted location & eserr */ + vtx *vv; /* Existing vertex at this combination (if opt) */ + double ceperr; /* Current eperr to be bettered */ + double oog; /* Out of gamut value */ + setmask vm; /* Sub-surface visibility setmask */ + int startex; /* nz if existing p[] should be starting dnsqe point */ +}; typedef struct _nodecomb nodecomb; + +/* Vertex cache hash index/table size */ +#define VTXCHSIZE 33037 +//#define VTXCHSIZE 67493 + +/* Record of a set of gamut surface plane combination */ +struct _surfcomb { + unsigned int co; /* Surface combination mask */ + int valid; /* Valid flag */ + int nos; /* Number surfaces */ + int smset; /* i_sm has been set */ + setmask i_sm; /* Indiviual set of this surface combination */ + setmask a_sm; /* Accumulated set of this and higher dimensions */ + struct _surfcomb *ds; /* Circular list of the disjoint set */ +}; typedef struct _surfcomb surfcomb; + +/* Main sample point object */ +struct _ofps { +/* private: */ + int verb; /* Verbose */ + + int di; /* Point dimensionality */ + double ilimit; /* Ink limit - limit on sum of p[] */ + double imin[MXPD]; /* Ink limit - limit on min of p[], must be >= 0.0 */ + double imax[MXPD]; /* Ink limit - limit on min of p[], must be <= 1.0 */ + int good; /* 0 = fast, 1 = good flag */ + double surftol; /* Surface tollerance distance */ + int maxits; /* Maximum itterative improvement passes */ + double lperterb; /* level of random peturbation */ + double ssurfpref, esurfpref; /* Start and end surface preference weightig */ + + /* Error estimate model parameters */ + double devd_wght; /* Device space weighting */ + double perc_wght; /* Perceptual space weighting */ + double curv_wght; /* Curvature weighting */ + + int fxno; /* Total number of fixed points provided, possibly non-unique */ + fxpos **ufx; /* fnp randomized unique fixed points to add */ + + int gnp; /* Number of fake gamut nodes (-ve index) */ + /* -1 to -2di-1 are fake boundary node indexes, */ + /* with -2di-1 being the ink limit boundary. */ + /* -2di-2 is the fake inside node, */ + /* -2di-3 is the fake outside node, */ + int fnp; /* Number of unique fixed points in list */ + int tinp; /* Target number of total points in list, including fnp */ + int np; /* Number of points currently in list */ + node *_n, **n; /* tinp allocation of points, list of pointers to points */ + int nv; /* Current number of verticies */ + int nxvno; /* Next vertex serial number */ + int nxmno; /* Next midpoint serial number */ + + /* Gamut surface definition planes. */ + int nbp; /* Number of boundary planes. Either 2di or 2di+1 */ + pleq gpeqs[2 + MXPD * 2 + 1]; /* Plane equations associated */ + /* with the fake gamut surface/boundary nodes */ + /* points. Index is 1-ix */ + /* (allow for other fakes, just in case) */ + int sminit; /* Flag, nz if sc has been inited */ + surfcomb *sc; /* Array of (1 << nbp) surface combinations */ + int smbits; /* Total set mask bits */ + int bpsmw; /* Bits per set mask word */ + int nsmw; /* Number of setmask words */ + unsigned int lwmask; /* Last word mask */ + + /* Perceptual function handed in. All device values must have been */ + /* clipped before calling this, otherwise use It is assumed that */ + void (*percept)(void *od, double *out, double *in); + void *od; /* Opaque data for perceptual point */ + + rspl *pcache; /* cache of perceptual lookup */ + + /* Other info */ + int rix; /* Next read index */ + double mn,mx,av; /* serr stats */ + double smns; /* Closest node spacing */ + double mxmvsq; /* Maximum movement during optimisation */ + int optit; /* Optimization itteration */ + vtx *nxh; /* Vtxs hit by node (to be deleted) list set by ofps_check_vtx_vn() */ + vtx *nxp; /* Vtxs that are already parents of added node list (fixups) */ + struct _vtx *batch; /* Batch update list */ + int checklev; /* check node recursion level */ + struct _vtx *fchl; /* Next vertex in fixup check list */ + struct _vtx **svtxs;/* Vertex "to be fixed" list */ + int nsvtxs; /* Number in vertex "to be fixed" list */ + int _nsvtxs; /* Allocated size of vertex "to be fixed" list */ + struct _node *nup; /* Next node in 'recomp_nvn' list */ + + /* Used and free lists */ + struct _vtx *uvtx; /* Linked list of used vtx's */ + struct _vtx *fvtx; /* Linked list of free vtx's */ + struct _vtx *hvtx; /* Linked list of hidden vtx's */ + struct _mid *umid; /* Linked list of used mid's */ + struct _mid *fmid; /* Linked list of free mid's */ + + /* Unbounded perceptual model */ + double pmod[MXPD * (1 << MXPD)]; + int pmod_init; /* It's been initialised */ + + /* Acceleration structure */ + int agres; /* Acceleration grid resolution (not including extra row) */ + double gw; /* Grid cell width */ + int gim[MXPD]; /* Grid index multiplier */ + double gcd; /* Grid cell diagonal */ + int nig; /* Number of cells in grid (including guard rows) */ + acell *_grid; /* Pointer to allocated array of grid structures */ + acell *grid; /* Pointer to base of array of grid structures */ + int agrid_init; /* accell grid p[] and v[] have been inited */ + unsigned int gflag; /* Acceleration grid search flag */ + int nacnl; /* Number of bytes in Accelleration cell neighbour offset list */ + int *acnl; /* Accelleration cell neighbour offset list */ + + int flag; /* Access flag associated with node being added */ + int nvnflag; /* node_recomp_nvn_dmxs access flag */ + int fflag; /* Fixup round flag */ + vtx *vch[VTXCHSIZE]; /* Vertex cache index */ + + aat_atree_t *vtreep; /* Binary tree of vertexes sorted by eperr */ + aat_atree_t *vtrees[MXPD+2]; /* Per nsp, binary tree of vertexes sorted by eserr */ + /* We get di+2 planes for fake initial nodes */ + + /* Utility - avoid re-allocation/initialization */ + sobol *sob; + nodecomb *combs; /* New node combinations being created in add_to_vsurf() */ + int _ncombs; /* Number of node combinations allocated. */ + + /* Debug/stats */ + int nopstop; /* Number of optimization passes before stopping with diagnostics */ + int ntostop; /* Number of points before stopping with diagnostics */ + int positions; /* Number of calls to locate vertex */ + int dnsqs; /* Number of dnsq is called */ + int funccount; /* Number of times dnsq callback function is called */ + int maxfunc; /* Maximum function count per dnsq */ + int sucfunc; /* Function count per sucessful dnsq */ + int sucdnsq; /* Number of sucessful dnsqs */ + int maxretries; /* Maximum retries used on sucessful dnsq */ + int posfails; /* Number of position_vtx failures */ + int posfailstp; /* Number of position_vtx failures this pass */ + int nvtxcreated; /* Number of vertexes created */ + int nvtxdeleted; /* Number of vertexes deleted */ + int add_hit; /* Number of add_to_vsurf hits */ + int add_mis; /* Number of add_to_vsurf misses */ + int fadd_hit; /* Number of fixup add_to_vsurf hits */ + int fadd_mis; /* Number of fixup add_to_vsurf misses */ + + int nvcheckhits; /* Number of vertexes hit durint ofps_check_node() */ + int vvchecks; /* Number of vertexes checked for hit */ + int vvpchecks; /* Number of vertexes that would be exaustively checked for hits */ + int naccsrch; /* Number of accellerated searches */ + int ncellssch; /* Number of accelleration cells searched */ + int nnschd; /* Number of nodes nearest searched */ + int nnfschd; /* Number of nodes for full nearest searched */ + int nvschd; /* Number of vertexes nearest searched */ + int nvfschd; /* Number of vertexes for full nearest searched */ + + int nsurfadds; /* Number of add_to_vsurf() calls */ + int nhitv; /* Number of hit vertexes in add_to_vsurf() */ + int maxhitv; /* Maximum number of hit vertexes in add_to_vsurf() */ + + int nfseeds; /* Number of times searched for vtx with largest eserr */ + int nfseedsvtx; /* Number of vertexes searched for vtx with largest eserr */ + + unsigned int l_mstime; /* Last create surface pass timestamp */ + int l_positions; /* Last Number of calls to locate vertex */ + int l_nvtxcreated; /* Last Number of vertexes created */ + int l_nvtxdeleted; /* Last Number of vertexes deleted */ + + struct _vtx *i_uvtx; /* Incrementallu created vertexes used by DEBUG_RESEED_AFTER_FIXUPS */ + +/* public: */ + /* return non-zero if the perceptual point is within the device gammut */ + int (*pig)(struct _ofps *s, double *p); + + /* Initialise, ready to read out all the points */ + void (*reset)(struct _ofps *s); + + /* Read the next set of non-fixed points values */ + /* return non-zero when no more points */ + /* p = position, v = value, either may be NULL */ + int (*read)(struct _ofps *s, double *p, double *v); + + /* Calculate and print stats */ + void (*stats)(struct _ofps *s); + + /* Destroy ourselves */ + void (*del)(struct _ofps *s); + + }; typedef struct _ofps ofps; + + +/* Constructor */ +extern ofps *new_ofps( + int verb, + int di, double ilimit, int npoints, + int good, + double dadaptation, + double devd_wght, + double perc_wght, + double curv_wght, + fxpos *fxlist, int fxno, /* Existing, fixed point list */ + void (*percept)(void *od, double *out, double *in), void *od); + +/* Extended constructor */ +ofps *new_ofps_ex( +int verb, /* Verbosity */ +int di, /* Dimensionality of device space */ +double ilimit, /* Ink limit (sum of device coords max) */ +double *imin, /* Ink limit - limit on min of p[], normally >= 0.0 */ +double *imax, /* Ink limit - limit on min of p[], normally <= 1.0 */ +int tinp, /* Total number of points to generate, including fixed */ +int good, /* 0 = fast, 1 = good */ +double dadaptation, /* Degree of adaptation to device characteristic 0.0 - 1.0, */ + /* use -ve value for explicit weightings: */ +double devd_wght, /* Device space weighting (if dad < 0) */ +double perc_wght, /* Perceptual space weighting (if dad < 0) */ +double curv_wght, /* Curvature weighting (if dad < 0) */ +fxpos *fxlist, /* List of existing fixed points (may be NULL) */ +int fxno, /* Number of existing fixes points */ +void (*percept)(void *od, double *out, double *in), /* Perceptual lookup func. */ +void *od, /* context for Perceptual function */ +int ntostop, /* Debug - number of points until diagnostic stop */ +int nopstop /* Debug - number of optimizations until diagnostic stop, -1 = not */ +); + +#define OFPS_H +#endif /* OFPS_H */ diff --git a/target/ppoint.c b/target/ppoint.c new file mode 100644 index 0000000..2a5dd26 --- /dev/null +++ b/target/ppoint.c @@ -0,0 +1,1056 @@ + +// ppoint7c +// Approach that picks poorly supprted points with maximum interpolation +// error each time. Version that creates a candidate list when adding +// previous points to the distance grid. +// Development of version that uses interpolation error and perceptual +// distance to nearest sample point driven point placement metric, this +// one usin incremental rspl for interpolation estimation. + +/* + * Argyll Color Correction System + * + * Perceptually distributed point class + * + * Author: Graeme W. Gill + * Date: 5/10/96 + * + * Copyright 1996 - 2004 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: + + */ + + +#include +#include +#include +#include +#if defined(__IBMC__) +#include +#endif +#include "numlib.h" +#include "rspl.h" +#include "sort.h" +#include "plot.h" +#include "icc.h" +#include "xcolorants.h" +#include "targen.h" +#include "ppoint.h" + +#undef DEBUG +#define DUMP_PLOT /* Show on screen plot */ +#define PERC_PLOT 0 /* Emit perceptive space plots */ +#define DO_WAIT 1 /* Wait for user key after each plot */ + +#define ALWAYS +#undef NEVER + +#ifdef NEVER +#ifdef __STDC__ +#include +void error(char *fmt, ...), warning(char *fmt, ...), verbose(int level, char *fmt, ...); +#else +#include +void error(), warning(), verbose(); +#endif +#endif /* NEVER */ + +#ifdef STANDALONE_TEST +#ifdef DUMP_PLOT +static void dump_image(ppoint *s, int pcp); +#endif +#endif + +static void add_dist_points(ppoint *s, co *pp, int nn); +//static double far_dist(ppoint *s, double *p); + +/* Default convert the nodes device coordinates into approximate perceptual coordinates */ +/* (usually overriden by caller supplied function) */ +static void +default_ppoint_to_percept(void *od, double *p, double *d) { + ppoint *s = (ppoint *)od; + int e; + +#ifndef NEVER + /* Default Do nothing - copy device to perceptual. */ + for (e = 0; e < s->di; e++) { + double tt = d[e]; + if (e == 0) + tt = pow(tt, 2.0); + else + tt = pow(tt, 0.5); + p[e] = tt * 100.0; + } +#else + for (e = 0; e < s->di; e++) { + double tt = d[e]; + /* Two slopes with a sharp turnover in X */ + if (e == 0) { + if (tt < 0.5) + tt = tt * 0.3/0.5; + else + tt = 0.3 + ((tt-0.5) * 0.7/0.5); + } + p[e] = tt * 100.0; + } +#endif +} + +/* return the distance of the device value from the device gamut */ +/* This will be -ve if the point is outside */ +/* If bvp is non-null, the index of the closest dim times 2 */ +/* will be returned for the 0.0 boundary, dim * 2 + 1 for the 1.0 */ +/* boundary, and di * 2 for the ink limit boundary. */ +static double +ppoint_in_dev_gamut(ppoint *s, double *d, int *bvp) { + int e; + int di = s->di; + double tt, dd = 1.0; + double ss = 0.0; + int bv = di; + for (e = 0; e < di; e++) { + tt = d[e]; + if (tt < dd) { + dd = tt; + bv = e * 2; + } + tt = 1.0 - d[e]; + if (tt < dd) { + dd = tt; + bv = e * 2 + 1; + } + ss += d[e]; + } + ss = (s->ilimit-ss)/di; /* Axis aligned distance to ink limit */ + tt = sqrt((double)di) * ss; /* Diagonal distance to ink limit */ + if (tt < dd) { + dd = tt; + bv = di * 2; + } + if (bvp != NULL) + *bvp = bv; + return dd; +} + +#ifdef NEVER /* Not currently used */ +/* Given the new intended device coordinates, */ +/* clip the new position to the device gamut edge */ +/* return non-zero if the point was clipped */ +static int +ppoint_clip_point(ppoint *s, double *d) { + int e; + double ss = 0.0; + int rv = 0; + for (e = 0; e < s->di; e++) { + if (d[e] < 0.0) { + d[e] = 0.0; + rv |= 1; + } else if (d[e] > 1.0) { + d[e] = 1.0; + rv |= 1; + } + ss += d[e]; + } + if (ss > s->ilimit) { + ss = (ss - s->ilimit)/s->di; + for (e = 0; e < s->di; e++) + d[e] -= ss; + rv |= 1; + } + return rv; +} +#endif /* NEVER */ + +/* --------------------------------------------------- */ +/* Locate the best set of points to add */ + +/* Definition of the optimization functions handed to powell(.) */ +/* Return distance error to be minimised (maximises distance from */ +/* an existing sample point) */ +static double efunc1(ppoint *s, double p[]) { + double rv = 0.0; /* return value */ + +//printf("\n~1 p = %f %f\n",p[0],p[1]); + if ((rv = (ppoint_in_dev_gamut(s, p, NULL))) < 0.0) { + rv = rv * -500.0 + 50000.0; /* Discourage being out of gamut */ +//printf("~1 out of gamut, rv = %f\n",rv); + + } else { + int e, di = s->di; + double vf[MXPD]; /* Perceptual value of reference */ + co tp; /* Lookup from interpolation grid */ + double ierr; /* Interpolation error */ + double cdist; /* closest distance to point */ + double errd; /* Overall error/distance to maximise */ + + for (e = 0; e < di; e++) + tp.p[e] = p[e]; + + s->pd->interp(s->pd, &tp); /* Lookup current closest distance value */ + cdist = tp.v[di]; + if (cdist >= 10000.0) /* Initial value */ + cdist = 0.0; + +//printf("~1 min pdist = %f\n",cdist); + + /* Not quite sure which is best here. */ + /* Using percept() is slower, and has more point placement artefacts, */ + /* but seems to arrive at a better result. */ +#ifdef NEVER + for (e = 0; e < di; e++) + vf[e] = tp.v[e]; /* Use interpolated perceptual value */ +#else + s->percept(s->od, vf, p); /* Lookup perceptual value */ +#endif + s->g->interp(s->g, &tp); /* Lookup current interpolation */ + +//printf("~1 interp %f %f, percept %f %f\n",tp.v[0],tp.v[1],vf[0],vf[1]); + for (ierr = 0.0, e = 0; e < di; e++) { + double tt = tp.v[e] - vf[e]; + ierr += tt * tt; + } + ierr = sqrt(ierr); +//printf("~1 interp error = %f\n",ierr); + + /* The ratio of interpolation error to support distance affects */ + /* peak vs. average error in final result. */ +#ifdef NEVER + /* Weighted squares */ + errd = ierr * ierr + DWEIGHT * cdist * cdist; +#else + /* Linear weighted then squared */ + errd = ierr + DWEIGHT * cdist; + errd = errd * errd; +#endif + + /* Convert max error to min return value */ + rv = 1000.0/(0.1 + errd); +//printf("~1 err val %f\n",rv); + + } + +//printf("~1 efunc1 returning %f from %f %f\n",rv,p[0],p[1]); + return rv; +} + + +/* return the interpolation error at the given device location */ +static double +ppoint_ierr( +ppoint *s, +double *p +) { + int e, di = s->di; + double vf[MXPD]; /* Perceptual value of reference */ + double err; + co tp; /* Perceptual value of test point */ + + for (e = 0; e < di; e++) + tp.p[e] = p[e]; + s->g->interp(s->g, &tp); + + s->percept(s->od, vf, p); + + for (err = 0.0, e = 0; e < di; e++) { + double tt = tp.v[e] - vf[e]; + err += tt * tt; + } + err = sqrt(err); + + return err; +} + +/* Find the next set of points to add to our test sample set. */ +/* Both device and perceptual value are returned. */ +/* We try and do a batch of points because adding points to the rspl interpolation */ +/* is a high cost operation. The main trap is that we may add points that are almost identical, */ +/* since we don't know the effect of adding other points in this batch. */ +/* To try and counter this, points are rejected that are two close together in this group. */ + +/* Candidate points are located that have amongst the largest distances to existing */ +/* points (measured in a device/perceptual distance mix), and from those points, */ +/* the ones with the highest current interpolation mis-prediction error are selected. */ +/* In this way a well spread set of samples is hoped to be gemerated, but favouring */ +/* those that best reduce overall interpolation error. */ +static int +ppoint_find_worst( +ppoint *s, +co *p, /* return device values */ +int tnn /* Number to return */ +) { + co *fp = s->fwfp; /* Copy of s-> info, stored in s because of size. */ + int nfp; /* Current number in fp[] */ + int opoints; + int e, di = s->di; + double sr[MXPD]; /* Search radius */ + int i, j; + + for (e = 0; e < di; e++) + sr[e] = 0.01; /* Device space search radius */ + +//printf("~1 currently %d points in fp list\n",s->nfp); + + /* The distance grid functions will have a list of the FPOINTS best */ + /* grid points to start from. Make a copy of it */ + for (nfp = 0; nfp < s->nfp; nfp++) { + fp[nfp] = s->fp[nfp]; /* Structure copy */ + fp[nfp].v[0] = efunc1(s, fp[nfp].p); /* Compute optimiser error value */ + } + + /* If list is not full, fill with random numbers: */ + if (nfp < FPOINTS) { +//printf("~1 not full, so adding %d random points\n",FPOINTS-nfp); +// for (; nfp < FPOINTS; nfp++) { + for (; nfp < tnn; nfp++) { + double sum; + + for (;;) { /* Find an in-gamut point */ + for (sum = 0.0, e = 0; e < di; e++) + sum += fp[nfp].p[e] = d_rand(0.0, 1.0); + if (sum <= s->ilimit) + break; + } + fp[nfp].v[0] = efunc1(s, fp[nfp].p); /* Compute optimiser dist error value */ + } + } + + /* Sort them by derr, smallest to largest */ +#define HEAP_COMPARE(A,B) ((A).v[0] < (B).v[0]) + HEAPSORT(co, fp, nfp); +#undef HEAP_COMPARE + + opoints = nfp < OPOINTS ? nfp : OPOINTS; + + /* Optimise best portion of the list of starting points, according to */ + /* interpolation error weighted distance. */ + for (i = 0; i < opoints; i++) { + double mx; + + if (powell(&mx, di, fp[i].p, sr, 0.001, 1000, + (double (*)(void *, double *))efunc1, (void *)s, NULL, NULL) != 0 || mx >= 50000.0) { +#ifdef ALWAYS + printf("ppoint powell failed, tt = %f\n",mx); +#endif + } + fp[i].v[0] = mx; +//printf("~1 optimised point %d to %f %f derr %f\n",i,fp[i].p[0],fp[i].p[1],mx); + + /* Check if this duplicates a previous point */ + for (j = 0; j < i; j++) { + + double ddif = 0.0; + for (e = 0; e < di; e++) { + double tt = fp[i].p[e] - fp[j].p[e]; + ddif += tt * tt; + } + ddif = sqrt(ddif); /* Device value difference */ + if (ddif < CLOSED) { +//printf("~1 duplicate of %d, so marked\n",j); + fp[i].v[0] = 50000.0; /* Mark so it won't be used */ + break; /* too close */ + } + } + } + +//printf("~1 derr sorted list:\n"); +//for (i = 0; i < opoints; i++) +// printf("~1 %d: loc %f %f derr %f\n", i, fp[i].p[0],fp[i].p[1],fp[i].v[0]); + + /* Compute the interpolation error for the points of interest */ + for (i = 0; i < opoints; i++) { + if (fp[i].v[0] >= 50000.0) /* Duplicate or failed to optimis point */ + fp[i].v[0] = -1.0; /* Impossibly low interpolation error */ + else + fp[i].v[0] = ppoint_ierr(s, fp[i].p); + } + + /* Sort them by ierr, largest to smallest */ +#define HEAP_COMPARE(A,B) ((A).v[0] > (B).v[0]) + HEAPSORT(co, fp, opoints); +#undef HEAP_COMPARE + +//printf("~1 ierr sorted list:\n"); +//for (i = 0; i < OPOINTS; i++) +// printf("~1 %d: loc %f %f ierr %f\n", i, fp[i].p[0],fp[i].p[1],fp[i].v[0]); + + /* Return the best tnn as next points */ + for (j = i = 0; j < tnn && i < opoints; i++) { + if (fp[i].v[0] < 0.0) + continue; /* Skip marked points */ + for (e = 0; e < di; e++) + p[j].p[e] = fp[i].p[e]; + s->percept(s->od, p[j].v, p[j].p); + j++; + } +//printf("~1 returning %d points\n",j); + return j; +} + + +/* --------------------------------------------------- */ + +/* determine the errors between the rspl and 100000 random test points */ +static void +ppoint_stats( +ppoint *s +) { + int i, n; + int e, di = s->di; + double mx = -1e80, av = 0.0, mn = 1e80; + + for (i = n = 0; i < 100000; i++) { + co tp; /* Perceptual value of test point */ + double vf[MXPD]; /* Perceptual value of reference */ + double sum, err; + + for (sum = 0.0, e = 0; e < di; e++) + sum += tp.p[e] = d_rand(0.0, 1.0); + + if (sum <= s->ilimit) { + + /* rspl estimate of expected profile interpolation */ + s->g->interp(s->g, &tp); + + /* Target values */ + s->percept(s->od, vf, tp.p); + + for (err = 0.0, e = 0; e < di; e++) { + double tt = tp.v[e] - vf[e]; + err += tt * tt; + } + err = sqrt(err); + if (err > mx) + mx = err; + if (err < mn) + mn = err; + av += err; + n++; + } + } + av /= (double)n; + + printf("~1 Random check errors max %f, avg %f, min %f\n",mx,av,mn); +} + +/* --------------------------------------------------- */ +/* Support for maintaining the device/perceptual distance grid */ +/* as well as keeping the far point candidate list up to date. */ + +/* Structure to hold data for callback function */ +struct _pdatas { + ppoint *s; /* ppoint structure */ + int init; /* Initialisation flag */ + co *pp; /* List of new points */ + int nn; /* Number of points */ +}; typedef struct _pdatas pdatas; + +/* rspl set callback function for maintaining perceptual distance information */ +static void +pdfunc1( + void *ctx, /* Context */ + double *out, /* output value, = di percept + distance */ + double *in /* inut value */ +) { + pdatas *pp = (pdatas *)ctx; + ppoint *s = pp->s; + int e, di = s->di; + + if (pp->init) { + s->percept(s->od, out, in); /* Lookup perceptual value */ + out[di] = 10000.0; /* Set to very high distance */ + + } else { /* Adding some points */ + int i; + double sd = 1e80; + + /* Find smallest distance from this grid point to any of the new points */ + for (i = 0; i < pp->nn; i++) { + double ddist, pdist; + double dist; /* Combined distance */ + + /* Compute device and perceptual distance */ + for (ddist = pdist = 0.0, e = 0; e < di; e++) { + double tt = out[e] - pp->pp[i].v[e]; + pdist += tt * tt; + tt = 100.0 * (in[e] - pp->pp[i].p[e]); + ddist += tt * tt; + } + dist = DDMIX * ddist + (1.0-DDMIX) * pdist; /* Combine both */ + if (dist < sd) + sd = dist; + } + + sd = sqrt(sd); + if (sd < out[di]) + out[di] = sd; + + /* Update far point candidate list */ + if (s->nfp < FPOINTS) { /* List isn't full yet */ + for (e = 0; e < di; e++) + s->fp[s->nfp].p[e] = in[e]; + s->fp[s->nfp].v[0] = sd; /* store distance here */ + + if (sd > s->wfpd) { /* If this is the worst */ + s->wfpd = sd; + s->wfp = s->nfp; + } + s->nfp++; + + } else if (sd < s->wfpd) { /* Found better, replace current worst */ + + for (e = 0; e < di; e++) + s->fp[s->wfp].p[e] = in[e]; + s->fp[s->wfp].v[0] = sd; /* store distance here */ + + /* Locate the next worst */ + s->wfpd = -1.0; + for (i = 0; i < s->nfp; i++) { + if (s->fp[i].v[0] > s->wfp) { + s->wfp = i; + s->wfpd = s->fp[i].v[0]; + } + } + } + } +} + +/* Add a list of new points to the perceptual distance grid */ +/* (Can change this to just adding 1 point) */ +static void add_dist_points( +ppoint *s, +co *pp, /* List of points including device and perceptual values */ +int nn /* Number in the list */ +) { + pdatas pdd; /* pd callback context */ + + pdd.s = s; + pdd.init = 0; /* Initialise values in the grid */ + pdd.pp = pp; + pdd.nn = nn; + + /* let callback do all the work */ + s->pd->re_set_rspl(s->pd, + 0, /* No special flags */ + &pdd, /* Callback function context */ + pdfunc1); /* Callback function */ +} + +#ifdef NEVER /* Not currently used */ +/* Return the farthest distance value for this given location */ +static double far_dist(ppoint *s, double *p) { + int e, di = s->di; + double cdist; + co tp; + + for (e = 0; e < di; e++) + tp.p[e] = p[e]; + + s->pd->interp(s->pd, &tp); /* Lookup current closest distance value */ + cdist = tp.v[di]; + if (cdist >= 10000.0) /* Initial value */ + cdist = 0.0; + return cdist; +} +#endif /* NEVER */ + +/* --------------------------------------------------- */ +/* Seed the whole thing with points */ + +static void +ppoint_seed( +ppoint *s, +fxpos *fxlist, /* List of existing fixed points */ +int fxno /* Number in fixed list */ +) { + int e, di = s->di; + int i, j; + + if (fxno > 0) { + co *pp; + + /* Place all the fixed points at the start of the list */ + if ((pp = (co *)malloc(fxno * sizeof(co))) == NULL) + error ("ppoint: malloc failed on %d fixed nodes",fxno); + + for (i = 0; (i < fxno) && (i < s->tinp); i++) { + node *p = &s->list[i]; /* Destination for point */ + + for (e = 0; e < di; e++) + p->p[e] = fxlist[i].p[e]; + + p->fx = 1; /* is a fixed point */ + s->percept(s->od, p->v, p->p); + + for (e = 0; e < di; e++) { + pp[i].p[e] = p->p[e]; + pp[i].v[e] = p->v[e]; + } + } + s->np = s->fnp = i; + + /* Add new points to rspl interpolation */ + s->g->add_rspl(s->g, 0, pp, i); + + free(pp); + } + + /* Seed the remainder points randomly */ + i = 0; + while(s->np < s->tinp) { + + +#ifdef NEVER + node *p = &s->list[s->np]; + double sum; + + /* Add random points */ + for (sum = 0.0, e = 0; e < di; e++) + sum += p->p[e] = d_rand(0.0, 1.0); + + if (sum > s->ilimit) + continue; + s->np++; + i++; + printf("%cAdded: %d",cr_char,i); +#else + +#ifdef NEVER + int nn; + co pp[WPOINTS]; /* Space for return values */ + + /* Add points at location with the largest error */ + nn = WPOINTS; + + if ((s->np + nn) > s->tinp) /* Limit to desired value */ + nn = s->tinp - s->np; + nn = ppoint_find_worst(s, pp, nn); + + /* Add new points to rspl interpolation and far field */ + s->g->add_rspl(s->g, 0, pp, nn); + add_dist_points(s, pp, nn); +#else + /* Diagnostic version */ + int nn; + co pp[WPOINTS]; /* Space for return values */ + double err1[WPOINTS]; + double err2[WPOINTS]; + + nn = WPOINTS; + + if ((s->np + nn) > s->tinp) /* Limit to desired value */ + nn = s->tinp - s->np; + nn = ppoint_find_worst(s, pp, nn); + + for (j = 0; j < nn; j++) + err1[j] = ppoint_ierr(s, pp[j].p); + + /* Add new points to rspl interpolation and far field */ + s->g->add_rspl(s->g, 0, pp, nn); + add_dist_points(s, pp, nn); + + for (j = 0; j < nn; j++) + err2[j] = ppoint_ierr(s, pp[j].p); + + for (j = 0; j < nn; j++) + printf("~1 improvement after adding point is %f to %f\n",err1[j],err2[j]); +#endif + /* Copy points into ppoint */ + for (j = 0; j < nn; j++) { + for (e = 0; e < di; e++) { + s->list[s->np].p[e] = pp[j].p[e]; + s->list[s->np].v[e] = pp[j].v[e]; + } + s->np++; + } + i += nn; + printf("%cAdded: %d",cr_char,i); +#endif + } + printf("\n"); /* Finish "Added:" */ +} + +/* --------------------------------------------------- */ + +/* Rest the read index */ +static void +ppoint_reset(ppoint *s) { + s->rix = 0; +} + +/* Read the next non-fixed point value */ +/* Return nz if no more */ +static int +ppoint_read(ppoint *s, double *p, double *f) { + int e; + + /* Advance to next non-fixed point */ + while(s->rix < s->np && s->list[s->rix].fx) + s->rix++; + + if (s->rix >= s->np) + return 1; + + /* Return point info to caller */ + for (e = 0; e < s->di; e++) { + if (p != NULL) + p[e] = s->list[s->rix].p[e]; + if (f != NULL) + f[e] = s->list[s->rix].v[e]; + } + s->rix++; + + return 0; +} + +/* Destroy ourselves */ +static void +ppoint_del(ppoint *s) { + + /* Free our nodes */ + free(s->list); + + /* Free our rspl interpolation */ + s->g->del(s->g); + + /* Free our perceptual distance grid */ + s->pd->del(s->pd); + + free (s); +} + +/* Creator */ +ppoint *new_ppoint( +int di, /* Dimensionality of device space */ +double ilimit, /* Ink limit (sum of device coords max) */ +int tinp, /* Total number of points to generate, including fixed */ +fxpos *fxlist, /* List of existing fixed points (may be NULL) */ +int fxno, /* Number of existing fixes points */ +void (*percept)(void *od, double *out, double *in), /* Perceptual lookup func. */ +void *od /* context for Perceptual function */ +) { + ppoint *s; + + // ~~~99 Info for logging + fprintf(stderr, "WPOINTS = %d\n",WPOINTS); + fprintf(stderr, "FPOINTS = %d\n",FPOINTS); + fprintf(stderr, "OPOINTS = %d\n",OPOINTS); + fprintf(stderr, "DDMIX = %f\n",DDMIX); + fprintf(stderr, "DWEIGHT = %f\n",DWEIGHT); + fprintf(stderr, "CLOSED = %f\n",CLOSED); + + if ((s = (ppoint *)calloc(sizeof(ppoint), 1)) == NULL) + error ("ppoint: malloc failed"); + +#if defined(__IBMC__) + _control87(EM_UNDERFLOW, EM_UNDERFLOW); + _control87(EM_OVERFLOW, EM_OVERFLOW); +#endif + + if (di > MXPD) + error ("ppoint: Can't handle di %d",di); + + s->di = di; + + if (tinp < fxno) /* Make sure we return at least the fixed points */ + tinp = fxno; + + s->tinp = tinp; /* Target total number of points */ + s->ilimit = ilimit; + + /* Init method pointers */ + s->reset = ppoint_reset; + s->read = ppoint_read; + s->stats = ppoint_stats; + s->del = ppoint_del; + + /* If no perceptual function given, use default */ + if (percept == NULL) { + s->percept = default_ppoint_to_percept; + s->od = s; + } else { + s->percept = percept; + s->od = od; + } + + /* Allocate the list of points */ + s->np = 0; + + if ((s->list = (node *)calloc(sizeof(node), tinp)) == NULL) + error ("ppoint: malloc failed on nodes"); + + /* Setup the interpolation and perceptual distance rspls */ + { + int e; + int tres, gres[MXDI]; + datai pl,ph; + datai vl,vh; + double avgdev[MXDO]; + pdatas pdd; /* pd callback context */ + +#ifndef NEVER /* High res. */ + if (di <= 2) + tres = 41; /* Make depend on no points and dim ? */ + else if (di <= 3) + tres = 33; /* Make depend on no points and dim ? */ + else + tres = 15; +#else + if (di <= 2) + tres = 3; /* Make depend on no points and dim ? */ + else if (di <= 3) + tres = 17; /* Make depend on no points and dim ? */ + else + tres = 9; +#endif + + /* The interpolation grid mimics the operation of the profile */ + /* package creating a device to CIE mapping for the device from */ + /* the given test points. */ + s->g = new_rspl(RSPL_NOFLAGS, di, di); + + for (e = 0; e < di; e++) { + pl[e] = 0.0; + ph[e] = 1.0; + if (e == 1 || e == 2) { /* Assume Lab */ + vl[e] = -128.0; + vh[e] = 128.0; + } else { + vl[e] = 0.0; + vh[e] = 100.0; + } + gres[e] = tres; + avgdev[e] = 0.005; + } + + /* Setup other details of rspl */ + s->g->fit_rspl(s->g, + RSPL_INCREMENTAL | + /* RSPL_EXTRAFIT | */ /* Extra fit flag */ + 0, + NULL, /* No test points initialy */ + 0, /* No test points */ + pl, ph, gres, /* Low, high, resolution of grid */ + vl, vh, /* Data scale */ + 0.3, /* Smoothing */ + avgdev, /* Average Deviation */ + NULL); + + + /* Track closest perceptual distance to existing test points. */ + /* To save looking up the perceptual value for every grid location */ + /* every time a point is added, cache this values in the grid too. */ + s->pd = new_rspl(RSPL_NOFLAGS, di, di+1); + + /* Initialise the pd grid ready for the first points. */ + pdd.s = s; + pdd.init = 1; /* Initialise values in the grid */ + + s->pd->set_rspl(s->pd, + 0, /* No special flags */ + &pdd, /* Callback function context */ + pdfunc1, /* Callback function */ + pl, ph, gres, /* Low, high, resolution of grid */ + vl, vh); /* Data scale */ + + s->wfpd = -1.0; /* Impossibly good worst point distance */ + } + + /* Create the points */ + ppoint_seed(s, fxlist, fxno); + + /* Print some stats */ + ppoint_stats(s); + + ppoint_reset(s); /* Reset read index */ + + return s; +} + +/* =================================================== */ + +#ifdef STANDALONE_TEST + +/* Graphics Gems curve */ +static double gcurve(double vv, double g) { + if (g >= 0.0) { + vv = vv/(g - g * vv + 1.0); + } else { + vv = (vv - g * vv)/(1.0 - g * vv); + } + return vv; +} + +#ifdef NEVER +static void sa_percept(void *od, double *out, double *in) { + double lab[3]; + + clu->dev_to_rLab(clu, lab, in); + + out[0] = lab[0]; +// out[1] = (lab[1]+100.0)/2.0; + out[1] = (lab[2]+100.0)/2.0; +} +#else + +static void sa_percept(void *od, double *p, double *d) { + +#ifndef NEVER + /* Default Do nothing - copy device to perceptual. */ + p[0] = 100.0 * gcurve(d[0], -4.5); + p[1] = 100.0 * gcurve(d[1], 2.8); + p[1] = 0.8 * p[1] + 0.2 * p[0]; +#else + for (e = 0; e < di; e++) { + double tt = d[e]; + /* Two slopes with a sharp turnover in X */ + if (e == 0) { + if (tt < 0.5) + tt = tt * 0.3/0.5; + else + tt = 0.3 + ((tt-0.5) * 0.7/0.5); + } + p[e] = tt * 100.0; + } +#endif +} +#endif + + +int +main(argc,argv) +int argc; +char *argv[]; +{ + int npoints = 21; + ppoint *s; + long stime,ttime; + error_program = argv[0]; + + printf("Standalone test of ppoint, argument is number of points, default %d\n",npoints); + + if (argc > 1) + npoints = atoi(argv[1]); + + /* Create the required points */ + stime = clock(); + s = new_ppoint(2, 1.5, npoints, NULL, 0, sa_percept, (void *)NULL); + + ttime = clock() - stime; + printf("Execution time = %f seconds\n",ttime/(double)CLOCKS_PER_SEC); + +#ifdef DUMP_PLOT + printf("Perceptual plot:\n"); + dump_image(s, 1); + + printf("Device plot:\n"); + dump_image(s, 0); +#endif /* DUMP_PLOT */ + + s->del(s); + + return 0; +} + +#ifdef NEVER +/* Basic printf type error() and warning() routines */ +#ifdef __STDC__ +void +error(char *fmt, ...) +#else +void +error(va_alist) +va_dcl +#endif +{ + va_list args; +#ifndef __STDC__ + char *fmt; +#endif + + fprintf(stderr,"ppoint: Error - "); +#ifdef __STDC__ + va_start(args, fmt); +#else + va_start(args); + fmt = va_arg(args, char *); +#endif + vfprintf(stderr, fmt, args); + va_end(args); + fprintf(stderr, "\n"); + fflush(stdout); + exit (-1); +} +#endif /* NEVER */ +#endif /* STANDALONE_TEST */ + + +#ifdef STANDALONE_TEST +#ifdef DUMP_PLOT + +/* Dump the current point positions to a plot window file */ +void +static dump_image(ppoint *s, int pcp) { + int i; + double minx, miny, maxx, maxy; + static double *x1a = NULL; + static double *y1a = NULL; + + if (pcp != 0) { /* Perceptual range */ + minx = 0.0; /* Assume */ + maxx = 100.0; + miny = 0.0; + maxy = 100.0; + } else { + minx = 0.0; /* Assume */ + miny = 0.0; + maxx = 1.0; + maxy = 1.0; + } + + if (x1a == NULL) { + if ((x1a = (double *)malloc(s->np * sizeof(double))) == NULL) + error ("ppoint: malloc failed"); + if ((y1a = (double *)malloc(s->np * sizeof(double))) == NULL) + error ("ppoint: malloc failed"); + } + + for (i = 0; i < s->np; i++) { + node *p = &s->list[i]; + + if (pcp != 0) { + x1a[i] = p->v[0]; + y1a[i] = p->v[1]; + } else { + x1a[i] = p->p[0]; + y1a[i] = p->p[1]; + } + } + + /* Plot the vectors */ + do_plot_vec(minx, maxx, miny, maxy, + x1a, y1a, x1a, y1a, s->np, DO_WAIT, NULL, NULL, NULL, NULL, 0); +} + +#endif /* DUMP_PLOT */ +#endif /* STANDALONE_TEST */ + + + + + + + + + + + + + + + diff --git a/target/ppoint.h b/target/ppoint.h new file mode 100644 index 0000000..2a4bc52 --- /dev/null +++ b/target/ppoint.h @@ -0,0 +1,134 @@ + +#ifndef PPOINT_H + +/* + * Argyll Color Correction System + * + * Perceptually distributed point class + * + * Author: Graeme W. Gill + * Date: 16/10/96 + * + * Copyright 1996 - 2004 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. + */ + +#define MXPD 4 /* Maximum ppoint dimentionality */ +#define POW2MXPD 16 /* 2 ^ MXPD */ +#define POW3MXPD 81 /* 3 ^ MXPD */ +#define MXNP (MXPD + 1 + 20) /* Maximum near points */ + +/* tuning parameters */ +#define WPOINTS 20 /* Points returned per group */ +#define FPOINTS 5000 /* Number of far points to track - more is better. */ +#define OPOINTS 250 /* Number of optimsed far points to use - more is slower */ +#define DDMIX 0.75 /* Device distance to perceptual ratio in distance computation */ +#define DWEIGHT 0.05 /* Distance factor weight added to maximum error in opt func */ +#define CLOSED 0.05 /* Too close criteria */ + +/* A sample point node */ +struct _node { + int fx; /* nz if point is fixed */ + double p[MXPD]; /* Device coordinate position */ + double v[MXPD]; /* Subjective value (Labk) */ +}; typedef struct _node node; + +/* Main perceptual point object */ +struct _ppoint { +/* private: */ + int di; /* Point dimensionality */ + double ilimit; /* Ink limit - limit on sum of p[] */ + int fnp; /* Number of existing fixed points in list */ + int tinp; /* target number of total points in list */ + + node *list; /* tinp list of points */ + int np; /* Number of points currently in list */ + + /* Perceptual function handed in */ + void (*percept)(void *od, double *out, double *in); + void *od; /* Opaque data for perceptual point */ + + /* Progressive interpolation grid */ + rspl *g; + + /* Perceptual distance map */ + rspl *pd; + + /* Candidate far point starting values */ + co fp[FPOINTS]; /* Candidate points */ + int nfp; /* Current number in fp[] */ + int wfp; /* Index of current worst far point */ + double wfpd; /* worst far point distance */ + co fwfp[FPOINTS]; /* Working space for find_worst() */ + + /* Other info */ + int rix; /* Next read index */ +// double mn,mx,av; /* Perceptual distance stats */ + +/* public: */ + /* return non-zero if the perceptual point is within the device gammut */ + int (*pig)(struct _ppoint *s, double *p); + + /* Initialise, ready to read out all the points */ + void (*reset)(struct _ppoint *s); + + /* Read the next set of non-fixed points values */ + /* return non-zero when no more points */ + int (*read)(struct _ppoint *s, double *p, double *f); + + /* Calculate and print stats */ + void (*stats)(struct _ppoint *s); + + /* Destroy ourselves */ + void (*del)(struct _ppoint *s); + + }; typedef struct _ppoint ppoint; + + +/* Constructor */ +extern ppoint *new_ppoint(int di, double ilimit, int npoints, + fxpos *fxlist, int fxno, + void (*percept)(void *od, double *out, double *in), void *od); + +/* ------------------------------------------------------- */ +/* Macros for a di dimensional counter */ +/* Declare the counter name nn, dimensions di, & count */ + +#define DCOUNT(nn, di, start, reset, count) \ + int nn[MXPD]; /* 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) + +/* ------------------------------------------------------- */ + +#define PPOINT_H +#endif /* PPOINT_H */ diff --git a/target/prand.c b/target/prand.c new file mode 100644 index 0000000..927243a --- /dev/null +++ b/target/prand.c @@ -0,0 +1,604 @@ + +/* + * Argyll Color Correction System + * + * Perceptual space random test point class + * + * Author: Graeme W. Gill + * Date: 12/9/2004 + * + * Copyright 2004, 2009 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: + + */ + +#include +#include +#include +#include +#if defined(__IBMC__) +#include +#endif +#ifdef DEBUG +#include "plot.h" +#endif +#include "numlib.h" +#include "sort.h" +#include "plot.h" +#include "icc.h" +#include "xicc.h" +#include "xcolorants.h" +#include "targen.h" +#include "prand.h" + +static int prand_from_percept( prand *s, double *p, double *v); + +/* ----------------------------------------------------- */ + +/* Default convert the nodes device coordinates into approximate perceptual coordinates */ +/* (usually overriden by caller supplied function) */ +static void +default_prand(void *od, double *p, double *d) { + prand *s = (prand *)od; + int e; + + /* Default Do nothing - copy device to perceptual. */ + for (e = 0; e < s->di; e++) { + p[e] = d[e] * 100.0; + } +} + +/* Return the largest distance of the point outside the device gamut. */ +/* This will be 0 if inside the gamut, and > 0 if outside. */ +static double +prand_in_dev_gamut(prand *s, double *d) { + int e; + int di = s->di; + double tt, dd = 0.0; + double ss = 0.0; + + for (e = 0; e < di; e++) { + ss += d[e]; + + tt = 0.0 - d[e]; + if (tt > 0.0) { + if (tt > dd) + dd = tt; + } + tt = d[e] - 1.0; + if (tt > 0.0) { + if (tt > dd) + dd = tt; + } + } + tt = ss - s->ilimit; + if (tt > 0.0) { + if (tt > dd) + dd = tt; + } + return dd; +} + +/* --------------------------------------------------- */ +/* Seed the object with the initial fixed points */ + +static void +prand_add_fixed( +prand *s, +fxpos *fxlist, /* List of existing fixed points */ +int fxno /* Number in fixed list */ +) { + int e, di = s->di; + int i; + + /* Add fixed points if there are any */ + if (fxno > 0) { + + for (i = 0; (i < fxno) && (i < s->tinp); i++) { + prnode *p = &s->n[i]; /* Destination for point */ + + for (e = 0; e < di; e++) + p->p[e] = fxlist[i].p[e]; + + p->fx = 1; /* is a fixed point */ + s->percept(s->od, p->v, p->p); + s->np = s->fnp = i+1; + } + } +} + +/* Seed the object with the perceptual space random points. */ +static void +prand_seed(prand *s) { + int e, di = s->di; + + printf("\n"); + + /* Seed the non-fixed points */ + for (; s->np < s->tinp;) { + prnode *p = &s->n[s->np]; /* Next node */ + + for (e = 0; e < di; e++) { + if (e == 1 || e == 2) + p->v[e] = d_rand(-128.0, 128.0); + else + p->v[e] = d_rand(0.0, 100.0); + } + if (prand_from_percept(s, p->p, p->v) == 0) { + s->np++; + printf("%cAdded %d/%d",cr_char,s->np,s->tinp); fflush(stdout); + } + } + printf("\n"); +} + +/* Seed the object with the perceptual space quasi random points. */ +static void +pqrand_seed(prand *s) { + int e, di = s->di; + sobol *sl = NULL; + + if ((sl = new_sobol(di)) == NULL) + error("Creating sobol sequence generator failed"); + + printf("\n"); + + /* Seed the non-fixed points */ + for (; s->np < s->tinp;) { + prnode *p = &s->n[s->np]; /* Next node */ + + if (sl->next(sl, p->v)) + error("Run out of sobol random numbers!"); + + for (e = 0; e < di; e++) { + if (e == 1 || e == 2) + p->v[e] = p->v[e] * 256.0 - 128.0; + else + p->v[e] *= 100.0; + } + if (prand_from_percept(s, p->p, p->v) == 0) { + s->np++; + printf("%cAdded %d/%d",cr_char,s->np,s->tinp); fflush(stdout); + } + } + printf("\n"); + sl->del(sl); +} + +/* --------------------------------------------------- */ +/* Support accessing the list of generated sample points */ + +/* Rest the read index */ +static void +prand_reset(prand *s) { + s->rix = 0; +} + +/* Read the next non-fixed point value */ +/* Return nz if no more */ +static int +prand_read(prand *s, double *p, double *f) { + int e; + + /* Advance to next non-fixed point */ + while(s->rix < s->np && s->n[s->rix].fx) + s->rix++; + + if (s->rix >= s->np) + return 1; + + /* Return point info to caller */ + for (e = 0; e < s->di; e++) { + if (p != NULL) + p[e] = s->n[s->rix].p[e]; + if (f != NULL) + f[e] = s->n[s->rix].v[e]; + } + s->rix++; + + return 0; +} + +/* --------------------------------------------------- */ +/* Main object creation/destruction */ + +static void init_pmod(prand *s); + +/* Destroy ourselves */ +static void +prand_del(prand *s) { + free(s->n); + + if (s->pmod != NULL) + free(s->pmod); + + free (s); +} + +/* Creator */ +prand *new_prand( +int di, /* Dimensionality of device space */ +double ilimit, /* Ink limit (sum of device coords max) */ +int tinp, /* Total number of points to generate, including fixed */ +fxpos *fxlist, /* List of existing fixed points (may be NULL) */ +int fxno, /* Number of existing fixes points */ +int quasi, /* nz to use quasi random (sobol) */ +void (*percept)(void *od, double *out, double *in), /* Perceptual lookup func. */ +void *od /* context for Perceptual function */ +) { + prand *s; + + if ((s = (prand *)calloc(sizeof(prand), 1)) == NULL) + error ("prand: malloc failed"); + +#if defined(__IBMC__) + _control87(EM_UNDERFLOW, EM_UNDERFLOW); + _control87(EM_OVERFLOW, EM_OVERFLOW); +#endif + + s->di = di; + + if (tinp < fxno) /* Make sure we return at least the fixed points */ + tinp = fxno; + + s->tinp = tinp; /* Target total number of points */ + s->ilimit = ilimit; + + /* Init method pointers */ + s->reset = prand_reset; + s->read = prand_read; + s->del = prand_del; + + /* If no perceptual function given, use default */ + if (percept == NULL) { + s->percept = default_prand; + s->od = s; + } else { + s->percept = percept; + s->od = od; + } + + /* Init the inverse perceptual function lookup */ + init_pmod(s); + + /* Allocate the space for the target number of points */ + if ((s->n = (prnode *)calloc(sizeof(prnode), s->tinp)) == NULL) + error ("prand: malloc failed on sample nodes"); + s->np = s->fnp = 0; + + /* Setup the fixed points */ + prand_add_fixed(s, fxlist, fxno); + + if (tinp > fxno) { /* Create the perceptual space random points */ + if (quasi) + pqrand_seed(s); + else + prand_seed(s); + } + + prand_reset(s); /* Reset read index */ + + return s; +} + +/* =================================================== */ +/* Compute a simple but unbounded model of the */ +/* perceptual function, used by inversion. We use the */ +/* current vertex values to setup the model */ +/* (Perhaps this should be moved to targen ?) */ + +/* A vertex point */ +struct _vxpt { + double p[MXTD]; /* Device position */ + double v[MXTD]; /* Perceptual value */ +}; typedef struct _vxpt vxpt; + +/* Structure to hold data for unbounded optimization function */ +struct _ubfit { + prand *s; /* prand structure */ + vxpt *vxs; /* List of vertex values */ + int _nvxs, nvxs; +}; typedef struct _ubfit ubfit; + +/* Matrix optimisation function handed to powell() */ +static double xfitfunc(void *edata, double *x) { + ubfit *uf = (ubfit *)edata; + prand *s = uf->s; + int i, e, di = s->di; + double rv = 0.0; + + /* For all the vertexes */ + for (i = 0; i < uf->nvxs; i++) { + double v[MXTD], ev; + + /* Apply matrix cube interpolation */ + icxCubeInterp(x, di, di, v, uf->vxs[i].p); + + /* Evaluate the error */ + for (ev = 0.0, e = 0; e < di; e++) { + double tt; + tt = uf->vxs[i].v[e] - v[e]; + ev += tt * tt; + } + rv += ev; + } + return rv; +} + +/* Fit the unbounded perceptual model to the perceptual function */ +static void init_pmod(prand *s) { + int i, ee, e, k, di = s->di; + double *sa; + double rerr; + ubfit uf; + + uf.s = s; + uf.vxs = NULL; + uf.nvxs = uf._nvxs = 0; + + /* Allocate space for parameters */ + if ((s->pmod = malloc(di * (1 << di) * sizeof(double))) == NULL) + error("Malloc failed for pmod"); + if ((sa = malloc(di * (1 << di) * sizeof(double))) == NULL) + error("Malloc failed for pmod sa"); + + /* Create a list of vertex values for the colorspace */ + /* Use in gamut vertexes, and compute clipped edges */ + for (ee = 0; ee < (1 << di); ee++) { + double p[MXTD], ss; + + for (ss = 0.0, e = 0; e < di; e++) { + if (ee & (1 << e)) + p[e] = 1.0; + else + p[e] = 0.0; + ss += p[e]; + } + if (ss < s->ilimit) { /* Within gamut */ + if (uf.nvxs >= uf._nvxs) { + uf._nvxs = 5 + uf._nvxs * 2; + if ((uf.vxs = (vxpt *)realloc(uf.vxs, sizeof(vxpt) * uf._nvxs)) == NULL) + error ("Failed to malloc uf.vxs"); + } + for (k = 0; k < di; k++) + uf.vxs[uf.nvxs].p[k] = p[k]; + uf.nvxs++; + } else if ((ss - 1.0) < s->ilimit) { /* far end of edge out of gamut */ + double max = s->ilimit - (ss - 1.0); /* Maximum value of one */ + for (e = 0; e < di; e++) { + if ((ee & (1 << e)) == 0) + continue; + p[e] = max; + if (uf.nvxs >= uf._nvxs) { + uf._nvxs = 5 + uf._nvxs * 2; + if ((uf.vxs = (vxpt *)realloc(uf.vxs, sizeof(vxpt) * uf._nvxs)) == NULL) + error ("Failed to malloc uf.vxs"); + } + for (k = 0; k < di; k++) + uf.vxs[uf.nvxs].p[k] = p[k]; + uf.nvxs++; + + p[e] = 1.0; /* Restore */ + } + } /* Else whole edge is out of gamut */ + } + + /* Lookup perceptual values */ + for (i = 0; i < uf.nvxs; i++) { + s->percept(s->od, uf.vxs[i].v, uf.vxs[i].p); +//printf("~1 vtx %d: dev %f %f %f, perc %f %f %f\n",i, uf.vxs[i].p[0], uf.vxs[i].p[1], uf.vxs[i].p[2], uf.vxs[i].v[0], uf.vxs[i].v[1], uf.vxs[i].v[2]); + } + + /* Setup matrix to be closest values initially */ + for (e = 0; e < (1 << di); e++) { /* For each colorant combination */ + int j, f; + double bdif = 1e6; + double ov[MXTD]; + int bix = -1; + + /* Search the vertex list to find the one closest to this input combination */ + for (i = 0; i < uf.nvxs; i++) { + double dif = 0.0; + + for (j = 0; j < di; j++) { + double tt; + if (e & (1 << j)) + tt = 1.0 - uf.vxs[i].p[j]; + else + tt = 0.0 - uf.vxs[i].p[j]; + dif += tt * tt; + } + if (dif < bdif) { /* best so far */ + bdif = dif; + bix = i; + if (dif < 0.001) + break; /* Don't bother looking further */ + } + } + for (f = 0; f < di; f++) + s->pmod[f * (1 << di) + e] = uf.vxs[bix].v[f]; + } + + for (e = 0; e < (di * (1 << di)); e++) + sa[e] = 10.0; + + if (powell(&rerr, di * (1 << di), s->pmod, sa, 0.001, 1000, + xfitfunc, (void *)&uf, NULL, NULL) != 0) { + warning("Powell failed to converge, residual error = %f",rerr); + } + +#ifdef DEBUG + printf("Perceptual model fit residual = %f\n",sqrt(rerr)); +#endif + s->pmod_init = 1; + + free(sa); +} + +/* Clip a device value to the gamut */ +static int +prand_clip_point(prand *s, double *cd, double *d) { + int e, di = s->di; + double ss = 0.0; + int rv = 0; + + for (e = 0; e < di; e++) { + ss += d[e]; + cd[e] = d[e]; + if (cd[e] < 0.0) { + cd[e] = 0.0; + rv |= 1; + } else if (cd[e] > 1.0) { + cd[e] = 1.0; + rv |= 1; + } \ + } + + if (ss > s->ilimit) { + ss = (ss - s->ilimit)/s->di; + for (e = 0; e < di; e++) + cd[e] -= ss; + rv |= 1; + } + return rv; +} + +/* Unbounded perceptual lookup. */ +/* return nz if it was actually clipped and extended */ +static int prand_cc_percept(prand *s, double *v, double *p) { + double cp[MXTD]; + int clip; + + clip = prand_clip_point(s, cp, p); + + s->percept(s->od, v, cp); + + /* Extend perceptual value using matrix model */ + if (clip) { + int e, di = s->di; + double mcv[MXTD], zv[MXTD]; + +#ifdef DEBUG + if (s->pmod_init == 0) + error("ofps_cc_percept() called before pmod has been inited"); +#endif + /* Lookup matrix mode of perceptual at clipped device */ + icxCubeInterp(s->pmod, di, di, mcv, cp); + + /* Compute a correction factor to add to the matrix model to */ + /* give the actual perceptual value at the clipped location */ + for (e = 0; e < di; e++) + zv[e] = v[e] - mcv[e]; + + /* Compute the unclipped matrix model perceptual value */ + icxCubeInterp(s->pmod, di, di, v, p); + + /* Add the correction value to it */ + for (e = 0; e < di; e++) + v[e] += zv[e]; + } + return clip; +} + + +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ +/* Reverse lookup function :- perceptual to device coordinates */ +/* Using dnsq */ + +/* Structure to hold data for optimization function */ +struct _rdatas { + prand *s; /* prand structure */ + double *ptv; /* Perceptual target value */ +}; typedef struct _rdatas rdatas; + + +/* calculate the functions at x[] */ +int prand_dnsq_solver( /* Return < 0 on abort */ + void *fdata, /* Opaque data pointer */ + int n, /* Dimenstionality */ + double *x, /* Multivariate input values */ + double *fvec, /* Multivariate output values */ + int iflag /* Flag set to 0 to trigger debug output */ +) { + rdatas *ed = (rdatas *)fdata; + prand *s = ed->s; + double v[MXTD]; + int e, di = s->di; + + prand_cc_percept(s, v, x); + + for (e = 0; e < di; e++) + fvec[e] = ed->ptv[e] - v[e]; + +//printf("~1 %f %f %f from %f %f %f\n", fvec[0], fvec[1], fvec[2], x[0], x[1], x[2]); + return 0; +} + +/* Given a point in perceptual space, an approximate point */ +/* in device space, return the device value corresponding to */ +/* the perceptual value, plus the clipped perceptual value. */ +/* Return 1 if the point is out of gamut or dnsq failed. */ +static int +prand_from_percept( +prand *s, +double *p, /* return (clipped) device position */ +double *v /* target perceptual */ +) { + int e, di = s->di; + rdatas ed; + double ss; /* Initial search area */ + double fvec[MXTD]; /* Array that will be RETURNed with thefunction values at the solution */ + double dtol; /* Desired tollerance of the solution */ + double tol; /* Desired tollerance of root */ + int maxfev; /* Maximum number of function calls. set to 0 for automatic */ + int rv; + +//printf("~1 percept2 called with %f %f %f\n", v[0], v[1], v[2]); + ed.s = s; + ed.ptv = v; /* Set target perceptual point */ + + for (e = 0; e < di; e++) + p[e] = 0.3; /* Start location */ + ss = 0.1; + dtol = 1e-6; + tol = 1e-8; + maxfev = 1000; + + rv = dnsqe((void *)&ed, prand_dnsq_solver, NULL, di, p, ss, fvec, dtol, tol, maxfev, 0); + + if (rv != 1 && rv != 3) { /* Fail to converge */ +//printf("~1 failed with rv %d\n",rv); + return 1; + } + +//printf("~1 got soln %f %f %f\n", p[0], p[1], p[2]); + if (prand_clip_point(s, p, p)) { +//printf("~1 clipped\n"); + return 1; + } + + return 0; +} + + + + + + + + + + + + + + + + diff --git a/target/prand.h b/target/prand.h new file mode 100644 index 0000000..d7e50eb --- /dev/null +++ b/target/prand.h @@ -0,0 +1,68 @@ + +#ifndef PRAND_H + +/* + * Argyll Color Correction System + * + * Perceptual space random test point class + * + * Author: Graeme W. Gill + * Date: 12/9/2004 + * + * Copyright 2004 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. + */ + +/* A sample point node */ +struct _prnode { + int fx; /* nz if point is fixed */ + double p[MXTD]; /* Device coordinate position */ + double v[MXTD]; /* Subjective value (Labk) */ +}; typedef struct _prnode prnode; + + +/* Main object */ +struct _prand { +/* private: */ + int di; /* Point dimensionality */ + double ilimit; /* Ink limit - limit on sum of p[] */ + int fnp; /* Number of existing fixed points in list */ + int tinp; /* target number of total points in list, including fixed points */ + + int np; /* Number of points currently in list */ + prnode *n; /* tinp list of points */ + + /* Perceptual function handed in */ + void (*percept)(void *od, double *out, double *in); + void *od; /* Opaque data for perceptual point */ + + /* Unbounded perceptual model */ + double *pmod; + int pmod_init; /* It's been initialised */ + + /* Other info */ + int rix; /* Next read index */ + +/* public: */ + /* Initialise, ready to read out all the points */ + void (*reset)(struct _prand *s); + + /* Read the next set of non-fixed points values */ + /* return non-zero when no more points */ + int (*read)(struct _prand *s, double *d, double *p); + + /* Destroy ourselves */ + void (*del)(struct _prand *s); + + }; typedef struct _prand prand; + +/* Constructor */ +extern prand *new_prand(int di, double ilimit, int npoints, + fxpos *fxlist, int fxno, int quasi, + void (*percept)(void *od, double *out, double *in), void *od); + +#define PRAND_H +#endif /* PRAND_H */ diff --git a/target/printtarg.c b/target/printtarg.c new file mode 100644 index 0000000..25d889a --- /dev/null +++ b/target/printtarg.c @@ -0,0 +1,4300 @@ + +/* + * Argyll Color Correction System + * PostScript print chart generator module. + * + * Author: Graeme W. Gill + * Date: 28/9/96 + * + * Copyright 1996 - 2009 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: + + Add "single pixel patch" mode, for pure digital processing for + abstract profile creation. + + Add -h2 flag for Munki for super high-res chart ? + Note: i1Pro: Illum spot: 3.5mm Aperture: 4.5mm, Physical aperture: 4.55mm + Munki: Illum spot: 8.0mm Aperture: 6.0mm, Physical aperture: 7.63mm + + Add an option that allows including a scale gauge, to detect + accidental re-scaling. + + Make it aportion extra space evenly around the chart + rather than at the trailing edges. + + Add direct PDF support, including NChannel output. +*/ + +/* This program generates a PostScript or TIFF print target file, */ +/* containing color test patches, given the .ti1 file specifying */ +/* what the colors are. */ + +/* The output is designed to suite a general XY spectrometer (such as */ +/* the Gretag SpectrScan), a handheld, manual instrument, or */ +/* an Xrite DTP51, DTP41 or Eye-One strip spectrometer. */ + +/* Description: + + This program simply generates a PostScripto or TIFF file containing + the patches layed out for an Xrite DTP20/DTP22/DTP51/DTP41/SpectroScan/i1pro/Munki. + It allows them to be layed out on a choice of paper sizes, + with the appropriate contrasting color spacers between + each patch for the strip reading instruments. Unlike other + charts, Argyll charts are generated as required, rather + that being fixed. Also unlike most other strip reading charts, + the spacers may colored, so that the density contrast ratio is + guaranteed, even when two patches are about 50% density. + + Another feature is the pseudo random patch layout. This has + three purposes. One is to try and average out any variation + in the device response in relationship to the location of + the patch on the paper. Color copiers and printing presses + (for instance), are notorious in having side to side density + variations. + + Another purpose of the random patch layout, is that it gives + the reading program a good mechanism for detecting user error. + It can guess the expected values, compare them to the readings, + and complain if it seems that the strip is probably the wrong + one. It can also be used to identify and rectify a strip + that has been read in backwards. + + The final purpose of the random patch layout is to optimse the + contrast between patches in a strip, to improve the robustness + of the strip reading, and to be able to distinguish the directin + a strip has been read in. Using this, small charts may be even be + generated without any gaps between the test patches. + + */ + +/* + * Nomencalture: + * + * Largely due to how the strip readers name things, the following terms + * are used for how patches are grouped: + * + * Pass, Row: One row of patches in a strip. A pass is usually labeled + * with a unique alphabetic label. + * Strip: A group of passes that can be read by a strip reader. + * For an XY instrument, the strip is a complete sheet, and + * each pass is one column. The rows of an XY chart are + * the step numbers within a pass. + * Step: One test patch in a pass. + * Sheet: One sheet of paper, containing full and partial strips. + * For an XY instrument, there will be only one strip per sheet. + * + */ + +/* TTBD: + * + * Improve EPS support to add a preview to each eps file. + */ + +#undef DEBUG +#undef FORCEN /* For testing, force DeviceN */ +#define DEN_COMPRESS /* Compress density estimates > 1.0 */ + /* - this biases it towards white spacers */ + +#include +#include +#include +#if defined(__IBMC__) +#include +#endif +#include +#include +#include +#include +#include "copyright.h" +#include "aconfig.h" +#include "numlib.h" +#include "cgats.h" +#include "icc.h" +#include "xicc.h" +#include "insttypes.h" +#include "render.h" +#include "randix.h" +#include "alphix.h" +#include "rspl.h" +#include "sort.h" + +#include + +/* Convert inches into mm */ +#define inch2mm(xx) ((xx) * 25.4) + +/* Convert mm into points */ +#define mm2pnt(xx) ((xx) * 72.0/25.4) + +/* A color structure */ +struct _col { + int nmask; /* colorant mask */ + int altrep; /* alternate grey or CMY representation type 0..8 */ + int i; /* cols list index */ + int ix; /* random list index */ + char *id; /* Id string */ + char loc[10]; /* Location ID string */ + int t; /* Tag */ +#define T_XYZ 0x0001 +#define T_LAB 0x0002 +#define T_DEN 0x0004 +#define T_RGB 0x0008 +#define T_N 0x0010 +#define T_NFB 0x2000 /* DeviceN fallback enabled */ +#define T_PRESET 0x4000 /* A preset color rather than a test patch */ +#define T_PAD 0x8000 /* A padding color patch */ + double XYZ[3]; /* Aproximate XYZ */ + double Lab[3]; /* Aproximate Lab */ + double den[4]; /* Approx statusT density + visual density */ + int dtp20_octval; /* DTP20 octal value */ + double dtp20_psize; /* DTP20 patch width */ + double rgb[3]; /* Aproximate sRGB */ + int n; /* Number of colorants */ + double dev[ICX_MXINKS]; /* Value of colorants */ + + struct _col *nc[2]; /* Neigborhood colors */ + struct _col *oc; /* Opposite direction color */ + double wnd; /* Worst neigborhood contrast density */ +}; typedef struct _col col; + +#define min2(a,b) ((a) < (b) ? (a) : (b)) +#define min3(a,b,c) (min2((a), min2((b),(c)))) +#define max2(a,b) ((a) > (b) ? (a) : (b)) +#define max3(a,b,c) (max2((a), max2((b),(c)))) + +/* Declare edge tracking functions */ +void et_init(void); +void et_height(double height); +void et_media(double *rgb); +void et_color(double *rgb); +void et_edge(int isx, int negh, double mj, double mi0, double mi1); +void et_patch(char *id, double xo, double yo, double w, double h); +void et_fiducial(double x, double y); +void et_write(char *fname, col *cols, int *rix, int si, int ei); +void et_clear(void); + +/* ====================================================== */ +/* Calibration Target rendering class */ +/* Outputs either PostScript or TIFF raster */ +/* test charts. */ +/* We just do an error() if something goes wrong */ + +/* Common class structure */ +#define TREND_STRUCT \ + /* Start a page */ \ + void (*startpage)(struct _trend *s, int pn); \ + /* End a page */ \ + void (*endpage)(struct _trend *s); \ + /* set the color */ \ + void (*setcolor)(struct _trend *s, xcal *cal, col *c); \ + /* A rectangle, with optional edge tracking */ \ + void (*rectangle)(struct _trend *s, /* Render a rectangle */ \ + double x, double y, /* Top left corner of rectangle in mm from origin */ \ + double w, double h, /* Width and height */ \ + char *id, /* Patch id, NULL if not a diagnostic mark */ \ + int et /* nz if use edge tracking on this */ \ + ); \ + /* A testpad hexagon. */ \ + void (*hexagon)(struct _trend *s, \ + double x, double y, /* Top left vertex of hex mm from origin */ \ + double w, double h, /* Width and height */ \ + int step, /* Step number from 0 to figure odd/even */ \ + char *id /* Patch id, NULL if not a diagnostic mark */ \ + ); \ + /* A centered string */ \ + void (*string)(struct _trend *s, \ + double x, double y, /* Bot Left Corner of rectangle in mm from origin */ \ + double w, double h, /* Width and height */ \ + char *str /* String */ \ + ); \ + /* A vertically centered string, rendered from bottom to top */ \ + void (*vstring)(struct _trend *s, \ + double x, double y, /* Bot Right Corner of rectangle in mm from origin */ \ + double w, double h, /* Width and height */ \ + char *str /* String */ \ + ); \ + /* A dotted line */ \ + void (*dline)(struct _trend *s, \ + double x0, double y0, /* Start of line */ \ + double x1, double y1, /* End of line */ \ + double w /* Width */ \ + ); \ + /* Delete the object */ \ + void (*del)(struct _trend *s); \ + +struct _trend { + TREND_STRUCT +}; typedef struct _trend trend; + +/* ==================================== */ +/* PostScript output class */ + +struct _ps_trend { + TREND_STRUCT + FILE *of; /* Postscript output file */ + int eps; /* EPS flag */ + char *fname; +}; typedef struct _ps_trend ps_trend; + +/* Start a page */ +static void ps_startpage(trend *ss, int pagen) { + ps_trend *s = (ps_trend *)ss; + + fprintf(s->of,"%%%%Page: (Page %d) %d\n",pagen,pagen); +} + +/* End a page */ +static void ps_endpage(trend *ss) { + ps_trend *s = (ps_trend *)ss; + + fprintf(s->of,"showpage\n"); + fprintf(s->of,"\n"); +} + +/* Set a device N color with fallback */ +static void +gen_ncolor(ps_trend *s, col *c) { + int i; + + /* define the colorspace */ + fprintf(s->of,"[ /DeviceN [ "); + for (i = 0; i < c->n; i++) { + int imask = icx_index2ink(c->nmask, i); + fprintf(s->of,"/%s ", icx_ink2psstring(imask)); + } + + if (c->t & T_NFB) { /* Use color fallback */ + fprintf(s->of,"] /DeviceRGB "); /* Fallback to RGB */ + fprintf(s->of,"{ "); + for (i = 0; i < c->n; i++) /* Remove N values */ + fprintf(s->of,"pop "); + for (i = 0; i < 3; i++) /* Set RGB values */ + fprintf(s->of,"%f ",c->rgb[i]); + } else { + fprintf(s->of,"] /DeviceGray "); /* Fallback to Gray */ + fprintf(s->of,"{ "); + for (i = 0; i < c->n; i++) /* Remove N values */ + fprintf(s->of,"pop "); + fprintf(s->of,"%f ",(c->rgb[0] + c->rgb[1] + c->rgb[2])/3.0); /* Set Gray value */ + } + + fprintf(s->of," } ] setcolorspace\n"); + + /* Set the color */ + for (i = 0; i < c->n; i++) + fprintf(s->of,"%f ",c->dev[i]); + fprintf(s->of,"setcolor\n"); +} + + +/* Set a device color */ +/* Set it by the rep with most components */ +static void ps_setcolor(trend *ss, xcal *cal, col *c) { + ps_trend *s = (ps_trend *)ss; + double cdev[ICX_MXINKS]; /* Calibrated device color */ + + if (cal != NULL) + cal->interp(cal, cdev, c->dev); + else { + int j; + for (j = 0; j < c->n; j++) + cdev[j] = c->dev[j]; + } + + if ((c->t & T_N) == 0) + error("ps_setcolor with no device values set"); + +#ifndef FORCEN + if (c->nmask == ICX_W) { + if ((c->t & T_PRESET) == 0) + fprintf(s->of,"%% Ref %s %s %f\n",c->id, c->loc, 100.0 * cdev[0]); + + if (c->altrep == 0) { /* DeviceGray */ + fprintf(s->of,"%f setgray\n",cdev[0]); + } else if (c->altrep == 4) { /* DeviceRGB */ + fprintf(s->of,"%f %f %f setrgbcolor\n",cdev[0],cdev[0],cdev[0]); + } else if (c->altrep == 5) { /* Separation */ + fprintf(s->of,"[ /Separation (White) /DeviceGray { pop %f } ] setcolorspace\n", + cdev[0]); + fprintf(s->of,"%f setcolor\n",cdev[0]); + } else if (c->altrep == 6) { /* DeviceN */ + gen_ncolor(s, c); + } else { + error("Device white encoding not approproate!"); + } + + } else if (c->nmask == ICX_K) { + if ((c->t & T_PRESET) == 0) + fprintf(s->of,"%% Ref %s %s %f\n",c->id, c->loc, 100.0 * cdev[0]); + if (c->altrep == 0) { /* DeviceGray */ + fprintf(s->of,"%f setgray\n",1.0 - cdev[0]); + } else if (c->altrep == 1) { /* DeviceCMYK */ + fprintf(s->of,"0.0 0.0 0.0 %f setcmykcolor\n",cdev[0]); + } else if (c->altrep == 2) { /* Separation */ + fprintf(s->of,"[ /Separation (Black) /DeviceGray { pop %f } ] setcolorspace\n", + 1.0 - cdev[0]); + fprintf(s->of,"%f setcolor\n",cdev[0]); + } else if (c->altrep == 3) { /* DeviceN */ + gen_ncolor(s, c); + } else { + error("Device black encoding not approproate!"); + } + + } else if (c->nmask == ICX_CMY) { + if ((c->t & T_PRESET) == 0) + fprintf(s->of,"%% Ref %s %s %f %f %f\n", c->id, c->loc, + 100.0 * cdev[0], 100.0 * cdev[1], 100.0 * cdev[2]); + + if (c->altrep == 0) { /* DeviceCMYK */ + fprintf(s->of,"%f %f %f 0.0 setcmykcolor\n",cdev[0],cdev[1],cdev[2]); + } else if (c->altrep == 7) { /* Inverted DeviceRGB */ + fprintf(s->of,"%f %f %f setrgbcolor\n",1.0-cdev[0],1.0-cdev[1],1.0-cdev[2]); + } else if (c->altrep == 8) { /* DeviceN */ + gen_ncolor(s, c); + } else { + error("Device CMY encoding not approproate!"); + } + + } else if (c->nmask == ICX_RGB || c->nmask == ICX_IRGB) { + if ((c->t & T_PRESET) == 0) + fprintf(s->of,"%% Ref %s %s %f %f %f\n",c->id, c->loc, + 100.0 * cdev[0], 100.0 *cdev[1], 100.0 *cdev[2]); + fprintf(s->of,"%f %f %f setrgbcolor\n",cdev[0],cdev[1],cdev[2]); + + } else if (c->nmask == ICX_CMYK) { + if ((c->t & T_PRESET) == 0) + fprintf(s->of,"%% Ref %s %s %f %f %f %f\n", c->id, c->loc, + 100.0 * cdev[0], 100.0 * cdev[1], 100.0 * cdev[2], 100.0 * cdev[3]); + fprintf(s->of,"%f %f %f %f setcmykcolor\n",cdev[0],cdev[1],cdev[2],cdev[3]); + + } else +#endif /* !FORCEN */ + { /* Device N */ + int i; + if ((c->t & T_PRESET) == 0) { + fprintf(s->of,"%% Ref %s %s",c->id, c->loc); + for (i = 0; i < c->n; i++) + fprintf(s->of,"%f ", 100.0 * cdev[i]); + fprintf(s->of,"\n"); + } + gen_ncolor(s, c); + } + + /* Remember edge tracking color */ + et_color(c->rgb); +} + +/* Generate a rectangle, with optional edge tracking */ +/* Note the page coordinate origin is bottom left. */ +static void ps_rectangle(trend *ss, + double x, double y, /* Top left corner of rectangle in mm from origin */ + double w, double h, /* Width and height */ + char *id, /* Patch id, NULL if not a diagnostic mark */ + int et /* nz if use edge tracking on this */ +) { + ps_trend *s = (ps_trend *)ss; + + if (w < 1e-6 || h < 1e-6) + return; /* Skip zero sized rectangle */ + y -= h; /* Convert to bottom left corner */ + x = mm2pnt(x); + y = mm2pnt(y); + w = mm2pnt(w); + h = mm2pnt(h); + fprintf(s->of,"%f %f %f %f rect\n",w,h,x,y); + + if (et) { + et_patch(id, x, y, w, h); + et_edge(1, 0, x, y, y + h); + et_edge(1, 1, x + w, y, y + h); + et_edge(0, 0, y, x, x + w); + et_edge(0, 1, y + h, x, x + w); + } +} + +/* Generate one testpad hexagon. */ +/* Note the page coordinate origin is bottom left. */ +/* The hex always has left/right sides */ +/* and peaks at the top and the bottom. */ +static void ps_hexagon(trend *ss, + double x, double y, /* Top left vertex of hex mm from origin */ + double w, double h, /* Width and height */ + int step, /* Step number from 0 to figure odd/even */ + char *id /* Patch id, NULL if not a diagnostic mark */ +) { + ps_trend *s = (ps_trend *)ss; + + if (w < 1e-6 || h < 1e-6) + return; /* Skip zero sized rectangle */ + if ((step & 1) == 0) /* Even so left side of stagger */ + x -= 0.25 * w; + else /* Odd so right side of stagger */ + x += 0.25 * w; + y = y - 5.0/6.0 * h; + h *= 2.0/3.0; /* Convert to hex side length */ + x = mm2pnt(x); + y = mm2pnt(y); + w = mm2pnt(w); + h = mm2pnt(h); + fprintf(s->of,"%f %f %f %f hex\n",w,h,x,y); +} + +/* A centered string */ +static void ps_string(trend *ss, + double x, double y, /* Bot Left Corner of rectangle in mm from origin */ + double w, double h, /* Width and height */ + char *str /* String */ +) { + ps_trend *s = (ps_trend *)ss; + + if (fabs(w) < 1e-6 || fabs(h) < 1e-6) + return; /* Skip zero sized string */ + x = mm2pnt(x); + y = mm2pnt(y); + w = mm2pnt(w); + h = mm2pnt(h); + fprintf(s->of,"%f scaleTimes\n",h * 0.75); + fprintf(s->of,"(%s) %f %f centerShow\n",str,x+w/2.0,y+h/2.0); +} + +/* A vertically centered string */ +static void ps_vstring(trend *ss, + double x, double y, /* Bot Right Corner of rectangle in mm from origin */ + double w, double h, /* Width and height */ + char *str /* String */ +) { + ps_trend *s = (ps_trend *)ss; + + if (fabs(w) < 1e-6 || fabs(h) < 1e-6) + return; /* Skip zero sized string */ + x = mm2pnt(x); + y = mm2pnt(y); + w = mm2pnt(w); + h = mm2pnt(h); + fprintf(s->of,"%f scaleTimes\n",w * 0.75); + fprintf(s->of,"(%s) %f %f vcenterShow\n",str,x-w/2.0,y+h/2.0); +} + +/* A dotted line */ +static void ps_dline(trend *ss, + double x1, double y1, /* Start of line */ + double x2, double y2, /* End of line */ + double w /* Width */ +) { + ps_trend *s = (ps_trend *)ss; + + if (fabs(w) < 1e-6 || fabs(y2 - y1) < 1e-6) + return; /* Skip zero sized line */ + x1 = mm2pnt(x1); + x2 = mm2pnt(x2); + y1 = mm2pnt(y1); + y2 = mm2pnt(y2); + w = mm2pnt(w); + fprintf(s->of,"[%f %f] %f setdash\n",mm2pnt(1.0),mm2pnt(2.0),mm2pnt(0.0)); + fprintf(s->of,"%f setlinewidth\n",w); + fprintf(s->of,"newpath %f %f moveto %f %f lineto stroke\n",x1,y1,x2,y2); +} + +/* Complete operations, then delete the object */ +static void ps_del(trend *ss) { + ps_trend *s = (ps_trend *)ss; + + if (s->of) { + fprintf(s->of,"\n"); + fprintf(s->of,"%%%%EOF\n"); + + if (fclose(s->of)) + error ("Unable to close output file '%s'",s->fname); + } + if (s->fname) + free(s->fname); + + free(s); +} + +trend *new_ps_trend( + char *fname, /* File name */ + int npages, /* Number of pages needed */ + int nmask, /* Non zero if we are doing a DeviceN chart */ + double pw, double ph, /* Page width and height in mm */ + int eps, /* EPS flag */ + int rand, /* randomize */ + int rstart /* Random start number/chart ID */ +) { + ps_trend *s; + + if ((s = (ps_trend *)calloc(1, sizeof(ps_trend))) == NULL) { + return NULL; + } + + s->startpage = ps_startpage; + s->endpage = ps_endpage; + s->setcolor = ps_setcolor; + s->rectangle = ps_rectangle; + s->hexagon = ps_hexagon; + s->string = ps_string; + s->vstring = ps_vstring; + s->dline = ps_dline; + s->del = ps_del; + + s->eps = eps; + + if ((s->of = fopen(fname,"w")) == NULL) { + error ("Unable to open output file '%s'",fname); + } + + if ((s->fname = strdup(fname)) == NULL) + error("stdup of fname failed"); + + /* Generate PS file prolog */ + { + time_t clk = time(0); + struct tm *tsp = localtime(&clk); + int ipw, iph; + + ipw = (int)ceil(mm2pnt(pw)); + iph = (int)ceil(mm2pnt(ph)); + if (eps) + fprintf(s->of,"%%!PS-Adobe-3.0 EPSF-3.0\n"); + else + fprintf(s->of,"%%!PS-Adobe-3.0\n"); + fprintf(s->of,"%%%%Title: Argyll Color Calibration Target\n"); +#ifdef FORCEN + if (1) { +#else + if (nmask != ICX_W /* If not a Gray, RGB or CMYK device space */ + && nmask != ICX_K + && nmask != ICX_RGB + && nmask != ICX_IRGB + && nmask != ICX_CMYK) { +#endif + fprintf(s->of,"%%%%LanguageLevel: 3\n"); + } else { + fprintf(s->of,"%%%%LanguageLevel: 1\n"); + if (nmask == ICX_CMYK) + fprintf(s->of,"%%%%Extensions: CMYK\n"); + } + + fprintf(s->of,"%%%%Creator: Argyll target chart generator\n"); + fprintf(s->of,"%%%%For: The user who wants accurate color\n"); +// fprintf(s->of,"%%%%Version: %s\n",VERSION); + fprintf(s->of,"%%%%CreationDate: %s",asctime(tsp)); + fprintf(s->of,"%%%%DocumentData: Clean7Bit\n"); + if (eps) + fprintf(s->of,"%%%%Pages: %d\n",1); + else + fprintf(s->of,"%%%%Pages: %d\n",npages); + fprintf(s->of,"%%%%PageOrder: Ascend\n"); + fprintf(s->of,"%%%%BoundingBox: %d %d %d %d\n",0,0,ipw-1,iph-1); + fprintf(s->of,"%%%%Orientation: Portrait\n"); /* Rows are always virtical */ + fprintf(s->of,"%%%%EndComments\n"); + fprintf(s->of,"\n"); + if (!eps) { + fprintf(s->of,"<< /PageSize [%d %d] >> setpagedevice\n",ipw, iph); + fprintf(s->of,"\n"); + } + fprintf(s->of,"%%%%BeginProlog\n\n"); +#ifdef NEVER + fprintf(s->of,"%% Duplicate nth element of stack\n"); + fprintf(s->of,"%% arguments: n, the offset from the element bellow the n\n"); + fprintf(s->of,"/dupn { 2 add dup -1 roll dup 3 -1 roll 1 roll } bind def\n"); + fprintf(s->of,"\n"); +#endif + fprintf(s->of,"%% arbitrary rectangle\n"); + fprintf(s->of,"%% arguments: w h x y\n"); + fprintf(s->of,"/rect { gsave \n"); + fprintf(s->of,"newpath\n"); + fprintf(s->of,"moveto\n"); + fprintf(s->of,"dup 0.0 exch rlineto\n"); + fprintf(s->of,"exch 0.0 rlineto\n"); + fprintf(s->of,"0.0 exch neg rlineto\n"); + fprintf(s->of,"closepath\n"); + fprintf(s->of,"fill\n"); + fprintf(s->of,"grestore } bind def\n"); + fprintf(s->of,"\n"); + fprintf(s->of,"%% hexagon with bottom left origin\n"); + fprintf(s->of,"%% arguments: w h x y\n"); + fprintf(s->of,"/hex { gsave \n"); + fprintf(s->of,"newpath\n"); + fprintf(s->of,"moveto\n"); + fprintf(s->of,"0 1 index rlineto\n"); + fprintf(s->of,"1 index 2 div 1 index 2 div rlineto\n"); + fprintf(s->of,"1 index 2 div 1 index 2 div neg rlineto\n"); + fprintf(s->of,"0 1 index neg rlineto\n"); + fprintf(s->of,"1 index 2 div neg 1 index 2 div neg rlineto\n"); + fprintf(s->of,"pop pop\n"); + fprintf(s->of,"closepath\n"); + fprintf(s->of,"fill\n"); + fprintf(s->of,"grestore } bind def\n"); + fprintf(s->of,"\n"); + fprintf(s->of,"%% set times-roman font\n"); + fprintf(s->of,"%% argument: scale\n"); + fprintf(s->of,"/scaleTimes {\n"); + fprintf(s->of,"/Times-Roman findfont\n"); + fprintf(s->of,"exch scalefont\n"); + fprintf(s->of,"setfont } bind def\n"); + fprintf(s->of,"\n"); + fprintf(s->of,"%% Print a centered string\n"); + fprintf(s->of,"%% argument: string, x, y\n"); + fprintf(s->of,"/centerShow {\n"); + fprintf(s->of,"gsave translate\n"); + fprintf(s->of,"newpath 0.0 0.0 moveto dup true charpath pathbbox\n"); + fprintf(s->of,"3 -1 roll sub exch 3 -1 roll sub\n"); + fprintf(s->of,"-0.5 mul exch -0.5 mul\n"); + fprintf(s->of,"moveto show grestore} bind def\n"); + fprintf(s->of,"\n"); + fprintf(s->of,"%% Print a vertically centered string\n"); + fprintf(s->of,"%% argument: string, x, y\n"); + fprintf(s->of,"/vcenterShow {\n"); + fprintf(s->of,"gsave translate 90.0 rotate\n"); + fprintf(s->of,"newpath 0.0 0.0 moveto dup true charpath pathbbox\n"); + fprintf(s->of,"3 -1 roll sub exch 3 -1 roll sub\n"); + fprintf(s->of,"-0.5 mul exch -0.5 mul\n"); + fprintf(s->of,"moveto show grestore} bind def\n"); + + fprintf(s->of,"%%%%EndProlog\n"); + fprintf(s->of,"\n"); + + if (rand != 0) + fprintf(s->of,"%% RandomStart %d\n",rstart); + else + fprintf(s->of,"%% ChartID %d\n",rstart); + fprintf(s->of,"\n"); + } + + return (trend *)s; +} + +/* ==================================== */ +/* TIFF raster output class */ +/* We use the render library to do all the hard work. */ + +struct _tiff_trend { + TREND_STRUCT + + render2d *r; /* Raster renderer object */ + char *fname; + color2d c; /* Last set color */ + int comp; /* Flag, use compression */ + +}; typedef struct _tiff_trend tiff_trend; + +/* Start a page */ +static void tiff_startpage(trend *ss, int pn) { + tiff_trend *s = (tiff_trend *)ss; + + /* Nothing to do */ +} + +/* End a page */ +static void tiff_endpage(trend *ss) { + tiff_trend *s = (tiff_trend *)ss; + + /* Nothing to do */ +} + +/* set the color */ +static void tiff_setcolor(trend *ss, xcal *cal, col *c) { + tiff_trend *s = (tiff_trend *)ss; + double cdev[ICX_MXINKS]; /* Calibrated device color */ + + if (cal != NULL) + cal->interp(cal, cdev, c->dev); + else { + int j; + for (j = 0; j < c->n; j++) + cdev[j] = c->dev[j]; + } + + if ((c->t & T_N) == 0) + error("tiff_setcolor with no device values set"); + + if (c->nmask == ICX_W) { + if (c->altrep == 0) { /* DeviceGray */ + s->c[0] = cdev[0]; + } else if (c->altrep == 4) { /* DeviceRGB */ + s->c[0] = cdev[0]; + s->c[1] = cdev[0]; + s->c[2] = cdev[0]; + } else if (c->altrep == 5) { /* Separation */ + s->c[0] = cdev[0]; + } else if (c->altrep == 6) { /* DeviceN single channel */ + s->c[0] = cdev[0]; + } else { + error("Device white encoding not approproate!"); + } + + } else if (c->nmask == ICX_K) { + if (c->altrep == 0) { /* DeviceGray */ + s->c[0] = cdev[0]; + } else if (c->altrep == 1) { /* DeviceCMYK */ + s->c[0] = 0.0; + s->c[1] = 0.0; + s->c[2] = 0.0; + s->c[3] = cdev[0]; + } else if (c->altrep == 2) { /* Separation */ + s->c[0] = cdev[0]; + } else if (c->altrep == 3) { /* DeviceN single channel */ + s->c[0] = cdev[0]; + } else { + error("Device black encoding not approproate!"); + } + + } else if (c->nmask == ICX_CMY) { + if (c->altrep == 0) { /* DeviceCMYK */ + s->c[0] = cdev[0]; + s->c[1] = cdev[1]; + s->c[2] = cdev[2]; + s->c[3] = 0.0; + } else if (c->altrep == 7) { /* Inverted DeviceRGB */ + s->c[0] = 1.0-cdev[0]; + s->c[1] = 1.0-cdev[1]; + s->c[2] = 1.0-cdev[2]; + } else if (c->altrep == 8) { /* DeviceN three channel */ + s->c[0] = cdev[0]; + s->c[1] = cdev[1]; + s->c[2] = cdev[2]; + } else { + error("Device CMY encoding not approproate!"); + } + + } else { + int j; + for (j = 0; j < s->r->ncc; j++) + s->c[j] = cdev[j]; + } + + /* Remember edge tracking color */ + et_color(c->rgb); +} + +/* A rectangle, with optional edge tracking */ +static void tiff_rectangle(trend *ss, + double x, double y, /* Top left corner of rectangle in mm from origin */ + double w, double h, /* Width and height */ + char *id, /* Patch id, NULL if not a diagnostic mark */ + int et /* nz if use edge tracking on this */ +) { + tiff_trend *s = (tiff_trend *)ss; + + y -= h; /* Convert to bottom left corner */ + s->r->add(s->r, new_rect2d(s->r, x, y, w, h, s->c)); + + if (et) { + et_patch(id, x, y, w, h); + et_edge(1, 0, x, y, y + h); + et_edge(1, 1, x + w, y, y + h); + et_edge(0, 0, y, x, x + w); + et_edge(0, 1, y + h, x, x + w); + } +} + +/* A testpad hexagon. */ +static void tiff_hexagon(trend *ss, + double x, double y, /* Top left vertex of hex mm from origin */ + double w, double h, /* Width and height */ + int step, /* Step number from 0 to figure odd/even */ + char *id /* Patch id, NULL if not a diagnostic mark */ +) { + tiff_trend *s = (tiff_trend *)ss; + double vv[3][2]; + color2d cc[3]; + int i, j; + + if ((step & 1) == 0) /* Even so left side of stagger */ + x -= 0.25 * w; + else /* Odd so right side of stagger */ + x += 0.25 * w; + y = y - 5.0/6.0 * h; + h *= 2.0/3.0; /* Convert to hex side length */ + + /* Triangle color */ + for (i = 0; i < 3; i++) + for (j = 0; j < s->r->ncc; j++) + cc[i][j] = s->c[j]; + + /* Top triangle */ + vv[0][0] = x; + vv[0][1] = y + h; + vv[1][0] = x + w; + vv[1][1] = y + h; + vv[2][0] = x + 0.5 * w; + vv[2][1] = y + 1.5 * h; + s->r->add(s->r, new_trivs2d(s->r, vv, cc)); + + /* Center rectangle */ + s->r->add(s->r, new_rect2d(s->r, x, y, w, h, s->c)); + + /* Bottom triangle */ + vv[0][0] = x; + vv[0][1] = y; + vv[1][0] = x + w; + vv[1][1] = y; + vv[2][0] = x + 0.5 * w; + vv[2][1] = y - 0.5 * h; + s->r->add(s->r, new_trivs2d(s->r, vv, cc)); +} + +/* A centered string */ +static void tiff_string(trend *ss, + double x, double y, /* Bot Left Corner of rectangle in mm from origin */ + double w, double h, /* Width and height */ + char *str /* String */ +) { + tiff_trend *s = (tiff_trend *)ss; + double sw = 0.0, sh = 0.0; + + sh = h * 0.57; + meas_string2d(s->r,&sw,NULL,timesr_b,str,sh,0); + /* Center the string within the recangle */ + x += 0.5 * (w - sw); + y += 0.5 * (h - sh); + add_string2d(s->r,NULL,NULL,timesr_b,str,x,y,sh,0,s->c); +} + +/* A vertically centered string */ +static void tiff_vstring(trend *ss, + double x, double y, /* Bot Right Corner of rectangle in mm from origin */ + double w, double h, /* Width and height */ + char *str /* String */ +) { + tiff_trend *s = (tiff_trend *)ss; + double sw = 0.0, sh = 0.0; + + x -= w; /* Make it bot left corner */ + sw = w * 0.57; + meas_string2d(s->r,NULL, &sh,timesr_b,str,sw,3); + /* Center the string within the recangle */ + x += 0.5 * (w + sw); + y += 0.5 * (h - sh); + add_string2d(s->r,NULL,NULL,timesr_b,str,x,y,sw,3,s->c); +} + +/* A dotted line */ +static void tiff_dline(trend *ss, + double x0, double y0, /* Start of line */ + double x1, double y1, /* End of line */ + double w /* Width */ +) { + tiff_trend *s = (tiff_trend *)ss; + + add_dashed_line2d(s->r,x0,y0,x1,y1,w,1.0,2.0,0,s->c); +} + +/* Complete operations, then delete the object */ +static void tiff_del(trend *ss) { + tiff_trend *s = (tiff_trend *)ss; + + if (s->r != NULL) { + s->r->write(s->r, s->fname, s->comp); + s->r->del(s->r); + } + if (s->fname != NULL) + free(s->fname); + free(s); +} + +static trend *new_tiff_trend( + char *fname, /* File name */ + int nmask, /* Non zero if we are doing a DeviceN chart */ + depth2d dpth, /* 8 or 16 bit */ + double pw, double ph, /* Page width and height in mm */ + double marg, /* Page margine in mm */ + double hres, double vres, /* Resolution */ + int altrep, /* printer grey/CMY representation type 0..8 */ + int ncha, /* flag, use nchannel alpha */ + int comp, /* flag, use compression */ + int dith /* flag, use 8 bit dithering */ +) { + tiff_trend *s; + color2d c; /* Background color */ + colort2d csp; + double ma[4]; /* Page margins */ + int nc = 0; + int j; + + if ((s = (tiff_trend *)calloc(1, sizeof(tiff_trend))) == NULL) { + error("Failed to create a tiff target rendering object"); + } + + s->startpage = tiff_startpage; + s->endpage = tiff_endpage; + s->setcolor = tiff_setcolor; + s->rectangle = tiff_rectangle; + s->hexagon = tiff_hexagon; + s->string = tiff_string; + s->vstring = tiff_vstring; + s->dline = tiff_dline; + s->del = tiff_del; + + if (nmask == ICX_W) { + if (altrep == 0 /* DeviceGray */ + || altrep == 5) { /* Separation single channel */ + csp = w_2d; + nc = 1; + } else if (altrep == 4) { /* DeviceRGB */ + csp = rgb_2d; + nc = 3; + } else if (altrep == 6) { /* DeviceN single channel */ + csp = ncol_2d; + nc = icx_noofinks(nmask); + nc = 1; + } else { + error("Device white encoding not approproate"); + } + + } else if (nmask == ICX_K) { + if (altrep == 0 /* DeviceGray */ + || altrep == 2) { /* Separation single channel */ + csp = k_2d; + nc = 1; + } else if (altrep == 1) { /* DeviceCMYK */ + csp = cmyk_2d; + nc = 4; + } else if (altrep == 3) { /* DeviceN single channel */ + csp = ncol_2d; + nc = icx_noofinks(nmask); + nc = 1; + } else { + error("Device black encoding not approproate"); + } + + } else if (nmask == ICX_RGB || nmask == ICX_IRGB) { + csp = rgb_2d; + nc = 3; + + } else if (nmask == ICX_CMY) { + if (altrep == 0) { /* DeviceCMYK */ + csp = cmyk_2d; + nc = 4; + } else if (altrep == 7) { /* Inverted DeviceRGB */ + csp = rgb_2d; + nc = 3; + } else if (altrep == 8) { /* DeviceN three channel */ + csp = ncol_2d; + nc = icx_noofinks(nmask); + } else { + error("Device CMY encoding not approproate"); + } + + } else if (nmask == ICX_CMYK) { + csp = cmyk_2d; + nc = 4; + } else { /* Device N */ + if (ncha) + csp = ncol_a_2d; + else + csp = ncol_2d; + nc = icx_noofinks(nmask); + } + + if (marg > 0) + ma[0] = ma[1] = ma[2] = ma[3] = marg; + else + ma[0] = ma[1] = ma[2] = ma[3] = 0; + + if ((s->r = new_render2d(pw, ph, ma, hres, vres, csp, nc, dpth, dith)) == NULL) { + error("Failed to create a render2d object for tiff output"); + } + + /* We're goin to assume this is all printed output, so */ + /* the background should be white. */ + if ((nmask & ICX_ADDITIVE) + || (nmask == ICX_CMY && altrep == 7)) { /* CMY as inverted RGB */ + for (j = 0; j < nc; j++) + c[j] = 1.0; + } else { + for (j = 0; j < nc; j++) + c[j] = 0.0; + } + s->r->set_defc(s->r, c); + + s->comp = comp; + + if ((s->fname = strdup(fname)) == NULL) + error("stdup of fname failed"); + + return (trend *)s; +} + + +/* ====================================================== */ + +/* Convert XYZ represention into Lab, XYZ density and RGB */ +void +col_convert(col *cp, double *wp) { + + if ((cp->t & T_XYZ) == 0 || (cp->t & T_N) == 0) + error("gen_color needs XYZ and device colors set !"); + + if ((cp->t & T_LAB) == 0) { + icmXYZNumber w; + w.X = wp[0]; + w.Y = wp[1]; + w.Z = wp[2]; + icmXYZ2Lab(&w, cp->Lab, cp->XYZ); + cp->t |= T_LAB; + } + if ((cp->t & T_DEN) == 0) { + icx_XYZ2Tdens(cp->den, cp->XYZ); + +#ifdef DEN_COMPRESS + /* Compress densities > 1.0, 2:1 */ + { + int e; + for (e = 0; e < 3; e++) { + if (cp->den[e] > 1.0) + cp->den[e] = 1.0 + (cp->den[e] - 1.0) * 0.5; + } + } +#endif /* DEN_COMPRESS */ + cp->t |= T_DEN; + } + + if ((cp->t & T_RGB) == 0) { + icx_XYZ2sRGB(cp->rgb, wp, cp->XYZ); + cp->t |= T_RGB; + } +} + +/* return the middle of 3 values */ +double mid3(double a, double b, double c) { + + if ((a < b && a > c) || (a < c && a > b)) + return a; + if ((b < a && b > c) || (b < c && b > a)) + return b; + return c; +} + +/* return the vector difference of 3 values */ +double vec3(double a, double b, double c) { + + return sqrt(a * a + b * b + c * c); +} + +/* Type of density rule to use with color patches/spacers. */ +/* This really depends on the algorithm used by the strip */ +/* reading instrument. For an Xrite DTP41, it appears that */ +/* using max3 is the best. This agrees with their documentation, */ +/* that it is looking for the largest change in one of the three */ +/* channels. Another make of instrument might use a different */ +/* algorithm. */ +/* Choices are: */ +/* max3 - aim for largest change to be greatest */ +/* mid3 - aim for middle change to be greatest */ +/* min3 - aim for minimum change to be greatest */ +/* vec3 - aim for the vector change to be greatest */ + +#define RULE3 max3 + +/* Setup a suitable spacer color, and */ +/* Return the worst case density difference. */ +double setup_spacer( +col **psc, /* Return pointer to spacer color */ +col *pp, /* Previous patch color */ +col *cp, /* Current patch color */ +col *pcol, /* 8 pre-defined spacer colors */ +int sptype, /* Spacer type code, -1 = none, */ + /* 0 = No spacer, 1 = b&W spacer, */ + /* 2 = colored */ +int usede /* Aim for maximum delta E rather than density */ +) { + col *sc = NULL; /* Spacer chosen */ + double dd, pdd; + +//printf("~1\n"); +//printf("~1 setting spacer between %s (%s) and %s (%s)\n",pp->loc, pp->id, cp->loc, cp->id); + + /* Compute contrast between the patches */ + if (usede) { + pdd = icmLabDE(pp->Lab, cp->Lab); + } else { + /* return the density contrast between the patches */ + if (pp->nmask == ICX_W + || pp->nmask == ICX_K) { /* If only capable of single density */ + + pdd = fabs(pp->den[3] - cp->den[3]); +//printf("~1 computed B&W diff of %f\n",dd); + + } else { + pdd = RULE3(fabs(pp->den[0] - cp->den[0]), + fabs(pp->den[1] - cp->den[1]), + fabs(pp->den[2] - cp->den[2])); + +//printf("~1 computed color diff of %f\n",dd); + } + } + + if (sptype <= 0) /* No spacers */ + return pdd; + + if (pp->nmask == ICX_W + || pp->nmask == ICX_K) { /* If only capable of single density */ + sptype = 1; /* Force to B&W spacer */ + } + + if (sptype == 1) { /* B&W spacer */ + double d1, d2; + + if (usede) { + /* Choose whether space should be white or black */ + /* Shoose color that has greatest worst contrast */ + d1 = min2(icmLabDE(pcol[0].Lab, pp->Lab), icmLabDE(pcol[0].Lab, cp->Lab)); + d2 = min2(icmLabDE(pcol[7].Lab, pp->Lab), icmLabDE(pcol[7].Lab, cp->Lab)); + +//printf("~1 worst difference to white = %f\n", d1); +//printf("~1 worst difference to black = %f\n", d2); + + if (d1 > d2) { +//printf("~1 chosen white\n"); + sc = &pcol[0]; + dd = d1; + } else { +//printf("~1 chosen black\n"); + sc = &pcol[7]; + dd = d2; + } + + } else { + /* Choose whether space should be white or black */ + /* Shoose color that has greatest worst contrast */ + d1 = min2(fabs(pcol[0].den[3] - pp->den[3]), fabs(pcol[0].den[3] - cp->den[3])); + d2 = min2(fabs(pcol[7].den[3] - pp->den[3]), fabs(pcol[7].den[3] - cp->den[3])); + +//printf("~1 worst difference to white = %f\n", d1); +//printf("~1 worst difference to black = %f\n", d2); + + if (d1 > d2) { +//printf("~1 chosen white\n"); + sc = &pcol[0]; + dd = d1; + } else { +//printf("~1 chosen black\n"); + sc = &pcol[7]; + dd = d2; + } + } + + } else { /* else colored spacer */ + int ii, i; + + /* Check out all the possible space values for the one that gives the best */ + /* and second best contrast to each edge */ + + if (usede) { + + /* for all possible spacer colors */ + dd = -1.0; + for (i = 0; i < 8; i++) { + double bb; + + bb = min2(icmLabDE(pcol[i].Lab, pp->Lab), icmLabDE(pcol[i].Lab, cp->Lab)); + + /* Worst of two edges best is better than any previous */ + if (bb > dd) { + dd = bb; /* Worst color of worst edge */ + ii = i; + sc = &pcol[i]; + } + } + + } else { + /* for all possible spacer colors */ + dd = -1.0; + for (i = 0; i < 8; i++) { + double b1, b2, bb; + + b1 = RULE3(fabs(pcol[i].den[0] - pp->den[0]), + fabs(pcol[i].den[1] - pp->den[1]), + fabs(pcol[i].den[2] - pp->den[2])); + + b2 = RULE3(fabs(pcol[i].den[0] - cp->den[0]), + fabs(pcol[i].den[1] - cp->den[1]), + fabs(pcol[i].den[2] - cp->den[2])); + + bb = min2(b1, b2); /* Worst of two edges */ + + /* Worst of two edges best is better than any previous */ + if (bb > dd) { + dd = bb; /* Worst color of worst edge */ + ii = i; + sc = &pcol[i]; + } + } + } + } + +//printf("~1 returning spacer contrast %f + patch contrast %f\n",dd,pdd); + if (psc != NULL) + *psc = sc; /* Return pointer to chosen spacer */ + + return 0.6 * dd + 0.4 * pdd; /* Return propotion of spacer and patch contrast */ +} + +/* Given two patches, compute the density difference between them */ +double density_difference( +col *p1, /* Previous patch color */ +col *p2, /* Current patch color */ +int usede /* Aim for maximum delta E rather than density */ +) { + double dd, pdd; + + /* Compute contrast between the patches */ + if (usede) { + pdd = icmLabDE(p1->Lab, p2->Lab); + } else { + /* return the density contrast between the patches */ + if (p1->nmask == ICX_W + || p1->nmask == ICX_K) { /* If only capable of single density */ + + pdd = fabs(p1->den[3] - p2->den[3]); + + } else { + pdd = RULE3(fabs(p1->den[0] - p2->den[0]), + fabs(p1->den[1] - p2->den[1]), + fabs(p1->den[2] - p2->den[2])); + + } + } + + return pdd; +} + +/* Given a number, return the DTP20 SID patch encoding. */ +/* return nz on error */ +static int dtp20_enc( + col *pcol, /* pcol[8] of spacer patch colors */ + int ndig, /* Number of octal digits/patches */ + int lend, /* NZ if little endian order for SID row index */ + col **ppcol, /* return the pointers to pcol for the encoding */ + unsigned int rix /* Number to encode */ +) { + int si, ei, ii; + int i, wv = rix; + + if (lend) /* Little endian order */ + si = 0, ei = ndig, ii = 1; + else + si = ndig-1, ei = -1, ii = -1; + + /* LSB to MSB octal */ + for (i = si; i != ei; i += ii) { + int j, k; + j = wv % 8; /* Digit value */ + wv /= 8; + for (k = 0; k < 8; k++) { + if (pcol[k].dtp20_octval == j) + break; + } + if (k >= 8) + return 1; /* Something weird happened */ + ppcol[i] = &pcol[k]; + } + if (wv != 0) + return 1; /* Number is too big to be encoded */ +//printf("~1dtp20_enc %d ->",rix); +//for (i = 0; i < ndig; i++) +// printf(" %d",ppcol[i]->dtp20_octval); +//printf("\n"); + return 0; +} + + +/* Sort function for stree */ +static int cmp_eperr(const void *p1, const void *p2) { + return ((col *)p1)->wnd == ((col *)p2)->wnd ? 0 : + (((col *)p1)->wnd < ((col *)p2)->wnd ? -1 : 1); +} + +#define SYMWT 1.3 /* Amount to discount direction delta E compared to spacers */ + +/* Setup the randomised index. */ +/* The index only covers test sample patches, not TID or max/min/SID patches */ +void setup_randix( +int *rix, /* Index lookup array to fill in */ +int npat, /* Number of test targets needed */ +int rand, /* Randomise flag */ +int rstart, /* Starting index for random */ +int verb, /* Verbose flag */ +col *cols, /* Array of colors to be put on target chart */ +col *pcol, /* 8 spacer colors */ +int tpprow, /* Test sample patches per row */ +int spacer, /* Spacer code, 0 = None, 1 = b&w, 2 = colored */ +int needpc, /* Need patch to patch contrast in a row */ +int domaxmin, /* Top and tail strip with max/min or SID. 0 = DTP51, 2 = DTP20 SID */ +col *media, /* Alias for media color */ +int usede /* NZ to use delta E rather than density */ +) { + int i; + randix *r = NULL; /* Random index order object */ + + if (rand) + r = new_randix(npat, rstart); + + /* Setup initial linear or randomised index */ + for (i = 0; i < npat; i++) { + if (rand) { + rix[i] = r->next(r); + } else { + rix[i] = i; + } + cols[rix[i]].ix = i; /* This colors random index */ + } + rix[i] = 0; /* npat+1 may be read, so fill extra entry at end. */ + + if (rand) + r->del(r); + + /* Setup initial contrast check */ + { + col *pp, *cp, *np, *op; /* Previous, current, next and opposite patch */ + col *maxd; /* Alias for maximum density */ + col *mind; /* Alias for minimum density */ + aat_atree_t *stree; /* Tree holding colors sorted by worst case contrast */ + aat_atrav_t *aat_tr; /* Tree accessor */ + double temp, trate; /* Annealing temperature & rate */ + double tstart, tend;/* Annealing chedule range */ + + mind = &pcol[0]; /* White */ + maxd = &pcol[7]; /* Black */ + + /* Create sorted tree so as to be able to locate the largest wnd */ + if ((stree = aat_anew(cmp_eperr)) == NULL) + error("Allocating aat tree for colors failed"); + if ((aat_tr = aat_atnew()) == NULL) + error("aat_atnew returned NULL"); + + for (i = 0; i < npat; i++) { + int j, k; /* Patch index, Row index */ + int tpitr; /* Test patches in this row */ + double tt; + + j = (i % tpprow); + k = i / tpprow; + + /* Figure previous patch */ + if (j == 0) { /* First in row */ + if (domaxmin == 1) + pp = maxd; /* Maxd will be before first patch */ + else if (domaxmin == 2) { + col *ppcol[3]; + /* Compute the SID colors the DTP20 will use before row */ + if (dtp20_enc(pcol, 3, 1, ppcol, k+1) != 0) + error("Internal, dtp20 SID row id failed, val %d, digits %d",k+1,3); + pp = ppcol[2]; /* Barcode patch next to first test patch */ + } else + pp = media; /* Media will be before first patch */ + } else { + pp = &cols[rix[i-1]]; + } + + /* Current patch */ + cp = &cols[rix[i]]; + + /* Next patch */ + if (j == (tpprow-1) || i == (npat-1)) { /* Last in row or last patch */ + if ((j == (tpprow-1) || i == (npat-1)) && domaxmin == 1) + np = mind; + else if ((j == (tpprow-1) || i == (npat-1)) && domaxmin == 2) + np = mind; /* DTP20 has white trailing patch */ + else + np = media; + } else { + np = &cols[rix[i+1]]; + } + + /* Opposite patch */ + + if (k == npat/tpprow) /* Last row */ + tpitr = npat - tpprow * (npat/tpprow); + else + tpitr = tpprow; + op = &cols[rix[ k * tpprow + (tpitr -1 - j)]]; + + /* Setup pointers and worst case contrast */ + cp->nc[0] = pp; + cp->nc[1] = np; + cp->oc = op; + cp->wnd = setup_spacer(NULL, pp, cp, pcol, spacer, usede); + tt = setup_spacer(NULL, cp, np, pcol, spacer, usede); + if (tt < cp->wnd) + cp->wnd = tt; + if (cp != op) { + tt = SYMWT * density_difference(cp, op, usede); + if (tt < cp->wnd) + cp->wnd = tt; + } + + /* Insert it into the sorted list */ + if ((aat_ainsert(stree, (void *)cp)) == 0) + error("aat_ainsert color %d failed",i); + } + + if (verb) { + double wrdc = 1e300; + + if ((cp = aat_atfirst(aat_tr, stree)) == NULL) + error("There seem to be no colors in the tree"); + if (usede) + printf("Worst case delta E = %f\n", cp->wnd); + else + printf("Worst case density contrast = %f\n", cp->wnd); + + /* Evaluate each strips direction confusion */ + for (i = 0; ; i++) { + int j, tpitr; /* Test patches in this row */ + double tot = 0.0; + if ((i * tpprow) >= npat) + break; + if (i == npat/tpprow) /* Last row */ + tpitr = npat - tpprow * (npat/tpprow); + else + tpitr = tpprow; + + for (tot = 0.0, j = 0; j < tpitr; j++) { + tot += density_difference(&cols[rix[ i * tpprow + j]], + &cols[rix[ i * tpprow + (tpitr -1 - j)]], usede); + } + tot /= tpitr; + if (tot < wrdc) + wrdc = tot; + } + if (usede) + printf("Worst case direction distinction delta E = %f\n", wrdc); + else + printf("Worst case direction distinction density contrast = %f\n", wrdc); + } + + if (needpc == 0 || !rand || npat < 3) + return; /* Current order is sufficient */ + + if (verb) { + printf("Optimising layout for strip reader:\n"); + printf(" 0%%"); fflush(stdout); + } + + if (spacer == 2) { /* Colored spacer, don't optimise too hard */ + tstart = 0.4; + tend = 0.00001; + trate = 0.87; + } else { /* No spacer or B&W spacer, do more optimisation */ + tstart = 0.4; + tend = 0.000005; + trate = 0.95; + } + /* Simulated anealing */ + for (temp = tstart; temp > tend; temp *= trate) { + + int ii, itlim; /* Maximum passes at a temperature */ + int nsuc = 0; /* Number that succeed */ + int suclim; /* Number of successful changes before continuing */ + + if (spacer == 2) { /* Colored spacer, don't optimise too hard */ + itlim = npat * 10; + suclim = npat; + } else { + itlim = npat * 14; + suclim = npat; + } + + if (verb) { /* Output percent intervals */ + double pc; + + pc = (log(temp) - log(tstart))/(log(tend) - log(tstart)); + printf("%c%2d%%",cr_char,(int)(100.0 * pc+0.5)); fflush(stdout); + } + + /* Improve the ordering */ + for (ii = 0; ii < itlim ; ii++) { + col *p1, *p2; + double tt, de; + + /* Chose another patch to try swapping worst with */ + p1 = aat_atfirst(aat_tr, stree); /* worst */ + for (;;) { + tt = d_rand(0.0, 1.0); + p2 = &cols[(int)(tt * (npat-1.0))]; /* Swap candidate */ + if (p1 != p2 && p2 != p1->oc) + break; /* Swap is not the worst or opposite */ + } + + /* Check p1 in p2's place */ + de = setup_spacer(NULL, p2->nc[0], p1, pcol, spacer, usede); + tt = setup_spacer(NULL, p1, p2->nc[1], pcol, spacer, usede); + if (tt < de) + de = tt; + tt = SYMWT * density_difference(p2->oc, p1, usede); + if (tt < de) + de = tt; + + /* Check p2 in p1's place */ + tt = setup_spacer(NULL, p1->nc[0], p2, pcol, spacer, usede); + if (tt < de) + de = tt; + tt = setup_spacer(NULL, p2, p1->nc[1], pcol, spacer, usede); + if (tt < de) + de = tt; + tt = SYMWT * density_difference(p1->oc, p2, usede); + if (tt < de) + de = tt; + + de = de - p1->wnd; /* Increase in worst difference */ + + /* If this swap will improve things, or temp is high enough, */ + /* then actually do the swap. */ + if (de > 0.0 + || d_rand(0.0, 1.0) < exp(de/temp)) { + int t; + col *tp0, *tp1; + + nsuc++; + +//printf("~1 temp = %f, ii = %d, swapping %d and %d\n",temp,ii,p1->i, p2->i); + + /* Remove them from the tree */ + if ((aat_aerase(stree, (void *)p1)) == 0) + error("aat_aerase failed to find color no %d", p1->i); + if ((aat_aerase(stree, (void *)p2)) == 0) + error("aat_aerase failed to find color no %d", p2->i); + + /* Swap them in random index list */ + rix[p1->ix] = p2->i; + rix[p2->ix] = p1->i; + t = p1->ix; + p1->ix = p2->ix; + p2->ix = t; + + /* Swap their neighbors, taking care */ + /* of the situation if they are neigbors */ + tp0 = p1->nc[0]; + tp1 = p2->nc[0]; + if (tp0 == p1) + tp0 = p2; + else if (tp0 == p2) + tp0 = p1; + if (tp1 == p1) + tp1 = p2; + else if (tp1 == p2) + tp1 = p1; + p2->nc[0] = tp0; + p1->nc[0] = tp1; + + tp0 = p1->nc[1]; + tp1 = p2->nc[1]; + if (tp0 == p1) + tp0 = p2; + else if (tp0 == p2) + tp0 = p1; + if (tp1 == p1) + tp1 = p2; + else if (tp1 == p2) + tp1 = p1; + p2->nc[1] = tp0; + p1->nc[1] = tp1; + + /* Swap their opposites (they cannot be opposites of each other) */ + p1->oc->oc = p2; + p2->oc->oc = p1; + tp0 = p1->oc; + p1->oc = p2->oc; + p2->oc = tp0; + + /* Reset backwards references */ + p1->nc[0]->nc[1] = p1; + p1->nc[1]->nc[0] = p1; + p2->nc[0]->nc[1] = p2; + p2->nc[1]->nc[0] = p2; + + /* re-compute contrast to neighbors */ + p1->wnd = setup_spacer(NULL, p1->nc[0], p1, pcol, spacer, usede); + tt = setup_spacer(NULL, p1, p1->nc[1], pcol, spacer, usede); + if (tt < p1->wnd) + p1->wnd = tt; + if (p1 != p1->oc) { + tt = SYMWT * density_difference(p1, p1->oc, usede); + if (tt < p1->wnd) + p1->wnd = tt; + } + + p2->wnd = setup_spacer(NULL, p2->nc[0], p2, pcol, spacer, usede); + tt = setup_spacer(NULL, p2, p2->nc[1], pcol, spacer, usede); + if (tt < p2->wnd) + p2->wnd = tt; + if (p2 != p2->oc) { + tt = SYMWT * density_difference(p2, p2->oc, usede); + if (tt < p2->wnd) + p2->wnd = tt; + } + + /* (!!! We haven't recomputed the possible change in the ->oc's */ + /* ->wnd due to it's opposite haveing changed. !!!) */ + + /* Add them back to the tree */ + if ((aat_ainsert(stree, (void *)p1)) == 0) + error("aat_ainsert color no %d failed",p1->i); + if ((aat_ainsert(stree, (void *)p2)) == 0) + error("aat_ainsert color no %d failed",p2->i); + +#ifdef NEVER +printf("~1 current list = \n"); +for (cp = aat_atfirst(aat_tr, stree); cp != NULL; cp = aat_atnext(aat_tr)) + printf("%d: %f\n",cp->i,cp->wnd); +cp = aat_atfirst(aat_tr, stree); +printf("~1 worst case contrast = %f, list index %d, id '%s', loc '%s'\n", +cp->wnd, cp->i,cp->id,cp->loc); +printf("~1 neighbor list index %d, id '%s', loc '%s'\n", +cp->nc[0]->i,cp->nc[0]->id,cp->nc[0]->loc); +printf("~1 neighbor list index %d, id '%s', loc '%s'\n", +cp->nc[1]->i,cp->nc[1]->id,cp->nc[1]->loc); +#endif + + if (nsuc > suclim) + break; + } + } + } + + if (verb) { + double wrdc = 1e300; + if ((cp = aat_atfirst(aat_tr, stree)) == NULL) + error("There seem to be no colors in the tree"); + if (usede) + printf("%c100%%\nAfter optimisation, worst delta E = %f\n",cr_char,cp->wnd); + else + printf("%c100%%\nAfter optimisation, density contrast = %f\n",cr_char,cp->wnd); + + /* Evaluate each strips direction confusion */ + for (i = 0; ; i++) { + int j, tpitr; /* Test patches in this row */ + double tot = 0.0; + if ((i * tpprow) >= npat) + break; + if (i == npat/tpprow) /* Last row */ + tpitr = npat - tpprow * (npat/tpprow); + else + tpitr = tpprow; + + for (tot = 0.0, j = 0; j < tpitr; j++) { + tot += density_difference(&cols[rix[ i * tpprow + j]], + &cols[rix[ i * tpprow + (tpitr -1 - j)]], usede); + } + tot /= tpitr; + if (tot < wrdc) + wrdc = tot; + } + if (usede) + printf("Worst case direction distinction delta E = %f\n", wrdc); + else + printf("Worst case direction distinction density contrast = %f\n", wrdc); + } + + + aat_atdelete(aat_tr); + aat_adelete(stree); + + } + +} + +#define MAXPPROW 500 /* Absolute maximum patches per pass/row permitted */ +#define MAXROWLEN 2000.0 /* Absolute maximum row length */ + +void +generate_file( +instType itype, /* Target instrument type */ +char *bname, /* Output file basename */ +col *cols, /* Array of colors to be put on target chart */ +int npat, /* Number of test targets needed */ +xcal *cal, /* Optional printer calibration, NULL if none */ +char *label, /* Per strip label */ +double pw, /* Page width */ +double ph, /* Page height */ +double bord, /* Border margin in mm */ +int nosubmarg, /* NZ if bord is not to be subtracted from raster */ +int nollimit, /* NZ to not limit the strip length */ +int nolpcbord, /* NZ to suppress left paper clip border */ +int rand, /* Randomise flag */ +int rstart, /* Starting index for random */ +alphix *saix, /* Strip alpha index object */ +alphix *paix, /* Patch alpha index object */ +int ixord, /* Index order, 0 = strip then patch */ +double pscale, /* Test patch & spacers scale factor */ +double sscale, /* Spacers scale factor */ +int hflag, /* Spectroscan/Munki high density modified */ +int verb, /* Verbose flag */ +int scanc, /* Scan compatible bits, 1 = .cht gen, 2 = wide first row */ +int oft, /* PS/EPS/TIFF select (0,1,2) */ +depth2d tiffdpth, /* TIFF pixel depth */ +double tiffres, /* TIFF resolution in DPI */ +int ncha, /* flag, use nchannel alpha */ +int tiffdith, /* flag, nz to use TIFF 8 bit dithering */ +int tiffcomp, /* flag, nz to use TIFF compression */ +int spacer, /* Spacer code, -1 = default, 0 = None, 1 = b&w, 2 = colored */ +int nmask, /* DeviceN mask */ +int altrep, /* printer grey/CMY representation type 0..8 */ +col *pcol, /* 8 spacer colors or 8 barcode colors for DTP20 */ +double *wp, /* Approximate white XYZ point */ +int *ptpprow, /* Return Test sample patches per row */ +unsigned char **pprps, /* Return malloced array holding Passes per strip */ +double *p_patchlen, /* Return patch length in mm */ +double *p_gaplen, /* Return gap length in mm */ +double *p_taplen, /* Return trailer length in mm */ +int *p_npat /* Return number of patches including padding */ +) { + char psname[MAXNAMEL+20]; /* Name of output file */ + trend *tro = NULL; /* Target rendering object */ + double x1, y1, x2, y2; /* Bounding box in mm */ + double iw, ih; /* Imagable areas width and height in mm */ + double arowl; /* Available row length */ + + int hex = 0; /* Hexagon patches flag (Spectrolino) */ + double stagger = 0.0; /* Stagger alternate rows by half patch length */ + + /* Chart definition variables. Set to default and override */ + /* for particular instruments. */ + double lbord = 0.0; /* Additional left border */ + int domaxmin = 0; /* if == 1, Top and tail strip with max and min values (DTP51) */ + /* if == 2, Top and tail with DTP20 strip identification (SID) */ + int nmaxp = 0; /* Number of max (header) patches for max/min/sid */ + int nminp = 0; /* Number of min (trailer) patches for max/min/sid */ + int nextrap = 0; /* Number of extra patches for max and min = nmaxp + nminp */ + int needpc = 0; /* Need patch to patch contrast in a row */ + int dorspace = 0; /* Do a rrsp from center of last patch to cut line & print label. */ + int dopglabel = 0; /* Write a per page label */ + double pglth = 0.0; /* Page Label text height */ + int padlrow = 0; /* flag - Pad the last row with white */ + double lspa = 0.0; /* Leader space before first patch containint border, label, SID etc. */ + double lcar = 0.0; /* Leading clear area before first patch. Will be white */ + double plen = 0.0; /* Patch min length */ + double tidplen = 0.0; /* TID Patch min length */ + double pspa = 0.0; /* Patch to patch spacer */ + double tspa = 0.0; /* Clear space after last patch */ + double txhi = 0.0; /* Step/cut, row label text height */ + double txhisl = 0.0;/* Strip/column label text height */ + int docutmarks = 0; /* Generate strip cut marks */ + double clwi = 0.0; /* Cut line width */ + + int dorowlabel = 0; /* Generate a set of row labels */ + double rlwi = 0.0; /* Row label test width */ + + double hxew = 0.0; /* Hexagon chart extra width padding to the right of patches */ + double hxeh = 0.0; /* Hexagon chart extra height padding around patches */ + + double pwid = 0.0; /* Patch min width */ + double rrsp = 0.0; /* Row to row spacing */ + double pwex = 0.0; /* Patch width expansion between rows of a strip */ + + int mxpprow = 0; /* Maximum patches per row permitted (including min/max patches) */ + double mxrowl = 0; /* Maximum row length for patchs (including min/max patches) */ + /* Number of patches is strip by whichever is shorter */ + int tidrows = 0; /* Rows on first page for target ID (ie. DTP20) */ + int tidtype = 0; /* Target ID type. 0 = DTP20 */ + int tidminp = 0; /* Target ID minumum number of patches */ + int tidpad = 0; /* Initial padding to place TID near middle */ + int tidnpat = npat; /* Number of test targets needed, including TID row */ + int rpstrip = 0; /* Rows per strip */ + int usede = 0; /* Use delta E to maximize patch/spacer conrast rather than density */ + + double mints, minbs; /* Minimum top & bottom space from paper edges */ + double amints, aminbs; /* Actual mints & minbs, allowing for unused space */ + double swid; /* Whole strip width */ + int pprow; /* patches per row (inc. max/min/sid patches) */ + int tidpprow; /* TID patches per row (inc. max/min/sid patches) */ + int tpprow; /* test patches per row (excludes max/min/sid patches) */ + int sppage; /* whole & partial strips per page */ + int rppstrip; /* rows per partial strip on whole page */ + int npages; /* Number whole & partial pages */ + int lsppage; /* Last page whole & partial strips per page */ + int lrpstrip; /* Last strips whole & partial rows per strip */ + int lpprow; /* last row patches per row (inc. max/min/sid patches) */ + int ppstrip; /* Real patches per whole strip */ + int pppage; /* Real patches per whole page */ + int rem; /* temporary */ + double sxwi = 0.0; /* Scan compatible first row extra width */ + + int *rix; /* Random index lookup (Logical -> patch index) */ + int i; /* Logical patch in target list */ + int l_si; /* Last start of page value of i */ + int ix; /* Patch index in target list */ + int pir; /* Patch in row */ + int ris; /* Row in strip */ + int sip; /* Set in page (including partial strip) */ + int pif; /* Page in file */ + double x = 0.0, y = 0.0; /* Current position */ + col *pp = NULL, *cp = NULL; /* Previous and current printed patch colors */ + int cpf = 0; /* Current patch special flag. 1 = start bit, 2 = stop bit */ + int slix; /* Strip label index, -1 for TID */ + char *slab = NULL; /* Strip label string */ + unsigned char *rpsp; /* Rows per strip, pointer */ + col *mark; /* Alias for mark color */ + col *media; /* Alias for media color */ + col *maxd; /* Alias for minimum density */ + col *mind; /* Alias for maximum density */ + col *sc; /* Alias for current spacer color */ + + /* Note pcol[] is setup by targen to be: + 0 = white + 1 = Cyan + 2 = Magenta + 3 = Blue + 4 = Yellow + 5 = Green + 6 = Red + 7 = Black + 8 = 50/50/50 CMY Grey + (Should switch to symbols for these ??) + */ + + /* We assume that since this is intended for a printer, */ + /* the media is always white. This may not be the case */ + /* on other output media. */ + if (itype == instDTP20) + mark = &pcol[8]; /* Grey */ + else + mark = &pcol[7]; /* Black */ + media = &pcol[0]; /* White */ + mind = &pcol[0]; /* White */ + maxd = &pcol[7]; /* Black */ + + /* Setup DTP20 bar code encoding for spacers. */ + pcol[0].dtp20_octval = 0; /* White */ + pcol[0].dtp20_psize = 6.5; + pcol[1].dtp20_octval = 4; /* Cyan */ + pcol[1].dtp20_psize = 10.0; + pcol[2].dtp20_octval = 2; /* Magenta */ + pcol[2].dtp20_psize = 0.0; + pcol[3].dtp20_octval = 6; /* Blue */ + pcol[3].dtp20_psize = 12.5; + pcol[4].dtp20_octval = 1; /* Yellow */ + pcol[4].dtp20_psize = 7.0; + pcol[5].dtp20_octval = 5; /* Green */ + pcol[5].dtp20_psize = 0.0; + pcol[6].dtp20_octval = 3; /* Red */ + pcol[6].dtp20_psize = 0.0; + pcol[7].dtp20_octval = 7; /* Black */ + pcol[7].dtp20_psize = 13.0; + + /* Setup .cht edge tracking information */ + et_init(); + et_height(oft != 2 ? mm2pnt(ph) : ph); + et_media(media->rgb); + + /* Set Instrument specific parameters */ + if (itype == instDTP20) { /* Xrite DTP20 */ + hex = 0; /* No hex for strip instruments */ + hxew = hxeh = 0.0; /* No extra padding because no hex */ + domaxmin = 2; /* Print SID patches */ + nmaxp = 4; /* Extra header patches */ + nminp = 1; /* Extra trailer patches */ + nextrap = nmaxp + nminp;/* Number of extra patches for max and min */ + dorspace = 0; /* Maximise number of rows */ + dopglabel = 1; /* Write a per page label */ + padlrow = 1; /* Pad the last row with white */ + spacer = 0; /* No Spacer between patches */ + pspa = 0.0; /* No spacer width */ + usede = 1; /* Use delta E to maximize patch/spacer conrast */ + needpc = 1; /* Helps to have patch to patch contrast in a row ? */ + lspa = bord + 5.0 + 5.0; /* Leader space before first patch = bord + pcar + yxhi */ + lcar = 5.0; /* Leading clear area before first patch */ + plen = pscale * (6.5); /* Patch min length */ + if(plen <= 6.75) /* Patch length must be one of 5 lengths */ + plen = 6.5; + else if(plen <= 8.0) + plen = 7.0; + else if(plen <= 11.25) + plen = 10.0; + else if(plen <= 12.75) + plen = 12.5; + else + plen = 13.0; + tidplen = 6.0; /* TID Patch length. Can't vary. */ + tspa = 5.0; /* Clear space after last patch */ + pwid = 10.0; /* Patch min width. (The guide slot is 12mm ?) */ + if (plen > pwid) + pwid = plen; /* Make patch at least as wide as long */ + rrsp = pwid; /* Row center to row center spacing */ + pwex = 0.0; /* Patch width expansion between rows of a strip */ + if (nollimit == 0) { + mxpprow = MAXPPROW; /* Maximum patches per row permitted (set by length) */ + mxrowl = (240.0 - lcar - tspa); /* Maximum row length */ + } else { + mxpprow = MAXPPROW; /* Maximum patches per row permitted (set by length) */ + mxrowl = MAXROWLEN; /* No row length */ + } + tidrows = 1; /* Rows on first page for target ID */ + tidtype = 0; /* Target ID type. 0 = DTP20 */ + tidminp = 21; /* Target ID minumum number of patches (+ white) */ + rpstrip = 999; /* Rows per strip */ + txhi = 5.0; /* Label Text Height */ + txhisl = 2.0; /* Strip Label Text Height */ + docutmarks = 0; /* Don't generate strip cut marks */ + clwi = 0.0; /* Cut line width */ + dorowlabel = 0; /* Don't generate row labels */ + rlwi = 0.0; /* Row label width */ + pglth = 5.0; /* Page Label text height */ + + if (npat > 4095) + error ("Number of patchs %d exceeds maximum of 4095 for DTP20"); + + } else if (itype == instDTP22 ) { /* X-Rite DTP22 Digital Swatchbook */ + hex = hflag ? 1 : 0; /* Hex if requestested */ + domaxmin = 0; /* Don't print max and min patches */ + nextrap = 0; /* Number of extra patches for max and min */ + nmaxp = nminp = 0; /* Extra max/min patches */ + nextrap = nmaxp + nminp;/* Number of extra patches for max and min */ + dorspace = 0; /* Do a rrsp from center of last patch to cut line */ + dopglabel = 1; /* Write a per page label */ + padlrow = 0; /* Pad the last row with white */ + spacer = 0; /* No spacer */ + usede = 1; /* Use delta E to maximize patch/spacer conrast */ + needpc = 1; /* Need patch to patch contrast in a row */ + lspa = bord + 8.0; /* Leader space before first patch = border + text */ + lcar = 0.0; /* Leading clear area before first patch */ + if (hex) { + plen = pscale * sqrt(0.75) * 8.0; /* Patch min length */ + hxeh = 1.0/6.0 * plen; /* Extra border for hex tops & bottoms */ + hxew = pscale * 0.25 * 8.0; /* Extra border for hex sides */ + } else { + plen = pscale * 8.0; /* Patch min length */ + hxew = hxeh = 0.0; /* No extra padding because no hex */ + } + pspa = 0.0; /* Inbetween Patch spacer */ + tspa = 0.0; /* Clear space after last patch */ + pwid = pscale * 8.0; /* Patch min width */ + rrsp = pscale * 8.0; /* Row center to row center spacing */ + pwex = 0.0; /* Patch width expansion between rows of a strip */ + mxpprow = MAXPPROW; /* Maximum patches per row permitted */ + mxrowl = MAXROWLEN; /* Maximum row length */ + tidrows = 0; /* No rows on first page for target ID */ + rpstrip = 999; /* Rows per strip */ + txhi = txhisl = 5.0; /* Text Height */ + docutmarks = 0; /* Don't generate strip cut marks */ + clwi = 0.0; /* Cut line width */ + dorowlabel = 1; /* Generate row labels */ + rlwi = 8.0; /* Row label width */ + pglth = 5.0; /* Page Label text height */ + + } else if (itype == instDTP41) { /* Xrite DTP41 */ + hex = 0; /* No hex for strip instruments */ + hxew = hxeh = 0.0; /* No extra padding because no hex */ + domaxmin = 0; /* Don't print max and min patches */ + nmaxp = nminp = 0; /* Extra max/min patches */ + nextrap = nmaxp + nminp;/* Number of extra patches for max and min */ + dorspace = 0; /* Maximise number of rows */ + dopglabel = 1; /* Write a per page label */ + padlrow = 1; /* Pad the last row with white */ + if (spacer < 0) + spacer = 2; /* Colored Spacer */ + needpc = 1; /* Need patch to patch contrast in a row */ + lspa = inch2mm(1.5); /* Leader space before first patch */ + lcar = inch2mm(0.5); /* Leading clear area before first patch */ + plen = pscale * inch2mm(0.29); /* Patch min length (should be 7.0 mm min.) */ + if (spacer > 0) + pspa = pscale * sscale * inch2mm(0.08); /* Inbetween Patch spacer (should be 2.0 mm min.)*/ + else + pspa = 0.0; /* No spacer */ + tspa = 2 * (plen + pspa); /* Clear space after last patch */ + pwid = inch2mm(0.5); /* Patch min width */ + rrsp = inch2mm(0.5); /* Row center to row center spacing */ + pwex = (rrsp - pwid)/2.0; /* Patch width expansion between rows of a strip */ + if (nollimit == 0) { + mxpprow = 100; /* Maximum patches per row permitted */ + mxrowl = inch2mm(55.0); /* Maximum row length */ + } else { + mxpprow = MAXPPROW; /* Maximum patches per row permitted */ + mxrowl = MAXROWLEN; /* Maximum row length */ + } + tidrows = 0; /* No rows on first page for target ID */ + rpstrip = 8; /* Rows per strip */ + txhi = txhisl = 5.0; /* Text Height */ + docutmarks = 1; /* Generate strip cut marks */ + clwi = 0.3; /* Cut line width */ + dorowlabel = 0; /* Don't generate row labels */ + rlwi = 0.0; /* Row label width */ + pglth = 5.0; /* Page Label text height */ + + } else if (itype == instDTP51) { /* Xrite DTP51 */ + hex = 0; /* No hex for strip instruments */ + hxew = hxeh = 0.0; /* No extra padding because no hex */ + domaxmin = 1; /* Print max and min patches */ + nmaxp = nminp = 1; /* Extra max/min patches */ + nextrap = nmaxp + nminp;/* Number of extra patches for max and min */ + dorspace = 1; /* Do a rrsp from center of last patch to cut line */ + dopglabel = 0; /* No need for a per page label */ + padlrow = 1; /* Pad the last row with white */ + if (spacer < 0) + spacer = 2; /* Colored Spacer */ + needpc = 1; /* Need patch to patch contrast in a row */ + lspa = inch2mm(1.2); /* Leader space before first patch */ + lcar = inch2mm(0.25); /* Leading clear area before first patch */ + plen = pscale * inch2mm(0.4); /* Patch min length */ + if (spacer > 0) + pspa = pscale * sscale * inch2mm(0.07); /* Inbetween Patch spacer */ + else + pspa = 0.0; /* No spacer */ + tspa = inch2mm(0.0); /* Clear space after last patch */ + pwid = inch2mm(0.4); /* Patch min width */ + rrsp = inch2mm(0.5); /* Row center to row center spacing */ + pwex = (rrsp - pwid)/2.0; /* Patch width expansion between rows of a strip */ + if (nollimit == 0) { + mxpprow = 72; /* Maximum patches per row permitted */ + mxrowl = inch2mm(40.0); /* Maximum row length */ + } else { + mxpprow = MAXPPROW; /* Maximum patches per row permitted */ + mxrowl = MAXROWLEN; /* Maximum row length */ + } + tidrows = 0; /* No rows on first page for target ID */ + rpstrip = 6; /* Rows per strip */ + txhi = txhisl = 5.0; /* Text Height */ + docutmarks = 1; /* Generate strip cut marks */ + clwi = 0.3; /* Cut line width */ + dorowlabel = 0; /* Don't generate row labels */ + rlwi = 0.0; /* Row label width */ + pglth = 5.0; /* Page Label text height */ + + } else if (itype == instSpectroScan ) { /* GretagMacbeth SpectroScan */ + hex = hflag ? 1 : 0; /* Hex if requestested */ + domaxmin = 0; /* Don't print max and min patches */ + nextrap = 0; /* Number of extra patches for max and min */ + nmaxp = nminp = 0; /* Extra max/min patches */ + nextrap = nmaxp + nminp;/* Number of extra patches for max and min */ + dorspace = 0; /* Maximise number of rows */ + dopglabel = 1; /* Write a per page label */ + padlrow = 0; /* Pad the last row with white */ + spacer = 0; /* No spacer */ + needpc = 0; /* Don't need patch to patch contrast in a row */ + lspa = bord + 7.0; /* Leader space before first patch = border + text */ + lcar = 0.0; /* Leading clear area before first patch */ + if (hex) { + plen = pscale * sqrt(0.75) * 7.0; /* Patch min length */ + hxeh = 1.0/6.0 * plen; /* Extra border for hex tops & bottoms */ + hxew = pscale * 0.25 * 7.0; /* Extra border for hex sides */ + } else { + plen = pscale * 7.0; /* Patch min length */ + hxew = hxeh = 0.0; /* No extra padding because no hex */ + } + pspa = 0.0; /* Inbetween Patch spacer */ + tspa = 0.0; /* Clear space after last patch */ + pwid = pscale * 7.0; /* Patch min width */ + rrsp = pscale * 7.0; /* Row center to row center spacing */ + pwex = 0.0; /* Patch width expansion between rows of a strip */ + mxpprow = MAXPPROW; /* Maximum patches per row permitted */ + mxrowl = MAXROWLEN; /* Maximum row length */ + tidrows = 0; /* No rows on first page for target ID */ + rpstrip = 999; /* Rows per strip */ + txhi = txhisl = 5.0; /* Row/Column Text Height */ + docutmarks = 0; /* Don't generate strip cut marks */ + clwi = 0.0; /* Cut line width */ + dorowlabel = 1; /* Generate row labels */ + rlwi = 7.5; /* Row label width */ + pglth = 5.0; /* Page Label text height */ + + } else if (itype == instI1Pro ) { /* GretagMacbeth Eye-One Pro */ + if (nolpcbord == 0 && bord < 26.0) + lbord = 26.0 - bord; /* need this for holder to grip paper and plastic spacer */ + hex = 0; /* No hex for strip instruments */ + hxew = hxeh = 0.0; /* No extra padding because no hex */ + domaxmin = 0; /* Don't print max and min patches */ + nextrap = 0; /* Number of extra patches for max and min */ + nmaxp = nminp = 0; /* Extra max/min patches */ + nextrap = nmaxp + nminp;/* Number of extra patches for max and min */ + dorspace = 0; /* Maximise number of rows by having no space between them */ + dopglabel = 1; /* Write a per page label */ + padlrow = 1; /* Don't need to pad the last row for the i1, */ + /* but the strip read logic can't handle it. */ + if (spacer < 0) + spacer = 2; /* Colored Spacer */ + needpc = 1; /* Need patch to patch contrast in a row */ + usede = 1; /* Use delta E to maximize patch/spacer conrast */ + lspa = bord + 7.0 + 10.0; /* Leader space before first patch = bord + txhisl + lcar */ + lcar = 10.0; /* Leading clear area before first patch */ + plen = pscale * 10.00; /* Patch min length - total 11 mm */ + if (spacer > 0) + pspa = pscale * sscale * 1.00; /* Inbetween Patch spacer 1mm */ + else + pspa = 0.0; /* No spacer */ + tspa = 10.0; /* Clear space after last patch - run off */ + pwid = pscale * 8.0; /* Patch min width */ + rrsp = pscale * 8.0; /* Row center to row center spacing */ + pwex = 0.0; /* Patch width expansion between rows of a strip */ + if (nollimit == 0) { + mxpprow = MAXPPROW; /* Maximum patches per row permitted (set by length) */ + mxrowl = (260.0 - lcar - tspa); /* Maximum holder row length */ + } else { + mxpprow = MAXPPROW; /* Maximum */ + mxrowl = MAXROWLEN; /* Maximum */ + } + tidrows = 0; /* No rows on first page for target ID */ + rpstrip = 999; /* Rows per strip */ + txhi = txhisl = 7.0; /* Text Height */ + docutmarks = 0; /* Don't generate strip cut marks */ + clwi = 0.0; /* Cut line width */ + dorowlabel = 0; /* Don't generate row labels */ + rlwi = 0.0; /* Row label width */ + pglth = 5.0; /* Page Label text height */ + + } else if (itype == instColorMunki ) { /* X-Rite ColorMunki */ + hex = 0; /* No hex for strip instruments */ + hxew = hxeh = 0.0; /* No extra padding because no hex */ + domaxmin = 0; /* Don't print max and min patches */ + nextrap = 0; /* Number of extra patches for max and min */ + nmaxp = nminp = 0; /* Extra max/min patches */ + nextrap = nmaxp + nminp;/* Number of extra patches for max and min */ + dorspace = 0; /* Put spaces between rows for guidance */ + dopglabel = 1; /* Write a per page label */ + padlrow = 1; /* Don't need to pad the last row for the Munki, */ + /* but the strip read logic can't handle it. */ + if (spacer < 0) + spacer = 2; /* Colored Spacer */ + needpc = 1; /* Need patch to patch contrast in a row */ + usede = 1; /* Use delta E to maximize patch/spacer conrast */ + lspa = bord + 7.0 + 20.0; /* Leader space before first patch = bord + txhisl + lcar */ + lcar = 20.0; /* Leading clear area before first patch */ + plen = pscale * 14.00; /* Patch min length - total 15 mm */ + if (spacer > 0) + pspa = pscale * sscale * 1.0; /* Inbetween Patch spacer 1mm */ + else + pspa = 0.0; /* No spacer */ + tspa = 25.0; /* Clear space after last patch - run off */ + if (hflag) { /* High density */ + pwid = pscale * 13.7; /* Patch min width */ + rrsp = pscale * 13.7; /* Row center to row center spacing */ + hxeh = 0.25 * plen; /* Extra space for row stagger */ + stagger = 0.5 * (plen + 0.5 * pspa); /* Do stagger */ + } else { + pwid = pscale * 28.0; /* Patch min width */ + rrsp = pscale * 28.0; /* Row center to row center spacing */ + } + pwex = 0.0; /* Patch width expansion between rows of a strip */ + mxpprow = MAXPPROW; /* Maximum patches per row permitted (set by length) */ + mxrowl = MAXROWLEN; /* Maximum row length */ + tidrows = 0; /* No rows on first page for target ID */ + rpstrip = 999; /* Rows per strip */ + txhi = txhisl = 7.0; /* Text Height */ + docutmarks = 0; /* Don't generate strip cut marks */ + clwi = 0.0; /* Cut line width */ + dorowlabel = 0; /* Don't generate row labels */ + rlwi = 0.0; /* Row label width */ + pglth = 5.0; /* Page Label text height */ + + + } else { + error("Unsupported intrument type"); + } + + /* Compute page limits */ + x1 = bord + lbord; /* Bounding box in mm */ + y1 = bord; + x2 = pw - bord; + y2 = ph - bord; + iw = x2 - x1; /* Imagable areas width and height in mm */ + ih = y2 - y1; + + *p_patchlen = plen; /* Return patch lenth in mm */ + *p_gaplen = pspa; /* Return gap lenth in mm */ + *p_taplen = tspa; /* Return trailer lenth in mm */ + + if (scanc & 2) /* Scan compatiblity */ + sxwi = pwid/2.0; /* First row patches extra width */ + + /* Compute limits for this page size */ + /* Figure the available space for patches */ + mints = bord + txhisl + lcar; /* Minimum top space due to border, text and clear area */ + if (mints < lspa) + mints = lspa; /* Minimum top space due to leader */ + minbs = bord; /* Minimum botom space due to border */ + if (minbs < tspa) + minbs = tspa; /* Minimum botom space due to trailer */ + arowl = ph - mints - minbs - 2.0 * hxeh; /* Available space for printing test patches */ + if (arowl > mxrowl) + arowl = mxrowl; /* Limit maximum row length */ + + /* We are assuming that every patch may be surounded by a spacer */ + /* (ie. there are always pprow+1 gaps spacers are used) */ + pprow = (int)((arowl - pspa)/(plen + pspa)); /* Raw Patches per row */ + if (pprow > mxpprow) /* Limit to maximum */ + pprow = mxpprow; + + tidpprow = 0; + if (tidminp > 0 && tidplen > 0.0) { + tidpprow = (int)((arowl - pspa)/(tidplen + pspa)); /* Raw TID Patches per row */ + if (tidpprow < tidminp) /* TID doesn't use nextrap */ + error("Paper size not long enough for target identification row (need %.1f mm, got %.1f mm)!",tidminp * (tidplen + pspa) - pspa, arowl); + } + + tidpad = (pprow - tidminp)/2; /* Center TID */ + + if (pprow < (1+nextrap)) + error("Paper size not long enought for a single patch per row!"); + + *ptpprow = tpprow = pprow - nextrap; /* Test sample patches per row */ + + tidnpat = npat + (tidrows * tpprow); /* Total patches including TID row, but not max/min/sid */ + + /* (Sample patches per row including TID) */ + if ((*pprps = (unsigned char *)malloc(sizeof(unsigned char) * (2 + (tidnpat/tpprow)))) == NULL) + error("Malloc failed!"); + rpsp = *pprps; + + /* Compute actual lowest coordinate used */ + aminbs = ph - mints - pspa - pprow * (plen + pspa); + amints = mints + 0.5 * (aminbs - minbs); /* Distribute extra space */ + aminbs = minbs + 0.5 * (aminbs - minbs); + + /* Compute whole strip width */ + if (dorspace) + swid = rpstrip * rrsp + pwid/2.0; /* set gutter is rrsp - pwid/2 wide */ + else + swid = (rpstrip-1) * rrsp + pwid + clwi; /* set gutter is 0, but allow for cut line */ + + /* Compute strips per page. Number of whole strips + partial strips */ + sppage = (int)((iw - rlwi - sxwi - 2.0 * hxew - (dopglabel ? pglth : 0.0))/swid) + 1; + + /* Compute rows per partial strip on whole page */ + if (dorspace) + rppstrip = (int)((iw - rlwi - sxwi - 2.0 * hxew - (dopglabel ? pglth : 0.0) - swid * (sppage-1) - pwid/2.0)/rrsp); + else + rppstrip = (int)((iw - rlwi - sxwi - 2.0 * hxew - (dopglabel ? pglth : 0.0) - swid * (sppage-1) - pwid + rrsp)/rrsp); + if (rppstrip < 0) + rppstrip = 0; + if (rppstrip == 0) { /* Make last partial strip a full strip */ + sppage--; + rppstrip = rpstrip; + } + + if (sppage <= 0) + error("Not enough width for even one row!"); + + /* The number of pages needed */ + pppage = tpprow * ((sppage-1) * rpstrip + rppstrip);/* Real patches per page */ + npages = (tidnpat + pppage -1)/pppage; /* whole & partial pages */ + ppstrip = tpprow * rpstrip; /* Real patches per full strip */ + + rem = tidnpat; /* Total test patches to print */ + rem -= (npages-1) * pppage; /* Remaining patches to be printed on last page */ + + lsppage = (rem + ppstrip -1)/ppstrip; /* Last pages whole & partial strips per page */ + rem -= (lsppage - 1) * ppstrip; /* remaining patches to be printed in last strip */ + + lrpstrip = (rem + tpprow - 1)/tpprow; + /* Last strips whole & partial rows per strip */ + + rem -= (lrpstrip - 1) * tpprow; /* remaining patches to be printed in last row */ + + lpprow = rem + nextrap; /* Patches in last row of last strip of last page */ + + if (verb) { + fprintf(stderr,"Patches = %d\n",npat); + fprintf(stderr,"Test patches per row = %d\n",tpprow); + if (sppage == 1) + fprintf(stderr,"Rows per page = %d, patches per page = %d\n",rppstrip, pppage); + else + fprintf(stderr,"Strips per page = %d, rows per partial strip = %d, patches per page = %d\n",sppage, rppstrip, pppage); + if (tidrows > 0) + fprintf(stderr,"Target ID rows in first page = %d\n", tidrows); + fprintf(stderr,"Rows in last strip = %d, patches in last row = %d\n", lrpstrip, lpprow-nextrap); + fprintf(stderr,"Total pages needed = %d\n",npages); + } + + if (padlrow) { /* Add in extra padding patches */ + int i; + for (i = 0; lpprow < pprow; lpprow++, npat++, tidnpat++, i = (i + 1) & 7) { +#ifdef NEVER + if (needpc && rand) + cols[npat] = pcol[i]; /* structure copy */ + else + cols[npat] = *media; /* structure copy */ +#else + cols[npat] = *media; /* structure copy */ +#endif + cols[npat].i = npat; /* Now has an index */ + cols[npat].t &= ~T_PRESET; + cols[npat].t |= T_PAD; + cols[npat].id = "0"; /* Padding identification */ + } + } + *p_npat = npat; /* Return number of patches including padding */ +//printf("~1 padded no of patches = %d\n", npat); + + /* Setup logical to random patch mapping */ + if ((rix = (int *)malloc(sizeof(int) * (npat + 1))) == NULL) + error("Malloc failed!"); + + setup_randix(rix, npat, rand, rstart, verb, cols, pcol, + tpprow, spacer, needpc, domaxmin, media, usede); + rix[npat] = -1; /* Shouldn't use this */ + + /* Init everything */ + l_si = i = 0; /* Physical test patch index. */ + ix = rix[i]; /* First index */ + + pir = 1; /* Starting patch in row (includes max/min patches) */ + ris = 1; /* Starting row in strip. */ + sip = 1; /* Starting strip in page */ + pif = 1; /* Starting page in file */ + + /* slix is 0..n but is pre-incremented, so start at -1 */ + slix = -1 -tidrows; /* Start at -2 if there is a TID */ + + /* Until there are no more patches to do */ + for (;;) { + char *sp = NULL; /* String pointer - label */ + double w; /* Width of next patch */ + int flags; /* flags for current patch */ +#define IS_FPIR 0x00001 /* Is first patch in row (possibly max density patch) */ +#define IS_XPAT 0x00002 /* Is max density/SID patch */ +#define IS_NPAT 0x00004 /* Is min density/SID patch */ +#define IS_LPIR 0x00008 /* Is last patch in row (possibly min density patch) */ +#define IS_FRIS 0x00010 /* Is first row in strip */ +#define IS_LRIS 0x00020 /* Is last row in strip */ +#define IS_FSIP 0x00040 /* Is first strip in page */ +#define IS_LSIP 0x00080 /* Is last strip in page */ +#define IS_FPIF 0x00100 /* Is first page in file */ +#define IS_LPIF 0x00200 /* Is last page in file */ +#define IS_PAD 0x04000 /* Is fake padding patch, to round out very last row */ + + /* Init flags for this patch */ + flags = 0; + if (pir == 1) + flags |= IS_FPIR; + if (pir <= nmaxp) + flags |= IS_XPAT; + + if (slix < -1) { /* If TID row */ + + if (pir == tidpprow) + flags |= IS_LPIR; + + } else { + + if (pir > (pprow - nminp)) + flags |= IS_NPAT; + + if (pir == pprow) + flags |= IS_LPIR; + } + + if (ris == 1) + flags |= IS_FRIS; + if (ris == rpstrip) + flags |= IS_LRIS; + + if (sip == 1) + flags |= IS_FSIP; + if (sip == sppage) { + flags |= IS_LSIP; + if (ris == rppstrip) + flags |= IS_LRIS; + } + if (pif == 1) + flags |= IS_FPIF; + if (pif == npages) { + flags |= IS_LPIF; + if (sip == lsppage) { + flags |= IS_LSIP; + if (ris == lrpstrip) { + flags |= IS_LRIS; + + if (padlrow) { /* Last row in chart may be a runt */ + if (pir > lpprow) + flags |= IS_PAD; + } else { + if (pir > (lpprow - nminp)) + flags |= IS_NPAT; + if (pir == lpprow) + flags |= IS_LPIR; + } + } + } + } + +//printf("~1 pir %d, ris %d, sip %d, pif %d\n", pir, ris, sip, pif); + + /* Set initial patch width */ + w = pwid; + if (!(flags & IS_FRIS)) + w += pwex; /* Extend patch into previous row to row gap */ + if (!(flags & IS_LRIS)) + w += pwex; /* Extend patch into next row to row gap */ + + if ((flags & IS_FRIS) && (flags & IS_FSIP)) /* First row on page */ + w += sxwi; /* Make first row fatter for scan compatiblity */ + + if (flags & IS_FPIR) { /* Start of row */ + y = ph - amints; /* Start ready for spacer or patch */ + + if (flags & IS_FRIS) { /* Start of strip */ + if (flags & IS_FSIP) { /* Start of page */ + x = x1; /* Start at leftmost position */ + if (oft == 0) { /* PS */ + if (flags & IS_FPIF) { /* First page */ + sprintf(psname,"%s.ps",bname); + if ((tro = new_ps_trend(psname,npages,nmask,pw,ph,oft,rand,rstart)) == NULL) + error ("Unable to create output rendering object file '%s'",psname); + if (verb) + printf("Creating file '%s'\n",psname); + } + } else if (oft == 1) { /* EPS */ + if (npages > 1) + sprintf(psname,"%s_%02d.eps",bname,pif); + else + sprintf(psname,"%s.eps",bname); + if ((tro = new_ps_trend(psname,npages,nmask,pw,ph,oft,rand,rstart)) == NULL) + error ("Unable to create output rendering object file '%s'",psname); + if (verb) + printf("Creating file '%s'\n",psname); + } else { /* TIFF */ + double res; /* pix/mm */ + if (npages > 1) + sprintf(psname,"%s_%02d.tif",bname,pif); + else + sprintf(psname,"%s.tif",bname); + + res = tiffres/25.4; + if ((tro = new_tiff_trend(psname,nmask,tiffdpth,pw,ph, + nosubmarg ? 0 : bord, res,res,altrep,ncha,tiffcomp, tiffdith)) == NULL) + error ("Unable to create output rendering object file '%s'",psname); + if (verb) + printf("Creating file '%s'\n",psname); + } + tro->startpage(tro,pif); + + /* Print all the row labels */ + if (dorowlabel) { + double ty = y; /* Temp y coord */ + int tpir; /* Temp patch in row */ + + for (tpir = 0; tpir < pprow; tpir++) { + /* If we're within test sample patch range */ + if (tpir >= nmaxp && tpir < (pprow - nminp)) { + char *rlabl; + if ((rlabl = paix->aix(paix, tpir - nmaxp)) == NULL) + error ("Patch in row label %d out of range",tpir); + tro->setcolor(tro, cal, mark); + tro->string(tro, x, ty-plen, rlwi, plen, rlabl); + free(rlabl); + } + + ty -= plen + pspa; + } + + x += rlwi; + } + + x += hxew; /* Extra space on left for bits of hex */ + + /* Clear edge list tracking */ + et_clear(); + } + } + + /* Increment strip label 0..n */ + slix++; + + /* Print strip label */ + if ((lspa - lcar - bord) >= txhisl) { /* There is room for label */ + if (slix < 0) { /* TID */ + tro->setcolor(tro, cal, mark); + tro->string(tro,x,y2-txhisl,w,txhisl,"TID"); + } else { /* Not TID */ + if (slab != NULL) + free(slab); + if ((slab = saix->aix(saix, slix)) == NULL) + error("strip index %d out of range",slix); + + tro->setcolor(tro, cal, mark); + tro->string(tro,x,y2-txhisl,w,txhisl,slab); + } + } + + /* Start with background = media */ + pp = media; + + /* Stagger rows */ + if (stagger > 0.0) { + if (slix & 1) + y -= stagger; + } + } + + /* Figure the current patch color */ + cpf = 0; + if (slix < 0) { /* TID */ + cp = mind; /* Default padding color is white */ + if (pir > tidpad && pir <= (tidpad + tidminp) ) { + int opir = pir - tidpad -1; /* TID index 0 .. tidminp-1 */ + + if (opir == 0) { + cp = &pcol[1]; /* Cyan */ + + } else if (opir >= 1 && opir <= 2) { /* Patches in each strip */ + col *ppcol[2]; + if (dtp20_enc(pcol, 2, 0, ppcol, tpprow) != 0) + error("Internal, dtp20 TID ppstrip failed, val %d, digits %d",tpprow,2); + cp = ppcol[opir-1]; + + } else if (opir >= 3 && opir <= 6) { /* Total patches, including padding */ + col *ppcol[4]; + if (dtp20_enc(pcol, 4, 0, ppcol, npat) != 0) + error("Internal, dtp20 tot patches failed, val %d, digits %d",npat,4); + cp = ppcol[opir-3]; + + } else if (opir == 7) { /* Patch size */ + int j; + for (j = 0; j < 8; j++) { + if (fabs(pcol[j].dtp20_psize - plen) < 0.001) + break; + } + if (j >= 8) + error("Can't encode patch length for DTP20"); + cp = &pcol[j]; + + } else if (opir == 8) { /* Patch spacer width */ + int j, k; + k = (int)(pspa / 0.5 + 0.5); + for (j = 0; j < 8; j++) { + if (pcol[j].dtp20_octval == k) + break; + } + if (j >= 8) + error("Can't encode spacer length for DTP20"); + cp = &pcol[j]; + + } else if (opir >= 9 && opir <= 18) { /* User defined */ + + if (opir == 9) { /* Indicate what user defined is used for */ + cp = &pcol[0]; /* 0 = random seed format */ + } else if (opir >= 10 && opir <= 13) { /* Random seend value */ + col *ppcol[4]; + if (dtp20_enc(pcol, 4, 0, ppcol, rstart) != 0) + error("Internal, dtp20 chart id failed, val %d, digits %d",rstart,4); + cp = ppcol[opir-10]; + + } else { /* 14 .. 18 */ + cp = &pcol[0]; /* Currently unused */ + } + + } else if (opir == 19) { + cp = &pcol[4]; /* Yellow */ + } else if (opir == 20) { + cp = &pcol[2]; /* Magenta */ + } + } + } else { + if (flags & IS_XPAT) { /* Max or SID at start of row */ + sp = NULL; /* Not a test patch (no label) */ + if (domaxmin == 1) { + cp = maxd; /* Maximum density patch at start */ + } else if (domaxmin == 2) { + if (pir == 1) { /* At very start */ + cpf = 1; /* Create start bit */ + cp = &pcol[8]; /* Starts with 50/50/50 DTP20 Grey */ + } else { + col *ppcol[3]; + /* Compute the patch colors the DTP20 will use before row */ + if (dtp20_enc(pcol, 3, 1, ppcol, slix+1) != 0) + error("Internal, dtp20 SID row id failed, val %d, digits %d",slix+1,3); + cp = ppcol[pir-2]; + } + } + + } else if (flags & IS_NPAT) { /* Min at end of rows or stop bit */ + sp = NULL; /* Not a test patch (no label) */ + if (domaxmin == 1) { + cp = mind; + } else if (domaxmin == 2) { /* DTP20 end patch */ + cpf = 2; /* Create stop bit */ + cp = mind; /* Starts with mind */ + } + + } else if (flags & IS_PAD) { /* Fake blank patch */ + sp = NULL; /* Not a test patch (no label) */ + cp = media; + + } else { /* set test sample patch location and color */ + int apir = pir - nmaxp; /* Adjusted pir for max/min patches */ + if (sp != NULL) + free(sp); + if ((sp = patch_location(saix, paix, ixord, slix, apir-1)) == NULL) + error ("Patch location out of range, strip %d patch %d",slix,apir-1); + if (ix < 0) { + error("Internal, got -ve patch index for generating patch %d",i); + } + strcpy(cols[ix].loc, sp); /* Record location */ + cp = &cols[ix]; /* Get color for this patch */ + + i++; /* Consumed a test patch */ + if (i > npat) + error("Internal - ran out of test patches !"); + + ix = rix[i]; /* Next patch index */ + } + } + + /* Print a spacer in front of patch if requested */ + if (spacer > 0) { + setup_spacer(&sc, pp, cp, pcol, spacer, usede); + tro->setcolor(tro, cal, sc); + tro->rectangle(tro, x, y, w, pspa, NULL,1); + y -= pspa; + } + + /* Print patch */ + { + double wplen = plen; + + if (slix < 0) { + wplen = tidplen; /* TID can have a different length patch */ + } + + tro->setcolor(tro, cal, cp); /* Patch color set above */ + if (hex) { + int apir = pir - nmaxp; /* Adjusted pir for max/min patches */ + tro->hexagon(tro, x, y, w, wplen, apir-1, sp); + } else { + /* We hack in the twin patches for the DTP20 start and stop */ + /* Initial color is set above (as for regular patches) */ + if (cpf == 1) { /* DTP20 start bit */ + tro->rectangle(tro, x, y, w, 1.0, sp,1); /* 50/50/50 set above */ + tro->setcolor(tro, cal, mind); /* White */ + tro->rectangle(tro, x, y - 1.0, w, wplen - 1.0, sp,1); + } else if (cpf == 2) { /* DTP20 stop bit */ + tro->rectangle(tro, x, y, w, wplen - 3.0, sp,1); /* mind set above */ + tro->setcolor(tro, cal, &pcol[8]); /* 50/50/50 Grey for DTP20 */ + tro->rectangle(tro, x, y - wplen + 3.0, w, 3.0, sp,1); + } else { /* Normal patch */ + tro->rectangle(tro, x, y, w, wplen, sp,1); + } + } + y -= wplen; + } + if (sp != NULL) { /* Done with sp for the moment */ + free(sp); + sp = NULL; + } + + /* Advance the patch count */ + pir++; + pp = cp; /* Current color becomes last color */ + + /* If this is the last patch in the row, */ + /* print a possible last spacer. */ + if (flags & IS_LPIR) { /* End of row */ + cp = media; + if (spacer > 0) { + setup_spacer(&sc, pp, cp, pcol, spacer, usede); + tro->setcolor(tro, cal, sc); + tro->rectangle(tro, x, y, w, pspa, NULL,1); + y -= pspa; + } + } + + if (flags & IS_LPIR) { /* Last patch in row */ + pir = 1; + ris++; + + /* First step to the middle of the patch */ + if (flags & IS_FRIS) + x += pwid/2.0; + else + x += pwid/2.0 + pwex; + + /* Then step to the start of the next patch */ + if (flags & IS_LRIS) { + if (dorspace) + x += rrsp; /* row to row space, making gutter */ + else + x += pwid/2.0 + clwi; /* no gutter, but room for cut line */ + } else + x += pwid/2.0 + pwex; + + if ((flags & IS_FRIS) && (flags & IS_FSIP)) /* First row on page */ + x += sxwi; /* Allow for scan compatible fatter first row */ + + if (flags & IS_LRIS) { /* End of strip */ + /* Ignore TID rows */ + if ((flags & IS_FPIF) && (flags & IS_FSIP)) + *rpsp++ = ris-tidrows-1; /* Record how many rows in this strip */ + else + *rpsp++ = ris-1; /* Record how many rows in this strip */ + ris = 1; + sip++; + + /* Print end of strip crop line */ + tro->setcolor(tro, cal, mark); + if (docutmarks) /* Generate strip cut marks */ + tro->dline(tro,x-0.3/2.0,y1,x-0.3/2.0,y2,0.3); /* 0.3 wide dotted line */ + + /* Print end of strip identification if we've allowed space */ + if (dorspace) + tro->vstring(tro,x,y1,rrsp-pwid/2.0-pwex,y2-y1,label); + + if (flags & IS_LSIP) { /* End of page */ + sip = 1; + + x += hxew; /* Allow space for extra bits of hexagons */ + + /* Print per page label if we've allowed for it */ + if (dopglabel) { + //tro->vstring(tro,x2,y1,pglth,y2-y1,label); /* At end of page */ + tro->vstring(tro,x+pglth,y1,pglth,y2-y1,label); /* After last strip */ + } + + /* If we expect to scan this chart in, add some fiducial marks at the corners */ + if (scanc & 1) { + double lw = 0.5; /* Line width */ + double ll = 5.0; /* Line length */ + +// fprintf(of,"%% Fiducial marks\n"); + + tro->setcolor(tro, cal, mark); + + tro->rectangle(tro, x1, y2, ll, lw, NULL, 0); /* Top left */ + tro->rectangle(tro, x1, y2, lw, ll, NULL, 0); + if (oft != 2) + et_fiducial(mm2pnt(x1 + 0.5 * lw), mm2pnt(y2 - 0.5 * lw)); + else + et_fiducial(x1 + 0.5 * lw, y2 - 0.5 * lw); + + tro->rectangle(tro, x2 - ll, y2, ll, lw, NULL, 0); /* Top right */ + tro->rectangle(tro, x2 - lw, y2, lw, ll, NULL, 0); + if (oft != 2) + et_fiducial(mm2pnt(x2 - 0.5 * lw), mm2pnt(y2 - 0.5 * lw)); + else + et_fiducial(x2 - 0.5 * lw, y2 - 0.5 * lw); + + tro->rectangle(tro, x2 - ll, y1 + lw, ll, lw, NULL, 0); /* Bottom right */ + tro->rectangle(tro, x2 - lw, y1 + ll, lw, ll, NULL, 0); + if (oft != 2) + et_fiducial(mm2pnt(x2 - 0.5 * lw), mm2pnt(y1 + 0.5 * lw)); + else + et_fiducial(x2 - 0.5 * lw, y1 + 0.5 * lw); + + tro->rectangle(tro, x1, y1 + lw, ll, lw, NULL, 0); /* Bottom left */ + tro->rectangle(tro, x1, y1 + ll, lw, ll, NULL, 0); + if (oft != 2) + et_fiducial(mm2pnt(x1 + 0.5 * lw), mm2pnt(y1 + 0.5 * lw)); + else + et_fiducial(x1 + 0.5 * lw, y1 + 0.5 * lw); + } + + tro->endpage(tro); + if (oft != 0) { /* EPS or TIFF */ + tro->del(tro); + } + if (flags & IS_LPIF) { /* Last page in file */ + if (oft == 0) { /* PS */ + tro->del(tro); + } + } + + /* If we are anticipating that scanin may be used with the */ + /* chart, create the scanin recognition file for this page. */ + if (scanc & 1) { + char chtname[MAXNAMEL+20]; /* Name of .cht file */ + + if (npages > 1) + sprintf(chtname,"%s_%02d.cht",bname,pif); + else + sprintf(chtname,"%s.cht",bname); + et_write(chtname, cols, rix, l_si, i); + } + + l_si = i; /* New last start i */ + + if (flags & IS_LPIF) { /* Last page in file */ + break; /* Done */ + } + pif++; + } + } + } + } + if (slab != NULL) + free(slab); + free(rix); + + *rpsp++ = 0; /* End of rows per strip stuff */ + + et_clear(); /* Cleanup edge list structures */ +} + +/* A paper size structure */ +typedef struct { + char *name; /* User name (lower case) */ + double w,h; /* Width and height in mm */ + int def; /* Non-zero if default */ +} paper; + +static paper psizes[] = { + { "A4", 210.0, 297.0, 0 }, + { "A4R", 297.0, 210.0, 0 }, + { "A3", 297.0, 420.0, 1 }, + { "A2", 420.0, 594.0, 0 }, + { "Letter", 215.9, 279.4, 0 }, + { "LetterR", 279.4, 215.9, 0 }, + { "Legal", 215.9, 355.6, 0 }, + { "4x6", 101.6, 152.4, 0 }, + { "11x17", 279.4, 431.8, 0 }, + { NULL, 0.0, 0.0, 0 } +}; + +#define DEF_MARGINE 6.0 + +/* Case independent string compare */ +int +cistrcmp(char *s1, char *s2) { + for (;;s1++, s2++) { + if (tolower(*s1) != tolower(*s2)) + return 1; + if (*s1 == '\000') + return 0; + } +} + +#define DEF_SIXPAT "A-Z, A-Z" /* Default strip index pattern */ +#define DEF_PIXPAT "0-9,@-9,@-9;1-999" /* Default patch index pattern */ + +void usage(char *diag, ...) { + paper *pp; + fprintf(stderr,"Generate Target PostScrip file, 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: printtarg [-v] [-i instr] [-r] [-s] [-p size] basename\n"); + fprintf(stderr," -v Verbose mode\n"); + fprintf(stderr," -i 20 | 22 | 41 | 51 | SS | i1 | CM Select target instrument (default DTP41)\n"); + fprintf(stderr," 20 = DTP20, 22 = DTP22, 41 = DTP41, 51 = DTP51,\n"); + fprintf(stderr," SS = SpectroScan, i1 = i1Pro, CM = ColorMunki\n"); + fprintf(stderr," -h Use hexagon patches for SS, double density for CM\n"); + fprintf(stderr," -a scale Scale patch size and spacers by factor (e.g. 0.857 or 1.5 etc.)\n"); + fprintf(stderr," -A scale Scale spacers by additional factor (e.g. 0.857 or 1.5 etc.)\n"); + fprintf(stderr," -r Don't randomize patch location\n"); + fprintf(stderr," -s Create a scan image recognition (.cht) file\n"); + fprintf(stderr," -S Same as -s, but don't generate wide orientation strip.\n"); + fprintf(stderr," -c Force colored spacers\n"); + fprintf(stderr," -b Force B&W spacers\n"); + fprintf(stderr," -n Force no spacers\n"); + fprintf(stderr," -f Create PostScript DeviceN Color fallback\n"); + fprintf(stderr," -w g|r|s|n White colorspace encoding DeviceGray (def), DeviceRGB, Separation or DeviceN\n"); + fprintf(stderr," -k g|c|s|n Black colorspace encoding DeviceGray (def), DeviceCMYK, Separation or DeviceN\n"); + fprintf(stderr," -o k|r|n CMY colorspace encoding DefiveCMYK (def), inverted DeviceRGB or DeviceN\n"); + fprintf(stderr," -e Output EPS compatible file\n"); + fprintf(stderr," -t [res] Output 8 bit TIFF raster file, optional res DPI (default 100)\n"); + fprintf(stderr," -T [res] Output 16 bit TIFF raster file, optional res DPI (default 100)\n"); + fprintf(stderr," -C Don't use TIFF compression\n"); + fprintf(stderr," -N Use TIFF alpha N channels more than 4\n"); + fprintf(stderr," -D Dither 8 bit TIFF values down from 16 bit\n"); + fprintf(stderr," -Q nbits Quantize test values to fit in nbits\n"); + fprintf(stderr," -R rsnum Use given random start number\n"); + fprintf(stderr," -K file.cal Apply printer calibration to patch values and include in .ti2\n"); + fprintf(stderr," -I file.cal Include calibration in .ti2 (but don't apply it)\n"); + fprintf(stderr," -x pattern Use given strip indexing pattern (Default = \"%s\")\n",DEF_SIXPAT); + fprintf(stderr," -y pattern Use given patch indexing pattern (Default = \"%s\")\n",DEF_PIXPAT); + fprintf(stderr," -m margin Set a page margin in mm (default %3.1f mm)\n",DEF_MARGINE); + fprintf(stderr," -M margin Set a page margin in mm and include it in TIFF\n"); + fprintf(stderr," -P Don't limit strip length\n"); + fprintf(stderr," -L Suppress any left paper clip border\n"); + fprintf(stderr," -p size Select page size from:\n"); + for (pp = psizes; pp->name != NULL; pp++) + fprintf(stderr," %-8s [%.1f x %.1f mm]%s\n", pp->name, pp->w, pp->h, + pp->def ? " (default)" : ""); + fprintf(stderr," -p WWWxHHH Custom size, WWW mm wide by HHH mm high\n"); + fprintf(stderr," basname Base name for input(.ti1), output(.ti2) and output(.ps/.eps/.tif)\n"); + exit(1); + } + +int +main(argc,argv) +int argc; +char *argv[]; +{ + int fa, nfa, mfa; /* argument we're looking at */ + int verb = 0; + int hflag = 0; /* Hexagon patches for SS, high density for CM */ + double pscale = 1.0; /* Patch size scale */ + double sscale = 1.0; /* Spacer size scale */ + int rand = 1; + int qbits = 0; /* Quantization bits */ + int oft = 0; /* Ouput File type, 0 = PS, 1 = EPS , 2 = TIFF */ + depth2d tiffdpth = bpc8_2d; /* TIFF pixel depth */ + double tiffres = 100.0; /* TIFF resolution in DPI */ + int ncha = 0; /* flag, use nchannel alpha */ + int tiffdith = 0; /* flag, use TIFF 8 bit dithering */ + int tiffcomp = 1; /* flag, use TIFF compression */ + int spacer = -1; /* -1 = default for instrument */ + /* 0 = forse no spacer, 1 = Force B&W spacers */ + /* 2 = Force colored spacer */ + int rstart = -1; /* Random sequence start value */ + char *sixpat = DEF_SIXPAT; /* Strip index pattern */ + char *pixpat = DEF_PIXPAT; /* Patch index pattern */ + alphix *saix, *paix; /* Strip and Patch index generators */ + int ixord = 0; /* Index order, 0 = strip then patch */ + int scanc = 0; /* Scan compatible bits, 1 = .cht, 2 = wide first row */ + int devnfb = 0; /* Add device N fallback colors */ + int altrep = 0; /* Device K/W/CMY color type 0..8 */ + int applycal = 0; /* NZ to apply calibration */ + static char inname[MAXNAMEL+20] = { 0 }; /* Input cgats file name */ + static char calname[MAXNAMEL+1] = { 0 }; /* Input printer calibration */ + static char psname[MAXNAMEL+1] = { 0 }; /* Output postscrip file base name */ + static char outname[MAXNAMEL+20] = { 0 }; /* Output cgats file name */ + cgats *icg; /* input cgats structure */ + cgats *ocg; /* output cgats structure */ + xcal *cal = NULL; /* printer calibration */ + instType itype = instDTP41; /* Default target instrument */ + int nmask = 0; /* Device colorant mask */ + int nchan = 0; /* Number of device chanels */ + int i; + int si, fi, wi; /* sample id index, field index, keyWord index */ + char label[400]; /* Space for chart label */ + double marg = DEF_MARGINE; /* Margin from paper edge in mm */ + int nosubmarg = 0; /* Don't subtract it from raster */ + int nolpcbord = 0; /* NZ to suppress left paper clip border */ + int nollimit = 0; /* NZ to release any strip length limits */ + paper *pap = NULL; /* Paper size pointer, NULL if custom */ + double cwidth, cheight; /* Custom paper width and height in mm */ + col *cols; /* test patch colors */ + int npat; /* Number of patches */ + int nppat; /* Number of patches including padding */ + col pcold[8]; /* pre-defined density extreme colors */ + int pcolvv = 0; /* pcolv valid if nz */ + col pcolv[9]; /* pre-defined device combination ecolors */ + col *pcol; /* Chosen color patches for device */ + double wp[3]; /* Approximate XYZ white point */ + time_t clk = time(0); + struct tm *tsp = localtime(&clk); + char *atm = asctime(tsp); /* Ascii time */ + int sip; /* Steps in Pass */ + unsigned char *pis; /* Passes in strip array */ + double plen, glen, tlen;/* Patch, gap and trailer length in mm */ + char *bp, buf[500]; /* general sprintf buffer */ + + error_program = "printtarg"; + check_if_not_interactive(); + +#if defined(__IBMC__) + _control87(EM_UNDERFLOW, EM_UNDERFLOW); + _control87(EM_OVERFLOW, EM_OVERFLOW); +#endif + + if (argc <= 1) + usage("Not enough arguments"); + +#ifdef DEBUG + printf("target: DEBUG is #defined\n"); +#endif + + /* Find the default paper size */ + for (pap = psizes; pap->name != NULL; pap++) { + if (pap->def != 0) + break; + } + if (pap->name == NULL) + error ("Internal - can't find default paper size"); + + /* 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("Requested usage"); + + /* Verbosity */ + else if (argv[fa][1] == 'v' || argv[fa][1] == 'V') + verb = 1; + + /* hflag patches */ + else if (argv[fa][1] == 'h' || argv[fa][1] == 'H') + hflag = 1; + + /* Patch scaling */ + else if (argv[fa][1] == 'a') { + fa = nfa; + if (na == NULL) usage("Expected scale factor to -a"); + pscale = atof(na); + if (pscale < 0.1 || pscale > 4.0) + usage("Scale factor %f is outside expected range 0.1 - 4.0",pscale); + } + + /* Spacer scaling */ + else if (argv[fa][1] == 'A') { + fa = nfa; + if (na == NULL) usage("Expected scale factor to -a"); + sscale = atof(na); + if (sscale < 0.1 || sscale > 8.0) + usage("Scale factor %f is outside expected range 0.1 - 8.0",sscale); + } + + /* Scan compatible */ + else if (argv[fa][1] == 's') + scanc = 3; + + else if (argv[fa][1] == 'S') + scanc = 1; + + /* Force colored spacer */ + else if (argv[fa][1] == 'c') + spacer = 2; + + /* Force B&W spacer */ + else if (argv[fa][1] == 'b' || argv[fa][1] == 'B') + spacer = 1; + + /* No spacer */ + else if (argv[fa][1] == 'n') + spacer = 0; + + /* Randomisation off */ + else if (argv[fa][1] == 'r') + rand = 0; + + /* Specify random seed */ + else if (argv[fa][1] == 'R') { + fa = nfa; + if (na == NULL) usage("Expected argument to -R"); + rstart = atoi(na); + if (rstart < 0) + usage("Argument to -R must be positive"); + } + + /* Enable DeviceN color fallback */ + else if (argv[fa][1] == 'f' || argv[fa][1] == 'F') + devnfb = 1; + + /* Select the printer W color representation */ + else if (argv[fa][1] == 'w' || argv[fa][1] == 'W') { + fa = nfa; + if (na == NULL) usage("Expected argument to -w"); + switch(na[0]) { + case 'g': + case 'G': + altrep = 0; + break; + case 'r': + case 'R': + altrep = 4; + break; + case 's': + case 'S': + altrep = 5; + break; + case 'n': + case 'N': + altrep = 6; + break; + default: + usage("Unexpected argument to -w"); + } + } + + /* Select the printer K color representation */ + else if (argv[fa][1] == 'k') { + fa = nfa; + if (na == NULL) usage("Expected argument to -k"); + switch(na[0]) { + case 'g': + case 'G': + altrep = 0; + break; + case 'c': + case 'C': + altrep = 1; + break; + case 's': + case 'S': + altrep = 2; + break; + case 'n': + case 'N': + altrep = 3; + break; + default: + usage("Unexpected argument to -k"); + } + } + + /* Select the printer CMY color representation */ + else if (argv[fa][1] == 'o') { + fa = nfa; + if (na == NULL) usage("Expected argument to -o"); + switch(na[0]) { + case 'k': + case 'K': + altrep = 0; + break; + case 'r': + case 'R': + altrep = 7; + break; + case 'n': + case 'N': + altrep = 8; + break; + default: + usage("Unexpected argument to -o"); + } + } + + /* EPS */ + else if (argv[fa][1] == 'e' || argv[fa][1] == 'E') + oft = 1; + + /* TIFF */ + else if (argv[fa][1] == 't' || argv[fa][1] == 'T') { + oft = 2; + if (argv[fa][1] == 'T') + tiffdpth = bpc16_2d; + else + tiffdpth = bpc8_2d; + + if (na != NULL) { /* Found an optional resolution */ + fa = nfa; + tiffres = atof(na); + if (tiffres <= 1.0 || tiffres > 1e6) + usage("TIFF resolution is out of range"); + } + } + /* use Nchannel alpha for TIFF */ + else if (argv[fa][1] == 'N') { + ncha = 1; + } + /* use 16->8 bit dithering for 8 bit TIFF */ + else if (argv[fa][1] == 'D') { + tiffdith = 1; + } + /* Don't use TIFF compression */ + else if (argv[fa][1] == 'C') { + tiffcomp = 0; + } + + /* Specify quantization bits */ + else if (argv[fa][1] == 'Q') { + fa = nfa; + if (na == NULL) usage("Expected argument to -Q"); + qbits = atoi(na); + if (qbits < 1 || qbits > 32) + usage("Argument to -Q must be between 1 and 32"); + } + + /* Specify strip index pattern */ + else if (argv[fa][1] == 'x' || argv[fa][1] == 'X') { + fa = nfa; + if (na == NULL) usage("Expected argument to -x"); + sixpat = na; + } + + /* Specify patch index pattern */ + else if (argv[fa][1] == 'y' || argv[fa][1] == 'Y') { + fa = nfa; + if (na == NULL) usage("Expected argument to -y"); + pixpat = na; + } + + /* Border margin */ + else if (argv[fa][1] == 'm' || argv[fa][1] == 'M') { + if (argv[fa][1] == 'M') + nosubmarg = 1; + fa = nfa; + if (na == NULL) usage("Expected border margine argument to -m"); + marg = atof(na); + if (marg < 0.0 || marg > 50.0) + usage("Border margin %f is outside expected range",marg); + } + + /* Don't limit the strip length */ + else if (argv[fa][1] == 'P') { + nollimit = 1; + } + + /* Suppress left paper clip border */ + else if (argv[fa][1] == 'L') { + nolpcbord = 1; + } + + /* Page size */ + else if (argv[fa][1] == 'p') { + fa = nfa; + if (na == NULL) usage("Expected an argument to -p"); + for (pap = psizes; pap->name != NULL; pap++) { + if (cistrcmp(na, pap->name) == 0) + break; + } + + if (pap->name == NULL) { /* See if it matches a custom size */ + if (sscanf(na,"%lfx%lf",&cwidth, &cheight) == 2) { + pap = NULL; /* Indicate custom */ + if (cwidth < 1.0 || cwidth > 4000.0 + || cheight < 1.0 || cheight > 4000.0) + usage("Argument to -p was of unexpected size"); /* Sanity check */ + } else { + usage("Failed to recognise argument to -p"); + } + } + } + /* Target Instrument type */ + else if (argv[fa][1] == 'i') { + fa = nfa; + if (na == NULL) usage("Expected an argument to -i"); + + if (strcmp("20", na) == 0) + itype = instDTP20; + else if (strcmp("22", na) == 0) + itype = instDTP22; + else if (strcmp("41", na) == 0) + itype = instDTP41; + else if (strcmp("51", na) == 0) + itype = instDTP51; + else if (strcmp("SS", na) == 0 || strcmp("ss", na) == 0) + itype = instSpectroScan; + else if (strcmp("i1", na) == 0 || strcmp("I1", na) == 0) + itype = instI1Pro; + else if (strcmp("cm", na) == 0 || strcmp("CM", na) == 0) + itype = instColorMunki; + else + usage("Argument to -i wasn't recognised"); + + /* Printer calibration */ + } else if (argv[fa][1] == 'K' || argv[fa][1] == 'I') { + if (argv[fa][1] == 'K') + applycal = 1; + else + applycal = 0; + fa = nfa; + if (na == NULL) usage("Expected an argument to -%c",argv[fa][1]); + strncpy(calname,na,MAXNAMEL); calname[MAXNAMEL] = '\000'; + } else + usage("Unknown flag"); + } + else + break; + } + + /* Get the file name argument */ + if (fa >= argc || argv[fa][0] == '-') usage("Expecting basename argument"); + strncpy(inname,argv[fa],MAXNAMEL); inname[MAXNAMEL] = '\000'; + strcpy(outname,inname); + strcpy(psname,inname); + strcat(inname,".ti1"); + strcat(outname,".ti2"); + + if (calname[0] != '\000') { + if ((cal = new_xcal()) == NULL) + error("new_xcal failed"); + if ((cal->read(cal, calname)) != 0) + error("%s",cal->err); + } + + /* Set default qantization for known output */ + if (qbits == 0 && oft == 2) { + if (tiffdpth == bpc16_2d || tiffdith != 0) + qbits = 16; + else if (tiffdpth == bpc8_2d) + qbits = 8; + } + + if (itype == instSpectroScan) { + if (scanc) { + if (verb) + printf("Can only select hexagonal patches if no scan recognition is needed - ignored!\n"); + hflag = 0; + } + } else if (itype == instColorMunki) { + /* OK */ + } else if (hflag) { + if (verb) + printf("Can only select h flag for SpectrScan or ColorMunki - ignored!\n"); + hflag = 0; + } + + if ((saix = new_alphix(sixpat)) == NULL) + error("Strip indexing pattern '%s' doesn't parse",sixpat); + + if ((paix = new_alphix(pixpat)) == NULL) + error("Patch in strip indexing pattern '%s' doesn't parse",pixpat); + + icg = new_cgats(); /* Create a CGATS structure */ + icg->add_other(icg, "CTI1"); /* our special input type is Calibration Target Information 1 */ + + if (icg->read_name(icg, inname)) + error("CGATS file read error : %s",icg->err); + + if (icg->t[0].tt != tt_other || icg->t[0].oi != 0) + error ("Input file isn't a CTI1 format file"); + if (icg->ntables < 2 || icg->ntables > 3) + error ("Input file doesn't contain two or three tables"); + + if ((npat = icg->t[0].nsets) <= 0) + error ("No sets of data"); + + /* Allocate room for test patches and maximum padding patches */ + if ((cols = (col *)malloc(sizeof(col) * (npat + MAXPPROW))) == NULL) + error("Malloc failed!"); + + /* Setup output cgats file */ + ocg = new_cgats(); /* Create a CGATS structure */ + ocg->add_other(ocg, "CTI2"); /* our special type is Calibration Target Information 2 */ + ocg->add_table(ocg, tt_other, 0); /* Start the first table */ + + ocg->add_kword(ocg, 0, "DESCRIPTOR", "Argyll Calibration Target chart information 2",NULL); + ocg->add_kword(ocg, 0, "ORIGINATOR", "Argyll printtarg", NULL); + atm[strlen(atm)-1] = '\000'; /* Remove \n from end */ + ocg->add_kword(ocg, 0, "CREATED",atm, NULL); + + /* Note what instrument the chart is setup for */ + ocg->add_kword(ocg, 0, "TARGET_INSTRUMENT", inst_name(itype) , NULL); + + /* Copy various parameters through */ + if ((wi = icg->find_kword(icg, 0, "SINGLE_DIM_STEPS")) >= 0) + ocg->add_kword(ocg, 0, "SINGLE_DIM_STEPS",icg->t[0].kdata[wi], NULL); + + if ((wi = icg->find_kword(icg, 0, "COMP_GREY_STEPS")) >= 0) + ocg->add_kword(ocg, 0, "COMP_GREY_STEPS",icg->t[0].kdata[wi], NULL); + + if ((wi = icg->find_kword(icg, 0, "MULTI_DIM_STEPS")) >= 0) + ocg->add_kword(ocg, 0, "MULTI_DIM_STEPS",icg->t[0].kdata[wi], NULL); + + if ((wi = icg->find_kword(icg, 0, "FULL_SPREAD_PATCHES")) >= 0) + ocg->add_kword(ocg, 0, "FULL_SPREAD_PATCHES",icg->t[0].kdata[wi], NULL); + + if ((wi = icg->find_kword(icg, 0, "ACCURATE_EXPECTED_VALUES")) >= 0) + ocg->add_kword(ocg, 0, "ACCURATE_EXPECTED_VALUES",icg->t[0].kdata[wi], NULL); + + /* Fields we want */ + ocg->add_field(ocg, 0, "SAMPLE_ID", nqcs_t); + ocg->add_field(ocg, 0, "SAMPLE_LOC", cs_t); + + if ((si = icg->find_field(icg, 0, "SAMPLE_ID")) < 0) + error ("Input file '%s' doesn't contain field SAMPLE_ID in first table",inname); + if (icg->t[0].ftype[si] != nqcs_t) + error ("Field SAMPLE_ID is wrong type"); + + /* Read the approximate white point */ + if ((fi = icg->find_kword(icg, 0, "APPROX_WHITE_POINT")) < 0) + error ("Input file doesn't contain keyword APPROX_WHITE_POINT"); + if (sscanf(icg->t[0].kdata[fi], "%lf %lf %lf", &wp[0], &wp[1], &wp[2]) != 3) + error ("Couldn't parse the white point data correctly"); + wp[0] /= 100.0; wp[1] /= 100.0; wp[2] /= 100.0; + ocg->add_kword(ocg, 0, "APPROX_WHITE_POINT",icg->t[0].kdata[fi], NULL); + +//printf("~1 got approx white point of %f %f %f\n",wp[0],wp[1],wp[2]); + + /* Figure out the color space */ + if ((fi = icg->find_kword(icg, 0, "COLOR_REP")) < 0) + error ("Input file '%s' doesn't contain keyword COLOR_REPS",inname); + + if ((nmask = icx_char2inkmask(icg->t[0].kdata[fi])) != 0) { + int i, j, ii; + int chix[ICX_MXINKS]; /* Device chanel indexes */ + int xyzix[3]; /* XYZ chanel indexes */ + char *ident; /* Full ident */ + char *bident; /* Base ident */ + char *xyzfname[3] = { "XYZ_X", "XYZ_Y", "XYZ_Z" }; + double qscale = (1 << qbits) - 1.0; + + if (cal != NULL && nmask != cal->devmask) + error ("Calibration colorspace %s doesn't match .ti1 %s",icx_inkmask2char(cal->devmask, 1),icx_inkmask2char(nmask, 1)); + + if ((ii = icg->find_kword(icg, 0, "TOTAL_INK_LIMIT")) >= 0) + ocg->add_kword(ocg, 0, "TOTAL_INK_LIMIT",icg->t[0].kdata[ii], NULL); + + nchan = icx_noofinks(nmask); + ident = icx_inkmask2char(nmask, 1); + bident = icx_inkmask2char(nmask, 0); + + for (j = 0; j < nchan; j++) { + int 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 = icg->find_field(icg, 0, fname)) < 0) + error ("Input file '%s' doesn't contain field %s in first table",inname,fname); + if (icg->t[0].ftype[ii] != r_t) + error ("Field %s is wrong type",fname); + + ocg->add_field(ocg, 0, fname, r_t); + chix[j] = ii; + } + + for (j = 0; j < 3; j++) { + if ((ii = icg->find_field(icg, 0, xyzfname[j])) < 0) + error ("Input '%s' file doesn't contain field %s in first table",inname,xyzfname[j]); + if (icg->t[0].ftype[ii] != r_t) + error ("Field %s is wrong type",xyzfname[j]); + + ocg->add_field(ocg, 0, xyzfname[j], r_t); + xyzix[j] = ii; + } + + ocg->add_kword(ocg, 0, "COLOR_REP", ident, NULL); + + /* Read all the test patches in, and quantize them */ + for (i = 0; i < npat; i++) { + cols[i].i = i; + cols[i].t = T_N | T_XYZ; + if (devnfb) + cols[i].t |= T_NFB; + cols[i].nmask = nmask; + cols[i].altrep = altrep; + cols[i].n = nchan; + cols[i].id = ((char *)icg->t[0].fdata[i][si]); + sprintf(cols[i].loc, "???"); + for (j = 0; j < nchan; j++) { + double vr, vv = *((double *)icg->t[0].fdata[i][chix[j]]) / 100.0; + if (qbits > 0) { + vv *= qscale; + vr = floor(vv + 0.5); + if ((vr - vv) == 0.5 && (((int)vr) & 1) != 0) /* Round to even */ + vr -= 1.0; + vv = vr/qscale; + } + cols[i].dev[j] = vv; + } + for (j = 0; j < 3; j++) + cols[i].XYZ[j] = *((double *)icg->t[0].fdata[i][xyzix[j]]) / 100.0; + col_convert(&cols[i], wp); /* Ensure other representations */ + } + + free(ident); + free(bident); + } else + error ("Input file keyword COLOR_REPS has unknown value"); + + /* Load up the pre-defined density extreme spacer colors */ + { + int i, j, ii; + int nsp; + int chix[ICX_MXINKS]; /* Device chanel indexes */ + int xyzix[3]; /* XYZ chanel indexes */ + char *bident; + char *xyzfname[3] = { "XYZ_X", "XYZ_Y", "XYZ_Z" }; + + nchan = icx_noofinks(nmask); + bident = icx_inkmask2char(nmask, 0); + + if ((nsp = icg->t[1].nsets) <= 0) + error ("No sets of data in second table"); + + for (j = 0; j < nchan; j++) { + int 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 = icg->find_field(icg, 1, fname)) < 0) + error ("Input file '%s' doesn't contain field %s in second table",inname,fname); + if (icg->t[1].ftype[ii] != r_t) + error ("Field %s is wrong type",fname); + chix[j] = ii; + } + + for (j = 0; j < 3; j++) { + if ((ii = icg->find_field(icg, 1, xyzfname[j])) < 0) + error ("Input file '%s' doesn't contain field %s in second table",inname,xyzfname[j]); + if (icg->t[1].ftype[ii] != r_t) + error ("Field %s is wrong type",xyzfname[j]); + xyzix[j] = ii; + } + + if (nsp != 8) + error ("Expect second set of data to have 8 sets, found %d",nsp); + + /* Read all the density spacer patches in */ + for (i = 0; i < nsp; i++) { + pcold[i].i = -1; + pcold[i].t = T_N | T_XYZ | T_PRESET; + if (devnfb) + pcold[i].t |= T_NFB; + pcold[i].nmask = nmask; + pcold[i].altrep = altrep; + pcold[i].n = nchan; + pcold[i].id = ""; + sprintf(cols[i].loc, "???"); + for (j = 0; j < nchan; j++) + pcold[i].dev[j] = *((double *)icg->t[1].fdata[i][chix[j]]) / 100.0; + for (j = 0; j < 3; j++) + pcold[i].XYZ[j] = *((double *)icg->t[1].fdata[i][xyzix[j]]) / 100.0; + col_convert(&pcold[i], wp); /* Ensure other representations */ + } + + free(bident); + } + + /* Load up the pre-defined device combination barcode colors */ + if (icg->ntables >= 3) { + int i, j, ii; + int nsp; + int chix[ICX_MXINKS]; /* Device chanel indexes */ + int xyzix[3]; /* XYZ chanel indexes */ + char *bident; /* Base ident */ + char *xyzfname[3] = { "XYZ_X", "XYZ_Y", "XYZ_Z" }; + + nchan = icx_noofinks(nmask); + bident = icx_inkmask2char(nmask, 0); + + if ((nsp = icg->t[2].nsets) > 0) { + + for (j = 0; j < nchan; j++) { + int 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 = icg->find_field(icg, 2, fname)) < 0) + error ("Input file '%s' doesn't contain field %s in third table",inname,fname); + if (icg->t[2].ftype[ii] != r_t) + error ("Field %s is wrong type",fname); + chix[j] = ii; + } + + for (j = 0; j < 3; j++) { + if ((ii = icg->find_field(icg, 2, xyzfname[j])) < 0) + error ("Input file '%s' doesn't contain field %s in third table",inname,xyzfname[j]); + if (icg->t[2].ftype[ii] != r_t) + error ("Field %s is wrong type",xyzfname[j]); + xyzix[j] = ii; + } + + if (nsp != 9) + error ("Expect third set of data to have 9 sets, found %d",nsp); + + /* Read all the barcode CMY color patches in */ + for (i = 0; i < nsp; i++) { + pcolv[i].i = -1; + pcolv[i].t = T_N | T_XYZ | T_PRESET; + if (devnfb) + pcolv[i].t |= T_NFB; + pcolv[i].nmask = nmask; + pcolv[i].altrep = altrep; + pcolv[i].n = nchan; + pcolv[i].id = ""; + sprintf(cols[i].loc, "???"); + for (j = 0; j < nchan; j++) + pcolv[i].dev[j] = *((double *)icg->t[2].fdata[i][chix[j]]) / 100.0; + for (j = 0; j < 3; j++) + pcolv[i].XYZ[j] = *((double *)icg->t[2].fdata[i][xyzix[j]]) / 100.0; + col_convert(&pcolv[i], wp); /* Ensure other representations */ + } + + free(bident); + pcolvv = 1; + } + } + + if (verb) { + if (pap != NULL) + printf("Paper chosen is %s [%.1f x %.1f mm]\n", pap->name, pap->w, pap->h); + else + printf("Paper chosen is custom %.1f x %.1f mm\n", cwidth, cheight); + } + + if (rstart == -1) { + rstart = clk % npat; + } else { + rstart = rstart % npat; + } + sprintf(buf,"%d",rstart); + if (rand) + ocg->add_kword(ocg, 0, "RANDOM_START", buf, NULL); + else + ocg->add_kword(ocg, 0, "CHART_ID", buf, NULL); + + if (itype == instSpectroScan && hflag) { + ocg->add_kword(ocg, 0, "HEXAGON_PATCHES", "True", NULL); + } + + if (itype == instDTP20) { + if (pcolvv == 0) + error("Input file '%s' doesn't contain device combination table needed for DTP20",inname); + pcol = pcolv; /* Barcode color values */ + } else + pcol = pcold; /* Density spacer alues */ + + + sprintf(label, "Argyll Color Management System - Test chart \"%s\" (%s %d) %s", + psname, rand ? "Random Start" : "Chart ID", rstart, atm); + generate_file(itype, psname, cols, npat, applycal ? cal : NULL, label, + pap != NULL ? pap->w : cwidth, pap != NULL ? pap->h : cheight, + marg, nosubmarg, nollimit, nolpcbord, rand, rstart, saix, paix, ixord, + pscale, sscale, hflag, verb, scanc, oft, tiffdpth, tiffres, ncha, tiffdith, + tiffcomp, spacer, nmask, altrep, pcol, wp, + &sip, &pis, &plen, &glen, &tlen, &nppat); + + if (itype == instDTP20 + || itype == instDTP41) { /* DTP20/41 needs this */ + sprintf(buf,"%f",plen); + ocg->add_kword(ocg, 0, "PATCH_LENGTH", buf, NULL); + sprintf(buf,"%f",glen); + ocg->add_kword(ocg, 0, "GAP_LENGTH", buf, NULL); + if (itype == instDTP41) { /* DTP41 needs this */ + sprintf(buf,"%f",tlen); + ocg->add_kword(ocg, 0, "TRAILER_LENGTH", buf, NULL); + } + } + + sprintf(buf,"%d",sip); + ocg->add_kword(ocg, 0, "STEPS_IN_PASS", buf, NULL); + + /* Convert pass in strips count to base 62 */ + buf[0] = '\000'; + bp = buf; + for (i = 0; ;i++) { + if (pis[i] == 0) + break; + sprintf(bp, "%s%d", i > 0 ? "," : "", pis[i]); + bp += strlen(bp); + } + ocg->add_kword(ocg, 0, "PASSES_IN_STRIPS2", buf, NULL); + + /* Output the default Argyll style strip and patch numbering */ + 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); + + /* Write out the patch info to the output CGATS file */ + for (i = 0; i < nppat; i++) { + cgats_set_elem ary[2 + ICX_MXINKS + 3]; + int j; + + if (strcmp(cols[i].loc, "???") == 0) + warning ("Internal, patch %s (%d) wasn't given a valid location string",cols[i].id,i+1); + ary[0].c = cols[i].id; + ary[1].c = cols[i].loc; + for (j = 0; j < nchan; j++) + ary[2 + j].d = 100.0 * cols[i].dev[j]; + for (j = 0; j < 3; j++) + ary[2 + nchan + j].d = 100.0 * cols[i].XYZ[j]; + ocg->add_setarr(ocg, 0, ary); + } + + /* If there is a calibration, append it to the .ti2 file */ + if (cal != NULL) { + if (cal->write_cgats(cal, ocg) != 0) + error("Writing cal error : %s",cal->err); + } + + if (ocg->write_name(ocg, outname)) + error("Write error : %s",ocg->err); + + if (cal != NULL) + cal->del(cal); + paix->del(paix); + saix->del(saix); + free(pis); + free(cols); + ocg->del(ocg); /* Clean up */ + icg->del(icg); /* Clean up */ + + return 0; +} + +/******************************************************************/ +/* Edge tracking support, for generating the scanner image */ +/* recognition reference chart file. */ + +/* Establish width and height to convert between topleft and */ +/* bottom left origin ??? ~~9 */ + +/* + Basic algorithm strategy: + + First we simply accumulate the raw recognition and patch + identification information. Once the chart is generated, we: + sort into horizontal and vertical half edges + sort into +ve and -ve edges + match +ve and -ve edges + for each match, generate a delta edge segment + Assume any non-matched edges are against the media. + Coalesce delta edges into X & Y edge lists + Compute normalised strength. + Compute crossing count. + Figure average box size, and compute shrink. + +*/ + +/* A half edge structure */ +/* coordinate origin is top left */ +struct _hedge { + double rgb[3]; /* Color this half edge transitions to */ + int negh; /* 1 if this is a -ve major coordinate side half edge, 0 otherwise */ + double mj; /* Major coordinate offset (ie. X coord for vertical edge) */ + double mi0; /* Minor coordinate smaller value (ie. Y for vertical edge) */ + double mi1; /* Minor coordinate larger value (ie. Y for vertical edge) */ + struct _hedge *next; /* Next in linked list */ +}; typedef struct _hedge hedge; + +/* A patch identifier */ +/* coordinate origin is top left */ +struct _patch { + char id[20]; /* ID string, Zeri length if a diagnostic rectangle */ + double xo; /* Location of the rectangle origin (bottom left ???) */ + double yo; + double w; /* Size of the patch */ + double h; + struct _patch *next; /* Next in linked list */ +}; typedef struct _patch patch; + + +/* Structure to one edge */ +struct _edge { + double p1,p2; /* Start and end of line in orthogonal direction */ + struct _edge *next; /* next in the linked list */ +}; typedef struct _edge edge; + +/* Structure of an edge list */ +struct _elist { + double pos; /* Position of edges along major axis */ + double len; /* Total length of edges atthis position */ + double cc; /* Crossing count */ + int ne; /* Count of edges */ + edge *e; /* Head of linked list of edges at this position */ + struct _elist *next; /* Next in linked list */ +}; typedef struct _elist elist; + +/* - - - - - - - - - - - - - - - - - - - */ +/* Structure to track recognition edges */ +struct { + double height; /* Height of the page */ + double mrgb[3]; /* Media RGB */ + double rgb[3]; /* Currently set RGB */ + + int nfid; /* Number of fiducial marks. Must be 4 to cause output */ + double fx[4]; /* Fiducial mark X coordinates */ + double fy[4]; /* Fiducial mark Y coordinates */ + + /* Raw half edge lists, [vertical, horizontal] */ + int nhe[2]; + hedge *he[2]; + + /* Patch identity information */ + int npatches; + patch *patches; + + /* Processed information */ + hedge **she[2]; /* Sorted half edges */ + + int nel[2]; /* Number of edge positions */ + elist *el[2]; /* Head of edge linked list */ + elist **nelp; /* Next edge to append to */ +} et; + +/* Initialise the structure */ +void et_init(void) { + memset(&et, 0, sizeof(et)); +} + +/* Tell et of the height, so the Y coordinate can be flipped */ +void et_height(double height) { + et.height = height; +//printf("~1 media height set to %f\n",height); +} + +/* Tell et of the media color */ +void et_media(double *rgb) { + int e; + for (e = 0; e < 3; e++) + et.mrgb[e] = rgb[e]; +} + +/* Track the current GC color */ +void et_color( +double *rgb /* New RGB values */ +) { + int e; + for (e = 0; e < 3; e++) + et.rgb[e] = rgb[e]; +} + +/* Track a drawn object half edge */ +/* We assume that no object is written over any other object, */ +/* and that each half edge has a perfect opposite edge (ie. same */ +/* mi0 and mi1), or no matching half edge (it is over the media) - */ +/* ie. no partialy overlapping half edges. */ +/* The arguments origin is assumed to be bottom left */ +void et_edge( +int isx, /* NZ if this is a vertical edge */ +int negh, /* NZ if this is a -ve major coordinate side half edge */ +double mj, /* Major coordinate offset (ie. X coord for vertical edge) */ +double mi0, /* Minor coordinate smaller value (ie. Y for vertical edge) */ +double mi1 /* Minor coordinate larger value (ie. Y for vertical edge) */ +) { + int e, h; + hedge *he; + + if (mi1 < mi0) + error ("et_edge, minor coords wern't ordered"); + + if ((he = (hedge *)calloc(sizeof(hedge), 1)) == NULL) + error("Malloc of half edge structure failed"); + + for (e = 0; e < 3; e++) + he->rgb[e] = et.rgb[e]; + + /* Flip the Y coordinate */ + if (isx) { + double tmi0, tmi1; + tmi0 = et.height - mi1; /* swap to keep smallest small */ + tmi1 = et.height - mi0; + mi0 = tmi0; + mi1 = tmi1; + } else { + mj = et.height - mj; + } + + he->negh = negh ? 1 : 0; + he->mj = mj; + he->mi0 = mi0; + he->mi1 = mi1; + + /* Add half edges to the list */ + h = isx ? 0 : 1; + et.nhe[h]++; + he->next = et.he[h]; + et.he[h] = he; +} + +/* Track a patch identity */ +/* The arguments origin is assumed to be bottom left */ +void et_patch( +char *id, /* ID string, NULL if a diagnostic mark */ +double xo, /* Bottom left of the rectangle */ +double yo, +double w, /* Size of the patch */ +double h +) { + patch *p; + +//printf("~1 got patch at %f %f, w %f, h %f\n", xo, yo, w, h); + + if ((p = (patch *)calloc(sizeof(patch), 1)) == NULL) + error("Malloc of patch structure failed"); + + /* Flip Y */ + yo = et.height - (yo + h); + + if (id != NULL) { + strncpy(p->id, id, 19); + p->id[19] = '\000'; + } else { + p->id[0] = '\000'; + } + p->xo = xo; + p->yo = yo; + p->h = h; + p->w = w; + + /* Add patch to list */ + et.npatches++; + p->next = et.patches; + et.patches = p; +} + +/* Add a fiducial mark location */ +/* It is an error to add more than 4, */ +/* and exactly 4 have to be added to cause fiducials to be output. */ +void et_fiducial( +double x, /* Bottom left of the rectangle */ +double y +) { + if (et.nfid >= 4) + error("et_fiducial: too many fiducial marks"); + et.fx[et.nfid] = x; + et.fy[et.nfid] = et.height - y; /* Flip Y */ + et.nfid++; +} + +/* Compute the image recognition information, and write the */ +/* .cht file. */ +void et_write(char *fname, col *cols, int *rix, int si, int ei) { + FILE *of; + hedge *ep; + int i, h; + +//printf("~1 et has %d vertical and %d horizontal half edges\n", et.nhe[0], et.nhe[1]); +//printf("~1 et has %d patches\n", et.npatches); + + for (h = 0; h < 2; h++) { + /* Create sorted list of vertical half edges */ + if ((et.she[h] = (hedge **)malloc(sizeof(patch*) * et.nhe[h])) == NULL) + error("Malloc of array of vertical halfedge pointers failed"); + + for (ep = et.he[h], i = 0; i < et.nhe[h]; i++, ep = ep->next) + et.she[h][i] = ep; + + /* Sort helf edges by their X location, then their Y0 location */ +#define HEAP_COMPARE(A,B) (fabs(A->mj - B->mj) < 1e-6 ? A->mi0 < B->mi0 : A->mj < B->mj) + HEAPSORT(hedge *, et.she[h], et.nhe[h]); +#undef HEAP_COMPARE + +#ifdef NEVER +for (i = 0; i < et.nhe[h]; i++) { +printf("%s %d at %c = %f from %c = %f to %f\n", +h == 0 ? "Vert" : "Horiz", i, +h == 0 ? 'X' : 'Y', et.she[h][i]->mj, +h == 0 ? 'Y' : 'X', et.she[h][i]->mi0, et.she[h][i]->mi1); +} +#endif /* NEVER */ + + et.nel[h] = 0; + et.nelp = &et.el[h]; /* Append next edge list here */ + *et.nelp = NULL; /* No edge list at this position yet */ + + /* Create the edge list information */ + for (i = 0; i < et.nhe[h];) { + int j, ii, nj; + double *rgb = NULL; /* Contrast RGB */ + elist *el; /* Current elist */ + + el = *et.nelp; + + /* Locate the end of the half edges at the same position */ + for (ii = i; ii < et.nhe[h]; ii++) { + if (fabs(et.she[h][i]->mj - et.she[h][ii]->mj) > 1e-6) + break; + } + +//printf("~1 doing group from %d to %d\n",i, ii); + /* Find half edge pairs */ + /* Note that we assume that the half edges match perfectly, */ + /* or not at all. This will be normaly be the case with targets */ + /* generated by printtarg. */ + for (j = i; j < ii; j = nj, j++) { + int e, k = j+1; + double vv; + + if (k < ii + && et.she[h][j]->negh != et.she[h][k]->negh + && fabs(et.she[h][j]->mi0 - et.she[h][k]->mi0) < 1e-5 + && fabs(et.she[h][j]->mi1 - et.she[h][k]->mi1) < 1e-5) { + /* Found a matching half edge */ + + nj = k; + rgb = et.she[h][k]->rgb; + + } else if (k < ii /* Assert */ + && ( (et.she[h][j]->mi0+1e-6) < et.she[h][k]->mi1 + && et.she[h][j]->mi1 > (et.she[h][k]->mi0+1e-6))) { + + /* Found an overlapping non-matching edge */ + nj = k; + +#ifdef NEVER +fprintf(stderr,"i = %d, j = %d\n",i,j); +fprintf(stderr,"%s %d at %c = %f from %c = %f to %f, half %s\n", +h == 0 ? "Vert" : "Horiz", i, +h == 0 ? 'X' : 'Y', et.she[h][j]->mj, +h == 0 ? 'Y' : 'X', et.she[h][j]->mi0, et.she[h][j]->mi1, +et.she[h][j]->negh ? "Neg" : "Pos"); +fprintf(stderr,"%s %d at %c = %f from %c = %f to %f, half %s\n", +h == 0 ? "Vert" : "Horiz", i, +h == 0 ? 'X' : 'Y', et.she[h][k]->mj, +h == 0 ? 'Y' : 'X', et.she[h][k]->mi0, et.she[h][k]->mi1, +et.she[h][k]->negh ? "Neg" : "Pos"); +#endif /* NEVER */ + error("Internal - half edges don't match"); + + } else { + /* Must be a non-matching edge */ + nj = j; + rgb = et.mrgb; /* Edge must be against media */ + } + + /* Compute vector delta in rgb */ + /* Add entry to edge list */ + for (e = 0, vv = 0.0; e < 3; e++) { + double tt = rgb[e] - et.she[h][j]->rgb[e]; + vv += tt * tt; + } + +//printf("~1 h %d, mj %f, mi %f, vv = %f\n",h, et.she[h][j]->mj, et.she[h][j]->mi0, vv); + /* If edge is of sufficient magnitude */ + // ~~99 + if (vv > 0.2) { + edge *ep; + + /* See if we need to add a first elist for this position */ + if (el == NULL) { /* We do */ + if ((el = (elist *)calloc(sizeof(elist), 1)) == NULL) + error("Malloc of elist structure failed"); + *et.nelp = el; + el->pos = et.she[h][j]->mj; + et.nel[h]++; + } + + /* Add another edge entry */ + if ((ep = (edge *)calloc(sizeof(edge), 1)) == NULL) + error("Malloc of edge structure failed"); + + ep->next = el->e; + ep->p1 = et.she[h][j]->mi0; + ep->p2 = et.she[h][j]->mi1; + + el->e = ep; /* Add to edge list */ + el->ne++; + } + } + + if (el != NULL) { + /* We've done that position, so get ready for next */ + et.nelp = &el->next; /* Append any more positions here */ + *et.nelp = NULL; + } + i = ii; /* Start search for next group here */ + } + } + + /* Figure the crossing count */ + for (h = 0; h < 2; h++) { + int oh = 1 - h; /* The other list */ + elist *el, *pl; /* Current, previous elist */ + + for (pl = NULL, el = et.el[h]; el != NULL; pl = el, el = el->next) { + edge *ep; + double pp, np; /* Window in pos direction for crossing */ + + if (pl != NULL) + pp = (pl->pos + el->pos)/2.0; /* Half distance to next line */ + else + pp = -1e6; + + if (el->next != NULL) + np = (el->next->pos + el->pos)/2.0; /* Half distance to next line */ + else + np = 1e6; + + /* For each edge on this edge position */ + for (ep = el->e; ep != NULL; ep = ep->next) { + elist *oel; /* Other edge list pointer */ + + /* For each edge in other list */ + for (oel = et.el[oh]; oel != NULL; oel = oel->next) { + edge *oep; + + if (oel->pos < ep->p1 || oel->pos > ep->p2) + continue; /* Other edge doesn't intersect this segment */ + + for (oep = oel->e; oep != NULL; oep = oep->next) { + + /* If crosses on this line within +-0.5 of line each side */ + if (oep->p1 <= np && oep->p2 >= pp) { + el->cc++; /* Count crossing */ + } + + } + } + } + } + } + + /* Compute and normalise the length (strength) & crossing count of each edge */ + for (h = 0; h < 2; h++) { + elist *el; /* Current elist */ + double maxlen = 0.0; + double maxcc = 0.0; + + for (el = et.el[h]; el != NULL; el = el->next) { + edge *ep; + double tlen; + + for (tlen = 0.0, ep = el->e; ep != NULL; ep = ep->next) { + tlen += ep->p2 - ep->p1; + } + el->len = tlen; + if (maxlen < tlen) + maxlen = tlen; + if (maxcc < el->cc) + maxcc = el->cc; + } + + /* Normalise */ + for (el = et.el[h]; el != NULL; el = el->next) { + el->len /= maxlen; + el->cc /= maxcc; + } + } + + /* Output the .cht file */ + if ((of = fopen(fname,"w")) == NULL) + error ("Unable to open output file '%s' for writing",fname); + + fprintf(of,"\n\n"); + fprintf(of, "BOXES %d\n",et.npatches); + + + /* Locate fiducials if they've been added to chart */ + if (et.nfid == 4) { + fprintf(of, " F _ _ %f %f %f %f %f %f %f %f\n", + et.fx[0], et.fy[0], et.fx[1], et.fy[1], et.fx[2], et.fy[2], et.fx[3], et.fy[3]); + } + + { + int fidc = 0; + patch *pp; + double mins; /* Minimum sample box size */ + + for (pp = et.patches; pp != NULL; pp = pp->next) { + + if (pp->id[0] == '\000') { + fprintf(of, " D MARK%d MARK%d _ _ %f %f %f %f 0 0\n", + fidc, fidc, pp->w, pp->h, pp->xo, pp->yo); + fidc++; + } + } + + mins = 1e6; + for (pp = et.patches; pp != NULL; pp = pp->next) { + + if (pp->id[0] != '\000') { + fprintf(of, " X %s %s _ _ %f %f %f %f 0 0\n", + pp->id, pp->id, pp->w, pp->h, pp->xo, pp->yo); + if (mins > pp->w) + mins = pp->w; + if (mins > pp->h) + mins = pp->h; + } + } + fprintf(of,"\n"); + + /* Use a 15% box shrink */ + fprintf(of, "BOX_SHRINK %f\n", mins * 0.15); + fprintf(of,"\n"); + + } + + fprintf(of,"REF_ROTATION %f\n", 0.0); + fprintf(of,"\n"); + + { + elist *el; /* Current elist */ + + fprintf(of,"XLIST %d\n",et.nel[0]); + for (el = et.el[0]; el != NULL; el = el->next) + fprintf(of," %f %f %f\n",el->pos, el->len, el->cc); + fprintf(of,"\n"); + + fprintf(of,"YLIST %d\n",et.nel[1]); + for (el = et.el[1]; el != NULL; el = el->next) + fprintf(of," %f %f %f\n",el->pos, el->len, el->cc); + fprintf(of,"\n"); + + fprintf(of,"\n"); + } + + + fprintf(of, "EXPECTED XYZ %d\n",ei - si); + + for (i = si; i < ei; i++) { + int ix = rix[i]; + fprintf(of, " %s %f %f %f\n", cols[ix].loc, 100.0 * cols[ix].XYZ[0], 100.0 * cols[ix].XYZ[1], 100.0 * cols[ix].XYZ[2]); + } + fprintf(of,"\n"); + + if (fclose(of)) + error ("Unable to close output file '%s'",fname); +} + + +/* Cleanup any allocation */ +void et_clear(void) { + int h; + patch *p; + + et.nfid = 0; + + for (h = 0; h < 2; h++) { + hedge *he; + elist *el; + + /* Free up half edges */ + he = et.he[h]; + while (he != NULL) { + hedge *ne = he->next; + free(he); + he = ne; + } + et.nhe[h] = 0; + et.he[h] = NULL; + + /* Free up sorted half edge lists */ + if (et.she[h] != NULL) + free(et.she[h]); + et.she[h] = NULL; + + /* Free up edge lists and edges */ + el = et.el[h]; + while (el != NULL) { + elist *nel; + edge *ep; + + ep = el->e; + while (ep != NULL) { + edge *nep; + + nep = ep->next; + free(ep); + ep = nep; + } + el->ne = 0; + el->e = NULL; + + nel = el->next; + free(el); + el = nel; + } + et.nel[h] = 0; + et.el[h] = NULL; + } + et.nelp = NULL; + + p = et.patches; + while (p != NULL) { + patch *np = p->next; + free(p); + p = np; + } + et.patches = NULL; + et.npatches = 0; +} + + + + + + + + diff --git a/target/randix.c b/target/randix.c new file mode 100644 index 0000000..bf85764 --- /dev/null +++ b/target/randix.c @@ -0,0 +1,166 @@ + +/* + * Argyll Color Correction System + * + * Random index routines. + * + * Author: Graeme W. Gill + * Date: 5/10/96 + * + * Copyright 1996, 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. + */ + +#include +#include +#include "randix.h" + +#include +void error(char *fmt, ...), warning(char *fmt, ...), verbose(int level, char *fmt, ...); + +/* A table entry structure */ +typedef struct { + int bits; /* Number of bits */ + int length; /* Length of sequence */ + int xorm; /* Xor mask */ +} tabe; + + +static tabe table[] = { + { 1, (1<<1)-1, 0x1}, + { 2, (1<<2)-1, 0x3}, + { 3, (1<<3)-1, 0x3}, + { 4, (1<<4)-1, 0x13}, + { 5, (1<<5)-1, 0x1b}, + { 6, (1<<6)-1, 0x1b}, + { 7, (1<<7)-1, 0x65}, + { 8, (1<<8)-1, 0xc3}, + { 9, (1<<9)-1, 0x1b5}, + { 10, (1<<10)-1, 0x1c7}, + { 11, (1<<11)-1, 0x6bb}, + { 12, (1<<12)-1, 0x5c5}, + { 13, (1<<13)-1, 0x15b9}, + { 14, (1<<14)-1, 0x36d1}, + { 15, (1<<15)-1, 0x376b}, + { 16, (1<<16)-1, 0x5bab}, + { 17, (1<<17)-1, 0x1b5c5}, + { 18, (1<<18)-1, 0x15561}, + { 19, (1<<19)-1, 0x5b4db}, + { 20, (1<<20)-1, 0xf6e01}, + { 21, (1<<21)-1, 0x186517}, + { 22, (1<<22)-1, 0x6bf4f}, + { 23, (1<<23)-1, 0x376623}, + { 24, (1<<24)-1, 0xf54e35}, + { 0, 0, 0} +}; + +#define PSRAND(S, XOV, TBIT, MASK) ((((S) & TBIT) ? (((S) << 1) ^ (XOV)) : ((S) << 1)) & MASK) + +static int +randix_next(struct _randix *p) { + int rv = p->ss-1; /* Return start value first */ + do { + p->ss = PSRAND(p->ss, p->xorm, p->tbit, p->mask); + } while(p->ss >= p->length); + return rv; +} + + +/* Destroy ourselves */ +static void +randix_del(randix *p) { + free (p); +} + +/* Creator */ +randix *new_randix(int length, int start) +{ + int i; + randix *p; + if ((p = (randix *)malloc(sizeof(randix))) == NULL) + error ("randix: malloc failed"); + + p->next = randix_next; + p->del = randix_del; + + if (length == 0) + error ("randix: Can't handle length %d",length); + + start %= length; + + p->length = length+1; + for (i = 0; table[i].bits != 0; i++) { + if (length <= table[i].length) { + p->tbit = 1 << (table[i].bits-1); + p->mask = (p->tbit << 1)-1; + p->xorm = table[i].xorm; + p->ss = start + 1; + return p; + break; + } + } + error ("randix: Can't handle length %d",length); + return NULL; +} + + + + + +#ifdef NEVER +main(argc,argv) +int argc; +char *argv[]; +{ + int length = 11; + int i, iv, cv; + randix *r; + + if (argc > 1) + length = atoi(argv[1]); + + r = new_randix(length, 0); + + iv = r->next(r); + printf("First Val = %d\n",iv); + i = 0; + do { + cv = r->next(r); + i++; + } while (cv != iv); + printf("Last Val = %d\n",cv); + printf("Count for length %d = %d\n",length,i); + return 0; +} + +/* Basic printf type error() and warning() routines */ +void +error(char *fmt, ...) +{ + va_list args; + + fprintf(stderr,"targen: Error - "); + va_start(args, fmt); + vfprintf(stderr, fmt, args); + va_end(args); + fprintf(stderr, "\n"); + fflush(stdout); + exit (-1); +} +#endif /* NEVER */ + + + + + + + + + + + + + diff --git a/target/randix.h b/target/randix.h new file mode 100644 index 0000000..b4c36c0 --- /dev/null +++ b/target/randix.h @@ -0,0 +1,41 @@ + +#ifndef RANDIX_H +/* + * Argyll Color Correction System + * + * Random array indexing class + * + * Author: Graeme W. Gill + * Date: 16/10/96 + * + * Copyright 1996, 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. + */ + +struct _randix { +/* private: */ + int tbit; /* Top bit mask */ + int mask; /* Overall mask */ + int xorm; /* Xor value */ + int length; /* Length needed */ + int ss; /* Current value */ + +/* public: */ + /* return the next in the sequence */ + int (*next)(struct _randix *p); + + /* Destroy ourselves */ + void (*del)(struct _randix *p); + + }; typedef struct _randix randix; + +/* Creator */ +/* Counts withing range 0 to length-1 */ +extern randix *new_randix(int length, int start); + + +#define RANDIX_H +#endif /* RANDIX_H */ diff --git a/target/simdlat.c b/target/simdlat.c new file mode 100644 index 0000000..c5edf36 --- /dev/null +++ b/target/simdlat.c @@ -0,0 +1,1063 @@ + +/* + * Argyll Color Correction System + * + * Device space latice test point generator class, + * set to generate a body centered cubic lattice. + * + * Author: Graeme W. Gill + * Date: 30/8/2004 + * + * Copyright 2002 - 2004 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. + * + * Based on simplat.c + */ + +/* TTBD: + + This is not the most efficient way to generate a body centered + cubic lattice, a simpler grid counter would work faster, without + having to track what points have been generated. + + This seems too inexact/slow to read a specified number of test + points for use in higher dimensions. + + */ + +#undef DEBUG +#undef DUMP_PLOT /* Show on screen plot */ +#define PERC_PLOT 0 /* Emit perceptive space plots */ +#define DO_WAIT 1 /* Wait for user key after each plot */ + + +#include +#include +#include +#include +#if defined(__IBMC__) +#include +#endif +#ifdef DEBUG +#include "plot.h" +#endif +#include "numlib.h" +#include "sort.h" +#include "plot.h" +#include "icc.h" +#include "xcolorants.h" +#include "targen.h" +#include "simdlat.h" + +#if defined(DEBUG) || defined(DUMP_PLOT) +static void dump_image(simdlat *s, int pcp); +static void dump_image_final(simdlat *s, int pcp); +#endif + +#define SNAP_TOL 0.02 /* Snap to gamut boundary tollerance */ +#define MAX_TRIES 30 /* Maximum itterations */ + + +/* ----------------------------------------------------- */ + +/* Default convert the nodes device coordinates into approximate perceptual coordinates */ +/* (usually overriden by caller supplied function) */ +static void +default_simdlat_to_percept(void *od, double *p, double *d) { + simdlat *s = (simdlat *)od; + int e; + + /* Default Do nothing - copy device to perceptual. */ + for (e = 0; e < s->di; e++) { + p[e] = d[e] * 100.0; + } +} + + +#ifdef NEVER /* Not currently used */ +/* Return the largest distance of the point outside the device gamut. */ +/* This will be 0 if inside the gamut, and > 0 if outside. */ +static double +simdlat_in_dev_gamut(simdlat *s, double *d) { + int e; + int di = s->di; + double tt, dd = 0.0; + double ss = 0.0; + + for (e = 0; e < di; e++) { + ss += d[e]; + + tt = 0.0 - d[e]; + if (tt > 0.0) { + if (tt > dd) + dd = tt; + } + tt = d[e] - 1.0; + if (tt > 0.0) { + if (tt > dd) + dd = tt; + } + } + tt = ss - s->ilimit; + if (tt > 0.0) { + if (tt > dd) + dd = tt; + } + return dd; +} +#endif /* NEVER */ + +/* Snap a point to the device gamut boundary. */ +/* Return nz if it has been snapped. */ +static int snap_to_gamut(simdlat *s, double *d) { + int e; + int di = s->di; + double dd; /* Smallest distance */ + double ss; /* Sum */ + int rv = 0; + + /* Snap to ink limit first */ + for (ss = 0.0, e = 0; e < di; e++) + ss += d[e]; + dd = ss - s->ilimit; + + if (dd >= -s->tol) { /* Within tol or beyond limit */ + int j; + for (j = 0; j < di; j++) + d[j] *= s->ilimit/ss; /* Snap to ink limit */ + rv = 1; + } + + /* Now snap to any other dimension */ + for (e = 0; e < di; e++) { + + dd = 0.0 - d[e]; + if (dd >= -s->tol) { + d[e] = 0.0; /* Snap to orthogonal boundary */ + rv = 1; + } + dd = d[e] - 1.0; + if (dd >= -s->tol) { + d[e] = 1.0; /* Snap to orthogonal boundary */ + rv = 1; + } + } + + return rv; +} + +/* Snap a point to the gamut boundary if it is close enough. */ +/* Return 1 if the point has been clipped. */ +/* Return 2 if the point has been clipped by a dia. */ +static int snap_to_gamut2(simdlat *s, double *d) { + int rv = 0; + double ud[MXTD]; + int e, di = s->di; + +//printf("\n~1 snap_to_gamut2() called with %f %f\n",d[0],d[1]); + + for (e = 0; e < di; e++) + ud[e] = d[e]; /* save unclipped location */ + + if (snap_to_gamut(s, d)) { + double tt; + + tt = 0.0; + for (e = 0; e < di; e++) { + double t = ud[e] - d[e]; + tt += t * t; + } + tt = sqrt(tt); +//printf("~1 Got snapped to %f %f by dist %f\n",d[0], d[1], tt); + if (tt > (0.5 * s->dia)) + rv = 2; /* invalid & !explore */ + else + rv = 1; /* Valid & explore */ + } +//else +//printf("~1 Didn't get snapped\n"); + +//printf("~1 snap_to_gamut2() on %f %f returning %d\n",d[0],d[1],rv); + return rv; +} + + +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +/* Compute the equalateral simplex basis vectors */ +static void comp_basis( +simdlat *s, +int type, /* 0 = body centered cubic */ + /* 1 = equilateral simplex */ + /* 2 = face centered cubic */ +double dia, /* Diameter of simplex circumspehere */ +double off, /* Starting offset in device space */ +double angle /* Rotation angle 0.0 - 1.0 */ +) { + int i, j, di = s->di; + double sx[MXTD+1][MXTD]; /* Simplex verticies */ + + switch (type) { + + case SIMDLAT_BCC: + /* Create the node positions for body centered */ + /* cubic latice simplex basis vectors */ + /* Body centered places points at locations where the */ + /* lattice integer coordinates are all even or all odd. */ + for (i = 0; i < (di+1); i++) { + + for (j = 0; j < di; j++) + sx[i][j] = -0.5; + if (i < di) { + if (i > 0) + sx[i][i-1] += dia; + } else { + for (j = 0; j < di; j++) + sx[i][j] += 0.5 * dia; + } + } + break; + + case SIMDLAT_EQSPLX: + /* Create the node positions for the */ + /* equalateral simplex basis vectors */ + for (i = 0; i < (di+1); i++) { + double rr = 1.0; /* Current radius squared */ + double ss = dia / sqrt(3.0); /* Scale */ + + /* The bounding points form a equalateral simplex */ + /* whose vertexes are on a sphere about the data */ + for (j = 0; j < di; j++) { + double ddi; + double hh = 1.0/(di-j); /* Weight for remaining points */ + + if (j > i) + sx[i][j] = 0.0; /* If beyond last */ + else if (j == i) /* If last non-zero */ + sx[i][j] = ss * sqrt(rr); + else /* If before last */ + sx[i][j] = -hh * ss * sqrt(rr); + + ddi = (double)(di - j); + rr *= (ddi * ddi - 1.0)/(ddi * ddi); + } + } + break; + + case SIMDLAT_FCC: + /* Create the node positions for the */ + /* face centered arrangement */ + /* Face centered places points at locations where the */ + /* sum of the lattice integer coordinates is even. */ + for (i = 0; i < (di+1); i++) { + + for (j = 0; j < di; j++) + sx[i][j] = 0.0 * -0.5; + + if (i > 0 && i < di) { + sx[i][i-1] += 0.5 * dia; + sx[i][di-1] += 0.5 * dia; + } else if (i == di) { + sx[i][di-1] += dia; + } + } + break; + } + + /* Apply a rotation to avoid possible alignment with */ + /* the device axes */ + { + int m, k; + int ldi = di-1; /* Last dimension */ + double a, b; + + b = angle; + a = sqrt(1.0 - b * b); + + /* Apply rotation to all except last dimension */ + for (m = 0; m < ldi; m++) { /* Dimension being rotated */ + + for (i = 0; i < (di+1); i++) { /* Node being rotated */ + double out[MXTD]; + + for (j = 0; j < di; j++) { /* Coord being produced */ + out[j] = 0.0; + + for (k = 0; k < di; k++) { /* Coord being used */ + if ((j == m && k == m) + || (j == ldi && k == ldi)) + out[j] += a * sx[i][k]; /* Diagonal multiplier */ + else if (j == m && k == ldi) + out[j] += b * sx[i][k]; + else if (j == ldi && k == m) + out[j] -= b * sx[i][k]; + else if (j == k) + out[j] += sx[i][k]; + } + } + for (j = 0; j < di; j++) + sx[i][j] = out[j]; /* Transfer result */ + } + } + } + +#ifdef DEBUG /* Dump stats on verticies */ +for(i = 0; i < (di+1); i++) { + double val = 0.0; + printf("vert %d = ",i); + for(j = 0; j < di; j++) { + val += sx[i][j] * sx[i][j]; + printf("%f ",sx[i][j]); + } + printf(" (%f)\n",sqrt(val)); +} + +for(i = 0; i < di; i++) { + for (j = i+1; j < (di+1); j++) { + int e; + double val; + + /* Distance between nodes */ + for (val = 0.0, e = 0; e < di; e++) { + double tt = sx[i][e] - sx[j][e]; + val += tt * tt; + } + val = sqrt(val); + printf("dist %d %d = %f\n",i,j,val); + } +} +#endif /* DEBUG */ + + /* Convert from di+1 verticies to di base vectors */ + for (i = 0; i < di; i++) { + for (j = 0; j < di; j++) { + s->bv[i][j] = sx[i+1][j] - sx[i][j]; + } + } + + /* Establish the basis origin */ + { + for (j = 0; j < di; j++) + s->bo[j] = off * s->ilimit/di; + } +} + +/* Compute the hash */ +static int comp_hash( +simdlat *s, +int *x /* Index */ +) { + int j, di = s->di; + unsigned long hash; + + for (hash = 0, j = 0; j < di; j++) + hash = hash * 7 + x[j]; + hash %= SPT_HASHSIZE; + + return hash; +} + +/* Check if a node already exists. Return -1 if not, */ +/* or node index if it does. */ +static int check_exists( +simdlat *s, +int *x, /* Index */ +int hash /* Hash */ +) { + int di = s->di; + int hp; /* node index */ + int j; + + for (hp = s->hash[hash]; hp >= 0; hp = s->nodes[hp].hp) { + + /* Check if we have a match */ + for (j = 0; j < di; j++) { + if (s->nodes[hp].x[j] != x[j]) + break; + } + if (j >= di) + break; /* Found a match */ + } + + return hp; +} + +/* Create a new node. We assume it doesn't already exist */ +/* Return its index */ +static int new_node( +simdlat *s, +int *x, /* Index */ +int hash /* Hash */ +) { + int di = s->di; + int b = 0; /* NZ if a boundary point */ + int nn; /* New node index */ + int hp; /* Hash chain index */ + int i, j; + + /* Make room for it */ + if ((s->np+1) >= s->np_a) { + s->np_a *= 2; + if ((s->nodes = (sdtnode *)realloc(s->nodes, s->np_a * sizeof(sdtnode))) == NULL) + error ("simdlat: node realloc failed"); + } + + nn = s->np++; /* Add the new point */ + + /* Compute the intended device value */ + for (j = 0; j < di; j++) + s->nodes[nn].p[j] = s->bo[j]; + + for (i = 0; i < di; i++) { + for (j = 0; j < di; j++) { + s->nodes[nn].p[j] += x[i] * s->bv[i][j]; /* Sum basis vector product */ + } + } + + /* See whether we are well outside the gamut or not */ + b = snap_to_gamut2(s, s->nodes[nn].p); + + s->percept(s->od, s->nodes[nn].v, s->nodes[nn].p); /* Compute perceptual */ + + /* Store node information */ + for (j = 0; j < di; j++) + s->nodes[nn].x[j] = x[j]; + + s->nodes[nn].b = b; + if (b < 2) { + s->nodes[nn].vald = 1; /* Valid if within or on gamut */ + s->nvp++; /* Got another valid one */ + } else + s->nodes[nn].vald = 0; /* Not valid if it's a boundary point */ + s->nodes[nn].expm[0] = + s->nodes[nn].expm[1] = (1 << di)-1; /* Assum all dimensions need exploring */ + s->nodes[nn].hp = s->nodes[nn].up = -1; /* Linked list indexes */ + + /* Add an entry in the hash table */ + if (s->hash[hash] < 0) + s->hash[hash] = nn; /* We are the only entry */ + else { + hp = s->hash[hash]; + while (s->nodes[hp].hp >= 0) + hp = s->nodes[hp].hp; /* Follow chain */ + s->nodes[hp].hp = nn; /* Add at the end of the chain */ + } + + return nn; +} + +/* ============================================= */ +/* Main object functions */ + +/* Initialise, ready to read out all the points */ +static void simdlat_reset(simdlat *s) { + s->rix = 0; +} + +/* Read the next set of non-fixed points values */ +/* return non-zero when no more points */ +static int simdlat_read( +simdlat *s, +double *d, /* Device position */ +double *p /* Perceptual value */ +) { + int j; + + for (; s->rix < s->bnp; s->rix++) { + + if (s->bnodes[s->rix].vald != 0) { + for (j = 0; j < s->di; j++) { + if (d != NULL) + d[j] = s->bnodes[s->rix].p[j]; + if (p != NULL) + p[j] = s->bnodes[s->rix].v[j]; + } + s->rix++; + return 0; + } + } + return 1; +} + +/* Do a pass of seed filling the whole gamut, given a simplex dia. */ +/* Return the number of nodes produced */ +static int do_pass( +simdlat *s, +double dia /* Simplex diameter to try */ +) { + int di = s->di; + int hash; + int i, j, k; + int x[MXTD]; + int nn; /* New nodes index */ + int np; + + /* Rest the current list */ + s->np = 0; + s->nvp = 0; + for (i = 0; i < SPT_HASHSIZE; i++) + s->hash[i] = -1; + + /* Initial alloc of nodes */ + if (s->nodes == NULL) { + s->np_a = 10; + if ((s->nodes = (sdtnode *)malloc(s->np_a * sizeof(sdtnode))) == NULL) + error ("simdlat: nodes malloc failed"); + } + + /* Compute the simplex basis vectors */ + /* arguments: simplex diameter, device space offset, angle to skew grid */ +// comp_basis(s, s->type, dia, 0.5, s->angle); + comp_basis(s, s->type, dia, 0.4 + dia/150.0, s->angle); + + s->dia = dia; + + /* Add an initial seed point */ + for (j = 0; j < di; j++) + x[j] = 0; + hash = comp_hash(s, x); + nn = new_node(s, x, hash); + + if (s->nodes[nn].b > 1) { + error("simdlat: initial seed point is not within gamut"); + } + + s->unex = nn; /* Initial entry in unexplored list */ + +//printf("~1 seed node is [%d %d]\n",s->nodes[nn].x[0], s->nodes[nn].x[1]); + + /* While there is more unexplored area */ + /* and we arn't finding a ridiculous number of points */ + while(s->unex >= 0 && (s->nvp < 3 * s->inp)) { + int pos; /* Positive or -ve direction */ + nn = s->unex; /* Node we're looking at */ + s->unex = s->nodes[nn].up; /* remove from unexplored list */ + +//printf("\n~1 exploring beyond node [%d %d]\n",s->nodes[nn].x[0], s->nodes[nn].x[1]); + + if (s->nodes[nn].b > 1) + continue; /* Don't look at boundary points */ + + /* For all unexplored directions */ + for (i = 0; i < di; i++) { + for (pos = 0; pos < 2; pos++) { + int on; /* Other node index */ + +//printf("~1 checking direction dim %d, sign %d, [%d %d]\n",i,pos,x[0],x[1]); + + if (((1 << i) & s->nodes[nn].expm[pos]) == 0) { +//printf("~1 that direction has been explored\n"); + continue; /* Try next direction */ + } + + /* Check out that direction */ + for (j = 0; j < di; j++) + x[j] = s->nodes[nn].x[j]; + x[i] += pos ? 1 : -1; + + /* If that node already exists */ + hash = comp_hash(s, x); + if ((on = check_exists(s, x, hash)) >= 0) { + /* back direction doesn't need checking */ + s->nodes[on].expm[pos ^ 1] &= ~(1 << i); +//printf("~1 that node already exists\n"); + continue; /* Try next direction */ + } + + /* Create a new node in that direction */ + on = new_node(s, x, hash); + + if (s->nodes[on].b > 1) { /* If new node is boundary, don't explore beyond it */ +//printf("~1 added new boundary node [%d %d]\n",x[0],x[1]); + continue; + } + /* back direction on new node doesn't need checking */ + s->nodes[on].expm[pos ^ 1] &= ~(1 << i); + +//printf("~1 added new internal node [%d %d] **\n",x[0],x[1]); + s->nodes[on].up = s->unex; /* Add this node to unexplored list */ + s->unex = on; + } + } + } + + + /* Rationalise cooincident points, and count final valid */ + s->nvp = 0; + for (i = 0; i < s->np; i++) { + +//printf("~1 rationalising %d, = %f %f\n",i, s->nodes[i].p[0], s->nodes[i].p[1]); + if (s->nodes[i].vald == 0) { +//printf("~1 point %d is not valid\n",i); + continue; + } + + /* First against fixed points in device space */ + for (k = 0; k < s->fxno; k++) { + double dd; + + /* Compute distance */ + dd = 0.0; + for (j = 0; j < di; j++) { + double tt = s->nodes[i].p[j] - s->fxlist[k].p[j]; + dd += tt * tt; + } + dd = sqrt(dd); + + if (dd < s->tol) { + s->nodes[i].vald = 0; /* Ignore this point */ +//printf("~1 point %d matches input point %d\n",i, k); + break; + } + } + + if (s->nodes[i].vald == 0) + continue; + + /* Then against all the other points */ + for (k = i+1; k < s->np; k++) { + double dd; + + if (s->nodes[k].vald == 0) + continue; + + /* Compute distance */ + dd = 0.0; + for (j = 0; j < di; j++) { + double tt = s->nodes[i].p[j] - s->nodes[k].p[j]; + dd += tt * tt; + } + dd = sqrt(dd); + + if (dd < s->tol) { + s->nodes[i].vald = 0; /* Ignore this point */ +//printf("~1 point %d matches other point %d\n",i, k); + break; + } + } + + if (s->nodes[i].vald != 0) + s->nvp++; /* Found a valid one */ + } + +#ifdef DUMP_PLOT + /* Dump plot */ + dump_image(s, PERC_PLOT); +#endif /* DUMP_PLOT */ + +//printf("~1 got %d valid out of %d total\n",s->nvp, s->np); + np = s->nvp; + + /* If we have a new best */ + if (s->nvp <= s->inp && (s->inp - s->nvp) < (s->inp - s->bnvp)) { + sdtnode *tnodes; + int tnp_a; + tnodes = s->bnodes; /* Swap them */ + tnp_a = s->bnp_a; + s->bnp = s->np; + s->bnvp = s->nvp; + s->bnodes = s->nodes; + s->bnp_a = s->np_a; + s->bdia = s->dia; + s->nodes = tnodes; + s->np_a = tnp_a; + s->np = s->nvp = 0; /* Zero current */ + } + + return np; +} + +/* Destroy ourselves */ +static void +simdlat_del(simdlat *s) { + + if (s->nodes != NULL) + free(s->nodes); + if (s->bnodes != NULL) + free(s->bnodes); + + free (s); +} + +/* Constructor */ +simdlat *new_simdlat( +int di, /* Dimensionality of device space */ +double ilimit, /* Ink limit (sum of device coords max) */ +int inp, /* Number of points to generate */ +fxpos *fxlist, /* List of existing fixed points (may be NULL) */ +int fxno, /* Number of existing fixes points */ +int type, /* type of geometry, 0 = body centered cubic, */ + /* 1 = equilateral simplex, 2 = face centered cubic */ +double angle, /* Angle to orient grid at (0.0 - 0.5 typical) */ +void (*percept)(void *od, double *out, double *in), /* Perceptual lookup func. */ +void *od /* context for Perceptual function */ +) { + int i; + double ctol; + double hdia, ldia, dia; + int hnp, lnp, np; + simdlat *s; + +#ifdef DEBUG + printf("new_simdlat called with di %d, inp %d\n",di,inp); +#endif + + if ((s = (simdlat *)calloc(sizeof(simdlat), 1)) == NULL) + error ("simdlat: simdlat malloc failed"); + +#if defined(__IBMC__) + _control87(EM_UNDERFLOW, EM_UNDERFLOW); + _control87(EM_OVERFLOW, EM_OVERFLOW); +#endif + + s->reset = simdlat_reset; + s->read = simdlat_read; + s->del = simdlat_del; + + /* If no perceptual function given, use default */ + /* (Not that it's used here) */ + if (percept == NULL) { + s->percept = default_simdlat_to_percept; + s->od = s; + } else { + s->percept = percept; + s->od = od; + } + + s->ilimit = ilimit; + + s->inp = inp - fxno; /* Intended number of points */ + s->angle = angle; /* desired grid angle */ + + s->tol = SNAP_TOL; + + ctol = 0.6/pow((double)s->inp, 1.0/di); + if (ctol < s->tol) { + s->tol = ctol; + } +//printf("~1 tol = %f\n",s->tol); + + s->fxlist = fxlist; /* remember fixed points */ + s->fxno = fxno; + + /* Compute perceptual values in fixed list */ + for (i = 0; i < s->fxno; i++) + s->percept(s->od, s->fxlist[i].v, s->fxlist[i].p); + + if (di > MXTD) + error ("simdlat: Can't handle di %d",di); + s->di = di; + + /* We need to do a binary search to establish the desired */ + /* latice spacing. */ + + /* Do an initial stab */ +#ifdef NEVER + dia = 0.3; +#else + { /* For body centered cubic */ + double vol = (double)ilimit/(double)di; + double cellvol = (2.0 * vol * di)/(double)inp; + dia = pow(cellvol, 1.0/di); + printf("~1 initial dia = %f\n",dia); + } +#endif + np = do_pass(s, dia); + if (np == 0) + error("simdlat: First pass gave 0 points!"); + +//printf("~1 first cut dia %f ang %f gave %d points, target = %d\n",dia, s->angle, np, s->inp); + + if (np < s->inp) { /* Low count */ + ldia = dia; + lnp = np; + for(;;) { + dia = pow(np/(1.5 * s->inp), 1.0/di) * dia; +//printf("~1 next try dia %f in hope of %f\n",dia, 1.5 * s->inp); + + np = do_pass(s, dia); +//printf("~1 second cut dia %f ang %f gave %d points, target = %d\n",dia, s->angle, np,s->inp); + if (np >= s->inp) + break; + ldia = dia; /* New low count */ + lnp = np; + } + hdia = dia; + hnp = np; + } else { + hdia = dia; /* High count */ + hnp = np; + for(;;) { + dia = pow(np/(0.6 * s->inp), 1.0/di) * dia; +//printf("~1 next try dia %f in hope of %f\n",dia, 0.6 * s->inp); + np = do_pass(s, dia); +//printf("~1 second cut dia %f ang %f gave %d points, target = %d\n",dia, s->angle, np,s->inp); + if (np <= s->inp) + break; + hdia = dia; /* new high count */ + hnp = np; + } + ldia = dia; + lnp = np; + } + + /* Now zoom into correct number, with linear interp. binary search. */ + for (i = 0; s->bnvp != s->inp && i < MAX_TRIES; i++) { + double ratio; + + /* Bail out early if we're close enough */ + if ((3 * i) > MAX_TRIES) { + if (((double)s->bnvp/(double)s->inp) > 0.99) + break; + } + + ratio = ((double)s->inp - lnp)/(hnp - lnp); /* Distance between low and high */ + dia = ratio * (hdia - ldia) + ldia; + np = do_pass(s, dia); + +//printf("~1 try %d, cut dia %f ang %f gave %d points, target = %d\n",i, dia, s->angle, np,s->inp); + if (np > s->inp) { + hdia = dia; + hnp = np; + } else { + ldia = dia; + lnp = np; + } + } + + simdlat_reset(s); + +//printf("~1 total of %d patches\n",s->bnvp); + + return s; +} + +/* =================================================== */ + +#ifdef STANDALONE_TEST + +//#define ANGLE 0.33333 +#define ANGLE 0.0 + +icxColorantLu *clu; + +#ifdef NEVER +static void sa_percept(void *od, double *out, double *in) { + double lab[3]; + + clu->dev_to_rLab(clu, lab, in); + + out[0] = lab[0]; +// out[1] = (lab[1]+100.0)/2.0; + out[1] = (lab[2]+100.0)/2.0; +} +#else + +static void sa_percept(void *od, double *p, double *d) { + int e, di = 2; + +#ifndef NEVER + /* Default Do nothing - copy device to perceptual. */ + for (e = 0; e < di; e++) { + double tt = d[e]; + if (e == 0) + tt = pow(tt, 2.0); + else + tt = pow(tt, 0.5); + p[e] = tt * 100.0; + } +#else + for (e = 0; e < di; e++) { + double tt = d[e]; + /* Two slopes with a sharp turnover in X */ + if (e == 0) { + if (tt < 0.5) + tt = tt * 0.3/0.5; + else + tt = 0.3 + ((tt-0.5) * 0.7/0.5); + } + p[e] = tt * 100.0; + } +#endif +} +#endif + +int +main(argc,argv) +int argc; +char *argv[]; +{ + int npoints = 50; + simdlat *s; + int mask = ICX_BLACK | ICX_GREEN; + + error_program = argv[0]; + + if (argc > 1) + npoints = atoi(argv[1]); + + if ((clu = new_icxColorantLu(mask)) == NULL) + error ("Creation of xcolorant lu object failed"); + + /* Create the required points */ + s = new_simdlat(2, 1.5, npoints, NULL, 0, SIMDLAT_BCC, ANGLE, sa_percept, NULL); + +#ifdef DUMP_PLOT + printf("Perceptual plot:\n"); + dump_image_final(s, 1); + + printf("Device plot:\n"); + dump_image_final(s, 0); +#endif /* DUMP_PLOT */ + + s->del(s); + + return 0; +} + +#endif /* STANDALONE_TEST */ + + + +#if defined(DEBUG) || defined(DUMP_PLOT) + +/* Dump the current point positions to a plot window file */ +static void +dump_image(simdlat *s, int pcp) { + double minx, miny, maxx, maxy; + double *x1a = NULL; + double *y1a = NULL; + double *x2a = NULL; + double *y2a = NULL; + double *x3a = NULL; + double *y3a = NULL; + + int i, nu; + sdtnode *p; + + if (s->nvp == 0) + return; + + if (pcp) { /* Perceptual range */ + minx = 0.0; /* Assume */ + miny = 0.0; + maxx = 100.0; + maxy = 100.0; + } else { + minx = 0.0; /* Assume */ + miny = 0.0; + maxx = 1.0; + maxy = 1.0; + } + + if ((x1a = (double *)malloc(s->nvp * sizeof(double))) == NULL) + error ("simdlat: plot malloc failed %d",s->nvp); + if ((y1a = (double *)malloc(s->nvp * sizeof(double))) == NULL) + error ("simdlat: plot malloc failed %d",s->nvp); + if ((x2a = (double *)malloc(s->nvp * sizeof(double))) == NULL) + error ("simdlat: plot malloc failed %d",s->nvp); + if ((y2a = (double *)malloc(s->nvp * sizeof(double))) == NULL) + error ("simdlat: plot malloc failed %d",s->nvp); + + for (nu = i = 0; i < s->np; i++) { + p = &s->nodes[i]; + + if (p->vald == 0) + continue; + if (pcp) { + x1a[nu] = p->v[0]; + y1a[nu] = p->v[1]; + x2a[nu] = p->v[0]; + y2a[nu] = p->v[1]; + } else { + x1a[nu] = p->p[0]; + y1a[nu] = p->p[1]; + x2a[nu] = p->p[0]; + y2a[nu] = p->p[1]; + } + nu++; + } + + /* Plot the vectors */ + do_plot_vec(minx, maxx, miny, maxy, + x1a, y1a, x2a, y2a, nu, DO_WAIT, x3a, y3a, 0); + + free(x1a); + free(y1a); + free(x2a); + free(y2a); +} + +/* Dump the final point positions to a plot window file */ +static void +dump_image_final(simdlat *s, int pcp) { + double minx, miny, maxx, maxy; + double *x1a = NULL; + double *y1a = NULL; + double *x2a = NULL; + double *y2a = NULL; + double *x3a = NULL; + double *y3a = NULL; + + int i, nu; + sdtnode *p; + + if (pcp) { /* Perceptual range */ + minx = 0.0; /* Assume */ + miny = 0.0; + maxx = 100.0; + maxy = 100.0; + } else { + minx = 0.0; /* Assume */ + miny = 0.0; + maxx = 1.0; + maxy = 1.0; + } + + if ((x1a = (double *)malloc(s->bnvp * sizeof(double))) == NULL) + error ("simdlat: plot malloc failed"); + if ((y1a = (double *)malloc(s->bnvp * sizeof(double))) == NULL) + error ("simdlat: plot malloc failed"); + if ((x2a = (double *)malloc(s->bnvp * sizeof(double))) == NULL) + error ("simdlat: plot malloc failed"); + if ((y2a = (double *)malloc(s->bnvp * sizeof(double))) == NULL) + error ("simdlat: plot malloc failed"); + + for (nu = i = 0; i < s->bnp; i++) { + p = &s->bnodes[i]; + + if (p->vald == 0) + continue; + if (pcp) { + x1a[nu] = p->v[0]; + y1a[nu] = p->v[1]; + x2a[nu] = p->v[0]; + y2a[nu] = p->v[1]; + } else { + x1a[nu] = p->p[0]; + y1a[nu] = p->p[1]; + x2a[nu] = p->p[0]; + y2a[nu] = p->p[1]; + } + nu++; + } + + /* Plot the vectors */ + do_plot_vec(minx, maxx, miny, maxy, + x1a, y1a, x2a, y2a, nu, DO_WAIT, x3a, y3a, 0); + + free(x1a); + free(y1a); + free(x2a); + free(y2a); +} + +#endif /* DEBUG */ + + + + + diff --git a/target/simdlat.h b/target/simdlat.h new file mode 100644 index 0000000..93e0ad3 --- /dev/null +++ b/target/simdlat.h @@ -0,0 +1,94 @@ + +#ifndef SIMDLAT_H +/* + * Argyll Color Correction System + * + * Simplex device space latice test point class + * + * Author: Graeme W. Gill + * Date: 30/8/2004 + * + * Copyright 2002 - 2004 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. + * + * Based on simplat.h + */ + +#define SPT_HASHSIZE 4463 /* Hash index size */ + +/* A sample point node */ +struct _sdtnode { + int vald; /* Valid flag */ + int x[MXTD]; /* Index */ + double p[MXTD]; /* Device coordinate position */ + double v[MXTD]; /* Subjective value (Labk) */ + int b; /* 1 if a gamut point, 2 if a boundary point */ + unsigned long expm[2]; /* -ve/+ve dimention explored flags */ + + int hp; /* Hash linked list index */ + int up; /* Unexplored linked list index */ +}; typedef struct _sdtnode sdtnode; + + +/* Main simplex latice object */ +struct _simdlat { +/* private: */ + int di; /* Point dimensionality */ + double ilimit; /* Ink limit - limit on sum of p[] */ + int inp; /* Intended number of points in list */ + int type; /* Type of point geometry, 0 = body centered etc. */ + double angle; /* Grid angle */ + double bo[MXTD]; /* Basis origin */ + double bv[MXTD][MXTD]; /* Simplex basis vectors */ + int np; /* Number of point nodes in list */ + int nvp; /* Number of valid point nodes in list */ + int np_a; /* Number of points allocated */ + double dia; /* Point spacing in latice */ + sdtnode *nodes; /* Current array of nodes */ + int bnp; /* Number of point nodes in best list */ + int bnvp; /* Number of best valid point nodes in list */ + int bnp_a; /* Number of best points allocated */ + double bdia; /* Point spacing in best latice */ + sdtnode *bnodes; /* Current best array of nodes */ + int hash[SPT_HASHSIZE]; /* Hash index */ + int unex; /* Head of unexplored list index */ + double tol; /* Snap tollerance */ + + /* Perceptual function */ + void (*percept)(void *od, double *out, double *in); + void *od; /* Opaque data for perceptual point */ + + /* Fixed points to avoid */ + fxpos *fxlist; /* List of existing fixed points (may be NULL) */ + int fxno; /* Number of existing fixes points */ + + int rix; /* Next read index */ + +/* public: */ + /* Initialise, ready to read out all the points */ + void (*reset)(struct _simdlat *s); + + /* Read the next set of non-fixed points values */ + /* return non-zero when no more points */ + int (*read)(struct _simdlat *s, double *d, double *p); + + /* Destroy ourselves */ + void (*del)(struct _simdlat *s); + + }; typedef struct _simdlat simdlat; + + /* geometry type */ +#define SIMDLAT_BCC 0 /* Body centered Cubic */ +#define SIMDLAT_EQSPLX 1 /* Equilateral Simplex */ +#define SIMDLAT_FCC 2 /* Face Centered Cubic */ + +/* Constructor */ +extern simdlat *new_simdlat(int di, double ilimit, int npoints, + fxpos *fxlist, int fxno, int type, double angle, + void (*percept)(void *od, double *out, double *in), void *od); + +#define SIMDLAT_H +#endif /* SIMDLAT_H */ diff --git a/target/simplat.c b/target/simplat.c new file mode 100644 index 0000000..736b0e8 --- /dev/null +++ b/target/simplat.c @@ -0,0 +1,1095 @@ + +/* + * Argyll Color Correction System + * + * Simplex perceptual space latice test point class + * set to generate an equilateral simplex regular lattice, + * in perceptual space. + * + * Author: Graeme W. Gill + * Date: 27/3/2002 + * + * Copyright 2002 - 2004 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: + + This seems too inexact/slow to read a specified number of test + points for use in higher dimensions. + + */ + +#undef SPHERICAL /* spherical (equalateral simplex) packing, rather */ + /* than body centered cubic lattice. Better than face centered, */ + /* worse than body centered. */ +#undef FCCPACK /* Face centered cubic lattice (worse than body centered and spherical) */ + +#undef DEBUG +#undef DUMP_PLOT /* Show on screen plot */ +#define PERC_PLOT 1 /* Emit perceptive space plots */ +#define DO_WAIT 1 /* Wait for user key after each plot */ + + +#include +#include +#include +#include +#if defined(__IBMC__) +#include +#endif +#ifdef DEBUG +#include "plot.h" +#endif +#include "numlib.h" +#include "sort.h" +#include "plot.h" +#include "icc.h" +#include "xcolorants.h" +#include "targen.h" +#include "simplat.h" + +#if defined(DEBUG) || defined(DUMP_PLOT) +static void dump_image(simplat *s, int pcp); +static void dump_image_final(simplat *s, int pcp); +#endif + +#define SNAP_TOL 0.01 /* Snap to gamut boundary tollerance */ +#define MAX_TRIES 30 /* Maximum itterations */ + + +/* ----------------------------------------------------- */ + +/* Default convert the nodes device coordinates into approximate perceptual coordinates */ +/* (usually overriden by caller supplied function) */ +static void +default_simplat_to_percept(void *od, double *p, double *d) { + simplat *s = (simplat *)od; + int e; + + /* Default Do nothing - copy device to perceptual. */ + for (e = 0; e < s->di; e++) { + p[e] = d[e] * 100.0; + } +} + +/* Return the largest distance of the point outside the device gamut. */ +/* This will be 0 if inside the gamut, and > 0 if outside. */ +static double +simplat_in_dev_gamut(simplat *s, double *d) { + int e; + int di = s->di; + double tt, dd = 0.0; + double ss = 0.0; + + for (e = 0; e < di; e++) { + ss += d[e]; + + tt = 0.0 - d[e]; + if (tt > 0.0) { + if (tt > dd) + dd = tt; + } + tt = d[e] - 1.0; + if (tt > 0.0) { + if (tt > dd) + dd = tt; + } + } + tt = ss - s->ilimit; + if (tt > 0.0) { + if (tt > dd) + dd = tt; + } + return dd; +} + +/* Snap a point to the device gamut boundary. */ +/* Return nz if it has been snapped. */ +static int snap_to_gamut(simplat *s, double *d) { + int e; + int di = s->di; + double dd; /* Smallest distance */ + double ss; /* Sum */ + int rv = 0; + + /* Snap to ink limit first */ + for (ss = 0.0, e = 0; e < di; e++) + ss += d[e]; + dd = fabs(ss - s->ilimit); + + if (dd <= s->tol) { + int j; + for (j = 0; j < di; j++) + d[j] *= s->ilimit/ss; /* Snap to ink limit */ + rv = 1; + } + + /* Now snap to any other dimension */ + for (e = 0; e < di; e++) { + + dd = fabs(d[e] - 0.0); + if (dd < s->tol) { + d[e] = 0.0; /* Snap to orthogonal boundary */ + rv = 1; + } + dd = fabs(1.0 - d[e]); + if (dd < s->tol) { + d[e] = 1.0; /* Snap to orthogonal boundary */ + rv = 1; + } + } + + return rv; +} + + +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ +/* Reverse lookup function :- perceptual to device coordinates */ + +/* Structure to hold data for optimization function */ +struct _edatas { + simplat *s; /* simplat structure */ + double *ptp; /* Perceptual target point */ + }; typedef struct _edatas edatas; + +/* Definition of the optimization functions handed to powell() */ + +/* This one returns error from perceptual target point, and */ +/* an error >= 50000 on being out of device gamut */ +static double efunc(void *edata, double p[]) { + edatas *ed = (edatas *)edata; + simplat *s = ed->s; + int e, di = s->di; + double rv, pp[MXTD]; + if ((rv = (simplat_in_dev_gamut(s, p))) > 0.0) { + rv = rv * 5000.0 + 100000.0; /* Discourage being out of gamut */ + } else { + s->percept(s->od, pp, p); + for (rv = 0.0, e = 0; e < di; e++) { + double tt = pp[e] - ed->ptp[e]; + rv += tt * tt; + } + } +//printf("rv = %f from %f %f\n",rv,p[0],p[1]); + return rv; +} + +/* Given a point in perceptual space, an approximate point */ +/* in device space, return the device value corresponding to */ +/* the perceptual value, plus the clipped perceptual value. */ +/* Return 1 if the point has been clipped. */ +/* Return 2 if the point has been clipped by a dia. */ +static int +simplat_from_percept( +simplat *s, +double *d, /* return device position */ +double *p /* Given perceptual value */ +) { + int e, di = s->di; + edatas ed; + double pp[MXTD]; + double sr[MXTD]; /* Search radius */ + double tt; + double drad = 50.0; /* Search radius */ + double ptol = 0.00001; /* Tolerance */ + ed.s = s; + ed.ptp = p; /* Set target perceptual point */ + + for (e = 0; e < di; e++) { + sr[e] = drad; /* Device space search radius */ + } + if (powell(&tt, di, d, sr, ptol, 500, efunc, (void *)&ed, NULL, NULL) != 0 || tt >= 50000.0) { + error("simplat: powell failed, tt = %f\n",tt); + } + snap_to_gamut(s, d); + s->percept(s->od, pp, d); /* Lookup clipped perceptual */ + + tt = 0.0; + for (e = 0; e < di; e++) { + double t = p[e] - pp[e]; + p[e] = pp[e]; + tt += t * t; + } + tt = sqrt(tt); +//printf("~1 perc %f %f -> %f %f dev %f %f, err = %f\n",ed.ptp[0],ed.ptp[1],p[0],p[1],d[0],d[1],tt); + if (tt > (0.5 * s->dia)) + return 2; /* invalid & !explore */ + if (tt > 0.5) + return 1; /* Valid & explore */ + return 0; +} + +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +/* Compute the simplex basis vectors */ +static void comp_basis( +simplat *s, +double dia, /* Diameter of simplex circumspehere */ +double off, /* Starting offset in device space */ +double angle /* Rotation angle 0.0 - 1.0 */ +) { + int i, j, di = s->di; + double sx[MXTD+1][MXTD]; /* Simplex verticies */ + +#ifdef SPHERICAL + + /* Create the node positions for the */ + /* equalateral simplex basis vectors */ + for (i = 0; i < (di+1); i++) { + double rr = 1.0; /* Current radius squared */ + double ss = dia / sqrt(3.0); /* Scale */ + + /* The bounding points form a equalateral simplex */ + /* whose vertexes are on a sphere about the data */ + for (j = 0; j < di; j++) { + double ddi; + double hh = 1.0/(di-j); /* Weight for remaining points */ + + if (j > i) + sx[i][j] = 0.0; /* If beyond last */ + else if (j == i) /* If last non-zero */ + sx[i][j] = ss * sqrt(rr); + else /* If before last */ + sx[i][j] = -hh * ss * sqrt(rr); + + ddi = (double)(di - j); + rr *= (ddi * ddi - 1.0)/(ddi * ddi); + } + } + +#else /* !SPHERICAL */ + +#ifdef FCCPACK + + /* Create the node positions for the */ + /* face centered arrangement */ + /* Face centered places points at locations where the */ + /* sum of the lattice integer coordinates is even. */ + for (i = 0; i < (di+1); i++) { + + for (j = 0; j < di; j++) + sx[i][j] = 0.0 * -0.5; + + if (i > 0 && i < di) { + sx[i][i-1] += 0.5 * dia; + sx[i][di-1] += 0.5 * dia; + } else if (i == di) { + sx[i][di-1] += dia; + } + } + +#else /* Body cenetered */ + + /* Create the node positions for body centered */ + /* cubic latice simplex basis vectors */ + /* Body centered places points at locations where the */ + /* lattice integer coordinates are all even or all odd. */ + for (i = 0; i < (di+1); i++) { + + for (j = 0; j < di; j++) + sx[i][j] = -0.5; + if (i < di) { + if (i > 0) + sx[i][i-1] += dia; + } else { + for (j = 0; j < di; j++) + sx[i][j] += 0.5 * dia; + } + } + +#endif /* !FCCPACK */ +#endif /* !SPHERICAL */ + + /* Apply a rotation to avoid possible alignment with */ + /* the device axes */ + { + int m, k; + int ldi = di-1; /* Last dimension */ + double a, b; + + b = angle; + a = sqrt(1.0 - b * b); + + /* Apply rotation to all except last dimension */ + for (m = 0; m < ldi; m++) { /* Dimension being rotated */ + + for (i = 0; i < (di+1); i++) { /* Node being rotated */ + double out[MXTD]; + + for (j = 0; j < di; j++) { /* Coord being produced */ + out[j] = 0.0; + + for (k = 0; k < di; k++) { /* Coord being used */ + if ((j == m && k == m) + || (j == ldi && k == ldi)) + out[j] += a * sx[i][k]; /* Diagonal multiplier */ + else if (j == m && k == ldi) + out[j] += b * sx[i][k]; + else if (j == ldi && k == m) + out[j] -= b * sx[i][k]; + else if (j == k) + out[j] += sx[i][k]; + } + } + for (j = 0; j < di; j++) + sx[i][j] = out[j]; /* Transfer result */ + } + } + } + +#ifdef DEBUG /* Dump stats on verticies */ +for(i = 0; i < (di+1); i++) { + double val = 0.0; + printf("vert %d = ",i); + for(j = 0; j < di; j++) { + val += sx[i][j] * sx[i][j]; + printf("%f ",sx[i][j]); + } + printf(" (%f)\n",sqrt(val)); +} + +for(i = 0; i < di; i++) { + for (j = i+1; j < (di+1); j++) { + int e; + double val; + + /* Distance between nodes */ + for (val = 0.0, e = 0; e < di; e++) { + double tt = sx[i][e] - sx[j][e]; + val += tt * tt; + } + val = sqrt(val); + printf("dist %d %d = %f\n",i,j,val); + } +} +#endif /* DEBUG */ + + /* Convert from di+1 verticies to di base vectors */ + for (i = 0; i < di; i++) { + for (j = 0; j < di; j++) { + s->bv[i][j] = sx[i+1][j] - sx[i][j]; + } + } + + /* Establish the basis origin */ + { + double dv[MXTD]; + + for (j = 0; j < di; j++) + dv[j] = off * s->ilimit/di; + s->percept(s->od, s->bo, dv); + } +} + +/* Compute the hash */ +static int comp_hash( +simplat *s, +int *x /* Index */ +) { + int j, di = s->di; + unsigned long hash; + + for (hash = 0, j = 0; j < di; j++) + hash = hash * 7 + x[j]; + hash %= SPT_HASHSIZE; + + return hash; +} + +/* Check if a node already exists. Return -1 if not, */ +/* or node index if it does. */ +static int check_exists( +simplat *s, +int *x, /* Index */ +int hash /* Hash */ +) { + int di = s->di; + int hp; /* node index */ + int j; + + for (hp = s->hash[hash]; hp >= 0; hp = s->nodes[hp].hp) { + + /* Check if we have a match */ + for (j = 0; j < di; j++) { + if (s->nodes[hp].x[j] != x[j]) + break; + } + if (j >= di) + break; /* Found a match */ + } + + return hp; +} + +/* Create a new node. We assume it doesn't already exist */ +/* Return its index */ +static int new_node( +simplat *s, +int *x, /* Index */ +int hash /* Hash */ +) { + int di = s->di; + int b = 0; /* NZ if a boundary point */ + int nn; /* New node index */ + int hp; /* Hash chain index */ + int i, j; + + /* Make room for it */ + if ((s->np+1) >= s->np_a) { + s->np_a *= 2; + if ((s->nodes = (sptnode *)realloc(s->nodes, s->np_a * sizeof(sptnode))) == NULL) + error ("simplat: node realloc failed"); + } + + nn = s->np++; /* Add the new point */ + + /* Compute the target perceptual value */ + for (j = 0; j < di; j++) { + s->nodes[nn].v[j] = s->bo[j]; + s->nodes[nn].p[j] = 0.5; /* Search start point */ + } + for (i = 0; i < di; i++) { + for (j = 0; j < di; j++) { + s->nodes[nn].v[j] += x[i] * s->bv[i][j]; /* Sum basis vector product */ + } + } + + /* Lookup the device position */ + b = simplat_from_percept(s, s->nodes[nn].p, s->nodes[nn].v); + + /* Store node information */ + for (j = 0; j < di; j++) + s->nodes[nn].x[j] = x[j]; + + s->nodes[nn].b = b; + if (b < 2) { + s->nodes[nn].vald = 1; /* Valid if within or on gamut */ + s->nvp++; /* Got another valid one */ + } else + s->nodes[nn].vald = 0; /* Not valid if it's a boundary point */ + s->nodes[nn].expm[0] = + s->nodes[nn].expm[1] = (1 << di)-1; /* Assum all dimensions need exploring */ + s->nodes[nn].hp = s->nodes[nn].up = -1; /* Linked list indexes */ + + /* Add an entry in the hash table */ + if (s->hash[hash] < 0) + s->hash[hash] = nn; /* We are the only entry */ + else { + hp = s->hash[hash]; + while (s->nodes[hp].hp >= 0) + hp = s->nodes[hp].hp; /* Follow chain */ + s->nodes[hp].hp = nn; /* Add at the end of the chain */ + } + + return nn; +} + +/* ============================================= */ +/* Main object functions */ + +/* Initialise, ready to read out all the points */ +static void simplat_reset(simplat *s) { + s->rix = 0; +} + +/* Read the next set of non-fixed points values */ +/* return non-zero when no more points */ +static int simplat_read( +simplat *s, +double *d, /* Device position */ +double *p /* Perceptual value */ +) { + int j; + + for (; s->rix < s->bnp; s->rix++) { + + if (s->bnodes[s->rix].vald != 0) { + for (j = 0; j < s->di; j++) { + if (d != NULL) + d[j] = s->bnodes[s->rix].p[j]; + if (p != NULL) + p[j] = s->bnodes[s->rix].v[j]; + } + s->rix++; + return 0; + } + } + return 1; +} + +/* Do a pass of seed filling the whole gamut, given a simplex dia. */ +/* Return the number of nodes produced */ +static int do_pass( +simplat *s, +double dia /* Simplex diameter to try */ +) { + int di = s->di; + int hash; + int i, j, k; + int x[MXTD]; + int nn; /* New nodes index */ + int np; + + /* Rest the current list */ + s->np = 0; + s->nvp = 0; + for (i = 0; i < SPT_HASHSIZE; i++) + s->hash[i] = -1; + + /* Initial alloc of nodes */ + if (s->nodes == NULL) { + s->np_a = 10; + if ((s->nodes = (sptnode *)malloc(s->np_a * sizeof(sptnode))) == NULL) + error ("simplat: nodes malloc failed"); + } + + /* Compute the simplex basis vectors */ + /* arguments: simplex diameter, device space offset, angle to skew grid */ + comp_basis(s, dia, 0.5, s->angle); + +// comp_basis(s, dia, 0.5, ANGLE); +// comp_basis(s, dia, 0.5, dia/200.0); +// comp_basis(s, dia, 0.4 + dia/150.0, ANGLE); +// comp_basis(s, dia, 0.4 + dia/150.0, dia/147.0); +// comp_basis(s, dia, 0.5, fmod(dia, 1.0)); + + s->dia = dia; + + /* Add an initial seed point */ + for (j = 0; j < di; j++) + x[j] = 0; + hash = comp_hash(s, x); + nn = new_node(s, x, hash); + + if (s->nodes[nn].b > 1) { + error("simplat: initial seed point is not within gamut"); + } + + s->unex = nn; /* Initial entry in unexplored list */ + +//printf("~1 seed node is [%d %d]\n",s->nodes[nn].x[0], s->nodes[nn].x[1]); + + /* While there is more unexplored area */ + /* and we arn't finding a ridiculous number of points */ + while(s->unex >= 0 && (s->nvp < 3 * s->inp)) { + int pos; /* Positive or -ve direction */ + nn = s->unex; /* Node we're looking at */ + s->unex = s->nodes[nn].up; /* remove from unexplored list */ + +//printf("\n~1 exploring beyond node [%d %d]\n",s->nodes[nn].x[0], s->nodes[nn].x[1]); + + if (s->nodes[nn].b > 1) + continue; /* Don't look at boundary points */ + + /* For all unexplored directions */ + for (i = 0; i < di; i++) { + for (pos = 0; pos < 2; pos++) { + int on; /* Other node index */ + +//printf("~1 checking direction dim %d, sign %d, [%d %d]\n",i,pos,x[0],x[1]); + + if (((1 << i) & s->nodes[nn].expm[pos]) == 0) { +//printf("~1 that direction has been explored\n"); + continue; /* Try next direction */ + } + + /* Check out that direction */ + for (j = 0; j < di; j++) + x[j] = s->nodes[nn].x[j]; + x[i] += pos ? 1 : -1; + + /* If that node already exists */ + hash = comp_hash(s, x); + if ((on = check_exists(s, x, hash)) >= 0) { + /* back direction doesn't need checking */ + s->nodes[on].expm[pos ^ 1] &= ~(1 << i); +//printf("~1 that node already exists\n"); + continue; /* Try next direction */ + } + + /* Create a new node in that direction */ + on = new_node(s, x, hash); + + if (s->nodes[on].b > 1) { /* If new node is boundary, don't explore beyond it */ +//printf("~1 added new boundary node [%d %d]\n",x[0],x[1]); + continue; + } + /* back direction on new node doesn't need checking */ + s->nodes[on].expm[pos ^ 1] &= ~(1 << i); + +//printf("~1 added new internal node [%d %d] **\n",x[0],x[1]); + s->nodes[on].up = s->unex; /* Add this node to unexplored list */ + s->unex = on; + } + } + } + + + /* Rationalise cooincident points, and count final valid */ + s->nvp = 0; + for (i = 0; i < s->np; i++) { + +//printf("~1 rationalising %d, = %f %f\n",i, s->nodes[i].v[0], s->nodes[i].v[1]); + if (s->nodes[i].vald == 0) { +//printf("~1 point %d is not valid\n",i); + continue; + } + + /* First against fixed points in device space */ + for (k = 0; k < s->fxno; k++) { + double dd; + + /* Compute distance */ + dd = 0.0; + for (j = 0; j < di; j++) { + double tt = s->nodes[i].v[j] - s->fxlist[k].v[j]; + dd += tt * tt; + } + dd = 0.01 * sqrt(dd); + + if (dd < s->tol) { + s->nodes[i].vald = 0; /* Ignore this point */ +//printf("~1 point %d matches input point %d\n",i, k); + break; + } + } + + if (s->nodes[i].vald == 0) + continue; + + /* Then against all the other points */ + for (k = i+1; k < s->np; k++) { + double dd; + + if (s->nodes[k].vald == 0) + continue; + + /* Compute distance */ + dd = 0.0; + for (j = 0; j < di; j++) { + double tt = s->nodes[i].v[j] - s->nodes[k].v[j]; + dd += tt * tt; + } + dd = 0.01 * sqrt(dd); + + if (dd < s->tol) { + s->nodes[i].vald = 0; /* Ignore this point */ +//printf("~1 point %d matches other point %d\n",i, k); + break; + } + } + + if (s->nodes[i].vald != 0) + s->nvp++; /* Found a valid one */ + } + +#ifdef DUMP_PLOT + /* Dump plot */ + dump_image(s, PERC_PLOT); +#endif /* DUMP_PLOT */ + +printf("~1 got %d valid out of %d total\n",s->nvp, s->np); + np = s->nvp; + + /* If we have a new best */ + if (s->nvp <= s->inp && (s->inp - s->nvp) < (s->inp - s->bnvp)) { + sptnode *tnodes; + int tnp_a; + tnodes = s->bnodes; /* Swap them */ + tnp_a = s->bnp_a; + s->bnp = s->np; + s->bnvp = s->nvp; + s->bnodes = s->nodes; + s->bnp_a = s->np_a; + s->bdia = s->dia; + s->nodes = tnodes; + s->np_a = tnp_a; + s->np = s->nvp = 0; /* Zero current */ + } + + return np; +} + +/* Destroy ourselves */ +static void +simplat_del(simplat *s) { + + if (s->nodes != NULL) + free(s->nodes); + if (s->bnodes != NULL) + free(s->bnodes); + + free (s); +} + +/* Constructor */ +simplat *new_simplat( +int di, /* Dimensionality of device space */ +double ilimit, /* Ink limit (sum of device coords max) */ +int inp, /* Number of points to generate */ +fxpos *fxlist, /* List of existing fixed points (may be NULL) */ +int fxno, /* Number of existing fixes points */ +double angle, /* Angle to orient grid at (0.0 - 0.5 typical) */ +void (*percept)(void *od, double *out, double *in), /* Perceptual lookup func. */ +void *od /* context for Perceptual function */ +) { + int i; + double ctol; + double hdia, ldia, dia; + int hnp, lnp, np; + simplat *s; + +#ifdef DEBUG + printf("new_simplat called with di %d, inp %d\n",di,inp); +#endif + + if ((s = (simplat *)calloc(sizeof(simplat), 1)) == NULL) + error ("simplat: simplat malloc failed"); + +#if defined(__IBMC__) + _control87(EM_UNDERFLOW, EM_UNDERFLOW); + _control87(EM_OVERFLOW, EM_OVERFLOW); +#endif + + s->reset = simplat_reset; + s->read = simplat_read; + s->del = simplat_del; + + /* If no perceptual function given, use default */ + if (percept == NULL) { + s->percept = default_simplat_to_percept; + s->od = s; + } else { + s->percept = percept; + s->od = od; + } + + s->ilimit = ilimit; + + s->inp = inp - fxno; /* Intended number of points */ + s->angle = angle; /* desired grid angle */ + + s->tol = SNAP_TOL; + + ctol = 0.6/pow((double)s->inp, 1.0/di); + if (ctol < s->tol) { + s->tol = ctol; + } +printf("~1 tol = %f\n",s->tol); + + s->fxlist = fxlist; /* remember fixed points */ + s->fxno = fxno; + + /* Compute perceptual values in fixed list */ + for (i = 0; i < s->fxno; i++) + s->percept(s->od, s->fxlist[i].v, s->fxlist[i].p); + + + if (di > MXTD) + error ("simplat: Can't handle di %d",di); + s->di = di; + + /* We need to do a binary search to establish the desired */ + /* latice spacing. */ + + /* Do an initial stab */ + dia = 50.0; + np = do_pass(s, dia); + if (np == 0) + error("simplat: First pass gave 0 points!"); + +printf("~1 first cut dia %f gave %d points, target = %d\n",dia, np, s->inp); + + if (np < s->inp) { /* Low count */ + ldia = dia; + lnp = np; + for(;;) { + dia = pow(np/(1.5 * s->inp), 1.0/di) * dia; +//printf("~1 next try dia %f in hope of %f\n",dia, 1.5 * s->inp); + + np = do_pass(s, dia); +printf("~1 second cut dia %f gave %d points, target = %d\n",dia, np,s->inp); + if (np >= s->inp) + break; + ldia = dia; /* New low count */ + lnp = np; + } + hdia = dia; + hnp = np; + } else { + hdia = dia; /* High count */ + hnp = np; + for(;;) { + dia = pow(np/(0.6 * s->inp), 1.0/di) * dia; +//printf("~1 next try dia %f in hope of %f\n",dia, 0.6 * s->inp); + np = do_pass(s, dia); +printf("~1 second cut dia %f gave %d points, target = %d\n",dia, np,s->inp); + if (np <= s->inp) + break; + hdia = dia; /* new high count */ + hnp = np; + } + ldia = dia; + lnp = np; + } + + /* Now zoom into correct number, with linear interp. binary search. */ + for (i = 0; s->bnvp != s->inp && i < MAX_TRIES; i++) { + double ratio; + + /* Bail out early if we're close enough */ + if ((3 * i) > MAX_TRIES) { + if (((double)s->bnvp/(double)s->inp) > 0.99) + break; + } + + ratio = ((double)s->inp - lnp)/(hnp - lnp); /* Distance between low and high */ + dia = ratio * (hdia - ldia) + ldia; + np = do_pass(s, dia); + +printf("~1 try %d, cut dia %f gave %d points, target = %d\n",i, dia, np,s->inp); + if (np > s->inp) { + hdia = dia; + hnp = np; + } else { + ldia = dia; + lnp = np; + } + } + + simplat_reset(s); + +printf("~1 total of %d patches\n",s->bnvp); + + return s; +} + +/* =================================================== */ + +#ifdef STANDALONE_TEST + +#define ANGLE 0.0 + +icxColorantLu *clu; + +#ifdef NEVER +static void sa_percept(void *od, double *out, double *in) { + double lab[3]; + + clu->dev_to_rLab(clu, lab, in); + + out[0] = lab[0]; +// out[1] = (lab[1]+100.0)/2.0; + out[1] = (lab[2]+100.0)/2.0; +} +#else + +static void sa_percept(void *od, double *p, double *d) { + int e, di = 2; + +#ifndef NEVER + /* Default Do nothing - copy device to perceptual. */ + for (e = 0; e < di; e++) { + double tt = d[e]; + if (e == 0) + tt = pow(tt, 2.0); + else + tt = pow(tt, 0.5); + p[e] = tt * 100.0; + } +#else + for (e = 0; e < di; e++) { + double tt = d[e]; + /* Two slopes with a sharp turnover in X */ + if (e == 0) { + if (tt < 0.5) + tt = tt * 0.3/0.5; + else + tt = 0.3 + ((tt-0.5) * 0.7/0.5); + } + p[e] = tt * 100.0; + } +#endif +} +#endif + +int +main(argc,argv) +int argc; +char *argv[]; +{ + int npoints = 50; + simplat *s; + int mask = ICX_BLACK | ICX_GREEN; + + error_program = argv[0]; + + if (argc > 1) + npoints = atoi(argv[1]); + + if ((clu = new_icxColorantLu(mask)) == NULL) + error ("Creation of xcolorant lu object failed"); + + /* Create the required points */ + s = new_simplat(2, 1.5, npoints, NULL, 0, ANGLE, sa_percept, (void *)NULL); + +#ifdef DUMP_PLOT + printf("Perceptual plot:\n"); + dump_image_final(s, 1); + + printf("Device plot:\n"); + dump_image_final(s, 0); +#endif /* DUMP_PLOT */ + + s->del(s); + + return 0; +} + +#endif /* STANDALONE_TEST */ + + + +#if defined(DEBUG) || defined(DUMP_PLOT) + +/* Dump the current point positions to a plot window file */ +static void +dump_image(simplat *s, int pcp) { + double minx, miny, maxx, maxy; + double *x1a = NULL; + double *y1a = NULL; + double *x2a = NULL; + double *y2a = NULL; + double *x3a = NULL; + double *y3a = NULL; + + int i, nu; + sptnode *p; + + if (s->nvp == 0) + return; + + if (pcp) { /* Perceptual range */ + minx = 0.0; /* Assume */ + miny = 0.0; + maxx = 100.0; + maxy = 100.0; + } else { + minx = 0.0; /* Assume */ + miny = 0.0; + maxx = 1.0; + maxy = 1.0; + } + + if ((x1a = (double *)malloc(s->nvp * sizeof(double))) == NULL) + error ("simplat: plot malloc failed %d",s->nvp); + if ((y1a = (double *)malloc(s->nvp * sizeof(double))) == NULL) + error ("simplat: plot malloc failed %d",s->nvp); + if ((x2a = (double *)malloc(s->nvp * sizeof(double))) == NULL) + error ("simplat: plot malloc failed %d",s->nvp); + if ((y2a = (double *)malloc(s->nvp * sizeof(double))) == NULL) + error ("simplat: plot malloc failed %d",s->nvp); + + for (nu = i = 0; i < s->np; i++) { + p = &s->nodes[i]; + + if (p->vald == 0) + continue; + if (pcp) { + x1a[nu] = p->v[0]; + y1a[nu] = p->v[1]; + x2a[nu] = p->v[0]; + y2a[nu] = p->v[1]; + } else { + x1a[nu] = p->p[0]; + y1a[nu] = p->p[1]; + x2a[nu] = p->p[0]; + y2a[nu] = p->p[1]; + } + nu++; + } + + /* Plot the vectors */ + do_plot_vec(minx, maxx, miny, maxy, + x1a, y1a, x2a, y2a, nu, DO_WAIT, x3a, y3a, 0); + + free(x1a); + free(y1a); + free(x2a); + free(y2a); +} + +/* Dump the final point positions to a plot window file */ +static void +dump_image_final(simplat *s, int pcp) { + double minx, miny, maxx, maxy; + double *x1a = NULL; + double *y1a = NULL; + double *x2a = NULL; + double *y2a = NULL; + double *x3a = NULL; + double *y3a = NULL; + + int i, nu; + sptnode *p; + + if (pcp) { /* Perceptual range */ + minx = 0.0; /* Assume */ + miny = 0.0; + maxx = 100.0; + maxy = 100.0; + } else { + minx = 0.0; /* Assume */ + miny = 0.0; + maxx = 1.0; + maxy = 1.0; + } + + if ((x1a = (double *)malloc(s->bnvp * sizeof(double))) == NULL) + error ("simplat: plot malloc failed"); + if ((y1a = (double *)malloc(s->bnvp * sizeof(double))) == NULL) + error ("simplat: plot malloc failed"); + if ((x2a = (double *)malloc(s->bnvp * sizeof(double))) == NULL) + error ("simplat: plot malloc failed"); + if ((y2a = (double *)malloc(s->bnvp * sizeof(double))) == NULL) + error ("simplat: plot malloc failed"); + + for (nu = i = 0; i < s->bnp; i++) { + p = &s->bnodes[i]; + + if (p->vald == 0) + continue; + if (pcp) { + x1a[nu] = p->v[0]; + y1a[nu] = p->v[1]; + x2a[nu] = p->v[0]; + y2a[nu] = p->v[1]; + } else { + x1a[nu] = p->p[0]; + y1a[nu] = p->p[1]; + x2a[nu] = p->p[0]; + y2a[nu] = p->p[1]; + } + nu++; + } + + /* Plot the vectors */ + do_plot_vec(minx, maxx, miny, maxy, + x1a, y1a, x2a, y2a, nu, DO_WAIT, x3a, y3a, 0); + + free(x1a); + free(y1a); + free(x2a); + free(y2a); +} + +#endif /* DEBUG */ + + + + + diff --git a/target/simplat.h b/target/simplat.h new file mode 100644 index 0000000..4cede20 --- /dev/null +++ b/target/simplat.h @@ -0,0 +1,86 @@ + +#ifndef SIMPLAT_H +/* + * Argyll Color Correction System + * + * Simplex perceptual space latice test point class + * + * Author: Graeme W. Gill + * Date: 27/3/2002 + * + * Copyright 2002 - 2004 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. + */ + +#define SPT_HASHSIZE 4463 /* Hash index size */ + +/* A sample point node */ +struct _sptnode { + int vald; /* Valid flag */ + int x[MXTD]; /* Index */ + double p[MXTD]; /* Device coordinate position */ + double v[MXTD]; /* Subjective value (Labk) */ + int b; /* 1 if a gamut point, 2 if a boundary point */ + unsigned long expm[2]; /* -ve/+ve dimention explored flags */ + + int hp; /* Hash linked list index */ + int up; /* Unexplored linked list index */ +}; typedef struct _sptnode sptnode; + + +/* Main simplex latice object */ +struct _simplat { +/* private: */ + int di; /* Point dimensionality */ + double ilimit; /* Ink limit - limit on sum of p[] */ + int inp; /* Intended number of points in list */ + double angle; /* Grid angle */ + double bo[MXTD]; /* Basis origin */ + double bv[MXTD][MXTD]; /* Simplex basis vectors */ + int np; /* Number of point nodes in list */ + int nvp; /* Number of valid point nodes in list */ + int np_a; /* Number of points allocated */ + double dia; /* Point spacing in latice */ + sptnode *nodes; /* Current array of nodes */ + int bnp; /* Number of point nodes in best list */ + int bnvp; /* Number of best valid point nodes in list */ + int bnp_a; /* Number of best points allocated */ + double bdia; /* Point spacing in best latice */ + sptnode *bnodes; /* Current best array of nodes */ + int hash[SPT_HASHSIZE]; /* Hash index */ + int unex; /* Head of unexplored list index */ + double tol; /* Snap tollerance */ + + /* Perceptual function */ + void (*percept)(void *od, double *out, double *in); + void *od; /* Opaque data for perceptual point */ + + /* Fixed points to avoid */ + fxpos *fxlist; /* List of existing fixed points (may be NULL) */ + int fxno; /* Number of existing fixes points */ + + int rix; /* Next read index */ + +/* public: */ + /* Initialise, ready to read out all the points */ + void (*reset)(struct _simplat *s); + + /* Read the next set of non-fixed points values */ + /* return non-zero when no more points */ + int (*read)(struct _simplat *s, double *d, double *p); + + /* Destroy ourselves */ + void (*del)(struct _simplat *s); + + }; typedef struct _simplat simplat; + +/* Constructor */ +extern simplat *new_simplat(int di, double ilimit, int npoints, + fxpos *fxlist, int fxno, double angle, + void (*percept)(void *od, double *out, double *in), void *od); + +#define SIMPLAT_H +#endif /* SIMPLAT_H */ diff --git a/target/targen.c b/target/targen.c new file mode 100644 index 0000000..09dcc35 --- /dev/null +++ b/target/targen.c @@ -0,0 +1,2224 @@ + +/* + * Argyll Color Correction System + * Test target chart Generator. + * + * Author: Graeme W. Gill + * Date: 28/9/96 + * + * Copyright 1996 - 2004, 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 CGATS.5 compatibe file, that */ +/* containing device color test patch values. */ + +/* TTBD: + + Should add an option to generate grey and near grey + or other PCS based pattern test points based on the previous profile. + How about an option to read in an CGATS file containing + PCS or device values ? How is the black level chosen for PCS though ? + + Would be nice to be able to take a previous .ti3 and + then suppliment the measured patches. Would have to add another + set of measurement columns to .ti1 & .ti2 to carry the + already measured values through, or do clumbsy post merge ? + + Would be nice to be able to generate secondary + color ramps (ie. CMY for RGB space, RGB for CMYK space.) + + Using adaptive patch creation for grey colorspace is broken. + This should be fixed. + + */ + +/* NOTE: + + The device model is assumed to not take xpow into account, + hence the expected values don't reflect its effect. + The general filter is applied prior to the xpow being applied. + Many of the test patch types do take it into account + when computing the ink limit. + The ones that don't are the more complicated full spread patches. + + */ + +/* Description: + + >> THIS NEEDS REVISION << + + Nearly all current Color correction systems generate test charts (or + device characterisation target charts) by laying out a regular rectangular + grid of test points in device space (Targen will do this if you feed it a non-zero + m option). On some consideration, this approach is far from optimal. Not only + is a regular grid inefficent in packing the multidimentional device space, + but if the points are spaced evenly in device space, they will be poorly + spaced in human perceptual space, and errors in perceptual space are + the ultimate arbiter of the end profiles accuracy. Some commercial + color systems tackle the latter problem by "pre-linearising" the device, + which amounts to distorting the regular device space grid points with + a perceptual inverse per device chanel lookup curve. + + The approach I have taken with Argyll, is a little different. By + using an iterative sphere packing algorithm, I constrain the given + number of test points to the devices physical gamut (including an + ink limit for a printer device), and then try and pack the points + evenly in human perceptual space, or even space them to minimise + curvature approximation errors. Because the packing is a stocastic + process, the resulting points are distributed without evident + patterns. + +#ifdef NEVER + For higher dimensional spaces, where the aim is to create a + more aproximate device profile, I've used a "perfect simplex + latice" generator to lay out perfectly packed sample points + in perceptual space. The latice spacing is sized by an + iterative search to (hopefully) create the right number of + test points. +#else + For higher dimensional spaces, where the aim is to create a + more aproximate device profile, I've used an "incremental far + point" point generator, that for each added point, locates + the device values that result in a percetual value farthest + from any existing points in the test set. +#endif + + Another issue with laying test points out in regular grids, is + that this means that the device response is poorly sampled + (since the grids are usually coarse), and this can make it + impossible to create detailed device linearisation "shaper" + curves from the resulting data ! + Ideally, in any colorspace (input or output), when viewed from + any possible angle, none of the test data points should appear + to line up. The Argyll target generator seems to acheive this goal. + + */ + +#undef DEBUG + +#define VRML_DIAG /* Enable option to dump a VRML of the resulting full spread points */ +#undef ADDRECCLIPPOINTS /* Add ink limited clipping points to regular grid */ +#define EMPH_NEUTRAL /* Emphasise neutral axis, like CIE94 does */ +#define NEMPH_DEFAULT 0.5 /* Default emphasis == 2 x CIE94 */ +#define DEFANGLE 0.3333 /* For simdlat and simplat */ +#define SIMDLAT_TYPE SIMDLAT_BCC /* Simdlat geometry type */ +#define MATCH_TOLL 1e-3 /* Tollerance of device value to consider a patch a duplicate */ + +#include +#include +#include +#include +#include +#include +#include "copyright.h" +#include "aconfig.h" +#include "numlib.h" +#include "vrml.h" +#include "rspl.h" +#include "cgats.h" +#include "icc.h" +#include "xicc.h" +#include "targen.h" +//#include "ppoint.h" +#include "ofps.h" +#include "ifarp.h" +#include "simplat.h" +#include "simdlat.h" +#include "prand.h" + +#include + +#define min2(a,b) ((a) < (b) ? (a) : (b)) +#define min3(a,b,c) (min2((a), min2((b),(c)))) +#define max2(a,b) ((a) > (b) ? (a) : (b)) +#define max3(a,b,c) (max2((a), max2((b),(c)))) + +/* 32 bit pseudo random sequencer */ +#define PSRAND32(S) (((S) & 0x80000000) ? (((S) << 1) ^ 0xa398655d) : ((S) << 1)) + +/* ---------------------------- */ +/* The perception function data */ +/* (Used for test point distribution) */ +struct _pcpt { +/* public: */ + void (*del)(struct _pcpt *s); /* We're done with it */ + + int (*is_specific)(struct _pcpt *s); /* Is a specific model, not defaulte */ + + /* Conversions */ + void (*dev_to_perc)(struct _pcpt *s, double *out, double *in); /* N-chan Perceptual */ + void (*dev_to_XYZ)(struct _pcpt *s, double *out, double *in); /* Absolute XYZ */ + void (*dev_to_rLab)(struct _pcpt *s, double *out, double *in); /* Relative Lab */ + void (*den_to_dev)(struct _pcpt *s, double *out, double *in); /* Density to device */ + void (*rLab_to_dev)(struct _pcpt *s, double *out, double *in); /* Lab to device */ + + /* !!! Should add perc_to_dev using code from prand that uses dnsq !!! */ + +/* private: */ + inkmask xmask; /* external xcolorants inkmask */ + inkmask nmask; /* internal xcolorants inkmask */ + int di; /* Number of Device dimensions */ + + /* Tuning parameters */ + double nemph; /* neutral emphasis, 0.0 - 1.0. Default 0.35 for == CIE94 */ + + /* ICC profile based */ + icmFile *fp; + icc *icco; + icmLuBase *luo; /* Device -> rLab conversion */ + icmLuBase *luo2; /* Device -> XYZ conversion */ + + /* MPP profile based */ + mpp *mlu; /* Device -> XYZ */ + + /* Xcolorants model based */ + icxColorantLu *clu; /* Device -> CIE */ + + rspl *nlin[MXTD - 3]; /* Perceptual linearisation for other chanels */ + int e; /* Chanel being set */ + + /* Reverse lookup support */ + double ilimit; /* Ink limit (scale 1.0) */ + double den[3]; /* Target density or Lab */ + double uniform; /* NZ if target is uniform */ + int kchan; /* Set to the K chanel (-1 if none) */ + +}; typedef struct _pcpt pcpt; + + +/* Absolute XYZ conversion function */ +/* Internal device values 0.0 - 1.0 are converted into XYZ values */ +/* (Used for downstream checking) */ +static void +pcpt_to_XYZ(pcpt *s, double *out, double *in) { + int e; + double inv[MXTD]; + + if (s->xmask == s->nmask) { + for (e = 0; e < s->di; e++) + inv[e] = in[e]; + } else { + for (e = 0; e < s->di; e++) + inv[e] = 1.0 - in[e]; + } + if (s->luo2 != NULL) + s->luo2->lookup(s->luo2, out, inv); + else if (s->mlu != NULL) + s->mlu->lookup(s->mlu, out, inv); + else if (s->clu != NULL) + s->clu->dev_to_XYZ(s->clu, out, inv); + else { /* Linear conversion */ + out[0] = 100.0 * inv[0]; + out[1] = 100.0 * inv[1] - 50.0; + out[2] = 100.0 * inv[2] - 50.0; + icmLab2XYZ(&icmD50, out, out); + } +} + + +/* Relative Lab conversion function */ +/* Internal device values 0.0 - 1.0 are converted into Lab values */ +/* (Used for VRML visualisation checking) */ +static void +pcpt_to_rLab(pcpt *s, double *out, double *in) { + int e; + double inv[MXTD]; + + if (s->xmask == s->nmask) { + for (e = 0; e < s->di; e++) + inv[e] = in[e]; + } else { + for (e = 0; e < s->di; e++) + inv[e] = 1.0 - in[e]; + } + if (s->luo != NULL) + s->luo->lookup(s->luo, out, inv); + else if (s->mlu != NULL) { + s->mlu->lookup(s->mlu, out, inv); + icmXYZ2Lab(&icmD50, out, out); + } else if (s->clu != NULL) + s->clu->dev_to_rLab(s->clu, out, inv); + else { /* Linear conversion */ + out[0] = 100.0 * inv[0]; + out[1] = 100.0 * inv[1] - 50.0; + out[2] = 100.0 * inv[2] - 50.0; + } +} + +/* Perceptual conversion function */ +/* Internal device values 0.0 - 1.0 are converted into perceptually uniform 0.0 - 100.0 */ +static void +pcpt_to_nLab(pcpt *s, double *out, double *in) { + int e; + double inv[MXTD]; + + if (s->xmask == s->nmask) { + for (e = 0; e < s->di; e++) + inv[e] = in[e]; + } else { + for (e = 0; e < s->di; e++) + inv[e] = 1.0 - in[e]; + } + + /* If we have some sort of perceptual conversion */ + if (s->luo != NULL || s->mlu != NULL || s->clu != NULL) { + double lab[3]; + + if (s->luo != NULL) + s->luo->lookup(s->luo, lab, inv); + else if (s->mlu != NULL) { + s->mlu->lookup(s->mlu, lab, inv); + icmXYZ2Lab(&icmD50, lab, lab); + } else + s->clu->dev_to_rLab(s->clu, lab, inv); + +#ifdef EMPH_NEUTRAL /* Emphasise neutral axis, like CIE94 does */ + { + double c; /* Chromanance */ + + c = sqrt(lab[1] * lab[1] + lab[2] * lab[2]); /* Compute chromanance */ + +// c = 2.6624 / (1.0 + 0.013 * c); /* Full strength scale factor */ + c = 3.0 / (1.0 + 0.03 * c); /* Full strength scale factor */ + c = 1.0 + s->nemph * (c - 1.0); /* Reduced strength scale factor */ + + lab[1] *= c; /* scale a & b */ + lab[2] *= c; + } +#endif + /* Copy Lab values to output */ + for (e = 0; e < (s->di < 3 ? s->di : 3); e++) + out[e] = lab[e]; + + /* Lookup perceptual linearised auxiliary values */ + for (e = 0; e < (s->di-3); e++) { + co cc; + cc.p[0] = inv[3 + e]; + s->nlin[e]->interp(s->nlin[e], &cc); + out[3 + e] = cc.v[0]; + } + + } else { + /* Default linear in Device space */ + + for (e = 0; e < s->di; e++) + out[e] = 100.0 * inv[e]; + if (e == 1 || e == 2) + out[e] -= 50.0; /* Make it Lab like */ + } +} + + +/* Return the largest distance of the point outside the device gamut. */ +/* This will be 0 if inside the gamut, and > 0 if outside. */ +static double +pcpt_in_dev_gamut(pcpt *s, double *d) { + int e; + int di = s->di; + double tt, dd = 0.0; + double ss = 0.0; + double id[MXTD]; + + if (s->xmask == s->nmask) { + for (e = 0; e < s->di; e++) + id[e] = d[e]; + } else { + for (e = 0; e < s->di; e++) + id[e] = 1.0 - d[e]; + } + + for (e = 0; e < di; e++) { + ss += id[e]; + + tt = 0.0 - id[e]; + if (tt > 0.0) { + if (tt > dd) + dd = tt; + } + tt = id[e] - 1.0; + if (tt > 0.0) { + if (tt > dd) + dd = tt; + } + } + tt = ss - s->ilimit; + if (tt > 0.0) { + if (tt > dd) + dd = tt; + } + return dd; +} + +/* optimisation function to find device values */ +/* for a target density value. */ +static double efunc(void *edata, double p[]) { + pcpt *s = (pcpt *)edata; + int e, di = s->di; + double rv, xyz[3], den[4]; + +//printf("~1 efunc got dev %f %f %f %f\n",p[0],p[1],p[2],p[3]); + pcpt_to_XYZ(s, xyz, p); /* Convert device to XYZ */ +//printf("~1 efunc got XYZ %f %f %f\n",xyz[0],xyz[1],xyz[2]); + icx_XYZ2Tdens(den, xyz); /* Convert XYZ to approx statusT density */ +//printf("~1 efunc got density %f %f %f %f\n",den[0],den[1],den[2],den[3]); + +//printf("~1 efunc got in_dev_gamut %f\n",pcpt_in_dev_gamut(s, p)); + + /* Penalise for being out of gamut */ + rv = 5000.0 * pcpt_in_dev_gamut(s, p); + + /* Error to density target */ + { + double ss = 0.0; + for (e = 0; e < 3; e++) { + double tt; + tt = s->den[e] - den[e]; + ss += tt * tt; + } + rv += ss; +//printf("~1 efunc target den %f %f %f, err = %f, toterr %f\n",s->den[0],s->den[1],s->den[2],ss, rv); + } + + { + /* Minimise all channels beyond the */ + /* (assumed) first primary 3, but don't count black. */ + /* Minimise all channels except black if nchan >= 4 and uniform target */ + double ss = 0.0; + + for (e = 0; e < di; e++) { + double tt = 0.0; + + if (di >= 4 && s->uniform && e < 3 && e != s->kchan) + tt = p[e]; /* Minimise primary non-black if uniform */ +// else if (!s->uniform && (e < 3 || e == s->kchan)) +// tt = p[e]; /* Minimise sum of primaries & black if uniform */ + else if (e >= 3 && e != s->kchan) + tt = 3.0 * p[e]; /* Suppress non-primary, non-black */ + + ss += tt; + } + rv += 1.5 * ss * ss; +//printf("~1 efunc sum err = %f, toterr %f\n",ss, rv); + } + +//printf("~1 returning %f\n",rv); + return rv; +} + +/* Given target CMY densities, return a suitable device value */ +static void +pcpt_den_to_dev(pcpt *s, double *out, double *in) { + int e, di = s->di; + double tt, sr[MXTD]; /* Search radius */ + +//printf("\n"); +//printf("~1 targen density = %f %f %f\n",in[0],in[1],in[2]); +//printf("~1 di = %d, ilimit = %f\n",s->di,s->ilimit); + + for (e = 0; e < 3; e++) + s->den[e] = in[e]; + + for (e = 0; e < di; e++) { + sr[e] = 0.5; /* Device space search radius */ + out[e] = 0.5; + } + + if (fabs(in[0] - in[1]) < 0.1 + && fabs(in[0] - in[2]) < 0.1 + && fabs(in[1] - in[2]) < 0.1) { + s->uniform = 1; +//printf("~1 uniform set\n"); + } else + s->uniform = 0; + + if (powell(&tt, di, out, sr, 0.0001, 2000, efunc, (void *)s, NULL, NULL) != 0 || tt >= 50000.0) { + error("targen: powell failed, tt = %f\n",tt); + } + + /* Filter out silly values */ + for (e = 0; e < di; e++) { + if (out[e] < 0.001) + out[e] = 0.0; + else if (out[e] > 0.999) + out[e] = 1.0; + } +//printf("~1 returning device values %f %f %f\n",out[0],out[1],out[2]); +} + +/* Optimisation function to find device values */ +/* for a target Lab value. */ +static double efunc2(void *edata, double p[]) { + pcpt *s = (pcpt *)edata; + int e, di = s->di; + double rv, lab[3]; + +//printf("~1 efunc2 got dev %f %f %f %f\n",p[0],p[1],p[2],p[3]); +//printf("~1 efunc2 got dev %f %f %f %f %f %f\n",p[0],p[1],p[2],p[3],p[4],p[5]); + + pcpt_to_rLab(s, lab, p); /* Convert device to rLab */ +//printf("~1 efunc2 got Lab %f %f %f\n",lab[0],lab[1],lab[2]); + +//printf("~1 efunc2 got in_dev_gamut %f\n",pcpt_in_dev_gamut(s, p)); + + /* Penalise for being out of gamut */ + rv = 5000.0 * pcpt_in_dev_gamut(s, p); + + /* Error to Lab target */ + { + double ss = 0.0; + for (e = 0; e < 3; e++) { + double tt; + tt = s->den[e] - lab[e]; + ss += tt * tt; + } + rv += ss; +//printf("~1 efunc2 target Lab %f %f %f, err = %f, toterr %f\n",s->den[0],s->den[1],s->den[2],ss, rv); + } + + { + int f; + + /* Minimise all channels except K, and especially any */ + /* beyond the first primary 3 or 4. */ + double ss = 0.0; + + if ((s->nmask & ICX_CMYK) == ICX_CMYK) + f = 4; + else + f = 3; + for (e = 0; e < di; e++) { + if (e >= f) + ss += 10.0 * p[e]; /* Suppress non-primary */ + else if (e < 3) + ss += 0.05 * p[e]; /* Suppress first 3 primary slightly */ + } + rv += ss * ss; +//printf("~1 efunc2 sum err = %f, toterr %f\n",ss, rv); + } + +//printf("~1 efunc2 returning %f\n\n",rv); + return rv; +} + +/* Given target Lab densities, return a suitable device value */ +static void +pcpt_rLab_to_dev(pcpt *s, double *out, double *in) { + int e, di = s->di; + double tt, sr[MXTD]; /* Search radius */ + +//printf("\n"); +//printf("#######################3\n"); +//printf("~1 targen Lab = %f %f %f\n",in[0],in[1],in[2]); +//printf("~1 di = %d, ilimit = %f\n",s->di,s->ilimit); + + for (e = 0; e < 3; e++) + s->den[e] = in[e]; + + for (e = 0; e < di; e++) { + sr[e] = 0.5; /* Device space search radius */ + out[e] = 0.5; + } + + if (powell(&tt, di, out, sr, 0.0001, 2000, efunc2, (void *)s, NULL, NULL) != 0 || tt >= 50000.0) { + error("targen: powell failed, tt = %f\n",tt); + } + + /* Filter out silly values */ + for (e = 0; e < di; e++) { + if (out[e] <= 0.02) + out[e] = 0.0; + else if (out[e] >= 0.98) + out[e] = 1.0; + } +//printf("~1 returning device values %f %f %f\n",out[0],out[1],out[2]); +} + +/* Callback to setup s->nlin[e] mapping */ +static void set_nlin(void *cbntx, double *out, double *in) { + pcpt *s = (pcpt *)cbntx; /* Object we're setting up from */ + int e, di = s->di; + double dev[MXTD]; + double lab[3]; + + /* Just input extra channel into perceptual type lookup */ + if (s->xmask == s->nmask) { + for (e = 0; e < di; e++) + dev[e] = 0.0; + dev[3 + s->e] = in[0]; + } else { /* Fake RGB */ + for (e = 0; e < di; e++) + dev[e] = 1.0; + dev[3 + s->e] = 1.0 - in[0]; + } + + if (s->luo != NULL) { + s->luo->lookup(s->luo, lab, dev); + } else if (s->mlu != NULL) { + s->mlu->lookup(s->mlu, lab, dev); + icmXYZ2Lab(&icmD50, lab, lab); + } else if (s->clu != NULL) { + s->clu->dev_to_rLab(s->clu, lab, dev); + } else { + lab[0] = 100.0 * in[0]; + } + + /* ~~~ should we make this delta lab along locus, rather than L value ??? */ + out[0] = lab[0]; +} + +/* Is a specific model, not default */ +int pcpt_is_specific(pcpt *s) { + if (s->luo2 != NULL || s->mlu != NULL) + return 1; + return 0; +} + +/* Free the pcpt */ +static void pcpt_del(pcpt *s) { + + if (s != NULL) { + int e; + + if (s->luo != NULL) { + s->luo->del(s->luo); + s->luo2->del(s->luo2); + s->icco->del(s->icco); + s->fp->del(s->fp); + } + if (s->mlu != NULL) { + s->mlu->del(s->mlu); + } + if (s->clu != NULL) { + s->clu->del(s->clu); + } + for (e = 0; e < (s->di-3); e++) { + if (s->nlin[e] != NULL) + s->nlin[e]->del(s->nlin[e]); + } + + free(s); + } +} + +/* Create a pcpt conversion class */ +pcpt *new_pcpt( +char *profName, /* ICC or MPP profile path, NULL for default, "none" for linear */ +inkmask xmask, /* external xcolorants mask */ +inkmask nmask, /* internal xcolorants mask */ +double *ilimit, /* ink sum limit (scale 1.0) input and return, -1 if default */ +double *uilimit, /* underlying ink sum limit (scale 1.0) input and return, -1 if default */ +double nemph /* Neutral emphasis, 0.0 - 1.0. < 0.0 for default == CIE94 */ +) { + int e; + pcpt *s; + + if ((s = (pcpt *)calloc(1, sizeof(pcpt))) == NULL) { + fprintf(stderr,"targen: malloc failed allocating pcpt object\n"); + exit(-1); + } + + /* Initialise methods */ + s->del = pcpt_del; + s->is_specific = pcpt_is_specific; + s->dev_to_perc = pcpt_to_nLab; + s->dev_to_XYZ = pcpt_to_XYZ; + s->dev_to_rLab = pcpt_to_rLab; + s->den_to_dev = pcpt_den_to_dev; + s->rLab_to_dev = pcpt_rLab_to_dev; + + s->xmask = xmask; + s->nmask = nmask; + s->di = icx_noofinks(nmask); + + if (nemph < 0.0) + nemph = NEMPH_DEFAULT; + s->nemph = nemph; + + /* See if we have a profile */ + if (profName != NULL + && profName[0] != '\000' + && strcmp(profName, "none") != 0 + && strcmp(profName, "NONE") != 0) { + int rv = 0; + + /* Try and open the file as an ICC profile */ + if ((s->fp = new_icmFileStd_name(profName,"r")) == NULL) + error ("Can't open device profile '%s'",profName); + + + if ((s->icco = new_icc()) == NULL) + error ("Creation of ICC object failed"); + + if ((rv = s->icco->read(s->icco,s->fp,0)) == 0) { + icColorSpaceSignature ins, outs; /* Type of input and output spaces */ + xcal *cal = NULL; /* Device calibration curves */ + + /* Get a conversion object for relative Lab */ + if ((s->luo = s->icco->get_luobj(s->icco, icmFwd, icRelativeColorimetric, + icSigLabData, icmLuOrdNorm)) == NULL) { + if ((s->luo = s->icco->get_luobj(s->icco, icmFwd, icmDefaultIntent, + icSigLabData, icmLuOrdNorm)) == NULL) { + error ("%d, %s",s->icco->errc, s->icco->err); + } + } + + /* Get a conversion object for absolute XYZ */ + if ((s->luo2 = s->icco->get_luobj(s->icco, icmFwd, icAbsoluteColorimetric, + icSigXYZData, icmLuOrdNorm)) == NULL) { + if ((s->luo2 = s->icco->get_luobj(s->icco, icmFwd, icmDefaultIntent, + icSigXYZData, icmLuOrdNorm)) == NULL) { + error ("%d, %s",s->icco->errc, s->icco->err); + } + } + + /* Get details of conversion (Arguments may be NULL if info not needed) */ + s->luo->spaces(s->luo, &ins, NULL, &outs, NULL, NULL, NULL, NULL, NULL, NULL); + + if (icx_colorant_comb_match_icc(xmask, ins) == 0) { + s->luo->del(s->luo); + error("ICC profile doesn't match device!"); + } + + /* Grab any device calibration curves */ + cal = xiccReadCalTag(s->icco); + + /* Set the default ink limits if not set by user */ + if (*ilimit < 0.0) { + + if (cal != NULL) { + *ilimit = s->icco->get_tac(s->icco, NULL, xiccCalCallback, (void *)cal); + *uilimit = s->icco->get_tac(s->icco, NULL, NULL, NULL); + } else { + *uilimit = *ilimit = s->icco->get_tac(s->icco, NULL, NULL, NULL); + } + *ilimit += 0.1; /* + 10% */ + *uilimit += 0.1; /* + 10% */ + + /* Convert the user limit to a maximum underlying limit */ + } else if (cal != NULL && *ilimit < (double)s->di) { + *uilimit = icxMaxUnderlyingLimit(cal, *ilimit); + } + + } else { /* Not a valid ICC */ + /* Close out the ICC profile */ + s->icco->del(s->icco); + s->icco = NULL; + s->fp->del(s->fp); + s->fp = NULL; + } + + /* If we don't have an ICC lookup object, look for an MPP */ + if (s->luo == NULL) { + inkmask imask; + double dlimit = 0.0; + + if ((s->mlu = new_mpp()) == NULL) + error ("Creation of MPP object failed"); + + if ((rv = s->mlu->read_mpp(s->mlu, profName)) != 0) + error ("%d, %s",rv,s->mlu->err); + + s->mlu->get_info(s->mlu, &imask, NULL, &dlimit, NULL, NULL, NULL, NULL, NULL); + + if (xmask != imask) { + s->mlu->del(s->mlu); + error("MPP profile doesn't match device!"); + } + if (*ilimit < 0.0 && dlimit > 0.0) {/* If not user specified, use MPP inklimit */ + *uilimit = *ilimit = dlimit + 0.1; + } + } + } + + /* Fall back on an xcolorants model */ + if (s->luo == NULL + && s->mlu == NULL + && strcmp(profName, "none") != 0 + && strcmp(profName, "NONE") != 0) { + if ((s->clu = new_icxColorantLu(xmask)) == NULL) + error ("Creation of xcolorant lu object failed"); + } + /* else leave pointers NULL */ + + if (*ilimit < 0.0) + s->ilimit = (double)s->di; /* Default to no limit */ + else + s->ilimit = *ilimit/100.0; + + if (s->di > 1) + s->kchan = icx_ink2index(xmask, ICX_BLACK); + else + s->kchan = -1; + + /* Create extra chanel linearisation lookups */ + for (e = 0; e < (s->di-3); e++) { + double inmin = 0.0, inmax = 1.0; + double outmax = 100.0; + int gres = 256; + + if ((s->nlin[e] = new_rspl(RSPL_NOFLAGS, 1, 1)) == NULL) + error("RSPL creation failed"); + + s->e = e; /* Chanel to set */ + s->nlin[e]->set_rspl(s->nlin[e], 0, s, set_nlin, + &inmin, &inmax, &gres, &inmax, &outmax); + } + + return s; +} + +/* ------------------------------------ */ + +void +usage(int level, char *diag, ...) { + int i; + fprintf(stderr,"Generate Target deviceb test chart color values, Version %s\n",ARGYLL_VERSION_STR); + fprintf(stderr,"Author: Graeme W. Gill, licensed under the AGPL Version 3\n"); + fprintf(stderr,"usage: targen [options] outfile\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," -v [level] Verbose mode [optional level 1..N]\n"); + fprintf(stderr," -d col_comb choose colorant combination from the following:\n"); + for (i = 0; ; i++) { + char *desc; + if (icx_enum_colorant_comb(i, &desc) == 0) + break; + fprintf(stderr," %d: %s\n",i,desc); + } + fprintf(stderr," -D colorant Add or delete colorant from combination:\n"); + if (level == 0) + fprintf(stderr," (Use -?? to list known colorants)\n"); + else { + fprintf(stderr," %d: %s\n",0,"Additive"); + for (i = 0; ; i++) { + char *desc; + if (icx_enum_colorant(i, &desc) == 0) + break; + fprintf(stderr," %d: %s\n",i+1,desc); + } + } + fprintf(stderr," -G Generate good optimized points rather than Fast\n"); + fprintf(stderr," -e patches White test patches (default 4)\n"); + fprintf(stderr," -s steps Single channel steps (default grey 50, color 0)\n"); + fprintf(stderr," -g steps Grey axis RGB or CMY steps (default 0)\n"); + fprintf(stderr," -m steps Multidimensional device space cube steps (default 0)\n"); + fprintf(stderr," -f patches Add iterative & adaptive full spread patches to total (default grey 0, color 836)\n"); + fprintf(stderr," Default is Optimised Farthest Point Sampling (OFPS)\n"); + fprintf(stderr," -t Use incremental far point for full spread\n"); + fprintf(stderr," -r Use device space random for full spread\n"); + fprintf(stderr," -R Use perceptual space random for full spread\n"); + fprintf(stderr," -q Use device space-filling quasi-random for full spread\n"); + fprintf(stderr," -Q Use perceptual space-filling quasi-random for full spread\n"); + fprintf(stderr," -i Use device space body centered cubic grid for full spread\n"); + fprintf(stderr," -I Use perceptual space body centered cubic grid for full spread\n"); + fprintf(stderr," -a angle Simplex grid angle 0.0 - 0.5 for B.C.C. grid, default %f\n",DEFANGLE); + fprintf(stderr," -A adaptation Degree of adaptation of OFPS 0.0 - 1.0 (default 0.1, -c profile used 1.0)\n"); +/* Research options: */ +/* fprintf(stderr," -A pPERCWGHT Device (0.0) ... Perceptual (1.0) weighting\n"); */ +/* fprintf(stderr," -A cCURVEWGHT Curvature weighting 0.0 = none ... "); */ + fprintf(stderr," -l ilimit Total ink limit in %% (default = none)\n"); + fprintf(stderr," -p power Optional power-like value applied to all device values.\n"); + fprintf(stderr," -c profile Optional device ICC or MPP pre-conditioning profile filename\n"); + fprintf(stderr," (Use \"none\" to turn off any conditioning)\n"); + fprintf(stderr," -N emphasis Degree of neutral axis patch concentration 0.0-1.0 (default %.2f)\n",NEMPH_DEFAULT); + fprintf(stderr," -F L,a,b,rad Filter out samples outside Lab sphere.\n"); +#ifdef VRML_DIAG + fprintf(stderr," -w Dump diagnostic outfilel.wrl file (Lab locations)\n"); + fprintf(stderr," -W Dump diagnostic outfiled.wrl file (Device locations)\n"); +#endif /* VRML_DIAG */ + fprintf(stderr," outfile Base name for output(.ti1)\n"); + exit(1); +} + +/* Test if outside filter sphere. Return nz if it is */ +int dofilt( + pcpt *pdata, /* Perceptual conversion routine */ + double *filt, /* Filter sphere definition */ + double *dev /* Device values to check */ +) { + int i; + double Lab[3], rr; + pdata->dev_to_rLab(pdata, Lab, dev); + for (rr = 0.0, i = 0; i < 3; i++) { + double tt = Lab[i] - filt[i]; + rr += tt * tt; + } + if (rr > (filt[3] * filt[3])) { +//printf("~1 rejecting rad %f of %f %f %f <=> %f %f %f\n",sqrt(rr),Lab[0],Lab[1],Lab[2],filt[0],filt[1],filt[2]); + return 1; + } + return 0; +} + +int main(int argc, char *argv[]) { + int i, j, k; + int fa, nfa, mfa; /* current argument we're looking at */ + int verb = 0; /* Verbose flag */ +#ifdef VRML_DIAG + int dumpvrml = 0; /* Dump diagnostic .wrl file */ +#endif /* VRML_DIAG */ + inkmask xmask = 0; /* External ink mask combination */ + inkmask nmask = 0; /* Working ink mask combination (ie. CMY for printer external sRGB) */ + int di = 0; /* Output dimensions */ + char *ident; /* Ink combination identifier (includes possible leading 'i') */ + int good = 0; /* 0 - fast, 1 = good */ + int esteps = 4; /* White color patches */ + int ssteps = -1; /* Single channel steps */ + double xpow = 1.0; /* Power to apply to all device values created */ + int gsteps = 0; /* Composite grey wedge steps */ + int msteps = 0; /* Regular grid multidimensional steps */ + int fsteps = -1; /* Fitted Multidimensional patches */ + int uselat = 0; /* Use incremental far point alg. for full spread points */ + int userand = 0; /* Use random for full spread points, 2 = perceptual */ + int useqrand = 0; /* Use sobol for full spread points, 2 = perceptual */ + int usedsim = 0; /* Use device space simplex grid */ + int usepsim = 0; /* Use perceptual space simplex grid */ + double simangle = DEFANGLE; /* BCC grid angle */ + double dadapt = -2.0; /* Degree of iterative adaptation */ + double perc_wght = 0.0; /* Perceptual weighting */ + double curv_wght = 0.0; /* Curvature weighting */ + double ilimit = -1.0; /* Ink limit (scale 1.0) (default none) */ + double uilimit = -1.0; /* Underlying (pre-calibration, scale 1.0) ink limit */ + double nemph = NEMPH_DEFAULT; + int filter = 0; /* Filter values */ + double filt[4] = { 50,0,0,0 }; + static char fname[MAXNAMEL+1] = { 0 }; /* Output file base name */ + static char pname[MAXNAMEL+1] = { 0 }; /* Device profile name */ + static char wdname[MAXNAMEL+1] = { 0 }; /* Device diagnostic .wrl name */ + static char wlname[MAXNAMEL+1] = { 0 }; /* Lab diagnostic .wrl name */ + char buf[500]; /* Genaral use text buffer */ + int id = 1; /* Sample ID */ + time_t clk = time(0); + struct tm *tsp = localtime(&clk); + char *atm = asctime(tsp); /* Ascii time */ + cgats *pp; /* cgats structure */ + long stime,ttime; + pcpt *pdata; /* Space linearisation callback struct */ + fxpos *fxlist = NULL; /* Fixed point list for full spread */ + int fxlist_a = 0; /* Fixed point list allocation */ + int fxno = 0; /* The number of fixed points */ + +#ifdef NUMSUP_H + error_program = "targen"; +#endif + check_if_not_interactive(); + + if (argc <= 1) + usage(0,"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] == '?' || argv[fa][1] == '-') { + if (argv[fa][2] == '?' || argv[fa][2] == '-') + usage(1, "Extended usage requested"); + usage(0, "Usage requested"); + } + + else if (argv[fa][1] == 'v' || argv[fa][1] == 'V') { + verb = 1; + if (na != NULL && na[0] >= '0' && na[0] <= '9') { + verb = atoi(na); + fa = nfa; + } + } + + /* Select the ink enumeration */ + else if (argv[fa][1] == 'd') { + fa = nfa; + if (na == NULL) usage(0,"Expect argument after -d"); + i = atoi(na); + if (i == 0 && na[0] != '0') + usage(0,"Expect number argument after -d"); + if ((xmask = icx_enum_colorant_comb(i, NULL)) == 0) + usage(0,"Argument to -d is not recognized"); + } + /* Toggle the colorant in ink combination */ + else if (argv[fa][1] == 'D') { + int tmask; + fa = nfa; + if (na == NULL) usage(0,"Expect argument after -D"); + i = atoi(na); + if (i == 0 && na[0] != '0') + usage(0,"Expect number argument after -D"); + if (i == 0) + tmask = ICX_ADDITIVE; + else + if ((tmask = icx_enum_colorant(i-1, NULL)) == 0) + usage(0,"Argument to -D is not recognized"); + xmask ^= tmask; + } + /* Good rather than fast */ + else if (argv[fa][1] == 'G') { + good = 1; + } + /* White color patches */ + else if (argv[fa][1] == 'e' || argv[fa][1] == 'E') { + int tt; + fa = nfa; + if (na == NULL) usage(0,"Expect argument after -e"); + if ((tt = atoi(na)) >= 0) + esteps = tt; + } + /* Individual chanel steps */ + else if (argv[fa][1] == 's' || argv[fa][1] == 'S') { + int tt; + fa = nfa; + if (na == NULL) usage(0,"Expect argument after -s"); + if ((tt = atoi(na)) >= 0) + ssteps = tt; + } + /* RGB or CMY grey wedge steps */ + else if (argv[fa][1] == 'g') { + int tt; + fa = nfa; + if (na == NULL) usage(0,"Expect argument after -g"); + if ((tt = atoi(na)) >= 0) + gsteps = tt; + } + /* Multidimentional cube steps */ + else if (argv[fa][1] == 'm') { + int tt; + fa = nfa; + if (na == NULL) usage(0,"Expect argument after -m"); + if ((tt = atoi(na)) >= 0) { + msteps = tt; + if (msteps == 1) + msteps = 2; + } + } + /* Full even spread Multidimentional patches */ + else if (argv[fa][1] == 'f') { + int tt; + fa = nfa; + if (na == NULL) usage(0,"Expect argument after -f"); + if ((tt = atoi(na)) >= 0) + fsteps = tt; + } + + /* Use incremental far point algorithm for full spread */ + else if (argv[fa][1] == 't' || argv[fa][1] == 'T') { + uselat = 1; + userand = 0; + useqrand = 0; + usedsim = 0; + usepsim = 0; + } + + /* Random requested */ + else if (argv[fa][1] == 'r' || argv[fa][1] == 'R') { + uselat = 0; + if (argv[fa][1] == 'R') + userand = 2; + else + userand = 1; + useqrand = 0; + usedsim = 0; + usepsim = 0; + } + + /* Space filling quasi-random requested */ + else if (argv[fa][1] == 'q' || argv[fa][1] == 'Q') { + uselat = 0; + userand = 0; + if (argv[fa][1] == 'Q') + useqrand = 2; + else + useqrand = 1; + usedsim = 0; + usepsim = 0; + } + + + /* Device simplex grid requested */ + else if (argv[fa][1] == 'i') { + uselat = 0; + userand = 0; + useqrand = 0; + usedsim = 1; + usepsim = 0; + } + + /* Perceptual simplex grid requested */ + else if (argv[fa][1] == 'I') { + uselat = 0; + userand = 0; + useqrand = 0; + usedsim = 0; + usepsim = 1; + } + + /* Simplex grid angle */ + else if (argv[fa][1] == 'a') { + fa = nfa; + if (na == NULL) usage(0,"Expect argument after -a"); + simangle = atof(na); + } + + /* Degree of iterative adaptation */ + else if (argv[fa][1] == 'A') { + fa = nfa; + if (na == NULL) usage(0,"Expected argument to average deviation flag -A"); + if (na[0] == 'p') { /* (relative, for verification) */ + perc_wght = atof(na+1); + if (perc_wght < 0.0 || perc_wght > 1.0) + usage(0,"Perceptual weighting argument %f to '-Ap' must be between 0.0 and 1.0",perc_wght); + dadapt = -1.0; + } else if (na[0] == 'c') { /* (absolute, for testing) */ + curv_wght = atof(na+1); + if (curv_wght < 0.0 || curv_wght > 100.0) + usage(0,"Curvature weighting argument %f to '-Ac' must be between 0.0 and 100.0",curv_wght); + dadapt = -1.0; + } else { + dadapt = atof(na); + if (dadapt < 0.0 || dadapt > 1.0) + usage(0,"Average Deviation argument %f must be between 0.0 and 1.0",dadapt); + } + } + + /* Ink limit percentage */ + else if (argv[fa][1] == 'l' || argv[fa][1] == 'L') { + double tt; + fa = nfa; + if (na == NULL) usage(0,"Expect argument after -l"); + if ((tt = atof(na)) > 0.0) + uilimit = ilimit = 0.01 * tt; + } + + /* Extra device power-like to use */ + else if (argv[fa][1] == 'p' || argv[fa][1] == 'P') { + double tt; + fa = nfa; + if (na == NULL) usage(0,"Expect argument after -p"); + if ((tt = atof(na)) > 0.0) + xpow = tt; + } + + /* ICC profile for perceptual linearisation */ + else if (argv[fa][1] == 'c' || argv[fa][1] == 'C') { + fa = nfa; + if (na == NULL) usage(0,"Expect argument after -c"); + strncpy(pname,na,MAXNAMEL-1); pname[MAXNAMEL-1] = '\000'; + } + + /* Degree of neutral axis emphasis */ + else if (argv[fa][1] == 'N') { + fa = nfa; + if (na == NULL) usage(0,"Expected argument to neutral emphasis flag -N"); + nemph = atof(na); + if (nemph < 0.0 || nemph > 10.0) + usage(0,"Neautral weighting argument %f to '-N' is out of range",nemph); + } + + /* Filter out samples outside given sphere */ + else if (argv[fa][1] == 'F') { + fa = nfa; + if (na == NULL) usage(0,"Expect argument after -F"); + if (sscanf(na, " %lf,%lf,%lf,%lf ",&filt[0], &filt[1], &filt[2], &filt[3]) != 4) + usage(0,"Argument to -F '%s' isn't correct",na); + filter = 1; + } + +#ifdef VRML_DIAG + else if (argv[fa][1] == 'w') /* Lab */ + dumpvrml |= 1; + else if (argv[fa][1] == 'W') /* Device */ + dumpvrml |= 2; +#endif /* VRML_DIAG */ + else + usage(0,"Unknown flag '%c'",argv[fa][1]); + } + else + break; + } + + /* Get the file name argument */ + if (fa >= argc || argv[fa][0] == '-') usage(0,"Expect file base name argument"); + strncpy(fname,argv[fa],MAXNAMEL-4); fname[MAXNAMEL-4] = '\000'; + strcat(fname,".ti1"); + + strncpy(wdname,argv[fa],MAXNAMEL-5); wdname[MAXNAMEL-5] = '\000'; + strcat(wdname,"d.wrl"); + + strncpy(wlname,argv[fa],MAXNAMEL-5); wlname[MAXNAMEL-5] = '\000'; + strcat(wlname,"l.wrl"); + + /* Set default colorant combination as CMYK */ + if (xmask == 0) + xmask = ICX_CMYK; + + nmask = xmask; + + /* Deal with fake printer RGB, where we use CMY internally and invert all */ + /* the resulting device values. */ + if (xmask & ICX_INVERTED) { + if (xmask != ICX_IRGB) + error("Don't know how to deal with inverted colorant combination 0x%x\n",xmask); + nmask = ICX_CMY; /* Internally treat it as CMY and invert the result */ + } + + ident = icx_inkmask2char(xmask, 1); + di = icx_noofinks(nmask); /* Lookup number of dimensions */ + stime = clock(); + + /* Implement some defaults */ + if (di == 1) { + if (ssteps < 0) + ssteps = 50; + if (fsteps < 0) + fsteps = 0; + } else { + if (ssteps < 0) /* Defaults */ + ssteps = 0; + if (fsteps < 0) + fsteps = 836; + } + + /* Do some sanity checking */ + if (di == 1) { + if (ssteps == 0 && fsteps == 0 && msteps == 0) + error ("Must have some Gray steps"); + if (gsteps > 0) { + warning ("Composite grey steps ignored for monochrome output"); + gsteps = 0; + } + } else if (di == 3) { + if (ssteps == 0 && fsteps == 0 && msteps == 0 && gsteps == 0) + error ("Must have some single or multi dimensional RGB or CMY steps"); + } else { + if (ssteps == 0 && fsteps == 0 && msteps == 0 && gsteps == 0) + error ("Must have some single or multi dimensional steps"); + } + + /* Deal with ICC, MPP or fallback profile */ + if ((pdata = new_pcpt(pname, xmask, nmask, &ilimit, &uilimit, nemph)) == NULL) { + error("Perceptual lookup object creation failed"); + } + + /* Set default adapation level */ + if (dadapt < -1.5) { /* Not set by user */ + if (pname[0] != '\000') + dadapt = 1.0; + else + dadapt = 0.1; + } + + if (verb) { + printf("%s test chart\n",ident); + + if (ssteps > 0) + printf("Single channel steps = %d\n",ssteps); + if (gsteps > 0) + printf("Compostie Grey steps = %d\n",gsteps); + if (fsteps > 0) + printf("Full spread patches = %d\n",fsteps); + if (msteps > 0) + printf("Multi-dimention cube steps = %d\n",msteps); + if (ilimit >= 0.0) + printf("Ink limit = %.1f%% (underlying %.1f%%)\n",ilimit * 100.0, uilimit * 100.0); + if (filter) { + printf("Filtering out samples outside sphere at %f %f %f radius %f\n", + filt[0], filt[1], filt[2], filt[3]); + } + } + pp = new_cgats(); /* Create a CGATS structure */ + pp->add_other(pp, "CTI1"); /* our special type is Calibration Target Information 1 */ + + pp->add_table(pp, tt_other, 0); /* Add the first table for target points */ + pp->add_table(pp, tt_other, 0); /* Add the second table for density pre-defined device values */ + pp->add_table(pp, tt_other, 0); /* Add the second table for device pre-defined device values */ + pp->add_kword(pp, 0, "DESCRIPTOR", "Argyll Calibration Target chart information 1",NULL); + pp->add_kword(pp, 1, "DESCRIPTOR", "Argyll Calibration Target chart information 1",NULL); + pp->add_kword(pp, 2, "DESCRIPTOR", "Argyll Calibration Target chart information 1",NULL); + pp->add_kword(pp, 0, "ORIGINATOR", "Argyll targen", NULL); + pp->add_kword(pp, 1, "ORIGINATOR", "Argyll targen", NULL); + pp->add_kword(pp, 2, "ORIGINATOR", "Argyll targen", NULL); + atm[strlen(atm)-1] = '\000'; /* Remove \n from end */ + pp->add_kword(pp, 0, "CREATED",atm, NULL); + + /* Make available the aproximate white point to allow relative */ + /* interpretation of the aproximate XYZ values */ + { + int e; + double val[MXTD], XYZ[3]; + + /* Setup device white */ + if (nmask & ICX_ADDITIVE) + for (e = 0; e < di; e++) + val[e] = 1.0; + else + for (e = 0; e < di; e++) + val[e] = 0.0; + pdata->dev_to_XYZ(pdata, XYZ, val); /* Lookup white XYZ */ + + sprintf(buf,"%f %f %f", 100.0 * XYZ[0], 100.0 * XYZ[1], 100.0 * XYZ[2]); + pp->add_kword(pp, 0, "APPROX_WHITE_POINT", buf, NULL); + } + + pp->add_field(pp, 0, "SAMPLE_ID", cs_t); + pp->add_field(pp, 1, "INDEX", i_t); /* Index no. 0..7 in second table */ + pp->add_field(pp, 2, "INDEX", i_t); /* Index no. 0..7 in third table */ + + /* Setup CGATS fields */ + { + int j; + char c_ilimit[20]; + char *bident = icx_inkmask2char(xmask, 0); + + for (j = 0; j < di; j++) { + int imask; + char fname[100]; + + imask = icx_index2ink(xmask, j); + sprintf(fname,"%s_%s",nmask == ICX_W || nmask == ICX_K ? "GRAY" : bident, + icx_ink2char(imask)); + + pp->add_field(pp, 0, fname, r_t); + pp->add_field(pp, 1, fname, r_t); + pp->add_field(pp, 2, fname, r_t); + } + + pp->add_kword(pp, 0, "COLOR_REP", ident, NULL); + + if (ilimit >= 0.0) { + sprintf(c_ilimit,"%5.1f",ilimit * 100.0); + pp->add_kword(pp, 0, "TOTAL_INK_LIMIT", c_ilimit, NULL); + } + free(bident); + } + + /* ilimit is assumed to be in a valid range from here on */ + if (ilimit < 0.0) { + uilimit = ilimit = di; /* default is no limit */ + } + + /* Add expected XYZ values to aid previews, scan recognition & strip recognition */ + pp->add_field(pp, 0, "XYZ_X", r_t); + pp->add_field(pp, 0, "XYZ_Y", r_t); + pp->add_field(pp, 0, "XYZ_Z", r_t); + pp->add_field(pp, 1, "XYZ_X", r_t); + pp->add_field(pp, 1, "XYZ_Y", r_t); + pp->add_field(pp, 1, "XYZ_Z", r_t); + pp->add_field(pp, 2, "XYZ_X", r_t); + pp->add_field(pp, 2, "XYZ_Y", r_t); + pp->add_field(pp, 2, "XYZ_Z", r_t); + + /* Note if the expected values are expected to be accurate */ + if (pdata->is_specific(pdata)) + pp->add_kword(pp, 0, "ACCURATE_EXPECTED_VALUES", "true", NULL); + + if (xpow != 1.0) { + sprintf(buf,"%f",xpow); + pp->add_kword(pp, 0, "EXTRA_DEV_POW",buf, NULL); + } + + /* Only use optimsed full spread if <= 4 dimensions, else use ifarp */ + if (di > 4 + && userand == 0 /* Not other high D useful method */ + && useqrand == 0 + && usedsim == 0 + && usepsim == 0) + uselat = 1; + + /* Allocate space to record fixed steps */ + { + fxlist_a = 4; + if ((fxlist = (fxpos *)malloc(sizeof(fxpos) * fxlist_a)) == NULL) + error ("Failed to malloc fxlist"); + } + + /* White color patches */ + if (esteps > 0) { + int j, e; + + sprintf(buf,"%d",esteps); + pp->add_kword(pp, 0, "WHITE_COLOR_PATCHES",buf, NULL); + + for (j = 0; j < esteps; j++) { + double val[MXTD], XYZ[3]; + cgats_set_elem ary[1 + MXTD + 3]; + + if (nmask & ICX_ADDITIVE) { + for (e = 0; e < di; e++) { + val[e] = 1.0; /* White is full colorant */ + } + } else { + for (e = 0; e < di; e++) { + val[e] = 0.0; /* White is no colorant */ + } + } + + /* Apply general filter */ + if (filter && dofilt(pdata, filt, val)) + continue; + + sprintf(buf,"%d",id++); + ary[0].c = buf; + pdata->dev_to_XYZ(pdata, XYZ, val); /* Add expected XYZ */ + if (xmask == nmask) { + for (e = 0; e < di; e++) + ary[1 + e].d = 100.0 * icx_powlike(val[e],xpow); + } else { + for (e = 0; e < di; e++) + ary[1 + e].d = 100.0 * (1.0 - icx_powlike(val[e],xpow)); + } + ary[1 + di + 0].d = 100.0 * XYZ[0]; + ary[1 + di + 1].d = 100.0 * XYZ[1]; + ary[1 + di + 2].d = 100.0 * XYZ[2]; + + pp->add_setarr(pp, 0, ary); + + if (fxlist != NULL) { /* Note in fixed list */ + if (fxno >= fxlist_a) { + fxlist_a *= 2; + if ((fxlist = (fxpos *)realloc(fxlist, sizeof(fxpos) * fxlist_a)) == NULL) + error ("Failed to malloc fxlist"); + } + for (e = 0; e < di; e++) + fxlist[fxno].p[e] = val[e]; + fxno++; + } + } + } + + /* Primary wedge steps */ + if (ssteps > 0) { + sprintf(buf,"%d",ssteps); + pp->add_kword(pp, 0, "SINGLE_DIM_STEPS",buf, NULL); + for (j = 0; j < di; j++) { + for (i = 0; i < ssteps; i++) { + int addp, e; + double val[MXTD], XYZ[3]; + cgats_set_elem ary[1 + MXTD + 3]; + + addp = 1; /* Default add the point */ + + for (e = 0; e < di; e++) { + if (e == j) + val[e] = (double)i/(ssteps-1); + else + val[e] = 0.0; + } + + pdata->dev_to_XYZ(pdata, XYZ, val); /* Add expected XYZ */ + + /* See if it is already in the fixed list */ + if (fxlist != NULL) { + int k; + for (k = 0; k < fxno; k++) { + for (e = 0; e < di; e++) { + double tt; + tt = fabs(fxlist[k].p[e] - val[e]); + if (tt > MATCH_TOLL) + break; /* Not identical */ + } + if (e >= di) + break; /* Was identical */ + } + if (k < fxno) /* Found an identical patch */ + addp = 0; /* Don't add the point */ + } + + /* Apply general filter */ + if (filter && dofilt(pdata, filt, val)) + addp = 0; + + if (addp) { + + sprintf(buf,"%d",id++); + ary[0].c = buf; + if (xmask == nmask) { + for (e = 0; e < di; e++) + ary[1 + e].d = 100.0 * icx_powlike(val[e],xpow); + } else { + for (e = 0; e < di; e++) + ary[1 + e].d = 100.0 * (1.0 - icx_powlike(val[e],xpow)); + } + ary[1 + di + 0].d = 100.0 * XYZ[0]; + ary[1 + di + 1].d = 100.0 * XYZ[1]; + ary[1 + di + 2].d = 100.0 * XYZ[2]; + + pp->add_setarr(pp, 0, ary); + + if (fxlist != NULL) { /* Note in fixed list */ + if (fxno >= fxlist_a) { + fxlist_a *= 2; + if ((fxlist = (fxpos *)realloc(fxlist, sizeof(fxpos) * fxlist_a)) == NULL) + error ("Failed to malloc fxlist"); + } + for (e = 0; e < di; e++) + fxlist[fxno].p[e] = val[e]; + fxno++; + } + } + } + } + } + + /* Composite gray wedge steps */ + if (gsteps > 0) { + int cix[3]; /* Composite indexes */ + + sprintf(buf,"%d",gsteps); + pp->add_kword(pp, 0, "COMP_GREY_STEPS",buf, NULL); + + if (nmask & ICX_ADDITIVE) { /* Look for the RGB */ + cix[0] = icx_ink2index(nmask, ICX_RED); + cix[1] = icx_ink2index(nmask, ICX_GREEN); + cix[2] = icx_ink2index(nmask, ICX_BLUE); + + } else { /* Look for the CMY */ + cix[0] = icx_ink2index(nmask, ICX_CYAN); + cix[1] = icx_ink2index(nmask, ICX_MAGENTA); + cix[2] = icx_ink2index(nmask, ICX_YELLOW); + } + if (cix[0] < 0 || cix[1] < 0 || cix[2] < 0) + error("Composite grey wedges aren't appropriate for %s device\n",ident); + + for (i = 0; i < gsteps; i++) { + int addp, e; + double sum, val[MXTD], XYZ[3]; + cgats_set_elem ary[1 + MXTD + 3]; + + addp = 1; /* Default add the point */ + + for (e = 0; e < di; e++) { + if (e == cix[0] || e == cix[1] || e == cix[2]) + val[e] = (double)i/(gsteps-1); + else + val[e] = 0.0; + } + + /* Apply general filter */ + if (filter && dofilt(pdata, filt, val)) + addp = 0; + + pdata->dev_to_XYZ(pdata, XYZ, val); /* Add expected XYZ */ + + /* Compute sum that includes affect of power */ + for (sum = 0.0, e = 0; e < di; e++) + sum += icx_powlike(val[e], xpow); + + if (sum > uilimit) + addp = 0; + + /* See if it is already in the fixed list */ + if (fxlist != NULL) { + int k; + for (k = 0; k < fxno; k++) { + for (e = 0; e < di; e++) { + double tt; + tt = fabs(fxlist[k].p[e] - val[e]); + if (tt > MATCH_TOLL) + break; /* Not identical */ + } + if (e >= di) + break; /* Was identical */ + } + if (k < fxno) /* Found an identical patch */ + addp = 0; /* Don't add the point */ + } + + if (addp) { + + sprintf(buf,"%d",id++); + ary[0].c = buf; + if (xmask == nmask) { + for (e = 0; e < di; e++) + ary[1 + e].d = 100.0 * icx_powlike(val[e],xpow); + } else { + for (e = 0; e < di; e++) + ary[1 + e].d = 100.0 * (1.0 - icx_powlike(val[e],xpow)); + } + ary[1 + di + 0].d = 100.0 * XYZ[0]; + ary[1 + di + 1].d = 100.0 * XYZ[1]; + ary[1 + di + 2].d = 100.0 * XYZ[2]; + + pp->add_setarr(pp, 0, ary); + + if (fxlist != NULL) { /* Note in fixed list */ + if (fxno >= fxlist_a) { + fxlist_a *= 2; + if ((fxlist = (fxpos *)realloc(fxlist, sizeof(fxpos) * fxlist_a)) == NULL) + error ("Failed to malloc fxlist"); + } + for (e = 0; e < di; e++) + fxlist[fxno].p[e] = val[e]; + fxno++; + } + } + } + } + + /* Regular Gridded Multi dimension steps */ + if (msteps > 0) { + int gc[MXTD]; /* Grid coordinate */ + + sprintf(buf,"%d",msteps); + pp->add_kword(pp, 0, "MULTI_DIM_STEPS",buf, NULL); + + for (j = 0; j < di; j++) + gc[j] = 0; /* init coords */ + + for (;;) { /* For all grid points */ + double sum, val[MXTD], XYZ[3]; + int addp, e; + + addp = 1; /* Default add the point */ + + for (e = 0; e < di; e++) + val[e] = (double)gc[e]/(msteps-1); + + /* Apply general filter */ + if (filter && dofilt(pdata, filt, val)) + addp = 0; + + pdata->dev_to_XYZ(pdata, XYZ, val); /* Add expected XYZ */ + + /* Compute sum that includes affect of power */ + for (sum = 0.0, e = 0; e < di; e++) + sum += icx_powlike(val[e], xpow); + + if (sum > uilimit) + addp = 0; /* Don't add patches over ink limit */ + + /* See if it is already in the fixed list */ + if (addp && fxlist != NULL) { + int k; + for (k = 0; k < fxno; k++) { + for (e = 0; e < di; e++) { + double tt; + tt = fabs(fxlist[k].p[e] - val[e]); + if (tt > MATCH_TOLL) + break; /* Not identical */ + } + if (e >= di) + break; /* Was identical */ + } + if (k < fxno) /* Found an identical patch */ + addp = 0; /* Don't add the point */ + } + + /* Add patch to list if OK */ + if (addp) { + cgats_set_elem ary[1 + MXTD + 3]; + + sprintf(buf,"%d",id++); + ary[0].c = buf; + if (xmask == nmask) { + for (e = 0; e < di; e++) + ary[1 + e].d = 100.0 * icx_powlike(val[e],xpow); + } else { + for (e = 0; e < di; e++) + ary[1 + e].d = 100.0 * (1.0 - icx_powlike(val[e],xpow)); + } + ary[1 + di + 0].d = 100.0 * XYZ[0]; + ary[1 + di + 1].d = 100.0 * XYZ[1]; + ary[1 + di + 2].d = 100.0 * XYZ[2]; + + pp->add_setarr(pp, 0, ary); + + if (fxlist != NULL) { /* Note in fixed list */ + if (fxno >= fxlist_a) { + fxlist_a *= 2; + if ((fxlist = (fxpos *)realloc(fxlist, sizeof(fxpos) * fxlist_a)) == NULL) + error ("Failed to malloc fxlist"); + } + for (e = 0; e < di; e++) + fxlist[fxno].p[e] = val[e]; + fxno++; + } + } + + next_cpoint:; + /* Increment grid index and position */ + for (j = 0; j < di; j++) { + gc[j]++; + if (gc[j] < msteps) + break; /* No carry */ + gc[j] = 0; + } + if (j >= di) + break; /* Done grid */ + } + +#ifdef ADDRECCLIPPOINTS + /* Add extra points that intersect */ + /* grid, and lie on ink limit plane */ + /* !!!!!!!!!! this doesn't cope with xpow !!!!!!!!!!! */ + if (uilimit < (di * 100.0)) { + double val[MXTD], tv; + double XYZ[3]; + for (k = 0; k < di; k++) { /* dimension not on grid */ + for (j = 0; j < di; j++) + gc[j] = 0; /* init coords */ + + for (;;) { /* Until done */ + for (tv = 0.0, j = 0; j < di; j++) { + if (j != k) + tv += val[j] = (double)gc[j]/(msteps-1); + } + if (tv <= uilimit && (tv + 1.0) >= uilimit) { /* Will intersect */ + double fr; + val[k] = uilimit - tv; /* Point of intersection */ + fr = fmod((val[k] * msteps), 1.0); + if (fr > 0.05 && fr < 0.95) { /* Not within 5% of a grid point */ + int addp, e; + cgats_set_elem ary[1 + MXTD + 3]; + + addp = 1; /* Default add the point */ + + /* See if it is already in the fixed list */ + if (fxlist != NULL) { + int k; + for (k = 0; k < fxno; k++) { + for (e = 0; e < di; e++) { + double tt; + tt = fabs(fxlist[k].p[e] - val[e]); + if (tt > MATCH_TOLL) + break; /* Not identical */ + } + if (e >= di) + break; /* Was identical */ + } + if (k < fxno) { /* Found an identical patch */ + addp = 0; /* Don't add the point */ + } + } + + /* Apply general filter */ + if (filter && dofilt(pdata, filt, val)) { + addp = 0; + } + + if (addp) { + sprintf(buf,"%d",id++); + ary[0].c = buf; + pdata->dev_to_XYZ(pdata, XYZ, val); /* Add expected XYZ */ + if (xmask == nmask) { + for (e = 0; e < di; e++) + ary[1 + e].d = 100.0 * icx_powlike(val[e],xpow); + } else { + for (e = 0; e < di; e++) + ary[1 + e].d = 100.0 * (1.0 - icx_powlike(val[e],xpow)); + } + ary[1 + di + 0].d = 100.0 * XYZ[0]; + ary[1 + di + 1].d = 100.0 * XYZ[1]; + ary[1 + di + 2].d = 100.0 * XYZ[2]; + + pp->add_setarr(pp, 0, ary); + + if (fxlist != NULL) { /* Note in fixed list */ + if (fxno >= fxlist_a) { + fxlist_a *= 2; + if ((fxlist = (fxpos *)realloc(fxlist, sizeof(fxpos) * fxlist_a)) == NULL) + error ("Failed to malloc fxlist"); + } + for (e = 0; e < di; e++) + fxlist[fxno].p[e] = val[e]; + fxno++; + } + } + } + } + + /* Increment grid index and position */ + for (j = 0; j < di; j++) { + gc[j]++; + if (j != k && gc[j] < msteps) + break; /* No carry */ + gc[j] = 0; + } + if (j >= di) + break; /* ALL done */ + } + } + } +#endif /* ADDRECCLIPPOINTS */ + } + + if (fsteps > fxno) { /* Top up with full spread (perceptually even) and other patch types */ + + /* Generate device random numbers. Don't check for duplicates */ + if (userand == 1 || useqrand == 1) { + int i, j; + sobol *sl = NULL; + + if (useqrand) { + if ((sl = new_sobol(di)) == NULL) + error("Creating sobol sequence generator failed"); + } + + /* Create more points up to fsteps */ + if (verb) + printf("\n"); + for (j = 0, i = fxno; i < fsteps;) { + int e; + double sum; + double val[MXTD], XYZ[3]; + cgats_set_elem ary[1 + MXTD + 3]; + + if (sl != NULL) { + if (sl->next(sl, val)) + error("Run out of sobol random numbers!"); + } else { /* else uniform random distribution */ + for (e = 0; e < di; e++) + val[e] = d_rand(0.0, 1.0); + } + + /* Apply general filter */ + if (filter && dofilt(pdata, filt, val)) + continue; + + pdata->dev_to_XYZ(pdata, XYZ, val); /* Add expected XYZ */ + + /* Compute sum that includes the affect of power */ + for (sum = 0.0, e = 0; e < di; e++) + sum += icx_powlike(val[e], xpow); + + if (sum > uilimit) + continue; + + sprintf(buf,"%d",id++); + ary[0].c = buf; + if (xmask == nmask) { + for (e = 0; e < di; e++) + ary[1 + e].d = 100.0 * icx_powlike(val[e],xpow); + } else { + for (e = 0; e < di; e++) + ary[1 + e].d = 100.0 * (1.0 - icx_powlike(val[e],xpow)); + } + ary[1 + di + 0].d = 100.0 * XYZ[0]; + ary[1 + di + 1].d = 100.0 * XYZ[1]; + ary[1 + di + 2].d = 100.0 * XYZ[2]; + + pp->add_setarr(pp, 0, ary); + + if (fxlist != NULL) { /* Note in fixed list to allow stats later */ + if (fxno >= fxlist_a) { + fxlist_a *= 2; + if ((fxlist = (fxpos *)realloc(fxlist, sizeof(fxpos) * fxlist_a)) == NULL) + error ("Failed to malloc fxlist"); + } + for (e = 0; e < di; e++) + fxlist[fxno].p[e] = val[e]; + fxno++; + } + + if (verb) { + printf("%cAdded %d/%d",cr_char,i+1,fxno); fflush(stdout); + } + i++, j++; + } + if (verb) + printf("\n"); + + sprintf(buf,"%d",j); + if (sl != NULL) + pp->add_kword(pp, 0, "SPACEFILLING_RANDOM_PATCHES", buf, NULL); + else + pp->add_kword(pp, 0, "RANDOM_DEVICE_PATCHES", buf, NULL); + + if (sl != NULL) + sl->del(sl); + + } else { +// ppoint *s = NULL; + ofps *s = NULL; + ifarp *t = NULL; + simdlat *dx = NULL; + simplat *px = NULL; + prand *rx = NULL; + + /* (Note that the ink limit for these algorithms won't take into account the xpow), */ + /* and that we're not applying the filter until after generation, so the */ + /* number of patches won't reach the target. This could be fixed fairly easily */ + /* for some of these (new_prand). */ + if (uselat) { + /* A "greedy"/incremental far point approach */ + t = new_ifarp(di, uilimit, fsteps, fxlist, fxno, + (void(*)(void *, double *, double *))pdata->dev_to_perc, (void *)pdata); + sprintf(buf,"%d",fsteps - fxno); + pp->add_kword(pp, 0, "IFP_PATCHES", buf, NULL); + } else if (usedsim) { + /* Device space simplex latice test points */ + dx = new_simdlat(di, uilimit, fsteps, fxlist, fxno, SIMDLAT_TYPE, simangle, + (void(*)(void *, double *, double *))pdata->dev_to_perc, (void *)pdata); + sprintf(buf,"%d",fsteps - fxno); + pp->add_kword(pp, 0, "SIMPLEX_DEVICE_PATCHES", buf, NULL); + } else if (usepsim) { + /* Perceptual space simplex latice test points */ + px = new_simplat(di, uilimit, fsteps, fxlist, fxno, simangle, + (void(*)(void *, double *, double *))pdata->dev_to_perc, (void *)pdata); + sprintf(buf,"%d",fsteps - fxno); + pp->add_kword(pp, 0, "SIMPLEX_PERCEPTUAL_PATCHES", buf, NULL); + } else if (userand == 2 || useqrand == 2) { + /* Perceptual random test points */ + rx = new_prand(di, uilimit, fsteps, fxlist, fxno, useqrand == 2 ? 1 : 0, + (void(*)(void *, double *, double *))pdata->dev_to_perc, (void *)pdata); + sprintf(buf,"%d",fsteps - fxno); + pp->add_kword(pp, 0, "RANDOM_PERCEPTUAL_PATCHES", buf, NULL); + + } else { /* Default full spread algorithm */ + /* Optimised Farthest Point Sampling */ + s = new_ofps(verb, di, uilimit, fsteps, good, + dadapt, 1.0 - perc_wght, perc_wght, curv_wght, fxlist, fxno, + (void(*)(void *, double *, double *))pdata->dev_to_perc, (void *)pdata); + sprintf(buf,"%d",fsteps - fxno); + pp->add_kword(pp, 0, "OFPS_PATCHES", buf, NULL); + } + + + for (;;) { + int e; + double XYZ[3], val[MXTD]; + cgats_set_elem ary[1 + MXTD + 3]; + if (( s ? s->read(s, val, NULL) : + t ? t->read(t, val, NULL) : + dx ? dx->read(dx, val, NULL) : + rx ? rx->read(rx, val, NULL) : + px->read(px, val, NULL))) + break; + + /* Filter out silly values from ppoint */ + for (e = 0; e < di; e++) { + if (val[e] < 0.001) + val[e] = 0.0; + else if (val[e] > 0.999) + val[e] = 1.0; + } + + /* Apply general filter */ + if (filter && dofilt(pdata, filt, val)) + continue; + + pdata->dev_to_XYZ(pdata, XYZ, val); /* Add expected XYZ */ + + /* Do a simple ink limit that include the effect of xpow */ + if (uilimit < (double)di) { + double tot = 0.0; + for (e = 0; e < di; e++) + tot += icx_powlike(val[e],xpow); + if (tot > uilimit) { + for (e = 0; e < di; e++) + val[e] = icx_powlike(icx_powlike(val[e],xpow) * uilimit/tot, 1.0/xpow); + } + } + + sprintf(buf,"%d",id++); + ary[0].c = buf; + if (xmask == nmask) { + for (e = 0; e < di; e++) + ary[1 + e].d = 100.0 * icx_powlike(val[e],xpow); + } else { + for (e = 0; e < di; e++) + ary[1 + e].d = 100.0 * (1.0 - icx_powlike(val[e],xpow)); + } + ary[1 + di + 0].d = 100.0 * XYZ[0]; + ary[1 + di + 1].d = 100.0 * XYZ[1]; + ary[1 + di + 2].d = 100.0 * XYZ[2]; + + pp->add_setarr(pp, 0, ary); + + if (fxlist != NULL) { /* Note in fixed list to allow stats later */ + if (fxno >= fxlist_a) { + fxlist_a *= 2; + if ((fxlist = (fxpos *)realloc(fxlist, sizeof(fxpos) * fxlist_a)) == NULL) + error ("Failed to malloc fxlist"); + } + for (e = 0; e < di; e++) + fxlist[fxno].p[e] = val[e]; + fxno++; + } + } + (s ? s->del(s) : t ? t->del(t) : dx ? dx->del(dx) : rx ? rx->del(rx) : px->del(px)); + } + } + + /* Use ofps to measure the stats of the points */ + /* Note that if new_ofps() fails it will exit() */ + if (verb > 1 + && di <= 4 + && (userand || useqrand || usedsim || usepsim || uselat)) { + ofps *s; + printf("Computing device space point stats:\n"); + if ((s = new_ofps(verb, di, uilimit, fxno, 0, 0.0, 0.0, 0.0, 0.0, fxlist, fxno, + (void(*)(void *, double *, double *))pdata->dev_to_perc, (void *)pdata)) == NULL) { + printf("Failed to compute stats\n"); + } else { + s->stats(s); + printf("Max distance stats: Min = %f, Average = %f, Max = %f\n",s->mn,s->av,s->mx); + s->del(s); + } + } + + /* Add the eight entries in the second table. */ + /* These are legal device values that we think may */ + /* give all combinations of min/max CMY density values. */ + /* These are typically used for DTP51 and DTP41 patch separators. */ + { + int i; + + pp->add_kword(pp, 1, "DENSITY_EXTREME_VALUES", "8", NULL); + + for (i = 0; i < 8; i++) { + int e; + double den[4], val[MXTD], XYZ[3]; + cgats_set_elem ary[1 + MXTD + 3]; + + /* Setup target density combination */ + for (e = 0; e < 3; e++) { + if (i & (1 << e)) + den[e] = 2.5; + else + den[e] = -0.5; + } + + /* Lookup device values for target density */ + pdata->den_to_dev(pdata, val, den); + pdata->dev_to_XYZ(pdata, XYZ, val); /* Add expected XYZ */ + + /* Apply extra power */ + for (e = 0; e < di; e++) + val[e] = icx_powlike(val[e], xpow); + + /* Do a simple ink limit */ + if (uilimit < (double)di) { + double tot = 0.0; + for (e = 0; e < di; e++) + tot += val[e]; + if (tot > uilimit) { + for (e = 0; e < di; e++) + val[e] *= uilimit/tot; + } + } + + ary[0].i = i; + + if (xmask == nmask) { + for (e = 0; e < di; e++) + ary[1 + e].d = 100.0 * val[e]; + } else { + for (e = 0; e < di; e++) + ary[1 + e].d = 100.0 * (1.0 - val[e]); + } + ary[1 + di + 0].d = 100.0 * XYZ[0]; + ary[1 + di + 1].d = 100.0 * XYZ[1]; + ary[1 + di + 2].d = 100.0 * XYZ[2]; + + pp->add_setarr(pp, 1, ary); + + } + } + + /* Add the nine entries in the third table. */ + /* These are legal device values that we calculate */ + /* give all combinations of typical CMY device values + 50% CMY */ + /* These are typically use for DTP20 bar coding. */ + { + int i; + + icxColorantLu *ftarg = NULL; + + /* If not possible to use native space, use fake CMY */ + if ((nmask & ICX_CMYK) != ICX_CMYK + && (nmask & ICX_CMY) != ICX_CMY + && (nmask & ICX_RGB) != ICX_RGB) { + if ((ftarg = new_icxColorantLu(ICX_CMY)) == NULL) + error ("Creation of xcolorant lu object failed"); + } + + pp->add_kword(pp, 2, "DEVICE_COMBINATION_VALUES", "9", NULL); + + for (i = 0; i < 9; i++) { + int e; + double val[MXTD], lab[3], XYZ[3]; + cgats_set_elem ary[1 + MXTD + 3]; + + for (e = 0; e < di; e++) + val[e] = 0.0; + + /* Setup target device combination */ + /* Order must be White, Cyan, Magenta, Blue Yellow Green Red Black */ + if (ftarg != NULL || (nmask & ICX_CMY) == ICX_CMY) { + for (e = 0; e < 3; e++) { + if (i & (1 << e)) + val[e] = 1.0; + else + val[e] = 0.0; + } + if (i == 7) + val[3] = 1.0; + if (i == 8) { /* Special 50/50/50 grey for DTP20 */ + val[0] = val[1] = val[2] = 0.5; + val[3] = 0.0; + } + + } else { /* RGB like */ + for (e = 0; e < 3; e++) { + if (i & (1 << e)) + val[e] = 0.0; + else + val[e] = 1.0; + } + if (i == 8) + val[0] = val[1] = val[2] = 0.5; + } + + /* If target space isn't something we recognise, convert it */ + if (ftarg != NULL) { + ftarg->dev_to_rLab(ftarg, lab, val); + pdata->rLab_to_dev(pdata, val, lab); + } + + pdata->dev_to_XYZ(pdata, XYZ, val); /* Add expected XYZ */ + + /* Apply extra power */ + for (e = 0; e < di; e++) + val[e] = icx_powlike(val[e], xpow); + + /* Do a simple ink limit */ + if (uilimit < (double)di) { + double tot = 0.0; + for (e = 0; e < di; e++) + tot += val[e]; + if (tot > uilimit) { + for (e = 0; e < di; e++) + val[e] *= uilimit/tot; + } + } + + ary[0].i = i; + if (xmask == nmask) { + for (e = 0; e < di; e++) + ary[1 + e].d = 100.0 * val[e]; + } else { + for (e = 0; e < di; e++) + ary[1 + e].d = 100.0 * (1.0 - val[e]); + } + ary[1 + di + 0].d = 100.0 * XYZ[0]; + ary[1 + di + 1].d = 100.0 * XYZ[1]; + ary[1 + di + 2].d = 100.0 * XYZ[2]; + + pp->add_setarr(pp, 2, ary); + } + + if (ftarg != NULL) + ftarg->del(ftarg); + } + + ttime = clock() - stime; + if (verb) { + printf("Total number of patches = %d\n",id-1); + if (id < (1 + (1 << di))) + printf("WARNING : not enough patches for %d channels, need at least %d\n",di,(1 + (1 << di))); + printf("Execution time = %f seconds\n",ttime/(double)CLOCKS_PER_SEC); + } + + if (pp->write_name(pp, fname)) + error("Write error : %s",pp->err); + +#ifdef VRML_DIAG /* Dump a VRML of the resulting points */ + if (dumpvrml & 1) { /* Lab space */ + vrml *wrl; + int nsets = pp->t[0].nsets; + double rad; + double dev[MXTD], Lab[3], col[3]; + + wrl = new_vrml(wlname, 1, 0); /* Do axes */ + + /* Fudge sphere diameter */ + rad = 15.0/pow(nsets, 1.0/(double)(di <= 3 ? di : 3)); + + for (i = 0; i < nsets; i++) { + /* Re-do any inversion before using dev_to_rLab() */ + if (xmask == nmask) { + for (j = 0; j < di; j++) + dev[j] = 0.01 * *((double *)pp->t[0].fdata[i][j + 1]); + } else { + for (j = 0; j < di; j++) + dev[j] = 0.01 * (100.0 - *((double *)pp->t[0].fdata[i][j + 1])); + } + pdata->dev_to_rLab(pdata, Lab, dev); + wrl->Lab2RGB(wrl, col, Lab); + + wrl->add_marker(wrl, Lab, col, rad); + } + wrl->del(wrl); /* Write file and delete */ + } + if (dumpvrml & 2) { /* Device space */ + vrml *wrl; + int nsets = pp->t[0].nsets; + double rad; + double dev[MXTD], idev[MXTD], Lab[3], col[3]; + + wrl = new_vrml(wdname, 0, 0); + + /* Fudge sphere diameter */ + rad = 15.0/pow(nsets, 1.0/(double)(di <= 3 ? di : 3)); + + for (i = 0; i < nsets; i++) { + /* Re-do any inversion before using dev_to_rLab() */ + if (xmask == nmask) { + for (j = 0; j < di; j++) + idev[j] = dev[j] = 0.01 * *((double *)pp->t[0].fdata[i][j + 1]); + } else { + for (j = 0; j < di; j++) { + dev[j] = 0.01 * *((double *)pp->t[0].fdata[i][j + 1]); + idev[j] = 1.0 - dev[j]; + } + } + pdata->dev_to_rLab(pdata, Lab, idev); + wrl->Lab2RGB(wrl, col, Lab); + + /* Fudge device locations into Lab space */ + Lab[0] = 100.0 * dev[0]; + Lab[1] = 100.0 * dev[1] - 50.0; + Lab[2] = 100.0 * dev[2] - 50.0; + + wrl->add_marker(wrl, Lab, col, rad); + } + wrl->del(wrl); /* Write file and delete */ + } +#endif /* VRML_DIAG */ + + pdata->del(pdata); /* Cleanup perceptual conversion */ + + free(ident); + pp->del(pp); /* Clean up */ + if (fxlist != NULL) + free(fxlist); + + return 0; +} + + + + + + diff --git a/target/targen.h b/target/targen.h new file mode 100644 index 0000000..2f4665a --- /dev/null +++ b/target/targen.h @@ -0,0 +1,30 @@ + +#ifndef TARGEN_H +/* + * Argyll Color Correction System + * Test target chart Generator. + * + * Author: Graeme W. Gill + * Date: 7/4/2002 + * + * Copyright 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. + */ + + +/* Targen generation common defines */ + +#define MXTD ICX_MXINKS /* From xicc/xcolorants.h */ + +/* A fixed point position */ +struct _fxpos { + double p[MXTD]; /* Device coordinate position */ + double v[MXTD]; /* Room for perceptual value */ +}; typedef struct _fxpos fxpos; + + +#define TARGEN_H +#endif /* TARGEN_H */ -- cgit v1.2.3