summaryrefslogtreecommitdiff
path: root/target
diff options
context:
space:
mode:
Diffstat (limited to 'target')
-rw-r--r--target/ColorChecker.ti254
-rw-r--r--target/ECI2002.ti21578
-rw-r--r--target/ECI2002R.ti21517
-rw-r--r--target/FograStrip2.ti1120
-rw-r--r--target/FograStrip2_2.ti275
-rw-r--r--target/FograStrip3.ti1146
-rw-r--r--target/FograStrip3_3.ti2101
-rw-r--r--target/Jamfile74
-rw-r--r--target/License.txt662
-rw-r--r--target/Makefile.am28
-rw-r--r--target/Readme.txt13
-rw-r--r--target/afiles32
-rw-r--r--target/alphix.c468
-rw-r--r--target/alphix.h118
-rw-r--r--target/filmtarg.c458
-rw-r--r--target/i1_RGB_Scan_1.4.ti2319
-rw-r--r--target/ifarp.c946
-rw-r--r--target/ifarp.h67
-rw-r--r--target/ofps.c10222
-rw-r--r--target/ofps.h438
-rw-r--r--target/ppoint.c1056
-rw-r--r--target/ppoint.h134
-rw-r--r--target/prand.c604
-rw-r--r--target/prand.h68
-rw-r--r--target/printtarg.c4300
-rw-r--r--target/randix.c166
-rw-r--r--target/randix.h41
-rw-r--r--target/simdlat.c1063
-rw-r--r--target/simdlat.h94
-rw-r--r--target/simplat.c1095
-rw-r--r--target/simplat.h86
-rw-r--r--target/targen.c2224
-rw-r--r--target/targen.h30
33 files changed, 28397 insertions, 0 deletions
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. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU Affero General Public License is a free, copyleft license for
+software and other kinds of works, specifically designed to ensure
+cooperation with the community in the case of network server software.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+our General Public Licenses are intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ Developers that use our General Public Licenses protect your rights
+with two steps: (1) assert copyright on the software, and (2) offer
+you this License which gives you legal permission to copy, distribute
+and/or modify the software.
+
+ A secondary benefit of defending all users' freedom is that
+improvements made in alternate versions of the program, if they
+receive widespread use, become available for other developers to
+incorporate. Many developers of free software are heartened and
+encouraged by the resulting cooperation. However, in the case of
+software used on network servers, this result may fail to come about.
+The GNU General Public License permits making a modified version and
+letting the public access it on a server without ever releasing its
+source code to the public.
+
+ The GNU Affero General Public License is designed specifically to
+ensure that, in such cases, the modified source code becomes available
+to the community. It requires the operator of a network server to
+provide the source code of the modified version running there to the
+users of that server. Therefore, public use of a modified version, on
+a publicly accessible server, gives the public access to the source
+code of the modified version.
+
+ An older license, called the Affero General Public License and
+published by Affero, was designed to accomplish similar goals. This is
+a different license, not a version of the Affero GPL, but Affero has
+released a new version of the Affero GPL which permits relicensing under
+this license.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU Affero General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Remote Network Interaction; Use with the GNU General Public License.
+
+ Notwithstanding any other provision of this License, if you modify the
+Program, your modified version must prominently offer all users
+interacting with it remotely through a computer network (if your version
+supports such interaction) an opportunity to receive the Corresponding
+Source of your version by providing access to the Corresponding Source
+from a network server at no charge, through some standard or customary
+means of facilitating copying of software. This Corresponding Source
+shall include the Corresponding Source for any work covered by version 3
+of the GNU General Public License that is incorporated pursuant to the
+following paragraph.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the work with which it is combined will remain governed by version
+3 of the GNU General Public License.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU Affero General Public License from time to time. Such new versions
+will be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU Affero General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU Affero General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU Affero General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If your software can interact with users remotely through a computer
+network, you should also make sure that it provides a way for users to
+get its source. For example, if your program is a web application, its
+interface could display a "Source" link that leads users to an archive
+of the code. There are many ways you could offer source, and different
+solutions will be better for different programs; see section 13 for the
+specific requirements.
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU AGPL, see
+<http://www.gnu.org/licenses/>.
+
diff --git a/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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#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 <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+#if defined(__IBMC__)
+#include <float.h>
+#endif
+#include <string.h>
+#include <sys/types.h>
+#include <time.h>
+#include <ctype.h>
+#include "copyright.h"
+#include "aconfig.h"
+#include "cgats.h"
+#include "randix.h"
+#include "tiffio.h"
+
+#include <stdarg.h>
+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; x<pw; x++) {
+ int xx = 3*x;
+ scanline[xx++] = (unsigned short)((cols[ix].r * 65535.0) + 0.5);
+ scanline[xx++] = (unsigned short)((cols[ix].g * 65535.0) + 0.5);
+ scanline[xx] = (unsigned short)((cols[ix].b * 65535.0) + 0.5);
+ }
+ for (y=0; y<ph; y++) {
+ if (TIFFWriteScanline(tiff, (tdata_t)scanline, y, 0) < 0) {
+ fprintf(stderr,"WriteScanline Failed at line %d\n",y);
+ exit (-1);
+ }
+ }
+ TIFFClose(tiff);
+ free(scanline);
+
+ i++;
+ }
+
+ if (rand)
+ r->del(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 <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+#include <time.h>
+#if defined(__IBMC__)
+#include <float.h>
+#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 <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+#include <time.h>
+#if defined(__IBMC__)
+#include <float.h>
+#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 <iperf.h>
+
+#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 <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+#include <time.h>
+#if defined(__IBMC__)
+#include <float.h>
+#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 <stdarg.h>
+void error(char *fmt, ...), warning(char *fmt, ...), verbose(int level, char *fmt, ...);
+#else
+#include <varargs.h>
+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 <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+#include <time.h>
+#if defined(__IBMC__)
+#include <float.h>
+#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 <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+#if defined(__IBMC__)
+#include <float.h>
+#endif
+#include <string.h>
+#include <sys/types.h>
+#include <time.h>
+#include <ctype.h>
+#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 <stdarg.h>
+
+/* 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 <stdio.h>
+#include <stdlib.h>
+#include "randix.h"
+
+#include <stdarg.h>
+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 <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+#include <time.h>
+#if defined(__IBMC__)
+#include <float.h>
+#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 <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+#include <time.h>
+#if defined(__IBMC__)
+#include <float.h>
+#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 <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+#include <string.h>
+#include <sys/types.h>
+#include <time.h>
+#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 <stdarg.h>
+
+#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 */