summaryrefslogtreecommitdiff
path: root/gamut
diff options
context:
space:
mode:
Diffstat (limited to 'gamut')
-rw-r--r--gamut/GenRMGam.c786
-rw-r--r--gamut/GenVisGam.c189
-rw-r--r--gamut/Jamfile93
-rw-r--r--gamut/License.txt662
-rw-r--r--gamut/Makefile.am27
-rw-r--r--gamut/Readme.txt25
-rw-r--r--gamut/afiles19
-rw-r--r--gamut/fakegam.c325
-rw-r--r--gamut/gammap.c2552
-rw-r--r--gamut/gammap.h68
-rw-r--r--gamut/gammap.txt259
-rw-r--r--gamut/gamut.c7136
-rw-r--r--gamut/gamut.h406
-rw-r--r--gamut/isecvol.c320
-rw-r--r--gamut/maptest.c240
-rw-r--r--gamut/nearsmth.c3570
-rw-r--r--gamut/nearsmth.h274
-rw-r--r--gamut/smthtest.c460
-rw-r--r--gamut/surftest.c255
-rw-r--r--gamut/viewgam.c700
20 files changed, 18366 insertions, 0 deletions
diff --git a/gamut/GenRMGam.c b/gamut/GenRMGam.c
new file mode 100644
index 0000000..5a62eee
--- /dev/null
+++ b/gamut/GenRMGam.c
@@ -0,0 +1,786 @@
+
+/* Convert the proposed ISO 12640-3 Reference Medium Gamut to Argyll Lab Gamut */
+/* Copyright 2005 by Graeme W. Gill */
+/* All rights reserved. */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <fcntl.h>
+#include <string.h>
+#include <math.h>
+#include "aconfig.h"
+#include "numlib.h"
+#include "icc.h"
+#include "gamut.h"
+#if defined(__IBMC__) && defined(_M_IX86)
+#include <float.h>
+#endif
+
+/* Gamut surface points in LCh space */
+/* + black and white points */
+#define NPOINTS (684+2+3)
+
+double points[NPOINTS][3] = {
+ { 100, 0, 0 }, /* White */
+ { 3.1373, 0, 0 }, /* Black */
+ { 98.75, 30.75, 90 }, /* Three extra values along yellow ridge */
+ { 97.5, 61.5, 90 },
+ { 96.25, 92.25, 90 },
+ { 5, 10, 0 },
+ { 10, 25, 0 },
+ { 15, 38, 0 },
+ { 20, 50, 0 },
+ { 25, 62, 0 },
+ { 30, 73, 0 },
+ { 35, 80, 0 },
+ { 40, 86, 0 },
+ { 45, 89, 0 },
+ { 50, 86, 0 },
+ { 55, 85, 0 },
+ { 60, 81, 0 },
+ { 65, 73, 0 },
+ { 70, 65, 0 },
+ { 75, 55, 0 },
+ { 80, 45, 0 },
+ { 85, 34, 0 },
+ { 90, 23, 0 },
+ { 95, 12, 0 },
+ { 5, 10, 10 },
+ { 10, 24, 10 },
+ { 15, 37, 10 },
+ { 20, 50, 10 },
+ { 25, 61, 10 },
+ { 30, 71, 10 },
+ { 35, 80, 10 },
+ { 40, 88, 10 },
+ { 45, 90, 10 },
+ { 50, 89, 10 },
+ { 55, 85, 10 },
+ { 60, 81, 10 },
+ { 65, 73, 10 },
+ { 70, 65, 10 },
+ { 75, 55, 10 },
+ { 80, 45, 10 },
+ { 85, 34, 10 },
+ { 90, 22, 10 },
+ { 95, 11, 10 },
+ { 5, 10, 20 },
+ { 10, 22, 20 },
+ { 15, 36, 20 },
+ { 20, 50, 20 },
+ { 25, 62, 20 },
+ { 30, 73, 20 },
+ { 35, 80, 20 },
+ { 40, 90, 20 },
+ { 45, 93, 20 },
+ { 50, 91, 20 },
+ { 55, 86, 20 },
+ { 60, 83, 20 },
+ { 65, 73, 20 },
+ { 70, 70, 20 },
+ { 75, 55, 20 },
+ { 80, 50, 20 },
+ { 85, 35, 20 },
+ { 90, 25, 20 },
+ { 95, 11, 20 },
+ { 5, 9, 30 },
+ { 10, 22, 30 },
+ { 15, 34, 30 },
+ { 20, 48, 30 },
+ { 25, 61, 30 },
+ { 30, 74, 30 },
+ { 35, 86, 30 },
+ { 40, 98, 30 },
+ { 45, 99, 30 },
+ { 50, 101, 30 },
+ { 55, 95, 30 },
+ { 60, 90, 30 },
+ { 65, 81, 30 },
+ { 70, 73, 30 },
+ { 75, 65, 30 },
+ { 80, 52, 30 },
+ { 85, 35, 30 },
+ { 90, 30, 30 },
+ { 95, 12, 30 },
+ { 5, 8, 40 },
+ { 10, 20, 40 },
+ { 15, 33, 40 },
+ { 20, 46, 40 },
+ { 25, 60, 40 },
+ { 30, 73, 40 },
+ { 35, 83, 40 },
+ { 40, 93, 40 },
+ { 45, 96, 40 },
+ { 50, 101, 40 },
+ { 55, 99, 40 },
+ { 60, 97, 40 },
+ { 65, 90, 40 },
+ { 70, 83, 40 },
+ { 75, 71, 40 },
+ { 80, 60, 40 },
+ { 85, 44, 40 },
+ { 90, 30, 40 },
+ { 95, 15, 40 },
+ { 5, 8, 50 },
+ { 10, 20, 50 },
+ { 15, 30, 50 },
+ { 20, 42, 50 },
+ { 25, 54, 50 },
+ { 30, 66, 50 },
+ { 35, 77, 50 },
+ { 40, 88, 50 },
+ { 45, 95, 50 },
+ { 50, 99, 50 },
+ { 55, 101, 50 },
+ { 60, 100, 50 },
+ { 65, 97, 50 },
+ { 70, 92, 50 },
+ { 75, 83, 50 },
+ { 80, 72, 50 },
+ { 85, 55, 50 },
+ { 90, 37, 50 },
+ { 95, 19, 50 },
+ { 5, 7, 60 },
+ { 10, 17, 60 },
+ { 15, 26, 60 },
+ { 20, 36, 60 },
+ { 25, 46, 60 },
+ { 30, 55, 60 },
+ { 35, 64, 60 },
+ { 40, 75, 60 },
+ { 45, 80, 60 },
+ { 50, 90, 60 },
+ { 55, 95, 60 },
+ { 60, 100, 60 },
+ { 65, 101, 60 },
+ { 70, 102, 60 },
+ { 75, 98, 60 },
+ { 80, 90, 60 },
+ { 85, 72, 60 },
+ { 90, 50, 60 },
+ { 95, 25, 60 },
+ { 5, 6, 70 },
+ { 10, 15, 70 },
+ { 15, 25, 70 },
+ { 20, 32, 70 },
+ { 25, 42, 70 },
+ { 30, 50, 70 },
+ { 35, 58, 70 },
+ { 40, 68, 70 },
+ { 45, 75, 70 },
+ { 50, 82, 70 },
+ { 55, 90, 70 },
+ { 60, 95, 70 },
+ { 65, 100, 70 },
+ { 70, 104, 70 },
+ { 75, 107, 70 },
+ { 80, 109, 70 },
+ { 85, 100, 70 },
+ { 90, 74, 70 },
+ { 95, 34, 70 },
+ { 5, 5, 80 },
+ { 10, 13, 80 },
+ { 15, 23, 80 },
+ { 20, 30, 80 },
+ { 25, 37, 80 },
+ { 30, 47, 80 },
+ { 35, 55, 80 },
+ { 40, 62, 80 },
+ { 45, 70, 80 },
+ { 50, 77, 80 },
+ { 55, 85, 80 },
+ { 60, 90, 80 },
+ { 65, 95, 80 },
+ { 70, 103, 80 },
+ { 75, 106, 80 },
+ { 80, 110, 80 },
+ { 85, 113, 80 },
+ { 90, 110, 80 },
+ { 95, 70, 80 },
+ { 5, 5, 90 },
+ { 10, 13, 90 },
+ { 15, 21, 90 },
+ { 20, 30, 90 },
+ { 25, 36, 90 },
+ { 30, 45, 90 },
+ { 35, 55, 90 },
+ { 40, 60, 90 },
+ { 45, 64, 90 },
+ { 50, 73, 90 },
+ { 55, 80, 90 },
+ { 60, 88, 90 },
+ { 65, 95, 90 },
+ { 70, 100, 90 },
+ { 75, 106, 90 },
+ { 80, 111, 90 },
+ { 85, 117, 90 },
+ { 90, 120, 90 },
+ { 95, 123, 90 },
+ { 5, 5, 100 },
+ { 10, 14, 100 },
+ { 15, 21, 100 },
+ { 20, 30, 100 },
+ { 25, 35, 100 },
+ { 30, 45, 100 },
+ { 35, 53, 100 },
+ { 40, 60, 100 },
+ { 45, 65, 100 },
+ { 50, 73, 100 },
+ { 55, 80, 100 },
+ { 60, 87, 100 },
+ { 65, 94, 100 },
+ { 70, 100, 100 },
+ { 75, 106, 100 },
+ { 80, 109, 100 },
+ { 85, 113, 100 },
+ { 90, 108, 100 },
+ { 95, 90, 100 },
+ { 5, 6, 110 },
+ { 10, 14, 110 },
+ { 15, 22, 110 },
+ { 20, 30, 110 },
+ { 25, 37, 110 },
+ { 30, 46, 110 },
+ { 35, 52, 110 },
+ { 40, 61, 110 },
+ { 45, 67, 110 },
+ { 50, 75, 110 },
+ { 55, 82, 110 },
+ { 60, 89, 110 },
+ { 65, 96, 110 },
+ { 70, 100, 110 },
+ { 75, 103, 110 },
+ { 80, 106, 110 },
+ { 85, 107, 110 },
+ { 90, 102, 110 },
+ { 95, 75, 110 },
+ { 5, 6, 120 },
+ { 10, 15, 120 },
+ { 15, 22, 120 },
+ { 20, 31, 120 },
+ { 25, 40, 120 },
+ { 30, 49, 120 },
+ { 35, 57, 120 },
+ { 40, 65, 120 },
+ { 45, 73, 120 },
+ { 50, 80, 120 },
+ { 55, 87, 120 },
+ { 60, 93, 120 },
+ { 65, 98, 120 },
+ { 70, 100, 120 },
+ { 75, 101, 120 },
+ { 80, 96, 120 },
+ { 85, 88, 120 },
+ { 90, 73, 120 },
+ { 95, 50, 120 },
+ { 5, 6, 130 },
+ { 10, 15, 130 },
+ { 15, 24, 130 },
+ { 20, 34, 130 },
+ { 25, 44, 130 },
+ { 30, 53, 130 },
+ { 35, 63, 130 },
+ { 40, 72, 130 },
+ { 45, 80, 130 },
+ { 50, 87, 130 },
+ { 55, 93, 130 },
+ { 60, 97, 130 },
+ { 65, 101, 130 },
+ { 70, 99, 130 },
+ { 75, 94, 130 },
+ { 80, 84, 130 },
+ { 85, 72, 130 },
+ { 90, 55, 130 },
+ { 95, 30, 130 },
+ { 5, 7, 140 },
+ { 10, 18, 140 },
+ { 15, 27, 140 },
+ { 20, 37, 140 },
+ { 25, 47, 140 },
+ { 30, 57, 140 },
+ { 35, 67, 140 },
+ { 40, 77, 140 },
+ { 45, 85, 140 },
+ { 50, 95, 140 },
+ { 55, 98, 140 },
+ { 60, 101, 140 },
+ { 65, 97, 140 },
+ { 70, 93, 140 },
+ { 75, 85, 140 },
+ { 80, 75, 140 },
+ { 85, 61, 140 },
+ { 90, 42, 140 },
+ { 95, 21, 140 },
+ { 5, 7, 150 },
+ { 10, 18, 150 },
+ { 15, 29, 150 },
+ { 20, 40, 150 },
+ { 25, 51, 150 },
+ { 30, 61, 150 },
+ { 35, 71, 150 },
+ { 40, 81, 150 },
+ { 45, 92, 150 },
+ { 50, 97, 150 },
+ { 55, 99, 150 },
+ { 60, 96, 150 },
+ { 65, 91, 150 },
+ { 70, 85, 150 },
+ { 75, 76, 150 },
+ { 80, 66, 150 },
+ { 85, 52, 150 },
+ { 90, 37, 150 },
+ { 95, 19, 150 },
+ { 5, 7, 160 },
+ { 10, 20, 160 },
+ { 15, 30, 160 },
+ { 20, 42, 160 },
+ { 25, 53, 160 },
+ { 30, 66, 160 },
+ { 35, 79, 160 },
+ { 40, 92, 160 },
+ { 45, 96, 160 },
+ { 50, 99, 160 },
+ { 55, 97, 160 },
+ { 60, 90, 160 },
+ { 65, 87, 160 },
+ { 70, 79, 160 },
+ { 75, 69, 160 },
+ { 80, 59, 160 },
+ { 85, 46, 160 },
+ { 90, 32, 160 },
+ { 95, 19, 160 },
+ { 5, 8, 170 },
+ { 10, 20, 170 },
+ { 15, 30, 170 },
+ { 20, 41, 170 },
+ { 25, 52, 170 },
+ { 30, 63, 170 },
+ { 35, 73, 170 },
+ { 40, 83, 170 },
+ { 45, 91, 170 },
+ { 50, 96, 170 },
+ { 55, 93, 170 },
+ { 60, 89, 170 },
+ { 65, 82, 170 },
+ { 70, 75, 170 },
+ { 75, 65, 170 },
+ { 80, 55, 170 },
+ { 85, 42, 170 },
+ { 90, 29, 170 },
+ { 95, 15, 170 },
+ { 5, 8, 180 },
+ { 10, 20, 180 },
+ { 15, 29, 180 },
+ { 20, 40, 180 },
+ { 25, 51, 180 },
+ { 30, 62, 180 },
+ { 35, 72, 180 },
+ { 40, 81, 180 },
+ { 45, 86, 180 },
+ { 50, 92, 180 },
+ { 55, 90, 180 },
+ { 60, 86, 180 },
+ { 65, 79, 180 },
+ { 70, 71, 180 },
+ { 75, 61, 180 },
+ { 80, 51, 180 },
+ { 85, 38, 180 },
+ { 90, 25, 180 },
+ { 95, 13, 180 },
+ { 5, 8, 190 },
+ { 10, 20, 190 },
+ { 15, 29, 190 },
+ { 20, 40, 190 },
+ { 25, 49, 190 },
+ { 30, 60, 190 },
+ { 35, 68, 190 },
+ { 40, 76, 190 },
+ { 45, 81, 190 },
+ { 50, 87, 190 },
+ { 55, 85, 190 },
+ { 60, 82, 190 },
+ { 65, 75, 190 },
+ { 70, 69, 190 },
+ { 75, 60, 190 },
+ { 80, 50, 190 },
+ { 85, 38, 190 },
+ { 90, 26, 190 },
+ { 95, 13, 190 },
+ { 5, 8, 200 },
+ { 10, 20, 200 },
+ { 15, 28, 200 },
+ { 20, 38, 200 },
+ { 25, 47, 200 },
+ { 30, 56, 200 },
+ { 35, 63, 200 },
+ { 40, 70, 200 },
+ { 45, 75, 200 },
+ { 50, 80, 200 },
+ { 55, 78, 200 },
+ { 60, 78, 200 },
+ { 65, 72, 200 },
+ { 70, 66, 200 },
+ { 75, 58, 200 },
+ { 80, 49, 200 },
+ { 85, 38, 200 },
+ { 90, 27, 200 },
+ { 95, 14, 200 },
+ { 5, 8, 210 },
+ { 10, 20, 210 },
+ { 15, 28, 210 },
+ { 20, 37, 210 },
+ { 25, 45, 210 },
+ { 30, 53, 210 },
+ { 35, 60, 210 },
+ { 40, 66, 210 },
+ { 45, 72, 210 },
+ { 50, 79, 210 },
+ { 55, 80, 210 },
+ { 60, 76, 210 },
+ { 65, 69, 210 },
+ { 70, 64, 210 },
+ { 75, 57, 210 },
+ { 80, 49, 210 },
+ { 85, 38, 210 },
+ { 90, 27, 210 },
+ { 95, 14, 210 },
+ { 5, 8, 220 },
+ { 10, 20, 220 },
+ { 15, 27, 220 },
+ { 20, 37, 220 },
+ { 25, 44, 220 },
+ { 30, 51, 220 },
+ { 35, 57, 220 },
+ { 40, 64, 220 },
+ { 45, 70, 220 },
+ { 50, 76, 220 },
+ { 55, 73, 220 },
+ { 60, 71, 220 },
+ { 65, 68, 220 },
+ { 70, 63, 220 },
+ { 75, 56, 220 },
+ { 80, 48, 220 },
+ { 85, 38, 220 },
+ { 90, 27, 220 },
+ { 95, 14, 220 },
+ { 5, 8, 230 },
+ { 10, 20, 230 },
+ { 15, 28, 230 },
+ { 20, 38, 230 },
+ { 25, 45, 230 },
+ { 30, 52, 230 },
+ { 35, 57, 230 },
+ { 40, 65, 230 },
+ { 45, 69, 230 },
+ { 50, 75, 230 },
+ { 55, 72, 230 },
+ { 60, 71, 230 },
+ { 65, 66, 230 },
+ { 70, 61, 230 },
+ { 75, 54, 230 },
+ { 80, 46, 230 },
+ { 85, 36, 230 },
+ { 90, 26, 230 },
+ { 95, 13, 230 },
+ { 5, 9, 240 },
+ { 10, 20, 240 },
+ { 15, 29, 240 },
+ { 20, 40, 240 },
+ { 25, 48, 240 },
+ { 30, 55, 240 },
+ { 35, 61, 240 },
+ { 40, 66, 240 },
+ { 45, 71, 240 },
+ { 50, 74, 240 },
+ { 55, 70, 240 },
+ { 60, 66, 240 },
+ { 65, 61, 240 },
+ { 70, 56, 240 },
+ { 75, 49, 240 },
+ { 80, 41, 240 },
+ { 85, 32, 240 },
+ { 90, 23, 240 },
+ { 95, 12, 240 },
+ { 5, 11, 250 },
+ { 10, 22, 250 },
+ { 15, 32, 250 },
+ { 20, 42, 250 },
+ { 25, 50, 250 },
+ { 30, 59, 250 },
+ { 35, 65, 250 },
+ { 40, 70, 250 },
+ { 45, 73, 250 },
+ { 50, 70, 250 },
+ { 55, 68, 250 },
+ { 60, 62, 250 },
+ { 65, 58, 250 },
+ { 70, 52, 250 },
+ { 75, 45, 250 },
+ { 80, 38, 250 },
+ { 85, 29, 250 },
+ { 90, 21, 250 },
+ { 95, 11, 250 },
+ { 5, 14, 260 },
+ { 10, 27, 260 },
+ { 15, 38, 260 },
+ { 20, 48, 260 },
+ { 25, 56, 260 },
+ { 30, 63, 260 },
+ { 35, 67, 260 },
+ { 40, 72, 260 },
+ { 45, 73, 260 },
+ { 50, 69, 260 },
+ { 55, 65, 260 },
+ { 60, 60, 260 },
+ { 65, 56, 260 },
+ { 70, 50, 260 },
+ { 75, 43, 260 },
+ { 80, 35, 260 },
+ { 85, 27, 260 },
+ { 90, 20, 260 },
+ { 95, 10, 260 },
+ { 5, 16, 270 },
+ { 10, 32, 270 },
+ { 15, 44, 270 },
+ { 20, 55, 270 },
+ { 25, 62, 270 },
+ { 30, 70, 270 },
+ { 35, 75, 270 },
+ { 40, 74, 270 },
+ { 45, 72, 270 },
+ { 50, 68, 270 },
+ { 55, 66, 270 },
+ { 60, 59, 270 },
+ { 65, 54, 270 },
+ { 70, 48, 270 },
+ { 75, 41, 270 },
+ { 80, 33, 270 },
+ { 85, 26, 270 },
+ { 90, 18, 270 },
+ { 95, 9, 270 },
+ { 5, 21, 280 },
+ { 10, 42, 280 },
+ { 15, 55, 280 },
+ { 20, 68, 280 },
+ { 25, 73, 280 },
+ { 30, 81, 280 },
+ { 35, 80, 280 },
+ { 40, 78, 280 },
+ { 45, 76, 280 },
+ { 50, 70, 280 },
+ { 55, 67, 280 },
+ { 60, 60, 280 },
+ { 65, 55, 280 },
+ { 70, 49, 280 },
+ { 75, 41, 280 },
+ { 80, 33, 280 },
+ { 85, 26, 280 },
+ { 90, 18, 280 },
+ { 95, 9, 280 },
+ { 5, 26, 290 },
+ { 10, 52, 290 },
+ { 15, 66, 290 },
+ { 20, 83, 290 },
+ { 25, 84, 290 },
+ { 30, 89, 290 },
+ { 35, 87, 290 },
+ { 40, 84, 290 },
+ { 45, 80, 290 },
+ { 50, 75, 290 },
+ { 55, 69, 290 },
+ { 60, 63, 290 },
+ { 65, 57, 290 },
+ { 70, 50, 290 },
+ { 75, 42, 290 },
+ { 80, 34, 290 },
+ { 85, 26, 290 },
+ { 90, 18, 290 },
+ { 95, 9, 290 },
+ { 5, 25, 300 },
+ { 10, 65, 300 },
+ { 15, 79, 300 },
+ { 20, 95, 300 },
+ { 25, 93, 300 },
+ { 30, 92, 300 },
+ { 35, 90, 300 },
+ { 40, 87, 300 },
+ { 45, 85, 300 },
+ { 50, 77, 300 },
+ { 55, 73, 300 },
+ { 60, 66, 300 },
+ { 65, 59, 300 },
+ { 70, 52, 300 },
+ { 75, 44, 300 },
+ { 80, 36, 300 },
+ { 85, 28, 300 },
+ { 90, 19, 300 },
+ { 95, 10, 300 },
+ { 5, 20, 310 },
+ { 10, 50, 310 },
+ { 15, 67, 310 },
+ { 20, 91, 310 },
+ { 25, 97, 310 },
+ { 30, 100, 310 },
+ { 35, 98, 310 },
+ { 40, 95, 310 },
+ { 45, 90, 310 },
+ { 50, 84, 310 },
+ { 55, 77, 310 },
+ { 60, 70, 310 },
+ { 65, 63, 310 },
+ { 70, 55, 310 },
+ { 75, 47, 310 },
+ { 80, 39, 310 },
+ { 85, 30, 310 },
+ { 90, 20, 310 },
+ { 95, 10, 310 },
+ { 5, 18, 320 },
+ { 10, 41, 320 },
+ { 15, 60, 320 },
+ { 20, 79, 320 },
+ { 25, 90, 320 },
+ { 30, 102, 320 },
+ { 35, 101, 320 },
+ { 40, 98, 320 },
+ { 45, 94, 320 },
+ { 50, 89, 320 },
+ { 55, 83, 320 },
+ { 60, 76, 320 },
+ { 65, 68, 320 },
+ { 70, 60, 320 },
+ { 75, 51, 320 },
+ { 80, 42, 320 },
+ { 85, 32, 320 },
+ { 90, 21, 320 },
+ { 95, 11, 320 },
+ { 5, 16, 330 },
+ { 10, 35, 330 },
+ { 15, 53, 330 },
+ { 20, 71, 330 },
+ { 25, 82, 330 },
+ { 30, 91, 330 },
+ { 35, 99, 330 },
+ { 40, 104, 330 },
+ { 45, 102, 330 },
+ { 50, 98, 330 },
+ { 55, 90, 330 },
+ { 60, 84, 330 },
+ { 65, 76, 330 },
+ { 70, 67, 330 },
+ { 75, 57, 330 },
+ { 80, 47, 330 },
+ { 85, 36, 330 },
+ { 90, 24, 330 },
+ { 95, 12, 330 },
+ { 5, 14, 340 },
+ { 10, 31, 340 },
+ { 15, 45, 340 },
+ { 20, 61, 340 },
+ { 25, 71, 340 },
+ { 30, 82, 340 },
+ { 35, 91, 340 },
+ { 40, 101, 340 },
+ { 45, 103, 340 },
+ { 50, 99, 340 },
+ { 55, 95, 340 },
+ { 60, 89, 340 },
+ { 65, 80, 340 },
+ { 70, 71, 340 },
+ { 75, 61, 340 },
+ { 80, 50, 340 },
+ { 85, 38, 340 },
+ { 90, 26, 340 },
+ { 95, 13, 340 },
+ { 5, 12, 350 },
+ { 10, 28, 350 },
+ { 15, 41, 350 },
+ { 20, 54, 350 },
+ { 25, 65, 350 },
+ { 30, 76, 350 },
+ { 35, 83, 350 },
+ { 40, 93, 350 },
+ { 45, 95, 350 },
+ { 50, 92, 350 },
+ { 55, 90, 350 },
+ { 60, 85, 350 },
+ { 65, 77, 350 },
+ { 70, 68, 350 },
+ { 75, 58, 350 },
+ { 80, 48, 350 },
+ { 85, 36, 350 },
+ { 90, 24, 350 },
+ { 95, 12, 350 }
+};
+
+/* selected "cusp" points */
+double cpoints[6][3] = {
+ { 50, 101, 36 },
+ { 95, 123, 90 },
+ { 60, 101, 140 },
+ { 55, 80, 210 },
+ { 20, 95, 300 },
+ { 40, 104, 330 },
+};
+
+/* Our idealised cusp point angles for Lab in degrees: */
+/* 36.0, Red */
+/* 101.0, Yellow */
+/* 149.0, Green */
+/* 225.0, Cyan */
+/* 300.0, Blue */
+/* 337.0 Magenta */
+
+#define GAMRES 1.0 /* Default surface resolution */
+
+int
+main(int argc, char *argv[]) {
+ int i;
+ char out_name[MAXNAMEL+1]; /* VRML output file */
+
+ double gamres = GAMRES; /* Surface resolution */
+ gamut *gam;
+
+
+#if defined(__IBMC__) && defined(_M_IX86)
+ _control87(EM_UNDERFLOW, EM_UNDERFLOW);
+#endif
+
+ if (argc < 2) {
+ strcpy(out_name, "RefMediumGamut.gam");
+ } else {
+ strncpy(out_name,argv[1],MAXNAMEL); out_name[MAXNAMEL] = '\000';
+ }
+
+ /* Creat a gamut surface */
+ gam = new_gamut(gamres, 0, 0);
+
+ /* For all the supplied surface points, */
+ /* add them to the gamut */
+ for (i = 0; i < NPOINTS; i++) {
+ double lab[3];
+
+ icmLCh2Lab(lab, points[i]);
+//printf("~1 LCh %f %f %f -> Lab %f %f %f\n", points[i][0], points[i][1], points[i][2], lab[0], lab[1], lab[2]);
+ gam->expand(gam, lab);
+ }
+
+ /* Add selected cusp points to set cusp values */
+ gam->setcusps(gam, 0, NULL);
+ for (i = 0; i < 6; i++) {
+ double lab[3];
+
+ icmLCh2Lab(lab, cpoints[i]);
+//printf("~1 LCh %f %f %f -> Lab %f %f %f\n", points[i][0], points[i][1], points[i][2], lab[0], lab[1], lab[2]);
+ gam->setcusps(gam, 1, lab);
+ }
+ gam->setcusps(gam, 2, NULL);
+
+ if (gam->write_gam(gam, out_name))
+ error ("write gamut failed on '%s'",out_name);
+
+ gam->del(gam);
+
+ return 0;
+}
+
diff --git a/gamut/GenVisGam.c b/gamut/GenVisGam.c
new file mode 100644
index 0000000..12d8d9d
--- /dev/null
+++ b/gamut/GenVisGam.c
@@ -0,0 +1,189 @@
+
+/* Generate the visual gamut in L*a*b* space */
+/* Copyright 2006 by Graeme W. Gill */
+/* All rights reserved. */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <fcntl.h>
+#include <string.h>
+#include <math.h>
+#include "numlib.h"
+#include "icc.h"
+#include "gamut.h"
+#if defined(__IBMC__) && defined(_M_IX86)
+#include <float.h>
+#endif
+
+/* 2 degree spectrum locus in xy coordinates */
+/* nm, x, y, Y CMC */
+double sl[65][4] = {
+ { 380, 0.1741, 0.0050, 0.000039097450 },
+ { 385, 0.1740, 0.0050, 0.000065464490 },
+ { 390, 0.1738, 0.0049, 0.000121224052 },
+ { 395, 0.1736, 0.0049, 0.000221434140 },
+ { 400, 0.1733, 0.0048, 0.000395705080 },
+ { 405, 0.1730, 0.0048, 0.000656030940 },
+ { 410, 0.1726, 0.0048, 0.001222776600 },
+ { 415, 0.1721, 0.0048, 0.002210898200 },
+ { 420, 0.1714, 0.0051, 0.004069952000 },
+ { 425, 0.1703, 0.0058, 0.007334133400 },
+ { 430, 0.1689, 0.0069, 0.011637600000 },
+ { 435, 0.1669, 0.0086, 0.016881322000 },
+ { 440, 0.1644, 0.0109, 0.023015402000 },
+ { 445, 0.1611, 0.0138, 0.029860866000 },
+ { 450, 0.1566, 0.0177, 0.038072300000 },
+ { 455, 0.1510, 0.0227, 0.048085078000 },
+ { 460, 0.1440, 0.0297, 0.060063754000 },
+ { 465, 0.1355, 0.0399, 0.074027114000 },
+ { 470, 0.1241, 0.0578, 0.091168598000 },
+ { 475, 0.1096, 0.0868, 0.112811680000 },
+ { 480, 0.0913, 0.1327, 0.139122260000 },
+ { 485, 0.0686, 0.2007, 0.169656160000 },
+ { 490, 0.0454, 0.2950, 0.208513180000 },
+ { 495, 0.0235, 0.4127, 0.259083420000 },
+ { 500, 0.0082, 0.5384, 0.323943280000 },
+ { 505, 0.0039, 0.6548, 0.407645120000 },
+ { 510, 0.0139, 0.7502, 0.503483040000 },
+ { 515, 0.0389, 0.8120, 0.608101540000 },
+ { 520, 0.0743, 0.8338, 0.709073280000 },
+ { 525, 0.1142, 0.8262, 0.792722560000 },
+ { 530, 0.1547, 0.8059, 0.861314320000 },
+ { 535, 0.1929, 0.7816, 0.914322820000 },
+ { 540, 0.2296, 0.7543, 0.953482260000 },
+ { 545, 0.2658, 0.7243, 0.979818740000 },
+ { 550, 0.3016, 0.6923, 0.994576720000 },
+ { 555, 0.3373, 0.6589, 0.999604300000 },
+ { 560, 0.3731, 0.6245, 0.994513460000 },
+ { 565, 0.4087, 0.5896, 0.978204680000 },
+ { 570, 0.4441, 0.5547, 0.951588260000 },
+ { 575, 0.4788, 0.5202, 0.915060800000 },
+ { 580, 0.5125, 0.4866, 0.869647940000 },
+ { 585, 0.5448, 0.4544, 0.816076000000 },
+ { 590, 0.5752, 0.4242, 0.756904640000 },
+ { 595, 0.6029, 0.3965, 0.694818180000 },
+ { 600, 0.6270, 0.3725, 0.630997820000 },
+ { 605, 0.6482, 0.3514, 0.566802360000 },
+ { 610, 0.6658, 0.3340, 0.503096860000 },
+ { 615, 0.6801, 0.3197, 0.441279360000 },
+ { 620, 0.6915, 0.3083, 0.380961920000 },
+ { 625, 0.7006, 0.2993, 0.321156580000 },
+ { 630, 0.7079, 0.2920, 0.265374180000 },
+ { 635, 0.7140, 0.2859, 0.217219520000 },
+ { 640, 0.7190, 0.2809, 0.175199900000 },
+ { 645, 0.7230, 0.2770, 0.138425720000 },
+ { 650, 0.7260, 0.2740, 0.107242628000 },
+ { 655, 0.7283, 0.2717, 0.081786794000 },
+ { 660, 0.7300, 0.2700, 0.061166218000 },
+ { 665, 0.7311, 0.2689, 0.044729418000 },
+ { 670, 0.7320, 0.2680, 0.032160714000 },
+ { 675, 0.7327, 0.2673, 0.023307860000 },
+ { 680, 0.7334, 0.2666, 0.017028548000 },
+ { 685, 0.7340, 0.2660, 0.011981432000 },
+ { 690, 0.7344, 0.2656, 0.008259734600 },
+ { 695, 0.7346, 0.2654, 0.005758363200 },
+ { 700, 0.7347, 0.2653, 0.004117206200 }
+};
+
+#define GAMRES 5.0 /* Default surface resolution */
+
+int
+main(int argc, char *argv[]) {
+ int i, j, e;
+ double Yxy[3], XYZ[3], Lab[3];
+ char out_name[100]; /* VRML output file */
+ double big[3] = { 0.0, 0.0, 0.0 }, bc;
+ double low[3] = { 1e6, 1e6, 1e6 };
+ double high[3] = { -1e6, -1e6, -1e6 };
+// double limit = 5.0; /* Limit multiplier of light at maximum sensitivity */
+
+ double gamres = GAMRES; /* Surface resolution */
+ gamut *gam;
+
+ strcpy(out_name, "VisGamut.gam");
+
+ /* Creat a gamut surface */
+ gam = new_gamut(gamres, 0, 0);
+
+ bc = 0.0;
+
+ /* The monochromic boundary with a unit energy monochromic source */
+ for (j = 0; j < 50; j++) {
+ double Y = j/(50 - 1.0);
+
+ Y = Y * Y;
+
+ for (i = 0; i < 65; i++) {
+// Yxy[0] = Y * limit * sl[i][3];
+// if (Yxy[0] > Y)
+ Yxy[0] = Y;
+ Yxy[1] = sl[i][1];
+ Yxy[2] = sl[i][2];
+
+ icmYxy2XYZ(XYZ, Yxy);
+ icmXYZ2Lab(&icmD50, Lab, XYZ);
+
+ gam->expand(gam, Lab);
+
+ for (e = 0; e < 3; e++) {
+ if (Lab[e] < low[e])
+ low[e] = Lab[e];
+ if (Lab[e] > high[e])
+ high[e] = Lab[e];
+ }
+ if ((Lab[1] * Lab[1] + Lab[2] * Lab[2]) > bc) {
+ bc = Lab[1] * Lab[1] + Lab[2] * Lab[2];
+ big[0] = Lab[0], big[1] = Lab[1], big[2] = Lab[2];
+ }
+ }
+
+ /* Do the purple line */
+ for (i = 0; i < 20; i++) {
+ double b = i/(20 - 1.0);
+
+// Yxy[0] = (b * sl[0][3] + (1.0 - b) * sl[64][3]) * limit * Y;
+// if (Yxy[0] > Y)
+ Yxy[0] = Y;
+ Yxy[1] = b * sl[0][1] + (1.0 - b) * sl[64][1];
+ Yxy[2] = b * sl[0][2] + (1.0 - b) * sl[64][2];
+ icmYxy2XYZ(XYZ, Yxy);
+ icmXYZ2Lab(&icmD50, Lab, XYZ);
+
+ gam->expand(gam, Lab);
+
+ for (e = 0; e < 3; e++) {
+ if (Lab[e] < low[e])
+ low[e] = Lab[e];
+ if (Lab[e] > high[e])
+ high[e] = Lab[e];
+ }
+ if ((Lab[1] * Lab[1] + Lab[2] * Lab[2]) > bc) {
+ bc = Lab[1] * Lab[1] + Lab[2] * Lab[2];
+ big[0] = Lab[0], big[1] = Lab[1], big[2] = Lab[2];
+ }
+ }
+ }
+
+ /* How to fill in less saturated, lighter area of gamut within */
+ /* our maximum monochrome level limit ? */
+ /* ~~~999 */
+
+ /* Fill in white point */
+ Lab[0] = 100.0;
+ Lab[1] = 0.0;
+ Lab[2] = 0.0;
+ gam->expand(gam, Lab);
+
+ printf("Bigest = %f %f %f\n",big[0],big[1],big[2]);
+ printf("Low = %f %f %f\n",low[0],low[1],low[2]);
+ printf("High = %f %f %f\n",high[0],high[1],high[2]);
+
+ if (gam->write_gam(gam, out_name))
+ error ("write gamut failed on '%s'",out_name);
+
+ gam->del(gam);
+
+ return 0;
+}
+
diff --git a/gamut/Jamfile b/gamut/Jamfile
new file mode 100644
index 0000000..a7273f3
--- /dev/null
+++ b/gamut/Jamfile
@@ -0,0 +1,93 @@
+
+
+# Optimization and Debug flags
+
+#PREF_CCFLAGS += $(CCOPTFLAG) ; # Turn optimisation on
+PREF_CCFLAGS += $(CCDEBUGFLAG) ; # Debugging flags
+#PREF_CCFLAGS += $(CCHEAPDEBUG) ; # Heap Debugging flags
+PREF_LINKFLAGS += $(LINKDEBUGFLAG) ; # Link with debug info
+#PREF_CCFLAGS += $(CCPROFFLAG) ; # Profile flags
+#PREF_LINKFLAGS += $(LINKPROFFLAG) ; # Profile flags
+
+#Products
+Libraries = libgamut libgammap ;
+Executables = viewgam ;
+Samples = RefMediumGamut.gam ;
+Headers = gammap.h gamut.h ;
+
+#Install
+InstallBin $(DESTDIR)$(PREFIX)/bin : $(Executables) ;
+InstallFile $(DESTDIR)$(PREFIX)/$(REFSUBDIR) : $(Samples) ;
+#InstallFile $(DESTDIR)$(PREFIX)/h : $(Headers) ;
+#InstallLib $(DESTDIR)$(PREFIX)lib : $(Libraries) ;
+
+# Header search path
+HDRS = ../h ../icc ../rspl ../numlib ../plot ../xicc ../cgats ../spectro ../gamut ;
+
+# Gamut handling library
+Library libgamut : gamut.c ;
+
+# Gamut mapping library
+Library libgammap : gammap.c nearsmth.c ;
+
+LINKLIBS = libgammap libgamut ../rspl/librspl ../icc/libicc ../cgats/libcgats ../numlib/libnum
+ ../plot/libvrml ;
+
+# Utilities
+Main viewgam : viewgam.c ;
+
+# Link all the tests and utils with these libraries
+
+# Smoothed nearpoint test routine
+Main smthtest : smthtest.c ;
+
+# Preliminary ICC V4 Reference Medium Gamut
+Main GenRMGam : GenRMGam.c ;
+
+# Generate referenec medium gamut the kernel files
+# (NoUpdate so that Cross Compile Win64 hack works)
+NNoUpdate RefMediumGamut.gam ;
+GenFile RefMediumGamut.gam : GenRMGam ;
+
+# Visual gamut
+Main GenVisGam : GenVisGam.c ;
+
+# Develop hue sensitive parameter interpolation */
+#Main tttt : tttt.c ;
+
+LINKLIBS = libgammap libgamut ../icc/libicc ../cgats/libcgats ../xicc/libxicc
+ ../rspl/librspl ../numlib/libnum ../plot/libplot ../plot/libvrml ;
+
+# Mapping test routine
+Main maptest : maptest.c ;
+
+# Fake test gamut generatio
+Main fakegam : fakegam.c ;
+
+# Surfacing test routine
+Main surftest : surftest.c ;
+
+# Filtering test cpde
+#Main filt : filt.c ;
+
+#Main tt : tt.c ;
+
+if $(BUILD_JUNK) {
+
+ # Gamut creation test routine
+ Main gamtest : gamtest.c ;
+
+ # Group finding test.
+ Main gtest : gtest.c ;
+
+ # Test routine
+ Main test : test.c ;
+
+ Main tt : tt.c ;
+
+ # Atan aproximation test
+ Main xtan : xtan.c ;
+
+ # Bit vector class test
+ Main bvtest : bvtest.c ;
+}
diff --git a/gamut/License.txt b/gamut/License.txt
new file mode 100644
index 0000000..a871fcf
--- /dev/null
+++ b/gamut/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/gamut/Makefile.am b/gamut/Makefile.am
new file mode 100644
index 0000000..266ae82
--- /dev/null
+++ b/gamut/Makefile.am
@@ -0,0 +1,27 @@
+include $(top_srcdir)/Makefile.shared
+
+privatelib_LTLIBRARIES = libgamut.la libgammap.la
+privatelibdir = $(pkglibdir)
+
+libgamut_la_SOURCES = gamut.h gamut.c
+libgamut_la_LIBADD = ../cgats/libcgats.la $(ICC_LIBS) \
+ ../numlib/libargyllnum.la
+
+libgammap_la_SOURCES = gammap.h gammap.c nearsmth.c nearsmth.h
+libgammap_la_LIBADD = ./libgamut.la $(ICC_LIBS) \
+ ../numlib/libargyllnum.la ../plot/libvrml.la \
+ ../rspl/librspl.la ../libargyll.la ../cgats/libcgats.la
+
+LDADD = ./libgamut.la ./libgammap.la $(ICC_LIBS) ../cgats/libcgats.la \
+ ../rspl/librspl.la ../plot/libvrml.la ../xicc/libxicc.la \
+ ../spectro/libinsttypes.la ../numlib/libargyllnum.la
+
+bin_PROGRAMS = viewgam
+
+check_PROGRAMS = smthtest GenRMGam GenVisGam maptest surftest fakegam
+
+refdir = $(datadir)/color/argyll/ref
+
+##ref_DATA = RefMediumGamut.gam
+
+EXTRA_DIST = License.txt Readme.txt
diff --git a/gamut/Readme.txt b/gamut/Readme.txt
new file mode 100644
index 0000000..56e7e13
--- /dev/null
+++ b/gamut/Readme.txt
@@ -0,0 +1,25 @@
+This directory contains the gamut boundary
+finding, and gamut mapping code.
+It also contains utilities for generating
+3D visualisations of gamut boundaries.
+
+ iccgamut:
+ Take an icc profile, and derive a gamut surface from one
+ of the forward or reverse transforms, using the desired
+ intent. Output the result as a CGATS format .gam file,
+ and optionaly as a VRML model file.
+
+ tiffgamut:
+ Take an tiff file and an icc profile, and derive a gamut
+ surface from the forward absolute profile and the tiff
+ image. Output the result as a CGATS format .gam file,
+ and optionaly as a VRML model file.
+
+ viewgam:
+
+ This program reads one or more CGATS format triangular gamut
+ surface descriptions (.gam), and combines them into a VRML file,
+ so that the gamuts can be visually compared.
+ Allowance is made for representing the gamuts as solid or
+ tranparent surfaces, wire frames, and coloring the gamut
+ with natural or uniform colors.
diff --git a/gamut/afiles b/gamut/afiles
new file mode 100644
index 0000000..951926b
--- /dev/null
+++ b/gamut/afiles
@@ -0,0 +1,19 @@
+Readme.txt
+gammap.txt
+License.txt
+afiles
+Jamfile
+gamut.c
+gamut.h
+isecvol.c
+viewgam.c
+gammap.c
+gammap.h
+nearsmth.c
+nearsmth.h
+maptest.c
+smthtest.c
+surftest.c
+fakegam.c
+GenRMGam.c
+GenVisGam.c
diff --git a/gamut/fakegam.c b/gamut/fakegam.c
new file mode 100644
index 0000000..ec40457
--- /dev/null
+++ b/gamut/fakegam.c
@@ -0,0 +1,325 @@
+
+/*
+ * Gamut test code. Generate a fake gamut surface
+ *
+ * Author: Graeme W. Gill
+ * Date: 23/10/2008
+ * Version: 1.00
+ *
+ * Copyright 2008, Graeme W. Gill
+ * All rights reserved.
+ *
+ * This material is licenced under the GNU AFFERO GENERAL PUBLIC LICENSE Version 3 :-
+ * see the License.txt file for licencing details.
+ */
+
+/* TTBD:
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <fcntl.h>
+#include <string.h>
+#include <math.h>
+#include "copyright.h"
+#include "aconfig.h"
+#include "numlib.h"
+#include "gamut.h"
+
+int get_value(double val[3]);
+
+#define DEF_POINTS 10
+
+void usage(char *diag) {
+ fprintf(stderr,"Generate a test gamut Version %s\n",ARGYLL_VERSION_STR);
+ fprintf(stderr,"Author: Graeme W. Gill, licensed under the AGPL Version 3\n");
+ fprintf(stderr,"usage: gamtest [options] outputname\n");
+ if (diag != NULL)
+ fprintf(stderr,"Diagnostic: %s\n",diag);
+ fprintf(stderr," -v Verbose\n");
+ fprintf(stderr," -n num Number of points to use (default %d)\n",DEF_POINTS);
+ fprintf(stderr," -c rad Create a cube\n");
+ fprintf(stderr," -p rad Create a sphere\n");
+ fprintf(stderr," -s w,d,h Create a sphereoid\n");
+ fprintf(stderr," -b w,d,h Create a box\n");
+ fprintf(stderr," -S nw,pw,nd,pd,nh,ph Create an asymetric sphereoid\n");
+ fprintf(stderr," -B nw,pw,nd,pd,nh,ph Create an asymetric box\n");
+ fprintf(stderr," -o x,y,z Offset the shape\n");
+ fprintf(stderr," -w Create WRL\n");
+ fprintf(stderr," -x No WRL axes\n");
+ fprintf(stderr,"\n");
+ exit(1);
+}
+
+
+int
+main(int argc, char *argv[]) {
+ int fa,nfa; /* argument we're looking at */
+ char *xl, out_name[500];
+ int npoints = DEF_POINTS;
+ gamut *gam;
+ int shape = 1; /* 0 = sphereoid, 1 = box */
+ double bsize[6] = { 20.0, 20.0, 20.0, 20.0, 20.0, 20.0 }; /* box/spheroid size */
+ double goff[3] = { 0.0, 0.0, 0.0 }; /* Gamut position offset */
+ double co[3];
+ double wp[3] = { -1e6, 0.0, 0.0 }, bp[3] = { 1e6, 0.0, 0.0 };
+ int verb = 0;
+ int dowrl = 0;
+ int doaxes = 1;
+ int i;
+
+#ifdef NUMSUP_H
+ error_program = "fakegam";
+#endif
+
+ /* Process the arguments */
+ for(fa = 1;fa < argc;fa++) {
+ nfa = fa; /* skip to nfa if next argument is used */
+ if (argv[fa][0] == '-') { /* Look for any flags */
+ char *na = NULL; /* next argument after flag, null if none */
+
+ if (argv[fa][2] != '\000')
+ na = &argv[fa][2]; /* next is directly after flag */
+ else {
+ if ((fa+1) < argc) {
+ if (argv[fa+1][0] != '-') {
+ nfa = fa + 1;
+ na = argv[nfa]; /* next is seperate non-flag argument */
+ }
+ }
+ }
+
+ if (argv[fa][1] == '?')
+ usage(NULL);
+
+ /* Verbosity */
+ else if (argv[fa][1] == 'v' || argv[fa][1] == 'V') {
+ verb = 1;
+ }
+ /* VRML */
+ else if (argv[fa][1] == 'w' || argv[fa][1] == 'W') {
+ dowrl = 1;
+ }
+ /* No axis output */
+ else if (argv[fa][1] == 'x' || argv[fa][1] == 'X') {
+ doaxes = 0;
+ }
+ /* Sphere */
+ else if (argv[fa][1] == 'p') {
+ double radius;
+ fa = nfa;
+ if (na == NULL) usage("No parameter after flag -h");
+ radius = atof(na);
+ bsize[0] = bsize[2] = bsize[4] = 2.0 * radius;
+ bsize[1] = bsize[3] = bsize[5] = 2.0 * radius;
+ shape = 0;
+ }
+ /* Cube */
+ else if (argv[fa][1] == 'c' || argv[fa][1] == 'C') {
+ double radius;
+ fa = nfa;
+ if (na == NULL) usage("No parameter after flag -h");
+ radius = atof(na);
+ bsize[0] = bsize[2] = bsize[4] = 2.0 * radius;
+ bsize[1] = bsize[3] = bsize[5] = 2.0 * radius;
+ shape = 1;
+ }
+ /* Sphereoid */
+ else if (argv[fa][1] == 's') {
+ if (na == NULL) usage("Expect parameter to -b");
+
+ fa = nfa;
+ if (sscanf(na, " %lf , %lf , %lf ",&bsize[1], &bsize[3], &bsize[5]) == 3) {
+ bsize[0] = bsize[1];
+ bsize[2] = bsize[3];
+ bsize[4] = bsize[5];
+ } else
+ usage("Couldn't parse sphereoid size (-b) values");
+ shape = 0;
+ }
+ /* Box */
+ else if (argv[fa][1] == 'b') {
+ if (na == NULL) usage("Expect parameter to -b");
+
+ fa = nfa;
+ if (sscanf(na, " %lf , %lf , %lf ",&bsize[1], &bsize[3], &bsize[5]) == 3) {
+ bsize[0] = bsize[1];
+ bsize[2] = bsize[3];
+ bsize[4] = bsize[5];
+ } else
+ usage("Couldn't parse box size (-b) values");
+ shape = 1;
+ }
+ /* Asymetric Sphereoid */
+ else if (argv[fa][1] == 'S') {
+ if (na == NULL) usage("Expect parameter to -S");
+
+ fa = nfa;
+ if (sscanf(na, " %lf , %lf , %lf , %lf , %lf , %lf ",
+ &bsize[0], &bsize[1], &bsize[2], &bsize[3], &bsize[4], &bsize[5]) == 6) {
+ bsize[0] *= 2.0;
+ bsize[1] *= 2.0;
+ bsize[2] *= 2.0;
+ bsize[3] *= 2.0;
+ bsize[4] *= 2.0;
+ bsize[5] *= 2.0;
+ } else
+ usage("Couldn't parse sphereoid size (-S) values");
+ shape = 0;
+ }
+ /* Asymetric Box */
+ else if (argv[fa][1] == 'B') {
+ if (na == NULL) usage("Expect parameter to -B");
+
+ fa = nfa;
+ if (sscanf(na, " %lf , %lf , %lf , %lf , %lf , %lf ",
+ &bsize[0], &bsize[1], &bsize[2], &bsize[3], &bsize[4], &bsize[5]) == 6) {
+ bsize[0] *= 2.0;
+ bsize[1] *= 2.0;
+ bsize[2] *= 2.0;
+ bsize[3] *= 2.0;
+ bsize[4] *= 2.0;
+ bsize[5] *= 2.0;
+ } else
+ usage("Couldn't parse box size (-B) values");
+ shape = 1;
+ }
+ /* Number of construction points */
+ else if (argv[fa][1] == 'n' || argv[fa][1] == 'N') {
+ fa = nfa;
+ if (na == NULL) usage("No parameter after flag -t");
+ npoints = atoi(na);
+ }
+ /* Gamut offset */
+ else if (argv[fa][1] == 'o' || argv[fa][1] == 'O') {
+ if (na == NULL) usage("Expect parameter to -o");
+
+ fa = nfa;
+ if (sscanf(na, " %lf , %lf , %lf ",&goff[0], &goff[1], &goff[2]) == 3) {
+ } else
+ usage("Couldn't parse gamut offset (-o) value");
+ }
+ else
+ usage("Unknown flag");
+ } else
+ break;
+ }
+
+ if (fa >= argc || argv[fa][0] == '-') usage("Output filename expected");
+ strcpy(out_name,argv[fa++]);
+
+ if ((xl = strrchr(out_name, '.')) == NULL) { /* Add .gam extention if there isn't one */
+ xl = out_name + strlen(out_name);
+ strcpy(xl,".gam");
+ }
+
+ if (verb) {
+ printf("Number of construction points = %d\n",npoints * npoints * npoints);
+ }
+
+ /* Creat a gamut object */
+ if ((gam = new_gamut(5.0, 0, 0)) == NULL)
+ error("Failed to create aa gamut object\n");
+
+ /* Create and add our gridded test points */
+ for (co[0] = 0; co[0] < npoints; co[0]++) {
+ for (co[1] = 0; co[1] < npoints; co[1]++) {
+ for (co[2] = 0; co[2] < npoints; co[2]++) {
+ double pp[3], sum, rad;
+ int m, n;
+
+ /* Make sure at least one coords are 0 & 1 */
+ for (n = m = 0; m < 3; m++) {
+ if (co[m] == 0 || co[m] == (npoints-1))
+ n++;
+ }
+ if (n < 1)
+ continue;
+
+ if (shape == 0) { /* Sphereoid */
+
+ pp[0] = (co[0]/(npoints-1.0) - 0.5);
+ pp[1] = (co[1]/(npoints-1.0) - 0.5);
+ pp[2] = (co[2]/(npoints-1.0) - 0.5);
+
+ /* vector length */
+ for (sum = 0.0, m = 0; m < 3; m++) {
+ sum += pp[m] * pp[m];
+ }
+ if (sum < 1e-6)
+ continue;
+ sum = sqrt(sum);
+
+ /* Make sum of squares == 0.5 */
+ for (m = 0; m < 3; m++) {
+ pp[m] /= sum;
+ pp[m] *= 0.5;
+ }
+ /* And then scale it */
+ if (pp[0] < 0.0)
+ pp[0] = bsize[4] * pp[0];
+ else
+ pp[0] = bsize[5] * pp[0];
+
+ if (pp[1] < 0.0)
+ pp[1] = bsize[0] * pp[1];
+ else
+ pp[1] = bsize[1] * pp[1];
+
+ if (pp[2] < 0.0)
+ pp[2] = bsize[2] * pp[2];
+ else
+ pp[2] = bsize[3] * pp[2];
+ } else { /* Box */
+ if (co[0] < 0.0)
+ pp[0] = bsize[4] * (co[0]/(npoints-1.0) - 0.5);
+ else
+ pp[0] = bsize[4] * (co[0]/(npoints-1.0) - 0.5);
+
+ if (co[1] < 0.0)
+ pp[1] = bsize[0] * (co[1]/(npoints-1.0) - 0.5);
+ else
+ pp[1] = bsize[1] * (co[1]/(npoints-1.0) - 0.5);
+
+ if (co[2] < 0.0)
+ pp[2] = bsize[1] * (co[2]/(npoints-1.0) - 0.5);
+ else
+ pp[2] = bsize[2] * (co[2]/(npoints-1.0) - 0.5);
+ }
+
+ pp[0] += 50.0 + goff[2];
+ pp[1] += goff[0];
+ pp[2] += goff[1];
+//printf("~1 point %f %f %f\n",pp[0],pp[1],pp[2]);
+ gam->expand(gam, pp);
+
+ if (pp[0] > wp[0])
+ wp[0] = pp[0];
+ if (pp[0] < bp[0])
+ bp[0] = pp[0];
+ }
+ }
+ }
+
+ gam->setwb(gam, wp, bp, NULL);
+
+ /* Write out the gamut */
+ if (gam->write_gam(gam, out_name))
+ error ("write gamut failed on '%s'",out_name);
+
+ if (dowrl) {
+ strcpy(xl,".wrl");
+ if (gam->write_vrml(gam, out_name, doaxes, 0))
+ error ("write vrml failed on '%s'",out_name);
+ }
+
+ if (verb)
+ printf("Written out the gamut surface\n");
+
+ gam->del(gam);
+
+ return 0;
+}
+
diff --git a/gamut/gammap.c b/gamut/gammap.c
new file mode 100644
index 0000000..a9bef1e
--- /dev/null
+++ b/gamut/gammap.c
@@ -0,0 +1,2552 @@
+
+/*
+ * Argyll Gamut Mapping Library
+ *
+ * Author: Graeme W. Gill
+ * Date: 1/10/00
+ * Version: 2.00
+ *
+ * Copyright 2000 - 2006 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.
+ *
+ * For a discussion of gamut mapping strategy used,
+ * see gammap.txt
+ */
+
+/*
+ * TTBD:
+ * Improve error handling.
+ *
+ * There is a general expectation (especially in comparing products)
+ * that the profile colorimetric intent be not strictly minimum delta E,
+ * but that it correct neutral axis, luminence range and keep hue
+ * proportionality. Ideally there should be an intent that matches
+ * this, that can be selected for the colorimetric table (or perhaps be default).
+ *
+ * It might be good to offer the black mapping method as an option (gmm_BPmap),
+ * as well as offering different profile (xicc/xlut.c) black point options
+ * (neutral, K hue max density, CMY max density, any max density).
+ *
+ * The gamut mapping code here and the near smooth code don't actually mesh
+ * very well. For instance, the black point bend approach in < V1.3.4
+ * means that the dest gamut isn't actually contained within the source,
+ * messing up the guide vector mappings. Even if this is fixed, the
+ * actual neutral aim point within nearsmooth is Jab 0,0, while
+ * the mapping in gammap is from the source neutral to the chosen
+ * ??????
+ */
+
+
+
+#define VERBOSE /* [Def] Print out extra interesting information when verbose is set */
+#undef PLOT_DIAG_WRL /* [Und] Always plot "gammap.wrl" */
+
+ /* What do display when user requests disgnostic .wrl */
+#define PLOT_SRC_GMT /* [Def] Plot the source surface to "gammap.wrl" as well */
+#define PLOT_DST_GMT /* [Def] Plot the dest surface to "gammap.wrl" as well */
+#undef PLOT_SRC_CUSPS /* [Und] Plot the source surface cusps to "gammap.wrl" as well */
+#undef PLOT_DST_CUSPS /* [Und] Plot the dest surface cusps to "gammap.wrl" as well */
+#undef PLOT_TRANSSRC_CUSPS /* [Und] Plot the gamut mapped source surface cusps to "gammap.wrl" */
+#undef PLOT_AXES /* [Und] Plot the axes to "gammap.wrl" as well */
+#undef SHOW_VECTOR_INDEXES /* [Und] Show the mapping vector index numbers */
+#define SHOW_MAP_VECTORS /* [Def] Show the mapping vectors */
+#undef SHOW_SUB_SURF /* [Und] Show the sub-surface mapping vector */
+#undef SHOW_CUSPMAP /* [Und] Show the cusp mapped vectors rather than final vectors */
+#undef SHOW_ACTUAL_VECTORS /* [Und?] Show how the source vectors actually map thought xform */
+#undef SHOW_ACTUAL_VEC_DIFF /* [Und] Show how the difference between guide and actual vectors */
+
+#undef PLOT_LMAP /* [Und] Plot L map */
+#undef PLOT_GAMUTS /* [Und] Save (part mapped) input and output gamuts as */
+ /* src.wrl, img.wrl, dst.wrl, gmsrc.wrl */
+#undef PLOT_3DKNEES /* [Und] Plot the 3D compression knees */
+#undef CHECK_NEARMAP /* [Und] Check how accurately near map vectors are represented by rspl */
+
+#define USE_GLUMKNF /* [Define] Enable luminence knee function points */
+#define USE_GREYMAP /* [Define] Enable 3D->3D mapping points down the grey axis */
+#define USE_GAMKNF /* [Define] Enable 3D knee function points */
+#define USE_BOUND /* [Define] Enable grid boundary anchor points */
+
+#undef SHOW_NEIGBORS /* [Und] Show nearsmth neigbors in gammap.wrl */
+
+#undef PLOT_DIGAM /* [Und] Rather than DST_GMT - don't free it (#def in nearsmth.c too) */
+
+
+#define XRES 100 /* Res of plots */
+
+/* Optional marker points for gamut mapping diagnosotic */
+struct {
+ int type; /* 1 = src point (xlate), 2 = dst point (no xlate) */
+ /* 0 = end marker */
+ double pos[3]; /* Position, (usually in Jab space) */
+ double col[3]; /* RGB color */
+} markers[] = {
+ { 0, }, /* End marker */
+ { 1, { 12.062, -0.87946, 0.97008 }, { 1.0, 0.3, 0.3 } }, /* Black point */
+ { 1, { 67.575411, -37.555250, -36.612862 }, { 1.0, 0.3, 0.3 } }, /* bad source in red (Red) */
+ { 1, { 61.003078, -44.466554, 1.922585 }, { 0.0, 1.0, 0.3 } }, /* good source in green */
+ { 2, { 49.294793, 50.749543, -51.383167 }, { 1.0, 0.0, 0.0 } },
+ { 2, { 42.783425, 49.089363, -37.823712 }, { 0.0, 1.0, 0.0 } },
+ { 2, { 41.222695, 63.911823, 37.695310 }, { 0.0, 1.0, 0.3 } }, /* destination in green */
+ { 1, { 41.951770, 60.220284, 34.788195 }, { 1.0, 0.3, 0.3 } }, /* source in red (Red) */
+ { 2, { 41.222695, 63.911823, 37.695310 }, { 0.3, 1.3, 0.3 } }, /* Dest in green */
+ { 1, { 85.117353, -60.807580, -22.195118 }, { 0.3, 0.3, 1 } }, /* Cyan Source (Blue) */
+ { 2, { 61.661622, -38.164411, -18.090824 }, { 1.0, 0.3, 0.3 } }, /* CMYK destination (Red) */
+ { 0 } /* End marker */
+};
+
+/* Optional marker rings for gamut mapping diagnosotic */
+struct {
+ int type; /* 1 = src ring point, 2 = ignore, */
+ /* 0 = end marker */
+ double ppoint[3]; /* Location of a point on the plane in source space */
+ double pnorm[3]; /* Plane normal direction in source space */
+ int nverts; /* Number of points to make ring */
+ double rad; /* Relative Radius from neutral to source surface (0.0 - 1.0) */
+ double scol[3]; /* Source RGB color */
+ double dcol[3]; /* Destination RGB color */
+} rings[] = {
+ { 0 }, /* End marker */
+ { 1,
+ { 60.0, 0.0, 0.0 }, { 1.0, 0.8, 0.0 }, /* plane point and normal */
+ 100, 1.0, /* 20 vertexes at source radius */
+ { 0.0, 1.0, 0.0 }, /* Green source */
+ { 1.0, 0.0, 0.0 } /* Red destination */
+ },
+ { 1,
+ { 60.0, 0.0, 0.0 }, { 1.0, 0.8, 0.0 }, /* plane point and normal */
+ 100, 0.9, /* 20 vertexes at source radius */
+ { 0.0, 1.0, 0.0 }, /* Green source */
+ { 1.0, 0.0, 0.0 } /* Red destination */
+ },
+ { 1,
+ { 60.0, 0.0, 0.0 }, { 1.0, 0.8, 0.0 }, /* plane point and normal */
+ 100, 0.8, /* 20 vertexes at source radius */
+ { 0.0, 1.0, 0.0 }, /* Green source */
+ { 1.0, 0.0, 0.0 } /* Red destination */
+ },
+ { 0 } /* End marker */
+};
+
+/* Degree to which the hue & saturation of the black point axes should be aligned: */
+#define GREYBPHSMF 0.0
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <fcntl.h>
+#include <string.h>
+#include <math.h>
+#include "counters.h"
+#include "icc.h"
+#include "numlib.h"
+#include "xicc.h"
+#include "gamut.h"
+#include "rspl.h"
+#include "gammap.h"
+#include "nearsmth.h"
+#include "vrml.h"
+#ifdef PLOT_LMAP
+#include "plot.h"
+#endif
+
+/* Callback context for enhancing the saturation of the clut values */
+typedef struct {
+ gamut *dst; /* Destination colorspace gamut */
+ double wp[3], bp[3];/* Destination colorspace white and black points */
+ double satenh; /* Saturation engancement value */
+} adjustsat;
+
+/* Callback context for making clut relative to white and black points */
+typedef struct {
+ double mat[3][4];
+} adjustwb;
+
+static void inv_grey_func(void *pp, double *out, double *in);
+static void adjust_wb_func(void *pp, double *out, double *in);
+static void adjust_sat_func(void *pp, double *out, double *in);
+
+#define XVRA 4.0 /* Extra mapping vertex ratio over no. tri verts from gamut */
+
+/* The smoothed near weighting control values. */
+/* These weightings setup the detailed behaviour of the */
+/* gamut mapping for the fully perceptual and saturation intents. */
+/* They are ordered here by increasing priority. A -ve value is ignored */
+
+/* Perceptual mapping weights, where smoothness and proportionality are important.. */
+gammapweights pweights[] = {
+ {
+ gmm_default, /* Non hue specific defaults */
+ { /* Cusp alignment control */
+ {
+ 0.1, /* Cusp luminance alignment weighting 0 = none, 1 = full */
+ 0.0, /* Cusp chroma alignment weighting 0 = none, 1 = full */
+ 0.3 /* Cusp hue alignment weighting 0 = none, 1 = full */
+ },
+ 1.00 /* Chroma expansion 1 = none */
+ },
+ { /* Radial weighting */
+ 0.0, /* Radial error overall weight, 0 + */
+ 0.5, /* Radial hue dominance vs l+c, 0 - 1 */
+ 0.5 /* Radial l dominance vs, c, 0 - 1 */
+ },
+ { /* Weighting of absolute error of destination from source */
+ 1.0, /* Absolute error overall weight */
+ 0.6, /* Hue dominance vs l+c, 0 - 1 */
+
+ 0.8, /* White l dominance vs, c, 0 - 1 */
+ 0.5, /* Grey l dominance vs, c, 0 - 1 */
+ 0.97, /* Black l dominance vs, c, 0 - 1 */
+
+ 0.4, /* White l blend start radius, 0 - 1, at white = 0 */
+ 0.7, /* Black l blend power, linear = 1.0, enhance < 1.0 */
+
+ 1.5, /* L error extra power with size, none = 1.0 */
+ 10.0 /* L error extra xover threshold in DE */
+ },
+ { /* Relative vector smoothing */
+ 25.0, 35.0 /* Relative Smoothing radius L* H* */
+ },
+ { /* Weighting of excessive compression error, which is */
+ /* the src->dst vector length over the available dst depth. */
+ /* The depth is half the distance to the intersection of the */
+ /* vector to the other side of the gamut. (doesn't get triggered much ?) */
+ 10.0, /* Compression depth weight */
+ 10.0 /* Expansion depth weight */
+ },
+ {
+ 0.0 /* Fine tuning expansion weight, 0 - 1 */
+ }
+ },
+#ifdef NEVER
+ {
+ gmm_l_d_blue, /* Increase maintaining hue importance for blue */
+ {
+ {
+ -1.0, /* Cusp luminance alignment weighting 0 = none, 1 = full */
+ -1.0, /* Cusp chroma alignment weighting 0 = none, 1 = full */
+ 0.0 /* Cusp hue alignment weighting 0 = none, 1 = full */
+ },
+ -1.0 /* Chroma expansion 1 = none */
+ },
+ { /* Radial weighting */
+ -1.0, /* Radial error overall weight, 0 + */
+ -1.0, /* Radial hue dominance vs l+c, 0 - 1 */
+ -1.0 /* Radial l dominance vs, c, 0 - 1 */
+ },
+ { /* Weighting of absolute error of destination from source */
+ -1.0, /* Absolute error overall weight */
+ 0.8, /* Hue dominance vs l+c, 0 - 1 */
+
+ -1.0, /* White l dominance vs, c, 0 - 1 */
+ -1.0, /* Grey l dominance vs, c, 0 - 1 */
+ -1.0, /* Black l dominance vs, c, 0 - 1 */
+
+ -1.0, /* White l threshold ratio to grey distance, 0 - 1 */
+ -1.0, /* Black l threshold ratio to grey distance, 0 - 1 */
+
+ -1.0, /* L error extra power, none = 1.0 */
+ -1.0 /* L error xover threshold in DE */
+ },
+ { /* Relative error preservation using smoothing */
+ -1.0, 25.0 /* Relative Smoothing radius L* H* */
+ },
+ { /* Weighting of excessive compression error, which is */
+ /* the src->dst vector length over the available dst depth. */
+ /* The depth is half the distance to the intersection of the */
+ /* vector to the other side of the gamut. (doesn't get triggered much ?) */
+ -1.0, /* Compression depth weight */
+ -1.0 /* Expansion depth weight */
+ },
+ {
+ -1.0 /* Fine tuning expansion weight, 0 - 1 */
+ }
+ },
+#endif /* NEVER */
+ {
+ gmm_light_yellow, /* Treat yellow differently, to get purer result. */
+ {
+ {
+ 0.9, /* Cusp luminance alignment weighting 0 = none, 1 = full */
+ 0.8, /* Cusp chroma alignment weighting 0 = none, 1 = full */
+ 0.7 /* Cusp hue alignment weighting 0 = none, 1 = full */
+ },
+ 1.15 /* Chroma expansion 1 = none */
+ },
+ { /* Radial weighting */
+ -1.0, /* Radial error overall weight, 0 + */
+ -1.0, /* Radial hue dominance vs l+c, 0 - 1 */
+ -1.0 /* Radial l dominance vs, c, 0 - 1 */
+ },
+ { /* Weighting of absolute error of destination from source */
+ -1.0, /* Absolute error overall weight */
+ -1.0, /* Hue dominance vs l+c, 0 - 1 */
+
+ -1.0, /* White l dominance vs, c, 0 - 1 */
+ -1.0, /* Grey l dominance vs, c, 0 - 1 */
+ -1.0, /* Black l dominance vs, c, 0 - 1 */
+
+ -1.0, /* White l threshold ratio to grey distance, 0 - 1 */
+ -1.0, /* Black l threshold ratio to grey distance, 0 - 1 */
+
+ -1.0, /* L error extra power, none = 1.0 */
+ -1.0 /* L error xover threshold in DE */
+ },
+ { /* Relative error preservation using smoothing */
+ 20.0, 20.0 /* Relative Smoothing radius L* H* */
+ },
+ { /* Weighting of excessive compression error, which is */
+ /* the src->dst vector length over the available dst depth. */
+ /* The depth is half the distance to the intersection of the */
+ /* vector to the other side of the gamut. (doesn't get triggered much ?) */
+ -1.0, /* Compression depth weight */
+ -1.0 /* Expansion depth weight */
+ },
+ {
+ 0.5 /* Fine tuning expansion weight, 0 - 1 */
+ }
+ },
+ {
+ gmm_end,
+ }
+};
+double psmooth = 5.0; /* [5.0] Level of RSPL smoothing for perceptual, 1 = nominal */
+
+/* Saturation mapping weights, where saturation has priority over smoothness */
+gammapweights sweights[] = {
+ {
+ gmm_default, /* Non hue specific defaults */
+ { /* Cusp alignment control */
+ {
+ 0.6, /* Cusp luminance alignment weighting 0 = none, 1 = full */
+ 0.5, /* Cusp chroma alignment weighting 0 = none, 1 = full */
+ 0.6 /* Cusp hue alignment weighting 0 = none, 1 = full */
+ },
+ 1.05 /* Chroma expansion 1 = none */
+ },
+ { /* Radial weighting */
+ 0.0, /* Radial error overall weight, 0 + */
+ 0.5, /* Radial hue dominance vs l+c, 0 - 1 */
+ 0.5 /* Radial l dominance vs, c, 0 - 1 */
+ },
+ { /* Weighting of absolute error of destination from source */
+ 1.0, /* Absolute error overall weight */
+ 0.4, /* Hue dominance vs l+c, 0 - 1 */
+
+ 0.6, /* White l dominance vs, c, 0 - 1 */
+ 0.4, /* Grey l dominance vs, c, 0 - 1 */
+ 0.6, /* Black l dominance vs, c, 0 - 1 */
+
+ 0.5, /* wl blend start radius, 0 - 1 */
+ 1.0, /* bl blend power, linear = 1.0, enhance < 1.0 */
+
+ 1.0, /* L error extra power with size, none = 1.0 */
+ 10.0 /* L error extra xover threshold in DE */
+ },
+ { /* Relative vector smoothing */
+ 15.0, 20.0 /* Relative Smoothing radius L* H* */
+ },
+ { /* Weighting of excessive compression error, which is */
+ /* the src->dst vector length over the available dst depth. */
+ /* The depth is half the distance to the intersection of the */
+ /* vector to the other side of the gamut. (doesn't get triggered much ?) */
+ 10.0, /* Compression depth weight */
+ 10.0 /* Expansion depth weight */
+ },
+ {
+ 0.5 /* Fine tuning expansion weight, 0 - 1 */
+ }
+ },
+ {
+ gmm_light_yellow, /* Treat yellow differently, to get purer result. */
+ {
+ {
+ 1.0, /* Cusp luminance alignment weighting 0 = none, 1 = full */
+ 1.0, /* Cusp chroma alignment weighting 0 = none, 1 = full */
+ 1.0 /* Cusp hue alignment weighting 0 = none, 1 = full */
+ },
+ 1.20 /* Chroma expansion 1 = none */
+ },
+ { /* Radial weighting */
+ -1.0, /* Radial error overall weight, 0 + */
+ -1.0, /* Radial hue dominance vs l+c, 0 - 1 */
+ -1.0 /* Radial l dominance vs, c, 0 - 1 */
+ },
+ { /* Weighting of absolute error of destination from source */
+ 1.0, /* Absolute error overall weight */
+ 0.3, /* Hue dominance vs l+c, 0 - 1 */
+
+ -1.0, /* White l dominance vs, c, 0 - 1 */
+ -1.0, /* Grey l dominance vs, c, 0 - 1 */
+ -1.0, /* Black l dominance vs, c, 0 - 1 */
+
+ -1.0, /* White l threshold ratio to grey distance, 0 - 1 */
+ -1.0, /* Black l threshold ratio to grey distance, 0 - 1 */
+
+ -1.0, /* L error extra power, none = 1.0 */
+ -1.0 /* L error xover threshold in DE */
+ },
+ { /* Relative error preservation using smoothing */
+ 10.0, 15.0 /* Relative smoothing radius */
+ },
+ { /* Weighting of excessive compression error, which is */
+ /* the src->dst vector length over the available dst depth. */
+ /* The depth is half the distance to the intersection of the */
+ /* vector to the other side of the gamut. (doesn't get triggered much ?) */
+ -1.0, /* Compression depth weight */
+ -1.0 /* Expansion depth weight */
+ },
+ {
+ -1.0 /* Fine tuning expansion weight, 0 - 1 */
+ }
+ },
+ {
+ gmm_end
+ }
+};
+double ssmooth = 2.0; /* Level of RSPL smoothing for saturation */
+
+/*
+ * Notes:
+ * The "knee" shape produced by the rspl (regular spline) code
+ * is not what one would expect for expansion. It is not
+ * symetrical with compression, and is less "sharp". This
+ * is due to the rspl "smoothness" criteria being based on
+ * grid value difference rather than smoothness being measured,
+ * as curvature. This means that the spline gets "stiffer" as
+ * it increases in slope.
+ * Possibly rspl could be improved in this respect ???
+ * (Doesn't matter for L compression now, because rspl is
+ * being inverted for expansion).
+ */
+
+static void del_gammap(gammap *s);
+static void domap(gammap *s, double *out, double *in);
+static void dopartialmap1(gammap *s, double *out, double *in);
+static void dopartialmap2(gammap *s, double *out, double *in);
+static gamut *parttransgamut(gammap *s, gamut *src);
+#ifdef PLOT_GAMUTS
+static void map_trans(void *cntx, double out[3], double in[3]);
+#endif
+
+/* Return a gammap to map from the input space to the output space */
+/* Return NULL on error. */
+gammap *new_gammap(
+ int verb, /* Verbose flag */
+ gamut *sc_gam, /* Source colorspace gamut */
+ gamut *si_gam, /* Source image gamut (NULL if none) */
+ gamut *d_gam, /* Destination colorspace gamut */
+ icxGMappingIntent *gmi, /* Gamut mapping specification */
+ int src_kbp, /* Use K only black point as src gamut black point */
+ int dst_kbp, /* Use K only black point as dst gamut black point */
+ int dst_cmymap, /* masks C = 1, M = 2, Y = 4 to force 100% cusp map */
+ int rel_oride, /* 0 = normal, 1 = clip like, 2 = max relative */
+ int mapres, /* Gamut map resolution, typically 9 - 33 */
+ double *mn, /* If not NULL, set minimum mapping input range */
+ double *mx, /* for rspl grid. */
+ char *diagname /* If non-NULL, write a gamut mapping diagnostic WRL */
+) {
+ gmm_BPmap bph = gmm_bendBP; /* Prefered algorithm */
+// gmm_BPmap bph = gmm_clipBP; /* Alternatives tried */
+// gmm_BPmap bph = gmm_BPadpt;
+// gmm_BPmap bph = gmm_noBPadpt;
+
+ gammap *s; /* This */
+ gamut *scl_gam; /* Source colorspace gamut with rotation and L mapping applied */
+ gamut *sil_gam; /* Source image gamut with rotation and L mapping applied */
+
+ double s_cs_wp[3]; /* Source colorspace white point */
+ double s_cs_bp[3]; /* Source colorspace black point */
+ double s_ga_wp[3]; /* Source (image) gamut white point */
+ double s_ga_bp[3]; /* Source (image) gamut black point */
+ double d_cs_wp[3]; /* Destination colorspace white point */
+ double d_cs_bp[3]; /* Destination colorspace black point */
+
+ double sr_cs_wp[3]; /* Source rotated colorspace white point */
+ double sr_cs_bp[3]; /* Source rotated colorspace black point */
+ double sr_ga_wp[3]; /* Source rotated (image) gamut white point */
+ double sr_ga_bp[3]; /* Source rotated (image) gamut black point */
+ double dr_cs_wp[3]; /* Target (gmi->greymf aligned) white point */
+ double dr_cs_bp[3]; /* Target (gmi->greymf aligned) black point */
+ double dr_be_bp[3]; /* Bend at start in source neutral axis direction */
+ /* Target black point (Same as dr_cs_bp[] otherwise) */
+
+ double sl_cs_wp[3]; /* Source rotated and L mapped colorspace white point */
+ double sl_cs_bp[3]; /* Source rotated and L mapped colorspace black point */
+
+ double s_mt_wp[3]; /* Overall source mapping target white point (used for finetune) */
+ double s_mt_bp[3]; /* Overall source mapping target black point (used for finetune) */
+ double d_mt_wp[3]; /* Overall destination mapping white point (used for finetune) */
+ double d_mt_bp[3]; /* Overall destination mapping black point (used for finetune) */
+
+ int defrgrid = 6; /* mapping range surface default anchor point resolution */
+ int nres = 512; /* Neutral axis resolution */
+ cow lpnts[10]; /* Mapping points to create grey axis map */
+ int revrspl = 0; /* Reverse grey axis rspl construction */
+ int ngreyp = 0; /* Number of grey axis mapping points */
+ int ngamp = 0; /* Number of gamut mapping points */
+ double xvra = XVRA; /* Extra ss vertex ratio to src gamut vertex count */
+ int j;
+
+#if defined(PLOT_LMAP) || defined(PLOT_GAMUTS) || defined(PLOT_3DKNEES)
+ fprintf(stderr,"##### A gammap.c PLOT is #defined ####\n");
+#endif
+
+ if (verb) {
+ xicc_dump_gmi(gmi);
+ printf("Gamut map resolution: %d\n",mapres);
+ if (si_gam != NULL)
+ printf("Image gamut supplied\n");
+ switch(bph) {
+ case gmm_clipBP: printf("Neutral axis no-adapt extend and clip\n"); break;
+ case gmm_BPadpt: printf("Neutral axis fully adapt\n"); break;
+ case gmm_bendBP: printf("Neutral axis no-adapt extend and bend\n"); break;
+ case gmm_noBPadpt: printf("Neutral axis no-adapt\n"); break;
+ }
+ }
+
+ /* Allocate the object */
+ if ((s = (gammap *)calloc(1, sizeof(gammap))) == NULL)
+ error("gammap: calloc failed on gammap object");
+
+ /* Setup methods */
+ s->del = del_gammap;
+ s->domap = domap;
+
+ /* Now create everything */
+
+ /* Grab the white and black points */
+ if (src_kbp) {
+ // ~~99 Hmm. Shouldn't this be colorspace rather than gamut ????
+ if (sc_gam->getwb(sc_gam, NULL, NULL, NULL, s_cs_wp, NULL, s_cs_bp)) {
+// if (sc_gam->getwb(sc_gam, s_cs_wp, NULL, s_cs_bp, NULL, NULL, NULL))
+ fprintf(stderr,"gamut map: Unable to read source colorspace white and black points\n");
+ free(s);
+ return NULL;
+ }
+ } else {
+ if (sc_gam->getwb(sc_gam, NULL, NULL, NULL, s_cs_wp, s_cs_bp, NULL)) {
+// if (sc_gam->getwb(sc_gam, s_cs_wp, s_cs_bp, NULL, NULL, NULL, NULL))
+ fprintf(stderr,"gamut map: Unable to read source colorspace white and black points\n");
+ free(s);
+ return NULL;
+ }
+ }
+
+ /* If source space is source gamut */
+ if (si_gam == NULL) {
+ si_gam = sc_gam;
+ for (j = 0; j < 3; j++) {
+ s_ga_wp[j] = s_cs_wp[j];
+ s_ga_bp[j] = s_cs_bp[j];
+ }
+
+ /* Else have explicit sourcegamut */
+ } else {
+
+ if (src_kbp) {
+ if (si_gam->getwb(si_gam, NULL, NULL, NULL, s_ga_wp, NULL, s_ga_bp)) {
+ fprintf(stderr,"gamut map: Unable to read source gamut white and black points\n");
+ free(s);
+ return NULL;
+ }
+ } else {
+ if (si_gam->getwb(si_gam, NULL, NULL, NULL, s_ga_wp, s_ga_bp, NULL)) {
+ fprintf(stderr,"gamut map: Unable to read source gamut white and black points\n");
+ free(s);
+ return NULL;
+ }
+ }
+
+ /* Guard against silliness. Image must be within colorspace */
+ if (s_ga_wp[0] > s_cs_wp[0]) {
+ int j;
+ double t;
+#ifdef VERBOSE
+ if (verb)
+ printf("Fixing wayward image white point\n");
+#endif
+ t = (s_cs_wp[0] - s_ga_bp[0])/(s_ga_wp[0] - s_ga_bp[0]);
+ for (j = 0; j < 3; j++)
+ s_ga_wp[j] = s_ga_bp[j] + t * (s_ga_wp[j] - s_ga_bp[j]);
+
+ }
+ if (s_ga_bp[0] < s_cs_bp[0]) {
+ int j;
+ double t;
+#ifdef VERBOSE
+ if (verb)
+ printf("Fixing wayward image black point\n");
+#endif
+ t = (s_cs_bp[0] - s_ga_wp[0])/(s_ga_bp[0] - s_ga_wp[0]);
+ for (j = 0; j < 3; j++)
+ s_ga_bp[j] = s_ga_wp[j] + t * (s_ga_bp[j] - s_ga_wp[j]);
+ }
+ }
+
+ if (dst_kbp) {
+ if (d_gam->getwb(d_gam, NULL, NULL, NULL, d_cs_wp, NULL, d_cs_bp)) {
+ fprintf(stderr,"gamut map: Unable to read destination white and black points\n");
+ free(s);
+ return NULL;
+ }
+ } else {
+ if (d_gam->getwb(d_gam, NULL, NULL, NULL, d_cs_wp, d_cs_bp, NULL)) {
+ fprintf(stderr,"gamut map: Unable to read destination white and black points\n");
+ free(s);
+ return NULL;
+ }
+ }
+
+#ifdef VERBOSE
+ if (verb) {
+ if (src_kbp)
+ printf("Using Src K only black point\n");
+
+ if (dst_kbp)
+ printf("Using Dst K only black point\n");
+
+ printf("Src colorspace white/black are %f %f %f, %f %f %f\n",
+ s_cs_wp[0], s_cs_wp[1], s_cs_wp[2], s_cs_bp[0], s_cs_bp[1], s_cs_bp[2]);
+
+ printf("Src gamut white/black are %f %f %f, %f %f %f\n",
+ s_ga_wp[0], s_ga_wp[1], s_ga_wp[2], s_ga_bp[0], s_ga_bp[1], s_ga_bp[2]);
+
+ printf("Dst colorspace white/black are %f %f %f, %f %f %f\n",
+ d_cs_wp[0], d_cs_wp[1], d_cs_wp[2], d_cs_bp[0], d_cs_bp[1], d_cs_bp[2]);
+ }
+#endif /* VERBOSE */
+
+ /* ------------------------------------ */
+ /* Figure out the destination grey axis alignment */
+ /* This is all done using colorspace white & black points */
+ {
+ double t, svl, dvl;
+ double wrot[3][3]; /* Rotation about 0,0,0 to match white points */
+ double sswp[3], ssbp[3]; /* Temporary source white & black points */
+ double fawp[3], fabp[3]; /* Fully adapted destination white & black */
+ double hawp[3], habp[3]; /* Half (full white, not black) adapted destination w & b */
+
+ /* - - - - - - - - - - - - - - - - - - - - - - - - - - - */
+ /* The first task is to decide what our target destination */
+ /* white and black points are going to be. */
+
+ /* Figure out what our initial target destination white point is going to be: */
+
+ /* Compute source white and black points with same L value as the destination */
+ t = (d_cs_wp[0] - s_cs_bp[0])/(s_cs_wp[0] - s_cs_bp[0]);
+ for (j = 0; j < 3; j++)
+ sswp[j] = s_cs_bp[j] + t * (s_cs_wp[j] - s_cs_bp[j]);
+
+ t = (d_cs_bp[0] - s_cs_wp[0])/(s_cs_bp[0] - s_cs_wp[0]);
+ for (j = 0; j < 3; j++)
+ ssbp[j] = s_cs_wp[j] + t * (s_cs_bp[j] - s_cs_wp[j]);
+
+ /* The raw grey axis alignment target is a blend between the */
+ /* source colorspace (NOT gamut) and the destination */
+ /* colorspace. */
+
+ for (j = 0; j < 3; j++) {
+ dr_cs_wp[j] = gmi->greymf * d_cs_wp[j] + (1.0 - gmi->greymf) * sswp[j];
+ dr_cs_bp[j] = gmi->greymf * d_cs_bp[j] + (1.0 - gmi->greymf) * ssbp[j];
+ }
+
+#ifdef VERBOSE
+ if (verb) {
+ printf("Target (blended) dst wp/bp = %f %f %f, %f %f %f\n",
+ dr_cs_wp[0], dr_cs_wp[1], dr_cs_wp[2], dr_cs_bp[0], dr_cs_bp[1], dr_cs_bp[2]);
+ }
+#endif /* VERBOSE */
+
+ /* Compute full adaptation target destinations */
+ for (j = 0; j < 3; j++) {
+ fawp[j] = dr_cs_wp[j]; /* White fully adapted */
+ fabp[j] = dr_cs_bp[j]; /* Black fully adapted */
+ }
+
+ /* Clip the target grey axis to the destination gamut */
+ if (d_gam->vector_isect(d_gam, fabp, fawp, fabp, fawp, NULL, NULL, NULL, NULL) == 0)
+ error("gamut: vector_isect failed!");
+
+ /* To work around the problem that vector_isect() is not entirely accurate, */
+ /* special case the situation where gmi->greymf == 1.0 */
+ if (gmi->greymf > 0.99) {
+ for (j = 0; j < 3; j++) {
+ fawp[j] = d_cs_wp[j];
+ fabp[j] = d_cs_bp[j];
+ }
+ }
+
+ /* If dst_kbp is set, then clipping to the dest gamut doesn't do what we want, */
+ /* since it extends the black to a full composite black point. */
+ /* A "K only" gamut is hard to define, so do a hack: */
+ /* scale fabp[] towards fawp[] so that it has the same L as */
+ /* the destination K only black point. */
+ if (dst_kbp && fabp[0] < d_cs_bp[0]) {
+ t = (d_cs_bp[0] - fawp[0])/(fabp[0] - fawp[0]);
+
+ for (j = 0; j < 3; j++)
+ fabp[j] = fawp[j] + t * (fabp[j] - fawp[j]);
+ }
+
+ /* Compute half adapted (full white, not black) target destinations */
+ for (j = 0; j < 3; j++)
+ hawp[j] = dr_cs_wp[j]; /* White fully adapted */
+
+ /* Compute the rotation matrix that maps the source white point */
+ /* onto the target white point. */
+ icmRotMat(wrot, sswp, dr_cs_wp);
+
+ /* Compute the target black point as the rotated source black point */
+ icmMulBy3x3(habp, wrot, s_cs_bp);
+
+ /* Now intersect the target white and black points with the destination */
+ /* colorspace gamut to arrive at the best possible in gamut values for */
+ /* the target white and black points. */
+ if (d_gam->vector_isect(d_gam, habp, hawp, habp, hawp, NULL, NULL, NULL, NULL) == 0)
+ error("gamut: vector_isect failed!");
+
+ /* To work around the problem that vector_isect() is not entirely accurate, */
+ /* special case the situation where gmi->greymf == 1.0 */
+ if (gmi->greymf > 0.99) {
+ for (j = 0; j < 3; j++) {
+ hawp[j] = d_cs_wp[j];
+ }
+ }
+
+ /* If dst_kbp is set, then clipping to the dest gamut doesn't do what we want, */
+ /* since it extends the black to a full composite black point. */
+ /* A "K only" gamut is hard to define, so do a hack: */
+ /* scale habp[] towards hawp[] so that it has the same L as */
+ /* the destination K only black point. */
+ if (dst_kbp && habp[0] < d_cs_bp[0]) {
+ t = (d_cs_bp[0] - hawp[0])/(habp[0] - hawp[0]);
+
+ for (j = 0; j < 3; j++)
+ habp[j] = hawp[j] + t * (habp[j] - hawp[j]);
+ }
+
+ /* Now decide the detail of the white and black alignment */
+ if (bph == gmm_BPadpt || bph == gmm_bendBP) { /* Adapt to destination white and black */
+
+
+ /* Use the fully adapted white and black points */
+ for (j = 0; j < 3; j++) {
+ dr_cs_wp[j] = fawp[j];
+ dr_cs_bp[j] = fabp[j];
+ }
+
+ if (bph == gmm_bendBP) {
+
+ /* Extend the half adapted (white = dst, black = src) black point */
+ /* to the same L as the target (dst), to use as the initial (bent) black point */
+ t = (dr_cs_bp[0] - dr_cs_wp[0])/(habp[0] - dr_cs_wp[0]);
+ for (j = 0; j < 3; j++)
+ dr_be_bp[j] = dr_cs_wp[j] + t * (habp[j] - dr_cs_wp[j]);
+
+ } else {
+
+ /* Set bent black point target to be the same as our actual */
+ /* black point target, so that the "bend" code does nothing. */
+ for (j = 0; j < 3; j++)
+ dr_be_bp[j] = dr_cs_bp[j];
+ }
+
+ } else { /* Adapt to destination white but not black */
+
+ /* Use the half adapted (white = dst, black = src) white and black points */
+ for (j = 0; j < 3; j++) {
+ dr_cs_wp[j] = hawp[j];
+ dr_cs_bp[j] = habp[j];
+ }
+
+#ifdef VERBOSE
+ if (verb) {
+ printf("Adapted target wp/bp = %f %f %f, %f %f %f\n",
+ dr_cs_wp[0], dr_cs_wp[1], dr_cs_wp[2], dr_cs_bp[0], dr_cs_bp[1], dr_cs_bp[2]);
+ }
+#endif
+ if (bph == gmm_clipBP) {
+
+ /* Extend the target black point to accomodate the */
+ /* bent or clipped destination space L* range */
+ if (fabp[0] < dr_cs_bp[0]) {
+ t = (fabp[0] - dr_cs_wp[0])/(dr_cs_bp[0] - dr_cs_wp[0]);
+ for (j = 0; j < 3; j++)
+ dr_cs_bp[j] = dr_cs_wp[j] + t * (dr_cs_bp[j] - d_cs_wp[j]);
+ }
+ }
+
+ /* Set the bent black point target to be the same as our actual */
+ /* black point target, so that the "bend" code does nothing. */
+ for (j = 0; j < 3; j++)
+ dr_be_bp[j] = dr_cs_bp[j];
+ }
+
+#ifdef VERBOSE
+ if (verb) {
+ printf("Adapted & extended tgt wp/bp = %f %f %f, %f %f %f\n",
+ dr_cs_wp[0], dr_cs_wp[1], dr_cs_wp[2], dr_cs_bp[0], dr_cs_bp[1], dr_cs_bp[2]);
+ }
+#endif /* VERBOSE */
+
+ /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
+ /* Now we need to figure out what origin alignment is needed, as well as */
+ /* making sure the vectors are the same length to avoid rescaling. */
+ /* (Scaling is meant to be done with the L curve though.) */
+
+ /* Create temporary source white point that has the same L as the */
+ /* target destination white point. */
+ t = (dr_cs_wp[0] - s_cs_bp[0])/(s_cs_wp[0] - s_cs_bp[0]);
+ for (j = 0; j < 3; j++)
+ sswp[j] = s_cs_bp[j] + t * (s_cs_wp[j] - s_cs_bp[j]);
+
+ /* Create temporary source black point that will form a vector to the src white */
+ /* point with same length as the target destination black->white vector. */
+ for (svl = dvl = 0.0, j = 0; j < 3; j++) {
+ double tt;
+ tt = sswp[j] - s_cs_bp[j];
+ svl += tt * tt;
+ tt = dr_cs_wp[j] - dr_cs_bp[j];
+ dvl += tt * tt;
+ }
+ svl = sqrt(svl);
+ dvl = sqrt(dvl);
+ for (j = 0; j < 3; j++)
+ ssbp[j] = sswp[j] + dvl/svl * (s_cs_bp[j] - sswp[j]);
+
+#ifdef VERBOSE
+ if (verb) {
+ printf("Rotate matrix src wp/bp = %f %f %f, %f %f %f\n",
+ sswp[0], sswp[1], sswp[2], ssbp[0], ssbp[1], ssbp[2]);
+ printf("Rotate matrix dst wp/bp = %f %f %f, %f %f %f\n",
+ dr_cs_wp[0], dr_cs_wp[1], dr_cs_wp[2], dr_cs_bp[0], dr_cs_bp[1], dr_cs_bp[2]);
+ }
+#endif /* VERBOSE */
+
+ /* Now create the general rotation and translation to map the source grey */
+ /* axis to our destination grey axis. */
+ icmVecRotMat(s->grot, sswp, ssbp, dr_cs_wp, dr_cs_bp);
+
+ /* And create the inverse as well: */
+ icmVecRotMat(s->igrot, dr_cs_wp, dr_cs_bp, sswp, ssbp);
+
+ /* Create rotated versions of source colorspace & image white and */
+ /* black points for use from now on, given that rotation will */
+ /* be applied first to all source points. */
+ icmMul3By3x4(sr_cs_wp, s->grot, s_cs_wp);
+ icmMul3By3x4(sr_cs_bp, s->grot, s_cs_bp);
+ icmMul3By3x4(sr_ga_wp, s->grot, s_ga_wp);
+ icmMul3By3x4(sr_ga_bp, s->grot, s_ga_bp);
+
+#ifdef VERBOSE
+ if (verb) {
+ printf("Bend target bp = %f %f %f\n",
+ dr_be_bp[0], dr_be_bp[1], dr_be_bp[2]);
+ printf("Rotated source grey axis wp/bp %f %f %f, %f %f %f\n",
+ sr_cs_wp[0], sr_cs_wp[1], sr_cs_wp[2], sr_cs_bp[0], sr_cs_bp[1], sr_cs_bp[2]);
+ printf("Rotated gamut grey axis wp/bp %f %f %f, %f %f %f\n",
+ sr_ga_wp[0], sr_ga_wp[1], sr_ga_wp[2], sr_ga_bp[0], sr_ga_bp[1], sr_ga_bp[2]);
+ printf("Destination axis target wp/bp %f %f %f, %f %f %f\n",
+ dr_cs_wp[0], dr_cs_wp[1], dr_cs_wp[2], dr_cs_bp[0], dr_cs_bp[1], dr_cs_bp[2]);
+ }
+#endif
+ }
+
+#ifdef NEVER
+sr_cs_wp[0] = 100.0;
+sr_cs_bp[0] = 30.0;
+dr_cs_wp[0] = 80.0;
+dr_cs_bp[0] = 10.0;
+glumknf = 1.0;
+#endif /* NEVER */
+
+ /* Create the mapping points needed to build the 1D L mapping rspl. */
+ /* If we have a gamut (ie. image) range that is smaller than the */
+ /* L range of the colorspace, then use its white and black L values */
+ /* as the source to be compressed to the destination L range. */
+ /* We expand only a colorspace range, not a gamut/image range. */
+ {
+ double swL, dwL; /* Source and destination white point L */
+ double sbL, dbL; /* Source and destination black point L */
+ int j;
+ double t;
+
+ /* Setup white point mapping */
+ if (sr_cs_wp[0] <= dr_cs_wp[0]) { /* Needs possible expansion */
+ swL = sr_cs_wp[0];
+ dwL = gmi->glumwexf * dr_cs_wp[0] + (1.0 - gmi->glumwexf) * sr_cs_wp[0];
+
+ } else {
+ if (sr_ga_wp[0] > dr_cs_wp[0]) { /* Gamut or colorspace needs compression */
+
+ swL = (1.0 - gmi->glumwcpf) * dr_cs_wp[0] + gmi->glumwcpf * sr_ga_wp[0];
+ dwL = dr_cs_wp[0];
+
+ } else { /* Neither needed */
+ swL = sr_ga_wp[0];
+ dwL = sr_ga_wp[0];
+ }
+ }
+
+ /* Setup black point mapping */
+ if (sr_cs_bp[0] >= dr_cs_bp[0]) { /* Needs possible expansion */
+ sbL = sr_cs_bp[0];
+ dbL = gmi->glumbexf * dr_cs_bp[0] + (1.0 - gmi->glumbexf) * sr_cs_bp[0];
+
+ } else {
+ if (sr_ga_bp[0] < dr_cs_bp[0]) { /* Gamut or colorspace needs compression */
+
+ sbL = (1.0 - gmi->glumbcpf) * dr_cs_bp[0] + gmi->glumbcpf * sr_ga_bp[0];
+ dbL = dr_cs_bp[0];
+
+ } else { /* Neither needed */
+ sbL = sr_ga_bp[0];
+ dbL = sr_ga_bp[0];
+ }
+ }
+
+ /* To ensure symetry between compression and expansion, always create RSPL */
+ /* for compression and its inverse, and then swap grey and igrey rspl to compensate. */
+ if ((dwL - dbL) > (swL - sbL))
+ revrspl = 1;
+
+ /* White point end */
+ lpnts[ngreyp].p[0] = swL;
+ lpnts[ngreyp].v[0] = dwL;
+ lpnts[ngreyp++].w = 10.0; /* Must go through here */
+
+ /* Black point end */
+ lpnts[ngreyp].p[0] = sbL;
+ lpnts[ngreyp].v[0] = dbL;
+ lpnts[ngreyp++].w = 10.0; /* Must go through here */
+
+//printf("~1 white loc %f, val %f\n",swL,dwL);
+//printf("~1 black loc %f, val %f\n",sbL,dbL);
+
+#ifdef USE_GLUMKNF
+ if (gmi->glumknf < 0.05)
+#endif /* USE_GLUMKNF */
+ { /* make sure curve is firmly anchored */
+ lpnts[ngreyp].p[0] = 0.3 * lpnts[ngreyp-1].p[0] + 0.7 * lpnts[ngreyp-2].p[0];
+ lpnts[ngreyp].v[0] = 0.3 * lpnts[ngreyp-1].v[0] + 0.7 * lpnts[ngreyp-2].v[0];
+ lpnts[ngreyp++].w = 1.0;
+
+ lpnts[ngreyp].p[0] = 0.7 * lpnts[ngreyp-2].p[0] + 0.3 * lpnts[ngreyp-3].p[0];
+ lpnts[ngreyp].v[0] = 0.7 * lpnts[ngreyp-2].v[0] + 0.3 * lpnts[ngreyp-3].v[0];
+ lpnts[ngreyp++].w = 1.0;
+ }
+#ifdef USE_GLUMKNF
+ else { /* There is at least some weight in knee points */
+ double cppos = 0.50; /* Center point ratio between black and white */
+ double cplv; /* Center point location and value */
+ double kppos = 0.30; /* Knee point ratio between white/black & center */
+ double kwl, kbl, kwv, kbv; /* Knee point values and locations */
+ double kwx, kbx; /* Knee point extra */
+
+
+//printf("sbL = %f, swL = %f\n",sbL,swL);
+//printf("dbL = %f, dwL = %f\n",dbL,dwL);
+
+ /* Center point */
+ cplv = cppos * (swL - sbL) + sbL;
+//printf("~1 computed cplv = %f\n",cplv);
+
+#ifdef NEVER /* Don't use a center point */
+ lpnts[ngreyp].p[0] = cplv;
+ lpnts[ngreyp].v[0] = cplv;
+ lpnts[ngreyp++].w = 0.5;
+#endif
+
+//printf("~1 black half diff = %f\n",dbL - sbL);
+//printf("~1 white half diff = %f\n",dwL - swL);
+
+ /* Knee point locations */
+ kwl = kppos * (cplv - swL) + swL;
+ kbl = kppos * (cplv - sbL) + sbL;
+
+ /* Extra compression for white and black knees */
+ kwx = 0.6 * (dbL - sbL) + 1.0 * (swL - dwL);
+ kbx = 1.0 * (dbL - sbL) + 0.6 * (swL - dwL);
+
+//kwx = 0.0;
+//kbx = 0.0;
+//glumknf = 0.0;
+
+ /* Knee point values */
+ kwv = (dwL + kwx - cplv) * (kwl - cplv)/(swL - cplv) + cplv;
+ if (kwv > dwL) /* Sanity check */
+ kwv = dwL;
+
+ kbv = (dbL - kbx - cplv) * (kbl - cplv)/(sbL - cplv) + cplv;
+ if (kbv < dbL) /* Sanity check */
+ kbv = dbL;
+
+
+//printf("~1 kbl = %f, kbv = %f\n",kbl, kbv);
+//printf("~1 kwl = %f, kwv = %f\n",kwl, kwv);
+
+ /* Emphasise points to cause "knee" curve */
+ lpnts[ngreyp].p[0] = kwl;
+ lpnts[ngreyp].v[0] = kwv;
+ lpnts[ngreyp++].w = gmi->glumknf * gmi->glumknf;
+
+ lpnts[ngreyp].p[0] = kbl;
+ lpnts[ngreyp].v[0] = kbv;
+ lpnts[ngreyp++].w = 1.5 * gmi->glumknf * 1.5 * gmi->glumknf;
+ }
+#endif /* USE_GLUMKNF */
+
+ /* Remember our source and destinatio mapping targets */
+ /* so that we can use them for fine tuning later. */
+
+ /* We scale the source and target white and black */
+ /* points to match the L values of the source and destination */
+ /* L curve mapping, as this is how we have chosen the */
+ /* white and black point mapping for the link. */
+ /* Put them back in pre-rotated space, so that we can */
+ /* check the overall transform of the white and black points. */
+ t = (swL - sr_cs_bp[0])/(sr_cs_wp[0] - sr_cs_bp[0]);
+ for (j = 0; j < 3; j++)
+ s_mt_wp[j] = sr_cs_bp[j] + t * (sr_cs_wp[j] - sr_cs_bp[j]);
+ icmMul3By3x4(s_mt_wp, s->igrot, s_mt_wp);
+
+ t = (sbL - sr_cs_wp[0])/(sr_cs_bp[0] - sr_cs_wp[0]);
+ for (j = 0; j < 3; j++)
+ s_mt_bp[j] = sr_cs_wp[j] + t * (sr_cs_bp[j] - sr_cs_wp[j]);
+//printf("~1 check black point rotated = %f %f %f\n",s_mt_bp[0],s_mt_bp[1],s_mt_bp[2]);
+ icmMul3By3x4(s_mt_bp, s->igrot, s_mt_bp);
+//printf("~1 check black point prerotated = %f %f %f\n",s_mt_bp[0],s_mt_bp[1],s_mt_bp[2]);
+
+ t = (dwL - dr_cs_bp[0])/(dr_cs_wp[0] - dr_cs_bp[0]);
+ for (j = 0; j < 3; j++)
+ d_mt_wp[j] = dr_cs_bp[j] + t * (dr_cs_wp[j] - dr_cs_bp[j]);
+
+ for (j = 0; j < 3; j++)
+ d_mt_bp[j] = dr_cs_wp[j] + t * (dr_cs_bp[j] - dr_cs_wp[j]);
+ }
+
+ /* We now create the 1D rspl L map, that compresses or expands the luminence */
+ /* range, independent of grey axis alignment, or gamut compression. */
+ /* Because the rspl isn't symetrical when we swap X & Y, and we would */
+ /* like a conversion from profile A to B to be the inverse of profile B to A */
+ /* (as much as possible), we contrive here to always create a compression */
+ /* RSPL, and create an inverse for it, and swap the two of them so that */
+ /* the transform is correct and has an accurate inverse available. */
+ {
+ datai il, ih;
+ datao ol, oh;
+ double avgdev[MXDO];
+ int gres = 256;
+
+ if (revrspl) { /* Invert creation and usage for symetry between compress and exp. */
+ int i;
+ for (i = 0; i < ngreyp; i++) {
+ double tt = lpnts[i].p[0]; /* Swap source and dest */
+ lpnts[i].p[0] = lpnts[i].v[0];
+ lpnts[i].v[0] = tt;
+ }
+ }
+
+ /* Create a 1D rspl, that is used to */
+ /* form the overall L compression mapping. */
+ if ((s->grey = new_rspl(RSPL_NOFLAGS, 1, 1)) == NULL) /* Allocate 1D -> 1D */
+ error("gamut: grey new_rspl failed");
+
+ il[0] = -1.0; /* Set possible input range */
+ ih[0] = 101.0;
+ ol[0] = 0.0; /* Set normalisation output range */
+ oh[0] = 100.0;
+
+#ifdef NEVER /* Dump out the L mapping points */
+ {
+ int i;
+ printf("1D rspl L mapping points:\n");
+ for (i = 0; i < ngreyp; i++)
+ printf("%d %f -> %f (w %f)\n",i,lpnts[i].p[0],lpnts[i].v[0],lpnts[i].w);
+ }
+#endif
+ /* Create spline from the data points, with appropriate smoothness. */
+ avgdev[0] = GAMMAP_RSPLAVGDEV;
+ if (s->grey->fit_rspl_w(s->grey, GAMMAP_RSPLFLAGS, lpnts, ngreyp, il, ih, &gres, ol, oh, 5.0, avgdev, NULL)) {
+ fprintf(stderr,"Warning: Grey axis mapping is non-monotonic - may not be very smooth ?\n");
+ }
+
+ /* Create an inverse mapping too, for reverse gamut and/or expansion. */
+ il[0] = -1.0; /* Set possible input range */
+ ih[0] = 101.0;
+ ol[0] = 0.0; /* Set normalisation output range */
+ oh[0] = 100.0;
+
+ if ((s->igrey = new_rspl(RSPL_NOFLAGS, 1, 1)) == NULL) /* Allocate 1D -> 1D */
+ error("gamut: igrey new_rspl failed");
+
+ /* Create it from inverse lookups of s->grey */
+ s->igrey->set_rspl(s->igrey, 0, (void *)s->grey, inv_grey_func, il, ih, &gres, ol, oh);
+
+ if (revrspl) { /* Swap to compensate for expansion */
+ rspl *tt = s->grey;
+ s->grey = s->igrey;
+ s->igrey = tt;
+ }
+ }
+
+#ifdef PLOT_LMAP
+ { /* Plot the 1D mapping */
+ double xx[XRES];
+ double y1[XRES];
+ int i;
+
+ for (i = 0; i < XRES; i++) {
+ double x;
+ co cp; /* Conversion point */
+ x = sr_cs_bp[0] + (i/(double)(XRES-1)) * (sr_cs_wp[0] - sr_cs_bp[0]);
+ xx[i] = x;
+ cp.p[0] = x;
+ s->grey->interp(s->grey, &cp);
+ y1[i] = cp.v[0];
+ }
+ do_plot(xx,y1,NULL,NULL,XRES);
+ }
+#endif /* PLOT_LMAP */
+
+ {
+ /* We want to rotate and then map L independently of everything else, */
+ /* so transform source csape & image gamuts through the rotation and L mapping */
+ /* before we create the surface 3D mapping from them */
+
+ /* Create L mapped versions of rotated src colorspace white/black points */
+#ifdef NEVER
+ co cp;
+ double t;
+ int i;
+
+ cp.p[0] = sr_cs_wp[0];
+ s->grey->interp(s->grey, &cp);
+
+ t = (cp.v[0] - sr_cs_bp[0])/(sr_cs_wp[0] - sr_cs_bp[0]);
+ for (j = 0; j < 3; j++)
+ sl_cs_wp[j] = sr_cs_bp[j] + t * (sr_cs_wp[j] - sr_cs_bp[j]);
+
+ cp.p[0] = sr_cs_bp[0];
+ s->grey->interp(s->grey, &cp);
+ t = (cp.v[0] - sr_cs_wp[0])/(sr_cs_bp[0] - sr_cs_wp[0]);
+ for (j = 0; j < 3; j++)
+ sl_cs_bp[j] = sr_cs_wp[j] + t * (sr_cs_bp[j] - sr_cs_wp[j]);
+#else
+ dopartialmap1(s, sl_cs_wp, s_cs_wp);
+ dopartialmap1(s, sl_cs_bp, s_cs_bp);
+#endif
+
+#ifdef VERBOSE
+ if (verb) {
+ printf("Mapped source grey axis wp/bp %f %f %f, %f %f %f\n",
+ sl_cs_wp[0], sl_cs_wp[1], sl_cs_wp[2], sl_cs_bp[0], sl_cs_bp[1], sl_cs_bp[2]);
+ }
+#endif
+
+ if ((scl_gam = parttransgamut(s, sc_gam)) == NULL) {
+ fprintf(stderr,"gamut map: parttransgamut failed\n");
+ free(s);
+ return NULL;
+ }
+
+ if (sc_gam == si_gam)
+ sil_gam = scl_gam;
+
+ else {
+ if ((sil_gam = parttransgamut(s, si_gam)) == NULL) {
+ fprintf(stderr,"gamut map: parttransgamut failed\n");
+ free(s);
+ return NULL;
+ }
+ }
+ }
+
+ /* Create all the 3D->3D gamut mapping points and 3D rspl, */
+ /* if there is any compression or expansion to do. */
+ if (gmi->gamcpf > 1e-6 || gmi->gamexf > 1e-6) {
+ cow *gpnts = NULL; /* Mapping points to create gamut mapping */
+ int nspts; /* Number of source gamut surface points */
+ int rgridpts; /* Number of range surface grid points */
+ int i, j;
+ datai il, ih;
+ datao ol, oh;
+ int gres[MXDI];
+ double avgdev[MXDO];
+ nearsmth *nsm = NULL; /* Returned list of near smooth points */
+ int nnsm; /* Number of near smoothed points */
+ double brad = 0.0; /* Black bend radius */
+ gammapweights xpweights[14], xsweights[14]; /* Explicit perceptial and sat. weights */
+ gammapweights xwh[14]; /* Structure holding blended weights */
+ double smooth = 1.0; /* Level of 3D RSPL smoothing, blend of psmooth and ssmooth */
+ vrml *wrl = NULL; /* Gamut mapping illustration (hulls + guide vectors) */
+ cgats *locus = NULL; /* Diagnostic locus to plot in wrl, NULL if none */
+
+#ifdef PLOT_3DKNEES
+typedef struct {
+ double v0[3], v1[3];
+} p3dk_lpoint;
+ p3dk_lpoint *p3dk_locus;
+ int p3dk_ix = 0;
+#endif /* PLOT_3DKNEES */
+
+ /* Get the maximum number of points that will be created */
+ nspts = near_smooth_np(scl_gam, sil_gam, d_gam, xvra);
+
+ rgridpts = 0;
+#ifdef USE_BOUND
+ if (defrgrid >= 2) {
+ rgridpts = defrgrid * defrgrid * defrgrid
+ - (defrgrid -2) * (defrgrid -2) * (defrgrid -2);
+ }
+#endif
+
+ if ((gpnts = (cow *)malloc((nres + 3 * nspts + rgridpts) * sizeof(cow))) == NULL) {
+ fprintf(stderr,"gamut map: Malloc of mapping setup points failed\n");
+ s->grey->del(s->grey);
+ s->igrey->del(s->igrey);
+ if (sil_gam != scl_gam)
+ sil_gam->del(sil_gam);
+ scl_gam->del(scl_gam);
+ free(s);
+ return NULL;
+ }
+
+#ifdef PLOT_3DKNEES
+ if ((p3dk_locus = (p3dk_lpoint *)malloc((2 * nspts) * sizeof(p3dk_lpoint))) == NULL)
+ error("gamut: Diagnostic array p3dk_locus malloc failed");
+#endif /* PLOT_3DKNEES */
+
+ /* ------------------------------------------- */
+ /* Finish off the grey axis mapping by creating the */
+ /* grey axis 3D->3D mapping points */
+ /* We use 4 times the grid density, and create */
+ /* points that span the source colorspace (this may exceed) */
+ /* the source image gamut, and map to points outside the */
+ /* destination gamut) */
+
+ /* See how much to bend the black - compute the color difference */
+ /* We start out in the direction of dr_be_bp at white, and at */
+ /* the end we bend towards the overall bp dr_cs_bp */
+ /* (brad will be 0 for non gmm_bendBP because dr_be_bp dr_cs_bp */
+ for (brad = 0.0, i = 1; i < 3; i++) {
+ double tt = dr_be_bp[i] - dr_cs_bp[i];
+ brad += tt * tt;
+ }
+ brad = sqrt(brad);
+
+//printf("~1 brad = %f, Bend target = %f %f %f, straight = %f %f %f\n",
+//brad, dr_be_bp[0], dr_be_bp[1], dr_be_bp[2], dr_cs_bp[0], dr_cs_bp[1], dr_cs_bp[2]);
+
+#ifdef USE_GREYMAP
+ for (i = 0; i < nres; i++) { /* From black to white */
+ double t;
+ double bv[3]; /* Bent (initial) destination value */
+ double dv[3]; /* Straight (final) destination value */
+ double wt = 1.0; /* Default grey axis point weighting */
+
+ /* Create source grey axis point */
+ t = i/(nres - 1.0);
+
+ /* Cover L = 0.0 to 100.0 */
+ t = ((100.0 * t) - sl_cs_bp[0])/(sl_cs_wp[0] - sl_cs_bp[0]);
+ for (j = 0; j < 3; j++)
+ gpnts[ngamp].p[j] = sl_cs_bp[j] + t * (sl_cs_wp[j] - sl_cs_bp[j]);
+
+ /* L values are the same, as they have been mapped prior to 3D */
+ gpnts[ngamp].v[0] = gpnts[ngamp].p[0];
+
+ /* Figure destination point on initial bent grey axis */
+ t = (gpnts[ngamp].v[0] - dr_cs_wp[0])/(dr_be_bp[0] - dr_cs_wp[0]);
+ for (j = 0; j < 3; j++)
+ bv[j] = dr_cs_wp[j] + t * (dr_be_bp[j] - dr_cs_wp[j]);
+//printf("~1 t = %f, bent dest %f %f %f\n",t, bv[0], bv[1],bv[2]);
+
+ /* Figure destination point on final straight grey axis */
+ t = (gpnts[ngamp].v[0] - dr_cs_wp[0])/(dr_cs_bp[0] - dr_cs_wp[0]);
+ for (j = 0; j < 3; j++)
+ dv[j] = dr_cs_wp[j] + t * (dr_cs_bp[j] - dr_cs_wp[j]);
+//printf("~1 t = %f, straight dest %f %f %f\n",t, dv[0], dv[1],dv[2]);
+
+ /* Figure out a blend value between the bent value */
+ /* and the straight value, so that it curves smoothly from */
+ /* one to the other. */
+ if (brad > 0.001) {
+ double ty;
+ t = ((dr_cs_bp[0] + brad) - gpnts[ngamp].v[0])/brad;
+ if (t < 0.0)
+ t = 0.0;
+ else if (t > 1.0)
+ t = 1.0;
+ /* Make it a spline ? */
+ t = t * t * (3.0 - 2.0 * t);
+ ty = t * t * (3.0 - 2.0 * t); /* spline blend value */
+ t = (1.0 - t) * ty + t * t; /* spline at t == 0, linear at t == 1 */
+
+ wt *= (1.0 + t * brad); /* Increase weigting with the bend */
+
+ } else {
+ t = 0.0; /* stick to straight, it will be close anyway. */
+ }
+
+ for (j = 0; j < 3; j++) /* full straight when t == 1 */
+ gpnts[ngamp].v[j] = t * dv[j] + (1.0 - t) * bv[j];
+ gpnts[ngamp].w = wt;
+//printf("~1 t = %f, blended %f %f %f\n",t, gpnts[ngamp].v[0], gpnts[ngamp].v[1],gpnts[ngamp].v[2]);
+
+#ifdef NEVER
+ printf("Grey axis %d maps %f %f %f -> %f %f %f wit %f\n",ngamp,
+ gpnts[ngamp].p[0], gpnts[ngamp].p[1], gpnts[ngamp].p[2],
+ gpnts[ngamp].v[0], gpnts[ngamp].v[1], gpnts[ngamp].v[2],
+ gpnts[ngamp].w);
+#endif
+ ngamp++;
+ }
+#endif /* USE_GREYMAP */
+
+ /* ---------------------------------------------------- */
+ /* Do preliminary computation of the rspl input and output bounding values */
+ for (j = 0; j < 3; j++) {
+ il[j] = ol[j] = 1e60;
+ ih[j] = oh[j] = -1e60;
+ }
+
+ /* From grey axis points */
+ for (i = 0; i < ngamp; i++) {
+ for (j = 0; j < 3; j++) {
+ if (gpnts[i].p[j] < il[j])
+ il[j] = gpnts[i].p[j];
+ if (gpnts[i].p[j] > ih[j])
+ ih[j] = gpnts[i].p[j];
+ }
+ }
+
+ /* From the source gamut */
+ {
+ double tmx[3], tmn[3];
+ scl_gam->getrange(scl_gam, tmn, tmx);
+ for (j = 0; j < 3; j++) {
+ if (tmn[j] < il[j])
+ il[j] = tmn[j];
+ if (tmx[j] > ih[j])
+ ih[j] = tmx[j];
+ }
+ }
+
+ /* from input arguments override */
+ if (mn != NULL && mx != NULL) {
+
+ for (j = 0; j < 3; j++) {
+ if (mn[j] < il[j])
+ il[j] = mn[j];
+ if (mx[j] > ih[j])
+ ih[j] = mx[j];
+ }
+ }
+
+ /* From the destination gamut */
+ {
+ double tmx[3], tmn[3];
+ d_gam->getrange(d_gam, tmn, tmx);
+ for (j = 0; j < 3; j++) {
+ if (tmn[j] < ol[j])
+ ol[j] = tmn[j];
+ if (tmx[j] > oh[j])
+ oh[j] = tmx[j];
+ }
+ }
+
+ /* ---------------------------------------------------- */
+ /* Deal with gamut hull guide vector creation. */
+
+ /* For compression, create a mapping for each vertex of */
+ /* the source gamut (image) surface towards the destination gamut */
+ /* For expansion, do the opposite. */
+
+ /* Convert from compact to explicit hextant weightings */
+ if (expand_weights(xpweights, pweights)
+ || expand_weights(xsweights, sweights)) {
+ fprintf(stderr,"gamut map: expand_weights() failed\n");
+ s->grey->del(s->grey);
+ s->igrey->del(s->igrey);
+ if (sil_gam != scl_gam)
+ sil_gam->del(sil_gam);
+ scl_gam->del(scl_gam);
+ free(s);
+ return NULL;
+ }
+ /* Create weights as blend between perceptual and saturation */
+ near_xwblend(xwh, xpweights, gmi->gampwf, xsweights, gmi->gamswf);
+ if ((gmi->gampwf + gmi->gamswf) > 0.1)
+ smooth = (gmi->gampwf * psmooth) + (gmi->gamswf * ssmooth);
+
+ /* Tweak gamut mappings according to extra cmy cusp flags or rel override */
+ if (dst_cmymap != 0 || rel_oride != 0) {
+ tweak_weights(xwh, dst_cmymap, rel_oride);
+ }
+
+ /* Create the near point mapping, which is our fundamental gamut */
+ /* hull to gamut hull mapping. */
+ nsm = near_smooth(verb, &nnsm, scl_gam, sil_gam, d_gam, src_kbp, dst_kbp,
+ dr_cs_bp, xwh, gmi->gamcknf, gmi->gamxknf,
+ gmi->gamcpf > 1e-6, gmi->gamexf > 1e-6,
+ xvra, mapres, smooth, il, ih, ol, oh);
+ if (nsm == NULL) {
+ fprintf(stderr,"Creating smoothed near points failed\n");
+ s->grey->del(s->grey);
+ s->igrey->del(s->igrey);
+ if (sil_gam != scl_gam)
+ sil_gam->del(sil_gam);
+ scl_gam->del(scl_gam);
+ free(s);
+ return NULL;
+ }
+ /* --------------------------- */
+
+ /* Make sure the input range to encompasss the guide vectors. */
+ for (i = 0; i < nnsm; i++) {
+ for (j = 0; j < 3; j++) {
+ if (nsm[i].sv[j] < il[j])
+ il[j] = nsm[i].sv[j];;
+ if (nsm[i].sv[j] > ih[j])
+ ih[j] = nsm[i].sv[j];
+ }
+ }
+
+#ifdef NEVER
+ if (verb) {
+ fprintf(stderr,"Input bounding box:\n");
+ fprintfstderr,("%f -> %f, %f -> %f, %f -> %f\n",
+ il[0], ih[0], il[1], ih[1], il[2], ih[2]);
+ }
+#endif
+
+ /* Now expand the bounding box by aprox 5% margin, but scale grid res */
+ /* to match, so that the natural or given boundary still lies on the grid. */
+ {
+ int xmapres;
+ double scale;
+
+ xmapres = (int) ((mapres-1) * 0.05 + 0.5);
+ if (xmapres < 1)
+ xmapres = 1;
+
+ scale = (double)(mapres-1 + xmapres)/(double)(mapres-1);
+
+ for (j = 0; j < 3; j++) {
+ double low, high;
+ high = ih[j];
+ low = il[j];
+ ih[j] = (scale * (high - low)) + low;
+ il[j] = (scale * (low - high)) + high;
+ }
+
+ mapres += 2 * xmapres;
+#ifdef NEVER
+ if (verb) {
+ fprintf(stderr,"After incresing mapres to %d, input bounding box for 3D gamut mapping is:\n",mapres);
+ fprintf(stderr,"%f -> %f, %f -> %f, %f -> %f\n",
+ il[0], ih[0], il[1], ih[1], il[2], ih[2]);
+ }
+#endif
+ }
+
+ /* ---------------------------------------------------- */
+ /* Setup for diagnostic plot, that will have elements added */
+ /* as we create the final 3D gamut mapping rspl */
+ /* (The plot is of the already rotated and L mapped source space) */
+ {
+ int doaxes = 0;
+
+#ifdef PLOT_AXES
+ doaxes = 1;
+#endif
+ if (diagname != NULL)
+ wrl = new_vrml(diagname, doaxes, 0);
+#ifdef PLOT_DIAG_WRL
+ else
+ wrl = new_vrml("gammap.wrl", doaxes, 0);
+#endif
+ }
+
+ if (wrl != NULL) {
+ /* See if there is a diagnostic locus to plot too */
+ if ((locus = new_cgats()) == NULL)
+ error("Failed to create cgats object");
+
+ locus->add_other(locus, "TS");
+
+ if (locus->read_name(locus, "locus.ts")) {
+ locus->del(locus);
+ locus = NULL;
+ } else {
+ if (verb)
+ printf("!! Found diagnostic locus.ts file !!\n");
+ /* locus will be added later */
+ }
+
+ /* Add diagnostic markers from markers structure */
+ for (i = 0; ; i++) {
+ double pp[3];
+ co cp;
+ if (markers[i].type == 0)
+ break;
+
+ if (markers[i].type == 1) { /* Src point - do luminance mapping */
+ dopartialmap1(s, pp, markers[i].pos);
+ } else {
+ pp[0] = markers[i].pos[0];
+ pp[1] = markers[i].pos[1];
+ pp[2] = markers[i].pos[2];
+ }
+ wrl->add_marker(wrl, pp, markers[i].col, 1.0);
+ }
+ }
+
+ /* --------------------------- */
+ /* Now computue our 3D mapping points from the near point mapping. */
+ for (i = 0; i < nnsm; i++) {
+ double cpexf; /* The effective compression or expansion factor */
+
+ if (nsm[i].vflag == 0) { /* Unclear whether compression or expansion */
+ /* Use larger to the the two factors */
+ cpexf = gmi->gamcpf > gmi->gamexf ? gmi->gamcpf : gmi->gamexf;
+
+ } else if (nsm[i].vflag == 1) { /* Compression */
+ cpexf = gmi->gamcpf;
+
+ } else if (nsm[i].vflag == 2) { /* Expansion */
+ cpexf = gmi->gamexf;
+
+ } else {
+ error("gammap: internal, unknown guide point flag");
+ }
+
+ /* Compute destination value which is a blend */
+ /* between the source value and the fully mapped destination value. */
+ icmBlend3(nsm[i].div, nsm[i].sv, nsm[i].dv, cpexf);
+
+#ifdef NEVER
+ printf("%s mapping:\n",nsm[i].vflag == 0 ? "Unclear" : nsm[i].vflag == 1 ? "Compression" : "Expansion");
+ printf("Src point = %f %f %f radius %f\n",nsm[i].sv[0], nsm[i].sv[1], nsm[i].sv[2], nsm[i].sr);
+ printf("Dst point = %f %f %f radius %f\n",nsm[i].dv[0], nsm[i].dv[1], nsm[i].dv[2], nsm[i].dr);
+ printf("Blended dst point = %f %f %f\n",nsm[i].div[0], nsm[i].div[1], nsm[i].div[2]);
+#endif /* NEVER */
+ /* Set the main gamut hull mapping point */
+ for (j = 0; j < 3; j++) {
+ gpnts[ngamp].p[j] = nsm[i].sv[j];
+ gpnts[ngamp].v[j] = nsm[i].div[j];
+ }
+ gpnts[ngamp++].w = 1.01; /* Main gamut surface mapping point */
+ /* (Use 1.01 as a marker value) */
+
+#ifdef USE_GAMKNF
+ /* Add sub surface mapping point if available */
+ if (nsm[i].vflag != 0) { /* Sub surface point is available */
+
+ /* Compute destination value which is a blend */
+ /* between the source value and the fully mapped destination value. */
+ icmBlend3(nsm[i].div2, nsm[i].sv2, nsm[i].dv2, cpexf);
+
+#ifdef NEVER
+ printf("Src2 point = %f %f %f radius %f\n",nsm[i].sv2[0], nsm[i].sv2[1], nsm[i].sv2[2], nsm[i].sr);
+ printf("Dst2 point = %f %f %f radius %f\n",nsm[i].dv2[0], nsm[i].dv2[1], nsm[i].dv2[2], nsm[i].dr);
+ printf("Blended dst2 point = %f %f %f\n",nsm[i].div2[0], nsm[i].div2[1], nsm[i].div2[2]);
+ printf("\n");
+#endif /* NEVER */
+ /* Set the sub-surface gamut hull mapping point */
+ for (j = 0; j < 3; j++) {
+ gpnts[ngamp].p[j] = nsm[i].sv2[j];
+ gpnts[ngamp].v[j] = nsm[i].div2[j];
+ }
+ gpnts[ngamp++].w = nsm[i].w2; /* Sub-suface mapping points */
+ }
+#endif /* USE_GAMKNF */
+ }
+
+ /* Create preliminary gamut mapping rspl, without grid boundary values. */
+ /* We use this to lookup the mapping for points on the source space gamut */
+ /* that result from clipping our grid boundary points */
+#ifdef USE_BOUND
+ for (j = 0; j < 3; j++) { /* Set resolution for all axes */
+ gres[j] = (mapres+1)/2;
+ avgdev[j] = GAMMAP_RSPLAVGDEV;
+ }
+ s->map = new_rspl(RSPL_NOFLAGS, 3, 3); /* Allocate 3D -> 3D */
+ s->map->fit_rspl_w(s->map, GAMMAP_RSPLFLAGS, gpnts, ngamp, il, ih, gres, ol, oh, smooth, avgdev, NULL);
+
+ /* Add input range grid surface anchor points to improve clipping behaviour. */
+ if (defrgrid >= 2) {
+ DCOUNT(gc, 3, 3, 0, 0, defrgrid);
+ double cent[3];
+
+ sc_gam->getcent(d_gam, cent);
+
+ DC_INIT(gc);
+ for (;;) {
+ /* If point is on the grid surface */
+ if ( gc[0] == 0 || gc[0] == (defrgrid-1)
+ || gc[1] == 0 || gc[1] == (defrgrid-1)
+ || gc[2] == 0 || gc[2] == (defrgrid-1)) {
+ double grid2gamut, gamut2cent, ww;
+ co cp;
+
+ /* Clip the point to the closest location on the source */
+ /* colorspace gamut. */
+ for (j = 0; j < 3; j++)
+ gpnts[ngamp].p[j] = il[j] + gc[j]/(defrgrid-1.0) * (ih[j] - il[j]);
+ sc_gam->nearest(sc_gam, cp.p, gpnts[ngamp].p);
+
+ /* Then lookup the equivalent gamut mapped value */
+ s->map->interp(s->map, &cp);
+
+ for (j = 0; j < 3; j++)
+ gpnts[ngamp].v[j] = cp.v[j];
+
+ /* Compute the distance of the grid surface point to the to the */
+ /* source colorspace gamut, as well as the distance from there */
+ /* to the gamut center point. */
+ for (grid2gamut = gamut2cent = 0.0, j = 0; j < 3; j++) {
+ double tt;
+ tt = gpnts[ngamp].p[j] - cp.p[j];
+ grid2gamut += tt * tt;
+ tt = cp.p[j] - cent[j];
+ gamut2cent += tt * tt;
+ }
+ grid2gamut = sqrt(grid2gamut);
+ gamut2cent = sqrt(gamut2cent);
+
+ /* Make the weighting inversely related to distance, */
+ /* to reduce influence on in gamut mapping shape, */
+ /* while retaining some influence at the edge of the */
+ /* grid. */
+ ww = grid2gamut / gamut2cent;
+ if (ww > 1.0)
+ ww = 1.0;
+
+ /* A low weight seems to be enough ? */
+ /* the lower the better in terms of geting best hull mapping fidelity */
+ gpnts[ngamp++].w = 0.05 * ww;
+ }
+ DC_INC(gc);
+ if (DC_DONE(gc))
+ break;
+ }
+ }
+#else /* !USE_BOUND */
+ printf("!!!! Warning - gammap boundary points disabled !!!!\n");
+#endif /* !USE_BOUND */
+
+ /* --------------------------- */
+ /* Compute the output bounding values, and check input range hasn't changed */
+ for (i = 0; i < ngamp; i++) {
+ for (j = 0; j < 3; j++) {
+ if (gpnts[i].p[j] < (il[j]-1e-5) || gpnts[i].p[j] > (ih[j]+1e-5))
+ warning("gammap internal: input bounds has changed! %f <> %f <> %f",il[j],gpnts[i].p[j],ih[j]);
+ if (gpnts[i].v[j] < ol[j])
+ ol[j] = gpnts[i].v[j];
+ if (gpnts[i].v[j] > oh[j])
+ oh[j] = gpnts[i].v[j];
+ }
+ }
+
+ /* --------------------------- */
+
+#ifdef NEVER /* Dump out all the mapping points */
+ {
+ for (i = 0; i < ngamp; i++) {
+ printf("%d: %f %f %f -> %f %f %f\n",i,
+ gpnts[i].p[0], gpnts[i].p[1], gpnts[i].p[2],
+ gpnts[i].v[0], gpnts[i].v[1], gpnts[i].v[2]);
+ }
+ }
+#endif
+
+ /* Create the final gamut mapping rspl. */
+ /* [ The smoothing is not as useful as it should be, because */
+ /* if it is increased it tends to push colors out of gamut */
+ /* where they get clipped. Some cleverer scheme which makes */
+ /* sure that smoothness errs on the side of more compression */
+ /* is needed. - Addressed in nearsmth now ? ] */
+ /* How about converting to a delta filer ? ie. */
+ /* create curren filter, then create point list of delta from */
+ /* smoothed value, filtering that and then un-deltering it ?? */
+ if (s->map != NULL)
+ s->map->del(s->map);
+ if (verb)
+ printf("Creating rspl..\n");
+ for (j = 0; j < 3; j++) { /* Set resolution for all axes */
+ gres[j] = mapres;
+ avgdev[j] = GAMMAP_RSPLAVGDEV;
+ }
+ s->map = new_rspl(RSPL_NOFLAGS, 3, 3); /* Allocate 3D -> 3D */
+ if (s->map->fit_rspl_w(s->map, GAMMAP_RSPLFLAGS, gpnts, ngamp, il, ih, gres, ol, oh, smooth, avgdev, NULL)) {
+ if (verb)
+ fprintf(stderr,"Warning: Gamut mapping is non-monotonic - may not be very smooth !\n");
+ }
+ /* return the min and max of the input values valid in the grid */
+ s->map->get_in_range(s->map, s->imin, s->imax);
+
+#ifdef CHECK_NEARMAP
+ /* Check how accurate gamut shell mapping is against nsm */
+ /* (This isn't a good indication now that vectors have been adjusted */
+ /* to counteract the rspl smoothing at the edges.) */
+ if (verb) {
+ double de, avgde = 0.0, maxde = 0.0; /* DE stats */
+
+ for (i = 0; i < nnsm; i++) {
+ double av[3];
+
+ /* Compute the mapping error */
+ dopartialmap2(s, av, nsm[i].sv); /* Just the rspl */
+
+ de = icmLabDE(nsm[i].div, av);
+ avgde += de;
+ if (de > maxde)
+ maxde = de;
+ }
+ printf("Gamut hull fit to guides: = avg %f, max %f\n",avgde/nnsm,maxde);
+ }
+#endif /* CHECK_NEARMAP */
+
+ /* If requested, enhance the saturation of the output values. */
+ if (gmi->satenh > 0.0) {
+ adjustsat cx; /* Adjustment context */
+
+ /* Compute what our source white and black points actually maps to */
+ s->domap(s, cx.wp, s_mt_wp);
+ s->domap(s, cx.bp, s_mt_bp);
+
+ cx.dst = d_gam;
+ cx.satenh = gmi->satenh;
+
+ /* Saturation enhance the output values */
+ s->map->re_set_rspl(
+ s->map, /* this */
+ 0, /* Combination of flags */
+ (void *)&cx, /* Opaque function context */
+ adjust_sat_func /* Function to set from */
+ );
+ }
+
+ /* Test the gamut white and black point mapping, and "fine tune" */
+ /* the mapping, to ensure an accurate transform of the white */
+ /* and black points to the destination colorspace. */
+ /* This compensates for any inacuracy introduced in the */
+ /* various rspl mappings. */
+ {
+ adjustwb cx; /* Adjustment context */
+ double a_wp[3]; /* actual white point */
+ double a_bp[3]; /* actual black point */
+
+ if (verb)
+ printf("Fine tuning white and black point mapping\n");
+
+ /* Check what the source white and black points actually maps to */
+ s->domap(s, a_wp, s_mt_wp);
+ s->domap(s, a_bp, s_mt_bp);
+
+#ifdef VERBOSE
+ if (verb) {
+ printf("White is %f %f %f, should be %f %f %f\n",
+ a_wp[0], a_wp[1], a_wp[2], d_mt_wp[0], d_mt_wp[1], d_mt_wp[2]);
+ printf("Black is %f %f %f, should be %f %f %f\n",
+ a_bp[0], a_bp[1], a_bp[2], d_mt_bp[0], d_mt_bp[1], d_mt_bp[2]);
+ }
+#endif /* VERBOSE */
+
+ /* Setup the fine tune transform */
+
+ /* We've decided not to fine tune the black point if we're */
+ /* bending to the destination black, as the bend is not */
+ /* followed perfectly (too sharp, or in conflict with */
+ /* the surface mapping ?) and we don't want to shift */
+ /* mid neutrals due to this. */
+ /* We do fine tune it if dst_kbp is set though, since */
+ /* we would like perfect K only out. */
+
+ /* Compute rotation/scale relative white point matrix */
+ icmVecRotMat(cx.mat, a_wp, a_bp, d_mt_wp, d_mt_bp); /* wp & bp */
+
+ /* Fine tune the 3D->3D mapping */
+ s->map->re_set_rspl(
+ s->map, /* this */
+ 0, /* Combination of flags */
+ (void *)&cx, /* Opaque function context */
+ adjust_wb_func /* Function to set from */
+ );
+
+#ifdef VERBOSE
+ if (verb) {
+ /* Check what the source white and black points actually maps to */
+ s->domap(s, a_wp, s_mt_wp);
+ s->domap(s, a_bp, s_mt_bp);
+
+ printf("After fine tuning:\n");
+ printf("White is %f %f %f, should be %f %f %f\n",
+ a_wp[0], a_wp[1], a_wp[2], d_mt_wp[0], d_mt_wp[1], d_mt_wp[2]);
+ printf("Black is %f %f %f, should be %f %f %f\n",
+ a_bp[0], a_bp[1], a_bp[2], d_mt_bp[0], d_mt_bp[1], d_mt_bp[2]);
+ }
+#endif /* VERBOSE */
+ }
+
+ if (wrl != NULL) {
+ int arerings = 0;
+ double cc[3] = { 0.7, 0.7, 0.7 };
+ double nc[3] = { 1.0, 0.4, 0.7 }; /* Pink for neighbors */
+ int nix = -1; /* Index of point to show neighbour */
+
+#ifdef SHOW_NEIGBORS
+#ifdef NEVER
+ /* Show all neighbours */
+ wrl->start_line_set(wrl, 0);
+ for (i = 0; i < nnsm; i++) {
+ for (j = 0; j < XNNB; j++) {
+ nearsmth *np = nsm[i].n[j]; /* Pointer to neighbor */
+
+ if (np == NULL)
+ break;
+
+ wrl->add_col_vertex(wrl, 0, nsm[i].sv, nc); /* Source value */
+ wrl->add_col_vertex(wrl, 0, np->sv, nc); /* Neighbpor value */
+ }
+ }
+ wrl->make_lines(wrl, 0, 2);
+#else
+ /* Show neighbours of points near source markers */
+ for (i = 0; ; i++) { /* Add diagnostic markers */
+ double pp[3];
+ co cp;
+ int ix, bix;
+ double bdist = 1e6;
+
+ if (markers[i].type == 0)
+ break;
+
+ if (markers[i].type != 1)
+ continue;
+
+ /* Rotate and map marker point the same as the src gamuts */
+ icmMul3By3x4(pp, s->grot, markers[i].pos);
+ cp.p[0] = pp[0]; /* L value */
+ s->grey->interp(s->grey, &cp);
+ pp[0] = cp.v[0];
+//printf("~1 looking for closest point to marker %d at %f %f %f\n",i,pp[0],pp[1],pp[2]);
+
+ /* Locate the nearest source point */
+ for (ix = 0; ix < nnsm; ix++) {
+ double dist = icmNorm33(pp, nsm[ix].sv);
+ if (dist < bdist) {
+ bdist = dist;
+ bix = ix;
+ }
+ }
+//printf("~1 closest src point ix %d at %f %f %f\n",bix,nsm[bix].sv[0],nsm[bix].sv[1],nsm[bix].sv[2]);
+//printf("~1 there are %d neighbours\n",nsm[bix].nnb);
+
+ wrl->start_line_set(wrl, 0);
+ for (j = 0; j < nsm[bix].nnb; j++) {
+ nearsmth *np = nsm[bix].n[j].n; /* Pointer to neighbor */
+
+ wrl->add_col_vertex(wrl, 0, nsm[bix].sv, nc); /* Source value */
+ wrl->add_col_vertex(wrl, 0, np->sv, nc); /* Neighbpor value */
+ }
+ wrl->make_lines(wrl, 0, 2);
+ }
+#endif
+#endif /* SHOW_NEIGBORS */
+
+ /* Add the source and dest gamut surfaces */
+#ifdef PLOT_SRC_GMT
+ wrl->make_gamut_surface_2(wrl, sil_gam, 0.6, 0, cc);
+#endif /* PLOT_SRC_GMT */
+#ifdef PLOT_DST_GMT
+ cc[0] = -1.0;
+ wrl->make_gamut_surface(wrl, d_gam, 0.2, cc);
+#endif /* PLOT_DST_GMT */
+#ifdef PLOT_DIGAM
+ if (nsm[0].dgam == NULL)
+ error("Need to #define PLOT_DIGAM in nearsmth.c!");
+ cc[0] = -1.0;
+ wrl->make_gamut_surface(wrl, nsm[0].dgam, 0.2, cc);
+#endif /* PLOT_DIGAM */
+#ifdef PLOT_SRC_CUSPS
+ wrl->add_cusps(wrl, sil_gam, 0.6, NULL);
+#endif /* PLOT_SRC_CUSPS */
+#ifdef PLOT_DST_CUSPS
+ wrl->add_cusps(wrl, d_gam, 0.3, NULL);
+#endif /* PLOT_DST_CUSPS */
+
+#ifdef PLOT_TRANSSRC_CUSPS
+ /* Add transformed source cusp markers */
+ {
+ int i;
+ double cusps[6][3];
+ double ccolors[6][3] = {
+ { 1.0, 0.1, 0.1 }, /* Red */
+ { 1.0, 1.0, 0.1 }, /* Yellow */
+ { 0.1, 1.0, 0.1 }, /* Green */
+ { 0.1, 1.0, 1.0 }, /* Cyan */
+ { 0.1, 0.1, 1.0 }, /* Blue */
+ { 1.0, 0.1, 1.0 } /* Magenta */
+ };
+
+ if (sc_gam->getcusps(sc_gam, cusps) == 0) {
+
+ for (i = 0; i < 6; i++) {
+ double val[3];
+
+ s->domap(s, val, cusps[i]);
+ wrl->add_marker(wrl, val, ccolors[i], 2.5);
+ }
+ }
+ }
+#endif
+
+#if defined(SHOW_MAP_VECTORS) || defined(SHOW_SUB_SURF) || defined(SHOW_ACTUAL_VECTORS) || defined(SHOW_ACTUAL_VEC_DIFF)
+ /* Start of guide vector plot */
+ wrl->start_line_set(wrl, 0);
+
+ for (i = 0; i < nnsm; i++) {
+ double cpexf; /* The effective compression or expansion factor */
+ double yellow[3] = { 1.0, 1.0, 0.0 };
+ double red[3] = { 1.0, 0.0, 0.0 };
+ double green[3] = { 0.0, 1.0, 0.0 };
+ double lgrey[3] = { 0.8, 0.8, 0.8 };
+ double purp[3] = { 0.6, 0.0, 1.0 };
+ double blue[3] = { 0.2, 0.2, 1.0 };
+ double *ccc;
+ double mdst[3];
+
+#if defined(SHOW_ACTUAL_VECTORS) || defined(SHOW_ACTUAL_VEC_DIFF)
+# ifdef SHOW_ACTUAL_VECTORS
+ wrl->add_col_vertex(wrl, 0, nsm[i].sv, yellow);
+# else /* SHOW_ACTUAL_VEC_DIFF */
+ wrl->add_col_vertex(wrl, 0, nsm[i].div, yellow);
+# endif
+ dopartialmap2(s, mdst, nsm[i].sv);
+ wrl->add_col_vertex(wrl, 0, mdst, red);
+
+#else
+# ifdef SHOW_MAP_VECTORS
+ ccc = yellow;
+
+ if (nsm[i].gflag == 0)
+ ccc = green; /* Mark "no clear direction" vectors in green->red */
+# ifdef SHOW_CUSPMAP
+ wrl->add_col_vertex(wrl, 0, nsm[i].csv, ccc); /* Cusp mapped source value */
+# else
+ wrl->add_col_vertex(wrl, 0, nsm[i].sv, ccc); /* Source value */
+# endif
+ wrl->add_col_vertex(wrl, 0, nsm[i].div, red); /* Blended destination value */
+# endif /* SHOW_MAP_VECTORS */
+
+# ifdef SHOW_SUB_SURF
+ if (nsm[i].vflag != 0) { /* Sub surface point is available */
+
+ wrl->add_col_vertex(wrl, 0, nsm[i].sv2, lgrey); /* Subs-surf Source value */
+ wrl->add_col_vertex(wrl, 0, nsm[i].div2, purp); /* Blended destination value */
+ }
+# endif /* SHOW_SUB_SURF */
+#endif /* !SHOW_ACTUAL_VECTORS */
+ }
+ wrl->make_lines(wrl, 0, 2); /* Guide vectors */
+#endif /* Show vectors */
+
+#ifdef SHOW_VECTOR_INDEXES
+ for (i = 0; i < nnsm; i++) {
+ double cream[3] = { 0.7, 0.7, 0.5 };
+ char buf[100];
+ sprintf(buf, "%d", i);
+ wrl->add_text(wrl, buf, nsm[i].sv, cream, 0.5);
+ }
+#endif /* SHOW_VECTOR_INDEXES */
+
+ /* add the locus from locus.ts file */
+ if (locus != NULL) {
+ int table, npoints;
+ char *fnames[3] = { "LAB_L", "LAB_A", "LAB_B" };
+ int ix[3];
+ double v0[3], v1[3];
+ double rgb[3];
+
+ /* Each table holds a separate locus */
+ for (table = 0; table < locus->ntables; table++) {
+
+ if ((npoints = locus->t[table].nsets) <= 0)
+ error("No sets of data in diagnostic locus");
+
+ for (j = 0; j < 3; j++) {
+ if ((ix[j] = locus->find_field(locus, 0, fnames[j])) < 0)
+ error ("Locus file doesn't contain field %s",fnames[j]);
+ if (locus->t[table].ftype[ix[j]] != r_t)
+ error ("Field %s is wrong type",fnames[j]);
+ }
+
+ /* Source locus */
+ rgb[0] = 1.0;
+ rgb[1] = 0.5;
+ rgb[2] = 0.5;
+ for (i = 0; i < npoints; i++) {
+ co cp;
+
+ for (j = 0; j < 3; j++)
+ v1[j] = *((double *)locus->t[table].fdata[i][ix[j]]);
+
+ /* Rotate and locus verticies the same as the src gamuts */
+ dopartialmap1(s, v1, v1);
+ if (i > 0 )
+ wrl->add_cone(wrl, v0, v1, rgb, 0.5);
+ icmAry2Ary(v0,v1);
+ }
+
+ /* Gamut mapped locus */
+ rgb[0] = 1.0;
+ rgb[1] = 1.0;
+ rgb[2] = 1.0;
+ for (i = 0; i < npoints; i++) {
+ co cp;
+
+ for (j = 0; j < 3; j++)
+ v1[j] = *((double *)locus->t[table].fdata[i][ix[j]]);
+
+ s->domap(s, v1, v1);
+ if (i > 0 )
+ wrl->add_cone(wrl, v0, v1, rgb, 0.5);
+ icmAry2Ary(v0,v1);
+ }
+ }
+
+ locus->del(locus);
+ locus = NULL;
+ }
+
+ /* Add any ring mapping diagnostics */
+ for (i = 0; ; i++) {
+ if (rings[i].type == 0)
+ break;
+
+ if (rings[i].type == 2)
+ continue;
+
+ if (rings[i].type == 1) {
+ double pconst;
+ double cpoint[3];
+ double mat[3][4]; /* translate to our plane */
+ double imat[3][4]; /* translate from our plane */
+ double s1[3], s0[3], t1[3];
+ int j;
+ double maxa, mina;
+ double maxb, minb;
+
+ if (arerings == 0) {
+ arerings = 1;
+ wrl->start_line_set(wrl, 1); /* Source ring */
+ wrl->start_line_set(wrl, 2); /* Destination ring */
+ }
+
+ if (icmNormalize3(rings[i].pnorm, rings[i].pnorm, 1.0))
+ error("Ring %d diagnostic plane normal failed",i);
+
+ pconst = -icmDot3(rings[i].ppoint, rings[i].pnorm);
+
+ /* Locate intersection of source neautral axis and plane */
+ if (icmVecPlaneIsect(cpoint, pconst, rings[i].pnorm, s_cs_wp, s_cs_bp))
+ error("Ring %d diagnostic center point intersection failed",i);
+
+ /* Compute the rotation and translation between */
+ /* a plane in ab and the plane we are using */
+ s0[0] = s0[1] = s0[2] = 0.0;
+ s1[0] = 1.0, s1[1] = s1[2] = 0.0;
+ t1[0] = cpoint[0] + rings[i].pnorm[0];
+ t1[1] = cpoint[1] + rings[i].pnorm[1];
+ t1[2] = cpoint[2] + rings[i].pnorm[2];
+ icmVecRotMat(mat, s1, s0, t1, cpoint);
+ icmVecRotMat(imat, t1, cpoint, s1, s0);
+
+ /* Do a min/max of a circle of vectors so as to */
+ /* establish an offset to the centroid for this slice */
+ maxa = maxb = -1e60;
+ mina = minb = 1e60;
+ for (j = 0; j < 20; j++) {
+ double ang = 2 * 3.1415926 * j/(20 - 1.0);
+ double vec[3], isect[3];
+ double pp[3];
+ co cp;
+ int k;
+
+ vec[0] = 0.0;
+ vec[1] = sin(ang);
+ vec[2] = cos(ang);
+ icmMul3By3x4(vec, mat, vec);
+
+ /* Intersect it with the source gamut */
+ if (si_gam->vector_isect(si_gam, vec, cpoint, isect,
+ NULL, NULL, NULL, NULL, NULL) == 0) {
+ continue;
+ }
+
+ /* Translate back to plane */
+ icmMul3By3x4(pp, imat, isect);
+
+ if (pp[1] > maxa)
+ maxa = pp[1];
+ if (pp[1] < mina)
+ mina = pp[1];
+ if (pp[2] > maxb)
+ maxb = pp[2];
+ if (pp[2] < minb)
+ minb = pp[2];
+ }
+ /* Move center to centroid of min/max box */
+ t1[0] = 0.0;
+ t1[1] = (maxa + mina) * 0.5;
+ t1[2] = (maxb + minb) * 0.5;
+ if (t1[1] < -200.0 || t1[1] > 200.0
+ || t1[2] < -200.0 || t1[2] > 200.0)
+ error("Failed to locate centroid of slice");
+ icmMul3By3x4(cpoint, mat, t1);
+
+//printf("~1 ring centroid point = %f %f %f\n", cpoint[0],cpoint[1],cpoint[2]);
+
+ /* Recompute the rotation and translation between */
+ /* a plane in ab and the plane we are using */
+ s0[0] = s0[1] = s0[2] = 0.0;
+ s1[0] = 1.0, s1[1] = s1[2] = 0.0;
+ t1[0] = cpoint[0] + rings[i].pnorm[0];
+ t1[1] = cpoint[1] + rings[i].pnorm[1];
+ t1[2] = cpoint[2] + rings[i].pnorm[2];
+ icmVecRotMat(mat, s1, s0, t1, cpoint);
+ icmVecRotMat(imat, t1, cpoint, s1, s0);
+
+//printf("~1 generating %d ring verts\n",rings[i].nverts);
+ /* Create a circle of vectors in the plane from the center */
+ /* point, to intersect with the source gamut surface. */
+ /* (Duplicate start and end vertex) */
+ for (j = 0; j <= rings[i].nverts; j++) {
+ double ang = 2 * 3.1415926 * j/((double) rings[i].nverts);
+ double vec[3], isect[3];
+ double pp[3];
+ co cp;
+ int k;
+
+ vec[0] = 0.0;
+ vec[1] = sin(ang);
+ vec[2] = cos(ang);
+ icmMul3By3x4(vec, mat, vec);
+
+ /* Intersect it with the source gamut */
+ if (si_gam->vector_isect(si_gam, vec, cpoint, isect,
+ NULL, NULL, NULL, NULL, NULL) == 0) {
+ warning("Ring %d vect %d diagnostic vector intersect failed",i,j);
+ continue;
+ }
+
+//printf("~1 vec %d = %f %f %f\n",j,isect[0],isect[1],isect[2]);
+
+ /* Scale them to the ratio */
+ for (k = 0; k < 3; k++)
+ vec[k] = isect[k] * rings[i].rad + (1.0 - rings[i].rad) * cpoint[k];
+
+//printf("~1 rad vec %d = %f %f %f\n",j,vec[0],vec[1],vec[2]);
+
+ /* Transform them into rotated and scaled destination space */
+ dopartialmap1(s, vec, vec);
+//printf("~1 trans vec %d = %f %f %f\n",j,vec[0],vec[1],vec[2]);
+
+ /* Add to plot */
+ wrl->add_col_vertex(wrl, 1, vec, rings[i].scol);
+//printf("~1 src vec %d = %f %f %f\n",j,vec[0],vec[1],vec[2]);
+
+ /* Gamut map and add to plot */
+ s->domap(s, vec, vec);
+//printf("~1 dst vec %d = %f %f %f\n",j,vec[0],vec[1],vec[2]);
+ wrl->add_col_vertex(wrl, 2, vec, rings[i].dcol);
+ }
+ wrl->make_last_vertex(wrl, 1); /* Source ring */
+ wrl->make_last_vertex(wrl, 2); /* Destination ring */
+ }
+ if (arerings) {
+ wrl->make_lines(wrl, 1, 1000000); /* Source ring */
+ wrl->make_lines(wrl, 2, 1000000); /* Destination ring */
+ }
+ }
+
+ wrl->del(wrl); /* Write and delete */
+ wrl = NULL;
+ }
+
+#ifdef PLOT_3DKNEES
+ /* Plot one graph per 3D gamut boundary mapping point */
+ for (j = 0; j < p3dk_ix; j++) {
+ double xx[XRES];
+ double yy[XRES];
+
+ printf("Vector %f %f %f -> %f %f %f\n", p3dk_locus[j].v0[0], p3dk_locus[j].v0[1], p3dk_locus[j].v0[2], p3dk_locus[j].v1[0], p3dk_locus[j].v1[1], p3dk_locus[j].v1[2]);
+
+ for (i = 0; i < XRES; i++) {
+ double v;
+ co cp; /* Conversion point */
+ v = (i/(double)(XRES-1.0));
+ cp.p[0] = p3dk_locus[j].v0[0] + v * (p3dk_locus[j].v1[0] - p3dk_locus[j].v0[0]);
+ cp.p[1] = p3dk_locus[j].v0[1] + v * (p3dk_locus[j].v1[1] - p3dk_locus[j].v0[1]);
+ cp.p[2] = p3dk_locus[j].v0[2] + v * (p3dk_locus[j].v1[2] - p3dk_locus[j].v0[2]);
+ xx[i] = sqrt(cp.p[1] * cp.p[1] + cp.p[2] * cp.p[2]);
+ s->map->interp(s->map, &cp);
+ yy[i] = sqrt(cp.v[1] * cp.v[1] + cp.v[2] * cp.v[2]);
+ }
+ do_plot(xx,yy,NULL,NULL,XRES);
+ }
+ free(p3dk_locus);
+#endif /* PLOT_3DKNEES */
+
+
+ free(gpnts);
+ free_nearsmth(nsm, nnsm);
+
+ } else if (diagname != NULL && verb) {
+ printf("Warning: Won't create '%s' because there is no 3D gamut mapping\n",diagname);
+ }
+
+#ifdef PLOT_GAMUTS
+ scl_gam->write_vrml(scl_gam, "src.wrl", 1, 0);
+ sil_gam->write_vrml(sil_gam, "img.wrl", 1, 0);
+ d_gam->write_vrml(d_gam, "dst.wrl", 1, 0);
+ sc_gam->write_trans_vrml(sc_gam, "gmsrc.wrl", 1, 0, map_trans, s);
+#endif
+
+ if (sil_gam != scl_gam)
+ sil_gam->del(sil_gam);
+ scl_gam->del(scl_gam);
+
+ return s;
+}
+
+#ifdef PLOT_GAMUTS
+
+/* Debug */
+static void map_trans(void *cntx, double out[3], double in[3]) {
+ gammap *map = (gammap *)cntx;
+
+ map->domap(map, out, in);
+}
+
+#endif
+
+/* Object methods */
+static void del_gammap(
+gammap *s
+) {
+ if (s->grey != NULL)
+ s->grey->del(s->grey);
+ if (s->igrey != NULL)
+ s->igrey->del(s->igrey);
+ if (s->map != NULL)
+ s->map->del(s->map);
+
+ free(s);
+}
+
+/* Apply the gamut mapping to the given color value */
+static void domap(
+gammap *s,
+double *out,
+double *in
+) {
+ double rin[3];
+ co cp;
+
+ if (s->dbg) printf("domap: got input %f %f %f\n",in[0],in[1],in[2]);
+ icmMul3By3x4(rin, s->grot, in); /* Rotate */
+
+ if (s->dbg) printf("domap: after rotate %f %f %f\n",rin[0],rin[1],rin[2]);
+ cp.p[0] = rin[0];
+ s->grey->interp(s->grey, &cp); /* L map */
+
+ if (s->dbg) printf("domap: after L map %f %f %f\n",cp.v[0],rin[1],rin[2]);
+
+ /* If there is a 3D->3D mapping */
+ if (s->map != NULL) {
+ int e;
+
+ /* Clip out of range a, b proportionately */
+ if (rin[1] < s->imin[1] || rin[1] > s->imax[1]
+ || rin[2] < s->imin[2] || rin[2] > s->imax[2]) {
+ double as = 1.0, bs = 1.0;
+ if (rin[1] < s->imin[1])
+ as = s->imin[1]/rin[1];
+ else if (rin[1] > s->imax[1])
+ as = s->imax[1]/rin[1];
+ if (rin[2] < s->imin[2])
+ bs = s->imin[2]/rin[2];
+ else if (rin[2] > s->imax[2])
+ bs = s->imax[2]/rin[2];
+ if (bs < as)
+ as = bs;
+ rin[1] *= as;
+ rin[2] *= as;
+ }
+
+ cp.p[0] = cp.v[0]; /* 3D map */
+ cp.p[1] = rin[1];
+ cp.p[2] = rin[2];
+ s->map->interp(s->map, &cp);
+
+ for (e = 0; e < s->map->fdi; e++)
+ out[e] = cp.v[e];
+
+ if (s->dbg) printf("domap: after 3D map %s\n\n",icmPdv(s->map->fdi, out));
+ } else {
+ out[0] = cp.v[0];
+ out[1] = rin[1];
+ out[2] = rin[2];
+ }
+}
+
+/* Apply the matrix and grey mapping to the given color value */
+static void dopartialmap1(
+gammap *s,
+double *out,
+double *in
+) {
+ double rin[3];
+ co cp;
+
+ icmMul3By3x4(rin, s->grot, in); /* Rotate */
+ cp.p[0] = rin[0];
+ s->grey->interp(s->grey, &cp); /* L map */
+ out[0] = cp.v[0];
+ out[1] = rin[1];
+ out[2] = rin[2];
+}
+
+/* Apply just the rspl mapping to the given color value */
+/* (ie. to a color already rotated and L mapped) */
+static void dopartialmap2(
+gammap *s,
+double *out,
+double *in
+) {
+ co cp;
+
+ /* If there is a 3D->3D mapping */
+ if (s->map != NULL) {
+ int e;
+
+ icmCpy3(cp.p, in);
+
+ /* Clip out of range a, b proportionately */
+ if (cp.p[1] < s->imin[1] || cp.p[1] > s->imax[1]
+ || cp.p[2] < s->imin[2] || cp.p[2] > s->imax[2]) {
+ double as = 1.0, bs = 1.0;
+ if (cp.p[1] < s->imin[1])
+ as = s->imin[1]/cp.p[1];
+ else if (cp.p[1] > s->imax[1])
+ as = s->imax[1]/cp.p[1];
+ if (cp.p[2] < s->imin[2])
+ bs = s->imin[2]/cp.p[2];
+ else if (cp.p[2] > s->imax[2])
+ bs = s->imax[2]/cp.p[2];
+ if (bs < as)
+ as = bs;
+ cp.p[1] *= as;
+ cp.p[2] *= as;
+ }
+
+ s->map->interp(s->map, &cp);
+
+ icmCpy3(out, cp.v);
+ } else {
+ icmCpy3(out, in);
+ }
+}
+
+/* Function to pass to rspl to invert grey curve */
+static void inv_grey_func(
+ void *cntx,
+ double *out,
+ double *in
+) {
+ rspl *fwd = (rspl *)cntx;
+ int nsoln; /* Number of solutions found */
+ co pp[2]; /* Room for all the solutions found */
+
+ pp[0].p[0] =
+ pp[0].v[0] = in[0];
+
+ nsoln = fwd->rev_interp(
+ fwd,
+ RSPL_NEARCLIP, /* Clip to nearest (faster than vector) */
+ 2, /* Maximum number of solutions allowed for */
+ NULL, /* No auxiliary input targets */
+ NULL, /* Clip vector direction and length */
+ pp); /* Input and output values */
+
+ nsoln &= RSPL_NOSOLNS; /* Get number of solutions */
+
+ if (nsoln != 1)
+ error("gammap: Unexpected failure to find reverse solution for grey axis lookup");
+
+ out[0] = pp[0].p[0];
+}
+
+/* Function to pass to rspl to alter output values, */
+/* to enhance the saturation. */
+static void
+adjust_sat_func(
+ void *pp, /* adjustsat structure */
+ double *out, /* output value to be adjusted */
+ double *in /* corresponding input value */
+) {
+ adjustsat *p = (adjustsat *)pp;
+ double cp[3]; /* Center point */
+ double rr, t1[3], p1;
+ double t2[3], p2;
+
+ /* Locate center point on the white/black axis corresponding to this color */
+ cp[0] = out[0];
+ rr = (out[0] - p->bp[0])/(p->wp[0] - p->bp[0]); /* Relative location on the white/black axis */
+ cp[1] = p->bp[1] + rr * (p->wp[1] - p->bp[1]);
+ cp[2] = p->bp[2] + rr * (p->wp[2] - p->bp[2]);
+
+ /* Locate the point on the destination gamut surface in the direction */
+ /* from the center point to the point being processed. */
+ if (p->dst->vector_isect(p->dst, cp, out, t2, t1, &p2, &p1, NULL, NULL) != 0) {
+
+ if (p1 > 1.0) { /* If this point is within gamut */
+ double ep1, bf;
+
+//printf("\n");
+//printf("~1 cp %f %f %f input %f %f %f\n",cp[0],cp[1],cp[2], out[0], out[1], out[2]);
+//printf("~1 min %f %f %f mint %f\n",t2[0],t2[1],t2[2],p2);
+//printf("~1 max %f %f %f maxt %f\n",t1[0],t1[1],t1[2],p1);
+
+ p1 = 1.0/p1; /* Position of out from cp to t1 */
+
+#ifdef NEVER
+ /* Enhanced parameter value */
+ ep1 = (p1 + p->satenh * p1)/(1.0 + p->satenh * p1);
+ /* Make blend between linear p1 and enhanced p1, */
+ /* to reduce effects on near neutrals. */
+ p1 = (1.0 - p1) * p1 + p1 * ep1;
+#else
+ /* Compute Enhanced p1 */
+ ep1 = (p1 + p->satenh * p1)/(1.0 + p->satenh * p1);
+
+ /* Make blend factor between linear p1 and enhanced p1, */
+ /* to reduce effects on near neutrals. */
+ {
+ double pp = 4.0; /* Sets where the 50% transition is */
+ double g = 2.0; /* Sets rate of transition */
+ double sec, vv = p1;
+
+ vv = vv/(pp - pp * vv + 1.0);
+
+ vv *= 2.0;
+ sec = floor(vv);
+ if (((int)sec) & 1)
+ g = -g; /* Alternate action in each section */
+ vv -= sec;
+ if (g >= 0.0) {
+ vv = vv/(g - g * vv + 1.0);
+ } else {
+ vv = (vv - g * vv)/(1.0 - g * vv);
+ }
+ vv += sec;
+ vv *= 0.5;
+
+ bf = (vv + pp * vv)/(1.0 + pp * vv);
+ }
+ /* Do the blend */
+ p1 = (1.0 - bf) * p1 + bf * ep1;
+#endif
+ /* Compute enhanced values position */
+ out[0] = cp[0] + (t1[0] - cp[0]) * p1;
+ out[1] = cp[1] + (t1[1] - cp[1]) * p1;
+ out[2] = cp[2] + (t1[2] - cp[2]) * p1;
+//printf("~1 output %f %f %f, param %f\n",out[0],out[1],out[2],p1);
+ }
+ }
+}
+
+/* Function to pass to rspl to re-set output values, */
+/* to adjust the white and black points */
+static void
+adjust_wb_func(
+ void *pp, /* adjustwb structure */
+ double *out, /* output value to be adjusted */
+ double *in /* corresponding input value */
+) {
+ adjustwb *p = (adjustwb *)pp;
+
+ /* Do a linear mapping from swp -> dwp and sbp -> dbp, */
+ /* to compute the adjusted value. */
+ icmMul3By3x4(out, p->mat, out);
+}
+
+
+/* Create a new gamut that the the given gamut transformed by the */
+/* gamut mappings rotation and grey curve mapping. Return NULL on error. */
+static gamut *parttransgamut(gammap *s, gamut *src) {
+ gamut *dst;
+ double cusps[6][3];
+ double wp[3], bp[3], kp[3];
+ double p[3];
+ int i;
+
+ if ((dst = new_gamut(src->getsres(src), src->getisjab(src), src->getisrast(src))) == NULL)
+ return NULL;
+
+ dst->setnofilt(dst);
+
+ /* Translate all the surface nodes */
+ for (i = 0;;) {
+ if ((i = src->getrawvert(src, p, i)) < 0)
+ break;
+
+ dopartialmap1(s, p, p);
+ dst->expand(dst, p);
+ }
+ /* Translate cusps */
+ if (src->getcusps(src, cusps) == 0) {
+ dst->setcusps(dst, 0, NULL);
+ for (i = 0; i < 6; i++) {
+ dopartialmap1(s, p, cusps[i]);
+ dst->setcusps(dst, 1, p);
+ }
+ dst->setcusps(dst, 2, NULL);
+ }
+ /* Translate white and black points */
+ if (src->getwb(src, wp, bp, kp, NULL, NULL, NULL) == 0) {
+ dopartialmap1(s, wp, wp);
+ dopartialmap1(s, bp, bp);
+ dopartialmap1(s, kp, kp);
+ dst->setwb(dst, wp, bp, kp);
+ }
+
+ return dst;
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/gamut/gammap.h b/gamut/gammap.h
new file mode 100644
index 0000000..b59e821
--- /dev/null
+++ b/gamut/gammap.h
@@ -0,0 +1,68 @@
+#ifndef GAMMAP_H
+#define GAMMAP_H
+
+/*
+ * Argyll Gamut Mapping Library
+ *
+ * Author: Graeme W. Gill
+ * Date: 1/10/2000
+ * Version: 2.00
+ *
+ * Copyright 2000 - 2006 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.
+ */
+
+
+/* Gamut mapping object */
+struct _gammap {
+
+/* Private: */
+ int dbg; /* NZ to turn on debug messages */
+ /* neutral axis alignment transform applied to source: */
+ double grot[3][4]; /* Incoming grey axis rotation matrix */
+ double igrot[3][4]; /* Inverse of above */
+ rspl *grey; /* Forward L map */
+ rspl *igrey; /* Inverse L map */
+ /* Source to destination gamut map applied */
+ /* to transformed source: */
+ rspl *map; /* Rotated, L mapped Lab -> Lab gamut map */
+ double imin[3], imax[3]; /* Input range limits of map */
+
+/* Public: */
+
+ /* Methods */
+ void (*del)(struct _gammap *s); /* Free ourselves */
+ void (*domap)(struct _gammap *s, double *out, double *in); /* Do the mapping */
+
+}; typedef struct _gammap gammap;
+
+/* Method of black point adaptation */
+typedef enum {
+ gmm_BPadpt = 0, /* Adapt source black point to destination */
+ gmm_noBPadpt = 1, /* Don't adapt black point to destination */
+ gmm_bendBP = 2, /* Don't adapt black point, bend it to dest. at end */
+ gmm_clipBP = 3 /* Don't adapt black point, clip it to dest. at end */
+} gmm_BPmap;
+
+/* Creator */
+gammap *new_gammap(
+ int verb, /* Verbose flag */
+ gamut *sc_gam, /* Source colorspace gamut */
+ gamut *s_gam, /* Source image gamut (NULL if none) */
+ gamut *d_gam, /* Destination colorspace gamut */
+ icxGMappingIntent *gmi, /* Gamut mapping specification */
+ int src_kbp, /* Use K only black point as src gamut black point */
+ int dst_kbp, /* Use K only black point as dst gamut black point */
+ int dst_cmymap, /* masks C = 1, M = 2, Y = 4 to force 100% cusp map */
+ int rel_oride, /* 0 = normal, 1 = override min relative, 2 = max relative */
+ int mapres, /* Gamut map resolution, typically 9 - 33 */
+ double *mn, /* If not NULL, set minimum mapping input range */
+ double *mx, /* for rspl grid */
+ char *diagname /* If non-NULL, write a gamut mapping diagnostic WRL */
+);
+
+
+#endif /* GAMMAP_H */
diff --git a/gamut/gammap.txt b/gamut/gammap.txt
new file mode 100644
index 0000000..f751248
--- /dev/null
+++ b/gamut/gammap.txt
@@ -0,0 +1,259 @@
+
+Author: Graeme W. Gill
+Date: 2000/10/28
+Updated: 2006/1/17
+
+Discussion of the gamut mapping algorithm used in Argyll:
+
+
+ Jam Morovic provides an extensive summary of previous Gamut Mapping Algoriths
+ (GMA) in his thesis "To Develop a Universal Gamut Mapping Algorithm".
+
+ Mark Fairchild and Gustav Braun also discuss some interesting aspects of
+ gamut mapping in "General-Purpose Gamut-Mapping Algorithms: Evaluation
+ of Contrast-Preserving Rescaling Functions for Color Gamut Mapping".
+
+ One thing that is striking in reading the current Gamut Mapping
+ literature, is the schism between gamut mapping, and gammut clipping.
+
+ A number of studies have indicated the satisfactory results from performing
+ gamut clipping by mapping out of gamut points to the closest point
+ within gamut, using a minumim delta E criteria. Ideally this would be
+ in a perceptually uniform space, and in practical spaces (ie L*a*b* space),
+ it appears a mapping that weightes luminance errors twice as much as
+ hue and saturation errors is the most satisfactory
+ (ie. Katoh & Ito 1996, Ebner & Fairchild 1997).
+ This approach makes perfect sense if the perceptually linear
+ delta E spaces is working as its should :- providing a means of
+ computing the minimal perceptual error between two colors.
+
+ Almost all the gamut mapping algorithms on the other hand, take a
+ completely different approach. They are dominated by algorithms with
+ appeal for their mathematical and algorithmic simplicity, rather than
+ any objective sense. Clipping along a line of constant hue, in a
+ particular direction, with some sort of compression along line
+ length curve seems to be a favourite.
+ My own experience with such an approach used for gamut clipping
+ shows up the logical flaws in this approach. In printed media, for
+ instance, the yellow dye tends to have a very sharp cusp in (say) Lab
+ space. When mapping along a line towards the neutral axis, it is almost
+ impossible to come up with a choice of line direction that
+ doesn't severly distort the clipped color. It is often extremely
+ de-saturated at the point the line hits the gamut boundary. Line
+ directions that take some account of the equivalent yellow cusp
+ in the target gamut improve the situation, but it seems highly
+ illogical to choosing an algorithm that gives a result with such
+ high delta E's, measured in any colorspace !
+
+ My conclusion is this: If we are working in a good perceptually
+ uniform space (ie. L*a*b*, Luv or CIECAM02), then we want to minimise
+ the delta E of the gamut mapped points at all times (this is the point
+ of using a perceptually uniform space !). This means that points
+ on the source gamut surface, should map to the target surface in
+ a very similar way to which they would be mapped using gamut clipping,
+ ie. minimum delta E. The distinction between gamut mapping and clipping
+ only becomes evident then, when we consider points that fall within
+ both gamuts. For pure gamut clipping, these points should not be
+ modified. For gamut mapping they should be modified in a way that
+ trades off increased absolute delta E, for reduced delta E relative
+ to surrounding colors.
+
+ [ Saturation intent brings some factors other than minimum
+ delta E into the picture, but minumum delta E is a good
+ starting point. ]
+
+ Gamut clipping should not be something separate to gamut mapping,
+ but should just be one extreme in a continuum of gamut mapping
+ choices.
+
+ A consideration revealed by Jam Morovic's work, is that it may be
+ desirable to treat colors on and close to the neutral axis a little
+ differently that those saturated colors near the gamut surface.
+ It seems desirable to align and compress the neutral axis to
+ give a good gray-scale mapping, as well as preserving the
+ relative colorimetry of low saturation colors.
+
+ Mark fairchild's work also indicates the desirablility of
+ trying to maintain the relative contrast of image data after
+ compression. This can be achieved by using a sigmoidal
+ mapping during compression (often called a soft knee compression
+ characteristic), rather than linear compression. Gamut
+ clipping can be considered to be an extreme example of
+ knee compression, where the knee is "hard".
+
+ A final consideration is how the various user intents are going
+ to be accomodated. One of the nice features of a consistent
+ clipping/compression approach is that many of the distinctions
+ between different intents seems to disappear.
+
+ Description of algorithms used:
+
+ The algorithm chosen here is (as far as I am aware) a new one,
+ that tries to combine all the considerations made above.
+
+ The gamut mapping is divided into two parts, mapping the luminance
+ axis orthogonally to other considerations, and then dealling
+ with any remaining gamut compression or expansion.
+
+ The basic transform mechanism is broken down into three part;
+ Aligning the neutral axes, mapping the luminence coordinate,
+ performing an overall 3D -> 3D mapping to compress and/or
+ expand the gamut surface. The rotation uses a matrix, the 1D -> 1D
+ luminence mapping uses a RSPL regular spline that is available
+ in Argyll, and the 3D -> 3D mapping also uses a RSPL.
+ By controlling the number, extent and strength of the sample
+ point mapping used to create the RSPL, it is possible to arrive
+ at a smooth mapping that has (controllable) areas of influence
+ between different regions, and can achieve sigmoid like "soft"
+ clipping curves.
+
+
+ Aligning the neutral axes.
+
+ The first step is to align the neutral axes. If the
+ colorspace used is an appearance space (Such as CIECAM02 Jab),
+ then the white points will already be close together, but may not
+ exactly cooincide. The black points will also probably differ.
+ There are a number of options for dealing with this. One
+ option would be to assume that people adapt to black points
+ the same way they adapt to the white points of a colorspace
+ or image. Research and experiemnce indicates that this
+ may not be the case though. If we assume that people
+ do not in general adapt to the black points of a colorspace,
+ then a consequence is that it is difficult to fully exploit
+ the full dynamic range of the colorspace, since an
+ source black that is misaligned with the destination black
+ will probably not be able to achieve as low a J value,
+ and this can noticably affect the percieved quality of
+ an image.
+
+ The compromise deemed the best in Argyll, is to
+ assume that people do not adapt to black points,
+ and that therefore only the white points should be
+ aligned by rotating the source space around 0,0,0
+ to line them up. The minimum J value on the other hand,
+ is mapped as if the black points were being fully
+ adapted to, and at the point that the source
+ neutral axis would leave the destination gamut,
+ it is clipped to the destination.
+
+ This gives a neutral axis in the destination that looks
+ the same as that of the source, while fully exploiting the
+ dynamic range of the destination colorspace. Since
+ the departure of the mapped neutral axis from neutral
+ only occurs in very dark colors, its deviation is not
+ usually visible.
+
+ [ The code allows for 4 possible black point/neutral
+ axis mapping algorithm, set by the value of bph:
+ Adapt source black point to destination, giving full black range.
+ Don't adapt black point to destination, giving compromised black range.
+ Don't adapt black point, bend it to dest. at end, giving full black range.
+ Don't adapt black point, clip it to dest. at end, giving full black range.
+ ]
+
+ Note that the black point mapping is performed by manipulating
+ J independently of the color components (a & b). In adapting
+ color from one space to another, this proves to be the correct
+ approach, maintaining the appearance of an image when
+ transformed to different device colorspaces.
+ This should not be confused with the situation where
+ optical flare or haze is being removed or simulated,
+ in which case the black point mapping should be done in a
+ linear light colorspace (such as XYZ), and will have an
+ effect simulaniously on lightness and saturation.
+
+
+ Mapping the luminence range.
+
+ The luminence mapping is acheived by created a small number of
+ mapping points, and then created a detailed RSPL (Regular Spline)
+ based on the points. The end points (which are heavily weighted
+ to ensure the overall range mapping is useful) are created
+ simply from the known J values of the source and destination
+ white and black points (taking into consuideration what
+ J values will result from possible gamut boundary clipping).
+
+ Middle points are introduced to either give a linear mapping (in J),
+ or to introduce a slight range compression aimed at preserving
+ the contrast ratio of the image.
+
+
+ Maping 3D gamut points.
+
+ The mapping points created for determinining the 3D compression or
+ expansioin are created from the source gamut surface verticies
+ that lie outside the destinatio gamut. The raw mapping for the
+ outside of the gamut is created by itterative optimisation,
+ that strikes a weighted balance between three different
+ factors: Mapping to the closest point on the destination surface,
+ Mapping in a way that is smoothly consistent with neighbour
+ mapping rays, and Mapping radially to the center of the colorspace.
+ By manipulating the weighting factors (held in a gammapweights
+ structure), it is possible to control to some degree the
+ nature of the gamut mapping.
+
+ Some compensation is applied to the vectors to try and counteract
+ the effects of the vector and (subsequent) RSPL smoothing.
+ This smoothing may cause the mapping to end up outside the
+ destination gamut, so we try and make sure that such smoothing
+ effects over rather than under-compress.
+
+ In addition to the suface mapping points, the 1D neutral axis
+ mapping is maintained by introducing a string of points
+ down the 3D neutral axis, to ensure that the 3D surface mappings
+ do not alter it.
+
+ Closer to the surface, optional "knee" mapping points
+ are also added, in an attempt to cause the inner
+ 3D mapping to be more linear, with the compression (or
+ expansion) being concentrated near the surface of the
+ gamuts ("Sigma" compression).
+
+Limitations and future challenges:
+
+ After re-working the gamut mapping in December 2005/January 2006,
+ the following challenges remain:
+
+ 1) For saturation mapping in particular, the handling of hue
+ mapping using the "nearest" weighting isn't very flexible.
+ It's not possible to map RGB Cyan to CMYK Cyan without
+ introducing unacceptable side effects.
+
+ To solve this, I would have to add some extra mapping controls
+ and gamut information. For each gamut the 6 major "cusps" would
+ have to be identified (either directly from the colorant
+ combinations, or indrectly by locating the point on the gamut
+ surface with the largest C (?) in each hextant), and
+ then a means of warping the hue angle (and Luminence ??)
+ in each hextant.
+
+ The gammapweights structure would be expanded to add a weight
+ as to how much the source hue cusps would be distorted.
+
+ [Done by Feb '06]
+
+
+ 2) There is not much flexibility in what the gammapweights
+ weightings can achive. Some controls simply don't work
+ very well. Often increases in saturation in one area (at
+ the transitions between major colors) are bought at the
+ expense of the middle of the major colors becomming
+ very "flat" and desaturated. This is generally when we
+ are in a region in which the source gamut is within the
+ destination gamut.
+
+ 3) Geting smoothness and saturation is very difficult
+ for some reason. The "relative error" weights do spread
+ things out, but at the cost of obvious banding (why ? -
+ possible answer - reflects the roughness of the gamut surface.
+ Solution is to use better algorithm for gamut surface extraction ?? ).
+ Increasing the 3D RSPL smoothness seems to have no
+ effect (why ?). Reducing absolute chroma weight improves
+ smoothness, but at the cost of reduced saturation and increased
+ lightness.
+
+
+
+
+
diff --git a/gamut/gamut.c b/gamut/gamut.c
new file mode 100644
index 0000000..052b8d8
--- /dev/null
+++ b/gamut/gamut.c
@@ -0,0 +1,7136 @@
+
+/*
+ * gamut
+ *
+ * Gamut support routines.
+ *
+ * Author: Graeme W. Gill
+ * Date: 9/3/2000
+ * Version: 1.00
+ *
+ * Copyright 2000 - 2006 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.
+ */
+
+/*
+ The gamut boundary is comuted using a variation of
+ Jan Morovic's Segment Maximum approach. The variations
+ are:
+
+ The segments are filtered with an adaptive depth structure,
+ so that approximately the same detail is kept on the gamut
+ surface. Multiple direction vectors at each point are retained.
+ The resultant points are used to create the overal convex
+ jull, but in an adaptive, non-linearly scaled radial space,
+ that allows for convexity in the PCS result.
+*/
+
+/* TTBD:
+
+*/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <fcntl.h>
+#include <string.h>
+#include <math.h>
+#include <time.h>
+#include "icc.h"
+#include "numlib.h"
+#include "vrml.h"
+#include "gamut.h"
+#include "cgats.h"
+#include "sort.h" /* ../h sort macro */
+#include "counters.h" /* ../h counter macros */
+#include "xlist.h" /* ../h expandable list macros */
+
+#define COLORED_VRML
+
+#define DO_TWOPASS /* Second pass with adjustment based on first pass */
+
+#define FAKE_SEED_SIZE 0.1 /* Usually 0.1 */
+#define TRIANG_TOL 1e-10 /* Triangulation tollerance, usually 1e-10 */
+
+#define NORM_LOG_POW 0.25 /* Normal, colorspace lopow value */
+#define RAST_LOG_POW 0.05 /* Raster lopow value */
+
+#undef TEST_CONVEX_HULL /* Use pure convex hull, not log hull */
+
+#undef DEBUG_TRIANG /* Enable detailed triangulation debugging */
+#undef DEBUG_TRIANG_VRML /* Create debug.wrl for each step of triangulation */
+ /* (Only on second pass if #define DO_TWOPASS) */
+#undef DEBUG_TRIANG_VRML_STEP /* Wait for return after each step */
+
+#undef DEBUG_SPLIT_VRML /* Create debug.wrl for each step of triangle plane split */
+
+#undef TEST_LOOKUP
+#undef TEST_NEAREST
+#undef TEST_NOSORT /* Turn off sorted insersion of verticies */
+
+#undef SHOW_BUCKETS /* Show vertex buckets as surface */
+#undef SHOW_SPHERE /* Show surface on sphere */
+#undef SHOW_HULL_PNTS /* Show log() length convex hull points */
+
+#undef ASSERTS /* Do internal checking */
+
+#undef INTERSECT_DEBUG /* Turn on compute_vector_isect debugging, inc isect.wrl plot */
+#undef INTERSECT_VERIFY /* Verify compute_vector_isect against brute force search */
+
+/* These routines support:
+
+ representing the 3D gamut boundary of a device or image as
+ radial surface height, described by a triangular poligon hull.
+
+ Interogate the surface to find the point lying on the hull
+ in the same radial direction as the query point.
+
+ Interogate the surface to find the point lying on the hull
+ that is the closest to the query point.
+
+ Save the gamut as a vrml format viewable file.
+ Save the gamut as a CGATS format .gam file.
+
+ */
+
+/* TTBD:
+ *
+ * Would be nice to take the exact colorspace specification
+ * (ie. Lab vs. Jab + viewing conditions), and store them
+ * in the .gam file, so that a warning can be issued if
+ * the gamut colorspace is a mismatch in icclink, or to be able
+ * to translate the verticies into the correct colorespace.
+ *
+ * Add interface to transform all the nodes, while
+ * keeping structure (For use within gamut map, or
+ * transforming existing gamut from Lab to Jab etc.)
+ *
+ * Add inteface to fetch the triangle information ?
+ *
+ * Need to cleanup error handling. We just exit() at the moment.
+ *
+ * Replace BSP tree optmisation with ball tree, to speedup
+ * radial, nearest, and vector search ?
+ *
+ * The log surface stuff is a compromise, that ends up with
+ * some dings and nicks, and a not fully detailed/smooth surface.
+ * The fundamental limitation is the use of the Delaunay triangulation
+ * criteria, and the triangulation algorithm dependence on it for consistency.
+ * Want to switch to triangulation algorithm that doesn't
+ * depend on this, and can triangulate concave objects,
+ * so that something like alpha-shapes criteria can be
+ * used to filter out non surface points. Inserting verticies
+ * from largest radius to smallest seems to do the right thing
+ * with cusp ridges, and this property needs to be retained.
+ *
+ */
+
+
+#ifndef M_PI
+#define M_PI (3.1415926535897932384626433832795)
+#endif
+
+static void triangulate(gamut *s);
+static void del_gamut(gamut *s);
+static gvert *expand_gamut(gamut *s, double in[3]);
+static double getsres(gamut *s);
+static int getisjab(gamut *s);
+static int getisrast(gamut *s);
+static void setnofilt(gamut *s);
+static void getcent(gamut *s, double *cent);
+static void getrange(gamut *s, double *min, double *max);
+static int compatible(gamut *s, gamut *t);
+static int nrawverts(gamut *s);
+static int getrawvert(gamut *s, double pos[3], int ix);
+static int nraw0verts(gamut *s);
+static int getraw0vert(gamut *s, double pos[3], int ix);
+static int nverts(gamut *s);
+static int getvert(gamut *s, double *rad, double pos[3], int ix);
+static int nssverts(gamut *s, double xvra);
+static int getssvert(gamut *s, double *rad, double pos[3], double norm[3], int ix);
+static void startnexttri(gamut *s);
+static int getnexttri(gamut *s, int v[3]);
+static double volume(gamut *s);
+static int write_trans_vrml(gamut *s, char *filename, int doaxes, int docusps,
+ void (*transform)(void *cntx, double out[3], double in[3]), void *cntx);
+static int write_vrml(gamut *s, char *filename, int doaxes, int docusps);
+static int write_gam(gamut *s, char *filename);
+static int read_gam(gamut *s, char *filename);
+static double radial(gamut *s, double out[3], double in[3]);
+static double nradial(gamut *s, double out[3], double in[3]);
+static void nearest(gamut *s, double out[3], double in[3]);
+static void nearest_tri(gamut *s, double out[3], double in[3], gtri **ctri);
+static void setwb(gamut *s, double *wp, double *bp, double *kp);
+static int getwb(gamut *s, double *cswp, double *csbp, double *cskp, double *gawp, double *gabp, double *gakp);
+static void setcusps(gamut *s, int flag, double in[3]);
+static int getcusps(gamut *s, double cusps[6][3]);
+static int compute_vector_isect(gamut *s, double *p1, double *p2, double *min, double *max,
+ double *mint, double *maxt, gtri **mntri, gtri **mxtri);
+static int compute_vector_isectns(gamut *s, double *p1, double *p2, gispnt *lp, int ll);
+static double log_scale(gamut *s, double ss);
+static int intersect(gamut *s, gamut *s1, gamut *s2);
+static int compdstgamut(gamut *s, gamut *img, gamut *src, gamut *dst, int docomp, int doexp,
+ gamut *nedst, void (*cvect)(void *cntx, double *vec, double *pos), void *cntx);
+static int vect_intersect(gamut *s, double *rvp, double *ip, double *p1, double *p2, gtri *t);
+
+/* in isecvol.c: */
+extern double isect_volume(gamut *s1, gamut *s2);
+
+/* ------------------------------------ */
+
+/* Generic hue directions in degrees for Lab and Jab */
+/* Must be in increasing order */
+double gam_hues[2][7] = {
+ {
+ /* Lab */
+ 36.0, /* Red */
+ 101.0, /* Yellow */
+ 149.0, /* Green */
+ 225.0, /* Cyan */
+ 300.0, /* Blue */
+ 337.0, /* Magenta */
+ 36.0 + 360.0 /* Red */
+ },
+ {
+ /* Jab */
+ 28.0, /* Red */
+ 101.0, /* Yellow */
+ 148.0, /* Green */
+ 211.0, /* Cyan */
+// 269.0, /* Blue */
+ 250.0, /* Blue */
+ 346.0, /* Magenta */
+ 28.0 + 360.0 /* Red */
+ }
+};
+
+
+/* ------------------------------------ */
+static
+gvert *new_gvert(
+gamut *s,
+gquad *p, /* Parent quad (may be NULL) */
+int i, /* Intended node in quad */
+int f, /* Flag value to be OR'ed */
+double pp[3], /* Point in xyz rectangular coordinates, absolute */
+double rr[3], /* Radial coordinates */
+double lrr0, /* log scaled rr[0] */
+double sp[3], /* Point mapped to surface of unit sphere, relative to center */
+double ch[3] /* Point mapped for convex hull testing, relative to center */
+) {
+ gvert *v;
+
+ if (s->doingfake == 0 && s->ul != NULL) { /* There is an unused one available */
+ v = s->ul;
+ s->ul = v->ul;
+
+ } else { /* Allocate a new one */
+
+ if (s->nv >= s->na) { /* We need a new slot in the list */
+ if (s->na == 0) {
+ s->na = 5;
+ if ((s->verts = (gvert **)malloc(s->na * sizeof(gvert *))) == NULL) {
+ fprintf(stderr,"gamut: malloc failed on %d gvert pointer\n",s->na);
+ exit (-1);
+ }
+ } else {
+ s->na *= 2;
+ if ((s->verts = (gvert **)realloc(s->verts, s->na * sizeof(gvert *))) == NULL) {
+ fprintf(stderr,"gamut: realloc failed on %d gvert pointer\n",s->na);
+ exit (-1);
+ }
+ }
+ }
+
+ if ((v = (gvert *)calloc(1, sizeof(gvert))) == NULL) {
+ fprintf(stderr,"gamut: malloc failed on gvert object\n");
+ exit (-1);
+ }
+ s->verts[s->nv] = v;
+ v->n = s->nv++;
+ }
+ v->tag = 1;
+
+ if (p != NULL) {
+ v->w = 0.5 * p->w;
+ v->h = 0.5 * p->h;
+
+ v->hc = p->hc;
+ if (i & 1)
+ v->hc += 0.5 * v->w;
+ else
+ v->hc -= 0.5 * v->w;
+
+ v->vc = p->vc;
+ if (i & 2)
+ v->vc += 0.5 * v->h;
+ else
+ v->vc -= 0.5 * v->h;
+ } else {
+ v->w = 0.0;
+ v->h = 0.0;
+ v->hc = 0.0;
+ v->vc = 0.0;
+ }
+
+ v->f = GVERT_NONE | f;
+ v->ul = NULL;
+ v->rc = 1;
+
+ v->p[0] = pp[0];
+ v->p[1] = pp[1];
+ v->p[2] = pp[2];
+ v->r[0] = rr[0];
+ v->r[1] = rr[1];
+ v->r[2] = rr[2];
+ v->lr0 = lrr0;
+ v->sp[0] = sp[0];
+ v->sp[1] = sp[1];
+ v->sp[2] = sp[2];
+ v->ch[0] = ch[0];
+ v->ch[1] = ch[1];
+ v->ch[2] = ch[2];
+
+ return v;
+}
+
+/* Set the size of gvert angular segment */
+static
+void set_gvert(
+gvert *v, /* gvert to set */
+gquad *p, /* Parent quad */
+int i /* Intended node in quad */
+) {
+ v->w = 0.5 * p->w;
+ v->h = 0.5 * p->h;
+
+ v->hc = p->hc;
+ if (i & 1)
+ v->hc += 0.5 * v->w;
+ else
+ v->hc -= 0.5 * v->w;
+
+ v->vc = p->vc;
+ if (i & 2)
+ v->vc += 0.5 * v->h;
+ else
+ v->vc -= 0.5 * v->h;
+}
+
+/* Increment the reference count on a gvert */
+static gvert *inc_gvert(gamut *s, gvert *v) {
+ if (v == NULL)
+ return v;
+ v->rc++;
+//printf("~1 incremented count on 0x%x to %d\n",v,v->rc);
+ return v;
+}
+
+/* Decrement the reference count on a gvert */
+/* Copes with NULL gvert. */
+/* If the reference count goes to 0, place the */
+/* vert on the unused list. */
+static void dec_gvert(gamut *s, gvert *v) {
+ if (v == NULL)
+ return;
+
+ v->rc--;
+//printf("~1 decremended count on 0x%x to %d\n",v,v->rc);
+
+#ifdef ASSERTS
+ if (v->tag != 1)
+ error("Assert: doing decremented on gquad node");
+
+ if (v->rc < 0)
+ error("Assert: decremented gvert ref count too far");
+#endif
+
+ if (v->rc <= 0) { /* Add it to the unused list */
+ memset((void *)v, 0, sizeof(gvert));
+ v->ul = s->ul;
+ s->ul = v;
+ }
+}
+
+/* Delete all the gverts */
+static void del_gverts(gamut *s) {
+ int i;
+ for (i = 0; i < s->nv; i++) {
+ free(s->verts[i]);
+ }
+ if (s->verts != NULL) {
+ free(s->verts);
+ s->na = 0;
+ s->nv = 0;
+ }
+}
+
+/* ------------------------------------ */
+
+static
+gquad *new_gquad(
+gquad *p, /* Parent quad */
+int i /* Intended node in quad */
+) {
+ gquad *q;
+ if ((q = (gquad *)calloc(1, sizeof(gquad))) == NULL) {
+ fprintf(stderr,"gamut: calloc failed on gquad object\n");
+ exit (-1);
+ }
+ q->tag = 2;
+
+ q->w = 0.5 * p->w;
+ q->h = 0.5 * p->h;
+
+ q->hc = p->hc;
+ if (i & 1)
+ q->hc += 0.5 * q->w;
+ else
+ q->hc -= 0.5 * q->w;
+
+ q->vc = p->vc;
+ if (i & 2)
+ q->vc += 0.5 * q->h;
+ else
+ q->vc -= 0.5 * q->h;
+
+ return q;
+}
+
+/* Same as above, but create with explicit size */
+static
+gquad *new_gquad2(
+double l, /* Left border */
+double r, /* Right border */
+double b, /* Top border */
+double t /* Bottom border */
+) {
+ gquad *q;
+ if ((q = (gquad *)calloc(1, sizeof(gquad))) == NULL) {
+ fprintf(stderr,"gamut: calloc failed on gquad object\n");
+ exit (-1);
+ }
+ q->tag = 2;
+ q->w = r - l;
+ q->h = t - b;
+ q->hc = (l + r) * 0.5;
+ q->vc = (t + b) * 0.5;
+ return q;
+}
+
+static void
+del_gquad(gquad *q) {
+ int i;
+
+ if (q == NULL)
+ return;
+ for (i = 0; i < 4; i++) {
+ gnode *n = (gnode *)q->qt[i][0];
+ if (n != NULL && n->tag == 2)
+ del_gquad((gquad *)n); /* Recurse */
+ }
+ free(q);
+}
+
+/* Helper functions */
+
+/* Given a gquad and a location, decide which quandrant we're in */
+static int gquad_quadrant(gquad *q, double p[3]) {
+ int i;
+
+#ifdef ASSERTS
+ if (p[1] < (q->hc - q->w * 0.5 - 1e-10)
+ || p[1] > (q->hc + q->w * 0.5 + 1e-10)
+ || p[2] < (q->vc - q->h * 0.5 - 1e-10)
+ || p[2] > (q->vc + q->h * 0.5 + 1e-10)) {
+ fprintf(stderr,"error! point doesn't fall into bucket chosen for it!!!\n");
+ fprintf(stderr,"point x: %f < %f\n", p[1], (q->hc - q->w * 0.5));
+ fprintf(stderr,"point x: %f > %f\n", p[1], (q->hc + q->w * 0.5));
+ fprintf(stderr,"point y: %f < %f\n", p[2], (q->vc - q->h * 0.5));
+ fprintf(stderr,"point y: %f > %f\n", p[2], (q->vc + q->h * 0.5));
+ exit(-1);
+ }
+#endif
+
+ i = 0;
+ if (p[1] >= q->hc)
+ i |= 1;
+ if (p[2] >= q->vc)
+ i |= 2;
+
+ return i;
+}
+
+/* ------------------------------------ */
+/* Allocate a BSP decision node structure */
+gbspn *new_gbspn(void) {
+ gbspn *t;
+ static int n = 0; /* Serial number */
+ if ((t = (gbspn *) calloc(1, sizeof(gbspn))) == NULL) {
+ fprintf(stderr,"gamut: malloc failed - bspn node\n");
+ exit(-1);
+ }
+ t->tag = 1; /* bspn decision node */
+ t->n = n++;
+
+ return t;
+}
+
+/* Delete a BSP decision node struture */
+void del_gbspn(gbspn *t) {
+ free(t);
+}
+
+/* ------------------------------------ */
+/* Allocate a BSP tree triangle list node structure */
+gbspl *new_gbspl(
+int nt, /* Number of triangles in the list */
+gtri **t /* List of triangles to copy into structure */
+) {
+ gbspl *l;
+ int i;
+ static int n = 0; /* Serial number */
+ if ((l = (gbspl *) calloc(1, sizeof(gbspl) + nt * sizeof(gtri *))) == NULL) {
+ fprintf(stderr,"gamut: malloc failed - bspl triangle tree node\n");
+ exit(-1);
+ }
+ l->tag = 3; /* bspl triangle list node */
+ l->n = n++;
+ l->nt = nt;
+ for (i = 0; i < nt; i++)
+ l->t[i] = t[i];
+
+ return l;
+}
+
+/* Delete a BSP tree triangle list structure */
+void del_gbspl(gbspl *l) {
+ free(l);
+}
+
+/* ------------------------------------ */
+/* Allocate a triangle structure */
+gtri *new_gtri(void) {
+ gtri *t;
+ static int n = 0; /* Serial number */
+ if ((t = (gtri *) calloc(1, sizeof(gtri))) == NULL) {
+ fprintf(stderr,"gamut: malloc failed - gamut surface triangle\n");
+ exit(-1);
+ }
+ t->tag = 2; /* Triangle */
+ t->n = n++;
+
+ return t;
+}
+
+/* Delete a triangle struture */
+void del_gtri(gtri *t) {
+ free(t);
+}
+
+/* ------------------------------------ */
+/* Allocate an edge structure */
+gedge *new_gedge(void) {
+ gedge *t;
+ static int n = 0; /* Serial number */
+ if ((t = (gedge *) calloc(1, sizeof(gedge))) == NULL) {
+ fprintf(stderr,"gamut: malloc failed - triangle edge\n");
+ exit(-1);
+ }
+ t->n = n++;
+ return t;
+}
+
+/* Delete an edge struture */
+void del_gedge(gedge *t) {
+ free(t);
+}
+
+/* ------------------------------------ */
+
+/* Create a standard gamut map */
+gamut *new_gamut(
+double sres, /* Resolution (in rect coord units) of surface triangles */
+ /* 0.0 = default */
+int isJab, /* Flag indicating Jab space */
+int isRast /* Flag indicating Raster rather than colorspace */
+) {
+ gamut *s;
+
+#ifdef ASSERTS
+ fprintf(stderr,">>>>>>> ASSERTS ARE COMPILED INTO GAMUT.C <<<<<<<\n");
+#endif /* ASSERTS */
+#ifdef TEST_LOOKUP
+ fprintf(stderr,">>>>>>> TEST_LOOKUP IS COMPILED INTO GAMUT.C <<<<<<<\n");
+#endif
+#ifdef TEST_NEAREST
+ fprintf(stderr,">>>>>>> TEST_NEAREST IS COMPILED INTO GAMUT.C <<<<<<<\n");
+#endif
+#ifdef TEST_NOSORT /* Turn off sorted insersion of verticies */
+ fprintf(stderr,">>>>>>> TEST_NOSORT IS COMPILED INTO GAMUT.C <<<<<<<\n");
+#endif
+#ifdef INTERSECT_VERIFY
+ fprintf(stderr,">>>>>>> INTERSECT_VERIFY IS COMPILED INTO GAMUT.C <<<<<<<\n");
+#endif
+
+ if ((s = (gamut *)calloc(1, sizeof(gamut))) == NULL) {
+ fprintf(stderr,"gamut: calloc failed on gamut object\n");
+ exit (-1);
+ }
+
+ if (sres <= 0.0)
+ sres = 10.0; /* default */
+ if (sres > 15.0) /* Anything less is very poor */
+ sres = 15.0;
+ s->sres = sres;
+
+ if (isJab != 0) {
+ s->isJab = 1;
+ }
+
+ if (isRast != 0) {
+ s->isRast = 1;
+ }
+
+ if (s->isRast) {
+ s->logpow = RAST_LOG_POW; /* Wrap the surface more closely */
+ s->no2pass = 1; /* Only do one pass */
+ } else {
+ s->logpow = NORM_LOG_POW; /* Convex hull compression power */
+ s->no2pass = 0; /* Do two passes */
+ }
+
+ /* Center point for radial values, surface creation etc. */
+ /* To compare two gamuts using radial values, their cent must */
+ /* be the same. */
+ s->cent[0] = 50.0;
+ s->cent[1] = 0.0;
+ s->cent[2] = 0.0;
+
+ s->mx[0] = -1e38;
+ s->mx[1] = -1e38;
+ s->mx[2] = -1e38;
+ s->mn[0] = 1e38;
+ s->mn[1] = 1e38;
+ s->mn[2] = 1e38;
+
+ /* Create top level quadtree nodes */
+ s->tl = new_gquad2(-M_PI, 0.0, -M_PI/2.0, M_PI/2.0); /* Left one */
+ s->tr = new_gquad2(0.0, M_PI, -M_PI/2.0, M_PI/2.0); /* Right one */
+
+ INIT_LIST(s->tris); /* Init triangle list */
+ INIT_LIST(s->edges); /* Init edge list (?) */
+ s->read_inited = 0;
+ s->lu_inited = 0;
+ s->ne_inited = 0;
+ s->cswbset = 0;
+ s->gawbset = 0;
+
+ /* Setup methods */
+ s->del = del_gamut;
+ s->expand = expand_gamut;
+ s->getsres = getsres;
+ s->getisjab = getisjab;
+ s->getisrast = getisrast;
+ s->setnofilt = setnofilt;
+ s->getcent = getcent;
+ s->getrange = getrange;
+ s->compatible = compatible;
+ s->nrawverts = nrawverts;
+ s->getrawvert = getrawvert;
+ s->nraw0verts = nraw0verts;
+ s->getraw0vert = getraw0vert;
+ s->nssverts = nssverts;
+ s->getssvert = getssvert;
+ s->nverts = nverts;
+ s->getvert = getvert;
+ s->startnexttri = startnexttri;
+ s->getnexttri = getnexttri;
+ s->getvert = getvert;
+ s->volume = volume;
+ s->intersect = intersect;
+ s->compdstgamut = compdstgamut;
+ s->radial = radial;
+ s->nradial = nradial;
+ s->nearest = nearest;
+ s->nearest_tri = nearest_tri;
+ s->vector_isect = compute_vector_isect;
+ s->vector_isectns = compute_vector_isectns;
+ s->setwb = setwb;
+ s->getwb = getwb;
+ s->setcusps = setcusps;
+ s->getcusps = getcusps;
+ s->write_vrml = write_vrml;
+ s->write_trans_vrml = write_trans_vrml;
+ s->write_gam = write_gam;
+ s->read_gam = read_gam;
+
+ return s;
+}
+
+static void del_gnn(gnn *p);
+static void del_gbsp(gbsp *n);
+
+/* Free and clear the triangulation structures, */
+/* and clear the triangulation vertex flags. */
+static void del_triang(gamut *s) {
+ int i;
+ gtri *tp; /* Triangle pointer */
+ gedge *ep;
+
+ /* Recursively free radial lookup acceleration structures */
+ /* Do this before we delete triangles, because there may */
+ /* be triangles in the tree. */
+ if (s->lutree != NULL) {
+ del_gbsp(s->lutree);
+ s->lutree = NULL;
+ }
+
+ if (s->tris != NULL) {
+ tp = s->tris; /* Delete all the triangles */
+ FOR_ALL_ITEMS(gtri, tp) {
+ DEL_LINK(s->tris, tp);
+ del_gtri(tp);
+ } END_FOR_ALL_ITEMS(tp);
+
+ INIT_LIST(s->tris); /* Init triangle list */
+ }
+
+ if (s->edges != NULL) {
+ ep = s->edges; /* Delete all the edges */
+ FOR_ALL_ITEMS(gedge, ep) {
+ DEL_LINK(s->edges, ep);
+ del_gedge(ep);
+ } END_FOR_ALL_ITEMS(ep);
+ INIT_LIST(s->edges); /* Init edge list */
+ }
+
+ s->lu_inited = 0;
+
+ if (s->nns != NULL) {
+ del_gnn(s->nns);
+ s->nns = NULL;
+ }
+ s->ne_inited = 0;
+
+ /* Reset the vertex flags triangulation changes */
+ for (i = 0; i < s->nv; i++) {
+ s->verts[i]->f &= ~GVERT_TRI;
+ s->verts[i]->f &= ~GVERT_INSIDE;
+ }
+}
+
+static void
+del_gamut(gamut *s) {
+ del_gquad(s->tl);
+ del_gquad(s->tr);
+
+ del_triang(s);
+ del_gverts(s);
+
+ if (s->ss != NULL)
+ s->ss->del(s->ss);
+
+ free(s);
+}
+
+/* ===================================================== */
+/* Segmented maxima filter code */
+/* ===================================================== */
+
+/*
+ This implementation has two twists on the usual
+ segmented maxima filtering:
+
+ * Rather than using a uniform radial grid, it
+ uses an adaptive, quadtree structure, so that
+ rather than having a constant angular resolution
+ for the segments, the sements are chosen so as to
+ aproximately correspond to a constant surface detail
+ level. [ A subtle problem with this approach is that
+ some points will be discarded early on, that wouldn't
+ be discarded later, when the quadtree is finer. A hack
+ is to run the points throught twice.]
+
+ * Rather than keep the single sample with the
+ largest radius in each radial segment,
+ four samples are kept, each being the largest
+ in a different direction. This helps avoid
+ "knicks" in edges, where a non edge sample
+ displaces an edge sample within a segment.
+
+ */
+
+/* Helper function that returns nz if v1 should replace v2 */
+static int smreplace(
+gamut *s,
+int d, /* Slot number */
+gvert *v1, /* Candidate vertex */
+gvert *v2 /* Existing vertex */
+) {
+ double xx, w[3], c[3];
+ int j;
+ if (v2 == NULL)
+ return 1;
+
+ /* Filter out any points that are almost identical */
+ /* This can cause numerical problems in the triangle BSP tree creation. */
+ for (xx = 0.0, j = 0; j < 3; j++) {
+ double tt = v1->p[j] - v2->p[j];
+ xx += tt * tt;
+ }
+ if (xx < (1e-4 * 1e-4))
+ return 0;
+
+ c[0] = s->cent[0];
+ c[1] = s->cent[1];
+ c[2] = s->cent[2];
+
+ /* Set L, a & b weighting depending on the slot */
+ switch(d) {
+ case 1:
+ w[0] = 0.5;
+ w[1] = 1.0;
+ w[2] = 1.0;
+ break;
+ case 2:
+ w[0] = 2.0;
+ w[1] = 1.0;
+ w[2] = 1.0;
+ break;
+ case 3:
+ w[0] = 4.0;
+ w[1] = 1.0;
+ w[2] = 1.0;
+ break;
+ case 4:
+ w[0] = 0.0;
+ w[1] = 1.0;
+ w[2] = 0.1;
+ break;
+ case 5:
+ w[0] = 0.0;
+ w[1] = 0.1;
+ w[2] = 1.0;
+ break;
+ default:
+ w[0] = 1.0;
+ w[1] = 1.0;
+ w[2] = 1.0;
+ break;
+ }
+ w[0] *= w[0]; /* Because we're weighting the squares */
+ w[1] *= w[1];
+ w[2] *= w[2];
+ return ( w[0] * (v1->p[0] - c[0]) * (v1->p[0] - c[0])
+ + w[1] * (v1->p[1] - c[1]) * (v1->p[1] - c[1])
+ + w[2] * (v1->p[2] - c[2]) * (v1->p[2] - c[2]))
+ > ( w[0] * (v2->p[0] - c[0]) * (v2->p[0] - c[0])
+ + w[1] * (v2->p[1] - c[1]) * (v2->p[1] - c[1])
+ + w[2] * (v2->p[2] - c[2]) * (v2->p[2] - c[2]));
+}
+
+
+/* Expand the gamut by adding a point. */
+/* If nofilter is set, return NULL if the point */
+/* is discarded, or the address of the point representing */
+/* the point added. If nofiler is not set, return NULL */
+static gvert *expand_gamut(
+gamut *s,
+double pp[3] /* rectangular coordinate of point */
+) {
+ gnode *n; /* Current node */
+ gvert *nv, *ov; /* new vertex, old vertex */
+ gquad *q; /* Parent quad */
+ int i; /* Sub element within quad */
+ int k; /* Index of direction slot */
+ double rr[3]; /* Radial coordinate version of pp[] */
+ double sp[3]; /* Unit shere mapped version of pp[] relative to center */
+ double ch[3]; /* Convex hull testing mapped version of pp[] relative to center */
+ double lrr0; /* log scaled rr[0] */
+ double hang, vang; /* Critical angles for this points depth */
+ double aa;
+ int j;
+
+ if (s->tris != NULL || s->read_inited || s->lu_inited || s->ne_inited) {
+ fprintf(stderr,"Can't add points to gamut now!\n");
+ exit(-1);
+ }
+
+ if (s->doingfake == 0)
+ s->cu_inited = 0; /* Invalidate cust info */
+
+ /* Tracl bounding range */
+ for (j = 0; j < 3; j++) {
+ if (pp[j] > s->mx[j])
+ s->mx[j] = pp[j];
+ if (pp[j] < s->mn[j])
+ s->mn[j] = pp[j];
+ }
+
+ /* Convert to radial coords */
+ gamut_rect2radial(s, rr, pp);
+
+ if (rr[0] < 1e-6) /* Ignore a point right at the center */
+ return NULL;
+
+ /* Figure log scaled radius */
+ lrr0 = log_scale(s, rr[0]);
+
+ /* Compute unit shere mapped location */
+ aa = 1.0/rr[0]; /* Adjustment to put in on unit sphere */
+ for (j = 0; j < 3; j++)
+ sp[j] = (pp[j] - s->cent[j]) * aa;
+
+ /* Compute hull testing mapped version */
+ for (j = 0; j < 3; j++)
+ ch[j] = sp[j] * lrr0;
+
+ /* compute twice angle resolution required (will compare to parent size) */
+ hang = pow(rr[0], 1.01) * fabs(cos(rr[2]));
+ if (hang < 1e-9)
+ hang = 1e-9;
+ hang = 4.0 * s->sres/hang;
+ vang = 4.0 * s->sres/pow(rr[0], 1.01);
+
+//printf("~1 Point at %f %f %f, radial %f %f %f\n", pp[0], pp[1], pp[2], rr[0], rr[1], rr[2]);
+//printf("~1 shere at %f %f %f, log %f %f %f, vhang %f %f\n", sp[0], sp[1], sp[2], ch[0], ch[1], ch[2], vang, hang);
+
+ /* If nofilter flag is set, add all points as verticies */
+ if (s->nofilter) {
+
+ /* Filter out any points that are almost identical. */
+ /* This can cause numerical problems in the triangle BSP tree creation. */
+ for (i = 0; i < s->nv; i++) {
+ double xx;
+
+ for (xx = 0.0, j = 0; j < 3; j++) {
+ double tt = pp[j] - s->verts[i]->p[j];
+ xx += tt * tt;
+ }
+ if (xx < (1e-4 * 1e-4)) {
+ if (s->doingfake)
+ s->verts[i]->f |= GVERT_ESTP;
+
+ return s->verts[i]; /* Existing point becomes added point */
+ }
+ }
+
+ /* Create a vertex for the point we're possibly adding */
+ nv = new_gvert(s, NULL, 0, GVERT_SET | (s->doingfake ? (GVERT_FAKE | GVERT_ESTP) : 0),
+ pp, rr, lrr0, sp, ch);
+
+ return nv;
+ }
+ /* else filter using adaptive segmented maxima */
+
+ /* Start by looking at the top level quads */
+ if (rr[1] >= 0.0) {
+ q = s->tr;
+ } else {
+ q = s->tl;
+ }
+ n = (gnode *)q;
+//printf("~1 Starting with quad 0x%x, width %f, height %f\n",q, q->w, q->h);
+
+ /* Now recurse until we have a virtex at the right location and depth */
+ for (;;) {
+ /* Recurse into quad node n */
+ q = (gquad *)n; /* Parent node */
+ i = gquad_quadrant(q, rr); /* Quadrand of parent */
+ n = q->qt[i][0]; /* Child node in quadrant */
+
+//printf("~1 Current quad 0x%x, width %f, height %f\n",q, q->w, q->h);
+//printf("~1 Current child in quadrant %d, node 0x%x, type %d\n", i, n, n != NULL ? n->tag : 0);
+
+ /* If we're at the right depth to create a vertex, break out of decent loop. */
+
+ if (n == NULL) { /* Create new node */
+ if (q->w <= hang && q->h <= vang) {
+//printf("~1 We're at the right depth to add vertex\n");
+ break;
+ }
+ /* Else create a new quad */
+ n = (gnode *)new_gquad(q, i);
+ q->qt[i][0] = n;
+//printf("~1 Empty child node not deep enough, creating new quad node 0x%x\n",n);
+
+ /* If we've found verticies at this node */
+ } else if (n->tag == 1) {
+ int j;
+ gquad *qq; /* New child quad */
+ gvert *vv[NSLOTS]; /* Existing verticies at this level */
+
+ if (q->w <= hang && q->h <= vang) {
+//printf("~1 We're at the right depth to replace vertex\n");
+ break;
+ }
+//printf("~1 deepening verticies\n");
+
+ for (k = 0; k < NSLOTS; k++)
+ vv[k] = (gvert *)q->qt[i][k]; /* Save pointers to current verticies */
+
+//printf("~1 existing verticies are 0x%x, 0x%x, 0x%x, 0x%x\n", vv[0], vv[1], vv[2], vv[3]);
+
+ /* make a quad to replace the current verticies */
+ qq = new_gquad(q, i);
+ n = (gnode *)qq;
+ q->qt[i][0] = n;
+ for (k = 1; k < NSLOTS; k++)
+ q->qt[i][k] = NULL;
+
+//printf("~1 added quad 0x%x to quadrant %d\n",i,q);
+
+ /* Distribute verticies that were here, into new quad */
+ for (j = 0; j < NSLOTS; j++) { /* For all existing verticies */
+
+ if (vv[j] == NULL)
+ continue;
+
+//printf("~1 re-distributing verticy 0x%x\n",vv[j]);
+ i = gquad_quadrant(qq, vv[j]->r); /* Quadrant for existing vertex */
+
+ set_gvert(vv[j], qq, i); /* Update vertex node location */
+
+ nv = vv[j];
+ for (k = 0; nv != NULL && k < NSLOTS; k++) { /* For direction slot */
+ ov = (gvert *)qq->qt[i][k];
+ if (smreplace(s, k, nv, ov)) {
+ if (k == 0) { /* Track points that are in k == 0 direction */
+ if (ov != NULL && ov->k0 > 0)
+ ov->k0--;
+ nv->k0++;
+ }
+#ifndef NEVER
+ qq->qt[i][k] = (gnode *)inc_gvert(s, nv);
+ dec_gvert(s, ov);
+#else
+ /* Use slots for best, 2nd best, etc */
+ qq->qt[i][k] = (gnode *)inc_gvert(s, nv);
+ dec_gvert(s, nv);
+ nv = ov;
+#endif
+//printf("Node 0x%x rc %d at %f %f %f is replacing\n",nv, nv->rc, nv->p[0], nv->p[1], nv->p[2]);
+//if (ov != NULL) printf(" replacing node 0x%x rc %d at %f %f %f\n",ov, ov->rc, ov->p[0], ov->p[1], ov->p[2]);
+//else printf(" NULL\n");
+ }
+ }
+ dec_gvert(s, nv);
+ }
+ }
+ /* Else it's a quad, and we will decend into it */
+
+ } /* keep decending until we find right depth */
+
+//printf("~1 Got parent quad 0x%x, quadrant %d, vertex 0x%x\n", q, i, n);
+
+ /* Create a vertex for the point we're possibly adding */
+ nv = new_gvert(s, q, i, GVERT_SET, pp, rr, lrr0, sp, ch);
+
+ /* Replace any existing gverts with this one */
+ for (k = 0; k < NSLOTS; k++) { /* For direction slot */
+ ov = (gvert *)q->qt[i][k];
+ if (smreplace(s, k, nv, ov)) {
+ if (k == 0) { /* Track points that are in k == 0 direction */
+ if (ov != NULL && ov->k0 > 0)
+ ov->k0--;
+ nv->k0++;
+ }
+#ifndef NEVER
+ q->qt[i][k] = (gnode *)inc_gvert(s, nv);
+ dec_gvert(s, ov);
+#else
+ /* Use slots for best, 2nd best, etc */
+ q->qt[i][k] = (gnode *)inc_gvert(s, nv);
+ dec_gvert(s, nv);
+ nv = ov;
+#endif
+ }
+ }
+ dec_gvert(s, nv); /* Make sure it's reclaimed if wasn't used */
+
+//printf("~1 Point is done\n\n");
+
+ return NULL;
+}
+
+/* ------------------------------------ */
+/* Initialise this gamut with the intersection of the */
+/* the two given gamuts. Return NZ on error. */
+/* Return 1 if gamuts are not compatible */
+/* (We assume that the this gamut is currently empty) */
+static int intersect(gamut *s, gamut *sa, gamut *sb) {
+ int i, j, k;
+ gamut *s1, *s2;
+
+ if (sa->compatible(sa, sb) == 0)
+ return 1;
+
+ if IS_LIST_EMPTY(sa->tris)
+ triangulate(sa);
+ if IS_LIST_EMPTY(sb->tris)
+ triangulate(sb);
+
+ s->isJab = sa->isJab;
+
+ if (sa->isRast || sb->isRast)
+ s->isRast = 1;
+
+ for (j = 0; j < 3; j++)
+ s->cent[j] = sa->cent[j];
+
+ /* Clear some flags */
+ s->cswbset = 0;
+ s->cswbset = 0;
+ s->dcuspixs = 0;
+
+ /* If either is a raster gamut, make it a raster gamut */
+ if (s->isRast)
+ s->logpow = RAST_LOG_POW; /* Wrap the surface more closely */
+ else
+ s->logpow = NORM_LOG_POW;
+
+ /* Don't filter the points (gives a more accurate result ?) */
+ s->nofilter = 1;
+
+ /* Add each source gamuts verticies that lie within */
+ /* the other gamut */
+ for (k = 0; k < 2; k++) {
+ gtri *tp1, *tp2; /* Triangle pointers */
+
+ if (k == 0) {
+ s1 = sa;
+ s2 = sb;
+ } else {
+ s1 = sb;
+ s2 = sa;
+ }
+ for (i = 0; i < s1->nv; i++) {
+ double pl;
+
+ if (!(s1->verts[i]->f & GVERT_TRI))
+ continue;
+
+ pl = s2->nradial(s2, NULL, s1->verts[i]->p);
+ if (pl <= (1.0 + 1e-9)) {
+ expand_gamut(s, s1->verts[i]->p);
+ s1->verts[i]->f &= ~GVERT_ISOS; /* s1 vert is not outside s2 */
+ } else {
+ s1->verts[i]->f |= GVERT_ISOS; /* s1 vert is outside s2 */
+ }
+ }
+
+ /* Now find the edges that intersect the other gamut */
+ tp1 = s1->tris;
+ FOR_ALL_ITEMS(gtri, tp1) { /* For all s1 triangles */
+
+ for (j = 0; j < 3; j++) { /* For all edges in s1 triangle */
+ /* If edge passes through the other gamut */
+ if ((tp1->e[j]->v[0]->f ^ tp1->e[j]->v[1]->f) & GVERT_ISOS) {
+
+ /* Exhaustive search of other triangles in s2, */
+ /* to find the one that the edge intersects with. */
+ tp2 = s2->tris;
+ FOR_ALL_ITEMS(gtri, tp2) {
+ double pv;
+ double tt[3];
+
+ /* Do a min/max intersection elimination test */
+ for (i = 0; i < 3; i++) {
+ if (tp2->mix[1][i] < tp1->mix[0][i]
+ || tp2->mix[0][i] > tp1->mix[1][i])
+ break; /* min/max don't overlap */
+ }
+ if (i < 3)
+ continue; /* Skip this triangle, it can't intersect */
+
+ if (vect_intersect(s1, &pv, tt, tp1->e[j]->v[0]->p, tp1->e[j]->v[1]->p, tp2)
+ && pv >= (0.0 - 1e-10) && pv <= (1.0 + 1e-10)) {
+ expand_gamut(s, tt);
+ }
+ } END_FOR_ALL_ITEMS(tp2);
+ }
+ }
+
+ } END_FOR_ALL_ITEMS(tp1);
+ }
+
+ s->nofilter = 0;
+
+ return 0;
+}
+
+/* ------------------------------------ */
+/*
+ Initialise this gamut with a destination mapping gamut.
+ s1 is the image gamut (a possible sub-set of the src gamut)
+ s2 is the source gamut
+ s3 is the destination gamut
+
+ if docomp:
+ this gamut will be set to the smaller of the img & dst gamuts
+ else
+ this gamut will be the img gamut
+
+ if doexp
+ this gamut will be expanded by the amount dst is outside the src gamut.
+
+ The vector direction of "inwards" is that returned by the
+ callback function if it is supplied, or radially inwards
+ if it is not.
+
+ Return 1 if gamuts are not compatible.
+ (We assume that the this gamut is currently empty)
+
+ */
+#define MXNIS 40 /* Maximum raw intersections handled */
+
+static int compdstgamut(
+gamut *s, /* This */
+gamut *s1, /* Image gamut, assumed to be a subset of source gamut */
+gamut *s2, /* The source space gamut */
+gamut *s3, /* The destination space gamut */
+int docomp, /* Flag, nz if compression is enabled */
+int doexp, /* Flag, nz if expansion is enabled. */
+gamut *nedst, /* Optional - if doexp, then expand nedst with non-expanded dst gamut */
+void (*cvect)(void *cntx, double *p2, double *p1), /* Compression direction callback */
+void *cntx /* Returns p2 which is in desired direction from given p1 */
+) {
+#ifdef STATS
+ int tedges, edgestested, testhits;
+#endif
+ int i, j, k;
+ gamut *ss[3];
+ gispnt lp1[MXNIS], lp2[MXNIS], lp3[MXNIS]; /* Intersection lists */
+ int ll1, ll2, ll3; /* Returned list length */
+ int ii, jj, kk; /* List indexes */
+
+ if (s1->compatible(s1, s2) == 0
+ || s1->compatible(s2, s3) == 0)
+ return 1;
+
+ if IS_LIST_EMPTY(s1->tris)
+ triangulate(s1);
+ if IS_LIST_EMPTY(s2->tris)
+ triangulate(s2);
+ if IS_LIST_EMPTY(s3->tris)
+ triangulate(s3);
+
+ s->isJab = s1->isJab;
+ s->isRast = s1->isRast;
+
+ for (j = 0; j < 3; j++)
+ s->cent[j] = s1->cent[j];
+
+ /* Clear some flags */
+ s->cswbset = 0;
+ s->cswbset = 0;
+ s->dcuspixs = 0;
+
+ /* If s1 is a raster gamut, make output a raster gamut */
+ if (s->isRast)
+ s->logpow = RAST_LOG_POW; /* Wrap the surface more closely */
+ else
+ s->logpow = NORM_LOG_POW;
+
+ /* Don't filter the points (gives a more accurate result ?) */
+ s->nofilter = 1;
+
+ ss[0] = s1;
+ ss[1] = s2;
+ ss[2] = s3;
+
+//printf("~1 compdstgamut docomp %d, doexp %d\n",docomp,doexp);
+ /* Reset the above/below flags */
+ /* 1 = img above dst */
+ /* 2 = img below dst */
+ /* 4 = src above dst */
+ /* 8 = src below dst */
+ for (k = 0; k < 3; k++) { /* For img, src & dst */
+ for (i = 0; i < ss[k]->nv; i++) {
+ if (!(ss[k]->verts[i]->f & GVERT_TRI))
+ continue;
+
+ ss[k]->verts[i]->as = 0;
+ }
+ }
+
+ /* Use all the triangle vertices from the three gamuts */
+ /* as candidate points, because any of them might */
+ /* determine a surface feature. */
+ for (k = 0; k < 3; k++) { /* For img, src & dst */
+
+ for (i = 0; i < ss[k]->nv; i++) {
+ double pp[3], ppv, p2[3];
+ double rr, r4;
+
+ if (!(ss[k]->verts[i]->f & GVERT_TRI))
+ continue;
+
+ icmCpy3(pp, ss[k]->verts[i]->p); /* Point in question */
+
+//printf("\n~1 k %d, point %d: %f %f %f\n", k,i,pp[0],pp[1],pp[2]);
+ if (cvect != NULL)
+ cvect(cntx, p2, pp); /* Get mapping direction */
+ else
+ icmCpy3(p2, ss[k]->cent); /* Radial vector */
+ icmNormalize33(p2, p2, pp, 1.0);
+
+ /* Locate the intersecting segments for each gamut */
+ ll1 = ll2 = ll3 = 0;
+ ll1 = s1->vector_isectns(s1, pp, p2, lp1, MXNIS); /* Image gamut */
+ if (doexp) /* For dst - src expansion */
+ ll2 = s2->vector_isectns(s2, pp, p2, lp2, MXNIS); /* Src gamut */
+ if (docomp || doexp) /* For img ^ dst or dst - src */
+ ll3 = s3->vector_isectns(s3, pp, p2, lp3, MXNIS); /* Dst gamut */
+
+//printf("~1 ll1 %d, ll2 %d, ll3 %d\n",ll1,ll2,ll3);
+#ifdef NEVER
+ printf("img segments:\n");
+ for (ii = 0; ii < ll1; ii++)
+ printf("Isect %d: pv %f, dir %d, edge %d, tri %d\n",ii,lp1[ii].pv,lp1[ii].dir,lp1[ii].edge,lp1[ii].tri->n);
+ printf("src segments:\n");
+ for (ii = 0; ii < ll2; ii++)
+ printf("Isect %d: pv %f, dir %d, edge %d, tri %d\n",ii,lp2[ii].pv,lp2[ii].dir,lp2[ii].edge,lp2[ii].tri->n);
+ printf("dst segments:\n");
+ for (ii = 0; ii < ll3; ii++)
+ printf("Isect %d: pv %f, dir %d, edge %d, tri %d\n",ii,lp3[ii].pv,lp3[ii].dir,lp3[ii].edge,lp3[ii].tri->n);
+#endif
+ /* Now go through each image segment */
+ for (ii = 0; ii < ll1; ii += 2) {
+
+ icmCpy3(pp, lp1[ii].ip);
+ ppv = lp1[ii].pv;
+
+//printf("~1 img pnt at %f\n",lp1[ii].pv);
+ if (docomp) {
+
+ /* Locate a dst segment around or below img point */
+ for (kk = 0; kk < ll3; kk += 2) {
+ if ((lp1[kk+1].pv + 1e-8) >= ppv)
+ break;
+ }
+
+ if (kk >= ll3) { /* No dst segments or none below */
+ ss[k]->verts[i]->as |= 1; /* img above dst */
+//printf("~1 img pnt is outside dst\n");
+ continue;
+ } else {
+//printf("~1 ing %f - %f, dst %f - %f\n", lp1[ii].pv,lp1[ii+1].pv,lp3[kk].pv,lp3[kk+1].pv);
+ /* Use the dst point if it is smaller */
+ if (ppv < lp3[kk].pv) {
+ icmCpy3(pp, lp3[kk].ip);
+ ppv = lp3[kk].pv;
+//printf("~1 dst is smaller %f\n",ppv);
+ ss[k]->verts[i]->as |= 1; /* img above dst */
+ } else {
+//printf("~1 ing is smaller %f\n",ppv);
+ ss[k]->verts[i]->as |= 2; /* img below dst */
+ }
+ }
+ }
+
+ if (nedst != NULL)
+ expand_gamut(nedst, pp);
+
+ while (doexp) {
+ /* Locate a src segment that ing point lies in. */
+ for (jj = 0; jj < ll2; jj += 2) {
+ if (lp1[ii].pv >= (lp2[jj].pv - 1e-8)
+ && lp1[ii].pv <= (lp2[jj+1].pv + 1e-8))
+ break;
+ }
+ if (jj >= ll2) {
+ ss[k]->verts[i]->as |= 4; /* src above dst ?? */
+ break; /* No overlapping segment */
+ }
+
+ /* Locate a dst segment that src point lies in */
+ for (kk = 0; kk < ll3; kk += 2) {
+ if (lp2[jj].pv >= (lp3[kk].pv - 1e-8)
+ && lp2[jj].pv <= (lp3[kk+1].pv + 1e-8))
+ break;
+ }
+ if (kk >= ll2) {
+ ss[k]->verts[i]->as |= 4; /* src above dst ?? */
+ break; /* No overlapping segment */
+ }
+
+ /* if src is outside dst, do nothing */
+ if (lp3[kk].pv >= lp2[jj].pv) {
+ ss[k]->verts[i]->as |= 4; /* src above dst */
+ break;
+ }
+
+ /* Expand point by dst - src */
+ icmAdd3(pp, pp, lp3[kk].ip);
+ icmSub3(pp, pp, lp2[jj].ip);
+ ss[k]->verts[i]->as |= 8; /* src below dst */
+//printf("~1 expanding point by %f - %f\nb",lp3[kk].pv,lp2[jj].pv);
+ break;
+ }
+ expand_gamut(s, pp);
+ }
+ }
+ }
+
+#ifdef STATS
+ tedges = edgestested = testhits = 0;
+#endif
+
+ /* Add any intersection points between img/dst, and src/dst */
+ for (k = 0; k < 4; k++) {
+ int mask;
+ gamut *sa, *sb;
+ gtri *tpa, *tpb; /* Triangle pointers */
+
+ if (k == 0) {
+ if (!docomp)
+ continue;
+ mask = 3;
+ sa = s1; /* img */
+ sb = s3; /* dst */
+ } else if (k == 1) {
+ if (!docomp)
+ continue;
+ mask = 3;
+ sa = s3; /* dst */
+ sb = s1; /* img */
+ } else if (k == 2) {
+ if (!doexp)
+ continue;
+ mask = 12;
+ sa = s2; /* src */
+ sb = s3; /* dst */
+ } else if (k == 3) {
+ if (!doexp)
+ continue;
+ mask = 12;
+ sa = s3; /* dst */
+ sb = s2; /* src */
+ }
+
+ /* Now find the edges that intersect the other gamut */
+ tpa = sa->tris;
+ FOR_ALL_ITEMS(gtri, tpa) {
+
+ for (j = 0; j < 3; j++) { /* For each edge */
+#ifdef STATS
+ tedges++;
+#endif
+ /* If edge disposition flags aren't the same */
+ if ((tpa->e[j]->v[0]->as ^ tpa->e[j]->v[1]->as) & mask
+ || (tpa->e[j]->v[0]->as & mask) == 3 || (tpa->e[j]->v[0]->as & mask) == 12
+ || (tpa->e[j]->v[1]->as & mask) == 3 || (tpa->e[j]->v[1]->as & mask) == 12) {
+#ifdef STATS
+ edgestested++;
+#endif
+ /* Exhaustive search of other triangles */
+ tpb = sb->tris;
+ FOR_ALL_ITEMS(gtri, tpb) {
+ double pv;
+ double pp[3];
+
+ /* Do a min/max intersection elimination test */
+ for (i = 0; i < 3; i++) {
+ if (tpb->mix[1][i] < tpa->mix[0][i]
+ || tpb->mix[0][i] > tpa->mix[1][i])
+ break; /* min/max don't overlap */
+ }
+ if (i < 3)
+ continue; /* Skip this triangle, it can't intersect */
+
+ if (vect_intersect(sa, &pv, pp, tpa->e[j]->v[0]->p, tpa->e[j]->v[1]->p, tpb)
+ && pv >= (0.0 - 1e-10) && pv <= (1.0 + 1e-10)) {
+ /* Process intersection point pp the same as the first loop */
+ double ppv, p2[3];
+ double rr, r4;
+#ifdef STATS
+ testhits++;
+#endif
+//printf("\n~1 tri isxn point %f %f %f\n", pp[0],pp[1],pp[2]);
+ if (cvect != NULL)
+ cvect(cntx, p2, pp); /* Get mapping direction */
+ else
+ icmCpy3(p2, sa->cent); /* Radial vector */
+ icmNormalize33(p2, p2, pp, 1.0);
+
+ /* Locate the intersecting segments for each gamut */
+ ll1 = ll2 = ll3 = 0;
+ ll1 = s1->vector_isectns(s1, pp, p2, lp1, MXNIS); /* Image gamut */
+ if (doexp) /* For dst - src expansion */
+ ll2 = s2->vector_isectns(s2, pp, p2, lp2, MXNIS); /* Src gamut */
+ if (docomp || doexp) /* For img ^ dst or dst - src */
+ ll3 = s3->vector_isectns(s3, pp, p2, lp3, MXNIS); /* Dst gamut */
+
+//printf("~1 ll1 %d, ll2 %d, ll3 %d\n",ll1,ll2,ll3);
+#ifdef NEVER
+ printf("img segments:\n");
+ for (ii = 0; ii < ll1; ii++)
+ printf("Isect %d: pv %f, dir %d, edge %d, tri %d\n",ii,lp1[ii].pv,lp1[ii].dir,lp1[ii].edge,lp1[ii].tri->n);
+ printf("src segments:\n");
+ for (ii = 0; ii < ll2; ii++)
+ printf("Isect %d: pv %f, dir %d, edge %d, tri %d\n",ii,lp2[ii].pv,lp2[ii].dir,lp2[ii].edge,lp2[ii].tri->n);
+ printf("dst segments:\n");
+ for (ii = 0; ii < ll3; ii++)
+ printf("Isect %d: pv %f, dir %d, edge %d, tri %d\n",ii,lp3[ii].pv,lp3[ii].dir,lp3[ii].edge,lp3[ii].tri->n);
+#endif
+ /* Now go through each image segment */
+ for (ii = 0; ii < ll1; ii += 2) {
+
+ icmCpy3(pp, lp1[ii].ip);
+ ppv = lp1[ii].pv;
+
+//printf("~1 img pnt at %f\n",lp1[ii].pv);
+ if (docomp) {
+
+ /* Locate a dst segment around or below img point */
+ for (kk = 0; kk < ll3; kk += 2) {
+ if ((lp1[kk+1].pv + 1e-8) >= ppv)
+ break;
+ }
+
+ if (kk >= ll3) { /* No dst segments or none below */
+//printf("~1 img pnt is outside dst\n");
+ continue;
+ } else {
+//printf("~1 ing %f - %f, dst %f - %f\n", lp1[ii].pv,lp1[ii+1].pv,lp3[kk].pv,lp3[kk+1].pv);
+ /* Use the dst point if it is smaller */
+ if (ppv < lp3[kk].pv) {
+ icmCpy3(pp, lp3[kk].ip);
+ ppv = lp3[kk].pv;
+//printf("~1 dst is smaller %f\n",ppv);
+ } else {
+//printf("~1 ing is smaller %f\n",ppv);
+ }
+ }
+ }
+
+ if (nedst != NULL)
+ expand_gamut(nedst, pp);
+
+ while (doexp) {
+ /* Locate a src segment that ing point lies in */
+ for (jj = 0; jj < ll2; jj += 2) {
+ if (lp1[ii].pv >= (lp2[jj].pv - 1e-8)
+ && lp1[ii].pv <= (lp2[jj+1].pv + 1e-8))
+ break;
+ }
+ if (jj >= ll2) {
+ break; /* No overlapping segment */
+ }
+
+ /* Locate a dst segment that src point lies in */
+ for (kk = 0; kk < ll3; kk += 2) {
+ if (lp2[jj].pv >= (lp3[kk].pv - 1e-8)
+ && lp2[jj].pv <= (lp3[kk+1].pv + 1e-8))
+ break;
+ }
+ if (kk >= ll2) {
+ break; /* No overlapping segment */
+ }
+
+ /* if src is outside dst, do nothing */
+ if (lp3[kk].pv >= lp2[jj].pv) {
+ break;
+ }
+
+ /* Expand point by dst - src */
+ icmAdd3(pp, pp, lp3[kk].ip);
+ icmSub3(pp, pp, lp2[jj].ip);
+//printf("~1 expanding point by %f - %f\nb",lp3[kk].pv,lp2[jj].pv);
+ break;
+ }
+ expand_gamut(s, pp);
+ }
+ }
+ } END_FOR_ALL_ITEMS(tpb);
+ }
+ }
+
+ } END_FOR_ALL_ITEMS(tpa);
+ }
+
+#ifdef STATS
+ printf("Total edges %d, edges tested %d, edge hits %d\n", tedges, edgestested, testhits);
+#endif
+ s->nofilter = 0;
+
+ return 0;
+}
+#undef MXNIS
+
+/* ------------------------------------ */
+/* Locate the vertices most likely to correspond to the */
+/* primary and secondary colors (cusps) */
+/*
+ * Notes:
+ *
+ * To better support gamuts of devices with more than 4 colorants,
+ * it may be necessary to add another flag type that expands
+ * the cusps to lie on the actual gamut surface, as for
+ * some devices this lies outside the pure colorant combinations.
+ *
+ * Flinging a grid of values at this doesn't always
+ * return sensible results. Sometimes two "cusps" might be
+ * unreasonable close to each other (ie. one isn't a real cusp).
+ * This can cause gammut mapping to fail ...
+ *
+ * How could this be made more robust ?
+ *
+ */
+
+static void setcusps(gamut *s, int flag, double in[3]) {
+ int i, j;
+
+ /* - - - - - - - - - - - - - - - - - - - - - - */
+ if (flag == 0) { /* Reset */
+ for (j = 0; j < 6; j++) {
+ s->cusps[j][0] = 0.0; /* Marker values */
+ s->cusps[j][1] = 0.0;
+ s->cusps[j][2] = 0.0;
+ }
+ s->dcuspixs = 0;
+ s->cu_inited = 0;
+ return;
+
+ /* - - - - - - - - - - - - - - - - - - - - - - */
+ } else if (flag == 2) { /* Finalize */
+
+ if (s->dcuspixs > 0) {
+ double JCh[3];
+ double hues[6];
+ int r, br = 0.0;
+ double berr;
+
+ /* Figure out where to put the ones we got */
+ for (j = 0; j < 6; j++) { /* Compute the hues */
+ icmLab2LCh(JCh, s->dcusps[j]);
+ hues[j] = JCh[2];
+ }
+
+ /* Sort them into hue order */
+ for (j = 0; j < 5; j++) {
+ for (i = j+1; i < 6; i++) {
+ if (hues[j] > hues[i]) {
+ double tt;
+ tt = hues[j]; hues[j] = hues[i]; hues[i] = tt;
+ tt = s->dcusps[j][0]; s->dcusps[j][0] = s->dcusps[i][0]; s->dcusps[i][0] = tt;
+ tt = s->dcusps[j][1]; s->dcusps[j][1] = s->dcusps[i][1]; s->dcusps[i][1] = tt;
+ tt = s->dcusps[j][2]; s->dcusps[j][2] = s->dcusps[i][2]; s->dcusps[i][2] = tt;
+ }
+ }
+ }
+
+ /* Figure out which is the best match by rotation */
+ berr = 1e6;
+ for (r = 0; r < 6; r++) {
+ double terr = 0.0;
+ for (j = 0; j < 6; j++) {
+ double tt;
+
+ tt = fabs(gam_hues[s->isJab][j] - hues[(j + r) % 6]);
+ if (tt > 180.0)
+ tt = 360.0 - tt;
+ terr += tt;
+ }
+ if (terr < berr) {
+ br = r;
+ berr = terr;
+ }
+ }
+ /* Place them at that rotation */
+ for (j = 0; j < 6; j++) { /* Compute the hues */
+//printf("~1 placing hue %f ix %d into hue %f ix %d\n", hues[(j + br) % 6],(j + br) % 6, gam_hues[s->isJab][j] ,j);
+
+ s->cusps[j][0] = s->dcusps[(j + br) % 6][0];
+ s->cusps[j][1] = s->dcusps[(j + br) % 6][1];
+ s->cusps[j][2] = s->dcusps[(j + br) % 6][2];
+ }
+ }
+
+ /* Check we've got a cusp */
+ for (j = 0; j < 6; j++) {
+ if (s->cusps[j][0] == 0.0
+ && s->cusps[j][1] == 0.0
+ && s->cusps[j][2] == 0.0) {
+ s->cu_inited = 0;
+ return; /* Not all have been set */
+ }
+ }
+
+ {
+ double JCh[3];
+ double hues[6];
+
+ /* Check how far appart the cusps are in hue angle */
+ // ~~999
+ for (j = 0; j < 6; j++) {
+ icmLab2LCh(JCh, s->cusps[j]);
+ hues[j] = JCh[2];
+//printf("~1 cusp %d = hue %f\n",j,hues[j]);
+ }
+ for (j = 0; j < 6; j++) {
+ int k = j < 5 ? j + 1 : 0;
+ double rh, h;
+ rh = gam_hues[s->isJab][k] - gam_hues[s->isJab][j];
+ if (rh < 0.0)
+ rh = 360 + rh;
+ h = hues[k] - hues[j];
+ if (h < 0.0)
+ h = 360 + h;
+//printf("~1 cusp %d - %d = ref dh %f, dh %f\n",j,k,rh,h);
+
+ /* if our delta is less than half reference, */
+ /* assume the cusps are bad. */
+ if ((2.0 * h) < rh) {
+
+ s->cu_inited = 0; /* Not trustworthy */
+//printf("~1 cusps are not trustworthy\n");
+ return;
+ }
+ }
+ }
+
+ s->cu_inited = 1;
+ return;
+
+ /* - - - - - - - - - - - - - - - - - - - - - - */
+ } else if (flag == 3) { /* Definite 1/6 cusp */
+
+ if (s->dcuspixs >= 6) {
+//printf("~1 too many cusp values added\n");
+ return; /* Error - extra cusp ignored */
+ }
+ s->dcusps[s->dcuspixs][0] = in[0];
+ s->dcusps[s->dcuspixs][1] = in[1];
+ s->dcusps[s->dcuspixs++][2] = in[2];
+
+ } else { /* Consider another point as a cusp point */
+ double JCh[3];
+ int bj = 0, sbj = 0;
+ double bh = 1e6, sbh = 1e6;
+ double ns, es;
+
+ icmLab2LCh(JCh, in);
+
+//printf("~1 cusp at %f %f %f\n",JCh[0],JCh[1],JCh[2]);
+ /* See which hue it is closest and 2nd closet to cusp hue. */
+ for (j = 0; j < 6; j++) {
+ double tt;
+
+ tt = fabs(gam_hues[s->isJab][j] - JCh[2]);
+ if (tt > 180.0)
+ tt = 360.0 - tt;
+
+ if (tt < bh) {
+ if (bh < sbh) {
+ sbh = bh;
+ sbj = bj;
+ }
+ bh = tt;
+ bj = j;
+ } else if (tt < sbh) {
+ sbh = tt;
+ sbj = j;
+ }
+ }
+
+ /* Compute distance of existing and new */
+ es = s->cusps[bj][1] * s->cusps[bj][1] + s->cusps[bj][2] * s->cusps[bj][2];
+ ns = in[1] * in[1] + in[2] * in[2];
+//printf("~1 chroma dist of existing %f, new %f\n",es,ns);
+ if (ns > es) {
+//printf("~1 New closest\n");
+ s->cusps[bj][0] = in[0];
+ s->cusps[bj][1] = in[1];
+ s->cusps[bj][2] = in[2];
+
+ } else { /* If 2nd best has no entry, use this to fill it */
+ if (s->cusps[sbj][0] == 0.0
+ && s->cusps[sbj][1] == 0.0
+ && s->cusps[sbj][2] == 0.0) {
+//printf("~1 Fill with 2nd closest\n");
+ s->cusps[sbj][0] = in[0];
+ s->cusps[sbj][1] = in[1];
+ s->cusps[sbj][2] = in[2];
+ }
+ }
+ }
+}
+
+
+/* Get the cusp values for red, yellow, green, cyan, blue & magenta */
+/* Return nz if there are no cusps available */
+static int getcusps(
+gamut *s,
+double cusps[6][3]
+) {
+ int i, j;
+
+ if (s->cu_inited == 0) {
+ return 1;
+ }
+
+ for (i = 0; i < 6; i++)
+ for (j = 0; j < 3; j++)
+ cusps[i][j] = s->cusps[i][j];
+
+ return 0;
+}
+
+/* ===================================================== */
+/* Triangulation code */
+/* ===================================================== */
+
+static double ne_point_on_tri(gamut *s, gtri *t, double *out, double *in);
+
+/* Given three points, compute the normalised plane equation */
+/* of a surface through them. */
+/* Return non-zero on error */
+static int plane_equation(
+double *eq, /* Return equation parameters */
+double *p0, /* The three points */
+double *p1,
+double *p2
+) {
+ double ll, v1[3], v2[3];
+
+ /* Compute vectors along edges */
+ v1[0] = p1[0] - p0[0];
+ v1[1] = p1[1] - p0[1];
+ v1[2] = p1[2] - p0[2];
+
+ v2[0] = p2[0] - p0[0];
+ v2[1] = p2[1] - p0[1];
+ v2[2] = p2[2] - p0[2];
+
+ /* Compute cross products v1 x v2, which will be the normal */
+ eq[0] = v1[1] * v2[2] - v1[2] * v2[1];
+ eq[1] = v1[2] * v2[0] - v1[0] * v2[2];
+ eq[2] = v1[0] * v2[1] - v1[1] * v2[0];
+
+ /* Normalise the equation */
+ ll = sqrt(eq[0] * eq[0] + eq[1] * eq[1] + eq[2] * eq[2]);
+ if (ll < 1e-10) {
+ return 1;
+ }
+ eq[0] /= ll;
+ eq[1] /= ll;
+ eq[2] /= ll;
+
+ /* Compute the plane equation constant */
+ eq[3] = - (eq[0] * p0[0])
+ - (eq[1] * p0[1])
+ - (eq[2] * p0[2]);
+
+#ifdef NEVER
+ /* Veritify the plane equation */
+ {
+ double c;
+ c = eq[0] * p0[0]
+ + eq[1] * p0[1]
+ + eq[2] * p0[2]
+ + eq[3];
+ if (fabs(c) > 1e-10) {
+ printf("Plane equation check 0 failed by %f\n",c);
+ }
+ c = eq[0] * p1[0]
+ + eq[1] * p1[1]
+ + eq[2] * p1[2]
+ + eq[3];
+ if (fabs(c) > 1e-10) {
+ printf("Plane equation check 1 failed by %f\n",c);
+ }
+ c = eq[0] * p2[0]
+ + eq[1] * p2[1]
+ + eq[2] * p2[2]
+ + eq[3];
+ if (fabs(c) > 1e-10) {
+ printf("Plane equation check 2 failed by %f\n",c);
+ }
+ }
+#endif /* NEVER */
+ return 0;
+}
+
+/* Compute the log surface plane equation for the triangle */
+/* and other triangle attributes. (Doesn't depend on edge info.) */
+void
+comptriattr(
+gamut *s,
+gtri *t
+) {
+ int j;
+ static double v0[3] = {0.0, 0.0, 0.0};
+ double cp[3]; /* Closest point - not used */
+
+ /* Compute the plane equation for the absolute triangle. */
+ /* This is used for testing if a point is inside the gamut hull. */
+ plane_equation(t->pe, t->v[0]->p, t->v[1]->p, t->v[2]->p);
+
+ /* Compute the plane equation for the triangle */
+ /* based on the log compressed convex hull verticies. */
+ /* This is used for convex hull construction. */
+ plane_equation(t->che, t->v[0]->ch, t->v[1]->ch, t->v[2]->ch);
+
+ /* Compute the plane equation for the triangle */
+ /* mapped to the surface of the sphere */
+ /* This can be used for point in triangle testing ?? */
+ plane_equation(t->spe, t->v[0]->sp, t->v[1]->sp, t->v[2]->sp);
+
+ /* Compute the plane equations of the spherical mapped vertex */
+ /* values with regard to the center of the sphere, so that */
+ /* a point in triangle test can be performed, and baricentric, */
+ /* coordinates can be computed. */
+ plane_equation(t->ee[0], v0, t->v[1]->sp, t->v[2]->sp);
+ plane_equation(t->ee[1], v0, t->v[2]->sp, t->v[0]->sp);
+ plane_equation(t->ee[2], v0, t->v[0]->sp, t->v[1]->sp);
+
+ /* Compute the radius range of the triangle to the center */
+ /* Compute the maximum from the vertexes */
+ t->rs1 = -1.0;
+ for (j = 0; j < 3; j++) {
+ int k;
+ double rs, tt;
+ for (rs = 0.0, k = 0;k < 3; k++) {
+ tt = t->v[j]->p[k] - s->cent[k];
+ rs += tt * tt;
+ }
+ if (rs > t->rs1)
+ t->rs1 = rs;
+ }
+ /* The minimum may be on the plane, an edge or a vertex, */
+ /* so use closest point in triangle function. */
+ t->rs0 = ne_point_on_tri(s, t, cp, s->cent);
+
+ /* Allow a tollerance around the radius squareds */
+ t->rs0 -= 1e-4;
+ t->rs1 += 1e-4;
+
+#ifdef NEVER // ???
+#ifdef ASSERTS
+ {
+ double tt[3]; /* Triangle test point */
+ double ds;
+ for (j = 0; j < 3; j++) {
+ tt[j] = (t->v[0]->p[j] + t->v[1]->p[j] + t->v[2]->p[j])/3.0;
+ tt[j] -= s->cent[j]; /* Make it center relative */
+ }
+ for (j = 0; j < 3; j++) {
+ ds = t->ee[j][0] * tt[0] /* Point we know is inside */
+ + t->ee[j][1] * tt[1]
+ + t->ee[j][2] * tt[2]
+ + t->ee[j][3];
+ if (ds > 1e-8)
+ break; /* Not within triangle */
+ }
+ if (j < 3) {
+ fprintf(stderr,"Assert: point expected to be within triangle %d (vx %d %d %d) is not\n",
+ t->n, t->v[0]->n, t->v[1]->n, t->v[2]->n);
+ fprintf(stderr,"Known point is %f, expect -ve\n",ds);
+ exit(-1);
+ }
+ }
+#endif /* ASSERTS */
+#endif /* NEVER */
+
+}
+
+/* By using the pow() or log() of the radial distance, */
+/* blended with a sphere surface, we try and strike a compromise */
+/* between a pure convex hull surface, and a pure Delaunay triangulation */
+/* the latter which would show dings and nicks from points */
+/* that fall withing the "real" gamut. */
+static double log_scale(gamut *s, double rr) {
+ double aa;
+
+#ifdef TEST_CONVEX_HULL
+ return rr;
+#else
+#ifdef NEVER /* (Not using this version, doesn't work reliably) */
+ aa = (2.0 + rr)/3.0; /* Blend with sphere */
+ aa = log(aa); /* Allow for concave slope */
+ if (aa < 0.0) /* but constrain to be +ve */
+ aa = 0.0;
+#else /* (Using simpler version) */
+ aa = 20.0 * pow(rr, s->logpow); /* Default 0.25 */
+#endif
+#endif /* TEST_CONVEX_HULL */
+
+ return aa;
+}
+
+/* Comput r[], lr0, sp[] and ch[] from p[] for all verticies */
+/* (Note that lr0 will be the first cut, log_scale() value) */
+static void
+compute_vertex_coords(
+gamut *s
+) {
+ int i, j;
+
+ for (i = 0; i < s->nv; i++) {
+ gamut_rect2radial(s, s->verts[i]->r, s->verts[i]->p);
+
+ if (s->verts[i]->r[0] < 1e-6) { /* Ignore a point right at the center */
+ s->verts[i]->lr0 = 0.0;
+ for (j = 0; j < 3; j++) {
+ s->verts[i]->sp[j] = 0.0;
+ s->verts[i]->ch[j] = 0.0;
+ }
+ } else {
+ double aa;
+
+ /* Figure log scaled radius */
+ s->verts[i]->lr0 = log_scale(s, s->verts[i]->r[0]);
+
+ /* Compute unit shere mapped location */
+ aa = 1.0/s->verts[i]->r[0]; /* Adjustment to put in on unit sphere */
+ for (j = 0; j < 3; j++)
+ s->verts[i]->sp[j] = (s->verts[i]->p[j] - s->cent[j]) * aa;
+
+ /* Compute hull testing mapped version */
+ for (j = 0; j < 3; j++)
+ s->verts[i]->ch[j] = s->verts[i]->p[j] * s->verts[i]->lr0;
+ }
+ }
+}
+
+/* Sort the verticies from maximum radius */
+static void sort_verticies(
+gamut *s
+) {
+ int i;
+
+#ifndef TEST_NOSORT
+
+ /* Sort them */
+//#define HEAP_COMPARE(A,B) (A->r[0] > B->r[0])
+#define HEAP_COMPARE(A,B) (A->lr0 > B->lr0)
+ HEAPSORT(gvert *, s->verts, s->nv)
+#undef HEAP_COMPARE
+
+#endif /* !TEST_NOSORT */
+
+ /* Renumber them */
+ for (i = 0; i < s->nv; i++) {
+ s->verts[i]->n = i;
+ }
+}
+
+/* Number just the verticies that have been set, */
+/* and those that have been used in the convex hull */
+static void renumber_verticies(
+gamut *s
+) {
+ int i, j;
+
+ for (j = i = 0; i < s->nv; i++) {
+ if (!(s->verts[i]->f & GVERT_SET))
+ continue;
+
+ s->verts[i]->sn = j;
+ j++;
+ }
+ s->nsv = j;
+
+ for (j = i = 0; i < s->nv; i++) {
+ if (!(s->verts[i]->f & GVERT_TRI))
+ continue;
+
+ s->verts[i]->tn = j;
+ j++;
+ }
+ s->ntv = j;
+}
+
+#ifdef ASSERTS
+
+/* Diagnpostic aid */
+/* Check that the triangulation adjacenty info is OK */
+static void check_triangulation(gamut *s, int final) {
+ int i, j;
+ gtri *tp; /* Triangle pointer */
+ gedge *ep; /* Edge pointer */
+ int failed = 0;
+
+ tp = s->tris;
+ FOR_ALL_ITEMS(gtri, tp) {
+
+ /* Check verticies for duplication */
+ for (i = 0; i < 3; i++) {
+ for (j = i+1; j < 3; j++) {
+ if (tp->v[i] == tp->v[j]) {
+ failed = 1;
+ printf("Validation failed - duplicate verticies:\n");
+ printf("Triangle %d, has verticies %d %d %d\n", tp->n, tp->v[0]->n, tp->v[1]->n, tp->v[2]->n);
+ fflush(stdout);
+ }
+ }
+ }
+
+ /* Check edges for duplication */
+ for (i = 0; i < 3; i++) {
+ for (j = i+1; j < 3; j++) {
+ if (tp->e[i] == tp->e[j]) {
+ failed = 1;
+ printf("Validation failed - duplicate connectivity:\n");
+ printf("Triangle %d, has verticies %d %d %d\n", tp->n, tp->v[0]->n, tp->v[1]->n, tp->v[2]->n);
+ printf("Triangle %d, has edges %d %d %d\n", tp->n, tp->e[0]->n, tp->e[1]->n, tp->e[2]->n);
+ fflush(stdout);
+ }
+ }
+ }
+
+ /* Check connectivity */
+ for (i = 0; i < 3; i++) {
+ gtri *t1, *t2;
+ gedge *e;
+ int ei1, ei2;
+ int tei; /* Edges index for this triangle [0..1] */
+
+ e = tp->e[i]; /* The edge in question */
+ tei = tp->ei[i];
+ ei1 = e->ti[tei]; /* Edges record of edge index withing this triangle */
+
+ /* Check that the edges reconing of what index edge it is */
+ /* for this triangle is correct */
+ if (ei1 != i) {
+ failed = 1;
+ printf("Validation failed - triangle edge index doesn't match record withing edge:\n");
+ printf("Triangle %d, edge index %d edge %d has record %d\n", tp->n, i, e->n, ei1);
+ fflush(stdout);
+ }
+
+ /* Check that the edges pointer to the triangle is this triangle */
+ if (tp != e->t[tei]) {
+ failed = 1;
+ printf("Validation failed - edge doesn't point back to triangle:\n");
+ printf("Triangle %d, edge index %d is edge %d\n",tp->n, i, e->n);
+ printf("Edge %d, triangle index %d is triangle %d\n", e->n, tei, e->t[tei]->n);
+ printf("Edge %d, triangle index %d is triangle %d\n", e->n, tei^1, e->t[tei^1]->n);
+ fflush(stdout);
+ }
+
+ /* Check the verticies for this edge match edge record */
+ if ((e->v[0] != tp->v[i] || e->v[1] != tp->v[(i+1) % 3])
+ && (e->v[1] != tp->v[i] || e->v[0] != tp->v[(i+1) % 3])) {
+ failed = 1;
+ printf("Validation failed - edge doesn't have same verticies as triangle expects:\n");
+ printf("Triangle %d, has verticies %d %d\n", tp->n, tp->v[i]->n, tp->v[(i+1) % 3]->n);
+ printf("Edge %d, has verticies %d %d\n", e->n, e->v[0]->n, e->v[1]->n);
+ fflush(stdout);
+ }
+
+ t2 = e->t[tei ^ 1]; /* The other triangle */
+ ei2 = e->ti[tei ^ 1]; /* Edges index number withing triangle t2 */
+
+ if (t2 == tp) {
+ failed = 1;
+ printf("Validation failed - connects to itself:\n");
+ printf("Triangle %d, has edges %d %d %d\n", tp->n, tp->e[0]->n, tp->e[1]->n, tp->e[2]->n);
+ fflush(stdout);
+ }
+
+ /* Check that the connection is reflective */
+ if (e != t2->e[ei2]) {
+ failed = 1;
+ printf("Validation failed - connectivity not reflected:\n");
+ printf("Triangle %d, edge index %d points to edge %d\n",tp->n, i, e->n);
+ printf("Triangle %d, edge index %d points to edge %d\n",t2->n, ei2, t2->e[ei2]->n);
+ fflush(stdout);
+ }
+ }
+ } END_FOR_ALL_ITEMS(tp);
+ if (failed) {
+ exit(-1);
+ }
+
+ /* - - - - - - - - - - - - - - - - - - - - - - - - - - - */
+ /* Check that every point is part of a triangle and edge */
+
+ for (i = 0; i < s->nv; i++) { /* Reset the assert flag */
+ gvert *v = s->verts[i];
+
+ v->as = 0;
+
+ /* Check out the flags */
+ if (!(v->f & GVERT_SET)) {
+ if ((v->f & GVERT_TRI)
+ || (v->f & GVERT_INSIDE)) {
+ printf("Validation failed - vertex %d has strange flags 0x%x\n",i, v->f);
+ fflush(stdout);
+ failed = 1;
+ }
+ } else {
+ if ((v->f & GVERT_TRI) && (v->f & GVERT_INSIDE)) {
+ printf("Validation failed - vertex %d has strange flags 0x%x\n",i, v->f);
+ fflush(stdout);
+ failed = 1;
+ }
+ }
+ }
+
+ FOR_ALL_ITEMS(gtri, tp) {
+ for (i = 0; i < 3; i++)
+ tp->v[i]->as |= 1; /* Vertex is in a triangle */
+ } END_FOR_ALL_ITEMS(tp);
+
+ ep = s->edges;
+ FOR_ALL_ITEMS(gedge, ep) {
+ ep->v[0]->as |= 2; /* Vertex is in an edge */
+ ep->v[1]->as |= 2;
+ ep->as = 0; /* Reset the assert flag */
+ } END_FOR_ALL_ITEMS(ep);
+
+ for (i = 0; i < s->nv; i++) {
+ if (s->verts[i]->f & GVERT_TRI) {
+ if ((s->verts[i]->as & 1) == 0) {
+ printf("Validation failed - vertex %d is not in any triangles\n",i);
+ fflush(stdout);
+ failed = 1;
+ }
+ if ((s->verts[i]->as & 2) == 0) {
+ printf("Validation failed - vertex %d is not in any edge\n",i);
+ fflush(stdout);
+ failed = 1;
+ }
+ }
+ }
+
+
+ /* - - - - - - - - - - - - - - - - - - - - - - */
+ /* Check that every edge is part of a triangle */
+
+ /* as flag in triangle was reset above */
+ FOR_ALL_ITEMS(gtri, tp) {
+ for (i = 0; i < 3; i++)
+ tp->e[i]->as |= 1; /* Mark edge used in triangle */
+ } END_FOR_ALL_ITEMS(tp);
+
+ ep = s->edges;
+ FOR_ALL_ITEMS(gedge, ep) {
+ if (ep->as != 1) {
+ printf("Validation failed - edge %d is not in any triangle\n",ep->n);
+ fflush(stdout);
+ failed = 1;
+ }
+ } END_FOR_ALL_ITEMS(ep);
+
+ if (failed) {
+ exit(-1);
+ }
+}
+
+#endif /* ASSERTS */
+
+/* -------------------------------------- */
+/* Add a face to the hit list, if it is not a duplicate. */
+static void add_to_hit_list(
+gamut *s,
+gtri **hlp, /* Hit list */
+gtri *cf /* Face to be added (triangle verts 0, 1) */
+) {
+ gtri *tp; /* Triangle pointer */
+ gvert *c0 = cf->v[0];
+ gvert *c1 = cf->v[1];
+
+//printf("Adding face to hit list %d: %d %d\n",
+//cf->n, cf->v[0]->n, cf->v[1]->n);
+
+ tp = *hlp;
+ /* Search current faces in hit list */
+ FOR_ALL_ITEMS(gtri, tp) {
+ gvert *v0 = tp->v[0];
+ gvert *v1 = tp->v[1];
+ if ((c0 == v0 && c1 == v1) /* Same face from other side */
+ || (c0 == v1 && c1 == v0)) {
+ /* Duplicate found */
+//printf("Duplicate found %d: %d %d\n",
+//tp->n, tp->v[0]->n, tp->v[1]->n);
+ DEL_LINK(*hlp, tp); /* Delete from the hit list */
+
+ /* Check face is common */
+ if (cf->e[0] != tp->e[0]) {
+ fprintf(stderr,"gamut: internal error - face match inconsistency\n");
+ exit(-1);
+ }
+ /* Delete edge */
+ DEL_LINK(s->edges, cf->e[0]);
+ del_gedge(cf->e[0]);
+
+ /* Delete the two faces */
+ del_gtri(tp);
+ del_gtri(cf);
+ return;
+ }
+ } END_FOR_ALL_ITEMS(tp);
+
+ /* Safe to add it to face hit list */
+ /* This removes triangle from triangles list ? */
+ ADD_ITEM_TO_BOT(*hlp, cf);
+//printf("Face added\n");
+}
+
+/* Add a triangles faces to the hit list. */
+static void add_tri_to_hit_list(
+gamut *s,
+gtri **hlp, /* Hit list */
+gtri *tp /* Triangle faces to be added */
+) {
+ int j;
+ gtri *t1, *t2;
+
+ /* In case some verticies disapear below the log surface, */
+ /* and don't remain part of the triangulation, we mark them off. */
+ for (j = 0; j < 3 ; j++) {
+ tp->v[j]->f &= ~GVERT_TRI;
+ tp->v[j]->f |= GVERT_INSIDE;
+ }
+
+ /* Decompose the triangle into three faces, each face being stored */
+ /* into a triangle created on the hit list, using verticices 0, 1. */
+ /* The edges adjacency info remains valid for the three faces, */
+ /* as does the edge plane equation. */
+ DEL_LINK(s->tris, tp); /* Delete it from the triangulation list */
+ t1 = new_gtri();
+ t1->v[0] = tp->v[1]; /* Duplicate with rotated faces */
+ t1->v[1] = tp->v[2];
+ t1->e[0] = tp->e[1]; /* Edge adjacency for this edge */
+ t1->ei[0] = tp->ei[1]; /* Edge index of this triangle */
+ t1->e[0]->t[t1->ei[0]] = t1; /* Fixup reverse adjacency for valid edge */
+ t1->e[0]->ti[t1->ei[0]] = 0; /* Rotated index of new triangles edge */
+ t1->e[1] = t1->e[2] = NULL; /* be safe */
+ for (j = 0; j < 4; j++) /* Copy edge plane equation */
+ t1->ee[2][j] = tp->ee[0][j];
+
+ t2 = new_gtri();
+ t2->v[0] = tp->v[2]; /* Duplicate with rotated faces */
+ t2->v[1] = tp->v[0];
+ t2->e[0] = tp->e[2]; /* Edge adjacency for this edge */
+ t2->ei[0] = tp->ei[2]; /* Edge index of this triangle */
+ t2->e[0]->t[t2->ei[0]] = t2; /* Fixup reverse adjacency for valid edge */
+ t2->e[0]->ti[t2->ei[0]] = 0; /* Rotated index of new triangles edge */
+ t2->e[1] = t2->e[2] = NULL; /* be safe */
+ for (j = 0; j < 4; j++) /* Copy edge plane equation */
+ t2->ee[2][j] = tp->ee[1][j];
+
+ tp->e[1] = tp->e[2] = NULL; /* be safe */
+ add_to_hit_list(s, hlp, tp); /* Add edge 0 to hit list as is */
+ add_to_hit_list(s, hlp, t1); /* Add edge 1 to hit list */
+ add_to_hit_list(s, hlp, t2); /* Add edge 2 to hit list */
+}
+
+#ifdef DEBUG_TRIANG
+ typedef struct {
+ int tix[3]; /* Triangle indexes */
+ int type; /* 0 = hit, 1 = added */
+ } tidxs;
+#endif
+
+/* Insert a vertex into the triangulation */
+static void insert_vertex(
+gamut *s,
+gvert *v /* Vertex to insert */
+) {
+ gtri *tp, *tp2; /* Triangle pointers */
+ gtri *hl; /* Triangle face hit list (polygon faces) */
+ double tol = TRIANG_TOL;
+ int hit = 0; /* Vertex expands hull flag */
+#ifdef DEBUG_TRIANG
+ int intri = 0; /* Vertex landed in a triangle */
+ XLIST(tidxs, hittris)
+ tidxs xxs;
+
+ XLIST_INIT(tidxs, &hittris);
+#endif
+
+#ifdef DEBUG_TRIANG
+ printf("Adding vertex %d: %f %f %f to triangles\n", v->n, v->p[0], v->p[1], v->p[2]);
+#endif
+
+ /* First we search the current triangles, and convert */
+ /* any trianges that are visible from the new point, */
+ /* into a list of faces stored on the face */
+ /* hit list. */
+ /* We are using a brute force search, which will make the */
+ /* algorithm speed proportional to n^2. For better performance */
+ /* with a large number of verticies, an acceleration structure */
+ /* should be used to speed circumradius hit detection. */
+ v->f &= ~GVERT_INSIDE; /* Reset flags */
+ v->f &= ~GVERT_TRI;
+ INIT_LIST(hl);
+ hit = 0;
+ tp = s->tris;
+ FOR_ALL_ITEMS(gtri, tp) {
+ double c;
+
+ /* Check the depth out compared to this triangle log plane equation */
+ c = tp->che[0] * v->ch[0]
+ + tp->che[1] * v->ch[1]
+ + tp->che[2] * v->ch[2]
+ + tp->che[3];
+
+ /* If vertex is above the log hull surface, add triangle to the hit list. */
+ if (c < -tol) {
+#ifdef DEBUG_TRIANG
+ int j;
+ double bds = -1e10;
+#endif
+ hit = 1;
+
+#ifdef DEBUG_TRIANG
+ printf("Got a hit on triangle %d: %d %d %d by %f\n",
+ tp->n, tp->v[0]->n, tp->v[1]->n, tp->v[2]->n,c);
+#endif
+
+#ifdef DEBUG_TRIANG
+ for (j = 0; j < 3; j++) {
+ double ds;
+ ds = tp->ee[j][0] * v->ch[0]
+ + tp->ee[j][1] * v->ch[1]
+ + tp->ee[j][2] * v->ch[2]
+ + tp->ee[j][3];
+ if (ds > tol) {
+ printf("Vertex is not in triangle by %e\n",ds);
+ break;
+ }
+ if (ds > bds)
+ bds = ds;
+ }
+ if (j >= 3) {
+ printf("Vertex is in triangle by %e\n",bds);
+ intri = 1; /* Landed in this triangle */
+ }
+
+ xxs.tix[0] = tp->v[0]->n, xxs.tix[1] = tp->v[1]->n, xxs.tix[2] = tp->v[2]->n;
+ xxs.type = 0;
+ XLIST_ADD(&hittris, xxs)
+#endif
+ add_tri_to_hit_list(s, &hl, tp);
+ }
+ } END_FOR_ALL_ITEMS(tp);
+
+ if (hit == 0) {
+
+//printf("No hits - must be inside the log hull\n");
+ v->f |= GVERT_INSIDE; /* This point is inside the log hull */
+ v->f &= ~GVERT_TRI;
+ } else {
+ int changed = 1;
+
+#ifdef DEBUG_TRIANG
+ /* Point doesn't lie radially within any of the triangles it is */
+ /* above the plane of. This is a geometric conundrum. (?) */
+if (!intri) printf("~1 ###### vertex didn't land in any triangle! ########\n");
+#endif
+
+//printf("Checking out hit polygon edges:\n");
+ /* Now we must make a pass though the hit list, checking that each */
+ /* hit list face will make a correctly oriented, non-sliver triangle */
+ /* when joined to the vertex. */
+ /* Do this check until there are no changes */
+ for (;changed != 0 ;) {
+ tp = hl;
+ changed = 0;
+ FOR_ALL_ITEMS(gtri, tp) {
+ /* Check which side of the edge our vertex is */
+ double ds;
+ ds = tp->ee[2][0] * v->ch[0]
+ + tp->ee[2][1] * v->ch[1]
+ + tp->ee[2][2] * v->ch[2]
+ + tp->ee[2][3];
+//printf("Vertex margin to edge = %e\n",ds);
+ /* If vertex is not to the right of this edge by tol */
+ /* add associated triangle to the hit list. */
+ if (ds > -tol) {
+ gtri *xtp;
+//printf("~1 ###### vertex on wrong side by %e - expand hit list ######\n",ds);
+ if (tp->e[0]->t[0] != tp)
+ xtp = tp->e[0]->t[0];
+ else
+ xtp = tp->e[0]->t[1];
+//printf("Got a hit on triangle %d: %d %d %d\n", xtp->n, xtp->v[0]->n, xtp->v[1]->n, xtp->v[2]->n);
+
+#ifdef DEBUG_TRIANG
+ xxs.tix[0] = xtp->v[0]->n, xxs.tix[1] = xtp->v[1]->n, xxs.tix[2] = xtp->v[2]->n;
+ xxs.type = 1;
+ XLIST_ADD(&hittris, xxs)
+#endif
+
+ add_tri_to_hit_list(s, &hl, xtp);
+ changed = 1;
+ break;
+ }
+ } END_FOR_ALL_ITEMS(tp);
+ }
+
+#ifdef DEBUG_TRIANG_VRML
+ write_diag_vrml(s, v->ch, hittris.no, hittris.list, hl);
+#endif
+
+//printf("About to turn polygon faces into triangles\n");
+ /* Turn all the faces that made it to the */
+ /* hit list, into triangles using the new vertex. */
+ tp = hl;
+ FOR_ALL_ITEMS(gtri, tp) {
+ tp->v[2] = v; /* Add third vertex to face to make triangle */
+ comptriattr(s, tp); /* Compute triangle attributes */
+
+ /* Find the new adjacent triangles from the triangles being formed, */
+ /* to maintain edge adjacency information. */
+ /* Do only one edge at a time, since each adjacency */
+ /* will be visited twice. */
+ tp2 = hl;
+ FOR_ALL_ITEMS(gtri, tp2) {
+ if (tp2->v[0] == tp->v[1]) { /* Found 1/2 tp/tp2 edge adjacency */
+ gedge *e;
+ e = new_gedge();
+ ADD_ITEM_TO_BOT(s->edges, e); /* Append to edge list */
+ tp->e[1] = e; /* Point to edge */
+ tp->ei[1] = 0; /* edges 0th triangle */
+ e->t[0] = tp; /* triangles 1st edge */
+ e->ti[0] = 1; /* triangles 1st edge */
+ tp2->e[2] = e; /* Point to edge */
+ tp2->ei[2] = 1; /* edges 1st triangle */
+ e->t[1] = tp2; /* Triangles 2nd edge */
+ e->ti[1] = 2; /* Triangles 2nd edge */
+ e->v[0] = v; /* Add the two verticies */
+ e->v[1] = tp->v[1];
+ }
+ } END_FOR_ALL_ITEMS(tp2);
+
+//printf("~1 Creating new triangle %d: %d %d %d\n", tp->n, tp->v[0]->n, tp->v[1]->n, tp->v[2]->n);
+ } END_FOR_ALL_ITEMS(tp);
+
+#ifdef DEBUG_TRIANG_VRML
+ tp = hl;
+ hittris.no = 0;
+ FOR_ALL_ITEMS(gtri, tp) {
+ xxs.tix[0] = tp->v[0]->n, xxs.tix[1] = tp->v[1]->n, xxs.tix[2] = tp->v[2]->n;
+ xxs.type = 2;
+ XLIST_ADD(&hittris, xxs)
+ } END_FOR_ALL_ITEMS(tp);
+ write_diag_vrml(s, v->ch, hittris.no, hittris.list, NULL);
+#ifdef DEBUG_TRIANG_VRML_STEP
+#ifdef DO_TWOPASS
+ if (s->pass > 0)
+#endif /* DO_TWOPASS */
+ getchar();
+#endif
+#endif
+
+ /* Move them to the triangulation. */
+ tp = hl;
+ FOR_ALL_ITEMS(gtri, tp) {
+ int j;
+ DEL_LINK(hl, tp); /* Gone from the hit list */
+ ADD_ITEM_TO_BOT(s->tris, tp); /* Append to triangulation list */
+ for (j = 0; j < 3 ; j++) { /* Verticies weren't dropped from triangulation */
+ tp->v[j]->f |= GVERT_TRI;
+ tp->v[j]->f &= ~GVERT_INSIDE;
+ }
+ } END_FOR_ALL_ITEMS(tp);
+
+ v->f |= GVERT_TRI; /* This vertex has been added to triangulation */
+ v->f &= ~GVERT_INSIDE; /* and it's not inside */
+ }
+
+#ifdef DEBUG_TRIANG
+ XLIST_FREE(&hittris);
+#endif
+}
+
+/* - - - - - - - - - - - - - - - - */
+
+/* Create the convex hull surface triangulation */
+static void triangulate_ch(
+gamut *s
+) {
+ /* Establish the base triangulation */
+ {
+ int i, j;
+ gvert *tvs[4]; /* Initial verticies */
+ gtri *tr[4]; /* Initial triangles */
+ gedge *ed[6]; /* Initial edges */
+ double fsz = FAKE_SEED_SIZE; /* Initial tetra size */
+ double ff[3];
+ int onf;
+ static double foffs[4][3] = { /* tetrahedral offsets */
+ { 0.0, 0.0, 1.0 },
+ { 0.0, 0.80254, -0.5 },
+ { 0.75, -0.330127, -0.5 },
+ { -0.75, -0.330127, -0.5 }
+ };
+
+ /* Delete any current fake verticies */
+ for (j = i = 0; i < s->nv; i++) {
+ s->verts[i]->f &= ~GVERT_ESTP; /* Unmark non-fake establishment points */
+ if (!(s->verts[i]->f & GVERT_FAKE))
+ s->verts[j++] = s->verts[i];
+ else
+ free(s->verts[i]);
+ }
+ s->nv = j;
+
+ /* Re-create fake points on each pass */
+ onf = s->nofilter;
+ s->nofilter = 1; /* Turn off filtering */
+ s->doingfake = 1; /* Adding fake points */
+
+ for (j = i = 0; i < 4; i++) {
+ ff[0] = fsz * foffs[i][2] + s->cent[0];
+ ff[1] = fsz * foffs[i][0] + s->cent[1];
+ ff[2] = fsz * foffs[i][1] + s->cent[2];
+ if ((tvs[j++] = expand_gamut(s, ff)) == NULL) {
+ fprintf(stderr,"gamut: internal error - failed to register a fake initial verticies!\n");
+ exit (-1);
+ }
+ }
+
+ s->nofilter = onf;
+ s->doingfake = 0;
+
+#ifdef NEVER
+ printf("Initial verticies:\n");
+ for (i = 0; i < 4; i++) {
+ printf(" %d: %f %f %f\n",tvs[i]->n, tvs[i]->p[0], tvs[i]->p[1], tvs[i]->p[2]);
+ }
+#endif
+ /* Setup the initial triangulation */
+ for (i = 0; i < 4; i++) {
+ tr[i] = new_gtri();
+ }
+
+ for (i = 0; i < 6; i++) {
+ ed[i] = new_gedge();
+ ADD_ITEM_TO_BOT(s->edges, ed[i]);
+ }
+
+ /* Enter the edge verticies */
+ ed[0]->v[0] = tvs[0];
+ ed[0]->v[1] = tvs[1];
+ ed[1]->v[0] = tvs[1];
+ ed[1]->v[1] = tvs[2];
+ ed[2]->v[0] = tvs[0];
+ ed[2]->v[1] = tvs[2];
+ ed[3]->v[0] = tvs[0];
+ ed[3]->v[1] = tvs[3];
+ ed[4]->v[0] = tvs[1];
+ ed[4]->v[1] = tvs[3];
+ ed[5]->v[0] = tvs[2];
+ ed[5]->v[1] = tvs[3];
+
+ /* Triangle facing in the +x, +y +z direction */
+ tr[0]->v[0] = tvs[0];
+ tr[0]->v[1] = tvs[1];
+ tr[0]->v[2] = tvs[2];
+
+ tr[0]->e[0] = ed[0]; /* Should make edge joining a function ? */
+ tr[0]->ei[0] = 0;
+ ed[0]->t[0] = tr[0];
+ ed[0]->ti[0] = 0;
+
+ tr[0]->e[1] = ed[1];
+ tr[0]->ei[1] = 0;
+ ed[1]->t[0] = tr[0];
+ ed[1]->ti[0] = 1;
+
+ tr[0]->e[2] = ed[2];
+ tr[0]->ei[2] = 0;
+ ed[2]->t[0] = tr[0];
+ ed[2]->ti[0] = 2;
+
+ comptriattr(s, tr[0]); /* Compute triangle attributes */
+ ADD_ITEM_TO_BOT(s->tris, tr[0]); /* Append to list */
+
+ /* Triangle facing in the -x, +y +z direction */
+ tr[1]->v[0] = tvs[0];
+ tr[1]->v[1] = tvs[3];
+ tr[1]->v[2] = tvs[1];
+
+ tr[1]->e[0] = ed[3];
+ tr[1]->ei[0] = 0;
+ ed[3]->t[0] = tr[1];
+ ed[3]->ti[0] = 0;
+
+ tr[1]->e[1] = ed[4];
+ tr[1]->ei[1] = 0;
+ ed[4]->t[0] = tr[1];
+ ed[4]->ti[0] = 1;
+
+ tr[1]->e[2] = ed[0];
+ tr[1]->ei[2] = 1;
+ ed[0]->t[1] = tr[1];
+ ed[0]->ti[1] = 2;
+
+ comptriattr(s, tr[1]); /* Compute triangle attributes */
+ ADD_ITEM_TO_BOT(s->tris, tr[1]); /* Append to list */
+
+ /* Triangle facing in the -y +z direction */
+ tr[2]->v[0] = tvs[0];
+ tr[2]->v[1] = tvs[2];
+ tr[2]->v[2] = tvs[3];
+
+ tr[2]->e[0] = ed[2];
+ tr[2]->ei[0] = 1;
+ ed[2]->t[1] = tr[2];
+ ed[2]->ti[1] = 0;
+
+ tr[2]->e[1] = ed[5];
+ tr[2]->ei[1] = 0;
+ ed[5]->t[0] = tr[2];
+ ed[5]->ti[0] = 1;
+
+ tr[2]->e[2] = ed[3];
+ tr[2]->ei[2] = 1;
+ ed[3]->t[1] = tr[2];
+ ed[3]->ti[1] = 2;
+
+ comptriattr(s, tr[2]); /* Compute triangle attributes */
+ ADD_ITEM_TO_BOT(s->tris, tr[2]); /* Append to list */
+
+ /* Triangle facing in the -z direction */
+ tr[3]->v[0] = tvs[1];
+ tr[3]->v[1] = tvs[3];
+ tr[3]->v[2] = tvs[2];
+
+ tr[3]->e[0] = ed[4];
+ tr[3]->ei[0] = 1;
+ ed[4]->t[1] = tr[3];
+ ed[4]->ti[1] = 0;
+
+ tr[3]->e[1] = ed[5];
+ tr[3]->ei[1] = 1;
+ ed[5]->t[1] = tr[3];
+ ed[5]->ti[1] = 1;
+
+ tr[3]->e[2] = ed[1];
+ tr[3]->ei[2] = 1;
+ ed[1]->t[1] = tr[3];
+ ed[1]->ti[1] = 2;
+
+ comptriattr(s, tr[3]); /* Compute triangle attributes */
+ ADD_ITEM_TO_BOT(s->tris, tr[3]); /* Append to list */
+
+ /* The four used verticies are now part of the triangulation */
+ for (i = 0; i < 4; i++) {
+ tvs[i]->f |= GVERT_TRI;
+//printf("Base triangle %d: %d %d %d (Verticies 0x%x, 0x%x, 0x%x, 0x%x)\n", tr[i]->n, tr[i]->v[0]->n, tr[i]->v[1]->n, tr[i]->v[2]->n, tr[i]->v[0], tr[i]->v[1], tr[i]->v[2]);
+ }
+#ifdef ASSERTS
+ check_triangulation(s, 0);
+#endif
+ }
+
+ /* Sort the verticies from maximum radius, */
+ /* to make our log convex hull logic work */
+ sort_verticies(s);
+
+ {
+ int i;
+ /* Complete the triangulation by adding all the remaining verticies */
+ /* in order of decreasing radius, so that those below the log */
+ /* convex hull get discarded. */
+ for (i = 0; i < s->nv; i++) {
+ if (!(s->verts[i]->f & GVERT_SET)
+ || (s->verts[i]->f & GVERT_TRI)
+ || (s->verts[i]->f & GVERT_INSIDE)) {
+ continue;
+ }
+
+ insert_vertex(s, s->verts[i]);
+#ifdef ASSERTS
+ check_triangulation(s, 0);
+#endif
+ }
+ }
+
+ /* Number the used verticies */
+ renumber_verticies(s);
+
+#ifdef ASSERTS
+ check_triangulation(s, 1);
+#endif
+}
+
+/* - - - - - - - - - - - - - - - - - - - - - - - - - */
+/* Compute a new convex hull mapping radius for the sample points, */
+/* on the basis of the initial mapping. */
+static void compute_smchrad(
+gamut *s
+) {
+ int i, j;
+ double zz[3] = { 0.0, 0.0, 1.0 };
+ double rot[3][3];
+ double ssr = 0.5 * s->sres; /* Sample size radius in delta E */
+// double ssr = 10.0;
+ int res = 4; /* resolution of sample grid */
+
+//printf("~1 computing smoothed chrads\n");
+
+ /* Compute the new log surface value */
+ for (i = 0; i < s->nv; i++) {
+ double pr; /* Current surface radius at this vertex */
+ double rad, rw; /* Smoothed radius, weight */
+ double out[3]; /* Point on surface for this vertex */
+ int x, y;
+
+ if (!(s->verts[i]->f & GVERT_SET)) {
+ continue;
+ }
+
+//printf("~1 vertex %d, %f %f %f\n",i, s->verts[i]->p[0], s->verts[i]->p[1], s->verts[i]->p[2]);
+
+ /* Find the average surface level near this point */
+ pr = s->radial(s, out, s->verts[i]->p);
+
+//printf("~1 surface radius %f, location %f %f %f\n",pr, out[0],out[1],out[2]);
+
+ /* Compute a rotation that lines Z up with the radial direction */
+ out[0] -= s->cent[0]; /* Radial vector through point to surface */
+ out[1] -= s->cent[1];
+ out[2] -= s->cent[2];
+ zz[2] = pr; /* Z vector of same length */
+ icmRotMat(rot, zz, out); /* Compute vector from Z to radial */
+ out[0] += s->cent[0];
+ out[1] += s->cent[1];
+ out[2] += s->cent[2];
+ rad = rw = 0.0;
+
+ /* Sample a rectangular array orthogonal to radial vector, */
+ /* and weight samples appropriately */
+ for (x = 0; x < res; x++) {
+ for (y = 0; y < res; y++) {
+ double tt, off[3], rv, in[3];
+
+ off[0] = 2.0 * (x/(res-1.0) - 0.5); /* -1.0 to 1.0 */
+ off[1] = 2.0 * (y/(res-1.0) - 0.5); /* -1.0 to 1.0 */
+ off[2] = 0.0;
+
+ rv = off[0] * off[0] + off[1] * off[1];
+ if (rv > 1.0)
+ continue; /* Outside circle */
+#ifdef NEVER
+ rv = 1.0 - sqrt(rv); /* Radius from center */
+ rv = rv * rv * (3.0 - 2.0 * rv); /* Spline weight it */
+#else
+ rv = 1.0; /* Moving average weighting */
+#endif
+
+ off[0] *= ssr; /* Scale offset to sampling radius */
+ off[1] *= ssr;
+
+ /* Rotate offset to be orthogonal to radial vector */
+ icmMulBy3x3(off, rot, off);
+
+ /* Add offset to surface point over current vertex */
+ in[0] = out[0] + off[0];
+ in[1] = out[1] + off[1];
+ in[2] = out[2] + off[2];
+
+//printf("~1 grid %d %d, weight %f, offset %f %f %f\n",x,y,rv,off[0],off[1],off[2]);
+
+ /* Sum weighted radius at sample point */
+ tt = s->radial(s, NULL, in);
+ tt = log_scale(s, tt);
+ rad += rv * tt;
+ rw += rv;
+
+ }
+ }
+ /* Compute sample filtered radius at the sample point */
+ rad /= rw;
+//printf("~1 sampled radius = %f\n\n",rad);
+
+ /* Now compute new hull mapping radius for this point, */
+ /* based on dividing out the sampled radius */
+
+// s->verts[i]->lr0 = 40.0 + s->verts[i]->lr0 - rad;
+ s->verts[i]->lr0 = 40.0 + log_scale(s, s->verts[i]->r[0]) - rad;
+ /* Prevent silliness */
+ if (s->verts[i]->lr0 < (2.0 * FAKE_SEED_SIZE))
+ s->verts[i]->lr0 = (2.0 * FAKE_SEED_SIZE);
+
+//printf("~1 new lr0 = %f\n\n",s->verts[i]->lr0);
+
+ /* recompute ch[] for new lr0 */
+ for (j = 0; j < 3; j++)
+ s->verts[i]->ch[j] = s->verts[i]->sp[j] * s->verts[i]->lr0;
+ }
+}
+
+/* ===================================================== */
+/* Overall triangulation */
+static void triangulate(
+gamut *s
+) {
+
+ /* Create the convex hull */
+ triangulate_ch(s);
+
+#if defined(DO_TWOPASS) && !defined(TEST_CONVEX_HULL)
+ if (s->no2pass == 0) {
+#ifdef DEBUG_TRIANG
+ printf("############ Starting second pass ###################\n");
+#endif
+ compute_smchrad(s);
+ del_triang(s);
+ s->pass++;
+ triangulate_ch(s);
+
+ /* Three passes is typically slightly better, but slower... */
+// compute_smchrad(s);
+// del_triang(s);
+// s->pass++;
+// triangulate_ch(s);
+ }
+#endif /* DO_TWOPASS && !TEST_CONVEX_HULL */
+}
+
+/* ===================================================== */
+/* Code that makes use of the triangulation */
+/* ===================================================== */
+
+/* return the current surface resolution */
+static double getsres(
+gamut *s
+) {
+ return s->sres;
+}
+
+/* return the isJab flag value */
+static int getisjab(
+gamut *s
+) {
+ return s->isJab;
+}
+
+/* return the isRast flag value */
+static int getisrast(
+gamut *s
+) {
+ return s->isRast;
+}
+
+/* Disable segmented maxima filtering */
+static void setnofilt(gamut *s) {
+ s->nofilter = 1;
+}
+
+/* return the gamut center value */
+static void getcent(gamut *s, double *cent) {
+ cent[0] = s->cent[0];
+ cent[1] = s->cent[1];
+ cent[2] = s->cent[2];
+}
+
+/* Return the gamut min/max range */
+static void getrange(gamut *s, double *min, double *max) {
+
+ if (min != NULL) {
+ min[0] = s->mn[0];
+ min[1] = s->mn[1];
+ min[2] = s->mn[2];
+ }
+ if (max != NULL) {
+ max[0] = s->mx[0];
+ max[1] = s->mx[1];
+ max[2] = s->mx[2];
+ }
+}
+
+/* return nz if the two gamut are compatible */
+static int compatible(
+gamut *s, struct _gamut *t) {
+ int j;
+
+ /* The same colorspace ? */
+ if ((s->isJab && !t->isJab)
+ || (!s->isJab && t->isJab)) {
+ return 0;
+ }
+
+ /* The same gamut center ? */
+ for (j = 0; j < 3; j++) {
+ if (fabs(s->cent[j] - t->cent[j]) > 1e-9) {
+ return 0;
+ }
+ }
+ return 1;
+}
+
+
+/* Return the number of raw verticies used to construct surface */
+static int nrawverts(
+gamut *s
+) {
+ int i, nrv = 0;
+
+ /* Sort them so that triangulate doesn't mess indexing up */
+ sort_verticies(s);
+
+ /* Count them */
+ for (i = 0; i < s->nv; i++) {
+ if (s->verts[i]->f & GVERT_SET)
+ nrv++;
+ }
+
+ return nrv;
+}
+
+/* Return the raw (triangle and non-triangle surface) verticies */
+/* location given its index. */
+/* return the next (sparse) index, or -1 if beyond last */
+static int getrawvert(
+gamut *s,
+double pos[3], /* Return absolute position */
+int ix /* Input index */
+) {
+ if (ix < 0)
+ return -1;
+
+ /* Find then next used in the triangulation */
+ for (; ix < s->nv; ix++) {
+ if (!(s->verts[ix]->f & GVERT_SET))
+ continue;
+ break;
+ }
+
+ if (ix >= s->nv)
+ return -1;
+
+ pos[0] = s->verts[ix]->p[0];
+ pos[1] = s->verts[ix]->p[1];
+ pos[2] = s->verts[ix]->p[2];
+
+ return ix+1;
+}
+
+/* Return the number of raw direction 0 verticies used */
+/* to construct surface. (Direction 0 is radial direction maxima) */
+static int nraw0verts(
+gamut *s
+) {
+ int i, nrv = 0;
+
+ /* Sort them so that triangulate doesn't mess indexing up */
+ sort_verticies(s);
+
+ /* Count them */
+ for (i = 0; i < s->nv; i++) {
+ if ((s->verts[i]->f & GVERT_SET)
+ && (s->verts[i]->k0 > 0))
+ nrv++;
+ }
+
+ return nrv;
+}
+
+/* Return the raw (triangle and non-triangle surface) direction 0 */
+/* verticies location given its index. (Direction 0 is radial direction maxima) */
+/* return the next (sparse) index, or -1 if beyond last */
+static int getraw0vert(
+gamut *s,
+double pos[3], /* Return absolute position */
+int ix /* Input index */
+) {
+ if (ix < 0)
+ return -1;
+
+ /* Find then next used in the triangulation and direction 0 */
+ for (; ix < s->nv; ix++) {
+ if (!(s->verts[ix]->f & GVERT_SET)
+ || !(s->verts[ix]->k0 > 0))
+ continue;
+ break;
+ }
+
+ if (ix >= s->nv)
+ return -1;
+
+ pos[0] = s->verts[ix]->p[0];
+ pos[1] = s->verts[ix]->p[1];
+ pos[2] = s->verts[ix]->p[2];
+
+ return ix+1;
+}
+
+/* Return the number of stratified sampling surface verticies, */
+/* for the given verticies per unit area parameter. */
+static int nssverts(
+gamut *s,
+double xvra /* Extra vertex ratio */
+) {
+
+ if IS_LIST_EMPTY(s->tris)
+ triangulate(s);
+
+//printf("~1 nssverts called with xvra = %f\n",xvra);
+ if (s->xvra != xvra) {
+ int i, j;
+ gtri *tp; /* Triangle pointer */
+ double tarea; /* Total area */
+ double tnverts; /* Target number of verticies */
+ int anverts; /* Actual number of verticies */
+
+ /* Calculate the total surface area of the triangulation */
+ tarea = 0.0;
+ tp = s->tris;
+ FOR_ALL_ITEMS(gtri, tp) {
+ double sp, ss[3]; /* Triangle side lengths */
+ double dp; /* Dot product of point in triangle and normal */
+
+ for (i = 0; i < 3; i++) { /* For each edge */
+ for (ss[i] = 0.0, j = 0; j < 3; j++) {
+ double dd = tp->e[i]->v[1]->p[j] - tp->e[i]->v[0]->p[j];
+ ss[i] += dd * dd;
+ }
+ ss[i] = sqrt(ss[i]);
+ }
+
+ /* semi-perimeter */
+ sp = 0.5 * (ss[0] + ss[1] + ss[2]);
+
+ /* Area of triangle */
+ tp->area = sqrt(sp * (sp - ss[0]) * (sp - ss[1]) * (sp - ss[2]));
+
+ tarea += tp->area;
+ } END_FOR_ALL_ITEMS(tp);
+
+ /* target number of vectors */
+ tnverts = xvra * s->ntv;
+//printf("~1 total area = %f, tnverts = %f\n",tarea, tnverts);
+
+ /* Number that need to be added using stratified sampling */
+ tnverts -= (double)s->ntv;
+ anverts = 0;
+
+ /* Compute number of extra verticies for each triangle */
+ if (tnverts > 0.0) {
+ double exvpua; /* Extra verticies per unit area to create */
+
+ exvpua = tnverts/tarea;
+//printf("~1 extra verts = %f\n",exvpua);
+ tp = s->tris;
+ FOR_ALL_ITEMS(gtri, tp) {
+ tp->ssverts = (int)(exvpua * tp->area + 0.5);
+ anverts += tp->ssverts;
+ } END_FOR_ALL_ITEMS(tp);
+ }
+ anverts += s->ntv;
+ s->xvra = xvra;
+ s->ssnverts = anverts;
+ }
+
+//printf("~1 returning total verts %d\n",s->ssnverts);
+ return s->ssnverts;
+}
+
+/* Return the stratified sampling surface verticies */
+/* location and radius. nssverts() sets vpua */
+static int getssvert(
+gamut *s,
+double *rad, /* Return radial radius */
+double pos[3], /* Return absolute position */
+double norm[3], /* Return normal of triangle it orginates from */
+int ix /* Input index */
+) {
+ int sskip = 0; /* Number of points to skip after each reset of pseudo rand */
+
+//printf("getssvert called\n");
+
+ if (ix < 0)
+ return -1;
+
+ if (ix < s->nv) {
+
+ /* Find then next used vertex in the triangulation */
+ for (; ix < s->nv; ix++) {
+ if (!(s->verts[ix]->f & GVERT_TRI))
+ continue;
+ break;
+ }
+ }
+
+ if (ix < s->nv) { /* returning verticies */
+
+ if (rad != NULL)
+ *rad = s->verts[ix]->r[0];
+ if (pos != NULL) {
+ pos[0] = s->verts[ix]->p[0];
+ pos[1] = s->verts[ix]->p[1];
+ pos[2] = s->verts[ix]->p[2];
+ }
+ if (norm != NULL) {
+ gvert *vp = s->verts[ix];
+ gtri *tp;
+ int i, j, nt = 0;
+ for (j = 0; j < 3; j++)
+ norm[j] = 0.0;
+
+ /* Slow, but search all triangles for this vertex. */
+ /* Return the average normal of all the triangles it is part of */
+ tp = s->tris;
+ FOR_ALL_ITEMS(gtri, tp) {
+ for (i = 0; i < 3; i++) {
+ if (tp->v[i] == vp) {
+ for (j = 0; j < 3; j++)
+ norm[j] += tp->pe[j];
+ nt++;
+ break;
+ }
+ }
+ } END_FOR_ALL_ITEMS(tp);
+ if (nt == 0)
+ error("gamut::getssvert() vertex doesn't have a triangle");
+ for (j = 0; j < 3; j++)
+ norm[j] /= (double)nt;
+ }
+//printf("~1 returning tri vertex %f %f %f\n", pos[0],pos[1],pos[2]);
+
+ } else { /* We're generating ss points for each triangle */
+ int i, j;
+ double tt;
+ double uv[2];
+ double tr[3]; /* Baricentric weighting */
+ double vv[3];
+
+ if (s->ss == NULL) {
+ if ((s->ss = new_sobol(2)) == NULL)
+ error("gamut::getssvert() new_sobol() failed");
+ for (i = 0; i < sskip; i++)
+ s->ss->next(s->ss, uv);
+ }
+ if (ix == s->nv) { /* Start of generating verticies in triangles */
+
+//printf("~1 setting up for scan through triangles\n");
+ s->nexttri = s->tris;
+ if (s->nexttri == NULL)
+ return -1;
+ s->ssvertn = 0;
+ s->ss->reset(s->ss);
+ }
+ if (s->ssvertn >= s->nexttri->ssverts) {
+ do {
+//printf("~1 skipping to next triangle\n");
+ s->nexttri = NEXT_FWD(s->nexttri);
+ if (s->nexttri == s->tris)
+ return -1;
+ } while(s->nexttri->ssverts <= 0);
+ s->ssvertn = 0;
+ s->ss->reset(s->ss);
+ for (i = 0; i < sskip; i++)
+ s->ss->next(s->ss, uv);
+ }
+//printf("~1 generating ss vert %d out of %d\n",s->ssvertn+1,s->nexttri->ssverts);
+ s->ss->next(s->ss, uv);
+
+ tt = sqrt(uv[0]);
+ tr[0] = 1 - tt;
+ tr[1] = uv[1] * tt;
+ tr[2] = 1.0 - tr[0] - tr[1];
+
+ vv[0] = vv[1] = vv[2] = 0.0;
+ for (i = 0; i < 3; i++) {
+ for (j = 0; j < 3; j++)
+ vv[j] += s->nexttri->v[i]->p[j] * tr[i];
+ }
+
+ if (rad != NULL)
+ *rad = icmNorm33(vv, s->cent);
+ if (pos != NULL) {
+ pos[0] = vv[0];
+ pos[1] = vv[1];
+ pos[2] = vv[2];
+ }
+ if (norm != NULL) {
+ norm[0] = s->nexttri->pe[0];
+ norm[1] = s->nexttri->pe[1];
+ norm[2] = s->nexttri->pe[2];
+ }
+ s->ssvertn++;
+//printf("~1 returning ss vertex %f %f %f\n", pos[0],pos[1],pos[2]);
+ }
+
+ return ix+1;
+}
+
+/* Return the number of verticies in the triangulated surface */
+static int nverts(
+gamut *s
+) {
+ if IS_LIST_EMPTY(s->tris)
+ triangulate(s);
+
+ return s->ntv;
+}
+
+/* Return the verticies location and radius given its index. */
+/* return the next (sparse) index, or -1 if beyond last */
+static int getvert(
+gamut *s,
+double *rad, /* Return radial radius */
+double pos[3], /* Return absolute position */
+int ix /* Input index */
+) {
+ if (ix >= s->nv)
+ return -1;
+
+ /* Find then next used in the triangulation */
+ for (; ix < s->nv; ix++) {
+ if (!(s->verts[ix]->f & GVERT_TRI))
+ continue;
+ break;
+ }
+ if (ix >= s->nv)
+ return -1;
+
+ if (rad != NULL)
+ *rad = s->verts[ix]->r[0];
+ if (pos != NULL) {
+ pos[0] = s->verts[ix]->p[0];
+ pos[1] = s->verts[ix]->p[1];
+ pos[2] = s->verts[ix]->p[2];
+ }
+
+ return ix+1;
+}
+
+
+/* Reset indexing through triangles for getnexttri() */
+static void startnexttri(gamut *s) {
+ if IS_LIST_EMPTY(s->tris)
+ triangulate(s);
+
+ s->nexttri = NULL;
+}
+
+/* Return the next surface triange, nz on no more */
+static int getnexttri(
+gamut *s,
+int v[3] /* Return indexes for same order as getvert() */
+) {
+ if IS_LIST_EMPTY(s->tris)
+ triangulate(s);
+
+ if (s->nexttri == NULL) {
+ s->nexttri = s->tris;
+ if (s->nexttri == NULL)
+ return 1;
+ } else {
+ s->nexttri = NEXT_FWD(s->nexttri);
+ if (s->nexttri == s->tris)
+ return 1;
+ }
+
+ v[0] = s->nexttri->v[0]->tn;
+ v[1] = s->nexttri->v[1]->tn;
+ v[2] = s->nexttri->v[2]->tn;
+ return 0;
+}
+
+/* ===================================================== */
+
+/* Return the total volume of the gamut */
+/* [ We use the formula from "Area of planar polygons and */
+/* volume of polyhedra" by Ronald N. Goldman, */
+/* Graphics Gems II, pp 170 ] */
+static double volume(
+gamut *s
+) {
+ int i, j;
+ gtri *tp; /* Triangle pointer */
+ double vol; /* Gamut volume */
+
+ if IS_LIST_EMPTY(s->tris)
+ triangulate(s);
+
+ /* Compute the area of each triangle in the list, */
+ /* and accumulate the gamut volume. */
+ tp = s->tris;
+ vol = 0.0;
+ FOR_ALL_ITEMS(gtri, tp) {
+ double sp, ss[3]; /* Triangle side lengths */
+ double area; /* Area of this triangle */
+ double dp; /* Dot product of point in triangle and normal */
+
+ for (i = 0; i < 3; i++) { /* For each edge */
+ for (ss[i] = 0.0, j = 0; j < 3; j++) {
+ double dd = tp->e[i]->v[1]->p[j] - tp->e[i]->v[0]->p[j];
+ ss[i] += dd * dd;
+ }
+ ss[i] = sqrt(ss[i]);
+ }
+
+ /* semi-perimeter */
+ sp = 0.5 * (ss[0] + ss[1] + ss[2]);
+
+ /* Area of triangle */
+ area = sqrt(sp * (sp - ss[0]) * (sp - ss[1]) * (sp - ss[2]));
+
+ /* Dot product between first vertex in triangle and the unit normal vector */
+ dp = tp->v[0]->p[0] * tp->pe[0]
+ + tp->v[0]->p[1] * tp->pe[1]
+ + tp->v[0]->p[2] * tp->pe[2];
+
+ /* Accumulate gamut volume */
+ vol += dp * area;
+
+ } END_FOR_ALL_ITEMS(tp);
+
+ vol = fabs(vol)/3.0;
+
+ return vol;
+}
+
+/* ===================================================== */
+/* ===================================================== */
+/* Given a point, */
+/* return the distance to the gamut surface. */
+
+static void init_lu(gamut *s);
+static gtri *radial_point_triang(gamut *s, gbsp *np, double in[3]);
+static double radial_point(gamut *s, gbsp *np, double in[3]);
+
+/* Given a point, return the point in that direction */
+/* that lies on the gamut surface. Return the radial */
+/* radius to the surface point */
+/* Brute force search version. */
+static double
+radial_bf(
+gamut *s,
+double *out, /* result point (absolute)*/
+double *in /* input point (absolute)*/
+) {
+ gtri *tp;
+ int j;
+ double ss, rv;
+ double nin[3]; /* Normalised input vector */
+
+//printf("~1 radial called with %f %f %f\n", in[0], in[1], in[2]);
+ if IS_LIST_EMPTY(s->tris)
+ triangulate(s);
+
+ /* Compute vector length to center point */
+ for (ss = 0.0, j = 0; j < 3; j++)
+ ss += (in[j] - s->cent[j]) * (in[j] - s->cent[j]);
+ ss = 1.0/sqrt(ss); /* Normalising factor */
+ for (ss = 0.0, j = 0; j < 3; j++)
+ nin[j] = s->cent[j] + (in[j] - s->cent[j]) * ss;
+
+ tp = s->tris;
+ FOR_ALL_ITEMS(gtri, tp) {
+ if (vect_intersect(s, &rv, out, s->cent, nin, tp)) {
+ if (rv > 0.0) /* Expect only one intersection */
+ break;
+ }
+ } END_FOR_ALL_ITEMS(tp);
+
+//printf("~1 result = %f %f %f\n",out[0], out[1], out[2]);
+
+ return rv;
+}
+
+/* Implementation for following two functions: */
+/* Given a point, return the point in that direction */
+/* that lies on the gamut surface. Use the BSP accellerated search. */
+/* Return the radial length of the input and radial length of result */
+static void
+_radial(
+gamut *s,
+double *ir, /* return input radius (may be NULL) */
+double *or, /* return output radius (may be NULL) */
+double *out, /* result point (absolute) (may be NULL) */
+double *in /* input point (absolute)*/
+) {
+ int j;
+ double ss, rv;
+ double nin[3]; /* Normalised input vector */
+
+ if IS_LIST_EMPTY(s->tris)
+ triangulate(s);
+
+ /* We have to find out which triangle the point is in */
+ if (s->lu_inited == 0) {
+ init_lu(s); /* Init BSP search tree */
+ }
+//if (trace) printf("~1 radial called with %f %f %f\n", in[0], in[1], in[2]);
+
+ for (j = 0; j < 3; j++)
+ nin[j] = in[j] - s->cent[j]; /* relative to gamut center */
+
+ for (ss = 0.0, j = 0; j < 3; j++)
+ ss += nin[j] * nin[j];
+ ss = sqrt(ss);
+ if (ss > 1e-9) { /* Normalise to 1.0 */
+ for (j = 0; j < 3; j++)
+ nin[j] /= ss;
+ } else {
+ nin[0] = 1.0;
+ nin[1] = nin[2] = 0.0;
+ }
+
+//if (trace) printf("~1 Normalised in = %f %f %f\n", nin[0], nin[1], nin[2]);
+ rv = radial_point(s, s->lutree, nin);
+
+ if (rv < 0.0) {
+ error("gamut: radial internal error - failed to find triangle\n");
+ }
+
+ if (out != NULL) {
+ for (j = 0; j < 3; j++)
+ out[j] = nin[j] * rv + s->cent[j]; /* Scale out to surface length, absolute */
+//if (trace) printf("~1 result = %f %f %f\n",out[0], out[1], out[2]);
+ }
+
+ if (ir != NULL) {
+//if (trace) printf("~1 input radius res = %f\n",ss);
+ *ir = ss;
+ }
+
+ if (or != NULL) {
+//if (trace) printf("~1 output radius res = %f\n",rv);
+ *or = rv;
+ }
+}
+
+/* Given a point, return the point in that direction */
+/* that lies on the gamut surface */
+/* Return the normalised radial radius to the surface point */
+static double
+nradial(
+gamut *s,
+double *out, /* result point (absolute) (May be NULL) */
+double *in /* input point (absolute)*/
+) {
+ double ss, rv;
+
+ _radial(s, &ss, &rv, out, in);
+ return ss/rv;
+}
+
+/* Given a point, return the point in that direction */
+/* that lies on the gamut surface */
+/* Return the radial radius to the surface point */
+static double
+radial(
+gamut *s,
+double *out, /* result point (absolute) (May be NULL) */
+double *in /* input point (absolute)*/
+) {
+ double ss, rv;
+
+ _radial(s, &ss, &rv, out, in);
+ return rv;
+}
+
+void lu_split(gamut *s, gbsp **np, int rdepth, gtri **list, int llen);
+
+/* Setup the radial lookup function acceleration structure */
+static void
+init_lu(
+gamut *s
+) {
+ static double v0[3] = {0.0, 0.0, 0.0};
+ static
+ gedge *ep; /* Edge pointer */
+ gtri *tp; /* Triangle pointer */
+ gtri **tlist;
+ int ntris;
+
+//printf("~1 init_lu called\n");
+
+ /* Create mean angle dividing plane equations */
+ ep = s->edges;
+ FOR_ALL_ITEMS(gedge, ep) {
+ plane_equation(ep->re, v0, ep->v[0]->sp, ep->v[1]->sp);
+ } END_FOR_ALL_ITEMS(ep);
+
+ /* Create the initial triangle list */
+ /* First count them */
+ ntris = 0;
+ tp = s->tris;
+ FOR_ALL_ITEMS(gtri, tp) {
+ ntris++;
+ } END_FOR_ALL_ITEMS(tp);
+
+ /* Allocate a list */
+ if ((tlist = (gtri **) malloc(ntris * sizeof(gtri *))) == NULL) {
+ fprintf(stderr,"gamut: malloc failed - top level triangle list (%d entries)\n",ntris);
+ exit(-1);
+ }
+
+ /* Then add them to the list */
+ ntris = 0;
+ tp = s->tris;
+ FOR_ALL_ITEMS(gtri, tp) {
+ tlist[ntris] = tp;
+ ntris++;
+ } END_FOR_ALL_ITEMS(tp);
+
+ /* Recursively split them, and add objects to leaves */
+ lu_split(s, &s->lutree, 0, tlist, ntris);
+
+ free(tlist);
+
+//printf("~1 init_lu done\n");
+ s->lu_inited = 1;
+}
+
+/*
+ * BSP accellerator:
+ * This is setup specifically to accellerate finding
+ * the radial point on the gamut surface. To do this, all
+ * the BSP plains pass through the gamut center, creating
+ * wedge shaped sub-division regions.
+ *
+ * For accellerating the vector intersect code, this isn't
+ * so fabulous, and a general unconstrained BSP tree would
+ * be better. To address this, an orhogonal element to the
+ * radial BSP's is provided in the radius squared range
+ * of each set of elements below a BSP node.
+ */
+
+/* Recursive routine to choose a partition plane, */
+/* and then split the triangle list between the */
+/* +ve and -ve sides, or add triangles as leaves. */
+void
+lu_split(
+gamut *s,
+gbsp **np, /* Address of node pointer to be set */
+int rdepth, /* Current recursion depth */
+gtri **list, /* Current triangle list */
+int llen /* Number of triangles in the list */
+) {
+ double rs0, rs1; /* Radius squared range of elements */
+ int ii, jj; /* Progress through edges */
+ int pcount; /* Current best scored try */
+ int ncount;
+ int bcount;
+ int mcount;
+ double peqs[4] = { 0.0, 0.0, 0.0, 0.0 };
+ gtri **plist, **nlist; /* New sub-lists */
+ int pix, nix; /* pos/ned sublist indexes */
+ gbspn *bspn; /* BSP decision node */
+
+//printf("~1\nlu_split called at depth %d with %d triangles\n",rdepth, llen);
+#ifdef DEBUG
+ if (llen <= 3) {
+ int i;
+ for (i = 0; i < llen; i++) {
+ printf("Triang index %d = %d\n",i, list[i]->n);
+ printf("Triang verts %d %d %d\n",
+ list[i]->v[0]->tn, list[i]->v[1]->tn, list[i]->v[2]->tn);
+ printf("Vert 0 at %.18f %.18f %.18f\n",list[i]->v[0]->sp[0], list[i]->v[0]->sp[1], list[i]->v[0]->sp[2]);
+ printf("Vert 1 at %.18f %.18f %.18f\n",list[i]->v[1]->sp[0], list[i]->v[1]->sp[1], list[i]->v[1]->sp[2]);
+ printf("Vert 2 at %.18f %.18f %.18f\n",list[i]->v[2]->sp[0], list[i]->v[2]->sp[1], list[i]->v[2]->sp[2]);
+ }
+ }
+#endif /* DEBUG */
+
+ if ((rdepth+1) >= BSPDEPTH) { /* Oops */
+ printf("gamut internal error: ran out of recursion depth in BSP\n");
+ exit (-1);
+ }
+
+ /* Scan our list or triangles and figure out radius squared range */
+ {
+ int i, j, e;
+ double rs;
+
+ rs0 = 1e120;
+ rs1 = -1.0;
+ for (i = 0; i < llen; i++) {
+ if (list[i]->rs0 < rs0)
+ rs0 = list[i]->rs0;
+ if (list[i]->rs1 > rs1)
+ rs1 = list[i]->rs1;
+ }
+//printf("~1 no triangs %d, rs range %f - %f\n",llen,rs0,rs1);
+ }
+
+ pcount = ncount = bcount = -1;
+ mcount = 0;
+ /* test every edge in turn */
+ for (ii = jj = 0;ii < llen;) {
+ double eqs[4];
+ int i;
+ gedge *ep; /* Edge pointer */
+ int pc, nc, bc; /* Score a try, postive count, negative count, both count */
+ int mc; /* Minumum count */
+
+ ep = list[ii]->e[jj];
+ eqs[0] = ep->re[0]; /* Use this edge */
+ eqs[1] = ep->re[1];
+ eqs[2] = ep->re[2];
+ eqs[3] = ep->re[3];
+ if (++jj > 2) {
+ jj = 0;
+ ii++;
+ }
+
+ /* Do the trial split */
+ pc = nc = bc = 0;
+ for (i = 0; i < llen; i++) {
+ int j;
+ int po, ne;
+
+ /* Compute distance from plane of all verticies in triangle */
+ po = ne = 0;
+ for (j = 0; j < 3; j++) { /* For triangle verticies */
+ double ds;
+ /* Compute distance to dividing plane of this vertex */
+ ds = eqs[0] * list[i]->v[j]->sp[0]
+ + eqs[1] * list[i]->v[j]->sp[1]
+ + eqs[2] * list[i]->v[j]->sp[2]
+ + eqs[3];
+ /* Figure if the verticies are clearly to one side of the plane */
+ if (ds > 1e-10) {
+ po++;
+ } else if (ds < -1e-10) {
+ ne++;
+ }
+ }
+ /* Score this split */
+ if (po) {
+ pc++;
+ if (ne) {
+ nc++;
+ bc++;
+ list[i]->sort = 3; /* Both */
+ } else {
+ list[i]->sort = 1; /* +ve */
+ }
+ } else if (ne) {
+ nc++;
+ list[i]->sort = 2; /* -ve */
+ } else { /* Hmm. Neither */
+ bc++;
+ list[i]->sort = 3; /* Assume both */
+ }
+ }
+ mc = pc < nc ? pc : nc; /* Size of smallest group */
+ mc -= bc;
+//printf("~1 lu_split trial %d, mc %d, pc %d, nc %d, bc %d\n",ii * 3 + jj, mc, pc, nc, bc);
+ if (mc > mcount) { /* New largest small group */
+ mcount = mc;
+ pcount = pc;
+ ncount = nc;
+ bcount = bc;
+ peqs[0] = eqs[0];
+ peqs[1] = eqs[1];
+ peqs[2] = eqs[2];
+ peqs[3] = eqs[3];
+//printf("~1 new best - plane mc = %d, %f %f %f %f\n",mc, peqs[0], peqs[1], peqs[2], peqs[3]);
+ for (i = 0; i < llen; i++) {
+ list[i]->bsort = list[i]->sort;
+ }
+ }
+ }
+
+#ifdef DEBUG_SPLIT_VRML
+ write_split_diag_vrml(s, list, llen);
+ getchar();
+#endif /* DEBUG_SPLIT_VRML */
+
+ if (ii >= llen && bcount < 0) { /* We failed to find a split plane. */
+ /* This is usually a result of the list being 2 or more triangles */
+ /* that do not share any edges (disconected from each other), and */
+ /* lying so that any split plane formed from an edge of one, */
+ /* intersects one of the others. */
+ /* In theory we could solve this by picking some */
+ /* other radial split plane ? */
+
+ /* Instead leave our list of triangles as the leaf node, */
+ /* and let the search algorithms deal with this. */
+
+ *np = (gbsp *)new_gbspl(llen, list);
+ (*np)->rs0 = rs0; /* Radius squared range */
+ (*np)->rs1 = rs1;
+//printf("~1 lu_split returning with a non split list of %d triangles\n",llen);
+ return;
+ }
+
+ /* Divide the triangles into two lists */
+ bspn = new_gbspn(); /* Next node */
+ *np = (gbsp *)bspn; /* Put it in place */
+ bspn->rs0 = rs0; /* Radius squared range */
+ bspn->rs1 = rs1;
+ bspn->pe[0] = peqs[0]; /* Plane equation */
+ bspn->pe[1] = peqs[1];
+ bspn->pe[2] = peqs[2];
+ bspn->pe[3] = peqs[3];
+
+ /* Allocate the sub lists */
+ if ((plist = (gtri **) malloc(pcount * sizeof(gtri *))) == NULL) {
+ fprintf(stderr,"gamut: malloc failed - pos sub-list\n");
+ exit(-1);
+ }
+ if ((nlist = (gtri **) malloc(ncount * sizeof(gtri *))) == NULL) {
+ fprintf(stderr,"gamut: malloc failed - neg sub-list\n");
+ exit(-1);
+ }
+
+ /* Fill them in */
+ for (pix = nix = ii = 0; ii < llen; ii++) {
+ if (list[ii]->bsort & 1) { /* Positive */
+ plist[pix] = list[ii];
+ pix++;
+ }
+ if (list[ii]->bsort & 2) { /* Negative */
+ nlist[nix] = list[ii];
+ nix++;
+ }
+ }
+
+ /* Recurse if there are more triangles to split */
+ if (pix == 1) {
+ bspn->po = (gbsp *)plist[0]; /* leaf node */
+//printf("~1 pos leaf with triangle %d\n",plist[0]->n);
+ } else if (pix > 1) {
+//printf("~1 About to recurse on positive with list of %d\n",pix);
+ lu_split(s, &bspn->po, rdepth+1, plist, pix);
+ }
+
+ if (nix == 1) {
+//printf("~1 neg leaf with triangle %d\n",nlist[0]->n);
+ bspn->ne = (gbsp *)nlist[0]; /* leaf node */
+ } else if (nix > 1) {
+//printf("~1 About to recurse on negative with list of %d\n",nix);
+ lu_split(s, &bspn->ne, rdepth+1, nlist, nix);
+ }
+
+ free(plist);
+ free(nlist);
+//printf("~1 lu_split returning\n");
+}
+
+/* Given a point and a node in the BSP tree, recurse down */
+/* the tree, or return the triangle it lies in. */
+/* Return NULL if it wasn't in any triangle (shouldn't happen with a closed gamut ?). */
+static gtri *radial_point_triang(
+gamut *s,
+gbsp *np, /* BSP node pointer we're at */
+double *nin /* Normalised center relative point */
+) {
+ gtri *rv;
+//if (trace) printf("~1 rad_pnt_tri: BSP 0x%x tag = %d, point %f %f %f\n", np,np->tag,nin[0],nin[1],nin[2]);
+ if (np->tag == 1) { /* It's a BSP node */
+ gbspn *n = (gbspn *)np;
+ double ds;
+
+ ds = n->pe[0] * nin[0]
+ + n->pe[1] * nin[1]
+ + n->pe[2] * nin[2]
+ + n->pe[3];
+
+//if (trace) printf("~1 checking against BSP plane, ds = %e\n",ds);
+ /* Recurse down both sides it might be in */
+ if (ds > -1e-12) {
+ if ((rv = radial_point_triang(s, n->po, nin)) != NULL)
+ return rv;
+ }
+ if (ds < 1e-12) {
+ if ((rv = radial_point_triang(s, n->ne, nin)) != NULL)
+ return rv;
+ }
+ return NULL; /* Hmm */
+
+ } else { /* It's a triangle or list of triangles */
+ int nt; /* Number of triangles in list */
+ gtri **tpp; /* Pointer to list of triangles */
+ int i, j;
+
+ if (np->tag == 2) { /* It's a triangle */
+ tpp = (gtri **)&np;
+ nt = 1;
+ } else if (np->tag == 3) { /* It's a triangle list */
+ gbspl *n = (gbspl *)np;
+ tpp = n->t;
+ nt = n->nt;
+ }
+
+ /* Go through the list and stop at the first triangle */
+ /* that the node lies in. */
+ for (i = 0; i < nt; i++, tpp++) {
+ gtri *t = *tpp;
+
+ /* Check if the point is within this triangle */
+ for (j = 0; j < 3; j++) {
+ double ds;
+ ds = t->ee[j][0] * nin[0]
+ + t->ee[j][1] * nin[1]
+ + t->ee[j][2] * nin[2]
+ + t->ee[j][3];
+ if (ds > 1e-10)
+ break; /* Not within triangle */
+ }
+ if (j >= 3) {
+//if (trace) printf("~1 located triangle from list that we're in %d\n",n->t[i]->n);
+ return t;
+ }
+ }
+ /* Hmm. */
+ }
+
+//if (trace) printf("~1 failed to find a triangle\n");
+ return NULL;
+}
+
+/* Return the location on the surface of the triangle */
+/* that is intersected by the radial direction */
+/* of the given relative point. Return the distance to */
+/* the gamut surface. */
+static double radial_point(
+gamut *s,
+gbsp *np, /* BSP node pointer we're at */
+double *nin /* Normalised center relative point */
+) {
+ gtri *t;
+ double rv;
+
+//if (trace) printf("~1 radial_point: BSP 0x%x tag = %d, point %f %f %f\n", np,np->tag,nin[0],nin[1],nin[2]);
+
+ t = radial_point_triang(s, np, nin);
+
+ /* If we failed to find a triangle, or the result was incorrect, do a */
+ /* brute force search to be sure of the result. */
+ if (t == NULL) {
+ error("rspl.radial: failed to find radial triangle\n");
+ }
+
+ /* Compute the intersection of the input vector with the triangle plane */
+ /* (Since nin[] is already relative, we don't need to subtract cent[] from it) */
+ rv = -(t->pe[0] * s->cent[0] + t->pe[1] * s->cent[1] + t->pe[2] * s->cent[2] + t->pe[3])/
+ (t->pe[0] * nin[0] + t->pe[1] * nin[1] + t->pe[2] * nin[2]);
+
+#ifdef ASSERTS
+ /* check the result */
+ {
+ double tt[3];
+ double ds;
+ int j;
+ for (j = 0; j < 3; j++) /* Compute result, absolute */
+ tt[j] = nin[j] * rv + s->cent[j];
+
+ ds = t->pe[0] * tt[0]
+ + t->pe[1] * tt[1]
+ + t->pe[2] * tt[2]
+ + t->pe[3];
+
+ if (fabs(ds) > 1e-6) {
+ fprintf(stderr,"radial: distance to plane not zero! %e\n",ds);
+ exit(-1);
+ }
+
+ /* Check if the closest point is within this triangle */
+ for (j = 0; j < 3; j++) {
+ double ds;
+ ds = t->ee[j][0] * (tt[0] - s->cent[0])
+ + t->ee[j][1] * (tt[1] - s->cent[1])
+ + t->ee[j][2] * (tt[2] - s->cent[2])
+ + t->ee[j][3];
+ if (ds > 1e-8) {
+ fprintf(stderr,"radial: lookup point wasn't within its triangle (%f) !!\n",ds);
+ exit(-1);
+ }
+ }
+ }
+#endif /* ASSERTS */
+
+//if (trace) printf("~1 radial_point: rv = %f\n",rv);
+ return rv;
+}
+
+/* Recursively free a gbsp node and all its children */
+static void del_gbsp(gbsp *n) {
+ int tag = n->tag;
+
+ if (tag == 1) { /* Another decision node */
+ gbspn *dn = (gbspn *)n;
+ del_gbsp(dn->po); /* Delete children */
+ del_gbsp(dn->ne);
+ del_gbspn(dn); /* And itself */
+
+ } else if (tag == 3) { /* If a triangle list */
+ gbspl *dl = (gbspl *)n;
+ del_gbspl(dl); /* Delete itself */
+ }
+
+ /* Don't delete triangles (tag == 2) since they */
+ /* have their own linked list, and may have already been deleted. */
+ /* Note we need to be called _before_ triangles are deleted though, */
+ /* since we access them to get the tag. */
+}
+
+/* =================================== */
+/* Given a point, */
+/* return the nearest point on the gamut surface. */
+
+#define GNN_INF 1e307
+static void init_ne(gamut *s);
+
+/* Given an absolute point, return the point on the gamut */
+/* surface that is closest to it. */
+/* Use a brute force search */
+static void
+nearest_bf(
+gamut *s,
+double *out, /* result point (absolute) */
+double *q /* Target point (absolute) */
+) {
+ gtri *tp;
+ double bdist = 1e308; /* Best possible distance to an object outside the window */
+
+
+ if IS_LIST_EMPTY(s->tris)
+ triangulate(s);
+
+ tp = s->tris;
+ FOR_ALL_ITEMS(gtri, tp) {
+ double r[3]; /* Possible solution point */
+ double tdist;
+
+ /* Compute distance from query point to this object */
+ tdist = ne_point_on_tri(s, tp, r, q);
+
+ if (tdist < bdist) { /* New best point */
+ bdist = tdist;
+ out[0] = r[0];
+ out[1] = r[1];
+ out[2] = r[2];
+ }
+ } END_FOR_ALL_ITEMS(tp);
+}
+
+/* Using nearest neighbourhood accelleration structure: */
+
+/* Given an absolute point, return the point on the gamut */
+/* surface that is closest to it. */
+static void
+nearest_tri(
+gamut *s,
+double *rout, /* result point (absolute) */
+double *q, /* Target point (absolute) */
+gtri **ctri /* If not NULL, return pointer to nearest triangle */
+) {
+ gnn *p; /* Pointer to nearest neighbor structure */
+ int e, i;
+ double r[3] = {0.0, 0.0, 0.0 }; /* Possible solution point */
+ double out[3] = {0.0, 0.0, 0.0}; /* Current best output value */
+ int wex[3 * 2]; /* Current window edge indexes */
+ double wed[3 * 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");
+ if IS_LIST_EMPTY(s->tris)
+ triangulate(s);
+
+ /* We have to find out which triangle the point will be nearest */
+ if (s->ne_inited == 0) {
+ init_ne(s); /* Init nn structure */
+ }
+ p = s->nns;
+
+ if ((p->tbase + 3) < p->tbase) { /* Overflow of touch count */
+ for (i = 0; i < p->n; i++)
+ p->sax[0][i]->touch = 0; /* reset it in all the objects */
+ p->tbase = 0;
+ }
+ p->ttarget = p->tbase + 3; /* Target touch value */
+
+//printf("\n");
+//printf("Query point is %f %f %f\n",q[0], q[1], q[2]);
+
+ /* Find starting indexes within axis arrays */
+ for (e = 0; e < (2 * 3); e++) { /* For all axes min & max */
+ int f = e/2; /* Axis */
+ int mm = (e ^ 1) & 1; /* Min/Max index used for edges */
+ int i0, i1, i2;
+ double v0, v1, v2;
+ 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 = p->n - 1;
+ v0 = p->sax[e][i0]->mix[mm][f];
+ v2 = p->sax[e][i2]->mix[mm][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 = p->sax[e][i1]->mix[mm][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 p->n objects */
+ tc = p->n - i2 + wex[e ^ 1] + 1;
+//printf("got %d, expected %d\n",tc, p->n);
+
+ /* (I don't really understand why this works!) */
+ if (tc < p->n) { /* 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 < p->n) {
+ tv0 = GNN_INF; /* Guard values */
+ tv2 = -GNN_INF;
+
+ /* Increment low side until we find a straddler */
+ while (ti0 < (p->n-1)) {
+ ww = p->sax[el][++ti0]->mix[0][f]; /* Position of the other end */
+ if (ww < qf) {
+//printf("found low object %d at index %d that straddles\n",p->sax[el][ti0]->n,ti0);
+ tv0 = qf - p->sax[el][ti0]->mix[1][f];
+ break;
+ }
+ }
+
+ /* Decrement high side until we find a straddler */
+ while (ti2 > 0) {
+ ww = p->sax[e][--ti2]->mix[1][f]; /* Position of the other end */
+ if (ww > qf) {
+//printf("found high object %d at index %d that straddles\n",p->sax[e][ti2]->n,ti2);
+ tv2 = p->sax[e][ti2]->mix[0][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 3 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 = 1e308; /* Best possible distance to an object outside the window */
+ gtri *bobj = NULL;
+ int ptested = 0; /* Stats */
+ int pcalced = 0; /* Stats */
+
+ /* Until we're done */
+ for (;;ptested++) {
+ int ee; /* Axis & expanding box edge */
+ int ff; /* Axis */
+ int ii; /* Index of chosen point */
+ gtri *ob; /* Current object */
+ unsigned int ctv; /* Current touch value */
+//printf("\n");
+//printf("wwidth = %f, bdist = %f, window = %d-%d, %d-%d, %d-%d\n",
+//bw, bobj == NULL ? 0.0 : bdist, wex[0], wex[1], wex[2], wex[3], wex[4], wex[5]);
+//printf("window edge distances are = %f-%f, %f-%f, %f-%f\n",
+//wed[0], wed[1], wed[2], wed[3], wed[4], wed[5]);
+
+ /* find next (smallest) window increment axis and direction */
+ ee = 0;
+ ii = wex[ee];
+ bw = wed[ee];
+ for (e = 1; e < (2 * 3); 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, p->sax[ee][ii]->n, ii, bw);
+
+ if (bw == GNN_INF || bw > bdist) {
+ break; /* Can't got any further, or further points will be worse */
+ }
+
+#ifdef ASSERTS
+ if (ii < 0 || ii >= p->n) {
+ 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 = p->sax[ee][ii];
+
+ /* Touch value of current object */
+ ctv = ob->touch;
+
+ if (ctv < p->ttarget) { /* Not been dealt with before */
+
+ /* Touch this new window boundary point */
+ ob->touch = ctv = ((ctv < p->tbase) ? p->tbase : ctv) + 1;
+
+//printf("New touch count on %d is %d, target %d\n", ob->n, p->sax[ee][ii]->touch, p->ttarget);
+
+ /* Check the point out */
+ if (ctv == (p->tbase + 3)) { /* Is within window on all axes */
+ double tdist;
+
+ pcalced++; /* Stats */
+
+ /* Compute distance from query point to this object */
+ tdist = ne_point_on_tri(s, ob, r, q);
+
+//printf("Got new best point %d, dist %f\n",i,tdist);
+ if (tdist < bdist) { /* New best point */
+ bobj = ob;
+ bdist = tdist;
+ out[0] = r[0];
+ out[1] = r[1];
+ out[2] = r[2];
+ }
+ }
+ }
+
+ /* Increment next window edge candidate, and figure new edge distance */
+ if (ee & 1) { /* Top */
+ if (++wex[ee] >= p->n) {
+ wed[ee] = GNN_INF;
+ wex[ee]--;
+ } else {
+ double ww = p->sax[ee][wex[ee]]->mix[0][ff] - q[ff];
+ wed[ee] = fabs(ww) * ww;
+ }
+ } else {
+ if (--wex[ee] < 0) {
+ wed[ee] = GNN_INF;
+ wex[ee]++;
+ } else {
+ double ww = q[ff] - p->sax[ee][wex[ee]]->mix[1][ff];
+ wed[ee] = fabs(ww) * ww;
+ }
+ }
+ }
+
+//printf("Searched %d points out of %d = %f%%\n",ptested, p->n, 100.0 * ptested/p->n);
+
+ p->tbase += 3; /* Next touch */
+
+ if (rout != NULL) {
+ rout[0] = out[0]; /* Copy results to output */
+ rout[1] = out[1];
+ rout[2] = out[2];
+ }
+
+ if (ctri != NULL)
+ *ctri = bobj;
+
+ return;
+ }
+}
+
+/* Given an absolute point, return the point on the gamut */
+/* surface that is closest to it. */
+static void
+nearest(
+gamut *s,
+double *rout, /* result point (absolute) */
+double *q /* Target point (absolute) */
+) {
+ nearest_tri(s, rout, q, NULL);
+}
+
+/* Perturb the containment points to avoid */
+/* numerical co-incidence */
+double perturb[21] = {
+ 8.9919295344233395e-283, 1.1639766020018968e+224, 1.2554893023590904e+232,
+ 2.3898157055642966e+190, 1.5697612415774029e-076, 6.6912978722191457e+281,
+ 1.2369092402930559e+277, 1.4430907501246712e-153, 3.0017439193018232e+238,
+ 1.2978311824382444e+161, 5.5068703318775818e-311, 7.7791723264448801e-260,
+ 4.4296571592384350e+281, 8.9481529920968425e+165, 1.2845894914769635e-153,
+ 2.0835868791190880e-076, 5.4310198502711138e+241, 4.8689849775675438e+275,
+ 9.2709981544886391e+122, 3.7958270103353899e-153, 7.1366083837501666e-154
+};
+
+/* Setup the nearest function acceleration structure */
+static void
+init_ne(
+gamut *s
+) {
+ gnn *p;
+ int i, k;
+ gtri *tp; /* Triangle pointer */
+ int ntris;
+ double psf;
+
+//printf("~1 init_ne called\n");
+
+ /* Allocate the nearest neighbor acceleration structure */
+ if ((s->nns = p = (gnn *) calloc(1, sizeof(gnn))) == NULL) {
+ fprintf(stderr,"gamut: calloc failed - gnn structure\n");
+ exit(-1);
+ }
+
+ /* Count triangles */
+ ntris = 0;
+ tp = s->tris;
+ FOR_ALL_ITEMS(gtri, tp) {
+ ntris++;
+ } END_FOR_ALL_ITEMS(tp);
+
+ p->n = ntris;
+ p->tbase = 0; /* Initialse touch flag */
+
+ /* Allocate the arrays spaces */
+ for (k = 0; k < (3 * 2); k++) {
+ if ((p->sax[k] = (gtri **)malloc(sizeof(gtri *) * ntris)) == NULL)
+ error("Failed to allocate sorted index array");
+ }
+
+ /* Compute pertbation factor */
+ for (psf = 0.0, i = 1; i < 21; i++)
+ psf += perturb[i];
+ psf *= perturb[0];
+
+ /* For each triangle, create the triangle bounding box values, */
+ /* and add them to the axis lists. */
+ tp = s->tris;
+ i = 0;
+ FOR_ALL_ITEMS(gtri, tp) {
+ int j;
+ for (j = 0; j < 3; j++) { /* Init */
+ tp->mix[0][j] = 1e38;
+ tp->mix[1][j] = -1e38;
+ }
+ for (k = 0; k < 3; k++) {
+ for (j = 0; j < 3; j++) {
+ if (tp->v[k]->p[j] < tp->mix[0][j]) /* New min */
+ tp->mix[0][j] = psf * tp->v[k]->p[j];
+ if (tp->v[k]->p[j] > tp->mix[1][j]) /* New max */
+ tp->mix[1][j] = psf * tp->v[k]->p[j];
+ }
+ p->sax[k * 2 + 0][i] = tp;
+ p->sax[k * 2 + 1][i] = tp;
+ }
+//printf("~1 tri %d has bb %f - %f, %f - %f, %f - %f\n", i, tp->mix[0][0], tp->mix[1][0], tp->mix[0][1], tp->mix[1][1], tp->mix[0][2], tp->mix[1][2]);
+ i++;
+ } END_FOR_ALL_ITEMS(tp);
+
+
+ /* Sort the axis arrays */
+ for (k = 0; k < 3; k++) {
+
+ /* Sort upper edges of bounding box */
+#define HEAP_COMPARE(A,B) (A->mix[1][k] < B->mix[1][k])
+ HEAPSORT(gtri *, &p->sax[k * 2 + 0][0], ntris)
+#undef HEAP_COMPARE
+
+ /* Sort lower edges of bounding box */
+#define HEAP_COMPARE(A,B) (A->mix[0][k] < B->mix[0][k])
+ HEAPSORT(gtri *, &p->sax[k * 2 + 1][0], ntris)
+#undef HEAP_COMPARE
+ }
+ s->ne_inited = 1;
+
+//printf("~1 init_ne done\n");
+}
+
+/* Free everything */
+static void del_gnn(gnn *p) {
+ int k;
+
+ for (k = 0; k < (3 * 2); k++) {
+ free (p->sax[k]);
+ }
+
+ free(p);
+}
+
+/* ===================================================== */
+/* Define the colorspaces white and black point. May be NULL if unknown. */
+/* Note that as in all of the gamut library, we assume that we are in */
+/* an L*a*b* or Jab type color space. */
+static void setwb(
+gamut *s,
+double *wp,
+double *bp,
+double *kp
+) {
+ if (wp != NULL) {
+ s->cs_wp[0] = wp[0];
+ s->cs_wp[1] = wp[1];
+ s->cs_wp[2] = wp[2];
+ } else {
+ s->cs_wp[0] = 100.0;
+ s->cs_wp[1] = 0.0;
+ s->cs_wp[2] = 0.0;
+ }
+
+ if (bp != NULL) {
+ s->cs_bp[0] = bp[0];
+ s->cs_bp[1] = bp[1];
+ s->cs_bp[2] = bp[2];
+ } else {
+ s->cs_bp[0] = 0.0;
+ s->cs_bp[1] = 0.0;
+ s->cs_bp[2] = 0.0;
+ }
+
+ if (kp != NULL) {
+ s->cs_kp[0] = kp[0];
+ s->cs_kp[1] = kp[1];
+ s->cs_kp[2] = kp[2];
+ } else {
+ s->cs_kp[0] = s->cs_bp[0];
+ s->cs_kp[1] = s->cs_bp[1];
+ s->cs_kp[2] = s->cs_bp[2];
+ }
+
+ s->cswbset = 1;
+}
+
+
+/* Compute the gamut white/black points, assuming */
+/* that the colorspace white/black points have been set. */
+/* The gamut white/black are the points on the colorspace */
+/* white/black axis that have the same L values as the */
+/* extremes within the gamut. */
+static void compgawb(gamut *s) {
+ int i;
+ double ff, Lmax, Lmin, LKmin;
+
+ if (s->cswbset == 0 || s->gawbset != 0)
+ return; /* Nothing to do */
+
+ Lmax = -1000.0;
+ Lmin = 1000.0;
+
+ /* Discover min and max L values */
+ for (i = 0; i < s->nv; i++) {
+ if ((s->verts[i]->f & GVERT_SET) == 0 )
+ continue;
+
+ if (s->verts[i]->p[0] > Lmax)
+ Lmax = s->verts[i]->p[0];
+ if (s->verts[i]->p[0] < Lmin)
+ Lmin = s->verts[i]->p[0];
+ }
+
+ LKmin = Lmin;
+
+ if (Lmax > s->cs_wp[0]) /* Slightly Strange */
+ Lmax = s->cs_wp[0];
+ if (Lmin < s->cs_bp[0]) /* Also Slightly strange */
+ Lmin = s->cs_bp[0];
+ if (LKmin < s->cs_kp[0]) /* Expected */
+ LKmin = s->cs_kp[0];
+
+ /* Locate points along colorspace grey axis */
+ /* that correspond to the L extremes */
+ ff = (Lmax - s->cs_bp[0])/(s->cs_wp[0] - s->cs_bp[0]);
+ s->ga_wp[0] = Lmax;
+ s->ga_wp[1] = ff * (s->cs_wp[1] - s->cs_bp[1]) + s->cs_bp[1];
+ s->ga_wp[2] = ff * (s->cs_wp[2] - s->cs_bp[2]) + s->cs_bp[2];
+
+ ff = (Lmin - s->cs_bp[0])/(s->cs_wp[0] - s->cs_bp[0]);
+ s->ga_bp[0] = Lmin;
+ s->ga_bp[1] = ff * (s->cs_wp[1] - s->cs_bp[1]) + s->cs_bp[1];
+ s->ga_bp[2] = ff * (s->cs_wp[2] - s->cs_bp[2]) + s->cs_bp[2];
+
+ ff = (LKmin - s->cs_kp[0])/(s->cs_wp[0] - s->cs_kp[0]);
+ s->ga_kp[0] = LKmin;
+ s->ga_kp[1] = ff * (s->cs_wp[1] - s->cs_kp[1]) + s->cs_kp[1];
+ s->ga_kp[2] = ff * (s->cs_wp[2] - s->cs_kp[2]) + s->cs_kp[2];
+
+ s->gawbset = 1;
+}
+
+/* Get the colorspace and gamut white & black points. */
+/* Return pointers may be NULL */
+/* Return non-zero if not possible. */
+static int getwb(
+gamut *s,
+double *cswp, /* Color space */
+double *csbp,
+double *cskp,
+double *gawp, /* Gamut */
+double *gabp,
+double *gakp
+) {
+//printf("~1 getwb() called\n");
+ if (s->cswbset == 0) {
+ return 1;
+ }
+
+ if (cswp != NULL) {
+ cswp[0] = s->cs_wp[0];
+ cswp[1] = s->cs_wp[1];
+ cswp[2] = s->cs_wp[2];
+ }
+
+ if (csbp != NULL) {
+ csbp[0] = s->cs_bp[0];
+ csbp[1] = s->cs_bp[1];
+ csbp[2] = s->cs_bp[2];
+ }
+
+ if (cskp != NULL) {
+ cskp[0] = s->cs_kp[0];
+ cskp[1] = s->cs_kp[1];
+ cskp[2] = s->cs_kp[2];
+ }
+//printf("~1 colorspace white %f %f %f, black %f %f %f, kblack %f %f %f\n", s->cs_wp[0], s->cs_wp[1], s->cs_wp[2], s->cs_bp[0], s->cs_bp[1], s->cs_bp[2], s->cs_kp[0],s->cs_kp[1],s->cs_kp[2]);
+
+ if (gawp != NULL || gabp != NULL) {
+//printf("~1 computing gamut white & black\n");
+ compgawb(s); /* make sure we have gamut white/black available */
+ }
+
+ if (gawp != NULL) {
+ gawp[0] = s->ga_wp[0];
+ gawp[1] = s->ga_wp[1];
+ gawp[2] = s->ga_wp[2];
+ }
+
+ if (gabp != NULL) {
+ gabp[0] = s->ga_bp[0];
+ gabp[1] = s->ga_bp[1];
+ gabp[2] = s->ga_bp[2];
+ }
+
+ if (gakp != NULL) {
+ gakp[0] = s->ga_kp[0];
+ gakp[1] = s->ga_kp[1];
+ gakp[2] = s->ga_kp[2];
+ }
+//printf("~1 gamut white %f %f %f, black %f %f %f, kblack %f %f %f\n", s->ga_wp[0], s->ga_wp[1], s->ga_wp[2], s->ga_bp[0], s->ga_bp[1], s->ga_bp[2], s->ga_kp[0],s->ga_kp[1],s->ga_kp[2]);
+
+ return 0;
+}
+
+
+/* ---------------------------------------------------- */
+/* Per-triangle primitives used to compute brute force */
+/* radial & vector intersection and nearest point, */
+/* as well as gamut surface intersections. */
+/* See if the given triangle intersect the given vector. */
+/* Return 1 if it does, 0 if it doesn't */
+static int vect_intersect(
+gamut *s,
+double *rvp, /* parameter, 0.0 = p1, 1.0 = p2 */
+double *ip, /* return intersection point */
+double *p1, /* First point of vector (ie black) */
+double *p2, /* Second point of vector (ie white) */
+gtri *t /* Triangle in question */
+) {
+ double ti; /* Axis parameter value */
+ double vv[3]; /* vector vector */
+ double ival[3]; /* Intersection value */
+ double den;
+ int j;
+
+ vv[0] = p2[0] - p1[0];
+ vv[1] = p2[1] - p1[1];
+ vv[2] = p2[2] - p1[2];
+
+ den = t->pe[0] * vv[0] + t->pe[1] * vv[1] + t->pe[2] * vv[2];
+ if (fabs(den) < 1e-10) {
+ return 0;
+ }
+
+ /* Compute the intersection of the grey axis vector with the triangle plane */
+ ti = -(t->pe[0] * p1[0] + t->pe[1] * p1[1] + t->pe[2] * p1[2] + t->pe[3])/den;
+
+ /* Compute the actual intersection point */
+ ival[0] = p1[0] + ti * vv[0];
+ ival[1] = p1[1] + ti * vv[1];
+ ival[2] = p1[2] + ti * vv[2];
+
+ /* Check if the intersection point is within the triangle */
+ for (j = 0; j < 3; j++) {
+ double ds;
+ ds = t->ee[j][0] * (ival[0] - s->cent[0]) /* Convert to relative for edge check */
+ + t->ee[j][1] * (ival[1] - s->cent[1])
+ + t->ee[j][2] * (ival[2] - s->cent[2])
+ + t->ee[j][3];
+ if (ds > 1e-8) {
+ return 0; /* Not within triangle */
+ }
+ }
+//printf("~1 vect_intersect got intersection with tri %d at %f\n",t->n,ti);
+
+ /* Got an intersection point */
+ ip[0] = ival[0];
+ ip[1] = ival[1];
+ ip[2] = ival[2];
+
+ *rvp = ti;
+
+ return 1;
+}
+
+/* Given a point and a triangle, return the closest point on */
+/* the triangle closest to the given point. Also return the distance squared */
+/* (Doesn't depend on triangle edge info) */
+static double ne_point_on_tri(
+gamut *s,
+gtri *t, /* Triangle to use */
+double *out, /* Absolute output point */
+double *in /* Absolute input point */
+) {
+ int j;
+ double rv;
+ double bdist;
+
+ /* Compute the point on the triangles plane, that is orthogonal */
+ /* (closest) to the target point. */
+ rv = (t->pe[0] * in[0] + t->pe[1] * in[1] + t->pe[2] * in[2] + t->pe[3])/
+ (t->pe[0] * t->pe[0] + t->pe[1] * t->pe[1] + t->pe[2] * t->pe[2]);
+
+ out[0] = in[0] - rv * t->pe[0];
+ out[1] = in[1] - rv * t->pe[1];
+ out[2] = in[2] - rv * t->pe[2];
+
+ /* Check if the closest point is within this triangle */
+ for (j = 0; j < 3; j++) {
+ double ds;
+ ds = t->ee[j][0] * (out[0] - s->cent[0]) /* Convert to relative for edge check */
+ + t->ee[j][1] * (out[1] - s->cent[1])
+ + t->ee[j][2] * (out[2] - s->cent[2])
+ + t->ee[j][3];
+ if (ds > 1e-8) {
+ break; /* Not within triangle */
+ }
+ }
+ if (j >= 3) { /* It's OK */
+ return rv * rv; /* rv is distance since pe length is 1.0 */
+ }
+
+ /* Not in triangle, so find closest point along any edge, */
+ /* or at the verticies. (don't use edge info, it may not be set up) */
+ bdist = 1e38;
+ for (j = 0; j < 3; j++) { /* For each edge */
+ gvert *v0 = t->v[j], *v1 = t->v[j >= 2 ? 0 : j+1];
+ int k;
+ double nu, de, ds;
+ for (de = 0.0, k = 0; k < 3; k++) {
+ double tt = v1->p[k] - v0->p[k];
+ de += tt * tt;
+ }
+ for (nu = 0.0, k = 0; k < 3; k++)
+ nu += (v1->p[k] - v0->p[k]) * (in[k] - v0->p[k]);
+
+ ds = nu/de;
+
+ if (ds >= 0.0 && ds <= 1.0) { /* Valid edge */
+ double tout[3], ss;
+ for (ss = 0.0, k = 0; k < 3; k++) {
+ tout[k] = v0->p[k] + ds * (v1->p[k] - v0->p[k]);
+ ss += (in[k] - tout[k]) * (in[k] - tout[k]);
+ }
+ if (ss < bdist) {
+ bdist = ss;
+ out[0] = tout[0];
+ out[1] = tout[1];
+ out[2] = tout[2];
+ }
+ }
+ }
+
+ for (j = 0; j < 3; j++) { /* For each vertex */
+ int k;
+ double ss;
+ for (ss = 0.0, k = 0; k < 3; k++) {
+ double tt;
+ tt = in[k] - t->v[j]->p[k];
+ ss += tt * tt;
+ }
+
+ if (ss < bdist) {
+ bdist = ss;
+ out[0] = t->v[j]->p[0];
+ out[1] = t->v[j]->p[1];
+ out[2] = t->v[j]->p[2];
+ }
+ }
+
+ return bdist;
+}
+
+/* ----------------------------------------------------- */
+/* Arbitrary vector intersect */
+
+/* Given a vector, find the two extreme intersection with */
+/* the gamut surface using a brute force search. */
+/* Return 0 if there is no intersection */
+static int compute_vector_isect_bf(
+gamut *s,
+double *p1, /* First point (ie black) */
+double *p2, /* Second point (ie white) */
+double *omin, /* Return gamut surface points, min = closest to p1 */
+double *omax, /* max = farthest from p1 */
+double *omnt, /* Return parameter values for p1 and p2, 0 being at p1, */
+double *omxt, /* and 1 being at p2 */
+gtri **omntri, /* Return the intersection triangles */
+gtri **omxtri
+) {
+ gtri *tp, *t0, *t1;
+ double ip[3], min[3], max[3];
+ double mint, maxt;
+ int j;
+
+ if IS_LIST_EMPTY(s->tris)
+ triangulate(s);
+
+ maxt = -1e68; /* Setup to find min/max */
+ mint = 1e68;
+
+ tp = s->tris;
+ FOR_ALL_ITEMS(gtri, tp) {
+ double rv;
+ if (vect_intersect(s, &rv, ip, p1, p2, tp)) {
+ if (rv < mint) {
+ min[0] = ip[0];
+ min[1] = ip[1];
+ min[2] = ip[2];
+ mint = rv;
+ t0 = tp;
+ }
+ if (rv > maxt) {
+ max[0] = ip[0];
+ max[1] = ip[1];
+ max[2] = ip[2];
+ maxt = rv;
+ t1 = tp;
+ }
+ }
+ } END_FOR_ALL_ITEMS(tp);
+
+ if (((omin != NULL || omnt != NULL || omntri != NULL) && mint == 1e68)
+ || ((omax != NULL || omxt != NULL || omxtri != NULL) && maxt == -1e68)) {
+ return 0;
+ }
+
+ if (omin != NULL)
+ for (j = 0; j < 3; j++)
+ omin[j] = min[j];
+
+ if (omax != NULL)
+ for (j = 0; j < 3; j++)
+ omax[j] = max[j];
+
+ if (omnt != NULL)
+ *omnt = mint;
+
+ if (omxt != NULL)
+ *omxt = maxt;
+
+ if (omntri != NULL)
+ *omntri = t0;
+
+ if (omxtri != NULL)
+ *omxtri = t1;
+
+ return 1;
+}
+
+
+#ifdef INTERSECT_DEBUG
+
+#define ISDBG(xxx) if (deb_insect) printf xxx
+
+int deb_insect = 0; /* Do vrml plot */
+
+/* Debug - given a BSP node, add all the triangles vertexes indexes */
+/* below this node to the diagnosti wrl */
+static void debug_bsp_triangl_wrl(
+gamut *s,
+gbsp *np, /* BSP node pointer we're at */
+vrml *wrl /* Diagnostic plot */
+) {
+ if (np->tag == 1) { /* It's a BSP node */
+ gbspn *n = (gbspn *)np;
+
+ debug_bsp_triangl_wrl(s, n->po, wrl);
+ debug_bsp_triangl_wrl(s, n->ne, wrl);
+
+ } else {
+ int nt; /* Number of triangles in list */
+ gtri **tpp; /* Pointer to list of triangles */
+ int i, j;
+
+ if (np->tag == 2) { /* It's a triangle */
+ tpp = &((gtri *)np);
+ nt = 1;
+ } else if (np->tag == 3) { /* It's a triangle list */
+ gbspl *n = (gbspl *)np;
+ tpp = n->t;
+ nt = n->nt;
+ }
+ /* Go through the list of triangles and intersect with vector */
+ for (i = 0; i < nt; i++, tpp++) {
+ gtri *t = *tpp;
+ int ix[3];
+
+ ix[0] = t->v[0]->tn;
+ ix[1] = t->v[1]->tn;
+ ix[2] = t->v[2]->tn;
+
+ wrl->add_triangle(wrl, 0, ix);
+ }
+ }
+}
+#else /* !INTERSECT_DEBUG */
+# define ISDBG(xxx)
+#endif /* !INTERSECT_DEBUG */
+
+/* Recursive vector intersect using BSP accelleration. */
+static void vector_isect_rec(
+gamut *s,
+gbsp *np, /* BSP node pointer we're at */
+double *vb, /* Center relative base point of vector */
+double *vv, /* Vector direction from base */
+double t0, /* Start parameter value of line */
+double rs0, /* line start point radius squared */
+double t1, /* End parameter value of line */
+double rs1, /* line end point radius squared */
+double tc, /* Parameter value of closest point on line to center */
+double rsc, /* Radius squared of closest point on line to center */
+double rse0, /* Effective radius squared minimum */
+double rse1, /* Effective radius squared maximum */
+gispnt *lp, /* List to set intersections in. */
+int ll, /* Size of list. 0 == 2, min and max only */
+int *lu /* Number used in list */
+) {
+ double den; /* Intersection denominator */
+ double ti; /* Intersection parameter value */
+ double rsi; /* Radius squared of intersection point */
+ double ip[3]; /* Intersection point */
+
+#ifdef INTERSECT_DEBUG
+ if (ll == 0)
+ printf("\nvector_isect_rec got seg %f - %f (%e), best %f, %f\n",t0,t1,t1-t0,lp[0].pv,lp[1].pv);
+ else
+ printf("\nvector_isect_rec got seg %f - %f (%e), no isects %d\n",t0,t1,t1-t0,*lu);
+ printf(" rs %f, %f, tc %f, rsc %f, rse0 %f, rse1 %f, BSP rs %f - %f\n",rs0, rs1, tc, rsc, rse0, rse1, np->rs0, np->rs1);
+#endif
+#ifdef INTERSECT_DEBUG
+ if (deb_insect) {
+ vrml *wrl = NULL;
+ double cc[3] = { 1.0, 1.0, 0.0 };
+ double red[3] = { 1.0, 0.0, 0.0 };
+ double green[3] = { 0.0, 1.0, 0.0 };
+ double blue[3] = { 0.0, 0.0, 1.0 };
+ double p1[3], p2[3];
+ int i;
+
+ unlink("isect2.wrl");
+ rename("isect.wrl", "isect2.wrl");
+
+ if ((wrl = new_vrml("isect.wrl", 0)) == NULL)
+ error("New vrml failed");
+
+ /* The triangles below the BSP */
+ for (i = 0; i < s->nv; i++) {
+ if (!(s->verts[i]->f & GVERT_TRI))
+ continue;
+ wrl->add_vertex(wrl, 0, s->verts[i]->p);
+ }
+ debug_bsp_triangl_wrl(s, np, wrl);
+ wrl->make_triangles(wrl, 0, 0.0, cc);
+
+ /* The segment. The vrml browser can go crazy if the line */
+ /* is too long, so limit it to +/- 100 */
+ {
+ double vbl = icmNorm3(vb);
+ double tt0 = t0, tt1 = t1;
+ if (tt0 > 0.0) {
+ if ((tt0 * vbl) > 100.0)
+ tt0 = 100.0/vbl;
+ } else {
+ if ((tt0 * vbl) < -100.0)
+ tt0 = -100.0/vbl;
+ }
+ if (tt1 > 0.0) {
+ if ((tt1 * vbl) > 100.0)
+ tt1 = 100.0/vbl;
+ } else {
+ if ((tt1 * vbl) < -100.0)
+ tt1 = -100.0/vbl;
+ }
+ for (i = 0; i < 3; i++) {
+ p1[i] = s->cent[i] + vb[i] + tt0 * vv[i];
+ p2[i] = s->cent[i] + vb[i] + tt1 * vv[i];
+ }
+ }
+ wrl->add_col_vertex(wrl, 1, p1, red);
+ wrl->add_col_vertex(wrl, 1, p2, red);
+ wrl->make_lines(wrl, 1, 2);
+
+ /* Add two initial points */
+ for (i = 0; i < 3; i++) {
+ p1[i] = s->cent[i] + vb[i] + 0.0 * vv[i];
+ p2[i] = s->cent[i] + vb[i] + 1.0 * vv[i];
+ }
+ wrl->add_marker(wrl, p1, green, 0.5);
+ wrl->add_marker(wrl, p2, blue, 0.5);
+
+ wrl->del(wrl);
+ printf("Waiting for input after writing 'isect.wrl':\n");
+ getchar();
+
+ }
+#endif
+
+ if (np->tag == 1) { /* It's a BSP node */
+ int j;
+ gbspn *n = (gbspn *)np;
+ double ds;
+ gbsp *n1, *n2; /* next bsp to recurse to */
+ double ti1, ti2; /* Intersection parameters for recursion */
+ double rse1_0, rse1_1, rse2_0, rse2_1;
+
+ ISDBG(("vector_isect_rec at bsp node %d\n"));
+
+ /* Try and compute intersection with BSP */
+
+ den = n->pe[0] * vv[0] + n->pe[1] * vv[1] + n->pe[2] * vv[2];
+ if (fabs(den) > 1e-12) {
+ /* Compute the intersection point */
+ ti = -(n->pe[0] * vb[0] + n->pe[1] * vb[1] + n->pe[2] * vb[2] + n->pe[3])/den;
+ ISDBG(("intersects BSP plane at ti %f\n",ti));
+ }
+ if (fabs(den) < 1e-12 || ti < (t0 - 1e-6) || ti > (t1 + 1e-6)) { /* Doesn't intersect */
+ double ds; /* or intersects outside segment */
+
+ ISDBG(("doesn't intersect BSP plane within segment\n"));
+ /* Figure which side of the BSP segment is on */
+ ds = n->pe[0] * (vb[0] + 0.5 * (t0 + t1) * vv[0])
+ + n->pe[1] * (vb[1] + 0.5 * (t0 + t1) * vv[1])
+ + n->pe[2] * (vb[2] + 0.5 * (t0 + t1) * vv[2])
+ + n->pe[3];
+
+ ISDBG(("recursing down side that segment is on\n"));
+ /* And recurse approproately if it can be improved */
+ /* ???? Shouldn't we recurse down both if ds ~= 0.0 ? */
+ if (ds >= 0.0) {
+ if (rse0 <= n->po->rs1
+ && rse1 >= n->po->rs0
+ && (ll > 0 || t0 < lp[0].pv || t1 > lp[1].pv))
+ vector_isect_rec(s, n->po, vb, vv, t0, rs0, t1, rs1, tc, rsc, rse0, rse1,
+ lp, ll, lu);
+ } else {
+ if (rse0 <= n->ne->rs1
+ && rse1 >= n->ne->rs0
+ && (ll > 0 || t0 < lp[0].pv || t1 > lp[1].pv))
+ vector_isect_rec(s, n->ne, vb, vv, t0, rs0, t1, rs1, tc, rsc, rse0, rse1,
+ lp, ll, lu);
+ }
+ ISDBG(("vector_isect_rec returning\n"));
+ return;
+ }
+
+ /* Compute radius squared to center point at split point */
+ for (rsi = 0.0, j = 0; j < 3; j++) {
+ den = vb[j] + ti * vv[j];
+ rsi += den * den;
+ }
+
+ /* Compute the effective radius squared range for each segment */
+ rse1_0 = rs0;
+ rse1_1 = rs0;
+ if (rsi < rse1_0)
+ rse1_0 = rsi;
+ if (rsi > rse1_1)
+ rse1_1 = rsi;
+ if (tc >= t0 && tc <= ti) { /* Closest point is within segment */
+ if (rsc < rse1_0)
+ rse1_0 = rsc;
+ if (rsc > rse1_1)
+ rse1_1 = rsc;
+ }
+
+ rse2_0 = rsi;
+ rse2_1 = rsi;
+ if (rs1 < rse2_0)
+ rse2_0 = rs1;
+ if (rs1 > rse2_1)
+ rse2_1 = rs1;
+ if (tc >= ti && tc <= t1) { /* Closest point is within segment */
+ if (rsc < rse2_0)
+ rse2_0 = rsc;
+ if (rsc > rse2_1)
+ rse2_1 = rsc;
+ }
+
+ /* Test t0-1.0 to see what side of the BSP t0..ti is. */
+ ip[0] = vb[0] + (t0-1.0) * vv[0];
+ ip[1] = vb[1] + (t0-1.0) * vv[1];
+ ip[2] = vb[2] + (t0-1.0) * vv[2];
+
+ ds = n->pe[0] * ip[0]
+ + n->pe[1] * ip[1]
+ + n->pe[2] * ip[2]
+ + n->pe[3];
+
+ /* Because we're intersecting a line segment, we don't */
+ /* have to recurse down both sides of the BSP tree ? */
+ if (ds >= 0.0) {
+ n1 = n->po;
+ n2 = n->ne;
+ } else {
+ n1 = n->ne;
+ n2 = n->po;
+ }
+
+ /* Make sure that touching segments get properly tested */
+ ti1 = ti + 1e-7;
+ ti2 = ti - 1e-7;
+
+ /* Split the line into two segments and recurse for each. */
+ /* Don't recurse if the line can't improve either min or max. */
+ if (rse1_0 <= n1->rs1
+ && rse1_1 >= n1->rs0
+ && (ll > 0 || t0 < lp[0].pv || ti1 > lp[1].pv)) {
+ ISDBG(("recursing segment 1/2 %f .. %f\n",t0,ti1));
+ vector_isect_rec(s, n1, vb, vv, t0, rs0, ti1, rsi, tc, rsc, rse1_0, rse1_1,
+ lp, ll, lu);
+ }
+#ifdef INTERSECT_DEBUG
+ else if (deb_insect) {
+ printf("Skipped seg 1/2 because rse1_0 %f > n1->rs1 %f ? || rse1_1 %f < n1->rs0 %f\n",rse1_0,n1->rs1,rse1_1, n1->rs0);
+ if (ll == 0)
+ printf("|| ti1 %f <= t0 %f ? || ti1 %f <= omxt %f && t0 %f <= omnt %f ?\n",ti1,t0,ti1,lp[1].pv,t0,lp[0].pv);
+ else
+ printf("|| ti1 %f <= t0 %f ?\n",ti1,t0);
+ }
+#endif
+ if (rse2_0 <= n2->rs1
+ && rse2_1 >= n2->rs0
+ && (ll > 0 || ti2 < lp[0].pv || t1 > lp[1].pv)) {
+ ISDBG(("recursing segment 2/2 %f .. %f\n",ti2,t1));
+ vector_isect_rec(s, n2, vb, vv, ti2, rsi, t1, rs1, tc, rsc, rse2_0, rse2_1,
+ lp, ll, lu);
+ }
+#ifdef INTERSECT_DEBUG
+ else if (deb_insect) {
+ printf("Skipped seg 2/2 because rse2_0 %f > n2->rs1 %f ? || rse2_1 %f < n2->rs0 %f\n",rse2_0,n2->rs1,rse2_1, n2->rs0);
+ if (ll == 0)
+ printf("|| t1 %f <= ti2 %f ? || t1 %f <= omxt %f && ti2 %f <= omnt %f ?\n",t1,ti2,t1,lp[1].pv,ti2,lp[0].pv);
+ else
+ printf("|| t1 %f <= ti2 %f ?\n",t1,ti2);
+ }
+#endif
+
+ ISDBG(("vector_isect_rec returning\n"));
+ return;
+
+ /* It's a list of triangles */
+ } else {
+ int nt; /* Number of triangles in list */
+ gtri **tpp; /* Pointer to list of triangles */
+ int i, j;
+
+ if (np->tag == 2) { /* It's a triangle */
+ tpp = (gtri **)&np;
+ nt = 1;
+ } else if (np->tag == 3) { /* It's a triangle list */
+ gbspl *n = (gbspl *)np;
+ tpp = n->t;
+ nt = n->nt;
+ }
+ ISDBG(("vector_isect_rec at triangle(s) %d\n",nt));
+ /* Go through the list of triangles and intersect with vector */
+ for (i = 0; i < nt; i++, tpp++) {
+ double bds;
+ gtri *t = *tpp;
+
+ ISDBG(("triangle no %d\n",t->n));
+
+ den = t->pe[0] * vv[0] + t->pe[1] * vv[1] + t->pe[2] * vv[2];
+ if (fabs(den) < 1e-12) {
+ ISDBG(("segment is tangent to triangle\n"));
+ continue;
+ }
+
+ /* Compute the intersection of vector with the BSP plane */
+ ti = -(t->pe[0] * (vb[0] + s->cent[0])
+ + t->pe[1] * (vb[1] + s->cent[1])
+ + t->pe[2] * (vb[2] + s->cent[2])
+ + t->pe[3])/den;
+ ISDBG(("segment intersects at %f\n",ti));
+
+ /* Compute the actual (center relative) intersection point */
+ ip[0] = vb[0] + ti * vv[0];
+ ip[1] = vb[1] + ti * vv[1];
+ ip[2] = vb[2] + ti * vv[2];
+ ISDBG(("triangle intersection point %f %f %f\n",ip[0]+s->cent[0],ip[1]+s->cent[1],ip[2]+s->cent[2]));
+
+ /* Check if the intersection point is within the triangle */
+ bds = -1e6;
+ for (j = 0; j < 3; j++) {
+ double ds;
+ ds = t->ee[j][0] * ip[0]
+ + t->ee[j][1] * ip[1]
+ + t->ee[j][2] * ip[2]
+ + t->ee[j][3];
+ if (ds > 1e-8)
+ break; /* Not within triangle */
+ if (ds > bds)
+ bds = ds;
+ }
+ if (j < 3) {
+ ISDBG(("intersection not within triangle\n"));
+ continue; /* Not within triangle, so ignore */
+ }
+
+ /* Add intersection to list */
+ if (ll > 0) { /* List of all */
+ if (*lu < ll) {
+ lp[*lu].pv = ti;
+ icmAdd3(lp[*lu].ip,ip,s->cent); /* Abs. intersection point */
+ lp[*lu].dir = den > 0.0 ? 1 : 0;
+ lp[*lu].edge = bds > 0.0 ? 1 : 0;
+ lp[*lu].tri = t;
+ ISDBG(("new isect %d: pv %f, dir %d, edge %d\n",*lu,ti,lp[*lu].dir,lp[*lu].edge));
+ (*lu)++;
+ } else {
+ ISDBG(("new isect %d: List Too Short %d!!!\n",*lu,ll));
+ }
+ } else { /* Bigest/smallest list of 2 */
+ if (ti < lp[0].pv) {
+ ISDBG(("new min %f\n",ti));
+ lp[0].pv = ti;
+ icmAdd3(lp[0].ip,ip,s->cent); /* Abs. intersection point */
+ lp[0].dir = den > 0.0 ? 1 : 0;
+ lp[0].edge = bds > 0.0 ? 1 : 0;
+ lp[0].tri = t;
+ }
+ if (ti > lp[1].pv) {
+ ISDBG(("new max %f\n",ti));
+ lp[1].pv = ti;
+ icmAdd3(lp[1].ip,ip,s->cent); /* Abs. intersection point */
+ lp[1].dir = den > 0.0 ? 1 : 0;
+ lp[1].edge = bds > 0.0 ? 1 : 0;
+ lp[1].tri = t;
+ }
+ }
+ }
+ ISDBG(("vector_isect_rec returning\n"));
+ return;
+ }
+}
+
+/* Re-evaluate the intersections with an offset */
+static void reevaluate_isectns(
+gamut *s,
+gispnt *lp, /* List to set intersections in. */
+int n, /* Number to re-evaluate */
+double offset, /* Amount to offset vector */
+double *ivb, /* Center relative base point of vector */
+double *vv /* Vector direction from base */
+) {
+ double vb[3]; /* Offset vb */
+ double sv;
+ int sj, j, i;
+
+ /* Decide which way to offset base */
+ sv = -1e20;
+ for (j = 0; j < 3; j++) {
+ if (fabs(vv[j]) > sv) { /* Locate largest direction */
+ sv = fabs(vv[j]);
+ sj = j;
+ }
+ }
+
+ /* Apply the offset to other than largest */
+ for (j = 0; j < 3; j++) {
+ vb[j] = ivb[j];
+ if (j == sj)
+ continue;
+ vb[j] += offset;
+ }
+
+ for (i = 0; i < n; i++) {
+ double den, ti, ip[3], bds;
+ gtri *t = lp[i].tri;
+
+ lp[i].dir = 0;
+ lp[i].edge = 2; /* Assume no intersect */
+
+ den = t->pe[0] * vv[0] + t->pe[1] * vv[1] + t->pe[2] * vv[2];
+ if (fabs(den) < 1e-12) {
+ continue;
+ }
+
+ /* Compute the intersection of vector with the BSP plane */
+ ti = -(t->pe[0] * (vb[0] + s->cent[0])
+ + t->pe[1] * (vb[1] + s->cent[1])
+ + t->pe[2] * (vb[2] + s->cent[2])
+ + t->pe[3])/den;
+
+ /* Compute the actual intersection point */
+ ip[0] = vb[0] + ti * vv[0];
+ ip[1] = vb[1] + ti * vv[1];
+ ip[2] = vb[2] + ti * vv[2];
+
+ /* Check if the intersection point is within the triangle */
+ bds = -1e6;
+ for (j = 0; j < 3; j++) {
+ double ds;
+ ds = t->ee[j][0] * ip[0]
+ + t->ee[j][1] * ip[1]
+ + t->ee[j][2] * ip[2]
+ + t->ee[j][3];
+ if (ds > 1e-8)
+ break; /* Not within triangle */
+ if (ds > bds)
+ bds = ds;
+ }
+ if (j < 3) {
+ continue; /* Not within triangle, so ignore */
+ }
+
+ /* Update intersection info */
+ lp[i].dir = den > 0.0 ? 1 : 0;
+ lp[i].edge = bds > 0.0 ? 1 : 0;
+ }
+}
+
+/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
+#ifdef INTERSECT_VERIFY
+#define compute_vector_isect _compute_vector_isect
+#endif
+
+/* Given a vector, find the two extreme intersection with */
+/* the gamut surface. */
+/* BSP accellerated version */
+/* Return 0 if there is no intersection */
+static int compute_vector_isect(
+gamut *s,
+double *p1, /* First point (ie param value 0.0) */
+double *p2, /* Second point (ie param value 1.0) */
+double *omin, /* Return gamut surface points, min = closest to p1 */
+double *omax, /* max = farthest from p1 */
+double *omnt, /* Return parameter values for p1 and p2, 0 being at p1, */
+double *omxt, /* and 1 being at p2 */
+gtri **omntri, /* Return the intersection triangles */
+gtri **omxtri
+) {
+ gtri *tp;
+ double vb[3], vv[3]; /* Center relative base of vector, vector of vector */
+ double tt, t0, rs0, t1, rs1, tc, rsc;
+ double rse0, rse1; /* Effective radius squared min & max */
+ gispnt islist[2]; /* min and max result */
+ int lu = 0, j;
+ int rv = 0;
+
+ if IS_LIST_EMPTY(s->tris)
+ triangulate(s);
+
+ if (s->lu_inited == 0)
+ init_lu(s); /* Init BSP search tree */
+
+ /* Convert twp points to center relative base + vector direction */
+ for (tt = 0.0, j = 0; j < 3; j++) {
+ vv[j] = p2[j] - p1[j];
+ tt += vv[j] * vv[j];
+ vb[j] = p1[j] - s->cent[j]; /* relative to gamut center */
+ }
+ /* If vector is too small to have a valid direction */
+ if (tt < 1e-12) {
+ return 0;
+ }
+
+ islist[0].pv = 1e68;
+ islist[1].pv = -1e68; /* Setup to find min/max */
+ t0 = -1e6;
+ t1 = 1e6;
+
+ /* Compute radius range of segment */
+ for (rs0 = rs1 = 0.0, j = 0; j < 3; j++) {
+ tt = vb[j] + t0 * vv[j];
+ rs0 += tt * tt;
+ tt = vb[j] + t1 * vv[j];
+ rs1 += tt * tt;
+ }
+
+ /* Compute the point closest to the center */
+ tc = -(vv[0] * vb[0] + vv[1] * vb[1] + vv[2] * vb[2])
+ / (vv[0] * vv[0] + vv[1] * vv[1] + vv[2] * vv[2]);
+
+#ifdef INTERSECT_VERIFY
+ /* Check that this is correct */
+ for (tt = 0.0, j = 0; j < 3; j++) {
+ double pp;
+ pp = vb[j] + tc * vv[j];
+ tt += pp * vv[j];
+ }
+ if (fabs(tt) > 1e-5)
+ error("Failed to locate closest point on vector");
+#endif /* INTERSECT_VERIFY */
+
+ for (rsc = 0.0, j = 0; j < 3; j++) {
+ tt = vb[j] + tc * vv[j];
+ rsc += tt * tt;
+ }
+
+ /* Compute the effective min/max radius squared */
+ rse0 = rs0;
+ rse1 = rs0;
+ if (rs1 < rse0)
+ rse0 = rs1;
+ if (rs1 > rse1)
+ rse1 = rs1;
+ if (tc >= t0 && tc <= t1) { /* Closest point is within segment */
+ if (rsc < rse0)
+ rse0 = rsc;
+ if (rsc > rse1)
+ rse1 = rsc;
+ }
+
+ vector_isect_rec(s, s->lutree, vb, vv, t0, rs0, t1, rs1, tc, rsc, rse0, rse1,
+ islist, 0, &lu);
+
+ /* If we failed to locate a requested intersection */
+ if (((omin != NULL || omnt != NULL || omntri != NULL) && islist[0].pv == 1e68)
+ || ((omax != NULL || omxt != NULL || omxtri != NULL) && islist[1].pv == -1e68)) {
+ rv = 0;
+
+ } else {
+
+ if (omin != NULL) {
+ for (j = 0; j < 3; j++)
+ icmCpy3(omin,islist[0].ip);
+ ISDBG(("Fast min = %f %f %f\n", omin[0], omin[1], omin[2]));
+ }
+
+ if (omax != NULL) {
+ icmCpy3(omax,islist[1].ip);
+ ISDBG(("Fast max = %f %f %f\n", omax[0], omax[1], omax[2]));
+ }
+
+ if (omnt != NULL)
+ *omnt = islist[0].pv;
+
+ if (omxt != NULL)
+ *omxt = islist[1].pv;
+
+ if (omntri != NULL)
+ *omntri = islist[0].tri;
+
+ if (omxtri != NULL)
+ *omxtri = islist[1].tri;
+
+ rv = 1;
+ }
+
+ return rv;
+}
+
+#ifdef INTERSECT_VERIFY
+#undef compute_vector_isect
+
+/* Verifying version of above */
+static int compute_vector_isect(
+gamut *s,
+double *p1, /* First point (ie param value 0.0) */
+double *p2, /* Second point (ie param value 1.0) */
+double *omin, /* Return gamut surface points, min = closest to p1 */
+double *omax, /* max = farthest from p1 */
+double *omnt, /* Return parameter values for p1 and p2, 0 being at p1, */
+double *omxt, /* and 1 being at p2 */
+gtri **omintri, /* Return the intersection triangles */
+gtri **omaxtri
+) {
+ int rv, _rv;
+ double _omin[3];
+ double _omax[3];
+ double _omnt;
+ double _omxt;
+ gtri *_omintri;
+ gtri *_omaxtri;
+ int fail = 0;
+
+ ISDBG(("\n\n###########################################\n"));
+
+ /* Call the routine we're checking */
+ rv = _compute_vector_isect(s, p1, p2, omin, omax, omnt, omxt, omintri, omaxtri);
+
+ _rv = compute_vector_isect_bf(s, p1, p2, _omin, _omax, &_omnt, &_omxt, &_omintri, &_omaxtri);
+
+ if (rv != _rv) {
+ warning("compute_vector_isect verify: rv %d != _rv %d\n",rv,_rv);
+ fail = 1;
+ }
+
+ if (rv == 1) {
+ int j;
+ if (omnt != NULL)
+ if (fabs (*omnt - _omnt) > 1e-4) {
+ warning("compute_vector_isect verify:\n omnt %f != _omnt %f\n",*omnt,_omnt);
+ fail = 1;
+ }
+ if (omxt != NULL)
+ if (fabs (*omxt - _omxt) > 1e-4) {
+ warning("compute_vector_isect verify:\n omxt %f != _omxt %f\n",*omxt,_omxt);
+ fail = 1;
+ }
+ if (omin != NULL) {
+ ISDBG(("bf min = %f %f %f\n", _omin[0], _omin[1], _omin[2]));
+ for (j = 0; j < 3; j++) {
+ if (fabs (omin[j] - _omin[j]) > 1e-4)
+ break;
+ }
+ if (j < 3) {
+ warning("compute_vector_isect verify:\n omin %f %f %f != _omin %f %f %f\n", omin[0], omin[1], omin[2], _omin[0], _omin[1], _omin[2]);
+ fail = 1;
+ }
+ }
+ if (omax != NULL) {
+ ISDBG(("bf max = %f %f %f\n", _omax[0], _omax[1], _omax[2]));
+ for (j = 0; j < 3; j++) {
+ if (fabs (omax[j] - _omax[j]) > 1e-4)
+ break;
+ }
+ if (j < 3) {
+ warning("compute_vector_isect verify:\n omax %f %f %f != _omax %f %f %f\n", omax[0], omax[1], omax[2], _omax[0], _omax[1], _omax[2]);
+ fail = 1;
+ }
+ }
+#ifdef NEVER
+ if (omintri != NULL)
+ if (*omintri != _omintri) {
+ warning("compute_vector_isect verify:\n omintri %d != _omintri %d\n",(*omintri)->n, _omintri->n);
+ }
+ if (omaxtri != NULL)
+ if (*omaxtri != _omaxtri) {
+ warning("compute_vector_isect verify:\n omaxtri %d != _omaxtri %d\n",(*omaxtri)->n, _omaxtri->n);
+ }
+#endif /* NEVER */
+ }
+ if (fail) {
+#ifdef INTERSECT_DEBUG
+ printf("Re-running intersect with debug trace on\n");
+ deb_insect = 1;
+ _compute_vector_isect(s, p1, p2, _omin, _omax, &_omnt, &_omxt, &_omintri, &_omaxtri);
+#endif /* INTERSECT_DEBUG */
+ error("Verify failed");
+ }
+ return rv;
+}
+
+#endif /* INTERSECT_VERIFY */
+
+/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
+/* Compute all the intersection pairs of the vector p1->p2 with */
+/* the gamut surface. lp points to an array of ll gispnt to be */
+/* filled in. If the list is too small, intersections will be */
+/* arbitrarily ignored. */
+/* Return the number of intersections set in list. This will always be even, */
+/* and there will be pairs of in/out intersections in the direction p1->p2. */
+static int compute_vector_isectns(
+gamut *s,
+double *p1, /* First point (ie param value 0.0) */
+double *p2, /* Second point (ie param value 1.0) */
+gispnt *lp, /* List to return in/out intersection pairs */
+int ll /* Size of list. */
+) {
+ gtri *tp;
+ double vb[3], vv[3]; /* Center relative base of vector, vector of vector */
+ double tt, t0, rs0, t1, rs1, tc, rsc, vscale;
+ double rse0, rse1; /* Effective radius squared min & max */
+ int lu = 0, i, j, k, m, pdir;
+ int rv = 0;
+
+#ifdef INTERSECT_DEBUG
+ printf("compute_vector_isectns p1 %f %f %f, p2 %f %f %f\n", p1[0], p1[1], p1[2], p2[0], p2[1], p2[2]);
+#endif
+ if IS_LIST_EMPTY(s->tris)
+ triangulate(s);
+
+ if (s->lu_inited == 0)
+ init_lu(s); /* Init BSP search tree */
+
+ /* Convert twp points to relative base + vector direction */
+ for (tt = 0.0, j = 0; j < 3; j++) {
+ vv[j] = p2[j] - p1[j];
+ tt += vv[j] * vv[j];
+ vb[j] = p1[j] - s->cent[j]; /* relative to gamut center */
+ }
+ /* If vector is too small to have a valid direction, give up */
+ if (tt < 1e-12) {
+#ifdef INTERSECT_DEBUG
+ printf("p2 too close to p1\n");
+#endif
+ return 0;
+ }
+
+ /* Scale factor to make parameter delta = gamut space unit */
+ vscale = 1.0/sqrt(tt);
+
+ t0 = -1e6 * vscale; /* Set the parameter search space */
+ t1 = 1e6 * vscale;
+
+ /* Compute radius range of segment */
+ for (rs0 = rs1 = 0.0, j = 0; j < 3; j++) {
+ tt = vb[j] + t0 * vv[j];
+ rs0 += tt * tt;
+ tt = vb[j] + t1 * vv[j];
+ rs1 += tt * tt;
+ }
+
+ /* Compute the point closest to the center */
+ tc = -(vv[0] * vb[0] + vv[1] * vb[1] + vv[2] * vb[2])
+ / (vv[0] * vv[0] + vv[1] * vv[1] + vv[2] * vv[2]);
+
+#ifdef INTERSECT_DEBUG
+ /* Check that this is correct */
+ for (tt = 0.0, j = 0; j < 3; j++) {
+ double pp;
+ pp = vb[j] + tc * vv[j];
+ tt += pp * vv[j];
+ }
+ if (fabs(tt) > 1e-5)
+ error("Failed to locate closest point on vector");
+#endif /* INTERSECT_DEBUG */
+
+ for (rsc = 0.0, j = 0; j < 3; j++) {
+ tt = vb[j] + tc * vv[j];
+ rsc += tt * tt;
+ }
+
+ /* Compute the effective min/max radius squared */
+ rse0 = rs0;
+ rse1 = rs0;
+ if (rs1 < rse0)
+ rse0 = rs1;
+ if (rs1 > rse1)
+ rse1 = rs1;
+ if (tc >= t0 && tc <= t1) { /* Closest point is within segment */
+ if (rsc < rse0)
+ rse0 = rsc;
+ if (rsc > rse1)
+ rse1 = rsc;
+ }
+
+ /* Recursively locate all the triangle intersections using BSP */
+ vector_isect_rec(s, s->lutree, vb, vv, t0, rs0, t1, rs1, tc, rsc, rse0, rse1,
+ lp, ll, &lu);
+
+ if (lu <= 1) {
+#ifdef INTERSECT_DEBUG
+ printf("%d intersections found\n",lu);
+#endif
+ return 0; /* Too few to be useful */
+ }
+
+ /* Now we need to turn th raw intersections into sanitized segment pairs. */
+
+ /* Sort the intersections by parameter value */
+#define HEAP_COMPARE(A,B) (A.pv < B.pv)
+ HEAPSORT(gispnt, lp, lu)
+#undef HEAP_COMPARE
+
+#ifdef INTERSECT_DEBUG
+ printf("Before sanitizing %d\n",lu);
+ for (i = 0; i < lu; i++)
+ printf("Isect %d: pv %f, dir %d, edge %d, tri %d\n",i,lp[i].pv,lp[i].dir,lp[i].edge,lp[i].tri->n);
+#endif
+
+ /* Remove any duplicate intersections (triangles) */
+ for (j = i = 0; i < lu; i++) {
+
+ for (k = i+1; k < lu; k++) {
+ if (lp[k].tri == lp[i].tri) {
+ lp[k].edge &= lp[i].edge; /* Keep non-edge status */
+ break;
+ }
+ }
+ if (k < lu)
+ continue; /* Skip this one */
+
+ /* Accept this intersection */
+ memmove(&lp[j], &lp[i], sizeof(gispnt));
+ j++;
+ }
+ lu = j;
+
+ if (lu <= 1) {
+#ifdef INTERSECT_DEBUG
+ printf("%d intersections after removing duplicates\n",lu);
+#endif
+ return 0; /* Too few to be useful */
+ }
+
+#ifdef INTERSECT_DEBUG
+ printf("After removing duplicates %d\n",lu);
+ for (i = 0; i < lu; i++) {
+ printf("Isect %d: pv %f, dir %d, edge %d, tri %d\n",i,lp[i].pv,lp[i].dir,lp[i].edge,lp[i].tri->n);
+ }
+#endif
+
+ /* Sanitize the intersections. */
+ /* We must end up with in/out segment pairs. */
+ /* j = output index, i = current index */
+ pdir = 0; /* Previous isection direction = "out" */
+ for (j = i = 0; i < lu;) {
+ int nin, nout; /* Number fully in/out */
+ int inx, outx; /* Indexes of representative in/out */
+ int npin, npout; /* Number partially in/out */
+ int pinx, poutx; /* Indexes of representative in/out */
+
+//printf("~1 at %d out of %d, %d saved\n",i,lu,j);
+ /* Two tries, re-evaluate before second try */
+ for (m = 0; m < 2; m++) {
+
+ /* See how many we have at the next pv, and */
+ /* decide if they're in, or out or both. */
+ nin = nout = npin = npout = 0;
+ for (k = i; k < lu; k++) {
+ if (i != k && fabs((lp[i].pv - lp[k].pv) * vscale) >= 0.0001)
+ break;
+ if (lp[k].dir) { /* In */
+ if (lp[k].edge == 0) {
+ nin++;
+ inx = k;
+ } else if (lp[k].edge == 1) {
+ npin++;
+ pinx = k;
+ }
+ } else { /* Out */
+ if (lp[k].edge == 0) {
+ nout++;
+ outx = k;
+ } else if (lp[k].edge == 1) {
+ npout++;
+ poutx = k;
+ }
+ }
+ }
+
+ if (m == 1 /* We've already re-evaluated */
+ || (k - i) <= 2 /* Not worth re-evaluating */
+ || (npin == 0 && npout == 0)) /* All definite */
+ break;
+
+ /* Re-evaluate the intersections with an offset. */
+ /* (We need this because often the point of interest */
+ /* is a vertex, and there are lots of intersections */
+ /* with the edges of the triangles that share that vertex) */
+ reevaluate_isectns(s, lp+i, k-i, 1e-5, vb, vv);
+
+#ifdef INTERSECT_DEBUG
+ {
+ int ii;
+ printf("After re-evaluating intersections\n");
+ for (ii = i; ii < k; ii++)
+ printf("Isect %d: pv %f, dir %d, edge %d, tri %d\n",ii,lp[ii].pv,lp[ii].dir,lp[ii].edge,lp[ii].tri->n);
+ }
+#endif
+ }
+
+//printf("~1 nin %d, nout %d, npin %d, npout %d\n",nin,nout,npin,npout);
+ /* Create a zero length segment */
+ if ((k-i) > 1
+ && ((nin >= 1 && nout >= 1) /* both are definite */
+ || (nin == 0 && nout == 0 && npin >= 1 && npout >= 1) /* Both glancing */
+ || (nin == 0 && nout == 0 && npin == 0 && npout == 0)) /* No intersections now */
+ ) {
+ if (pdir != 0) { /* Not correct for segment */
+ i = k;
+//printf("~1 neither or both or uncertain\n");
+ continue; /* Discard them all */
+ }
+//printf("~1 creating zero length segment\n");
+ /* Hmm. For reasonable triangles we should really */
+ /* grab in/out from original evaluation... */
+ memmove(&lp[j], &lp[i], sizeof(gispnt));
+ lp[j].dir = 1;
+ lp[j].edge = 1;
+ j++;
+ memmove(&lp[j], &lp[i+1], sizeof(gispnt));
+ lp[j].dir = 0;
+ lp[j].edge = 1;
+ j++;
+ i = k;
+ continue;
+ }
+
+ /* We expect one conclusion */
+ if (nin >= 1)
+ i = inx;
+ else if (nout >= 1)
+ i = outx;
+ else if (npin >= 1)
+ i = pinx;
+ else /* npout >= 1 */
+ i = poutx;
+//printf("~1 using %d\n",i);
+
+ if ((lp[i].dir ^ pdir) == 0) { /* Not opposite to previous */
+//printf("~1 not opposite, discard it\n");
+ /* This shouldn't happen. */
+ i = k;
+ continue; /* Discard it */
+ }
+//printf("~1 save %d\n",i);
+ /* Accept this intersection */
+ memmove(&lp[j], &lp[i], sizeof(gispnt));
+ pdir = lp[j].dir;
+ j++;
+ i = k;
+ }
+ if (j & 1) /* Hmm. We ended up odd. This shouldn't happen. */
+ j--;
+ rv = j;
+
+#ifdef INTERSECT_DEBUG
+ if (rv == 0)
+ printf("No intersections left\n");
+ else {
+ printf("After sanitizing %d\n",rv);
+ for (i = 0; i < rv; i++)
+ printf("Isect %d: pv %f, dir %d, edge %d, tri %d\n",i,lp[i].pv,lp[i].dir,lp[i].edge,lp[i].tri->n);
+ }
+#endif
+ return rv;
+}
+
+#ifdef INTERSECT_DEBUG
+#undef ISDBG
+#endif /* INTERSECT_DEBUG */
+
+/* ===================================================== */
+/* Write to a VRML .wrl file */
+/* Return non-zero on error */
+static int write_vrml(
+gamut *s,
+char *filename,
+int doaxes, /* Non-zero if axes are to be written */
+int docusps /* Non-zero if cusp points are to be marked */
+) {
+ return write_trans_vrml(s, filename, doaxes, docusps, NULL, NULL);
+}
+
+/* Write to a VRML .wrl file */
+/* Return non-zero on error */
+static int write_trans_vrml(
+gamut *s,
+char *filename,
+int doaxes, /* Non-zero if axes are to be written */
+int docusps, /* Non-zero if cusp points are to be marked */
+void (*transform)(void *cntx, double out[3], double in[3]), /* Optional transformation callback */
+void *cntx
+) {
+ int i;
+ gtri *tp; /* Triangle pointer */
+ FILE *wrl;
+ struct {
+ double x, y, z;
+ double wx, wy, wz;
+ double r, g, b;
+ } axes[5] = {
+ { 0 - s->cent[1], 0 - s->cent[2], 50 - s->cent[0], 2, 2, 100, .7, .7, .7 },
+ /* L axis */
+ { 50 - s->cent[1], 0 - s->cent[2], 0 - s->cent[0], 100, 2, 2, 1, 0, 0 },
+ /* +a (red) axis */
+ { 0 - s->cent[1], -50 - s->cent[2], 0 - s->cent[0], 2, 100, 2, 0, 0, 1 },
+ /* -b (blue) axis */
+ { -50 - s->cent[1], 0 - s->cent[2], 0 - s->cent[0], 100, 2, 2, 0, 1, 0 },
+ /* -a (green) axis */
+ { 0 - s->cent[1], 50 - s->cent[2], 0 - s->cent[0], 2, 100, 2, 1, 1, 0 },
+ /* +b (yellow) axis */
+ };
+
+ /* Define the labels */
+ struct {
+ double x, y, z;
+ double size;
+ char *string;
+ double r, g, b;
+ } labels[6] = {
+ { -2 - s->cent[1], 2 - s->cent[2], - s->cent[0] + 100 + 10, 10, "+L*", .7, .7, .7 },
+ /* Top of L axis */
+ { -2 - s->cent[1], 2 - s->cent[2], - s->cent[0] - 10, 10, "0", .7, .7, .7 },
+ /* Bottom of L axis */
+ { 100 + 5 - s->cent[1], -3 - s->cent[2], 0 - s->cent[0], 10, "+a*", 1, 0, 0 },
+ /* +a (red) axis */
+ { -5 - s->cent[1], -100 - 10 - s->cent[2], 0 - s->cent[0], 10, "-b*", 0, 0, 1 },
+ /* -b (blue) axis */
+ { -100 - 15 - s->cent[1], -3 - s->cent[2], 0 - s->cent[0], 10, "-a*", 0, 0, 1 },
+ /* -a (green) axis */
+ { -5 - s->cent[1], 100 + 5 - s->cent[2], 0 - s->cent[0], 10, "+b*", 1, 1, 0 },
+ /* +b (yellow) axis */
+ };
+
+ if IS_LIST_EMPTY(s->tris)
+ triangulate(s);
+
+ if ((wrl = fopen(filename,"w")) == NULL) {
+ fprintf(stderr,"Error opening output file '%s'\n",filename);
+ return 2;
+ }
+
+ /* Spit out a VRML 2 Object surface of gamut */
+
+ fprintf(wrl,"#VRML V2.0 utf8\n");
+ fprintf(wrl,"\n");
+ fprintf(wrl,"# Created by the Argyll CMS\n");
+ fprintf(wrl,"Transform {\n");
+ fprintf(wrl,"children [\n");
+ fprintf(wrl," NavigationInfo {\n");
+ fprintf(wrl," type \"EXAMINE\" # It's an object we examine\n");
+ fprintf(wrl," } # We'll add our own light\n");
+ fprintf(wrl,"\n");
+ fprintf(wrl," DirectionalLight {\n");
+ fprintf(wrl," intensity 0.2\n");
+ fprintf(wrl," ambientIntensity 0.1\n");
+ fprintf(wrl," direction -1 -1 -1\n");
+ fprintf(wrl," }\n");
+ fprintf(wrl," DirectionalLight {\n");
+ fprintf(wrl," intensity 0.6\n");
+ fprintf(wrl," ambientIntensity 0.2\n");
+ fprintf(wrl," direction 1 1 1\n");
+ fprintf(wrl," }\n");
+ fprintf(wrl,"\n");
+ fprintf(wrl," Viewpoint {\n");
+ fprintf(wrl," position 0 0 250 # Position we view from\n");
+ fprintf(wrl," }\n");
+ fprintf(wrl,"\n");
+ if (doaxes != 0) {
+ fprintf(wrl,"# Lab axes as boxes:\n");
+ for (i = 0; i < 5; i++) {
+ fprintf(wrl,"Transform { translation %f %f %f\n", axes[i].x, axes[i].y, axes[i].z);
+ fprintf(wrl,"\tchildren [\n");
+ fprintf(wrl,"\t\tShape {\n");
+ fprintf(wrl,"\t\t\tgeometry Box { size %f %f %f }\n",
+ axes[i].wx, axes[i].wy, axes[i].wz);
+ fprintf(wrl,"\t\t\tappearance Appearance { material Material ");
+ fprintf(wrl,"{ diffuseColor %f %f %f} }\n", axes[i].r, axes[i].g, axes[i].b);
+ fprintf(wrl,"\t\t}\n");
+ fprintf(wrl,"\t]\n");
+ fprintf(wrl,"}\n");
+ }
+ fprintf(wrl,"# Axes identification:\n");
+ for (i = 0; i < 6; i++) {
+ fprintf(wrl,"Transform { translation %f %f %f\n", labels[i].x, labels[i].y, labels[i].z);
+ fprintf(wrl,"\tchildren [\n");
+ fprintf(wrl,"\t\tShape {\n");
+ fprintf(wrl,"\t\t\tgeometry Text { string [\"%s\"]\n",labels[i].string);
+ fprintf(wrl,"\t\t\t\tfontStyle FontStyle { family \"SANS\" style \"BOLD\" size %f }\n",
+ labels[i].size);
+ fprintf(wrl,"\t\t\t\t}\n");
+ fprintf(wrl,"\t\t\tappearance Appearance { material Material ");
+ fprintf(wrl,"{ diffuseColor %f %f %f} }\n", labels[i].r, labels[i].g, labels[i].b);
+ fprintf(wrl,"\t\t}\n");
+ fprintf(wrl,"\t]\n");
+ fprintf(wrl,"}\n");
+ }
+ fprintf(wrl,"\n");
+ }
+ fprintf(wrl," Transform {\n");
+ fprintf(wrl," translation 0 0 0\n");
+ fprintf(wrl," children [\n");
+ fprintf(wrl," Shape { \n");
+ fprintf(wrl," geometry IndexedFaceSet {\n");
+ fprintf(wrl," ccw FALSE\n");
+ fprintf(wrl," convex TRUE\n");
+ fprintf(wrl,"\n");
+ fprintf(wrl," coord Coordinate { \n");
+ fprintf(wrl," point [ # Verticy coordinates\n");
+
+ /* Spit out the point values, in order. */
+ /* Note that a->x, b->y, L->z */
+ for (i = 0; i < s->nv; i++) {
+ double out[3];
+
+#ifdef SHOW_BUCKETS /* Show vertex buckets as surface */
+ if (!(s->verts[i]->f & GVERT_SET))
+#else
+ if (!(s->verts[i]->f & GVERT_TRI))
+#endif
+ continue;
+
+#ifdef SHOW_BUCKETS /* Show vertex buckets as surface */
+ {
+ double cc[3], rr[3];
+# ifdef SHOW_SPHERE /* Show surface on sphere */
+ rr[0] = 50.0; /* Sphere radius */
+# else
+ rr[0] = s->verts[i]->r[0],
+# endif /* SHOW_SPHERE */
+
+ rr[1] = s->verts[i]->hc - 0.5 * s->verts[i]->w;
+ rr[2] = s->verts[i]->vc - 0.5 * s->verts[i]->h;
+ gamut_radial2rect(s, cc, rr);
+ fprintf(wrl,"%f %f %f,\n",cc[1], cc[2], cc[0]);
+
+ rr[1] = s->verts[i]->hc - 0.5 * s->verts[i]->w;
+ rr[2] = s->verts[i]->vc + 0.5 * s->verts[i]->h;
+ gamut_radial2rect(s, cc, rr);
+ fprintf(wrl,"%f %f %f,\n",cc[1], cc[2], cc[0]);
+
+ rr[1] = s->verts[i]->hc + 0.5 * s->verts[i]->w;
+ rr[2] = s->verts[i]->vc + 0.5 * s->verts[i]->h;
+ gamut_radial2rect(s, cc, rr);
+ fprintf(wrl,"%f %f %f,\n",cc[1], cc[2], cc[0]);
+
+ rr[1] = s->verts[i]->hc + 0.5 * s->verts[i]->w;
+ rr[2] = s->verts[i]->vc - 0.5 * s->verts[i]->h;
+ gamut_radial2rect(s, cc, rr);
+ fprintf(wrl,"%f %f %f,\n",cc[1], cc[2], cc[0]);
+ }
+
+#else /* Show point data */
+
+# ifdef SHOW_SPHERE /* Show surface on sphere */
+ fprintf(wrl,"%f %f %f,\n",s->verts[i]->sp[1], s->verts[i]->sp[2],
+ s->verts[i]->sp[0]);
+# else
+# ifdef SHOW_HULL_PNTS
+ fprintf(wrl,"%f %f %f,\n",s->verts[i]->ch[1], s->verts[i]->ch[2],
+ s->verts[i]->ch[0]);
+# else
+ /* Show normal gamut surface */
+ out[0] = s->verts[i]->p[0];
+ out[1] = s->verts[i]->p[1];
+ out[2] = s->verts[i]->p[2];
+
+ if (transform)
+ transform(cntx, out, out); /* Do transform */
+
+ fprintf(wrl,"%f %f %f,\n",out[1]-s->cent[1], out[2]-s->cent[2], out[0]-s->cent[0]);
+
+# endif /* SHOW_HULL_PNTS */
+# endif /* SHOW_SPHERE */
+
+#endif /* SHOW_BUCKETS */
+
+ }
+ fprintf(wrl," ]\n");
+ fprintf(wrl," }\n");
+ fprintf(wrl,"\n");
+ fprintf(wrl," coordIndex [ # Indexes of poligon Verticies \n");
+
+#ifdef SHOW_BUCKETS /* Show vertex buckets as surface */
+ for (i = 0; i < s->nv; i++) {
+ int j = s->verts[i]->sn;
+ if (!(s->verts[i]->f & GVERT_SET))
+ continue;
+ fprintf(wrl,"%d, %d, %d, %d, -1\n", j * 4, j * 4 + 1, j * 4 + 2, j * 4 + 3);
+ }
+#else /* Show gamut triangular surface */
+ tp = s->tris;
+ FOR_ALL_ITEMS(gtri, tp) {
+ fprintf(wrl,"%d, %d, %d, -1\n", tp->v[0]->tn, tp->v[1]->tn, tp->v[2]->tn);
+ } END_FOR_ALL_ITEMS(tp);
+#endif /* SHOW_BUCKETS */
+
+ fprintf(wrl," ]\n");
+ fprintf(wrl,"\n");
+ fprintf(wrl," colorPerVertex TRUE\n");
+ fprintf(wrl," color Color {\n");
+ fprintf(wrl," color [ # RGB colors of each vertex\n");
+
+ /* Spit out the colors for each vertex */
+ for (i = 0; i < s->nv; i++) {
+ double rgb[3];
+#ifdef SHOW_BUCKETS /* Show vertex buckets as surface */
+ if (!(s->verts[i]->f & GVERT_SET))
+#else
+ if (!(s->verts[i]->f & GVERT_TRI))
+#endif
+ continue;
+
+#ifdef COLORED_VRML
+ gamut_Lab2RGB(rgb, s->verts[i]->p);
+#else
+ rgb[0] = rgb[1] = rgb[2] = 1.0;
+#endif
+ fprintf(wrl,"%f %f %f,\n", rgb[0], rgb[1], rgb[2]);
+#ifdef SHOW_BUCKETS /* Show vertex buckets as surface */
+ fprintf(wrl,"%f %f %f,\n", rgb[0], rgb[1], rgb[2]);
+ fprintf(wrl,"%f %f %f,\n", rgb[0], rgb[1], rgb[2]);
+ fprintf(wrl,"%f %f %f,\n", rgb[0], rgb[1], rgb[2]);
+#endif /* SHOW_BUCKETS */
+ }
+ fprintf(wrl," ] \n");
+ fprintf(wrl," }\n");
+ fprintf(wrl," }\n");
+ fprintf(wrl," appearance Appearance { \n");
+ fprintf(wrl," material Material {\n");
+ fprintf(wrl," transparency 0.0\n");
+ fprintf(wrl," ambientIntensity 0.3\n");
+ fprintf(wrl," shininess 0.5\n");
+ fprintf(wrl," }\n");
+ fprintf(wrl," }\n");
+ fprintf(wrl," } # end Shape\n");
+ fprintf(wrl," ]\n");
+ fprintf(wrl," }\n");
+
+
+ if (s->gawbset && doaxes) {
+
+ /* Show the gamut white and black points */
+ fprintf(wrl,"\n");
+ fprintf(wrl," Transform {\n");
+ fprintf(wrl," translation %f %f %f\n",s->ga_wp[1]-s->cent[1], s->ga_wp[2]-s->cent[2], s->ga_wp[0]-s->cent[0]);
+ fprintf(wrl," children [\n");
+ fprintf(wrl," Shape { \n");
+ fprintf(wrl," geometry Sphere { radius 2.0 }\n");
+ fprintf(wrl," appearance Appearance { material Material { diffuseColor 0.9 0.9 0.9 } }\n");
+ fprintf(wrl," } \n");
+ fprintf(wrl," ]\n");
+ fprintf(wrl," }\n");
+ fprintf(wrl,"\n");
+ fprintf(wrl," Transform {\n");
+ fprintf(wrl," translation %f %f %f\n",s->ga_bp[1]-s->cent[1], s->ga_bp[2]-s->cent[2], s->ga_bp[0]-s->cent[0]);
+ fprintf(wrl," children [\n");
+ fprintf(wrl," Shape { \n");
+ fprintf(wrl," geometry Sphere { radius 2.0 }\n");
+ fprintf(wrl," appearance Appearance { material Material { diffuseColor 0.9 0.9 0.9 } }\n");
+ fprintf(wrl," } \n");
+ fprintf(wrl," ]\n");
+ fprintf(wrl," }\n");
+ }
+
+ if (docusps && s->cu_inited != 0) {
+ double ccolors[6][3] = {
+ { 1.0, 0.1, 0.1 }, /* Red */
+ { 1.0, 1.0, 0.1 }, /* Yellow */
+ { 0.1, 1.0, 0.1 }, /* Green */
+ { 0.1, 1.0, 1.0 }, /* Cyan */
+ { 0.1, 0.1, 1.0 }, /* Blue */
+ { 1.0, 0.1, 1.0 } /* Magenta */
+ };
+
+ for (i = 0; i < 6; i++) {
+ fprintf(wrl,"\n");
+ fprintf(wrl," Transform {\n");
+ fprintf(wrl," translation %f %f %f\n",s->cusps[i][1]-s->cent[1], s->cusps[i][2]-s->cent[2], s->cusps[i][0]-s->cent[0]);
+ fprintf(wrl," children [\n");
+ fprintf(wrl," Shape { \n");
+ fprintf(wrl," geometry Sphere { radius 2.0 }\n");
+ fprintf(wrl," appearance Appearance { material Material { diffuseColor %f %f %f } }\n", ccolors[i][0],ccolors[i][1],ccolors[i][2]);
+ fprintf(wrl," } \n");
+ fprintf(wrl," ]\n");
+ fprintf(wrl," }\n");
+ }
+ }
+
+#ifdef TEST_LOOKUP
+ {
+ int i, j;
+ double in[3], out[3];
+
+ fprintf(wrl,"\n");
+ fprintf(wrl,"Shape {\n");
+ fprintf(wrl," geometry PointSet { \n");
+ fprintf(wrl," coord Coordinate { \n");
+ fprintf(wrl," point [\n");
+
+ for (i = 0; i < 10; i++) {
+ double ss;
+ /* Create random vector relative to center, absolute */
+ in[0] = (rand() / (double)RAND_MAX) - 0.5 + s->cent[0];
+ in[1] = (rand() / (double)RAND_MAX) - 0.5 + s->cent[1];
+ in[2] = (rand() / (double)RAND_MAX) - 0.5 + s->cent[2];
+
+ s->radial(s, out, in); /* Lookup point on gamut surface */
+
+ out[0] = (out[0] - s->cent[0]) * 1.01 + s->cent[0];
+ out[1] = (out[1] - s->cent[1]) * 1.01 + s->cent[1];
+ out[2] = (out[2] - s->cent[2]) * 1.01 + s->cent[2];
+ fprintf(wrl,"%f %f %f,\n",out[1]-s->cent[1], out[2]-s->cent[2], out[0]-s->cent[0]);
+ }
+ fprintf(wrl," ]\n");
+ fprintf(wrl," }\n");
+ fprintf(wrl," }\n");
+ fprintf(wrl,"} # end shape\n");
+
+ }
+#endif /* TEST_LOOKUP */
+
+#ifdef TEST_NEAREST
+ {
+#define NTPTS 500
+ int i, j;
+ double in[3], out[3];
+
+ fprintf(wrl,"\n");
+ fprintf(wrl,"Shape {\n");
+ fprintf(wrl," geometry IndexedLineSet { \n");
+ fprintf(wrl," coord Coordinate { \n");
+ fprintf(wrl," point [\n");
+
+ for (i = 0; i < NTPTS; i++) {
+ double ss;
+ /* Create random vector relative to center */
+ in[0] = (rand() / (double)RAND_MAX) - 0.5;
+ in[1] = (rand() / (double)RAND_MAX) - 0.5;
+ in[2] = (rand() / (double)RAND_MAX) - 0.5;
+
+#ifndef NEVER /* Make points just above surface */
+ in[0] += s->cent[0]; /* Make absolute */
+ in[1] += s->cent[1];
+ in[2] += s->cent[2];
+ s->radial(s, in, in); /* Lookup point on gamut surface */
+ in[0] = (in[0] - s->cent[0]) * 1.20 + s->cent[0]; /* Extend by 10% */
+ in[1] = (in[1] - s->cent[1]) * 1.20 + s->cent[1];
+ in[2] = (in[2] - s->cent[2]) * 1.20 + s->cent[2];
+#else
+ /* Make distance 150 */
+ ss = sqrt(in[0] * in[0] + in[1] * in[1] + in[2] * in[2]);
+ in[0] = 60.0/ss * in[0] + s->cent[0];
+ in[1] = 60.0/ss * in[1] + s->cent[1];
+ in[2] = 60.0/ss * in[2] + s->cent[2];
+#endif
+
+// s->radial(s, out, in); /* Lookup point on gamut surface */
+
+ s->nearest(s, out, in); /* Nearest point on gamut surface */
+
+ fprintf(wrl,"%f %f %f,\n",in[1]-s->cent[1], in[2]-s->cent[2], in[0]-s->cent[0]);
+ fprintf(wrl,"%f %f %f,\n",out[1]-s->cent[1], out[2]-s->cent[2], out[0]-s->cent[0]);
+ }
+
+ fprintf(wrl," ]\n");
+ fprintf(wrl," }\n");
+ fprintf(wrl," coordIndex [\n");
+
+ for (i = 0; i < NTPTS; i++) {
+ fprintf(wrl,"%d, %d, -1,\n", i * 2, i * 2 + 1);
+ }
+ fprintf(wrl," ]\n");
+ fprintf(wrl," }\n");
+ fprintf(wrl,"} # end shape\n");
+
+ }
+#endif /* TEST_NEAREST */
+
+ fprintf(wrl,"\n");
+ fprintf(wrl," ] # end of children for world\n");
+ fprintf(wrl,"}\n");
+
+ if (fclose(wrl) != 0) {
+ fprintf(stderr,"Error closing output file '%s'\n",filename);
+ return 2;
+ }
+
+ return 0;
+}
+
+
+/* ----------------------------------- */
+/* Write to a CGATS .gam file */
+/* Return non-zero on error */
+static int write_gam(
+gamut *s,
+char *filename
+) {
+ time_t clk = time(0);
+ struct tm *tsp = localtime(&clk);
+ char *atm = asctime(tsp); /* Ascii time */
+ int i;
+ gtri *tp; /* Triangle pointer */
+ cgats *gam;
+ char buf[100];
+
+ if IS_LIST_EMPTY(s->tris)
+ triangulate(s);
+
+ gam = new_cgats(); /* Create a CGATS structure */
+ gam->add_other(gam, "GAMUT");
+
+ gam->add_table(gam, tt_other, 0); /* Start the first table as type "GAMUT" */
+
+ gam->add_kword(gam, 0, "DESCRIPTOR", "Argyll Gamut surface poligon data", NULL);
+ gam->add_kword(gam, 0, "ORIGINATOR", "Argyll CMS gamut library", NULL);
+ atm[strlen(atm)-1] = '\000'; /* Remove \n from end */
+ gam->add_kword(gam, 0, "CREATED",atm, NULL);
+
+#ifdef NEVER
+ /* would be nice to add extra info like description, source (ie icc filename) etc. */
+ gam->add_kword(gam, 0, "DEVICE_CLASS","INPUT", NULL); /* What sort of device this is */
+#endif
+ if (s->isJab)
+ gam->add_kword(gam, 0, "COLOR_REP","JAB", NULL);
+ else
+ gam->add_kword(gam, 0, "COLOR_REP","LAB", NULL);
+
+ if (s->isRast)
+ gam->add_kword(gam, 0, "SURF_TYPE","RASTER", NULL);
+
+ sprintf(buf,"%f %f %f", s->cent[0], s->cent[1], s->cent[2]);
+ gam->add_kword(gam, 0, "GAMUT_CENTER",buf, NULL);
+
+ /* If the white and black points are known, put them in the file */
+ if (s->cswbset) {
+
+ compgawb(s); /* make sure we have gamut white/black available */
+
+ sprintf(buf,"%f %f %f", s->cs_wp[0], s->cs_wp[1], s->cs_wp[2]);
+ gam->add_kword(gam, 0, "CSPACE_WHITE",buf, NULL);
+
+ sprintf(buf,"%f %f %f", s->ga_wp[0], s->ga_wp[1], s->ga_wp[2]);
+ gam->add_kword(gam, 0, "GAMUT_WHITE",buf, NULL);
+
+ sprintf(buf,"%f %f %f", s->cs_bp[0], s->cs_bp[1], s->cs_bp[2]);
+ gam->add_kword(gam, 0, "CSPACE_BLACK",buf, NULL);
+
+ sprintf(buf,"%f %f %f", s->ga_bp[0], s->ga_bp[1], s->ga_bp[2]);
+ gam->add_kword(gam, 0, "GAMUT_BLACK",buf, NULL);
+ }
+
+ /* If cusp values are known, put them in the file */
+ if (s->cu_inited != 0) {
+ char buf1[50], buf2[100];
+ char *cnames[6] = { "RED", "YELLOW", "GREEN", "CYAN", "BLUE", "MAGENTA" };
+
+ for (i = 0; i < 6; i++) {
+ sprintf(buf1,"CUSP_%s", cnames[i]);
+ sprintf(buf2,"%f %f %f", s->cusps[i][0], s->cusps[i][1], s->cusps[i][2]);
+ gam->add_kword(gam, 0, buf1, buf2, NULL);
+ }
+ }
+
+ gam->add_kword(gam, 0, NULL, NULL, "First come the triangle verticy location");
+
+ gam->add_field(gam, 0, "VERTEX_NO", i_t);
+ gam->add_field(gam, 0, "LAB_L", r_t);
+ gam->add_field(gam, 0, "LAB_A", r_t);
+ gam->add_field(gam, 0, "LAB_B", r_t);
+
+ /* Spit out the vertex values, in order. */
+ for (i = 0; i < s->nv; i++) {
+ if (!(s->verts[i]->f & GVERT_TRI))
+ continue;
+ gam->add_set(gam, 0, s->verts[i]->tn,
+ s->verts[i]->p[0], s->verts[i]->p[1], s->verts[i]->p[2]);
+ }
+
+ gam->add_table(gam, tt_other, 0); /* Start the second table */
+ gam->set_table_flags(gam, 1, 1, 1, 0); /* Suppress id & kwords */
+ gam->add_kword(gam, 1, NULL, NULL, "And then come the triangles");
+
+ gam->add_field(gam, 1, "VERTEX_0", i_t);
+ gam->add_field(gam, 1, "VERTEX_1", i_t);
+ gam->add_field(gam, 1, "VERTEX_2", i_t);
+
+ tp = s->tris;
+ FOR_ALL_ITEMS(gtri, tp) {
+ gam->add_set(gam, 1, tp->v[0]->tn, tp->v[1]->tn, tp->v[2]->tn);
+ } END_FOR_ALL_ITEMS(tp);
+
+
+ if (gam->write_name(gam, filename)) {
+ fprintf(stderr,"Error writing to file '%s' : '%s'\n",filename, gam->err);
+ return 2;
+ }
+
+ gam->del(gam); /* Clean up */
+ return 0;
+}
+
+/* ----------------------------------- */
+/* Read from a CGATS .gam file */
+/* Return non-zero on error */
+static int read_gam(
+gamut *s,
+char *filename
+) {
+ int i;
+ cgats *gam;
+ gtri *tp;
+ int nverts;
+ int ntris;
+ int Lf, af, bf; /* Fields holding L, a & b data */
+ int v0f, v1f, v2f; /* Fields holding verticies 0, 1 & 2 */
+ int cw, cb; /* Colorspace white, black keyword indexes */
+ int gw, gb; /* Gamut white, black keyword indexes */
+
+ if (s->tris != NULL || s->read_inited || s->lu_inited || s->ne_inited) {
+ fprintf(stderr,"Can't add read into gamut after it is initialised!\n");
+ return 1;
+ }
+
+ gam = new_cgats(); /* Create a CGATS structure */
+
+ gam->add_other(gam, "GAMUT"); /* Setup to cope with a gamut file */
+
+ if (gam->read_name(gam, filename)) {
+ fprintf(stderr,"Input file '%s' error : %s",filename, gam->err);
+ return 1;
+ }
+
+ if (gam->t[0].tt != tt_other || gam->t[0].oi != 0) {
+ fprintf(stderr,"Input file isn't a GAMUT format file");
+ return 1;
+ }
+ if (gam->ntables != 2) {
+ fprintf(stderr,"Input file doesn't contain exactly two tables");
+ return 1;
+ }
+
+ /* Figure the basic colorspace information */
+ s->isJab = 0;
+ if ((cw = gam->find_kword(gam, 0, "COLOR_REP")) >= 0) {
+ if (strcmp(gam->t[0].kdata[cw], "JAB") == 0)
+ s->isJab = 1;
+ }
+
+ /* Figure the surface type */
+ s->isRast = 0;
+ if ((cw = gam->find_kword(gam, 0, "SURF_TYPE")) >= 0) {
+ if (strcmp(gam->t[0].kdata[cw], "RASTER") == 0)
+ s->isRast = 1;
+ }
+ if (s->isRast) {
+ s->logpow = RAST_LOG_POW; /* Wrap the surface more closely */
+ s->no2pass = 1; /* Only do one pass */
+ } else {
+ s->logpow = NORM_LOG_POW; /* Convex hull compression power */
+ s->no2pass = 0; /* Do two passes */
+ }
+
+ /* If we can find the the colorspace white and black points, add them to the gamut */
+ cw = gam->find_kword(gam, 0, "CSPACE_WHITE");
+ cb = gam->find_kword(gam, 0, "CSPACE_BLACK");
+ if (cw >= 0 && cb >= 0) {
+ int ok = 1;
+ if (sscanf(gam->t[0].kdata[cw], "%lf %lf %lf",
+ &s->cs_wp[0], &s->cs_wp[1], &s->cs_wp[2]) != 3) {
+ ok = 0;
+ }
+
+ if (sscanf(gam->t[0].kdata[cb], "%lf %lf %lf",
+ &s->cs_bp[0], &s->cs_bp[1], &s->cs_bp[2]) != 3) {
+ ok = 0;
+ }
+
+ if (ok) {
+ s->cswbset = 1;
+ }
+ }
+
+ /* If we can find the the gamut white and black points, add them to the gamut */
+ gw = gam->find_kword(gam, 0, "GAMUT_WHITE");
+ gb = gam->find_kword(gam, 0, "GAMUT_BLACK");
+ if (gw >= 0 && gb >= 0) {
+ int ok = 1;
+ if (sscanf(gam->t[0].kdata[gw], "%lf %lf %lf",
+ &s->ga_wp[0], &s->ga_wp[1], &s->ga_wp[2]) != 3) {
+ ok = 0;
+ }
+
+ if (sscanf(gam->t[0].kdata[gb], "%lf %lf %lf",
+ &s->ga_bp[0], &s->ga_bp[1], &s->ga_bp[2]) != 3) {
+ ok = 0;
+ }
+
+ if (ok) {
+ s->gawbset = 1;
+ }
+ }
+
+ /* See if there are cusp values */
+ {
+ int kk;
+ char buf1[50];
+ char *cnames[6] = { "RED", "YELLOW", "GREEN", "CYAN", "BLUE", "MAGENTA" };
+
+ for (i = 0; i < 6; i++) {
+ sprintf(buf1,"CUSP_%s", cnames[i]);
+ if ((kk = gam->find_kword(gam, 0, buf1)) < 0)
+ break;
+
+ if (sscanf(gam->t[0].kdata[kk], "%lf %lf %lf",
+ &s->cusps[i][0], &s->cusps[i][1], &s->cusps[i][2]) != 3) {
+ break;
+ }
+ }
+ if (i >= 6)
+ s->cu_inited = 1;
+ }
+
+
+ if ((nverts = gam->t[0].nsets) <= 0) {
+ fprintf(stderr,"No verticies");
+ return 1;
+ }
+ if ((ntris = gam->t[1].nsets) <= 0) {
+ fprintf(stderr,"No triangles");
+ return 1;
+ }
+
+ /* Get ready to read the verticy data */
+ if ((Lf = gam->find_field(gam, 0, "LAB_L")) < 0) {
+ fprintf(stderr,"Input file doesn't contain field LAB_L");
+ return 1;
+ }
+ if (gam->t[0].ftype[Lf] != r_t) {
+ fprintf(stderr,"Field LAB_L is wrong type");
+ return 1;
+ }
+ if ((af = gam->find_field(gam, 0, "LAB_A")) < 0) {
+ fprintf(stderr,"Input file doesn't contain field LAB_A");
+ return 1;
+ }
+ if (gam->t[0].ftype[af] != r_t) {
+ fprintf(stderr,"Field LAB_A is wrong type");
+ return 1;
+ }
+ if ((bf = gam->find_field(gam, 0, "LAB_B")) < 0) {
+ fprintf(stderr,"Input file doesn't contain field LAB_B");
+ return 1;
+ }
+ if (gam->t[0].ftype[bf] != r_t) {
+ fprintf(stderr,"Field LAB_B is wrong type");
+ return 1;
+ }
+
+ /* Allocate an array to point at the verts */
+ if ((s->verts = (gvert **)malloc(nverts * sizeof(gvert *))) == NULL) {
+ fprintf(stderr,"gamut: malloc failed on gvert pointer\n");
+ return 2;
+ }
+ s->nv = s->na = nverts;
+
+ for (i = 0; i < nverts; i++) {
+ gvert *v;
+
+ /* Allocate and fill in each verticies basic information */
+ if ((v = (gvert *)calloc(1, sizeof(gvert))) == NULL) {
+ fprintf(stderr,"gamut: malloc failed on gvert object\n");
+ return 2;
+ }
+ s->verts[i] = v;
+ v->tag = 1;
+ v->tn = v->n = i;
+ v->f = GVERT_SET | GVERT_TRI; /* Will be part of the triangulation */
+
+ v->p[0] = *((double *)gam->t[0].fdata[i][Lf]);
+ v->p[1] = *((double *)gam->t[0].fdata[i][af]);
+ v->p[2] = *((double *)gam->t[0].fdata[i][bf]);
+
+ gamut_rect2radial(s, v->r, v->p);
+ }
+ s->ntv = i;
+
+ /* Compute the other vertex values */
+ compute_vertex_coords(s);
+
+ /* Get ready to read the triangle data */
+ if ((v0f = gam->find_field(gam, 1, "VERTEX_0")) < 0) {
+ fprintf(stderr,"Input file doesn't contain field VERTEX_0");
+ return 1;
+ }
+ if (gam->t[1].ftype[v0f] != i_t) {
+ fprintf(stderr,"Field VERTEX_0 is wrong type");
+ return 1;
+ }
+ if ((v1f = gam->find_field(gam, 1, "VERTEX_1")) < 0) {
+ fprintf(stderr,"Input file doesn't contain field VERTEX_1");
+ return 1;
+ }
+ if (gam->t[1].ftype[v1f] != i_t) {
+ fprintf(stderr,"Field VERTEX_1 is wrong type");
+ return 1;
+ }
+ if ((v2f = gam->find_field(gam, 1, "VERTEX_2")) < 0) {
+ fprintf(stderr,"Input file doesn't contain field VERTEX_2");
+ return 1;
+ }
+ if (gam->t[1].ftype[v2f] != i_t) {
+ fprintf(stderr,"Field VERTEX_2 is wrong type");
+ return 1;
+ }
+
+ /* Create all the triangles */
+ for (i = 0; i < ntris; i++) {
+ gtri *t;
+ int v0, v1, v2;
+
+ t = new_gtri();
+ ADD_ITEM_TO_BOT(s->tris, t); /* Append to triangulation list */
+
+ v0 = *((int *)gam->t[1].fdata[i][v0f]);
+ v1 = *((int *)gam->t[1].fdata[i][v1f]);
+ v2 = *((int *)gam->t[1].fdata[i][v2f]);
+
+ t->v[0] = s->verts[v0];
+ t->v[1] = s->verts[v1];
+ t->v[2] = s->verts[v2];
+
+ comptriattr(s, t); /* Compute triangle attributes */
+ }
+
+ /* Connect edge information */
+ tp = s->tris;
+ FOR_ALL_ITEMS(gtri, tp) {
+ int en;
+
+ for (en = 0; en < 3; en++) { /* For each edge */
+ gedge *e;
+ gvert *v0, *v1; /* The two verticies of the edge */
+ gtri *tp2; /* The other triangle */
+ int em; /* The other edge */
+ gvert *w0, *w1; /* The other verticies */
+
+ v0 = tp->v[en];
+ v1 = tp->v[en < 2 ? en+1 : 0];
+
+ if (v0->n > v1->n)
+ continue; /* Skip every other edge */
+
+ /* Find the corresponding edge of the other triangle */
+ w0 = w1 = NULL;
+ tp2 = s->tris;
+ FOR_ALL_ITEMS(gtri, tp2) {
+ for (em = 0; em < 3; em++) { /* For each edge */
+ w0 = tp2->v[em];
+ w1 = tp2->v[em < 2 ? em+1 : 0];
+ if (v0 == w1 && v1 == w0) /* Found it */
+ break;
+ }
+ if (em < 3)
+ break;
+ } END_FOR_ALL_ITEMS(tp2);
+ if (w0 == NULL) {
+ /* Should clean up ? */
+ fprintf(stderr,".gam file triangle data is not consistent\n");
+ return 1;
+ }
+
+ if (tp->e[en] != NULL
+ || tp2->e[em] != NULL) {
+ fprintf(stderr,".gam file triangle data is not consistent\n");
+ fprintf(stderr,"tp1->e[%d] = 0x%p, tp2->e[%d]= 0x%p\n",en,
+ (void *)tp->e[en],em,(void *)tp2->e[em]);
+ return 1;
+ }
+
+ /* Creat the edge structure */
+ e = new_gedge();
+ ADD_ITEM_TO_BOT(s->edges, e); /* Append to edge list */
+ tp->e[en] = e; /* This edge */
+ tp->ei[en] = 0; /* 0th triangle in edge */
+ e->t[0] = tp; /* 0th triangle is tp */
+ e->ti[0] = en; /* 0th triangles en edge */
+ tp2->e[em] = e; /* This edge */
+ tp2->ei[em] = 1; /* 1st triangle in edge */
+ e->t[1] = tp2; /* 1st triangle is tp2 */
+ e->ti[1] = em; /* 1st triangles em edge */
+ e->v[0] = v0; /* The two verticies */
+ e->v[1] = v1;
+ }
+ } END_FOR_ALL_ITEMS(tp);
+
+ gam->del(gam); /* Clean up */
+
+ s->read_inited = 1; /* It's now valid */
+
+#ifdef ASSERTS
+ check_triangulation(s, 1); /* Check out our work */
+#endif
+
+ return 0;
+}
+
+/* ===================================================== */
+/* ===================================================== */
+
+/* Convert from rectangular to radial coordinates */
+void
+gamut_rect2radial(
+gamut *s,
+double out[3], /* Radius, longitude, lattitude out */
+double in[3] /* Lab in */
+) {
+ double L, a, b; /* Lab values */
+ double R, g, t; /* Radial value */
+ double c; /* Chromatic length */
+
+
+ L = in[0] - s->cent[0]; /* Offset value */
+ a = in[1] - s->cent[1];
+ b = in[2] - s->cent[2];
+ c = a * a + b * b;
+ R = c + L * L;
+ c = sqrt(c); /* Saturation */
+ R = sqrt(R); /* Vector length */
+
+ if (R < 1e-6) { /* Hmm, a point at the center */
+ g = t = 0.0;
+ } else {
+
+ /* Figure out the longitude, -pi to +pi */
+ if (c < 1e-6) {
+ g = 0.0;
+ } else {
+ g = asin(b/c);
+ if (a < 0.0) {
+ if (b >= 0.0)
+ g = M_PI - g;
+ else
+ g = -g - M_PI;
+ }
+ }
+
+ /* Figure out the lattitude, -pi/2 to +pi/2 */
+ t = asin(L/R);
+ }
+ out[0] = R;
+ out[1] = g;
+ out[2] = t;
+}
+
+/* Convert from radial to rectangular coordinates */
+void
+gamut_radial2rect(
+gamut *s,
+double out[3], /* Lab out */
+double in[3] /* Radius, longitude, lattitude in */
+) {
+ double R, g, t; /* Radial value */
+ double L, a, b; /* Lab values */
+ double c; /* Chromatic length */
+
+ R = in[0];
+ g = in[1];
+ t = in[2];
+
+ L = R * sin(t);
+ c = R * cos(t);
+
+ a = c * cos(g);
+ b = c * sin(g);
+
+ out[0] = L + s->cent[0];
+ out[1] = a + s->cent[1];
+ out[2] = b + s->cent[2];
+}
+
+
+/* -------------------------------------------------- */
+
+/* Convert a gamut Lab value to an RGB value for display purposes */
+void
+gamut_Lab2RGB(double *out, double *in) {
+ double L = in[0], a = in[1], b = in[2];
+ double x,y,z,fx,fy,fz;
+ double R, G, B;
+
+ /* Scale so that black is visible */
+ L = L * (100 - 40.0)/100.0 + 40.0;
+
+ /* First convert to XYZ using D50 white point */
+ if (L > 8.0) {
+ fy = (L + 16.0)/116.0;
+ y = pow(fy,3.0);
+ } else {
+ y = L/903.2963058;
+ fy = 7.787036979 * y + 16.0/116.0;
+ }
+
+ fx = a/500.0 + fy;
+ if (fx > 24.0/116.0)
+ x = pow(fx,3.0);
+ else
+ x = (fx - 16.0/116.0)/7.787036979;
+
+ fz = fy - b/200.0;
+ if (fz > 24.0/116.0)
+ z = pow(fz,3.0);
+ else
+ z = (fz - 16.0/116.0)/7.787036979;
+
+ x *= 0.9642; /* Multiply by white point, D50 */
+ y *= 1.0;
+ z *= 0.8249;
+
+ /* Now convert to sRGB values */
+ R = x * 3.2410 + y * -1.5374 + z * -0.4986;
+ G = x * -0.9692 + y * 1.8760 + z * 0.0416;
+ B = x * 0.0556 + y * -0.2040 + z * 1.0570;
+
+ if (R < 0.0)
+ R = 0.0;
+ else if (R > 1.0)
+ R = 1.0;
+
+ if (G < 0.0)
+ G = 0.0;
+ else if (G > 1.0)
+ G = 1.0;
+
+ if (B < 0.0)
+ B = 0.0;
+ else if (B > 1.0)
+ B = 1.0;
+
+ R = pow(R, 1.0/2.2);
+ G = pow(G, 1.0/2.2);
+ B = pow(B, 1.0/2.2);
+
+ out[0] = R;
+ out[1] = G;
+ out[2] = B;
+}
+
+
+/* -------------------------------------------------- */
+
+#ifdef DEBUG_TRIANG
+
+/* Write a surface contrsuction diagnostic VRML .wrl file */
+static int write_diag_vrml(
+gamut *s,
+double vv[3], /* Vertex being added */
+int nh, /* Number of hit triangles */
+tidxs *hixs, /* verticy indexes of hit triangles */
+gtri *hl /* Edge hit list (may be NULL) */
+) {
+ char *filename;
+ int i, j;
+ gtri *tp; /* Triangle pointer */
+ FILE *wrl;
+
+ if (hl)
+ filename = "diag1.wrl"; /* Triangles hit */
+ else
+ filename = "diag2.wrl"; /* Triangles formed */
+
+ if ((wrl = fopen(filename,"w")) == NULL) {
+ fprintf(stderr,"Error opening output file '%s'\n",filename);
+ return 2;
+ }
+
+ /* Spit out a VRML 2 Object surface of gamut */
+
+ fprintf(wrl,"#VRML V2.0 utf8\n");
+ fprintf(wrl,"\n");
+ fprintf(wrl,"# Created by the Argyll CMS\n");
+ fprintf(wrl,"Transform {\n");
+ fprintf(wrl,"children [\n");
+ fprintf(wrl," NavigationInfo {\n");
+ fprintf(wrl," type \"EXAMINE\" # It's an object we examine\n");
+ fprintf(wrl," } # We'll add our own light\n");
+ fprintf(wrl,"\n");
+ fprintf(wrl," DirectionalLight {\n");
+ fprintf(wrl," direction 0 0 -1 # Light illuminating the scene\n");
+ fprintf(wrl," direction 0 -1 0 # Light illuminating the scene\n");
+ fprintf(wrl," }\n");
+ fprintf(wrl,"\n");
+ fprintf(wrl," Viewpoint {\n");
+ fprintf(wrl," position 0 0 200 # Position we view from\n");
+ fprintf(wrl," }\n");
+ fprintf(wrl,"\n");
+
+ fprintf(wrl," Transform {\n");
+ fprintf(wrl," translation 0 0 0\n");
+ fprintf(wrl," children [\n");
+ fprintf(wrl," Shape { \n");
+ fprintf(wrl," geometry IndexedFaceSet {\n");
+ fprintf(wrl," ccw FALSE\n");
+ fprintf(wrl," convex TRUE\n");
+ fprintf(wrl,"\n");
+ fprintf(wrl," coord Coordinate { \n");
+ fprintf(wrl," point [ # Verticy coordinates\n");
+
+ /* Spit out the vertex values, in order. */
+ /* Note that a->x, b->y, L->z */
+ for (i = 0; i < s->nv; i++) {
+ double out[3];
+
+ /* Show normal gamut surface */
+ out[0] = s->verts[i]->ch[0];
+ out[1] = s->verts[i]->ch[1];
+ out[2] = s->verts[i]->ch[2];
+
+ fprintf(wrl,"%f %f %f,\n",out[1], out[2], out[0]);
+ }
+ fprintf(wrl," ]\n");
+ fprintf(wrl," }\n");
+ fprintf(wrl,"\n");
+ fprintf(wrl," coordIndex [ # Indexes of poligon Verticies \n");
+
+ tp = s->tris;
+ FOR_ALL_ITEMS(gtri, tp) {
+ fprintf(wrl,"%d, %d, %d, -1\n", tp->v[0]->n, tp->v[1]->n, tp->v[2]->n);
+ } END_FOR_ALL_ITEMS(tp);
+
+ for (i = 0; i < nh; i++) {
+ fprintf(wrl,"%d, %d, %d, -1\n", hixs[i].tix[0], hixs[i].tix[1], hixs[i].tix[2]);
+ }
+
+ fprintf(wrl," ]\n");
+ fprintf(wrl,"\n");
+ fprintf(wrl," colorPerVertex FALSE\n");
+ fprintf(wrl," color Color {\n");
+ fprintf(wrl," color [ # RGB colors of each vertex\n");
+
+ /* Spit out the colors for each face */
+ tp = s->tris;
+ FOR_ALL_ITEMS(gtri, tp) {
+ fprintf(wrl,"%f %f %f,\n", 0.7, 0.7, 0.7);
+ } END_FOR_ALL_ITEMS(tp);
+ for (i = 0; i < nh; i++) {
+ if (hixs[i].type == 0)
+ fprintf(wrl,"%f %f %f,\n", 0.4, 1.0, 0.4); /* Green for hit */
+ else if (hixs[i].type == 1)
+ fprintf(wrl,"%f %f %f,\n", 0.4, 0.4, 1.0); /* Blue for extra */
+ else
+ fprintf(wrl,"%f %f %f,\n", 0.8, 0.8, 0.2); /* Yellow for new */
+ }
+ fprintf(wrl," ] \n");
+ fprintf(wrl," }\n");
+ fprintf(wrl," } # end IndexedFaceSet\n");
+
+ fprintf(wrl," appearance Appearance { \n");
+ fprintf(wrl," material Material {\n");
+ fprintf(wrl," transparency 0.0\n");
+ fprintf(wrl," ambientIntensity 0.3\n");
+ fprintf(wrl," shininess 0.5\n");
+ fprintf(wrl," }\n");
+ fprintf(wrl," }\n");
+ fprintf(wrl," } # end Shape\n");
+ fprintf(wrl," ]\n");
+ fprintf(wrl," } # end of transform\n");
+
+ /* center of gamut */
+ fprintf(wrl,"\n");
+ fprintf(wrl," Transform {\n");
+ fprintf(wrl," translation %f %f %f\n",0.0, 0.0, 0.0);
+ fprintf(wrl," children [\n");
+ fprintf(wrl," Shape { \n");
+ fprintf(wrl," geometry Sphere { radius 1.5 }\n");
+ fprintf(wrl," appearance Appearance { material Material { diffuseColor %f %f %f } }\n", 1.0, 1.0, 0.0);
+ fprintf(wrl," } \n");
+ fprintf(wrl," ]\n");
+ fprintf(wrl," }\n");
+
+ /* vertex being added */
+ fprintf(wrl,"\n");
+ fprintf(wrl," Transform {\n");
+ fprintf(wrl," translation %f %f %f\n",vv[1], vv[2], vv[0]);
+ fprintf(wrl," children [\n");
+ fprintf(wrl," Shape { \n");
+ fprintf(wrl," geometry Sphere { radius 1.5 }\n");
+ fprintf(wrl," appearance Appearance { material Material { diffuseColor %f %f %f } }\n", 1.0, 0.0, 0.0);
+ fprintf(wrl," } \n");
+ fprintf(wrl," ]\n");
+ fprintf(wrl," }\n");
+
+
+ /* Verticies for Polygon edges, marked by directional cones */
+ if (hl != NULL) {
+ double base[3] = { 0.0, 0.0, 1.0 }; /* Default orientation of cone is b axis */
+ tp = hl;
+ FOR_ALL_ITEMS(gtri, tp) {
+ double len;
+ double loc[3];
+ double vec[3];
+ double axis[3]; /* Axis to rotate around */
+ double rot; /* In radians */
+
+//printf("~1 edge vert %d to %d\n",tp->v[0]->n, tp->v[1]->n);
+//printf("~1 edge %f %f %f to %f %f %f\n",
+//tp->v[0]->ch[0], tp->v[0]->ch[1], tp->v[0]->ch[2],
+//tp->v[1]->ch[0], tp->v[1]->ch[1], tp->v[1]->ch[2]);
+
+ icmAdd3(loc, tp->v[1]->ch, tp->v[0]->ch);
+ icmScale3(loc, loc, 0.5);
+ icmSub3(vec, tp->v[1]->ch, tp->v[0]->ch);
+ len = icmNorm3(vec);
+
+ if (len < 1.0)
+ len = 1.0;
+
+ icmNormalize3(base, base, 1.0);
+ icmNormalize3(vec, vec, 1.0);
+ icmCross3(axis, base, vec);
+ rot = icmDot3(base, vec);
+//printf("~1 Axis = %f %f %f\n",axis[0],axis[1],axis[2]);
+ if (icmNorm3sq(axis) < 1e-10) { /* 0 or 180 degrees */
+ double base2[3];
+ int mxi = 0;
+ base2[0] = vec[1]; /* Comute vector in a different direction */
+ base2[1] = vec[2];
+ base2[2] = vec[0];
+ for (j = 1; j < 3; j++) {
+ if (fabs(base2[j]) > fabs(base2[mxi]))
+ mxi = j;
+ }
+ base2[mxi] = -base2[mxi];
+
+ icmCross3(axis, base2, vec);
+ if (icmNorm3sq(axis) < 1e-10) { /* 0 or 180 degrees */
+ error("VRML rotate axis still too small");
+ }
+ if (rot < 0.0)
+ rot = 3.1415926;
+ else
+ rot = 0.0;
+ } else {
+ rot = acos(rot);
+//printf("~1 rotation %f\n",rot);
+ }
+
+ fprintf(wrl,"\n");
+ fprintf(wrl," Transform {\n");
+ fprintf(wrl," rotation %f %f %f %f\n",axis[1], axis[2], axis[0], rot);
+ fprintf(wrl," translation %f %f %f\n",loc[1], loc[2], loc[0]);
+ fprintf(wrl," children [\n");
+ fprintf(wrl," Shape { \n");
+ fprintf(wrl," geometry Cone { bottomRadius 0.5 height %f }\n",len);
+ fprintf(wrl," appearance Appearance { material Material { diffuseColor 0.7 0.0 1.0 } }\n");
+ fprintf(wrl," } \n");
+ fprintf(wrl," ]\n");
+ fprintf(wrl," }\n");
+ } END_FOR_ALL_ITEMS(tp);
+ }
+
+ fprintf(wrl,"\n");
+ fprintf(wrl," ] # end of children for world\n");
+ fprintf(wrl,"}\n");
+
+ if (fclose(wrl) != 0) {
+ fprintf(stderr,"Error closing output file '%s'\n",filename);
+ return 2;
+ }
+
+ return 0;
+}
+
+#endif /* DEBUG_TRIANG */
+
+
+#ifdef DEBUG_SPLIT_VRML
+
+/* Write a triangle split diagnostic VRML .wrl file */
+static int write_split_diag_vrml(
+gamut *s,
+gtri **list, /* Triangle list */
+int llen /* Number of triangles in the list */
+) {
+ char *filename;
+ int i, j;
+ FILE *wrl;
+
+ filename = "diag3.wrl"; /* Triangles split */
+
+ if ((wrl = fopen(filename,"w")) == NULL) {
+ fprintf(stderr,"Error opening output file '%s'\n",filename);
+ return 2;
+ }
+
+ /* Spit out a VRML 2 Object surface of gamut */
+
+ fprintf(wrl,"#VRML V2.0 utf8\n");
+ fprintf(wrl,"\n");
+ fprintf(wrl,"# Created by the Argyll CMS\n");
+ fprintf(wrl,"Transform {\n");
+ fprintf(wrl,"children [\n");
+ fprintf(wrl," NavigationInfo {\n");
+ fprintf(wrl," type \"EXAMINE\" # It's an object we examine\n");
+ fprintf(wrl," } # We'll add our own light\n");
+ fprintf(wrl,"\n");
+ fprintf(wrl," DirectionalLight {\n");
+ fprintf(wrl," ambientIntensity 0.3 # Ambient light illuminating the scene\n");
+ fprintf(wrl," direction 0 0 -1 # Light illuminating the scene\n");
+ fprintf(wrl," direction 0 -1 0 # Light illuminating the scene\n");
+ fprintf(wrl," }\n");
+ fprintf(wrl,"\n");
+ fprintf(wrl," Viewpoint {\n");
+ fprintf(wrl," position 0 0 5 # Position we view from\n");
+ fprintf(wrl," }\n");
+ fprintf(wrl,"\n");
+
+ fprintf(wrl," Transform {\n");
+ fprintf(wrl," translation 0 0 0\n");
+ fprintf(wrl," children [\n");
+ fprintf(wrl," Shape { \n");
+ fprintf(wrl," geometry IndexedFaceSet {\n");
+ fprintf(wrl," ccw FALSE\n");
+ fprintf(wrl," convex TRUE\n");
+ fprintf(wrl," solid FALSE\n");
+ fprintf(wrl,"\n");
+ fprintf(wrl," coord Coordinate { \n");
+ fprintf(wrl," point [ # Verticy coordinates\n");
+
+ /* Spit out the vertex values, in order. */
+ for (i = 0; i < llen; i++) {
+ fprintf(wrl,"%f %f %f,\n",list[i]->v[0]->sp[0], list[i]->v[0]->sp[1], list[i]->v[0]->sp[2]);
+ fprintf(wrl,"%f %f %f,\n",list[i]->v[1]->sp[0], list[i]->v[1]->sp[1], list[i]->v[1]->sp[2]);
+ fprintf(wrl,"%f %f %f,\n",list[i]->v[2]->sp[0], list[i]->v[2]->sp[1], list[i]->v[2]->sp[2]);
+ }
+ fprintf(wrl," ]\n");
+ fprintf(wrl," }\n");
+ fprintf(wrl,"\n");
+ fprintf(wrl," coordIndex [ # Indexes of poligon Verticies \n");
+
+ for (i = 0; i < llen; i++) {
+ fprintf(wrl,"%d, %d, %d, -1\n", i * 3 + 0, i * 3 + 1, i * 3 + 2);
+ }
+ fprintf(wrl," ]\n");
+ fprintf(wrl,"\n");
+ fprintf(wrl," colorPerVertex FALSE\n");
+ fprintf(wrl," color Color {\n");
+ fprintf(wrl," color [ # RGB colors of each vertex\n");
+
+ /* Spit out the colors for each face */
+ for (i = 0; i < llen; i++) {
+ if (list[i]->bsort == 1) { /* Positive */
+ fprintf(wrl,"%f %f %f,\n", 1.0, 0.3, 0.3); /* Red */
+ } else if (list[i]->bsort == 2) { /* Negative */
+ fprintf(wrl,"%f %f %f,\n", 0.3, 1.0, 0.3); /* Green */
+ } else if (list[i]->bsort == 3) { /* Both */
+ fprintf(wrl,"%f %f %f,\n", 1.0, 1.0, 0.3); /* Yellow */
+ } else { /* Neither */
+ fprintf(wrl,"%f %f %f,\n", 0.3, 0.3, 1.0); /* Blue */
+ }
+ }
+ fprintf(wrl," ] \n");
+ fprintf(wrl," }\n");
+ fprintf(wrl," } # end IndexedFaceSet\n");
+
+ fprintf(wrl," appearance Appearance { \n");
+ fprintf(wrl," material Material {\n");
+ fprintf(wrl," transparency 0.0\n");
+ fprintf(wrl," ambientIntensity 0.3\n");
+ fprintf(wrl," shininess 0.5\n");
+ fprintf(wrl," }\n");
+ fprintf(wrl," }\n");
+ fprintf(wrl," } # end Shape\n");
+ fprintf(wrl," ]\n");
+ fprintf(wrl," } # end of transform\n");
+
+ /* center of gamut */
+ fprintf(wrl,"\n");
+ fprintf(wrl," Transform {\n");
+ fprintf(wrl," translation %f %f %f\n",0.0, 0.0, 0.0);
+ fprintf(wrl," children [\n");
+ fprintf(wrl," Shape { \n");
+ fprintf(wrl," geometry Sphere { radius 0.05 }\n");
+ fprintf(wrl," appearance Appearance { material Material { diffuseColor %f %f %f } }\n", 1.0, 1.0, 0.0);
+ fprintf(wrl," } \n");
+ fprintf(wrl," ]\n");
+ fprintf(wrl," }\n");
+
+ fprintf(wrl,"\n");
+ fprintf(wrl," ] # end of children for world\n");
+ fprintf(wrl,"}\n");
+
+ if (fclose(wrl) != 0) {
+ fprintf(stderr,"Error closing output file '%s'\n",filename);
+ return 2;
+ }
+
+ return 0;
+}
+
+#endif /* DEBUG_SPLIT_VRML */
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/gamut/gamut.h b/gamut/gamut.h
new file mode 100644
index 0000000..8f9f00e
--- /dev/null
+++ b/gamut/gamut.h
@@ -0,0 +1,406 @@
+#ifndef GAMUT_H
+#define GAMUT_H
+
+/*
+ * gamut
+ *
+ * Gamut support routines.
+ *
+ * Author: Graeme W. Gill
+ * Date: 9/3/2000
+ * Version: 1.00
+ *
+ * Copyright 2000-2006 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.
+ */
+
+
+
+/*
+ Note that the input and output are expected to be Lab style
+ color coordinates, and that L->Z, a->X, b->Y.
+
+*/
+
+#include "../h/llist.h"
+
+#define BSPDEPTH 100 /* Maximum BSP tree depth */
+
+#define PFARNDIST 0.1 /* Positive (inwards) far "near" distance */
+#define NFARNDIST -200.0 /* Negative (outwards) far "near" distance */
+#define MFARNDIST PFARNDIST /* Minimum (absolute) of the two */
+
+#define MAXGAMN 10 /* Maximum gamut point neighbors returned */
+#define NSLOTS 6 /* Number of maximum direction slots */
+
+/* ------------------------------------ */
+#define NODE_STRUCT \
+ int tag; /* Type of node, 1 = vertex, 2 = quad */ \
+ double w, h; /* longitude width, latitude height of this box */ \
+ double hc, vc; /* longitude center, latitude center of this box */ \
+
+struct _gnode {
+ NODE_STRUCT
+}; typedef struct _gnode gnode;
+
+/* ------------------------------------ */
+/* Vertex node */
+struct _gvert {
+ NODE_STRUCT
+ int rc; /* reference count */
+ struct _gvert *ul; /* Unused list */
+
+ int n; /* Index number of vertex */
+ int sn; /* Set Index number of vertex */
+ int tn; /* Triangulated Index number of vertex */
+ int f; /* Flag value */
+#define GVERT_NONE 0x0000 /* No flags */
+#define GVERT_SET 0x0001 /* Value has been set */
+#define GVERT_TRI 0x0002 /* Vertex has been added to triangulation (Exclsv with _INSIDE) */
+#define GVERT_INSIDE 0x0004 /* Vertex is inside the log hull (Exclusive with _TRI) */
+#define GVERT_ISOS 0x0008 /* Intersecting gamuts "outside other gamut" flag */
+#define GVERT_ESTP 0x0010 /* Non-fake establishment point */
+#define GVERT_FAKE 0x0020 /* Fake establishment point */
+ int k0; /* k0 direction reference count */
+
+ double p[3]; /* Point in xyz rectangular coordinates, absolute */
+ double r[3]; /* Radial coordinates */
+ double lr0; /* log scaled r[0] */
+ double sp[3]; /* Point mapped to surface of unit sphere, relative to center */
+ double ch[3]; /* Point mapped for convex hull testing, relative to center */
+
+ int as; /* Assert checking flag, compdstgamut flag */
+}; typedef struct _gvert gvert;
+
+/* ------------------------------------ */
+/* Quadtree node */
+struct _gquad {
+ NODE_STRUCT
+ gnode *qt[4][NSLOTS];
+
+ /* Child nodes, NULL if none, */
+ /* Nodes are ordered: */
+ /* --------- */
+ /* | 2 | 3 | */
+ /* --------- */
+ /* | 0 | 1 | */
+ /* --------- */
+ /* and each node contains NSLOTS slots. */
+ /* If the quadtree recurses, the first slot containts another */
+ /* quadtree, otherwise the slots contain pointers to the */
+ /* four best verticies in the various directions. */
+
+}; typedef struct _gquad gquad;
+
+/* ------------------------------------ */
+
+/* Common base class of BSP nodes */
+#define BSP_STRUCT \
+ int tag; /* Type of node, 1 = bsp node, 2 = triangle, 3 = list */ \
+ double rs0, rs1; /* min/max radius squared from origin of all contained triangles */
+
+struct _gbsp {
+ BSP_STRUCT
+}; typedef struct _gbsp gbsp;
+
+/* ------------------------------------ */
+
+/* A BSP tree decision node */
+struct _gbspn {
+ BSP_STRUCT
+ int n; /* Serial number */
+ double pe[4]; /* Plane equation values (center relative) */
+ struct _gbsp *po; /* Positive branch */
+ struct _gbsp *ne; /* Negative branch */
+}; typedef struct _gbspn gbspn;
+
+/* ------------------------------------ */
+
+/* A BSP tree triangle list node */
+struct _gbspl {
+ BSP_STRUCT
+ int n; /* Serial number */
+ int nt; /* Number of triangles in the list */
+ struct _gtri *t[]; /* List of triangles - allocated with struct */
+}; typedef struct _gbspl gbspl;
+
+/* ------------------------------------ */
+
+/* A triangle in the surface mesh */
+struct _gtri {
+ BSP_STRUCT
+ int n; /* Serial number */
+ struct _gvert *v[3]; /* Verticies in cw order */
+ struct _gedge *e[3]; /* Edges v[n] - v[n+1] */
+ int ei[3]; /* Index within edge structure of this triangle [0..1] */
+
+ double pe[4]; /* Vertex plane equation (absolute) */
+ /* (The first three elements is the unit normal vector to the plane, */
+ /* which points inwards) */
+ double che[4]; /* convex hull testing triangle plane equation (relative) */
+ double spe[4]; /* sphere mapped triangle plane equation (relative) */
+ double ee[3][4]; /* sphere sp[] Edge triangle plane equations for opposite edge (relative) */
+
+ int sort; /* lookup: Plane sorting result for each try */
+ int bsort; /* lookup: Current best tries sort */
+
+ unsigned int touch; /* nn: Per value touch count */
+ double mix[2][3]; /* nn: Bounding box min and max */
+
+ double area; /* Area - computed by nssverts() */
+ int ssverts; /* Number of stratified sampling verts needed - computed by nssverts() */
+
+ LINKSTRUCT(struct _gtri); /* Linked list structure */
+}; typedef struct _gtri gtri;
+
+/* ------------------------------------ */
+
+/* An edge shared by two triangle in the mesh */
+struct _gedge {
+ int n; /* Serial number */
+ struct _gvert *v[2]; /* Verticies of edge */
+ struct _gtri *t[2]; /* Triangles edge is part of */
+ int ti[2]; /* record of indexes of edge within the triangles [0..2]*/
+ double re[4]; /* Radial edge plane equation (relative) */
+
+ int as; /* Assert checking flag */
+
+ LINKSTRUCT(struct _gedge); /* Linked list structure */
+}; typedef struct _gedge gedge;
+
+/* ------------------------------------ */
+
+/* The gamut nearest neighbor search structure */
+struct _gnn {
+ struct _gamut *s; /* Base gamut object */
+ int n; /* Number of points stored */
+ gtri **sax[3 * 2]; /* 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 */
+}; typedef struct _gnn gnn;
+
+/* ------------------------------------ */
+
+/* A vector intersction point */
+struct _gispnt {
+ double ip[3]; /* Intersecion Point */
+ double pv; /* Parameter value at intersection */
+ int dir; /* Direction: 1 = into gamut, 0 = out or gamut */
+ int edge; /* Edge: 2 = no isect, 1 = on edge, 0 = not on edge */
+ gtri *tri; /* Pointer to intersection triangle */
+}; typedef struct _gispnt gispnt;
+
+/* Gamut object */
+struct _gamut {
+
+/* Private: */
+ double sres; /* Surface triangle resolution */
+ int isJab; /* nz if Jab CIECAM02 type space rather than L*a*b* */
+ int isRast; /* nz if raster file point cloud, else colorspace */
+ /* (This affects convex hull filtering) */
+ double cent[3]; /* Gamut center for radial conversion. Default 50.0,0,0 */
+ /* Must be same to compare radial values. */
+
+ int nv; /* Number of verticies used out of allocation */
+ gvert *ul; /* Linked list of unused verticies */
+ int na; /* Number of verticies allocated */
+ int nsv; /* Number of verticies that have been set */
+ int ntv; /* Number of verticies used in triangulation */
+ gvert **verts; /* Pointers to allocated verticies */
+ int read_inited; /* Flag set if gamut was initialised from a read */
+ int lu_inited; /* Flag set if radial surface lookup is inited */
+ int ne_inited; /* Flag set if nearest lookup is inited */
+ int cu_inited; /* Flag set if cusp values inited and trustworthy */
+ int nofilter; /* Flag, skip segmented maxima filtering */
+ int no2pass; /* Flag, do only one pass of convex hull */
+ int doingfake; /* Internal transient state */
+ int pass; /* Pass number for multi-pass */
+ double logpow; /* Convex hull compression power (default 0.25) */
+
+ gquad *tl, *tr; /* Top left and quadtree elements */
+
+ gtri *tris; /* Surface triangles linked list */
+ gedge *edges; /* Edges between the triangles linked list */
+
+ gbsp *lutree; /* Lookup function BSP tree root */
+ gnn *nns; /* nearest neighbor acceleration structure */
+
+ int cswbset; /* Flag to indicate that the cs white & black points are set */
+ double cs_wp[3]; /* Color spaces white point */
+ double cs_bp[3]; /* Color spaces black point */
+ double cs_kp[3]; /* Color spaces K only black point */
+ int gawbset; /* Flag to indicate that the gamut white & black points are set */
+ double ga_wp[3]; /* Gamut white point */
+ double ga_bp[3]; /* Gamut black point */
+ double ga_kp[3]; /* Gamut K only black point */
+
+ int dcuspixs; /* Cusp we're up to */
+ double dcusps[6][3];/* Entered cusp values to setcusps(, 3, ) */
+ double cusps[6][3]; /* Cusp values for red, yellow, green, cyan, blue & magenta */
+ /* if cu_inited nz */
+
+ double mx[3], mn[3]; /* Range covered by input points */
+
+ double xvra; /* Extra vertex ratio - set/used by nssverts() */
+ int ssnverts; /* total ss verticies - set/used by nssverts() */
+ int ssvertn; /* Number of verts created for current triangle */
+ sobol *ss; /* Sibol ss generator currently being used */
+
+ gtri *nexttri; /* Context for getnexttri() */
+
+/* Public: */
+ /* Methods */
+ void (*del)(struct _gamut *s); /* Free ourselves */
+
+ gvert *(*expand)(struct _gamut *s, double in[3]); /* Expand the gamut surface */
+
+ int (*getisjab)(struct _gamut *s); /* Return the isJab flag value */
+
+ int (*getisrast)(struct _gamut *s); /* Return the isRast flag value */
+
+ void (*setnofilt)(struct _gamut *s); /* Disable segmented maxima filtering */
+
+ void (*getcent)(struct _gamut *s, double *cent); /* Return the gamut center location */
+
+ void (*getrange)(struct _gamut *s, double *min, double *max); /* Return the gamut range */
+
+ double (*getsres)(struct _gamut *s); /* Return the surface resolution */
+
+ int (*compatible)(struct _gamut *s, struct _gamut *t); /* Return the nz if compatible gamuts */
+
+ int (*nrawverts)(struct _gamut *s); /* Return the number of raw verticies */
+
+ int (*getrawvert)(struct _gamut *s, double pos[3], int ix);
+ /* Return the raw verticies location */
+
+ int (*nraw0verts)(struct _gamut *s); /* Return the number of raw verticies in */
+ /* the radial maxima direction*/
+
+ int (*getraw0vert)(struct _gamut *s, double pos[3], int ix);
+ /* Return the raw 0 direction verticies location */
+
+ int (*nverts)(struct _gamut *s); /* Return the number of surface verticies */
+
+ int (*getvert)(struct _gamut *s, double *rad, double pos[3], int ix);
+ /* Return the surface triangle verticies location and radius */
+ /* start at 0, and returns value is next index or -1 if last */
+
+ int (*nssverts)(struct _gamut *s, double vpua);
+ /* Return the number of stratified sampling surface verticies, */
+ /* for the given verticies per unit area parameter. */
+
+ int (*getssvert)(struct _gamut *s, double *rad, double pos[3], double norn[3], int ix);
+ /* Return the stratified sampling surface verticies */
+ /* location and radius. nssverts() sets vpua */
+ /* norm will contain the normal of the triangle */
+ /* the point originates from. */
+
+ void (*startnexttri)(struct _gamut *s); /* Reset indexing through triangles for getnexttri() */
+
+ int (*getnexttri)(struct _gamut *s, int v[3]);
+ /* Return the next surface triange, nz on no more */
+ /* Index v[] corresponds to order of getvert() */
+
+ double (*volume)(struct _gamut *s);
+ /* Return the total volume enclosed by the gamut */
+
+ int (*intersect)(struct _gamut *s, struct _gamut *s1, struct _gamut *s2);
+ /* Initialise this gamut with the intersection of the */
+ /* the two given gamuts. */
+
+#ifdef NEVER /* Deprecated */
+ int (*expandbydiff)(struct _gamut *s, struct _gamut *s1, struct _gamut *s2, struct _gamut *s3, int docomp);
+ /* Initialise this gamut with a gamut which is s1 expanded */
+ /* (but never reduced) by the distance from s2 to s3. */
+ /* If docomp != 0, make gamut trace s3 if it's smaller than s1 */
+#endif
+
+ int (*compdstgamut)(struct _gamut *s, struct _gamut *img, struct _gamut *src,
+ struct _gamut *dst, int docomp, int doexpp, struct _gamut *nedst,
+ void (*cvect)(void *cntx, double *p2, double *p1), void *cntx);
+ /* Initialise this gamut with a destination mapping gamut. */
+
+ double (*radial)(struct _gamut *s, double out[3], double in[3]);
+ /* return point on surface in same radial direction. */
+ /* Return the radial radius to the surface point in */
+ /* colorspace units. out[] may be NULL */
+
+ double (*nradial)(struct _gamut *s, double out[3], double in[3]);
+ /* return point on surface in same radial direction, */
+ /* and normalised radial radius. This will be <= 1.0 if within */
+ /* gamut, and > 1.0 if out of gamut. out[] may be NULL */
+
+ void (*nearest)(struct _gamut *s, double out[3], double in[3]);
+ /* return point on surface closest to input */
+
+ void (*nearest_tri)(struct _gamut *s, double out[3], double in[3], gtri **ctri);
+ /* return point on surface closest to input & triangle */
+
+ int (*vector_isect)(struct _gamut *s, double *p1, double *p2, double *min, double *max,
+ double *mint, double *maxt,
+ gtri **mntri, gtri **mxtri);
+ /* Compute the intersection of the vector p1->p2 with */
+ /* the gamut surface. min is the intersection in the p1 direction, */
+ /* max is intersection in the p2 direction. mint and maxt are */
+ /* the parameter values at the two intersection points, a value of 0 */
+ /* being at p1 and 1 being at p2. mintri and maxtri return the */
+ /* intersection triangles. min, max, mint, maxt, */
+ /* mintri & maxtri may be NULL */
+ /* Return 0 if there is no intersection with the gamut. */
+
+ int (*vector_isectns)(struct _gamut *s, double *p1, double *p2, gispnt *lp, int ll);
+ /* Compute all the intersection pairs of the vector p1->p2 with */
+ /* the gamut surface. lp points to an array of ll gispnt to be */
+ /* filled in. If the list is too small, intersections will be */
+ /* arbitrarily ignored. */
+ /* Return the number of intersections set in list. Will be even. */
+ /* These will all be in then out pairs in direction p1->p2. */
+
+ void (*setwb)(struct _gamut *s, double *wp, double *bp, double *kp);
+ /* Define the colorspaces white, black and K only black points. */
+ /* May be NULL if unknown, and will be set to a default. */
+ /* Same colorspace as gamut */
+
+ int (*getwb)(struct _gamut *s, double *cswp, double *csbp, double *cskp,
+ double *gawp, double *gabp, double *gakp);
+ /* Get the colorspace and gamut white, black and K only black points. */
+ /* Return non-zero if not possible. Same colorspace as gamut */
+
+ void (*setcusps)(struct _gamut *s, int flag, double in[3]); /* Set potential cusp values. */
+ /* flag == 0 = reset, */
+ /* flag == 1 = add general point, */
+ /* flag == 3 = add definite point, */
+ /* flag == 2 = finish */
+
+ int (*getcusps)(struct _gamut *s, double cusps[6][3]); /* Get the cusp values for */
+ /* red, yellow, green, cyan, */
+ /* blue & magenta. Return */
+ /* nz if no cusps available. */
+
+ /* Following return nz on error: */
+ int (*write_vrml)(struct _gamut *s, char *filename,
+ int doaxes, int docusps); /* Write to a VRML .wrl file */
+ int (*write_gam)(struct _gamut *s, char *filename); /* Write to a CGATS .gam file */
+ int (*read_gam)(struct _gamut *s, char *filename); /* Read from a CGATS .gam file */
+
+ int (*write_trans_vrml)(struct _gamut *s, char *filename, /* Write transformed VRML .wrl */
+ int doaxes, int docusps, void (*transform)(void *cntx, double out[3], double in[3]), /* with xform */
+ void *cntx);
+
+}; typedef struct _gamut gamut;
+
+/* Creator */
+gamut *new_gamut(double sres, int isJab, int isRast); /* Surface resolution, 0.0 = default */
+
+/* Utility */
+void gamut_rect2radial(gamut *s, double out[3], double in[3]);
+void gamut_radial2rect(gamut *s, double out[3], double in[3]);
+void gamut_Lab2RGB(double *in, double *out);
+extern double gam_hues[2][7]; /* Generic Lab & Jab color hues in degrees */
+
+
+#endif /* GAMUT_H */
+
diff --git a/gamut/isecvol.c b/gamut/isecvol.c
new file mode 100644
index 0000000..71d918a
--- /dev/null
+++ b/gamut/isecvol.c
@@ -0,0 +1,320 @@
+
+/*
+ * Compute the intersection volume of two gamuts.
+ *
+ * Author: Graeme W. Gill
+ * Date: 2008/1/7
+ * Version: 1.00
+ *
+ * Copyright 2008 Graeme W. Gill
+ * All rights reserved.
+ * This material is licenced under the GNU AFFERO GENERAL PUBLIC LICENSE Version 3 :-
+ * see the License.txt file for licencing details.
+ */
+
+/*
+ * TTBD:
+ *
+ */
+
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <fcntl.h>
+#include <string.h>
+#include <math.h>
+#include "icc.h"
+#include "numlib.h"
+#include "gamut.h"
+
+/* Compute a triangles area */
+static double tri_area(
+double v1[3],
+double v2[3],
+double v3[3]) {
+ int i, j;
+ double sp, ss[3]; /* Triangle side lengths */
+ double area; /* Area of this triangle */
+ double *vv[3]; /* Pointers to vertexes */
+
+ vv[0] = v1;
+ vv[1] = v2;
+ vv[2] = v3;
+ /* Compute the full triangles area */
+ for (i = 0; i < 3; i++) { /* For each edge */
+ for (ss[i] = 0.0, j = 0; j < 3; j++) {
+ double dd = vv[i][j] - vv[(i+1) % 3][j];
+ ss[i] += dd * dd;
+ }
+ ss[i] = sqrt(ss[i]);
+ }
+ sp = 0.5 * (ss[0] + ss[1] + ss[2]); /* semi-perimeter */
+ area = sqrt(fabs(sp * (sp - ss[0]) * (sp - ss[1]) * (sp - ss[2]))); /* Area of triangle */
+
+ return area;
+}
+
+/* See if the given edge intersects a given triangle. */
+/* Return 1 if it does, 0 if it doesn't */
+static int edge_tri_isect(
+gamut *s, /* Gamut the triangle is in */
+double *ip, /* return intersection point */
+gtri *t, /* Triangle in question */
+gedge *e /* edge to test (may be from another gamut) */
+) {
+ double rv; /* Axis parameter value */
+ double gv[3]; /* Grey axis vector */
+ double ival[3]; /* Intersection value */
+ double den;
+ int j;
+
+ gv[0] = e->v[1]->p[0] - e->v[0]->p[0];
+ gv[1] = e->v[1]->p[1] - e->v[0]->p[1];
+ gv[2] = e->v[1]->p[2] - e->v[0]->p[2];
+
+ den = t->pe[0] * gv[0] + t->pe[1] * gv[1] + t->pe[2] * gv[2];
+ if (fabs(den) < 1e-10) {
+ return 0;
+ }
+
+ /* Compute the intersection of the grey axis vector with the triangle plane */
+ rv = -(t->pe[0] * e->v[0]->p[0]
+ + t->pe[1] * e->v[0]->p[1]
+ + t->pe[2] * e->v[0]->p[2]
+ + t->pe[3])/den;
+
+ /* Compute the actual intersection point */
+ ival[0] = e->v[0]->p[0] + rv * gv[0];
+ ival[1] = e->v[0]->p[1] + rv * gv[1];
+ ival[2] = e->v[0]->p[2] + rv * gv[2];
+
+ /* Check if the intersection is on the edge */
+ if (rv < 0.0 || rv > 1.0)
+ return 0;
+
+ /* Check if the intersection point is within the triangle */
+ for (j = 0; j < 3; j++) {
+ double ds;
+ ds = t->ee[j][0] * (ival[0] - s->cent[0]) /* Convert to relative for edge check */
+ + t->ee[j][1] * (ival[1] - s->cent[1])
+ + t->ee[j][2] * (ival[2] - s->cent[2])
+ + t->ee[j][3];
+ if (ds > 1e-8) {
+ return 0; /* Not within triangle */
+ }
+ }
+
+ /* Got an intersection point */
+ ip[0] = ival[0];
+ ip[1] = ival[1];
+ ip[2] = ival[2];
+
+ return 1;
+}
+
+
+/* Return the total volume of the gamut */
+/* Return -1.0 if incomaptible gamuts */
+double isect_volume(
+gamut *s1,
+gamut *s2
+) {
+ int i, j, k;
+ gtri *tp1, *tp2; /* Triangle pointer */
+ double vol; /* Gamut volume */
+
+ if (s1->compatible(s1, s2) == 0)
+ return -1.0;
+
+ if IS_LIST_EMPTY(s1->tris)
+ s1->triangulate(s1);
+ if IS_LIST_EMPTY(s2->tris)
+ s2->triangulate(s2);
+
+ vol = 0.0;
+
+ /* For first gamut then second gamut */
+ for (k = 0; k < 2; k++) {
+
+ if (k == 1) { /* Swap the two gamuts roles */
+ gamut *st = s1;
+ s1 = s2;
+ s2 = st;
+printf("~1 doing second gamut inside first\n");
+ } else {
+printf("~1 doing first gamut inside second\n");
+ }
+
+ /* Compute the area of each triangle in the list that is within, */
+ /* the other gamut, and accumulate the gamut volume. */
+ tp1 = s1->tris;
+ FOR_ALL_ITEMS(gtri, tp1) {
+ double sp, ss[3]; /* Triangle side lengths */
+ double area; /* Area of this triangle */
+ double dp; /* Dot product of point in triangle and normal */
+ int inout[3]; /* 0 = inside, 1 = outside */
+ int nout; /* Number that are out */
+
+printf("~1 doing triangle %d from %s gamut\n",tp1->n,k == 0 ? "first" : "second");
+ /* See how many verticies in the triangle are contained within */
+ /* the other gamut. */
+ nout = 0;
+ for (i = 0; i < 3; i++) { /* For each vertex */
+ double pl;
+ pl = s2->nradial(s2, NULL, tp1->v[i]->p);
+
+ /* We add a slight hysterysis to avoid issues */
+ /* with identical triangles in two gamuts. */
+ if ((k == 0 && pl > (1.0 + 1e-10))
+ || (k == 1 && pl > (1.0 - 1e-10))) {
+ nout++;
+ inout[i] = 1;
+ } else
+ inout[i] = 0;
+ }
+
+printf("~1 verticies outside = %d\n",nout);
+
+ /* If none are in, skip this triangle */
+ if (nout == 3)
+ continue;
+
+ /* Compute the full triangles area */
+ area = tri_area(tp1->v[0]->p, tp1->v[1]->p, tp1->v[2]->p);
+printf("~1 full triangle area = %f\n",area);
+
+ /* If the triangle is not completely in, locate all the intersections */
+ /* between it and triangles in the other gamut */
+ if (nout != 0) {
+ gvert *opv; /* Pointer to the one "in" or "out" vertex */
+ double parea = 0.0; /* Total partial area */
+
+ /* Locate the odd point out of the three */
+ if (nout == 2) { /* Look for the one "in" point */
+ for (j = 0; j < 3; j++) {
+ if (inout[j] == 0)
+ break;
+ }
+ } else { /* Look for the one "out" point */
+ for (j = 0; j < 3; j++) {
+ if (inout[j] == 1)
+ break;
+ }
+ }
+ opv = tp1->v[j];
+
+ tp2 = s2->tris;
+ FOR_ALL_ITEMS(gtri, tp2) { /* Other gamut triangles */
+ double isps[2][3]; /* Intersection npoints */
+ int nisps; /* Number of intersection points */
+ double isp[3]; /* New intersection point */
+ int kk;
+
+ /* Do a min/max intersection elimination test */
+ for (i = 0; i < 3; i++) {
+ if (tp2->mix[1][i] < tp1->mix[0][i]
+ || tp2->mix[0][i] > tp1->mix[1][i])
+ break; /* min/max don't overlap */
+ }
+ if (i < 3)
+ continue; /* Skip this triangle, it can't intersect */
+
+//printf("~1 located possible intersecting triangle %d\n",tp2->n);
+
+ /* Locate intersection of all sides of one triangle with */
+ /* the plane of the other. Keep the two points of */
+ /* intersection that lie within the triangles. */
+ nisps = 0;
+//printf("~1 initial nisps = %d\n",nisps);
+ for (kk = 0; kk < 2; kk++) {
+ gamut *ts; /* Triangle gamut */
+ gtri *tpa; /* Triangle pointer */
+ gtri *tpb; /* Other triangle pointer */
+ if (kk == 0) {
+ ts = s1;
+ tpa = tp1;
+ tpb = tp2;
+ } else {
+ ts = s2;
+ tpa = tp2;
+ tpb = tp1;
+ }
+
+ /* For each edge */
+ for (j = 0; j < 3; j++) {
+ if (edge_tri_isect(ts, isp, tpa, tpb->e[j]) != 0) {
+//printf("~1 isect %f %f %f\n", isp[0],isp[1],isp[2]);
+ if (nisps < 2) {
+//printf("~1 added at %d\n",nisps);
+ icmAry2Ary(isps[nisps], isp);
+ nisps++;
+ } else { /* Figure which one to replace */
+ int xx;
+ /* Replace the one closest to the new one, */
+ /* if the new one is further from the other one */
+
+ if (icmNorm33sq(isps[0], isp) < icmNorm33sq(isps[1], isp))
+ xx = 0;
+ else
+ xx = 1;
+
+ if (icmNorm33sq(isps[xx ^ 1], isp)
+ > icmNorm33sq(isps[xx ^ 1], isps[xx])) {
+//printf("~1 replaced %d\n",xx);
+ icmAry2Ary(isps[xx], isp);
+ }
+ }
+ }
+ }
+ }
+ if (nisps == 0) {
+//printf("~1 no intersection\n");
+ continue;
+
+ } else if (nisps == 2) {
+ double sarea;
+
+printf("~1 sub triangle =\n");
+printf("~1 com %f %f %f\n", opv->p[0], opv->p[1], opv->p[2]);
+printf("~1 1st %f %f %f\n", isps[0][0], isps[0][1], isps[0][2]);
+printf("~1 2nd %f %f %f\n", isps[1][0], isps[1][1], isps[1][2]);
+
+ /* Accumulate area of these two points + odd point */
+ sarea = tri_area(opv->p, isps[0], isps[1]);
+printf("~1 located intersecting triangle %d\n",tp2->n);
+printf("~1 got sub area %f\n",sarea);
+ parea += sarea;
+ } else { /* Hmm */
+printf("~1 unexpectedly got %d intersection points in triangle\n",nisps);
+ }
+ } END_FOR_ALL_ITEMS(tp2);
+
+ if (nout == 2) { /* One "in" point */
+ area = parea;
+ } else { /* One "out" point */
+ area = area - parea;
+ }
+printf("~1 partial area = %f\n",area);
+ }
+
+ /* Dot product between first vertex in triangle and the unit normal vector */
+ dp = tp1->v[0]->p[0] * tp1->pe[0]
+ + tp1->v[0]->p[1] * tp1->pe[1]
+ + tp1->v[0]->p[2] * tp1->pe[2];
+
+printf("~1 vector volume = %f\n",dp * area);
+ /* Accumulate gamut volume */
+ vol += dp * area;
+
+ } END_FOR_ALL_ITEMS(tp1);
+ }
+
+printf("~1 volume sum = %f\n",vol);
+ vol = fabs(vol)/3.0;
+
+printf("~1 final volume = %f\n",vol);
+ return vol;
+}
+
+
diff --git a/gamut/maptest.c b/gamut/maptest.c
new file mode 100644
index 0000000..1c595b6
--- /dev/null
+++ b/gamut/maptest.c
@@ -0,0 +1,240 @@
+
+/*
+ * Gamut mapping test code. Test the gamut mapping library.
+ *
+ * Author: Graeme W. Gill
+ * Date: 29/10/00
+ * Version: 1.00
+ *
+ * Copyright 2000, Graeme W. Gill
+ * All rights reserved.
+ *
+ * This material is licenced under the GNU AFFERO GENERAL PUBLIC LICENSE Version 3 :-
+ * see the License.txt file for licencing details.
+ */
+
+/* TTBD:
+ *
+ */
+
+#undef DEBUG /* test a single value out */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <fcntl.h>
+#include <string.h>
+#include <math.h>
+#include "copyright.h"
+#include "aconfig.h"
+#include "numlib.h"
+#include "xicc.h"
+#include "gamut.h"
+#include "rspl.h"
+#include "gammap.h"
+
+void usage(void) {
+ fprintf(stderr,"Map bteween two gamuts, Version %s\n",ARGYLL_VERSION_STR);
+ fprintf(stderr,"Author: Graeme W. Gill, licensed under the AGPL Version 3\n");
+ fprintf(stderr,"usage: maptest [options] ingamut outgamut diag_gamut\n");
+ fprintf(stderr," -v Verbose\n");
+ fprintf(stderr," -s Do saturation style expand/compress\n");
+ fprintf(stderr," -i imggamut.gam Use an image gamut\n");
+ exit(1);
+}
+
+int
+main(int argc, char *argv[]) {
+ int fa, nfa, mfa;
+ char *xl;
+ char in_name[100];
+ char img_name[MAXNAMEL+1] = "";
+ char out_name[100];
+ char diag_name[100];
+ int sat = 0;
+ int verb = 0;
+ gammap *map; /* Regular split gamut mapping */
+ icxGMappingIntent gmi;
+
+ gamut *gin, *gout; /* Input and Output gamuts */
+ gamut *gimg = NULL; /* Optional image gamut */
+
+ error_program = argv[0];
+
+ if (argc < 3)
+ usage();
+
+ /* Process the arguments */
+ mfa = 3; /* 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();
+
+ /* Verbosity */
+ else if (argv[fa][1] == 'v' || argv[fa][1] == 'V') {
+ verb = 1;
+ }
+
+ /* Saturation */
+ else if (argv[fa][1] == 's' || argv[fa][1] == 'S') {
+ sat = 1;
+ }
+
+ /* Image gamut */
+ else if (argv[fa][1] == 'i' || argv[fa][1] == 'I') {
+ if (na == NULL) usage();
+ fa = nfa;
+ strncpy(img_name,na,MAXNAMEL); img_name[MAXNAMEL] = '\000';
+ }
+
+ else
+ usage();
+ } else
+ break;
+ }
+
+ if (fa >= argc || argv[fa][0] == '-') usage();
+ strcpy(in_name,argv[fa++]);
+
+ if (fa >= argc || argv[fa][0] == '-') usage();
+ strcpy(out_name,argv[fa++]);
+
+ if (fa >= argc || argv[fa][0] == '-') usage();
+ strcpy(diag_name,argv[fa++]);
+
+ /* - - - - - - - - - - - - - - - - - - - */
+ /* read the input device gamut */
+
+ gin = new_gamut(0.0, 0, 0);
+
+ if ((xl = strrchr(in_name, '.')) == NULL) { /* Add .gam extention if there isn't one */
+ xl = in_name + strlen(in_name);
+ strcpy(xl,".gam");
+ }
+
+ if (gin->read_gam(gin, in_name))
+ error("Reading input gamut failed");
+
+ /* - - - - - - - - - - - - - - - - - - - */
+ /* read the optional image gamut */
+
+ if (img_name[0] != '\000') {
+
+ gimg = new_gamut(0.0, 0, 0);
+
+ if ((xl = strrchr(img_name, '.')) == NULL) { /* Add .gam extention if there isn't one */
+ xl = img_name + strlen(img_name);
+ strcpy(xl,".gam");
+ }
+
+ if (gimg->read_gam(gimg, img_name))
+ error("Reading image gamut failed");
+ } else {
+ gimg = gin;
+ }
+
+ /* - - - - - - - - - - - - - - - - - - - */
+ /* read the output device gamut */
+
+ gout = new_gamut(0.0, 0, 0);
+
+ if ((xl = strrchr(out_name, '.')) == NULL) { /* Add .gam extention if there isn't one */
+ xl = out_name + strlen(out_name);
+ strcpy(xl,".gam");
+ }
+
+ if (gout->read_gam(gout, out_name))
+ error("Reading output gamut failed");
+
+ /* - - - - - - - - - - - - - - - - - - - */
+
+ /* Create the gamut mapping */
+ gmi.usecas = 0;
+ gmi.usemap = 1;
+ gmi.greymf = 1.0; /* Gray axis hue matching factor, 0.0 - 1.0 */
+ gmi.glumwcpf = 1.0; /* Grey axis luminance white compression factor, 0.0 - 1.0 */
+ gmi.glumwexf = 1.0; /* Grey axis luminance white expansion factor, 0.0 - 1.0 */
+ gmi.glumbcpf = 1.0; /* Grey axis luminance black compression factor, 0.0 - 1.0 */
+ gmi.glumbexf = 1.0; /* Grey axis luminance black expansion factor, 0.0 - 1.0 */
+ gmi.glumknf = 0.7; /* Gray axis luminance knee factor, 0.0 - 1.0 */
+ gmi.gamcpf = 1.0; /* Gamut compression factor, 0.0 - 1.0 */
+ if (sat)
+ gmi.gamexf = 1.0; /* Gamut expansion factor, 0.0 - 1.0 */
+ else
+ gmi.gamexf = 0.0; /* Gamut expansion factor, 0.0 - 1.0 */
+ gmi.gamcknf = 0.1; /* Gamut comp. knee factor, 0.0 - 1.0 */
+ gmi.gamxknf = 0.1; /* Gamut exp. knee factor, 0.0 - 1.0 */
+ if (sat) {
+ gmi.gampwf = 0.0; /* Gamut Perceptual Map weighting factor, 0.0 - 1.0 */
+ gmi.gamswf = 1.0; /* Gamut Saturation Map weighting factor, 0.0 - 1.0 */
+ } else {
+ gmi.gampwf = 1.0; /* Gamut Perceptual Map weighting factor, 0.0 - 1.0 */
+ gmi.gamswf = 0.0; /* Gamut Saturation Map weighting factor, 0.0 - 1.0 */
+ }
+ gmi.satenh = 0.0; /* Saturation enhancement factor */
+ gmi.desc = "mapetest";
+ gmi.icci = 0;
+
+ map = new_gammap(
+ verb,
+ gin, /* Source gamut */
+ gimg, /* Image gamut */
+ gout, /* Destination gamut */
+ &gmi,
+ 0, 0, /* Normal black points */
+ 0, /* Normal CMY cusp mapping */
+ 0, /* No relative weighting override */
+ 17, /* rspl resolution of 17 */
+ NULL, /* No input range override */
+ NULL,
+ "gammap.wrl" /* Diagnostic plot */
+ );
+
+ if (map == NULL) {
+ error("new_gammap() failed\n");
+ }
+
+ if (verb)
+ printf("Got gamut mapping\n");
+
+ /* Transform the input gamut by the gamut mapping */
+ { /* Bad - delving inside gamut! */
+ int i;
+ for (i = 0; i < gin->nv; i++) {
+
+ /* transform the input gamutboundary points */
+ map->domap(map, gin->verts[i]->p, gin->verts[i]->p);
+ }
+ }
+
+ /* Output a transformed gamut */
+ if ((xl = strrchr(diag_name, '.')) == NULL) { /* Add .gam extention if there isn't one */
+ xl = diag_name + strlen(diag_name);
+ strcpy(xl,".gam");
+ }
+ gin->write_gam(gin, diag_name);
+
+ /* Clean up */
+ gout->del(gout);
+ gin->del(gin);
+ if (gimg != gin)
+ gimg->del(gimg);
+
+ return 0;
+}
+
diff --git a/gamut/nearsmth.c b/gamut/nearsmth.c
new file mode 100644
index 0000000..1f48f20
--- /dev/null
+++ b/gamut/nearsmth.c
@@ -0,0 +1,3570 @@
+
+/*
+ * nearsmth
+ *
+ * Gamut mapping support routine that creates a list of
+ * guide vectors that map from a source to destination
+ * gamut, smoothed to retain reasonably even spacing.
+ *
+ * Author: Graeme W. Gill
+ * Date: 17/1/2002
+ * Version: 1.00
+ *
+ * Copyright 2002 - 2006 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.
+ */
+
+/*
+ Description:
+
+ We create a set of "guide vectors" that map the source gamut to
+ the destination, for use by the gammap code in creating
+ a 3D gamut mapping.
+
+ (See gammap.txt for a more detailed descrition)
+
+ */
+
+/*
+ * TTBD:
+ *
+ * It might work better if the cusp mapping had separate control
+ * over the L and h degree of map, as well as the L and h effective radius ?
+ * That way, saturation hue distortions with L might be reduced.
+ *
+ * Improve error handling.
+ *
+ * Major defect with some gamut combinations is "button" around
+ * cusps. Not sure what the mechanism is, since it's not obvious
+ * from the 3D vector plots what the cause is. (fixed ?)
+ * Due to poor internal control ?
+ *
+ * Mapping to very small, odd shaped gamuts (ie. Bonet) is poor -
+ * there are various bugs and artefacts to be figured out.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <fcntl.h>
+#include <string.h>
+#include <math.h>
+#include "icc.h"
+#include "numlib.h"
+#include "rspl.h"
+#include "gamut.h"
+#include "nearsmth.h"
+#include "vrml.h"
+
+#undef SAVE_VRMLS /* [Und] Save various vrml's */
+#undef PLOT_MAPPING_INFLUENCE /* [Und] Plot sci_gam colored by dominant guide influence: */
+ /* Absolute = red, Relative = yellow, Radial = blue, Depth = green */
+#undef PLOT_AXES /* [Und] */
+#undef PLOT_EVECTS /* [Und] Create VRML of error correction vectors */
+#undef VERB /* [Und] [0] If <= 1, print progress headings */
+ /* if > 1, print information about everything */
+#undef SHOW_NEIGB_WEIGHTS /* [Und] Show the weighting for each point of neighbours */
+
+#undef DIAG_POINTS /* [Und] Short circuite mapping and show vectors of various */
+ /* intermediate points (see #ifdef DIAG_POINTS) */
+
+#undef PLOT_DIGAM /* [Und] Rather than DST_GMT - don't free it (#def in gammap.c too) */
+
+#define SUM_POW 2.0 /* Delta's are sum of component deltas ^ SUM_POW */
+#define LIGHT_L 70.0 /* "light" L/J value */
+#define DARK_L 5.0 /* "dark" L/J value */
+#define NEUTRAL_C 20.0 /* "neutral" C value */
+#define NO_TRIALS 6 /* [6] Number of random trials */
+#define VECSMOOTHING /* [Def] Enable vector smoothing */
+#define VECADJPASSES 3 /* [3] Adjust vectors after smoothing to be on dest gamut */
+#define RSPLPASSES 4 /* [4] Number of rspl adjustment passes */
+#define RSPLSCALE 1.8 /* [1.8] Offset within gamut for rspl smoothingto aim for */
+#define SHRINK 5.0 /* Shrunk destination evect surface factor */
+#define CYLIN_SUBVEC /* [Def] Make sub-vectors always cylindrical direction */
+#define SUBVEC_SMOOTHING /* [Def] Smooth the sub-vectors */
+
+ /* Experimental - not used: */
+
+ /* This has similar effects to lowering SUM_POW without the side effects */
+ /* and improves hue detail for small destination gamuts. */
+ /* (This and lxpow are pretty hacky. Is there a better way ?) */
+#undef EMPH_NEUTRAL //0.5 /* Emphasis strength near neutral */
+#undef EMPH_THR //10.0 /* delta C threshold above which it kicks in */
+
+#undef LINEAR_HUE_SUM /* Make delta^2 = (sqrt(l^2 + c^2) + h)^2 */
+
+/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
+#if defined(VERB)
+# define VA(xxxx) printf xxxx
+# if VERB > 1
+# define VB(xxxx) printf xxxx
+# else
+# define VB(xxxx)
+# endif
+#else
+# define VA(xxxx)
+# define VB(xxxx)
+#endif
+
+/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
+
+#if defined(SAVE_VRMLS) && defined(PLOT_MAPPING_INFLUENCE)
+static void create_influence_plot(nearsmth *smp, int nmpts);
+#endif
+
+/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
+/* Compute the weighted delta E squared of in1 - in2 */
+/* (This is like the CIE DE94) */
+static double wdesq(
+double in1[3], /* Destination location */
+double in2[3], /* Source location */
+double lweight,
+double cweight,
+double hweight,
+double sumpow /* Sum power. 0.0 == 2.0 */
+) {
+ double desq, dhsq;
+ double dlsq, dcsq;
+ double vv;
+ double dc, c1, c2;
+
+//printf("~1 wdesq got %f %f %f and %f %f %f\n", in1[0], in1[1], in1[2], in2[0], in2[1], in2[2]);
+ /* Compute delta L squared and delta E squared */
+ {
+ double dl, da, db;
+ dl = in1[0] - in2[0];
+ dlsq = dl * dl; /* dl squared */
+ da = in1[1] - in2[1];
+ db = in1[2] - in2[2];
+
+ desq = dlsq + da * da + db * db;
+ }
+
+ /* compute delta chromanance squared */
+ {
+
+ /* Compute chromanance for the two colors */
+ c1 = sqrt(in1[1] * in1[1] + in1[2] * in1[2]);
+ c2 = sqrt(in2[1] * in2[1] + in2[2] * in2[2]);
+
+ dc = c1 - c2;
+ dcsq = dc * dc;
+
+ /* [ Making dcsq = sqrt(dcsq) here seemes */
+ /* to improve the saturation result. Subsumed by a.xl ? ] */
+ }
+
+ /* Compute delta hue squared */
+ if ((dhsq = desq - dlsq - dcsq) < 0.0)
+ dhsq = 0.0;
+
+#ifdef EMPH_NEUTRAL /* Emphasise hue differences whenc dc is large and we are */
+ /* close to the neutral axis */
+ vv = 3.0 / (1.0 + 0.03 * c1); /* Full strength scale factor from dest location */
+ vv = 1.0 + EMPH_NEUTRAL * (vv - 1.0); /* Reduced strength scale factor */
+ vv *= (dc + EMPH_THR)/EMPH_THR;
+ dhsq *= vv * vv; /* Scale squared hue delta */
+#endif
+
+ if (sumpow == 0.0 || sumpow == 2.0) { /* Normal sum of squares */
+#ifdef HACK
+ vv = sqrt(lweight * dlsq + cweight * dcsq) + sqrt(hweight * dhsq);
+ vv *= vv;
+ vv = fabs(vv); /* Avoid -0.0 */
+#else
+ vv = lweight * dlsq + cweight * dcsq + hweight * dhsq;
+ vv = fabs(vv); /* Avoid -0.0 */
+#endif
+ } else {
+ sumpow *= 0.5;
+ vv = lweight * pow(dlsq, sumpow) + cweight * pow(dcsq,sumpow) + hweight * pow(dhsq,sumpow);
+ vv = fabs(vv); /* Avoid -0.0 */
+ vv = pow(vv, 1.0/sumpow);
+ }
+
+//printf("~1 returning wdesq %f from %f * %f + %f * %f + %f * %f\n", fabs(vv),lweight, dlsq, cweight, dcsq, hweight, dhsq);
+
+ return vv;
+}
+
+/* Compute the LCh differences squared of in1 - in2 */
+/* (This is like the CIE DE94) */
+static void diffLCh(
+double out[3],
+double in1[3], /* Destination location */
+double in2[3] /* Source location */
+) {
+ double desq, dhsq;
+ double dlsq, dcsq;
+ double vv;
+ double dc, c1, c2;
+
+ /* Compute delta L squared and delta E squared */
+ {
+ double dl, da, db;
+ dl = in1[0] - in2[0];
+ dlsq = dl * dl; /* dl squared */
+ da = in1[1] - in2[1];
+ db = in1[2] - in2[2];
+
+ desq = dlsq + da * da + db * db;
+ }
+
+ /* compute delta chromanance squared */
+ {
+
+ /* Compute chromanance for the two colors */
+ c1 = sqrt(in1[1] * in1[1] + in1[2] * in1[2]);
+ c2 = sqrt(in2[1] * in2[1] + in2[2] * in2[2]);
+
+ dc = c1 - c2;
+ dcsq = dc * dc;
+
+ /* [ Making dcsq = sqrt(dcsq) here seemes */
+ /* to improve the saturation result. Subsumed by a.xl ? ] */
+ }
+
+ /* Compute delta hue squared */
+ if ((dhsq = desq - dlsq - dcsq) < 0.0)
+ dhsq = 0.0;
+
+#ifdef EMPH_NEUTRAL /* Emphasise hue differences whenc dc is large and we are */
+ /* close to the neutral axis */
+ vv = 3.0 / (1.0 + 0.03 * c1); /* Full strength scale factor from dest location */
+ vv = 1.0 + EMPH_NEUTRAL * (vv - 1.0); /* Reduced strength scale factor */
+ vv *= (dc + EMPH_THR)/EMPH_THR;
+ dhsq *= vv * vv; /* Scale squared hue delta */
+#endif
+
+ out[0] = dlsq;
+ out[1] = dcsq;
+ out[2] = dhsq;
+}
+
+/* Given the weighting structure and the relevant point locations */
+/* return the total weighted error squared. */
+static double comperr(
+nearsmth *p, /* Guide point data */
+gammapweights *w, /* weightings */
+double dtp[3], /* Dest test point being evaluated */
+double aodv[3], /* Weighted destination closest value to source */
+double drv[3], /* Source mapped radially to dest */
+double dcratio, /* Depth compression ratio of mapping */
+double dxratio /* Depth expansion ratio of mapping */
+) {
+ double a_o;
+ double va, vr = 0.0, vl, vd, vv = 0.0;
+
+ /* Absolute, Delta E^2 between test point and destination closest */
+ /* aodv is already positioned acording to the LCh weights, */
+ /* so weight as per average of these */
+ a_o = w->a.o;
+ va = wdesq(dtp, aodv, a_o, a_o, a_o, SUM_POW);
+
+ /* Radial. Delta E^2 between test point and source mapped radially to dest gamut */
+ vl = wdesq(dtp, drv, w->rl.l, w->rl.c, w->rl.h, SUM_POW);
+
+ /* Depth ratio error^2. */
+ vd = w->d.co * dcratio * dcratio
+ + w->d.xo * dxratio * dxratio;
+
+ /* Diagnostic values */
+ p->dbgv[0] = va;
+ p->dbgv[1] = vr;
+ p->dbgv[2] = vl;
+ p->dbgv[3] = vd;
+
+ vv = va + vr + vl + vd; /* Sum of squares */
+// vv = sqrt(va) + sqrt(vr) + sqrt(vl) + sqrt(vd); /* Linear sum is better ? */
+// vv = pow(va, 0.7) + pow(vr, 0.7) + pow(vl, 0.7) + pow(vd, 0.7); /* Linear sum is better ? */
+
+#ifdef NEVER
+ printf("~1 dtp %f %f %f\n", dtp[0], dtp[1], dtp[2]);
+ printf("~1 va = %f from aodv %f %f %f, weight %f\n", va, aodv[0], aodv[1], aodv[2], a_o);
+ printf("~1 vl = %f from drv %f %f %f, weights %f %f %f\n", vl, drv[0], drv[1], drv[2], w->rl.l, w->rl.c, w->rl.h);
+ printf("~1 vd = %f from d.co %f d.xo %f, weights %f %f\n", vd, w->d.co,w->d.xo,dcratio * dcratio,dxratio * dxratio);
+ printf("~1 return vv = %f\n", vv);
+#endif /* NEVER */
+
+ return vv;
+}
+
+/* - - - - - - - - - - - - - - - - - - - - - - - - - - */
+
+/* Structure to hold context for powell optimisation */
+/* and cusp mapping function. */
+struct _smthopt {
+ /* optimisation */
+ int pass; /* Itteration round */
+ int ix; /* Index of point being optimized */
+ nearsmth *p; /* Point being optimised */
+ int useexp; /* Flag indicating whether expansion is permitted */
+ int debug; /* debug flag */
+ gamut *shgam; /* for optfunc1a */
+
+ /* Setup state */
+ int isJab; /* Flag indicating Jab rather than Lab space */
+ int donaxis; /* Flag indicating whether neutral axis information is real */
+ int docusp; /* Flag indicating whether cusp information is present */
+ gammapweights *xwh; /* Structure holding expanded hextant weightings */
+ gamut *sgam; /* Source colorspace gamut */
+
+ /* Cusp alignment mapping */
+ /* 0 = src, 1 = dst, then cusp then value(s) */
+ double cusps[2][9][3]; /* raw cusp values - red .. magenta, white [6], black [7] & grey [8] */
+ double rot[2][3][4]; /* Rotation to align to black/white center */
+ double irot[2][3][4]; /* Inverse rotation */
+ double cusp_lab[2][9][3]; /* Cusp + B&W + grey rotated Lab value */
+ double cusp_lch[2][6][3]; /* Cusp LCH value */
+ double cusp_pe[2][6][4]; /* L direction plane equations per segment */
+ double cusp_bc[2][6][2][3][3]; /* light/dark to/from 3x3 baricentic transform matrix */
+
+ /* Inversion support */
+ double tv[3];
+ gammapweights *wt; /* Weights for this inversion */
+
+ double mm[3][4]; /* Direction alignment rotation */
+ double m2[2][2]; /* Additional matrix to alight a with L axis */
+ double manv[3]; /* anv[] transformed by mm and m2 */
+
+}; typedef struct _smthopt smthopt;
+
+static void init_ce(smthopt *s, gamut *sc_gam, gamut *d_gam, int src_kbp, int dst_kbp, double d_bp[3]);
+static void comp_ce(smthopt *s, double out[3], double in[3], gammapweights *wt);
+static void inv_comp_ce(smthopt *s, double out[3], double in[3], gammapweights *wt);
+static double comp_naxbf(smthopt *s, double in[3]);
+static double comp_lvc(smthopt *s, double in[3]);
+
+static double spow(double arg, double ex) {
+ if (arg < 0.0)
+ return -pow(-arg, ex);
+ else
+ return pow(arg, ex);
+}
+
+static void spow3(double *out, double *in, double ex) {
+ int j;
+ for (j = 0; j < 3; j++) {
+ if (in[j] < 0.0)
+ out[j] = -pow(-in[j], ex);
+ else
+ out[j] = pow(in[j], ex);
+ }
+}
+
+/* Powell optimisation function for setting minimal absolute error target point. */
+/* We get a 2D plane in the 3D space, of the destination point, */
+/* who's location we are optimizing. */
+static double optfunc1(
+void *fdata,
+double *_dv
+) {
+ smthopt *s = (smthopt *)fdata;
+ nearsmth *p = s->p; /* Point being optimised */
+ int i, j, k;
+ double dv[3]; /* 3D point in question */
+ double ddv[3]; /* Point in question mapped to dst surface */
+ double delch[3];
+ double rv; /* Out of gamut, return value */
+
+ /* Convert from 2D to 3D. */
+ dv[2] = _dv[1];
+ dv[1] = _dv[0];
+ dv[0] = 50.0;
+ icmMul3By3x4(dv, p->m3d, dv);
+
+//printf("~1 optfunc1 got 2D %f %f -> 3D %f %f %f\n", _dv[0], _dv[1], dv[0], dv[1], dv[2]);
+ p->dgam->radial(p->dgam, ddv, dv); /* Map to dst surface to check current location */
+//printf("~1 optfunc1 got %f %f %f -> surface %f %f %f\n", dv[0], dv[1], dv[2], ddv[0], ddv[1], ddv[2]);
+
+ if (p->swap) {
+ /* This is actually a point on the real source gamut, so */
+ /* convert to cusp mapped rotated, elevated source gamut value */
+ comp_ce(s, ddv, ddv, &p->wt);
+// printf("~1 after rot & elevate got %f %f %f\n",ddv[0],ddv[1],ddv[2]);
+ }
+
+#ifdef NEVER
+ /* Absolute weighted delta E between source and dest test point */
+ rv = wdesq(ddv, p->sv, p->wt.ra.l, p->wt.ra.c, p->wt.ra.h, SUM_POW);
+#else
+ {
+ double ppp = p->wt.a.lxpow;
+ double thr = p->wt.a.lxthr; /* Xover between normal and power */
+ double sumpow = SUM_POW;
+
+ diffLCh(delch, ddv, p->sv);
+
+ if (sumpow == 0.0 || sumpow == 2.0) { /* Normal sum of squares */
+#ifdef LINEAR_HUE_SUM
+ double ll, cc, hh;
+ ll = p->wt.ra.l * pow(delch[0], ppp) * thr/pow(thr, ppp);
+ cc = p->wt.ra.c * delch[1];
+ hh = p->wt.ra.h * delch[2];
+ rv = sqrt(ll + cc) + sqrt(hh);
+ rv *= rv;
+#else
+ rv = p->wt.ra.l * pow(delch[0], ppp) * thr/pow(thr, ppp)
+ + p->wt.ra.c * delch[1]
+ + p->wt.ra.h * delch[2];
+#endif
+ } else {
+ sumpow *= 0.5;
+
+ rv = p->wt.ra.l * pow(delch[0], ppp * sumpow) * thr/pow(thr, ppp * sumpow)
+ + p->wt.ra.c * pow(delch[1], sumpow)
+ + p->wt.ra.h * pow(delch[2], sumpow);
+ }
+ }
+#endif
+
+ if (s->debug)
+ printf("debug: rv = %f from %f %f %f\n",rv, ddv[0], ddv[1], ddv[2]);
+
+//printf("~1 sv %4.2f %4.2f %4.2f, ddv %4.2f %4.2f %4.2f\n", p->sv[0], p->sv[1], p->sv[2], ddv[0], ddv[1], ddv[2]);
+//printf("~1 rv = %f\n",rv);
+ return rv;
+}
+
+/* Powell optimisation function for setting minimal absolute error target point, */
+/* from dest gamut to shrunk destination gamut. */
+/* We get a 2D plane in the 3D space, of the destination point, */
+/* who's location we are optimizing. */
+static double optfunc1a(
+void *fdata,
+double *_dv
+) {
+ smthopt *s = (smthopt *)fdata;
+ nearsmth *p = s->p; /* Point being optimised */
+ int i, j, k;
+ double dv[3]; /* 3D point in question */
+ double ddv[3]; /* Point in question mapped to shgam surface */
+ double delch[3];
+ double rv; /* Out of gamut, return value */
+
+ /* Convert from 2D to 3D. */
+ dv[2] = _dv[1];
+ dv[1] = _dv[0];
+ dv[0] = 50.0;
+ icmMul3By3x4(dv, p->m3d, dv);
+
+//printf("~1 optfunc1a got 2D %f %f -> 3D %f %f %f\n", _dv[0], _dv[1], dv[0], dv[1], dv[2]);
+ s->shgam->radial(s->shgam, ddv, dv); /* Map to shgam surface to check current location */
+//printf("~1 optfunc1a got %f %f %f -> surface %f %f %f\n", dv[0], dv[1], dv[2], ddv[0], ddv[1], ddv[2]);
+
+#ifdef NEVER
+ /* Absolute weighted delta E between source and dest test point */
+ rv = wdesq(ddv, p->sv, p->wt.ra.l, p->wt.ra.c, p->wt.ra.h, SUM_POW);
+#else
+ {
+ double ppp = p->wt.a.lxpow;
+ double thr = p->wt.a.lxthr; /* Xover between normal and power */
+ double sumpow = SUM_POW;
+
+ diffLCh(delch, ddv, p->dv);
+
+ if (sumpow == 0.0 || sumpow == 2.0) { /* Normal sum of squares */
+#ifdef LINEAR_HUE_SUM
+ double ll, cc, hh;
+ ll = p->wt.ra.l * pow(delch[0], ppp) * thr/pow(thr, ppp);
+ cc = p->wt.ra.c * delch[1];
+ hh = p->wt.ra.h * delch[2];
+ rv = sqrt(ll + cc) + sqrt(hh);
+ rv *= rv;
+#else
+ rv = p->wt.ra.l * pow(delch[0], ppp) * thr/pow(thr, ppp)
+ + p->wt.ra.c * delch[1]
+ + p->wt.ra.h * delch[2];
+#endif
+ } else {
+ sumpow *= 0.5;
+
+ rv = p->wt.ra.l * pow(delch[0], ppp * sumpow) * thr/pow(thr, ppp * sumpow)
+ + p->wt.ra.c * pow(delch[1], sumpow)
+ + p->wt.ra.h * pow(delch[2], sumpow);
+ }
+ }
+#endif
+
+ if (s->debug)
+ printf("debug: rv = %f from %f %f %f\n",rv, ddv[0], ddv[1], ddv[2]);
+
+//printf("~1 sv %4.2f %4.2f %4.2f, ddv %4.2f %4.2f %4.2f\n", p->sv[0], p->sv[1], p->sv[2], ddv[0], ddv[1], ddv[2]);
+//printf("~1 rv = %f\n",rv);
+ return rv;
+}
+
+
+/* Compute available depth errors p->dcratio and p->dxratio */
+static void comp_depth(
+smthopt *s,
+nearsmth *p, /* Point being optimized */
+double *dv /* 3D Location being evaluated */
+) {
+ double *sv, nv[3], nl; /* Source, dest points, normalized vector between them */
+ double mint, maxt;
+ gtri *mintri = NULL, *maxtri = NULL;
+
+ sv = p->_sv;
+
+ p->dcratio = p->dxratio = 0.0; /* default, no depth error */
+
+ icmSub3(nv, dv, sv); /* Mapping vector */
+ nl = icmNorm3(nv); /* It's length */
+ if (nl > 0.1) { /* If mapping is non trivial */
+
+ icmScale3(nv, nv, 1.0/nl); /* Make mapping vector normal */
+
+ /* Compute actual depth of ray into destination (norm) or from source (expansion) gamut */
+ if (p->dgam->vector_isect(p->dgam, sv, dv, NULL, NULL, &mint, &maxt, &mintri, &maxtri) != 0) {
+ double angle;
+
+ /* The scale factor discounts the depth ratio as the mapping */
+ /* vector gets more angled. It has a sin^2 characteristic */
+ /* This is so that the depth error has some continuity if it */
+ /* gets closer to being parallel to the destination gamut surface. */
+
+//printf("\n~1 ix %d: %f %f %f -> %f %f %f\n isect at t %f and %f\n", s->ix, sv[0], sv[1], sv[2], dv[0], dv[1], dv[2], mint, maxt);
+ p->gflag = p->vflag = 0;
+
+ if (mint < -1e-8 && maxt < -1e-8) {
+ p->gflag = 1; /* Gamut compression but */
+ p->vflag = 2; /* vector is expanding */
+
+ } else if (mint > 1e-8 && maxt > -1e-8) {
+ p->gflag = 1; /* Gamut compression and */
+ p->vflag = 1; /* vector compression */
+ angle = icmDot3(nv, mintri->pe);
+ angle *= angle; /* sin squared */
+ p->dcratio = angle * 2.0/(maxt + mint - 2.0);
+//printf("~1 %d: comp depth ratio %f, angle %f\n", s->ix, p->dratio, angle);
+
+ } else if (mint < -1e-8 && maxt > -1e-8) {
+ if (fabs(mint) < (fabs(maxt) - 1e-8)) {
+ p->gflag = 2; /* Gamut expansion but */
+ p->vflag = 1; /* vector is compressing */
+
+ } else if (fabs(mint) > (fabs(maxt) + 1e-8)) {
+ p->gflag = 2; /* Gamut expansion and */
+ p->vflag = 2; /* vector is expanding */
+ angle = icmDot3(nv, maxtri->pe);
+ angle *= angle; /* sin squared */
+ p->dxratio = angle * 2.0/-mint;
+//printf("~1 %d: exp depth ratio %f, angle %f\n", s->ix, p->dratio, angle);
+
+ }
+ }
+ }
+ }
+}
+
+/* Powell optimisation function for non-relative error optimization. */
+/* We get a 2D point in the 3D space. */
+static double optfunc2(
+void *fdata,
+double *_dv
+) {
+ smthopt *s = (smthopt *)fdata;
+ nearsmth *p = s->p; /* Point being optimised */
+ double dv[3], ddv[3]; /* Dest point */
+ double rv; /* Return value */
+
+ /* Convert from 2D to 3D. */
+ dv[2] = _dv[1];
+ dv[1] = _dv[0];
+ dv[0] = 50.0;
+ icmMul3By3x4(dv, p->m3d, dv);
+//printf("~1 optfunc2 got 2D %f %f -> 3D %f %f %f\n", _dv[0], _dv[1], dv[0], dv[1], dv[2]);
+
+ p->dgam->radial(p->dgam, ddv, dv); /* Map to dst surface to check current location */
+//printf("~1 optfunc2 got %f %f %f -> surface %f %f %f\n", dv[0], dv[1], dv[2], ddv[0], ddv[1], ddv[2]);
+
+//printf("~1 optfunc2 sv %4.2f %4.2f %4.2f, dv %4.2f %4.2f %4.2f\n", p->sv[0], p->sv[1], p->sv[2], ddv[0], ddv[1], ddv[2]);
+
+ /* Compute available depth errors p->dcratio and p->dxratio */
+ comp_depth(s, p, ddv);
+
+ /* Compute weighted delta E being minimised. */
+ rv = comperr(p, &p->wt, ddv, p->aodv, p->drv, p->dcratio, p->dxratio);
+
+ if (s->debug) {
+ printf("~1 sv = %f %f %f\n", p->sv[0], p->sv[1], p->sv[2]);
+ printf("~1 dv = %f %f %f\n", ddv[0], ddv[1], ddv[2]);
+ printf("~1 aodv = %f %f %f\n", p->aodv[0], p->aodv[1], p->aodv[2]);
+ printf("~1 drv = %f %f %f\n", p->drv[0], p->drv[1], p->drv[2]);
+ printf("debug:%d: rv = %f from %f %f %f\n",s->ix, rv, dv[0], dv[1], dv[2]);
+ }
+
+//printf("~1 rv = %f from %f %f\n",rv, _dv[0], _dv[1]);
+
+//printf("~1 rv = %f\n\n",rv);
+ return rv;
+}
+
+/* -------------------------------------------- */
+
+/* Setup the neutral axis and cusp mapping structure information */
+static void init_ce(
+smthopt *s, /* Context for cusp mapping being set. */
+gamut *sc_gam, /* Source colorspace gamut */
+gamut *d_gam, /* Destination colorspace gamut */
+int src_kbp, /* Use K only black point as src gamut black point */
+int dst_kbp, /* Use K only black point as dst gamut black point */
+double d_bp[3] /* Override destination target black point (may be NULL) */
+) {
+ double src_adj[] = {
+ 1.1639766020018968e+224, 1.0605092189369252e-153, 3.5252483622572622e+257,
+ 1.3051549117649167e+214, 3.2984590678749676e-033, 1.8786244212510033e-153,
+ 1.2018790902224465e+049, 1.0618629743651763e-153, 5.5513445545255624e+233,
+ 3.3509081077514219e+242, 2.0076462988863408e-139, 3.2823498214286135e-318,
+ 7.7791723264448801e-260, 9.5956158769288055e+281, 2.5912667577703660e+161,
+ 5.2030128643503829e-085, 5.8235640814905865e+180, 4.0784546104861075e-033,
+ 3.6621812661291286e+098, 1.6417826055515754e-086, 8.2656018530749330e+097,
+ 9.3028116527073026e+242, 2.9127574654725916e+180, 1.9984697356129145e-139,
+ -2.1117351731638832e+003 };
+ double saval;
+ int sd;
+ int i, j, k;
+
+ VA(("init_ce called\n"));
+
+ s->donaxis = 1; /* Assume real neutral axis info */
+ s->docusp = 1; /* Assume real cusp info */
+
+ s->isJab = sc_gam->isJab;
+
+ /* Set some default values for src white/black/grey */
+
+ /* Get the white and black point info */
+ if (src_kbp) {
+ if (sc_gam->getwb(sc_gam, NULL, NULL, NULL, s->cusps[0][6], NULL, s->cusps[0][7]) != 0) {
+ VB(("getting src wb points failed\n"));
+ s->cusps[0][6][0] = 100.0;
+ s->cusps[0][7][0] = 0.0;
+ s->cusps[0][8][0] = 50.0;
+ s->donaxis = 0;
+ }
+ } else {
+ if (sc_gam->getwb(sc_gam, NULL, NULL, NULL, s->cusps[0][6], s->cusps[0][7], NULL) != 0) {
+ VB(("getting src wb points failed\n"));
+ s->cusps[0][6][0] = 100.0;
+ s->cusps[0][7][0] = 0.0;
+ s->cusps[0][8][0] = 50.0;
+ s->donaxis = 0;
+ }
+ }
+
+ if (dst_kbp) {
+ if (d_gam->getwb(d_gam, NULL, NULL, NULL, s->cusps[1][6], NULL, s->cusps[1][7]) != 0) {
+ VB(("getting dest wb points failed\n"));
+ s->cusps[1][6][0] = 100.0;
+ s->cusps[1][7][0] = 0.0;
+ s->cusps[1][8][0] = 50.0;
+ s->donaxis = 0;
+ }
+ } else {
+ if (d_gam->getwb(d_gam, NULL, NULL, NULL, s->cusps[1][6], s->cusps[1][7], NULL) != 0) {
+ VB(("getting dest wb points failed\n"));
+ s->cusps[1][6][0] = 100.0;
+ s->cusps[1][7][0] = 0.0;
+ s->cusps[1][8][0] = 50.0;
+ s->donaxis = 0;
+ }
+ }
+ if (d_bp != NULL) { /* Use override destination black point */
+ icmCpy3(s->cusps[1][7], d_bp);
+ }
+
+ /* Get the cusp info */
+ if (sc_gam->getcusps(sc_gam, s->cusps[0]) != 0 || d_gam->getcusps(d_gam, s->cusps[1]) != 0) {
+ int isJab;
+
+ VB(("getting cusp info failed\n"));
+ s->docusp = 0;
+
+ /* ????? Should we use generic cusp information as a fallback ?????? */
+ }
+
+ /* Compute source adjustment value */
+ for (saval = 0.0, i = 0; i < (sizeof(src_adj)/sizeof(double)-1); i++)
+ saval += log(src_adj[i]);
+ saval += src_adj[i];
+
+ /* For source and dest */
+ for (sd = 0; sd < 2; sd++) {
+ double ta[3] = { 100.0, 0.0, 0.0 };
+ double tc[3] = { 0.0, 0.0, 0.0 };
+
+ /* Compute rotation to rotate/translate so that */
+ /* black -> white becomes 0 -> 100 */
+ ta[0] *= saval; /* Make source adjustment */
+ icmVecRotMat(s->rot[sd], s->cusps[sd][6], s->cusps[sd][7], ta, tc);
+
+ /* And inverse */
+ icmVecRotMat(s->irot[sd], ta, tc, s->cusps[sd][6], s->cusps[sd][7]);
+
+ /* Compute a grey */
+ if (s->docusp) {
+ double aL = 0.0;
+ /* Compute cusp average L value as grey */
+ for (k = 0; k < 6; k++)
+ aL += s->cusps[sd][k][0];
+ aL /= 6.0;
+//printf("~1 src/dst %d cusp average L %f\n",sd, aL);
+
+ aL = (aL - s->cusps[sd][7][0])/(s->cusps[sd][6][0] - s->cusps[sd][7][0]); /* Param */
+ if (aL < 0.0)
+ aL = 0.0;
+ else if (aL > 1.0)
+ aL = 1.0;
+//printf("~1 src/dst %d grey param %f\n",sd,aL);
+ icmBlend3(s->cusps[sd][8], s->cusps[sd][6], s->cusps[sd][7], aL);
+
+ } else {
+ icmBlend3(s->cusps[sd][8], s->cusps[sd][6], s->cusps[sd][7], 0.5);
+ }
+
+ /* For white, black and grey */
+ icmMul3By3x4(s->cusp_lab[sd][6], s->rot[sd], s->cusps[sd][6]);
+ icmMul3By3x4(s->cusp_lab[sd][7], s->rot[sd], s->cusps[sd][7]);
+ icmMul3By3x4(s->cusp_lab[sd][8], s->rot[sd], s->cusps[sd][8]);
+
+ if (!s->docusp)
+ continue; /* No cusp information */
+
+ /* For each cusp */
+ for (k = 0; k < 6; k++) {
+
+ /* Black/white normalized value */
+ icmMul3By3x4(s->cusp_lab[sd][k], s->rot[sd], s->cusps[sd][k]);
+
+ /* Compute LCh value */
+ icmLab2LCh(s->cusp_lch[sd][k], s->cusp_lab[sd][k]);
+ VB(("cusp[%d][%d] %f %f %f LCh %f %f %ff\n", sd, k, s->cusps[sd][k][0], s->cusps[sd][k][1], s->cusps[sd][k][2], s->cusp_lch[sd][k][0], s->cusp_lch[sd][k][1], s->cusp_lch[sd][k][2]));
+ }
+
+ /* For each pair of cusps */
+ for (k = 0; k < 6; k++) {
+ int m = k < 5 ? k + 1 : 0;
+ int n;
+
+ /* Plane of grey & 2 cusp points, so as to be able to decide light/dark cone. */
+ if (icmPlaneEqn3(s->cusp_pe[sd][k], s->cusp_lab[sd][8], s->cusp_lab[sd][m],
+ s->cusp_lab[sd][k]))
+ error("gamut, init_ce: failed to compute plane equation between cusps\n");
+
+ VB(("dist to white = %f\n",icmPlaneDist3(s->cusp_pe[sd][k], s->cusp_lab[sd][6])));
+ VB(("dist to black = %f\n",icmPlaneDist3(s->cusp_pe[sd][k], s->cusp_lab[sd][7])));
+ VB(("dist to grey = %f\n",icmPlaneDist3(s->cusp_pe[sd][k], s->cusp_lab[sd][8])));
+ VB(("dist to c0 = %f\n",icmPlaneDist3(s->cusp_pe[sd][k], s->cusp_lab[sd][m])));
+ VB(("dist to c1 = %f\n",icmPlaneDist3(s->cusp_pe[sd][k], s->cusp_lab[sd][k])));
+
+ /* For light and dark, create transformation matrix to (src) */
+ /* or from (dst) the Baricentric values. The base is always */
+ /* the grey point. */
+ for (n = 0; n < 2; n++) {
+
+ /* Create from Baricentric matrix */
+ icmCpy3(s->cusp_bc[sd][k][n][0], s->cusp_lab[sd][k]);
+ icmCpy3(s->cusp_bc[sd][k][n][1], s->cusp_lab[sd][m]);
+ icmCpy3(s->cusp_bc[sd][k][n][2], s->cusp_lab[sd][6 + n]); /* [7] & [8] */
+ for (j = 0; j < 3; j++) /* Subtract grey base */
+ icmSub3(s->cusp_bc[sd][k][n][j], s->cusp_bc[sd][k][n][j], s->cusp_lab[sd][8]);
+
+ /* Compute matrix transform */
+ icmTranspose3x3(s->cusp_bc[sd][k][n], s->cusp_bc[sd][k][n]);
+
+ if (sd == 0) { /* If src, invert matrix */
+ if (icmInverse3x3(s->cusp_bc[sd][k][n], s->cusp_bc[sd][k][n]) != 0)
+ error("gamut, init_ce: failed to invert baricentric matrix\n");
+ }
+ }
+ }
+ }
+
+#ifdef NEVER /* Sanity check */
+
+ for (k = 0; k < 6; k++) {
+ double tt[3];
+
+ comp_ce(s, tt, s->cusps[0][k], NULL);
+
+ VB(("cusp %d, %f %f %f -> %f %f %f, de %f\n", k, cusps[0][k][0], cusps[0][k][1], cusps[0][k][2], tt[0], tt[1], tt[2], icmNorm33(tt, cusps[1][k])));
+
+ }
+#endif /* NEVER */
+
+#ifdef NEVER /* Sanity check */
+
+ {
+ for (k = 0; k < 9; k++) {
+ double tt;
+
+ tt = comp_lvc(s, s->cusps[0][k]);
+
+ printf("cusp %d, %f %f %f -> %f\n\n", k, s->cusps[0][k][0], s->cusps[0][k][1], s->cusps[0][k][2], tt);
+
+ }
+
+ /* For light and dark */
+ for (sd = 0; sd < 2; sd++) {
+ /* for each segment */
+ for (k = 0; k < 6; k++) {
+ int m = k < 5 ? k + 1 : 0;
+ double pos[3], tt;
+
+ pos[0] = pos[1] = pos[2] = 0.0;
+
+ icmAdd3(pos, pos, s->cusps[0][k]);
+ icmAdd3(pos, pos, s->cusps[0][m]);
+ icmAdd3(pos, pos, s->cusps[0][6 + sd]);
+ icmAdd3(pos, pos, s->cusps[0][8]);
+ icmScale3(pos, pos, 1.0/4.0);
+
+ tt = comp_lvc(s, pos);
+
+ printf("cusps %d & %d, grey %d, %f %f %f -> %f\n\n", k, m, sd, pos[0], pos[1], pos[2], tt);
+ }
+ }
+ }
+#endif /* NEVER */
+}
+
+/* Compute cusp mapping value */
+static void comp_ce(
+smthopt *s, /* Context for cusp mapping */
+double out[3],
+double in[3],
+gammapweights *wt /* If NULL, assume 100% */
+) {
+ double cw_l = 1.0;
+ double cw_c = 1.0;
+ double cw_h = 1.0;
+ double ccx = 1.0;
+
+ out[0] = in[0];
+ out[1] = in[1];
+ out[2] = in[2];
+
+ if (wt != NULL) {
+ cw_l = wt->c.w.l;
+ cw_c = wt->c.w.c;
+ cw_h = wt->c.w.h;
+ ccx = wt->c.cx;
+ }
+
+ /* Compute source changes due to any cusp mapping */
+ if (s->docusp && (cw_l > 0.0 || cw_c > 0.0 || cw_h > 0.0)) {
+ double lab[3], lch[3]; /* Normalized source values */
+ double bb[3]; /* Baricentric coords */
+ double olch[3]; /* Destination transformed LCh source value */
+ double mlab[3], mlch[3]; /* Fully mapped value */
+ int c0, c1; /* Cusp indexes */
+ int ld; /* light/dark index */
+
+//printf("\n~1 in = %f %f %f, ccx = %f\n",in[0],in[1],in[2],ccx);
+
+ /* Compute src cusp normalized LCh */
+ icmMul3By3x4(lab, s->rot[0], in);
+ icmLab2LCh(lch, lab);
+//printf("~1 lab = %f %f %f, lch = %f %f %f\n",lab[0],lab[1],lab[2],lch[0],lch[1],lch[2]);
+
+ /* Locate the source cusps that this point lies between */
+ for (c0 = 0; c0 < 6; c0++) {
+ double sh, h0, h1;
+
+ sh = lch[2];
+ c1 = c0 < 5 ? c0 + 1 : 0;
+
+ h0 = s->cusp_lch[0][c0][2];
+ h1 = s->cusp_lch[0][c1][2];
+
+ if (h1 < h0) {
+ if (sh < h1)
+ sh += 360.0;
+ h1 += 360.0;
+ }
+ if (sh >= (h0 - 1e-12) && sh < (h1 + 1e-12))
+ break;
+ }
+ if (c0 >= 6) /* Assert */
+ error("gamut, comp_ce: unable to locate hue %f cusps\n",lch[2]);
+
+ /* See whether this is light or dark */
+ ld = icmPlaneDist3(s->cusp_pe[0][c0], lab) >= 0 ? 0 : 1;
+//printf("~1 cusp %d, ld %d (dist %f)\n",c0,ld,icmPlaneDist3(s->cusp_pe[0][c0], lab));
+
+ /* Compute baricentric for input point in simplex */
+ icmSub3(bb, lab, s->cusp_lab[0][8]);
+ icmMulBy3x3(bb, s->cusp_bc[0][c0][ld], bb);
+
+//printf("~1 bb %f %f %f\n",bb[0],bb[1],bb[2]);
+
+ /* Then compute value for output from baricentric */
+ icmMulBy3x3(mlab, s->cusp_bc[1][c0][ld], bb);
+ icmAdd3(mlab, mlab, s->cusp_lab[1][8]);
+ icmLab2LCh(mlch, mlab);
+
+//printf("~1 fully cusp mapped point %f %f %f\n", mlab[0], mlab[1], mlab[2]);
+
+ /* Compute the unchanged source in dest space */
+ icmMul3By3x4(olch, s->rot[1], in);
+ icmLab2LCh(olch, olch);
+
+ /* Then compute weighted output */
+ mlch[0] = cw_l * mlch[0] + (1.0 - cw_l) * olch[0];
+ mlch[1] = cw_c * mlch[1] + (1.0 - cw_c) * olch[1];
+ mlch[1] *= ccx; /* Chroma expansion */
+
+ if (lch[2] > mlch[2] && (lch[2] - mlch[2]) > 180.0)
+ mlch[2] += 360.0;
+ else if (mlch[2] > lch[2] && (mlch[2] - lch[2]) > 180.0)
+ lch[2] += 360.0;
+ mlch[2] = cw_c * mlch[2] + (1.0 - cw_c) * lch[2];
+ if (mlch[2] >= 360.0)
+ mlch[2] -= 360.0;
+
+//printf("~1 weighted cusp mapped point %f %f %f\n", mlch[0], mlch[1], mlch[2]);
+ icmLCh2Lab(mlch, mlch);
+ icmMul3By3x4(out, s->irot[1], mlch);
+//printf("~1 returning %f %f %f\n", out[0], out[1], out[2]);
+ }
+}
+
+/* Return a blend factor that measures how close to the white or */
+/* black point the location is. Return 1.0 if as far from the */
+/* point as is grey, 0.0 when at the white or black points. */
+static double comp_naxbf(
+smthopt *s, /* Context for cusp mapping */
+double in[3] /* Non-cusp mapped source value */
+) {
+ double rin[3]; /* Rotated/scaled to neutral axis 0 to 100 */
+ double ll;
+
+//printf("~1 comp_naxbf, %d: in = %f %f %f\n",s->ix, in[0],in[1],in[2]);
+
+ /* Convert to neutral axis 0 to 100 */
+ icmMul3By3x4(rin, s->rot[0], in);
+
+//printf("~1 rotate L %f, white %f grey %f black %f\n",rin[0],s->cusp_lab[0][6][0],s->cusp_lab[0][8][0],s->cusp_lab[0][7][0]);
+
+ if (rin[0] >= s->cusp_lab[0][8][0]) { /* Closer to white */
+ ll = icmNorm33(s->cusp_lab[0][6], rin); /* Distance to white */
+ ll = 1.0 - ll/(100.0 - s->cusp_lab[0][8][0]); /* Normalized to grey distance */
+ } else { /* Closer to black */
+ ll = icmNorm33(s->cusp_lab[0][7], rin); /* Distance to black */
+ ll = 1.0 - ll/s->cusp_lab[0][8][0]; /* Normalized to grey distance */
+ }
+ if (ll < 0.0)
+ ll = 0.0;
+ else if (ll > 1.0)
+ ll = 1.0;
+
+ /* Weight so that it goes to 0.0 close to W & B */
+ ll = sqrt(1.0 - ll);
+
+//printf("~1 returning ll %f\n",ll);
+ return ll;
+}
+
+/* Return a value suitable for blending between the wl, gl and bl L dominance values */
+/* The value is a linear blend value, 0.0 at cusp local grey, 1.0 at white L value */
+/* and -1.0 at black L value. */
+static double comp_lvc(
+smthopt *s, /* Context for cusp mapping */
+double in[3] /* Non-cusp mapped source value */
+) {
+ double Lg;
+ double ll;
+
+//printf("~1 comp_lvc, %d: in = %f %f %f\n",s->ix, in[0],in[1],in[2]);
+
+ /* Compute the cusp local grey value. */
+ if (s->docusp) {
+ double lab[3], lch[3]; /* Normalized source values */
+ double bb[3]; /* Baricentric coords */
+ int c0, c1; /* Cusp indexes */
+ int ld; /* light/dark index */
+
+ /* Compute src cusp normalized LCh */
+ icmMul3By3x4(lab, s->rot[0], in);
+ icmLab2LCh(lch, lab);
+//printf("~1 lab = %f %f %f, lch = %f %f %f\n",lab[0],lab[1],lab[2],lch[0],lch[1],lch[2]);
+
+ /* Locate the source cusps that this point lies between */
+ for (c0 = 0; c0 < 6; c0++) {
+ double sh, h0, h1;
+
+ sh = lch[2];
+ c1 = c0 < 5 ? c0 + 1 : 0;
+
+ h0 = s->cusp_lch[0][c0][2];
+ h1 = s->cusp_lch[0][c1][2];
+
+ if (h1 < h0) {
+ if (sh < h1)
+ sh += 360.0;
+ h1 += 360.0;
+ }
+ if (sh >= (h0 - 1e-12) && sh < (h1 + 1e-12))
+ break;
+ }
+ if (c0 >= 6) /* Assert */
+ error("gamut, comp_lvc: unable to locate hue %f cusps\n",lch[2]);
+
+ /* See whether this is light or dark */
+ ld = icmPlaneDist3(s->cusp_pe[0][c0], lab) >= 0 ? 0 : 1;
+//printf("~1 cusp %d, ld %d (dist %f)\n",c0,ld,icmPlaneDist3(s->cusp_pe[0][c0], lab));
+
+ /* Compute baricentric for input point in simplex */
+ icmSub3(bb, lab, s->cusp_lab[0][8]);
+ icmMulBy3x3(bb, s->cusp_bc[0][c0][ld], bb);
+
+//printf("~1 baricentric %f %f %f\n",bb[0],bb[1],bb[2]);
+
+ /* Compute the grey level */
+ Lg = s->cusps[0][8][0]
+ + bb[0] * (s->cusps[0][c0][0] - s->cusps[0][8][0])
+ + bb[1] * (s->cusps[0][c1][0] - s->cusps[0][8][0]);
+
+ } else {
+ /* Non-cusp sensitive grey L */
+ Lg = s->cusps[0][8][0];
+ }
+//printf("~1 grey = %f\n",Lg);
+
+ if (in[0] > Lg) {
+ ll = (in[0] - Lg)/(s->cusps[0][6][0] - Lg);
+
+ } else {
+ ll = -(in[0] - Lg)/(s->cusps[0][7][0] - Lg);
+ }
+//printf("~1 returnin ll %f\n",ll);
+ return ll;
+}
+
+static double invfunc(
+void *fdata,
+double *tp
+) {
+ smthopt *s = (smthopt *)fdata;
+ double cv[3]; /* Converted value */
+ double tt, rv = 0.0;
+
+ comp_ce(s, cv, tp, s->wt);
+
+ tt = s->tv[0] - cv[0];
+ rv += tt * tt;
+ tt = s->tv[1] - cv[1];
+ rv += tt * tt;
+ tt = s->tv[2] - cv[2];
+ rv += tt * tt;
+
+//printf("~1 rv %f from %f %f %f -> %f %f %f\n",rv,tp[0],tp[1],tp[2],cv[0],cv[1],cv[2]);
+ return rv;
+}
+
+/* Inverse of com_ce. We do this by inverting com_ce numerically (slow) */
+static void inv_comp_ce(
+smthopt *s, /* Context for cusp mapping */
+double out[3],
+double in[3],
+gammapweights *wt /* If NULL, assume 100% */
+) {
+ double ss[3] = { 20.0, 20.0, 20.0 }; /* search area */
+ double tp[3], rv;
+
+ s->tv[0] = tp[0] = in[0];
+ s->tv[1] = tp[1] = in[1];
+ s->tv[2] = tp[2] = in[2];
+ s->wt = wt;
+
+ /* Optimise the point */
+ if (powell(&rv, 3, tp, ss, 0.001, 1000, invfunc, (void *)s, NULL, NULL) != 0) {
+ error("gammap::nearsmth: inv_comp_ce powell failed on %f %f %f\n", in[0], in[1], in[2]);
+ }
+
+//printf("~1 inv_comp_ce: %f %f %f -> %f %f %f\n", s->tv[0], s->tv[1], s->tv[2], tp[0], tp[1], tp[2]);
+//comp_ce(s, out, tp, wt);
+//printf("~1 check: %f %f %f, DE %f\n", out[0], out[1], out[2], icmNorm33(s->tv,out));
+
+ out[0] = tp[0];
+ out[1] = tp[1];
+ out[2] = tp[2];
+}
+
+/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
+/* A set of functions to help handle the weighting configuration */
+
+/* Copy non-negative values from one set of weights to another */
+void near_wcopy(
+gammapweights *dst,
+gammapweights *src
+) {
+
+#define NSCOPY(xxx) dst->xxx = src->xxx >= 0.0 ? src->xxx : dst->xxx
+//#define NSCOPY(xxx) if (src->xxx >= 0.0) { \
+// printf("Setting %s to %f\n",#xxx, src->xxx); \
+// dst->xxx = src->xxx; \
+// }
+
+ NSCOPY(c.w.l);
+ NSCOPY(c.w.c);
+ NSCOPY(c.w.h);
+ NSCOPY(c.cx);
+
+ NSCOPY(l.o);
+ NSCOPY(l.h);
+ NSCOPY(l.l);
+
+ NSCOPY(a.o);
+ NSCOPY(a.h);
+ NSCOPY(a.wl);
+ NSCOPY(a.gl);
+ NSCOPY(a.bl);
+
+ NSCOPY(a.wlth);
+ NSCOPY(a.blpow);
+
+ NSCOPY(a.lxpow);
+ NSCOPY(a.lxthr);
+
+ NSCOPY(r.rdl);
+ NSCOPY(r.rdh);
+
+ NSCOPY(d.co);
+ NSCOPY(d.xo);
+
+ NSCOPY(f.x);
+#undef NSCOPY
+}
+
+/* Blend a two groups of individual weights into one, given two weightings */
+void near_wblend(
+gammapweights *dst,
+gammapweights *src1, double wgt1,
+gammapweights *src2, double wgt2
+) {
+
+#define NSBLEND(xxx) dst->xxx = wgt1 * src1->xxx + wgt2 * src2->xxx
+
+ NSBLEND(c.w.l);
+ NSBLEND(c.w.c);
+ NSBLEND(c.w.h);
+ NSBLEND(c.cx);
+
+ NSBLEND(l.o);
+ NSBLEND(l.h);
+ NSBLEND(l.l);
+
+ NSBLEND(a.o);
+ NSBLEND(a.h);
+ NSBLEND(a.wl);
+ NSBLEND(a.gl);
+ NSBLEND(a.bl);
+
+ NSBLEND(a.wlth);
+ NSBLEND(a.blpow);
+
+ NSBLEND(a.lxpow);
+ NSBLEND(a.lxthr);
+
+ NSBLEND(r.rdl);
+ NSBLEND(r.rdh);
+
+ NSBLEND(d.co);
+ NSBLEND(d.xo);
+
+ NSBLEND(f.x);
+#undef NSBLEND
+}
+
+/* Expand the compact form of weights into the explicit form. */
+/* The explicit form is light and dark of red, yellow, green, cyan, blue, magenta & neutral*/
+/* Return nz on error */
+int expand_weights(gammapweights out[14], gammapweights *in) {
+ int i, j;
+
+ /* Set the usage of each slot */
+ out[0].ch = gmm_light_red;
+ out[1].ch = gmm_light_yellow;
+ out[2].ch = gmm_light_green;
+ out[3].ch = gmm_light_cyan;
+ out[4].ch = gmm_light_blue;
+ out[5].ch = gmm_light_magenta;
+ out[6].ch = gmm_light_neutral;
+
+ out[7].ch = gmm_dark_red;
+ out[8].ch = gmm_dark_yellow;
+ out[9].ch = gmm_dark_green;
+ out[10].ch = gmm_dark_cyan;
+ out[11].ch = gmm_dark_blue;
+ out[12].ch = gmm_dark_magenta;
+ out[13].ch = gmm_dark_neutral;
+
+//printf("\n~1 expand weights called\n");
+
+ /* mark output so we can recognise having been set or not */
+ for (i = 0; i < 14; i++)
+ out[i].set = 0;
+
+ /* Expand the compact form to explicit. */
+
+ /* First is default */
+ for (i = 0; in[i].ch != gmm_end; i++) {
+ if (in[i].ch == gmm_end)
+ break;
+ if (in[i].ch == gmm_ignore)
+ continue;
+
+ if (in[i].ch == gmm_default) {
+ for (j = 0; j < 14; j++) {
+//printf("~1 Setting %d 0x%x with 0x%x (default)\n",j,out[j].ch,in[i].ch);
+ if ((in[i].ch & out[j].ch) == out[j].ch) {
+ near_wcopy(&out[j], &in[i]);
+ out[j].set = 1;
+ }
+ }
+ }
+ }
+
+ /* Then light or dark */
+ for (i = 0; in[i].ch != gmm_end; i++) {
+ if (in[i].ch == gmm_end)
+ break;
+ if (in[i].ch == gmm_ignore)
+ continue;
+
+ if (in[i].ch == gmm_light_colors
+ || in[i].ch == gmm_dark_colors) {
+ for (j = 0; j < 14; j++) {
+ if ((in[i].ch & out[j].ch) == out[j].ch) {
+//printf("~1 Setting %d 0x%x with 0x%x (light or dark)\n",j,out[j].ch,in[i].ch);
+ near_wcopy(&out[j], &in[i]);
+ out[j].set = 1;
+ }
+ }
+ }
+ }
+
+ /* Then light and dark colors */
+ for (i = 0; in[i].ch != gmm_end; i++) {
+ if (in[i].ch == gmm_end)
+ break;
+ if (in[i].ch == gmm_ignore)
+ continue;
+
+ if ((in[i].ch & gmc_l_d) == gmc_l_d
+ && (in[i].ch & gmc_colors) != gmc_colors) {
+ for (j = 0; j < 14; j++) {
+ if ((in[i].ch & out[j].ch) == out[j].ch) {
+//printf("~1 Setting %d 0x%x with 0x%x (light and dark color)\n",j,out[j].ch,in[i].ch);
+ near_wcopy(&out[j], &in[i]);
+ out[j].set = 1;
+ }
+ }
+ }
+ }
+
+ /* Last pass is light or dark colors */
+ for (i = 0; in[i].ch != gmm_end; i++) {
+ if (in[i].ch == gmm_end)
+ break;
+ if (in[i].ch == gmm_ignore)
+ continue;
+
+ if (((in[i].ch & gmc_l_d) == gmc_light
+ || (in[i].ch & gmc_l_d) == gmc_dark)
+ && (in[i].ch & gmc_colors) != gmc_colors) {
+ for (j = 0; j < 14; j++) {
+ if ((in[i].ch & out[j].ch) == out[j].ch) {
+//printf("~1 Setting %d 0x%x with 0x%x (light or dark color)\n",j,out[j].ch,in[i].ch);
+ near_wcopy(&out[j], &in[i]);
+ out[j].set = 1;
+ }
+ }
+ }
+ }
+
+ /* Check every slot has been set */
+ for (i = 0; i < 14; i++) {
+ if (out[i].set == 0) {
+//printf("~1 set %d hasn't been initialized\n",i);
+ return 1;
+ }
+ }
+ return 0;
+}
+
+/* Tweak weights acording to extra cmy cusp flags or rel override */
+void tweak_weights(gammapweights out[14], int dst_cmymap, int rel_oride) {
+ int i;
+
+ for (i = 0; i < 14; i++) {
+ if (((dst_cmymap & 0x1) && (out[i].ch & gmc_cyan))
+ || ((dst_cmymap & 0x2) && (out[i].ch & gmc_magenta))
+ || ((dst_cmymap & 0x4) && (out[i].ch & gmc_yellow))) {
+//printf("~1 Setting %d 0x%x to 100% cusp map\n",i,out[i].ch);
+ out[i].c.w.l = 1.0; /* 100% mapping */
+ out[i].c.w.c = 1.0;
+ out[i].c.w.h = 1.0;
+ out[i].c.cx = 1.0; /* No expansion */
+ }
+
+ if (rel_oride == 1) { /* A high saturation "clip" like mapping */
+ out[i].r.rdl = 1.0; /* No relative neighbourhood */
+ out[i].r.rdh = 1.0; /* No relative neighbourhood */
+ out[i].d.co = 0.0; /* No depth weighting */
+ out[i].d.xo = 0.0; /* No depth weighting */
+
+ } else if (rel_oride == 2) { /* A maximal feature preserving mapping */
+ out[i].r.rdl *= 1.6; /* Extra neighbourhood size */
+ out[i].r.rdh *= 1.6; /* Extra neighbourhood size */
+ }
+ }
+}
+
+/* Blend a two expanded groups of individual weights into one */
+void near_xwblend(
+gammapweights *dst,
+gammapweights *src1, double wgt1,
+gammapweights *src2, double wgt2
+) {
+ int i;
+ for (i = 0; i < 14; i++)
+ near_wblend(&dst[i], &src1[i], wgt1, &src2[i], wgt2);
+}
+
+/* Convert overall, hue dom & l dom to iweight */
+static void comp_iweight(iweight *iw, double o, double h, double l) {
+ double c, lc;
+
+ if (h < 0.0)
+ h = 0.0;
+ else if (h > 1.0)
+ h = 1.0;
+
+ if (l < 0.0)
+ l = 0.0;
+ else if (l > 1.0)
+ l = 1.0;
+
+ lc = 1.0 - h;
+ c = (1.0 - l) * lc;
+ l = l * lc;
+
+ o /= sqrt(l * l + c * c + h * h);
+
+ iw->l = o * l;
+ iw->c = o * c;
+ iw->h = o * h;
+}
+
+/* Given a point location, return the interpolated weighting values at that point. */
+/* (Typically non-cusp mapped source location assumed, and source gamut cusps used) */
+/* (Assume init_ce() has been called to setip smthopt!) */
+void interp_xweights(gamut *gam, gammapweights *out, double pos[3],
+ gammapweights in[14], smthopt *s, int cvec) {
+ double h, JCh[3], tmp[3];
+ int li, ui; /* The two hue indexes the color is between */
+ double lh, uh; /* Lower/upper hue of two colors */
+ double lw, uw; /* Lower/upper blend values */
+ double cusps[6][3];
+ gammapweights light, dark;
+
+ /* Convert to polar */
+ icmLab2LCh(JCh, pos);
+
+ if (gam->getcusps(gam, cusps) != 0) { /* Failed */
+ int isJab = gam->isJab ? 1 : 0;
+
+ /* Figure out what hextant we're between using generic cusps */
+ for (li = 0; li < 6; li++) {
+ ui = li < 5 ? li + 1 : 0;
+ h = JCh[2];
+
+ lh = gam_hues[isJab][li]; /* use generic ones */
+ uh = gam_hues[isJab][ui];
+ if (uh < lh) {
+ if (h < uh)
+ h += 360.0;
+ uh += 360.0;
+ }
+ if (h >= (lh - 1e-12) && h < (uh + 1e-12))
+ break;
+ }
+
+ } else {
+
+ /* Locate the source cusps that this point lies between */
+ for (li = 0; li < 6; li++) {
+ double tt[3];
+ ui = li < 5 ? li + 1 : 0;
+ h = JCh[2];
+
+ icmLab2LCh(tt, cusps[li]);
+ lh = tt[2];
+ icmLab2LCh(tt, cusps[ui]);
+ uh = tt[2];
+
+ if (uh < lh) {
+ if (h < uh)
+ h += 360.0;
+ uh += 360.0;
+ }
+ if (h >= (lh - 1e-12) && h < (uh + 1e-12))
+ break;
+ }
+ }
+ if (li >= 6) /* Assert */
+ error("gamut, interp_xweights: unable to locate hue %f cusps\n",JCh[2]);
+
+ /* Compute hue angle blend weights */
+ uw = (h - lh)/(uh - lh);
+ if (uw < 0.0)
+ uw = 0.0;
+ else if (uw > 1.0)
+ uw = 1.0;
+ uw = uw * uw * (3.0 - 2.0 * uw); /* Apply spline to smooth interpolation */
+ lw = (1.0 - uw);
+
+ /* Blend weights at the two hues */
+ near_wblend(&light, &in[li], lw, &in[ui], uw);
+ near_wblend(&dark, &in[7 + li], lw, &in[7 + ui], uw);
+
+ /* If we're close to the center, blend to the neutral weight */
+ if (JCh[1] < NEUTRAL_C) {
+ lw = (NEUTRAL_C - JCh[1])/NEUTRAL_C;
+ uw = (1.0 - lw);
+ near_wblend(&light, &in[6], lw, &light, uw);
+ near_wblend(&dark, &in[7 + 6], lw, &dark, uw);
+ }
+
+ /* Figure out where we are between light and dark, */
+ /* and create blend between their weightings */
+ uw = (JCh[0] - DARK_L)/(LIGHT_L - DARK_L);
+ if (uw > 1.0)
+ uw = 1.0;
+ else if (uw < 0.0)
+ uw = 0.0;
+ uw = uw * uw * (3.0 - 2.0 * uw); /* Apply spline to smooth interpolation */
+ lw = (1.0 - uw);
+ near_wblend(out, &dark, lw, &light, uw);
+
+ /* Convert radial dominance weights into raw weights */
+ comp_iweight(&out->rl, out->l.o, out->l.h, out->l.l);
+
+//printf("~1 %d: src %f %f %f (cvec %d)\n",s->ix, pos[0],pos[1],pos[2],cvec);
+ /* Compute l dominance value vs. closness to white or black point */
+ {
+ double wl, gl, bl, uw, l;
+
+ /* Closness to white and black points */
+ uw = comp_lvc(s, pos);
+
+//printf("~1 uw = %f\n",uw);
+ if (uw >= 0) {
+
+ /* Scale to threshold */
+ if (uw > (1.0 - out->a.wlth))
+ uw = (uw - 1.0 + out->a.wlth)/out->a.wlth;
+ else
+ uw = 0.0;
+//printf("~1 white, thresholded uw %f\n",uw);
+
+ /* Blend in log ratio space */
+ wl = log((1.0 - out->a.wl + 1e-5)/(out->a.wl + 1e-5));
+ gl = log((1.0 - out->a.gl + 1e-5)/(out->a.gl + 1e-5));
+ l = exp(uw * wl + (1.0 - uw) * gl);
+ l = ((1.0 - l) * 1e-5 + 1.0)/(l + 1.0);
+
+ } else {
+ uw = -uw;
+
+ /* Apply power */
+ uw = pow(uw, out->a.blpow);
+//printf("~1 black with power uw %f\n",uw);
+
+ /* Blend in log ratio space */
+ gl = log((1.0 - out->a.gl + 1e-5)/(out->a.gl + 1e-5));
+ bl = log((1.0 - out->a.bl + 1e-5)/(out->a.bl + 1e-5));
+ l = exp(uw * bl + (1.0 - uw) * gl);
+ l = ((1.0 - l) * 1e-5 + 1.0)/(l + 1.0);
+ }
+//printf("~1 wl %f, gl %f, bl %f -> %f\n",out->a.wl,out->a.gl,out->a.bl,l);
+
+ /* Convert absolute dominance weights into raw weights */
+ comp_iweight(&out->ra, out->a.o, out->a.h, l);
+//printf("~1 l %f, h %f, ra l %f c %f h %f\n\n", l, out->a.h, out->ra.l, out->ra.c, out->ra.h);
+ }
+}
+
+/* Callback used by compdstgamut() to establish the expected compression */
+/* mapping direction. */
+static void cvect(
+void *cntx, /* smthopt * */
+double *p2, /* Return point displaced from p1 in desired direction */
+double *p1 /* Given point */
+) {
+ double vv, gv[3], lv[3];
+ smthopt *s = (smthopt *)cntx;
+ gammapweights out;
+
+ interp_xweights(s->sgam, &out, p1, s->xwh, s, 1);
+
+//printf("~1 at %f %f %f, lch weight %f %f %f\n", p1[0], p1[1], p1[2], out.ra.l, out.ra.c, out.ra.h);
+ /* Now we need to convert the absolute weighting out.ra into a vector */
+ /* We do this in a very simple minded fashion. The hue weighting is ignored, */
+ /* because we assume a direction towards the neutral axis. The C weight is */
+ /* assumed to be the weight towards the grey point, while the L weight */
+ /* assumed to be the weight towards the point on the neutral axis with */
+ /* the same L value. */
+
+ /* Parameter along neutral axis black to white */
+ vv = (p1[0] - s->cusps[0][7][0])/(s->cusps[0][6][0] - s->cusps[0][7][0]);
+ /* lv is point at same L on neutral axis */
+ lv[0] = p1[0];
+ lv[1] = vv * (s->cusps[0][6][1] - s->cusps[0][7][1]) + s->cusps[0][7][1];
+ lv[2] = vv * (s->cusps[0][6][2] - s->cusps[0][7][2]) + s->cusps[0][7][2];
+ icmSub3(lv, lv, p1); /* Vector */
+ icmNormalize3(lv, lv, out.ra.l);
+
+ icmSub3(gv, s->cusps[0][8], p1); /* Grey vector */
+ icmNormalize3(gv, gv, out.ra.c);
+
+ icmAdd3(p2, gv, p1);
+ icmAdd3(p2, lv, p2); /* Combined destination */
+//printf("~1 p2 %f %f %f\n", p2[0], p2[1], p2[2]);
+}
+
+/* Shrink function */
+static void doshrink(smthopt *s, double *out, double *in, double shrink) {
+ double rad, len, p2[3];
+
+ cvect((void *)s, p2, in); /* Get shrink direction */
+
+ /* Conservative radius of point */
+ rad = sqrt(in[1] * in[1] + in[2] * in[2]);
+
+ len = shrink;
+ if (rad < (2.0 * shrink))
+ len = rad * 0.5;
+
+ icmNormalize33(out, p2, in, len);
+}
+
+/* Convenience function. Given a mapping vector, return the */
+/* intersection with the given gamut that is in the mapping */
+/* direction. Return NZ if no intersection */
+static int vintersect(
+gamut *g,
+int *p1out, /* Return nz if p1 is outside the gamut */
+double isec[3], /* Return intersection point */
+double p1[3], /* First point */
+double p2[3] /* Second point */
+) {
+ gispnt lp[40];
+ int ll, i, bi;
+
+ if ((ll = g->vector_isectns(g, p1, p2, lp, 40)) == 0)
+ return 1;
+
+ /* Locate the segment or non-segment the source lies in */
+ for (bi = -1, i = 0; i < ll; i += 2) {
+
+ if ((i == 0 || lp[i-1].pv < 0.0) /* p1 is outside gamut */
+ && lp[i].pv >= -1e-2) {
+ bi = i;
+ if (p1out != NULL)
+ *p1out = 1;
+ break;
+ }
+
+ if (lp[i].pv <= 0.0 /* p1 is inside gamut */
+ && lp[i+1].pv >= -1e-2) {
+ bi = i+1;
+ if (p1out != NULL)
+ *p1out = 0;
+ break;
+ }
+ }
+ if (bi < 0)
+ return 1;
+
+ if (isec != NULL)
+ icmCpy3(isec, lp[bi].ip);
+
+ return 0;
+}
+
+/* Convenience function. Given a point and an inwards mapping vector, */
+/* if the point is within the gamut, return the first intersection in */
+/* the opposite to vector direction. If the point is outside the gamut, */
+/* return the first intersction in the vector direction. */
+/* Return NZ if no intersection */
+static int vintersect2(
+gamut *g,
+int *p1out, /* Return nz if p1 is outside the gamut */
+double isec[3], /* Return intersection point */
+double vec[3], /* Vector */
+double p1[3] /* Point */
+) {
+ gispnt lp[40];
+ double p2[3];
+ int ll, i, bi;
+
+ icmAdd3(p2, p1, vec);
+
+ if ((ll = g->vector_isectns(g, p1, p2, lp, 40)) == 0)
+ return 1;
+
+ /* Locate the segment or non-segment the source lies in */
+ for (bi = -1, i = 0; i < ll; i += 2) {
+
+ if ((i == 0 || lp[i-1].pv < 0.0) /* p1 is outside gamut, */
+ && lp[i].pv >= 0.0) { /* so look in +ve pv direction. */
+ bi = i;
+ if (p1out != NULL)
+ *p1out = 1;
+ break;
+ }
+
+ if (lp[i].pv <= 0.0 /* p1 is inside gamut, */
+ && lp[i+1].pv >= 0.0) { /* so look in -ve pv direction. */
+ bi = i;
+ if (p1out != NULL)
+ *p1out = 0;
+ break;
+ }
+ }
+ if (bi < 0)
+ return 1;
+
+ if (isec != NULL)
+ icmCpy3(isec, lp[bi].ip);
+
+ return 0;
+}
+
+
+/* ============================================ */
+
+/* Return the maximum number of points that will be generated */
+int near_smooth_np(
+ gamut *sc_gam, /* Source colorspace gamut */
+ gamut *si_gam, /* Source image gamut (== sc_gam if none) */
+ gamut *d_gam, /* Destination colorspace gamut */
+ double xvra /* Extra vertex ratio */
+) {
+ gamut *p_gam; /* Gamut used for points - either source colorspace or image */
+ int ntpts, nmpts, nspts, nipts, ndpts;
+
+ nspts = sc_gam->nverts(sc_gam);
+ nipts = si_gam->nverts(si_gam);
+ ndpts = d_gam->nverts(d_gam);
+ p_gam = sc_gam;
+
+ /* Target number of points is max of any gamut */
+ ntpts = nspts > nipts ? nspts : nipts;
+ ntpts = ntpts > ndpts ? ntpts : ndpts;
+ ntpts = (int)(ntpts * xvra + 0.5);
+
+ /* Use image gamut if it exists */
+ if (nspts < nipts || si_gam != sc_gam) {
+ nspts = nipts; /* Use image gamut instead */
+ p_gam = si_gam;
+ }
+ xvra = ntpts/(double)nspts;
+ nmpts = p_gam->nssverts(p_gam, xvra); /* Stratified Sampling source points */
+
+ return nmpts;
+}
+
+
+/* ============================================ */
+/* Return a list of points. Free list after use */
+/* Return NULL on error */
+nearsmth *near_smooth(
+int verb, /* Verbose flag */
+int *npp, /* Return the actual number of points returned */
+gamut *sc_gam, /* Source colorspace gamut - uses cusp info if availablle */
+gamut *si_gam, /* Source image gamut (== sc_gam if none), just used for surface. */
+gamut *d_gam, /* Destination colorspace gamut */
+int src_kbp, /* Use K only black point as src gamut black point */
+int dst_kbp, /* Use K only black point as dst gamut black point */
+double d_bp[3], /* Override destination target black point - may be NULL */
+gammapweights xwh[14],/* Structure holding expanded hextant weightings */
+double gamcknf, /* Gamut compression knee factor, 0.0 - 1.0 */
+double gamxknf, /* Gamut expansion knee factor, 0.0 - 1.0 */
+int usecomp, /* Flag indicating whether smoothed compressed value will be used */
+int useexp, /* Flag indicating whether smoothed expanded value will be used */
+double xvra, /* Extra number of vertexes ratio */
+int mapres, /* Grid res for 3D RSPL */
+double mapsmooth, /* Target smoothing for 3D RSPL */
+datai map_il, /* Preliminary rspl input range */
+datai map_ih,
+datao map_ol, /* Preliminary rspl output range */
+datao map_oh
+) {
+ smthopt opts; /* optimisation and cusp mapping context */
+ int ix, i, j, k;
+ gamut *p_gam; /* Gamut used for points - either source colorspace or image */
+ gamut *sci_gam; /* Intersection of src and img gamut gamut */
+ gamut *di_gam; /* Modified destination gamut suitable for mapping from sci_gam. */
+ /* If just compression, this is the smaller of sci_gam and d_gam. */
+ /* If just expansion, this is the sci_gam expanded by d_gam - sc_gam. */
+ /* If both exp & comp, then where
+ d_gam is outside sci_gam this is sci_gam expanded by d_gam - sc_gam
+ else it is the smaller of sci_gam and d_gam */
+ gamut *nedi_gam;/* Same as above, but not expanded. */
+ int nmpts; /* Number of mapping gamut points */
+ nearsmth *smp; /* Absolute delta E weighting */
+ int pass;
+ int it;
+ rspl *evectmap = NULL; /* evector map */
+ double codf; /* Itteration overshoot/damping factor */
+ double mxmv; /* Maximum a point gets moved */
+ int nmxmv; /* Number of maxmoves less than stopping threshold */
+
+ /* Check gamuts are compatible */
+ if (sc_gam->compatible(sc_gam, d_gam) == 0
+ || (si_gam != NULL && sc_gam->compatible(sc_gam, si_gam) == 0)) {
+ fprintf(stderr,"gamut map: Gamuts aren't compatible\n");
+ *npp = 0;
+ return NULL;
+ }
+
+ {
+ int ntpts, nspts, nipts, ndpts;
+
+ nspts = sc_gam->nverts(sc_gam);
+ nipts = si_gam->nverts(si_gam);
+ ndpts = d_gam->nverts(d_gam);
+ p_gam = sc_gam;
+
+ /* Target number of points is max of any gamut */
+ ntpts = nspts > nipts ? nspts : nipts;
+ ntpts = ntpts > ndpts ? ntpts : ndpts;
+ ntpts = (int)(ntpts * xvra + 0.5);
+
+ /* Use image gamut if it exists */
+ if (nspts < nipts || si_gam != sc_gam) {
+ nspts = nipts; /* Use image gamut instead */
+ p_gam = si_gam;
+ }
+ xvra = ntpts/(double)nspts;
+ nmpts = p_gam->nssverts(p_gam, xvra); /* Stratified Sampling source points */
+
+ if (verb) printf("Vertex count: mult. = %f, src %d, img %d dst %d, target %d\n",
+ xvra,nspts,nipts,ndpts,nmpts);
+ }
+
+ /* Setup opts structure */
+ opts.useexp = useexp; /* Expansion used ? */
+ opts.debug = 0; /* No debug powell() failure */
+ opts.xwh = xwh; /* Weightings */
+ opts.sgam = sc_gam; /* Source colorspace gamut */
+
+ /* Setup source & dest neutral axis transform if white/black available. */
+ /* If cusps are available, also figure out the transformations */
+ /* needed to map source cusps to destination cusps */
+ init_ce(&opts, sc_gam, d_gam, src_kbp, dst_kbp, d_bp);
+
+ /* Allocate our guide points */
+ if ((smp = (nearsmth *)calloc(nmpts, sizeof(nearsmth))) == NULL) {
+ fprintf(stderr,"gamut map: Malloc of near smooth points failed\n");
+ *npp = 0;
+ return NULL;
+ }
+
+ /* Create a source gamut surface that is the intersection of the src colorspace */
+ /* and image gamut, in case (for some strange reason) the image gamut. */
+ /* exceeds the source colorspace size. */
+ sci_gam = sc_gam; /* Alias to source space gamut */
+ if (si_gam != sc_gam) {
+ if ((sci_gam = new_gamut(0.0, 0, 0)) == NULL) {
+ fprintf(stderr,"gamut map: new_gamut failed\n");
+ *npp = 0;
+ return NULL;
+ }
+ sci_gam->intersect(sci_gam, sc_gam, si_gam);
+#ifdef SAVE_VRMLS
+ printf("###### gamut/nearsmth.c: writing diagnostic sci_gam.wrl and di_gam.wrl\n");
+ sci_gam->write_vrml(sci_gam, "sci_gam.wrl", 1, 0);
+#endif
+ }
+
+ di_gam = sci_gam; /* Default no compress or expand */
+ if (usecomp || useexp) {
+ if ((di_gam = new_gamut(0.0, 0, 0)) == NULL) {
+ fprintf(stderr,"gamut map: new_gamut failed\n");
+ if (si_gam != sc_gam)
+ sci_gam->del(sci_gam);
+ *npp = 0;
+ return NULL;
+ }
+ if (useexp) {
+ /* Non-expand di_gam for testing double back img points against */
+ if ((nedi_gam = new_gamut(0.0, 0, 0)) == NULL) {
+ fprintf(stderr,"gamut map: new_gamut failed\n");
+ di_gam->del(di_gam);
+ if (si_gam != sc_gam)
+ sci_gam->del(sci_gam);
+ *npp = 0;
+ return NULL;
+ }
+ } else {
+ nedi_gam = di_gam;
+ }
+
+ /* Initialise this gamut with a destination mapping gamut. */
+ /* This will be the smaller of the image and destination gamut if compressing, */
+ /* and will be expanded by the (dst - src) if expanding. */
+ di_gam->compdstgamut(di_gam, sci_gam, sc_gam, d_gam, usecomp, useexp, nedi_gam,
+ cvect, &opts);
+ }
+
+#ifdef SAVE_VRMLS
+ di_gam->write_vrml(di_gam, "di_gam.wrl", 1, 0);
+#endif
+
+ /* Create a list of the mapping guide points, setup for a null mapping */
+ VA(("Creating the mapping guide point list\n"));
+ for (ix = i = 0; i < nmpts; i++) {
+ double imv[3], imr; /* Image gamut source point and radius */
+ double inorm[3]; /* Normal of image gamut surface at src point */
+
+ /* Get the source color/image space vertex value we are going */
+ /* to use as a sample point. */
+ if ((ix = p_gam->getssvert(p_gam, &imr, imv, inorm, ix)) < 0) {
+ break;
+ }
+//printf("~1 got point %d out of %d\n",i+1,nmpts);
+
+ if (p_gam != sc_gam) { /* If src colorspace point, map to img gamut surface */
+ imr = sci_gam->radial(sci_gam, imv, imv);
+ }
+
+ /* If point is within non-expanded modified destination gamut, */
+ /* then it is a "double back" image point, and should be ignored. */
+ if (nedi_gam->radial(nedi_gam, NULL, imv) > (imr + 1e-4)) {
+ VB(("Rejecting point %d because it's inside destination\n",i));
+ i--;
+ continue;
+ }
+
+ /* Lookup radialy equivalent point on modified destination gamut, */
+ /* in case we need it for compression or expansion */
+ smp[i].drr = di_gam->radial(di_gam, smp[i].drv, imv);
+
+ /* Default setup a null mapping of source image space point to source image point */
+ smp[i].vflag = smp[i].gflag = 0;
+ smp[i].dr = smp[i].sr = smp[i]._sr = imr;
+ smp[i].dv[0] = smp[i].sv[0] = smp[i]._sv[0] = imv[0];
+ smp[i].dv[1] = smp[i].sv[1] = smp[i]._sv[1] = imv[1];
+ smp[i].dv[2] = smp[i].sv[2] = smp[i]._sv[2] = imv[2];
+ smp[i].sgam = sci_gam;
+ smp[i].dgam = sci_gam;
+
+ VB(("In Src %d = %f %f %f\n",i,smp[i].sv[0],smp[i].sv[1],smp[i].sv[2]));
+
+ /* If we're going to comp. or exp., check that the guide vertex is not */
+ /* on the wrong side of the image gamut, due to the it being */
+ /* a small subset of the source colorspace, displaced to one side. */
+ /* Because of the gamut convexity limitations, this amounts */
+ /* to the source surface at the vertex being in the direction */
+ /* of the center. */
+ if (usecomp != 0 || useexp != 0) {
+ double mv[3], ml; /* Radial inward mapping vector */
+ double dir;
+
+ icmSub3(mv, sci_gam->cent, smp[i].sv); /* Vector to center */
+ ml = icmNorm3(mv); /* It's length */
+
+ if (ml > 0.001) {
+ dir = icmDot3(mv, inorm); /* Compare to normal of src triangle */
+//printf("~1 ix %d, dir = %f, dir/len = %f\n",i,dir, dir/ml);
+ dir /= ml;
+ if (dir < 0.02) { /* If very shallow */
+//printf("~1 rejecting point %d because it's oblique\n",i);
+ VB(("Rejecting point %d because it's oblique\n",i));
+ i--;
+ continue;
+ }
+ }
+ }
+
+ /* Set some default extra guide point values */
+ smp[i].anv[0] = smp[i].aodv[0] = smp[i].dv[0];
+ smp[i].anv[1] = smp[i].aodv[1] = smp[i].dv[1];
+ smp[i].anv[2] = smp[i].aodv[2] = smp[i].dv[2];
+
+ VB(("Src %d = %f %f %f\n",i,smp[i].sv[0],smp[i].sv[1],smp[i].sv[2]));
+ VB(("Dst %d = %f %f %f\n",i,smp[i].dv[0],smp[i].dv[1],smp[i].dv[2]));
+ }
+ nmpts = i; /* Number of points after rejecting any */
+ *npp = nmpts;
+
+ /* Don't need this anymore */
+ if (nedi_gam != di_gam)
+ nedi_gam->del(nedi_gam);
+ nedi_gam = NULL;
+
+ /* If nothing to be compressed or expanded, then return */
+ if (usecomp == 0 && useexp == 0) {
+ VB(("Neither compression nor expansion defined\n"));
+ if (si_gam != sc_gam)
+ sci_gam->del(sci_gam);
+ if (di_gam != sci_gam && di_gam != sci_gam)
+ di_gam->del(di_gam);
+ return smp;
+ }
+
+ /* Set the parameter weights for each point */
+ for (i = 0; i < nmpts; i++) {
+ opts.ix = i; /* Point in question */
+ opts.p = &smp[i];
+
+ /* Determine the parameter weighting for this point */
+ interp_xweights(opts.sgam, &smp[i].wt, smp[i]._sv, opts.xwh, &opts, 0);
+ }
+
+ VA(("Setting up cusp rotated compression or expansion mappings\n"));
+ VB(("rimv = Cusp rotated cspace/image gamut source point\n"));
+ VB(("imv = cspace/image gamut source point\n"));
+ VB(("drv = Destination space radial point and radius \n"));
+
+ /* Setup the cusp rotated compression or expansion mappings */
+ for (i = 0; i < nmpts; i++) {
+ double imv[3], imr; /* cspace/image gamut source point and radius */
+ double rimv[3], rimr; /* Cusp rotated cspace/image gamut source point and radius */
+
+ opts.ix = i; /* Point in question */
+ opts.p = &smp[i];
+
+ /* Grab the source image point */
+ imr = smp[i]._sr;
+ imv[0] = smp[i]._sv[0];
+ imv[1] = smp[i]._sv[1];
+ imv[2] = smp[i]._sv[2];
+
+ /* Compute the cusp rotated version of the cspace/image points */
+ /* Note that we're not elevating yet! */
+ comp_ce(&opts, rimv, imv, &smp[i].wt);
+ VB(("%f de, ix %d: cusp mapped %f %f %f -> %f %f %f\n", icmNorm33(rimv,imv), i, imv[0], imv[1], imv[2], rimv[0], rimv[1], rimv[2]));
+ rimr = icmNorm33(rimv, sci_gam->cent);
+
+ /* Default setup a no compress or expand mapping of */
+ /* source space/image point to modified destination gamut. */
+ smp[i].sr = rimr;
+ smp[i].sv[0] = rimv[0]; /* Temporary rotated src point */
+ smp[i].sv[1] = rimv[1];
+ smp[i].sv[2] = rimv[2];
+ smp[i].sgam = sci_gam;
+ smp[i].dgam = di_gam;
+
+ VB(("\n"));
+ VB(("point %d:, rimv = %f %f %f, rimr = %f\n",i,rimv[0],rimv[1],rimv[2],rimr));
+ VB(("point %d:, imv = %f %f %f, imr = %f\n",i,imv[0],imv[1],imv[2],imr));
+ VB(("point %d:, drv = %f %f %f, drr = %f\n",i,smp[i].drv[0],smp[i].drv[1],smp[i].drv[2],smp[i].drr));
+
+ /* Set a starting point for the optimisation */
+ smp[i].dgam->nearest(smp[i].dgam, smp[i].dv, smp[i].sv);
+ smp[i].dr = icmNorm33(smp[i].dv, smp[i].dgam->cent);
+
+ /* Re-lookup radialy equivalent point on destination gamut, */
+ /* to match rotated/elevated source */
+ smp[i].drr = smp[i].dgam->radial(smp[i].dgam, smp[i].drv, smp[i].sv);
+
+ /* A default average neighbour value */
+ smp[i].anv[0] = smp[i].drv[0];
+ smp[i].anv[1] = smp[i].drv[1];
+ smp[i].anv[2] = smp[i].drv[2];
+ }
+
+ /* Setup the white & black point blend factor, that makes sure the white and black */
+ /* points are not displaced. */
+ for (i = 0; i < nmpts; i++) {
+ smp[i].naxbf = comp_naxbf(&opts, smp[i]._sv);
+//printf("~1 point %d, comp_lvc = %f, naxbf = %f\n",i,comp_lvc(&opts, smp[i]._sv),smp[i].naxbf);
+ }
+
+ /* Setup the 3D -> 2D tangent conversion ready for guide vector optimization */
+ {
+ double ta[3] = { 50.0, 0.0, 0.0 };
+ double tc[3] = { 0.0, 0.0, 0.0 };
+
+ for (ix = 0; ix < nmpts; ix++) {
+ /* Coompute a rotation that brings the target point location to 50,0,0 */
+ icmVecRotMat(smp[ix].m2d, smp[ix].sv, sc_gam->cent, ta, tc);
+
+ /* And inverse */
+ icmVecRotMat(smp[ix].m3d, ta, tc, smp[ix].sv, sc_gam->cent);
+ }
+ }
+
+ /* Figure out which neighbors of the source values to use */
+ /* for the relative error calculations. */
+ /* Locate the neighbor within the radius for this point, */
+ /* and weight them with a Gausian filter weight. */
+ /* The radius is computed on the normalised surface for this point. */
+ VA(("Establishing filter neighbourhoods\n"));
+ {
+ double mm[3][4]; /* Tangent alignment rotation */
+ double m2[2][2]; /* Additional matrix to alight a with L axis */
+ double ta[3] = { 50.0, 0.0, 0.0 };
+ double tc[3] = { 0.0, 0.0, 0.0 };
+ double avgnd = 0.0; /* Total the average number of neighbours */
+ int minnd = 1e6; /* Minimum number of neighbours */
+
+ for (ix = 0; ix < nmpts; ix++) {
+ double tt[3], rrdl, rrdh, rrdc, dd;
+ double msv[3], ndx[4]; /* Midpoint source value, quadrant distance */
+ double pr; /* Average point radius */
+
+//printf("~1 computing neigbourhood for point %d at %f %f %f\n",ix, smp[ix].sv[0], smp[ix].sv[1], smp[ix].sv[2]);
+ /* Compute a rotation that brings the target point location to 50,0,0 */
+ icmNormalize33(tt, smp[ix].sv, smp[ix].sgam->cent, 1.0);
+ icmVecRotMat(mm, tt, smp[ix].sgam->cent, ta, tc);
+
+ /* Add another rotation to orient it so that [1] corresponds */
+ /* with the L direction, and [2] corresponds with the */
+ /* hue direction. */
+ m2[0][0] = m2[1][1] = 1.0;
+ m2[0][1] = m2[1][0] = 0.0;
+ tt[0] = smp[ix].sv[0] + 1.0;
+ tt[1] = smp[ix].sv[1];
+ tt[2] = smp[ix].sv[2];
+ icmNormalize33(tt, tt, smp[ix].sgam->cent, 1.0);
+ icmMul3By3x4(tt, mm, tt);
+ dd = tt[1] * tt[1] + tt[2] * tt[2];
+ if (dd > 1e-6) { /* There is a sense of L direction */
+
+ /* Create the 2x2 rotation matrix */
+ dd = sqrt(dd);
+ tt[1] /= dd;
+ tt[2] /= dd;
+
+ m2[0][0] = m2[1][1] = tt[1];
+ m2[0][1] = tt[2];
+ m2[1][0] = -tt[2];
+ }
+
+ /* Make rr inversely proportional to radius, so that */
+ /* filter scope is constant delta E */
+ rrdl = smp[ix].wt.r.rdl;
+ rrdh = smp[ix].wt.r.rdh;
+//printf("~1 rdl %f, rdh %f\n",smp[ix].wt.r.rdl,smp[ix].wt.r.rdh);
+ if (rrdl < 1e-3) rrdl = 1e-3;
+ if (rrdh < 1e-3) rrdh = 1e-3;
+
+ /* Average radius of source and destination */
+ pr = 0.5 * (smp[ix]._sr + smp[ix].dr);
+
+//printf("~1 pr = %f from _sr %f & dr %f\n",pr,smp[ix]._sr,smp[ix].dr);
+ if (pr < 5.0)
+ pr = 5.0;
+ rrdl *= 50.0/pr;
+ rrdh *= 50.0/pr;
+ rrdc = 0.5 * (rrdl + rrdh); /* Chrominance radius */
+
+ /* Scale the filter radius by the L/C value, */
+ /* so that the filters are largest at the equator, and smallest */
+ /* at the white & black points. This allows the wt.a.lx wt.a.cx to work. */
+ pr = smp[ix].naxbf + 0.1; /* "spherical" type weighting, 0.707 at 45 degrees */
+ rrdl *= pr;
+ rrdh *= pr;
+ rrdc *= pr;
+//printf("~1 at %f %f %f, rrdl = %f, rrdh = %f\n",smp[ix]._sv[0], smp[ix]._sv[1], smp[ix]._sv[2], rrdl, rrdh);
+
+ smp[ix].nnd = 0; /* Nothing in lists */
+
+ /* Search for points within the gausian radius */
+ for (i = 0; i < nmpts; i++) {
+ double x, y, z, tv[3];
+
+ /* compute rotated location */
+ icmNormalize33(tt, smp[i].sv, smp[ix].sgam->cent, 1.0);
+ icmMul3By3x4(tv, mm, tt);
+ icmMulBy2x2(&tv[1], m2, &tv[1]);
+
+ x = tv[1]/rrdl;
+ y = tv[2]/rrdh;
+ z = (tv[0] - 50.0)/rrdc;
+
+ /* Compute normalized delta normalized tangent surface */
+ dd = x * x + y * y + z * z;
+
+ /* If we're within the direction filtering radius, */
+ /* and not of the opposite hue */
+ if (dd <= 1.0 && tv[0] > 0.0) {
+ double w;
+
+ dd = sqrt(dd); /* Convert to radius <= 1.0 */
+
+ /* Add this point into the list */
+ if (smp[ix].nnd >= smp[ix]._nnd) {
+ neighb *nd;
+ int _nnd;
+ _nnd = 5 + smp[ix]._nnd * 2;
+ if ((nd = (neighb *)realloc(smp[ix].nd, _nnd * sizeof(neighb))) == NULL) {
+ VB(("realloc of neighbs at vector %d failed\n",ix));
+ if (si_gam != sc_gam)
+ sci_gam->del(sci_gam);
+ if (di_gam != sci_gam && di_gam != sci_gam)
+ di_gam->del(di_gam);
+ free_nearsmth(smp, nmpts);
+ *npp = 0;
+ return NULL;
+ }
+ smp[ix].nd = nd;
+ smp[ix]._nnd = _nnd;
+ }
+ smp[ix].nd[smp[ix].nnd].n = &smp[i];
+
+ /* Box filter */
+// w = 1.0;
+
+ /* Triangle filter */
+// w = 1.0 - dd;
+
+// /* Cubic spline filter */
+// w = 1.0 - dd;
+// w = w * w * (3.0 - 2.0 * w);
+
+ /* Gaussian filter (default) */
+ w = exp(-9.0 * dd/2.0);
+
+ /* Sphere filter */
+// w = sqrt(1.0 - dd * dd);
+
+ /* Sinc^2 filter */
+// w = 3.1415926 * dd;
+// if (w < 1e-9)
+// w = 1e-9;
+// w = sin(w)/w;
+// w = w * w;
+
+ smp[ix].nd[smp[ix].nnd].w = w; /* Will be normalized to sum to 1.0 */
+
+// /* Sphere filter for depth */
+// w = sqrt(1.0 - dd * dd);
+
+ /* Cubic spline filter for depth */
+// w = 1.0 - dd;
+// w = w * w * (3.0 - 2.0 * w);
+
+ /* Gaussian filter for depth (default) */
+ w = exp(-9.0 * dd/2.0);
+
+ smp[ix].nd[smp[ix].nnd].rw = w; /* Won't be normalized */
+
+//printf("~1 adding %d at %f %f %f, rad %f L %f, w %f dir.\n",i, smp[i].sv[0], smp[i].sv[1], smp[i].sv[2],sqrt(dd),tv[0],smp[ix].nd[smp[ix].nnd].w);
+
+ smp[ix].nnd++;
+ }
+ }
+ if (smp[ix].nnd < minnd)
+ minnd = smp[ix].nnd;
+ avgnd += (double)smp[ix].nnd;
+//printf("~1 total of %d dir neigbours\n\n",smp[ix].nnd);
+
+ }
+ avgnd /= (double)nmpts;
+
+ if (verb) printf("Average number of direction guide neigbours = %f, min = %d\n",avgnd,minnd);
+
+ /* Now normalize each points weighting */
+ for (i = 0; i < nmpts; i++) {
+ double tw;
+
+ /* Adjust direction weights to sum to 1.0 */
+ for (tw = 0.0, j = 0; j < smp[i].nnd; j++) {
+ tw += smp[i].nd[j].w;
+ }
+ for (j = 0; j < smp[i].nnd; j++) {
+ smp[i].nd[j].w /= tw;
+ }
+
+ }
+ }
+
+#ifdef SHOW_NEIGB_WEIGHTS
+ {
+ vrml *wrl = NULL;
+ double yellow[3] = { 1.0, 1.0, 0.0 };
+ double red[3] = { 1.0, 0.0, 0.0 };
+ double green[3] = { 0.0, 1.0, 0.0 };
+ double pp[3];
+
+ for (i = 0; i < nmpts; i++) {
+ double maxw;
+
+ if ((wrl = new_vrml("weights.wrl", 1)) == NULL)
+ error("New vrml failed");
+
+ maxw = 0.0;
+ for (j = 0; j < smp[i].nnd; j++) {
+ if (smp[i].nd[j].w > maxw)
+ maxw = smp[i].nd[j].w;
+ }
+ for (j = 0; j < smp[i].nnd; j++) {
+ wrl->add_col_vertex(wrl, 0, smp[i].sgam->cent, smp[i].nd[j].n == &smp[i] ? red : yellow);
+ icmNormalize33(pp, smp[i].nd[j].n->_sv, smp[i].sgam->cent, smp[i].nd[j].w * 50.0/maxw);
+ wrl->add_col_vertex(wrl, 0, pp, smp[i].nd[j].n == &smp[i] ? red : yellow);
+ }
+ wrl->make_lines(wrl, 0, 2);
+
+ wrl->del(wrl);
+ printf("Waiting for input after writing 'weights.wrl' for point %d:\n",i);
+ getchar();
+ }
+ }
+#endif /* SHOW_NEIGB_WEIGHTS */
+
+ /* Optimise the location of the source to destination mapping. */
+ if (verb) printf("Optimizing source to destination mapping...\n");
+
+ VA(("Doing first pass to locate the nearest point\n"));
+ /* First pass to locate the weighted nearest point, to use in subsequent passes */
+ {
+ double s[2] = { 20.0, 20.0 }; /* 2D search area */
+ double iv[3]; /* Initial start value */
+ double nv[2]; /* 2D New value */
+ double tp[3]; /* Resultint value */
+ double ne; /* New error */
+ int notrials = NO_TRIALS;
+
+ for (i = 0; i < nmpts; i++) { /* Move all the points */
+ double bnv[2]; /* Best 2d value */
+ double brv; /* Best return value */
+ int trial;
+ double mv;
+
+ opts.pass = 0; /* Itteration pass */
+ opts.ix = i; /* Point to optimise */
+ opts.p = &smp[i];
+
+ /* If the img point is within the destination, then we're */
+ /* expanding, so temporarily swap src and radial dest. */
+ /* (??? should we use the cvect() direction to determine swap, */
+ /* rather than radial ???) */
+ smp[i].swap = 0;
+ if (useexp && smp[i].dr > (smp[i].sr + 1e-9)) {
+ gamut *tt;
+ double dd;
+
+ smp[i].swap = 1;
+ tt = smp[i].dgam; smp[i].dgam = smp[i].sgam; smp[i].sgam = tt;
+
+ smp[i].dr = smp[i].sr;
+ smp[i].dv[0] = smp[i].sv[0];
+ smp[i].dv[1] = smp[i].sv[1];
+ smp[i].dv[2] = smp[i].sv[2];
+
+ smp[i].sr = smp[i].drr;
+ smp[i].sv[0] = smp[i].drv[0];
+ smp[i].sv[1] = smp[i].drv[1];
+ smp[i].sv[2] = smp[i].drv[2];
+ }
+
+ /* Convert our start value from 3D to 2D for speed. */
+ icmMul3By3x4(iv, smp[i].m2d, smp[i].dv);
+ nv[0] = iv[0] = iv[1];
+ nv[1] = iv[1] = iv[2];
+
+ /* Do several trials from different starting points to avoid */
+ /* any local minima, particularly with nearest mapping. */
+ brv = 1e38;
+ for (trial = 0; trial < notrials; trial++) {
+ double rv; /* Temporary */
+
+ /* Optimise the point */
+ if (powell(&rv, 2, nv, s, 0.01, 1000, optfunc1, (void *)(&opts), NULL, NULL) == 0
+ && rv < brv) {
+ brv = rv;
+//printf("~1 point %d, trial %d, new best %f\n",i,trial,sqrt(rv));
+ bnv[0] = nv[0];
+ bnv[1] = nv[1];
+ }
+//else printf("~1 powell failed with rv = %f\n",rv);
+ /* Adjust the starting point with a random offset to avoid local minima */
+ nv[0] = iv[0] + d_rand(-20.0, 20.0);
+ nv[1] = iv[1] + d_rand(-20.0, 20.0);
+ }
+ if (brv == 1e38) { /* We failed to get a result */
+ VB(("multiple powells failed to get a result\n"));
+#ifdef NEVER
+ /* Optimise the point with debug on */
+ opts.debug = 1;
+ icmMul3By3x4(iv, smp[i].m2d, smp[i].dv);
+ nv[0] = iv[0] = iv[1];
+ nv[1] = iv[1] = iv[2];
+ powell(NULL, 2, nv, s, 0.01, 1000, optfunc1, (void *)(&opts), NULL, NULL);
+#endif
+ free_nearsmth(smp, nmpts);
+ *npp = 0;
+ if (si_gam != sc_gam)
+ sci_gam->del(sci_gam);
+ if (di_gam != sci_gam && di_gam != sci_gam)
+ di_gam->del(di_gam);
+ return NULL;
+ }
+
+ /* Convert best result 2D -> 3D */
+ tp[2] = bnv[1];
+ tp[1] = bnv[0];
+ tp[0] = 50.0;
+ icmMul3By3x4(tp, smp[i].m3d, tp);
+
+ /* Remap it to the destinaton gamut surface */
+ smp[i].dgam->radial(smp[i].dgam, tp, tp);
+ icmCpy3(smp[i].aodv, tp);
+
+ /* Undo any swap */
+ if (smp[i].swap) {
+ gamut *tt;
+ double dd;
+
+ tt = smp[i].dgam; smp[i].dgam = smp[i].sgam; smp[i].sgam = tt;
+
+ /* We get the point on the real src gamut out when swap */
+ smp[i]._sv[0] = smp[i].aodv[0];
+ smp[i]._sv[1] = smp[i].aodv[1];
+ smp[i]._sv[2] = smp[i].aodv[2];
+
+ /* So we need to compute cusp mapped sv */
+ comp_ce(&opts, smp[i].sv, smp[i]._sv, &smp[i].wt);
+ smp[i].sr = icmNorm33(smp[i].sv, smp[i].sgam->cent);
+
+ VB(("Exp Src %d = %f %f %f\n",i,smp[i]._sv[0],smp[i]._sv[1],smp[i]._sv[2]));
+ smp[i].aodv[0] = smp[i].drv[0];
+ smp[i].aodv[1] = smp[i].drv[1];
+ smp[i].aodv[2] = smp[i].drv[2];
+ }
+ }
+ if (verb) {
+ printf("."); fflush(stdout);
+ }
+ }
+
+ VA(("Locating weighted mapping vectors without smoothing:\n"));
+ /* Second pass to locate the optimized overall weighted point nrdv[], */
+ /* not counting relative error. */
+ {
+ double s[2] = { 20.0, 20.0 }; /* 2D search area */
+ double iv[3]; /* Initial start value */
+ double nv[2]; /* 2D New value */
+ double tp[3]; /* Resultint value */
+ double ne; /* New error */
+ int notrials = NO_TRIALS;
+
+ for (i = 0; i < nmpts; i++) { /* Move all the points */
+ double bnv[2]; /* Best 2d value */
+ double brv; /* Best return value */
+ int trial;
+ double mv;
+
+ opts.pass = 0; /* Itteration pass */
+ opts.ix = i; /* Point to optimise */
+ opts.p = &smp[i];
+
+//printf("~1 point %d, sv %f %f %f\n",i,smp[i].sv[0],smp[i].sv[1],smp[i].sv[2]);
+
+ /* Convert our start value from 3D to 2D for speed. */
+ icmMul3By3x4(iv, smp[i].m2d, smp[i].aodv);
+
+ nv[0] = iv[0] = iv[1];
+ nv[1] = iv[1] = iv[2];
+//printf("~1 point %d, iv %f %f %f, 2D %f %f\n",i, smp[i].aodv[0], smp[i].aodv[1], smp[i].aodv[2], iv[0], iv[1]);
+
+ /* Do several trials from different starting points to avoid */
+ /* any local minima, particularly with nearest mapping. */
+ brv = 1e38;
+ for (trial = 0; trial < notrials; trial++) {
+ double rv; /* Temporary */
+
+ /* Optimise the point */
+ if (powell(&rv, 2, nv, s, 0.01, 1000, optfunc2, (void *)(&opts), NULL, NULL) == 0
+ && rv < brv) {
+ brv = rv;
+//printf("~1 point %d, trial %d, new best %f at xy %f %f\n",i,trial,sqrt(rv), nv[0],nv[1]);
+ bnv[0] = nv[0];
+ bnv[1] = nv[1];
+ }
+//else printf("~1 powell failed with rv = %f\n",rv);
+ /* Adjust the starting point with a random offset to avoid local minima */
+ nv[0] = iv[0] + d_rand(-20.0, 20.0);
+ nv[1] = iv[1] + d_rand(-20.0, 20.0);
+ }
+ if (brv == 1e38) { /* We failed to get a result */
+ VB(("multiple powells failed to get a result\n"));
+#ifdef NEVER
+ /* Optimise the point with debug on */
+ opts.debug = 1;
+ icmMul3By3x4(iv, smp[i].m2d, smp[i].dv);
+ nv[0] = iv[0] = iv[1];
+ nv[1] = iv[1] = iv[2];
+ powell(NULL, 2, nv, s, 0.01, 1000, optfunc2, (void *)(&opts), NULL, NULL);
+#endif
+ free_nearsmth(smp, nmpts);
+ *npp = 0;
+ if (si_gam != sc_gam)
+ sci_gam->del(sci_gam);
+ if (di_gam != sci_gam && di_gam != sci_gam)
+ di_gam->del(di_gam);
+ return NULL;
+ }
+
+ /* Convert best result 3D -> 2D */
+ tp[2] = bnv[1];
+ tp[1] = bnv[0];
+ tp[0] = 50.0;
+ icmMul3By3x4(tp, smp[i].m3d, tp);
+
+ /* Remap it to the destinaton gamut surface */
+ smp[i].dgam->radial(smp[i].dgam, tp, tp);
+
+ icmCpy3(smp[i].nrdv, tp); /* Non smoothed result */
+ icmCpy3(smp[i].anv, tp); /* Starting point for smoothing */
+ icmCpy3(smp[i].dv, tp); /* Default current solution */
+ smp[i].dr = icmNorm33(smp[i].dv, smp[i].dgam->cent);
+//printf("~1 %d: dv %f %f %f\n", i, smp[i].dv[0], smp[i].dv[1], smp[i].dv[2]);
+
+ }
+ if (verb) {
+ printf("."); fflush(stdout);
+ }
+ }
+
+#ifdef DIAG_POINTS
+ /* Show just the closest vectors etc. */
+ for (i = 0; i < nmpts; i++) { /* Move all the points */
+// icmCpy3(smp[i].dv, smp[i].drv); /* Radial */
+ icmCpy3(smp[i].dv, smp[i].aodv); /* Nearest */
+// icmCpy3(smp[i].dv, smp[i].nrdv); /* No smoothed weighted */
+// icmCpy3(smp[i].dv, smp[i].dv); /* pre-filter smooothed */
+ smp[i].dr = icmNorm33(smp[i].dv, smp[i].dgam->cent);
+ }
+#else
+ /* The smoothed direction and raw depth is a single pass, */
+ /* but we use multiple passes to determine the extra depth that */
+ /* needs to be added so that the smoothed result lies within */
+ /* the destination gamut. */
+
+#if VECADJPASSES > 0 || RSPLPASSES > 0
+ /* We will need inward pointing correction vectors */
+ {
+ gamut *shgam; /* Shrunken di_gam */
+ double cusps[6][3];
+ double wp[3], bp[3], kp[3];
+ double p[3], p2[3], rad;
+ int i;
+
+ double s[2] = { 20.0, 20.0 }; /* 2D search area */
+ double iv[3]; /* Initial start value */
+ double nv[2]; /* 2D New value */
+ double tp[3]; /* Resultint value */
+ double ne; /* New error */
+ int notrials = NO_TRIALS;
+
+ cow *gpnts = NULL; /* Mapping points to create 3D -> 3D mapping */
+ datai il, ih;
+ datao ol, oh;
+ int gres[MXDI];
+ double avgdev[MXDO];
+
+ /* Create a gamut that is a shrunk version of the destination */
+
+ if ((shgam = new_gamut(di_gam->getsres(di_gam), di_gam->getisjab(di_gam),
+ di_gam->getisrast(di_gam))) == NULL) {
+ free_nearsmth(smp, nmpts);
+ *npp = 0;
+ if (si_gam != sc_gam)
+ sci_gam->del(sci_gam);
+ if (di_gam != sci_gam && di_gam != sci_gam)
+ di_gam->del(di_gam);
+ return NULL;
+ }
+
+ shgam->setnofilt(shgam);
+
+ /* Translate all the surface nodes */
+ for (i = 0;;) {
+ double len;
+
+ if ((i = di_gam->getrawvert(di_gam, p, i)) < 0)
+ break;
+
+ doshrink(&opts, p, p, SHRINK);
+ shgam->expand(shgam, p);
+ }
+ /* Translate cusps */
+ if (di_gam->getcusps(di_gam, cusps) == 0) {
+ shgam->setcusps(shgam, 0, NULL);
+ for (i = 0; i < 6; i++) {
+ doshrink(&opts, p, cusps[i], SHRINK);
+ shgam->setcusps(shgam, 1, p);
+ }
+ shgam->setcusps(shgam, 2, NULL);
+ }
+ /* Translate white and black points */
+ if (di_gam->getwb(di_gam, wp, bp, kp, NULL, NULL, NULL) == 0) {
+ doshrink(&opts, wp, wp, SHRINK);
+ doshrink(&opts, bp, bp, SHRINK);
+ doshrink(&opts, kp, kp, SHRINK);
+ shgam->setwb(shgam, wp, bp, kp);
+ }
+
+ if ((gpnts = (cow *)malloc(nmpts * sizeof(cow))) == NULL) {
+ fprintf(stderr,"gamut map: Malloc of near smooth points failed\n");
+ free_nearsmth(smp, nmpts);
+ *npp = 0;
+ shgam->del(shgam);
+ if (si_gam != sc_gam)
+ sci_gam->del(sci_gam);
+ if (di_gam != sci_gam && di_gam != sci_gam)
+ di_gam->del(di_gam);
+ return NULL;
+ }
+
+ /* Now locate the closest points on the shrunken gamut */
+ /* and set them up for creating a rspl */
+ opts.shgam = shgam;
+ for (i = 0; i < nmpts; i++) { /* Move all the points */
+ gtri *ctri = NULL;
+ double tmp[3];
+ double bnv[2]; /* Best 2d value */
+ double brv; /* Best return value */
+ int trial;
+ double mv;
+
+ opts.pass = 0; /* Itteration pass */
+ opts.ix = i; /* Point to optimise */
+ opts.p = &smp[i];
+
+ /* Convert our start value from 3D to 2D for speed. */
+ icmMul3By3x4(iv, smp[i].m2d, smp[i].dv);
+ nv[0] = iv[0] = iv[1];
+ nv[1] = iv[1] = iv[2];
+
+ /* Do several trials from different starting points to avoid */
+ /* any local minima, particularly with nearest mapping. */
+ brv = 1e38;
+ for (trial = 0; trial < notrials; trial++) {
+ double rv; /* Temporary */
+
+ /* Optimise the point */
+ if (powell(&rv, 2, nv, s, 0.01, 1000, optfunc1a, (void *)(&opts), NULL, NULL) == 0
+ && rv < brv) {
+ brv = rv;
+ bnv[0] = nv[0];
+ bnv[1] = nv[1];
+ }
+ /* Adjust the starting point with a random offset to avoid local minima */
+ nv[0] = iv[0] + d_rand(-20.0, 20.0);
+ nv[1] = iv[1] + d_rand(-20.0, 20.0);
+ }
+ if (brv == 1e38) { /* We failed to get a result */
+ VB(("multiple powells failed to get a result\n"));
+ shgam->del(shgam); /* Done with this */
+ free_nearsmth(smp, nmpts);
+ *npp = 0;
+ if (si_gam != sc_gam)
+ sci_gam->del(sci_gam);
+ if (di_gam != sci_gam && di_gam != sci_gam)
+ di_gam->del(di_gam);
+ return NULL;
+ }
+
+ /* Convert best result 2D -> 3D */
+ tp[2] = bnv[1];
+ tp[1] = bnv[0];
+ tp[0] = 50.0;
+ icmMul3By3x4(tp, smp[i].m3d, tp);
+
+ /* Remap it to the destinaton gamut surface */
+ shgam->radial(shgam, tp, tp);
+
+ /* Compute mapping vector from dst to shdst */
+ icmSub3(smp[i].temp, tp, smp[i].dv);
+
+
+ /* In case shrunk vector is very short, add a small part */
+ /* of the nearest normal. */
+ smp[i].dgam->nearest_tri(smp[i].dgam, NULL, smp[i].dv, &ctri);
+ icmScale3(tmp, ctri->pe, 0.1); /* Scale to small inwards */
+ icmAdd3(smp[i].temp, smp[i].temp, tmp);
+
+ /* evector */
+ icmNormalize3(smp[i].temp, smp[i].temp, 1.0);
+
+ /* Place it in rspl setup array */
+ icmCpy3(gpnts[i].p, smp[i].dv);
+ icmCpy3(gpnts[i].v, smp[i].temp);
+ gpnts[i].w = 1.0;
+ }
+
+ for (j = 0; j < 3; j++) { /* Set resolution for all axes */
+// gres[j] = (mapres+1)/2; /* Half resolution */
+ gres[j] = mapres; /* Full resolution */
+ avgdev[j] = GAMMAP_RSPLAVGDEV;
+ }
+
+ evectmap = new_rspl(RSPL_NOFLAGS, 3, 3); /* Allocate 3D -> 3D */
+ evectmap->fit_rspl_w(evectmap, GAMMAP_RSPLFLAGS, gpnts, nmpts,
+ map_il, map_ih, gres, map_ol, map_oh, 1.0, avgdev, NULL);
+
+// ~~999
+#ifdef PLOT_EVECTS /* Create VRML of error correction vectors */
+ {
+ vrml *wrl = NULL;
+ int doaxes = 0;
+ double cc[3] = { 0.7, 0.7, 0.7 };
+ double red[3] = { 1.0, 0.0, 0.0 };
+ double green[3] = { 0.0, 1.0, 0.0 };
+ double tmp[3];
+ co cp;
+#ifdef PLOT_AXES
+ doaxes = 1;
+#endif
+
+ printf("###### gamut/nearsmth.c: writing diagnostic evects.wrl\n");
+ wrl = new_vrml("evects.wrl", doaxes);
+ wrl->make_gamut_surface_2(wrl, di_gam, 0.6, 0, cc);
+ cc[0] = -1.0;
+ wrl->make_gamut_surface(wrl, shgam, 0.2, cc);
+
+ /* Start of guide vector plot */
+ wrl->start_line_set(wrl, 0);
+
+ for (i = 0; i < nmpts; i++) {
+ wrl->add_col_vertex(wrl, 0, smp[i].dv, red);
+#ifdef NEVER /* Plot created vectors */
+ icmScale3(tmp, smp[i].temp, 4.0);
+ icmSub3(tmp, smp[i].dv, tmp);
+#else
+ /* Plot interpolated vectors */
+ icmCpy3(cp.p, smp[i].dv);
+ evectmap->interp(evectmap, &cp);
+ icmScale3(tmp, cp.v, 4.0);
+ icmSub3(tmp, smp[i].dv, tmp);
+#endif
+ wrl->add_col_vertex(wrl, 0, tmp, green);
+ }
+ wrl->make_lines(wrl, 0, 2); /* Guide vectors */
+ wrl->del(wrl); /* Write and delete */
+ }
+#endif /* PLOT_EVECTS */
+ shgam->del(shgam); /* Done with this */
+ free(gpnts);
+ }
+#endif /* VECADJPASSES > 0 || RSPLPASSES > 0 */
+
+#ifdef VECSMOOTHING
+ VA(("Smoothing guide vectors:\n"));
+
+ /* Compute the neighbourhood smoothed anv[] from dv[] */
+ for (i = 0; i < nmpts; i++) {
+ double anv[3]; /* new anv[] */
+ double tmp[3];
+
+ /* Compute filtered value */
+ anv[0] = anv[1] = anv[2] = 0.0;
+ for (j = 0; j < smp[i].nnd; j++) {
+ nearsmth *np = smp[i].nd[j].n; /* Pointer to neighbor */
+ double nw = smp[i].nd[j].w; /* Weight */
+ double tmp[3];
+
+ icmSub3(tmp, smp[i].sv, np->sv); /* Vector from neighbour src to src */
+ icmAdd3(tmp, tmp, np->dv); /* Neigbour dst + vector */
+
+ icmScale3(tmp, tmp, nw); /* weight for filter */
+ icmAdd3(anv, anv, tmp); /* sum filtered value */
+ }
+
+ /* Blend to un-smoothed value on neutral axis */
+ icmBlend3(anv, smp[i].dv, anv, smp[i].naxbf);
+
+ icmCpy3(smp[i].dv, anv);
+ icmCpy3(smp[i].anv, anv);
+ smp[i].rext = 0.0; /* No correction */
+ }
+
+#if VECADJPASSES > 0
+ /* Fine tune vectors to compensate for side effects of vector smoothing */
+
+ VA(("Fine tuning out of gamut guide vectors:\n"));
+
+ /* Loopkup correction vectors */
+ VA(("Computing fine tuning direction:\n"));
+ for (i = 0; i < nmpts; i++) {
+ co cp;
+ double nd, id, tmp[3];
+
+ icmCpy3(cp.p, smp[i].dv);
+ evectmap->interp(evectmap, &cp);
+ icmNormalize3(smp[i].evect, cp.v, 1.0);
+
+ /* ~~99 ?? should we deal with white & black direction here ?? */
+
+ /* Use closest as a default */
+ smp[i].dgam->nearest(smp[i].dgam, smp[i].tdst, smp[i].dv);
+ nd = icmNorm33(smp[i].tdst, smp[i].dv); /* Dist to nearest */
+
+ /* Compute intersection with dest gamut as tdst */
+ if (!vintersect2(smp[i].dgam, NULL, tmp, smp[i].evect, smp[i].dv)) {
+ /* Got an intersection */
+ id = icmNorm33(tmp, smp[i].dv); /* Dist to intersection */
+ if (id <= (nd + 5.0)) /* And it seems sane */
+ icmCpy3(smp[i].tdst, tmp);
+ }
+
+ smp[i].rext = 0.0;
+ }
+
+ VA(("Fine tuning guide vectors:\n"));
+ for (it = 0; it < VECADJPASSES; it++) {
+ double avgog = 0.0, maxog = 0.0, nog = 0.0;
+ double avgig = 0.0, maxig = 0.0, nig = 0.0;
+
+ /* Filter the level of out/in gamut, and apply correction vector */
+ for (i = 0; i < nmpts; i++) {
+ double cvec[3], clen;
+ double minext = 1e80;
+ double maxext = -1e80; /* Max weighted depth extension */
+ double dext, gain;
+
+ minext = -20.0;
+
+ /* Compute filtered value */
+ for (j = 0; j < smp[i].nnd; j++) {
+ nearsmth *np = smp[i].nd[j].n; /* Pointer to neighbor */
+ double nw = smp[i].nd[j].rw; /* Weight */
+ double tmpl;
+
+ icmSub3(cvec, np->tdst, np->anv); /* Vector needed to target for neighbour */
+ clen = icmDot3(smp[i].evect, cvec); /* Error in this direction */
+
+ tmpl = nw * (clen - minext); /* Track maximum weighted extra depth */
+ if (tmpl < 0.0)
+ tmpl = 0.0;
+ if (tmpl > maxext)
+ maxext = tmpl;
+ }
+ maxext += minext;
+
+ if (it == 0)
+ gain = 1.2;
+ else
+ gain = 0.8;
+
+ /* Accumulate correction with damping */
+ smp[i].rext += gain * maxext;
+
+ /* Error for just this point */
+ icmSub3(cvec, smp[i].tdst, smp[i].anv);
+ clen = icmDot3(smp[i].evect, cvec);
+
+ /* Blend to individual correction on neutral axis */
+ dext = smp[i].naxbf * smp[i].rext + (1.0 - smp[i].naxbf) * clen;
+
+ /* Apply integrated correction */
+ icmScale3(cvec, smp[i].evect, dext);
+ icmAdd3(smp[i].anv, smp[i].dv, cvec);
+
+ if (clen > 0.0) { /* Compression */
+ if (clen > maxog)
+ maxog = clen;
+ avgog += clen;
+ nog++;
+
+ } else { /* Expansion */
+ if (-clen > maxig)
+ maxig = -clen;
+ avgig += -clen;
+ nig++;
+ }
+ }
+ if (verb)
+ printf("No og %4.0f max %f avg %f, No ig %4.0f max %f avg %f\n",
+ nog,maxog,nog > 1 ? avgog/nog : 0.0, nig,maxig,nig > 1 ? avgig/nig : 0.0);
+ }
+
+ /* Copy final results */
+ for (i = 0; i < nmpts; i++) {
+ icmCpy3(smp[i].dv, smp[i].anv);
+ smp[i].dr = icmNorm33(smp[i].dv, smp[i].dgam->cent);
+ }
+
+ if (verb) {
+ double avgog = 0.0, maxog = 0.0, nog = 0.0;
+ double avgig = 0.0, maxig = 0.0, nig = 0.0;
+
+ /* Check the result */
+ for (i = 0; i < nmpts; i++) {
+ double cvec[3], clen;
+
+ /* Error for just this point, for stats */
+ icmSub3(cvec, smp[i].tdst, smp[i].anv);
+ clen = icmDot3(smp[i].evect, cvec);
+
+ if (clen > 0.0) { /* Compression */
+ if (clen > maxog)
+ maxog = clen;
+ avgog += clen;
+ nog++;
+
+ } else { /* Expansion */
+ if (-clen > maxig)
+ maxig = -clen;
+ avgig += -clen;
+ nig++;
+ }
+ }
+ printf("No og %4.0f max %f avg %f, No ig %4.0f max %f avg %f\n",
+ nog,maxog,nog > 1 ? avgog/nog : 0.0, nig,maxig,nig > 1 ? avgig/nig : 0.0);
+ }
+#endif /* VECADJUST */
+#endif /* VECADJPASSES > 0 */
+
+#if RSPLPASSES > 0
+ /* We need to adjust the vectors with extra depth to compensate for */
+ /* for the effect of rspl smoothing. */
+ {
+ cow *gpnts = NULL; /* Mapping points to create 3D -> 3D mapping */
+ rspl *map = NULL; /* Test map */
+ datai il, ih;
+ datao ol, oh;
+ int gres[MXDI];
+ double avgdev[MXDO];
+ double icgain, ixgain; /* Initial compression, expansion gain */
+ double fcgain, fxgain; /* Final compression, expansion gain */
+
+ VA(("Fine tuning vectors to allow for rspl smoothing:\n"));
+
+ for (j = 0; j < 3; j++) { /* Copy ranges */
+ il[j] = map_il[j];
+ ih[j] = map_ih[j];
+ ol[j] = map_ol[j];
+ oh[j] = map_oh[j];
+ }
+
+ /* Adjust the input ranges for guide vectors */
+ for (i = 0; i < nmpts; i++) {
+ for (j = 0; j < 3; j++) {
+ if (smp[i]._sv[j] < il[j])
+ il[j] = smp[i]._sv[j];
+ if (smp[i]._sv[j] > ih[j])
+ ih[j] = smp[i]._sv[j];
+ }
+ }
+
+ /* Now expand the bounding box by aprox 5% margin, but scale grid res */
+ /* to match, so that the natural or given boundary still lies on the grid. */
+ /* (This duplicates code in gammap applied after near_smooth() returns) */
+ /* (We are assuming that our changes to the giude vectprs won't expand the ranges) */
+ {
+ int xmapres;
+ double scale;
+
+ xmapres = (int) ((mapres-1) * 0.05 + 0.5);
+ if (xmapres < 1)
+ xmapres = 1;
+
+ scale = (double)(mapres-1 + xmapres)/(double)(mapres-1);
+
+ for (j = 0; j < 3; j++) {
+ double low, high;
+ high = ih[j];
+ low = il[j];
+ ih[j] = (scale * (high - low)) + low;
+ il[j] = (scale * (low - high)) + high;
+ }
+
+ mapres += 2 * xmapres;
+ }
+
+ if ((gpnts = (cow *)malloc(nmpts * sizeof(cow))) == NULL) {
+ fprintf(stderr,"gamut map: Malloc of near smooth points failed\n");
+ free_nearsmth(smp, nmpts);
+ *npp = 0;
+ if (evectmap != NULL)
+ evectmap->del(evectmap);
+ if (si_gam != sc_gam)
+ sci_gam->del(sci_gam);
+ if (di_gam != sci_gam && di_gam != sci_gam)
+ di_gam->del(di_gam);
+ return NULL;
+ }
+
+ /* Loopkup correction vectors */
+ VA(("Computing fine tuning direction for vectors:\n"));
+ for (i = 0; i < nmpts; i++) {
+ co cp;
+ double nd, id, tmp[3];
+
+ icmCpy3(cp.p, smp[i].dv);
+ evectmap->interp(evectmap, &cp);
+ icmNormalize3(smp[i].evect, cp.v, 1.0);
+
+ /* ~~99 ?? should we deal with white & black direction here ?? */
+
+ /* Use closest as a default */
+ smp[i].dgam->nearest(smp[i].dgam, smp[i].tdst, smp[i].dv);
+ nd = icmNorm33(smp[i].tdst, smp[i].dv); /* Dist to nearest */
+
+ /* Compute intersection with dest gamut as tdst */
+ if (!vintersect2(smp[i].dgam, NULL, tmp, smp[i].evect, smp[i].dv)) {
+ /* Got an intersection */
+ id = icmNorm33(tmp, smp[i].dv); /* Dist to intersection */
+ if (id <= (nd + 5.0)) /* And it seems sane */
+ icmCpy3(smp[i].tdst, tmp);
+ }
+
+ smp[i].coff[0] = smp[i].coff[1] = smp[i].coff[2] = 0.0;
+ smp[i].rext = 0.0;
+ }
+
+ /* We know initially that dv == anv */
+ /* Each pass computes a rext for each point, then */
+ /* anv[] = dv[] + smooth(rext * evect[]) to try and avoid clipping */
+ VA(("Fine tune guide vectors for rspl:\n"));
+ for (it = 0; it < RSPLPASSES; it++) {
+ double tmp[3];
+ double avgog = 0.0, maxog = 0.0, nog = 0.0;
+ double avgig = 0.0, maxig = 0.0, nig = 0.0;
+ double avgrext = 0.0;
+ double ovlen;
+
+ /* Setup the rspl guide points for creating rspl */
+ for (i = 0; i < nmpts; i++) {
+ icmCpy3(gpnts[i].p, smp[i]._sv); /* The orgininal src point */
+ icmCpy3(gpnts[i].v, smp[i].anv); /* current dst from previous results */
+ gpnts[i].w = 1.0;
+ }
+
+ for (j = 0; j < 3; j++) { /* Set resolution for all axes */
+// gres[j] = (mapres+1)/2; /* Half resolution for speed */
+ gres[j] = mapres; /* Full resolution */
+ avgdev[j] = GAMMAP_RSPLAVGDEV;
+ }
+ map = new_rspl(RSPL_NOFLAGS, 3, 3); /* Allocate 3D -> 3D */
+ map->fit_rspl_w(map, GAMMAP_RSPLFLAGS, gpnts, nmpts,
+ il, ih, gres, ol, oh, mapsmooth, avgdev, NULL);
+
+ /* See what the source actually maps to via rspl, and how far from */
+ /* the target point they are. */
+ for (i = 0; i < nmpts; i++) {
+ co cp;
+ double cvec[3];
+
+ /* Lookup rspl smoothed destination value */
+ icmCpy3(cp.p, smp[i]._sv);
+ map->interp(map, &cp);
+ icmCpy3(smp[i].temp, cp.v);
+
+ /* Compute the correction needed and it's signed length */
+ icmSub3(cvec, smp[i].tdst, smp[i].temp);
+ smp[i].clen = icmDot3(smp[i].evect, cvec);
+ }
+
+ /* Compute local correction */
+ for (i = 0; i < nmpts; i++) {
+ double minext = 1e80;
+ double maxext = -1e80; /* Max weighted depth extension */
+ double clen;
+ double tpoint[3], cvect[3];
+ double tt;
+ double cgain, xgain; /* This itters compression, expansion gain */
+ double gain; /* Gain used */
+
+ /* See what the worst case is in the local area, and */
+ /* aim to lower the whole local area by enough to */
+ /* cause the max to be 0.0 (just on the gamut) */
+
+ /* Compute local depth value */
+ minext = -20.0; /* Base to measure max from */
+ for (j = 0; j < smp[i].nnd; j++) {
+ nearsmth *np = smp[i].nd[j].n; /* Pointer to neighbor */
+ double nw = smp[i].nd[j].rw; /* Weight */
+ double tmpl;
+
+ tmpl = nw * (np->clen - minext); /* Track maximum weighted extra depth */
+ if (tmpl < 0.0)
+ tmpl = 0.0;
+ if (tmpl > maxext)
+ maxext = tmpl;
+ }
+ maxext += minext;
+
+ /* maxext is the current effective error at this point with rext aim point */
+ if (it == 0) { /* Set target on first itteration */
+ if (smp[i].rext <= 0.0) /* Expand direction */
+ smp[i].rext += maxext;
+ else
+ smp[i].rext += RSPLSCALE * maxext;
+ }
+ avgrext += smp[i].rext;
+
+ /* Compute offset target point at maxlen from tdst in evect dir. */
+ icmScale3(tpoint, smp[i].evect, smp[i].rext);
+ icmAdd3(tpoint, tpoint, smp[i].tdst);
+
+ /* Expansion/compression gain program */
+ icgain = 1.4; /* Initial itteration compression gain */
+ ixgain = smp[i].wt.f.x * icgain; /* Initial itteration expansion gain */
+ fcgain = 0.5 * icgain; /* Final itteration compression gain */
+ fxgain = 0.5 * ixgain; /* Final itteration expansion gain */
+
+ /* Set the gain */
+ tt = it/(RSPLPASSES - 1.0);
+ cgain = (1.0 - tt) * icgain + tt * fcgain;
+ xgain = (1.0 - tt) * ixgain + tt * fxgain;
+ if (it != 0) /* Expand only on first itter */
+ xgain = 0.0;
+
+//if (i == 0) printf("~1 i %d, it %d, wt.f.x = %f, cgain %f, xgain %f\n",i,it,smp[i].wt.f.x, cgain, xgain);
+ if (smp[i].rext > 0.0) /* Compress direction */
+ gain = cgain;
+ else
+ gain = xgain;
+
+ /* Keep stats of this point */
+ clen = smp[i].clen;
+ if (clen > 0.0) {
+ if (clen > maxog)
+ maxog = clen;
+ avgog += clen;
+ nog++;
+
+ } else { /* Expand */
+ if (-clen > maxig)
+ maxig = -clen;
+ avgig += -clen;
+ nig++;
+ }
+
+ /* Compute needed correction from current rspl smoothed anv */
+ /* to offset target point. */
+ icmSub3(cvect, tpoint, smp[i].temp); /* Correction still needed */
+ icmScale3(cvect, cvect, gain); /* Times gain */
+ icmAdd3(smp[i].coff, smp[i].coff, cvect); /* Accumulated */
+
+ icmCpy3(gpnts[i].p, smp[i].dv);
+ icmCpy3(gpnts[i].v, smp[i].coff);
+ gpnts[i].w = 1.0;
+ }
+
+ for (j = 0; j < 3; j++) { /* Set resolution for all axes */
+// gres[j] = (mapres+1)/2; /* Half resolution */
+ gres[j] = mapres; /* Full resolution */
+ avgdev[j] = GAMMAP_RSPLAVGDEV;
+ }
+ map = new_rspl(RSPL_NOFLAGS, 3, 3); /* Allocate 3D -> 3D */
+ map->fit_rspl_w(map, GAMMAP_RSPLFLAGS, gpnts, nmpts,
+ il, ih, gres, ol, oh, 2.0, avgdev, NULL);
+
+ /* Lookup the smoothed extension vector for each point and apply it */
+ for (i = 0; i < nmpts; i++) {
+ double tt;
+ co cp;
+
+ icmCpy3(cp.p, smp[i].dv);
+ map->interp(map, &cp);
+#ifdef RSPLUSEPOW
+ spow3(smp[i].coff, cp.v, 1.0/2.0); /* Filtered value is current value */
+#else
+ icmCpy3(smp[i].coff, cp.v); /* Filtered value is current value */
+#endif
+
+ /* Make sure anv[] is on the destination gamut at neutral axis */
+ icmScale3(cp.v, cp.v, smp[i].naxbf);
+
+ /* Apply accumulated offset */
+ icmAdd3(smp[i].anv, smp[i].dv, cp.v);
+ }
+ map->del(map);
+
+ if (verb)
+ printf("No og %4.0f max %f avg %f, No ig %4.0f max %f avg %f, avg rext %f\n",
+ nog,maxog,nog > 1 ? avgog/nog : 0.0, nig,maxig,nig > 1 ? avgig/nig : 0.0, avgrext/nmpts);
+ } /* Next pass */
+ free(gpnts);
+
+ /* Copy last anv to dv for result */
+ for (i = 0; i < nmpts; i++) {
+// ~~99
+// Normal target
+ icmCpy3(smp[i].dv, smp[i].anv);
+
+// Show evect direction
+// icmCpy3(smp[i]._sv, smp[i].dv);
+// icmScale3(smp[i].evect, smp[i].evect, 4.0);
+// icmAdd3(smp[i].dv, smp[i].evect, smp[i].dv);
+
+// Show target point destination
+// icmCpy3(smp[i].dv, smp[i].tdst);
+
+// Show offset target destination
+// icmScale3(smp[i].dv, smp[i].evect, smp[i].rext);
+// icmAdd3(smp[i].dv, smp[i].dv, smp[i].tdst);
+
+ smp[i].dr = icmNorm33(smp[i].dv, smp[i].dgam->cent);
+ }
+ }
+
+#endif /* RSPLPASSES > 0 */
+#endif /* NEVER (show debug values) */
+
+ VA(("Smoothing passes done, doing final houskeeping\n"));
+
+ if (verb)
+ printf("\n");
+
+#if defined(SAVE_VRMLS) && defined(PLOT_MAPPING_INFLUENCE)
+ create_influence_plot(smp, nmpts);
+#endif
+
+ VB(("Final guide points:\n"));
+
+ /* Restore the actual non elevated and cust rotated source point */
+ for (i = 0; i < nmpts; i++) {
+
+ VB(("Src %d = %f %f %f\n",i,smp[i].sv[0],smp[i].sv[1],smp[i].sv[2]));
+ VB(("Dst %d = %f %f %f\n",i,smp[i].dv[0],smp[i].dv[1],smp[i].dv[2]));
+
+ /* Save the cusp mapped source value */
+ icmCpy3(smp[i].csv, smp[i].sv);
+
+ /* Finally un cusp map the source point */
+// inv_comp_ce(&opts, smp[i].sv, smp[i].sv, &smp[i].wt);
+// smp[i].sr = icmNorm33(smp[i].sv, smp[i].sgam->cent);
+ icmCpy3(smp[i].sv, smp[i]._sv);
+ smp[i].sr = smp[i]._sr;
+ }
+
+ VB(("Creating sub-surface guide points:\n"));
+
+ /* Create sub-surface points. */
+ for (i = 0; i < nmpts; i++) {
+
+ /* Create a sub-surface mapping point too. */
+ /* Note that not every mapping point has a sub-surface point, */
+ /* and that the gflag and vflag will be nz if it does. */
+ /* We're assuming here that the dv is close to being on the */
+ /* destination gamut, so that the vector_isect param will be */
+ /* close to 1.0 at the intended destination gamut. */
+ {
+ double mv[3], ml, nv[3]; /* Mapping vector & length, noralized mv */
+ double minv[3], maxv[3];
+ double mint, maxt;
+ gtri *mintri, *maxtri;
+
+ smp[i].vflag = smp[i].gflag = 0; /* Default unknown */
+ smp[i].w2 = 0.0;
+ icmSub3(mv, smp[i].dv, smp[i].sv); /* Mapping vector */
+ ml = icmNorm3(mv); /* It's length */
+ if (ml > 0.1) { /* If mapping is non trivial */
+
+//#define PFCOND i == 802
+
+//if (PFCOND) printf("~1 mapping %d = %f %f %f -> %f %f %f\n", i, smp[i].sv[0],smp[i].sv[1],smp[i].sv[2],smp[i].dv[0],smp[i].dv[1],smp[i].dv[2]);
+//if (PFCOND) printf("~1 vector %f %f %f, len %f\n", mv[0], mv[1], mv[2],ml);
+ /* Compute actual depth of ray into destination gamut */
+ if (di_gam->vector_isect(di_gam, smp[i].sv, smp[i].dv,
+ minv, maxv, &mint, &maxt, &mintri, &maxtri) != 0) {
+ double wp[3], bp[3]; /* Gamut white and black points */
+ double p1, napoint[3] = { 50.0, 0.0, 0.0 }; /* Neutral axis point */
+ double natarg[3]; /* Neutral axis sub target */
+ double adepth1, adepth2 = 1000.0; /* Directional depth, radial depth */
+ double adepth; /* Minimum available depth */
+ double mv2[3], sml; /* Sub-surface mapping vector & norm. length */
+
+ /* Locate the point on the neutral axis that is closest to */
+ /* the guide ray. We use this as a destination direction */
+ /* if the sub surface ray gets very long, and to compute */
+ /* a sanity check on the available depth. */
+ if (d_gam->getwb(d_gam, NULL, NULL, NULL, wp, dst_kbp ? NULL : bp, dst_kbp ? bp : NULL) == 0) {
+ if (icmLineLineClosest(napoint, NULL, &p1, NULL, bp, wp,
+ smp[i].sv,smp[i].dv) == 0) {
+ /* Clip it */
+ if (p1 < 0.0)
+ icmCpy3(napoint, bp);
+ else if (p1 > 1.0)
+ icmCpy3(napoint, wp);
+
+//if (PFCOND) printf("~1 neutral axis point = %f %f %f\n", napoint[0], napoint[1], napoint[2]);
+ /* Compute a normalized available depth from distance */
+ /* to closest to neautral axis point */
+ if (maxt > 1.0) /* Compression */
+ if ((mint > 1e-8 && maxt > -1e-8) /* G. & V. Compression */
+ || ((mint < -1e-8 && maxt > -1e-8) /* G. Exp & V. comp. */
+ && (fabs(mint) < (fabs(maxt) - 1e-8))))
+ adepth2 = icmNorm33(napoint, smp[i].dv);
+ else /* Expansion */
+ adepth2 = icmNorm33(napoint, smp[i].sv);
+ }
+#ifdef VERB
+ else {
+ printf("icmLineLineClosest failed\n");
+ }
+#endif
+ }
+#ifdef VERB
+ else {
+ printf("d_gam->getwb failed\n");
+ }
+#endif
+
+//printf("\n~1 i %d: %f %f %f -> %f %f %f\n isect at t %f and %f\n", i, smp[i].sv[0], smp[i].sv[1], smp[i].sv[2], smp[i].dv[0], smp[i].dv[1], smp[i].dv[2], mint, maxt);
+
+ /* Only create sub-surface mapping vectors if it makes sense. */
+ /* If mapping vector is pointing away from destination gamut, */
+ /* (which shouldn't happen), ignore it. If the directional depth */
+ /* is very thin compared to the radial depth, indicating that we're */
+ /* near a "lip", ignore it. */
+ if (mint >= -1e-8 && maxt > 1e-8) {
+
+ /* Gamut compression and vector compression */
+ if (fabs(mint - 1.0) < fabs(maxt) - 1.0
+ && smp[i].dgam->radial(smp[i].dgam, NULL, smp[i].dv)
+ < smp[i].sgam->radial(smp[i].sgam, NULL, smp[i].dv)) {
+
+//if (PFCOND) printf("~1 point is gamut comp & vect comp.\n");
+//if (PFCOND) printf("~1 point is gamut comp & vect comp. mint %f maxt %f\n",mint,maxt);
+ adepth1 = ml * 0.5 * (maxt + mint - 2.0);
+#ifdef CYLIN_SUBVEC
+ adepth = adepth2; /* Always cylindrical depth */
+#else
+ adepth = adepth1 < adepth2 ? adepth1 : adepth2; /* Smaller of the two */
+#endif
+ if (adepth1 < (0.5 * adepth2))
+ continue;
+//if (PFCOND) printf("~1 dir adepth %f, radial adapeth %f\n",adepth1,adepth2);
+ adepth *= 0.9; /* Can't use 100% */
+ smp[i].gflag = 1; /* Gamut compression and */
+ smp[i].vflag = 1; /* vector compression */
+
+ /* Compute available depth and knee factor adjusted sub-vector */
+ icmCpy3(smp[i].sv2, smp[i].dv); /* Sub source is guide dest */
+ ml *= (1.0 - gamcknf); /* Scale by knee */
+ adepth *= (1.0 - gamcknf);
+ sml = ml < adepth ? ml : adepth; /* Smaller of two */
+//if (PFCOND) printf("~1 adjusted subvec len %f\n",sml);
+ icmNormalize3(mv2, mv, sml); /* Full sub-surf disp. == no knee */
+ icmAdd3(mv2, smp[i].sv2, mv2); /* Knee adjusted destination */
+
+//if (PFCOND) printf("~1 before blend sv2 %f %f %f, dv2 %f %f %f\n", smp[i].sv2[0], smp[i].sv2[1], smp[i].sv2[2], mv2[0], mv2[1], mv2[2]);
+ /* Blend towards n.axis as length of sub vector approaches */
+ /* distance to neutral axis. */
+ icmSub3(natarg, napoint, smp[i].sv2);
+ icmNormalize3(natarg, natarg, sml); /* Sub vector towards n.axis */
+ icmAdd3(natarg, natarg, smp[i].sv2); /* n.axis target */
+#ifdef CYLIN_SUBVEC
+ icmCpy3(mv2, natarg); /* cylindrical direction vector */
+#else
+ icmBlend3(mv2, mv2, natarg, sml/adepth2);
+#endif /* CYLIN_SUBVEC */
+//if (PFCOND) printf("~1 after blend sv2 %f %f %f, dv2 %f %f %f\n", smp[i].sv2[0], smp[i].sv2[1], smp[i].sv2[2], mv2[0], mv2[1], mv2[2]);
+
+ icmCpy3(smp[i].dv2, mv2); /* Destination */
+ icmCpy3(smp[i].temp, smp[i].dv2); /* Save a copy to temp */
+ smp[i].w2 = 0.8;
+ } else {
+//if (PFCOND) printf("~1 point is gamut exp & vect exp. mint %f maxt %f\n",mint,maxt);
+ smp[i].gflag = 2; /* Gamut expansion and */
+ smp[i].vflag = 0; /* vector expansion, */
+ /* but crossing over, so no sub vect. */
+//if (PFCOND) printf("~1 point is crossover point\n",mint,maxt);
+ }
+
+ /* Gamut expansion and vector expansion */
+ } else if (mint < -1e-8 && maxt > 1e-8) {
+
+//if (PFCOND) printf("~1 point is gamut exp & vect exp. mint %f maxt %f\n",mint,maxt);
+ /* This expand/expand case has reversed src/dst sense to above */
+ adepth1 = ml * 0.5 * -mint;
+#ifdef CYLIN_SUBVEC
+ adepth = adepth2; /* Always cylindrical depth */
+#else
+ adepth = adepth1 < adepth2 ? adepth1 : adepth2;
+#endif
+//if (PFCOND) printf("~1 dir adepth %f, radial adapeth %f\n",adepth1,adepth2);
+ adepth *= 0.9; /* Can't use 100% */
+
+ if (adepth1 < (0.6 * adepth2))
+ continue;
+
+ smp[i].gflag = 2; /* Gamut expansion */
+ smp[i].vflag = 2; /* vector is expanding */
+
+ icmCpy3(smp[i].dv2, smp[i].sv); /* Sub dest is guide src */
+ ml *= (1.0 - gamxknf); /* Scale by knee */
+ adepth *= (1.0 - gamxknf);
+ sml = ml < adepth ? ml : adepth;/* Smaller of two */
+ icmNormalize3(mv2, mv, sml); /* Full sub-surf disp. == no knee */
+ icmSub3(mv2, smp[i].dv2, mv2); /* Knee adjusted source */
+
+ /* Blend towards n.axis as length of sub vector approaches */
+ /* distance to neutral axis. */
+ icmSub3(natarg, smp[i].dv2, napoint);
+ icmNormalize3(natarg, natarg, sml); /* Sub vector away n.axis */
+ icmSub3(natarg, smp[i].dv2, natarg);/* n.axis oriented source */
+#ifdef CYLIN_SUBVEC
+ icmCpy3(mv2, natarg); /* cylindrical direction vector */
+#else
+ icmBlend3(mv2, mv2, natarg, sml/adepth2); /* dir adjusted src */
+#endif /* CYLIN_SUBVEC */
+
+ icmCpy3(smp[i].sv2, mv2); /* Source */
+ icmCpy3(smp[i].temp, smp[i].dv2); /* Save a copy to temp */
+ smp[i].w2 = 0.8;
+
+ /* Conflicted case */
+ } else {
+ /* Nonsense vector */
+ smp[i].gflag = 0; /* Gamut compression but */
+ smp[i].vflag = 0; /* vector is expanding */
+//if (PFCOND) printf("~1 point is nonsense vector mint %f maxt %f\n",mint,maxt);
+
+ icmCpy3(smp[i].dv, smp[i].aodv); /* Clip to the destination gamut */
+ }
+ }
+ }
+ }
+
+#ifdef NEVER // Diagnostic
+ smp[i].vflag = 0; /* Disable sub-points */
+#endif /* NEVER */
+
+ VB(("Out Src %d = %f %f %f\n",i,smp[i].sv[0],smp[i].sv[1],smp[i].sv[2]));
+ VB(("Out Dst %d = %f %f %f\n",i,smp[i].dv[0],smp[i].dv[1],smp[i].dv[2]));
+ if (smp[i].vflag != 0) {
+ VB(("Out Src2 %d = %f %f %f\n",i,smp[i].sv2[0],smp[i].sv2[1],smp[i].sv2[2]));
+ VB(("Out Dst2 %d = %f %f %f\n",i,smp[i].dv2[0],smp[i].dv2[1],smp[i].dv2[2]));
+ }
+ }
+
+#ifdef SUBVEC_SMOOTHING
+ VB(("Smoothing sub-surface guide points:\n"));
+
+ /* Smooth the sub-surface mapping points */
+ /* dv2[] is duplicated in temp[], so use temp[] as the values to be filtered */
+ for (i = 0; i < nmpts; i++) {
+ double tmp[3];
+ double fdv2[3]; /* Filtered dv2[] */
+ double tw; /* Total weight */
+ int rc;
+
+ if (smp[i].vflag == 0)
+ continue;
+
+ /* Compute filtered value */
+ tw = fdv2[0] = fdv2[1] = fdv2[2] = 0.0;
+ for (j = 0; j < smp[i].nnd; j++) {
+ nearsmth *np = smp[i].nd[j].n; /* Pointer to neighbor */
+ double nw = smp[i].nd[j].w; /* Weight */
+ double tmp[3];
+
+ if (np->vflag) {
+ icmSub3(tmp, smp[i].sv2, np->sv2); /* Vector from neighbour src to src */
+ icmAdd3(tmp, tmp, np->dv2); /* Neigbour dst + vector */
+
+ icmScale3(tmp, tmp, nw); /* weight for filter */
+ icmAdd3(fdv2, fdv2, tmp); /* sum filtered value */
+ tw += nw;
+ }
+ }
+
+ if (tw > 0.0) {
+//printf("~1 %d: moved %f %f %f -> %f %f %f de %f\n", i, smp[i].dv2[0], smp[i].dv2[1], smp[i].dv2[2], fdv2[0], fdv2[1], fdv2[2], icmNorm33(smp[i].dv2,fdv2));
+ icmScale3(smp[i].dv2, fdv2, 1.0/tw);
+ }
+ }
+#endif /* SUBVEC_SMOOTHING */
+
+ VB(("near_smooth is done\n"));
+
+ if (evectmap != NULL)
+ evectmap->del(evectmap);
+#ifndef PLOT_DIGAM
+ if (si_gam != sc_gam)
+ sci_gam->del(sci_gam);
+ if (di_gam != sci_gam && di_gam != sci_gam)
+ di_gam->del(di_gam);
+ for (i = 0; i < nmpts; i++) {
+ smp[i].sgam = NULL;
+ smp[i].dgam = NULL;
+ }
+#else /* !PLOT_DIGAM */
+ warning("!!!!! PLOT_DIGAM defined !!!!!");
+#endif /* !PLOT_DIGAM */
+ *npp = nmpts;
+ return smp;
+}
+
+/* Free the list of points that was returned */
+void free_nearsmth(nearsmth *smp, int nmpts) {
+ int i;
+
+ for (i = 0; i < nmpts; i++) {
+ if (smp[i].nd != NULL)
+ free(smp[i].nd);
+ }
+ free(smp);
+}
+
+
+/* =================================================================== */
+
+#if defined(SAVE_VRMLS) && defined(PLOT_MAPPING_INFLUENCE)
+/* Create a plot indicating how the source mapping has been guided by the */
+/* various weighting forces */
+static void create_influence_plot(nearsmth *smp, int nmpts) {
+ gamut *gam;
+ int src = 0; /* 1 = src, 0 = dst gamuts */
+ vrml *wrl = NULL;
+ co *fpnts = NULL; /* Mapping points to create diagnostic color mapping */
+ rspl *swdiag = NULL;
+ int gres[3];
+ double avgdev[3];
+ double cols[4][3] = { { 1.0, 0.0, 0.0 }, /* Absolute = red */
+ { 1.0, 1.0, 0.0 }, /* Relative = yellow */
+ { 0.0, 0.0, 1.0 }, /* Radial = blue */
+ { 0.0, 1.0, 0.0 } }; /* Depth = green */
+ double grey[3] = { 0.5, 0.5, 0.5 }; /* Grey */
+ double max, min;
+ int ix;
+
+ if (src)
+ gam = sci_gam;
+ else
+ gam = di_gam;
+
+ /* Setup the scattered data points */
+ if ((fpnts = (co *)malloc((nmpts) * sizeof(co))) == NULL) {
+ fprintf(stderr,"gamut map: Malloc of diagnostic mapping setup points failed\n");
+ if (si_gam != sc_gam)
+ sci_gam->del(sci_gam);
+ if (di_gam != sci_gam && di_gam != sci_gam)
+ di_gam->del(di_gam);
+ free_nearsmth(smp, nmpts);
+ *npp = 0;
+ return NULL;
+ }
+
+ /* Compute error values and diagnostic color */
+ /* for each guide vector */
+ for (i = 0; i < nmpts; i++) {
+ double dv[4], gv;
+ double rgb[3];
+
+ /* Source value location */
+ if (src) {
+ for (j = 0; j < 3; j++)
+ fpnts[i].p[j] = smp[i]._sv[j]; /* Non rotated and elevated */
+ } else { /* Dest value location */
+ for (j = 0; j < 3; j++)
+ fpnts[i].p[j] = smp[i].dv[j];
+ }
+
+ /* Diagnostic color */
+ max = -1e60; min = 1e60;
+ for (k = 0; k < 4; k++) { /* Find max and min error value */
+ dv[k] = smp[i].dbgv[k];
+ if (dv[k] > max)
+ max = dv[k];
+ if (dv[k] < min)
+ min = dv[k];
+ }
+ for (k = 0; k < 4; k++) /* Scale to max */
+ dv[k] /= max;
+ max /= max;
+ min /= max;
+ max -= min; /* reduce min to zero */
+ for (k = 0; k < 4; k++)
+ dv[k] /= max;
+ for (gv = 1.0, k = 0; k < 4; k++) /* Blend remainder with grey */
+ gv -= dv[k];
+
+ for (j = 0; j < 3; j++) /* Compute interpolated color */
+ fpnts[i].v[j] = 0.0;
+ for (k = 0; k < 4; k++) {
+ for (j = 0; j < 3; j++)
+ fpnts[i].v[j] += dv[k] * cols[k][j];
+ }
+ for (j = 0; j < 3; j++)
+ fpnts[i].v[j] += gv * grey[j];
+ }
+
+ /* Create the diagnostic color rspl */
+ for (j = 0; j < 3; j++) { /* Set resolution for all axes */
+ gres[j] = mapres;
+ avgdev[j] = 0.001;
+ }
+ swdiag = new_rspl(RSPL_NOFLAGS, 3, 3); /* Allocate 3D -> 3D */
+ swdiag->fit_rspl(swdiag, RSPL_NOFLAGS, fpnts, nmpts, NULL, NULL, gres, NULL, NULL, 1.0, avgdev, NULL);
+
+ /* Now create a plot of the sci_gam with the vertexes colored acording to the */
+ /* diagnostic map. */
+ if ((wrl = new_vrml("sci_gam_wt.wrl", 1)) == NULL) {
+ fprintf(stderr,"gamut map: new_vrml failed\n");
+ if (fpnts != NULL)
+ free(fpnts);
+ if (swdiag != NULL)
+ swdiag->del(swdiag);
+ if (si_gam != sc_gam)
+ sci_gam->del(sci_gam);
+ if (di_gam != sci_gam && di_gam != sci_gam)
+ di_gam->del(di_gam);
+ free_nearsmth(smp, nmpts);
+ *npp = 0;
+ return NULL;
+ }
+
+ /* Plot the gamut triangle vertexes */
+ for (ix = 0; ix >= 0;) {
+ co pp;
+ double col[3];
+
+ ix = gam->getvert(gam, NULL, pp.p, ix);
+ swdiag->interp(swdiag, &pp);
+ wrl->add_col_vertex(wrl, 0, pp.p, pp.v);
+ }
+ gam->startnexttri(gam);
+ for (;;) {
+ int vix[3];
+ if (gam->getnexttri(gam, vix))
+ break;
+ wrl->add_triangle(wrl, 0, vix);
+ }
+ wrl->make_triangles_vc(wrl, 0, 0.0);
+
+ printf("Writing sci_gam_wt.wrl file\n");
+ wrl->del(wrl); /* Write file */
+ free(fpnts);
+ swdiag->del(swdiag);
+}
+#endif
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/gamut/nearsmth.h b/gamut/nearsmth.h
new file mode 100644
index 0000000..6e94969
--- /dev/null
+++ b/gamut/nearsmth.h
@@ -0,0 +1,274 @@
+#ifndef NEARSMTH_H
+#define NEARSMTH_H
+
+/*
+ * nearsmth
+ *
+ * Gamut mapping support routine that creates a list of
+ * guide vectors from the source to destination
+ * gamut, smoothed to retain reasonably even spacing.
+ *
+ * Author: Graeme W. Gill
+ * Date: 17/1/2002
+ * Version: 1.00
+ *
+ * Copyright 2002 - 2006 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.
+ */
+
+
+/* Settings for nearsmth & gammap */
+
+#define GAMMAP_RSPLFLAGS (0) /* Default rspl flags */
+#define GAMMAP_RSPLAVGDEV 0.005 /* Default average deviation */
+
+/* Structures to hold weightings */
+
+/* Color x light & dark = 14 combinations */
+typedef enum {
+ /* Combinations in prioroty order */
+ gmm_light_red = 0x101,
+ gmm_light_yellow = 0x102,
+ gmm_light_green = 0x104,
+ gmm_light_cyan = 0x108,
+ gmm_light_blue = 0x110,
+ gmm_light_magenta = 0x120,
+ gmm_light_neutral = 0x140,
+
+ gmm_dark_red = 0x201,
+ gmm_dark_yellow = 0x202,
+ gmm_dark_green = 0x204,
+ gmm_dark_cyan = 0x208,
+ gmm_dark_blue = 0x210,
+ gmm_dark_magenta = 0x220,
+ gmm_dark_neutral = 0x240,
+
+ gmm_l_d_red = 0x301,
+ gmm_l_d_yellow = 0x302,
+ gmm_l_d_green = 0x304,
+ gmm_l_d_cyan = 0x308,
+ gmm_l_d_blue = 0x310,
+ gmm_l_d_magenta = 0x320,
+ gmm_l_d_neutral = 0x340,
+
+ gmm_light_colors = 0x17f, /* All light colors */
+ gmm_dark_colors = 0x27f, /* All dark colors */
+
+ gmm_default = 0x37f, /* All light and dark colors */
+
+ gmm_end = 0x0000, /* Mark the end of the list */
+ gmm_ignore = 0x1001, /* Ignore this entry */
+
+ /* Components */
+ gmc_light = 0x100,
+ gmc_dark = 0x200,
+ gmc_l_d = 0x300, /* Both Light and dark */
+ gmc_red = 0x001,
+ gmc_yellow = 0x002,
+ gmc_green = 0x004,
+ gmc_cyan = 0x008,
+ gmc_blue = 0x010,
+ gmc_magenta = 0x020,
+ gmc_neutral = 0x040,
+ gmc_colors = 0x07f /* All colors */
+
+} gmm_chex;
+
+/* Single weight */
+typedef struct {
+ double l,c,h;
+} iweight;
+
+/* Group of complete weights */
+/* (Remeber to alter near_wcopy() and near_wblend() !!) */
+typedef struct {
+
+ gmm_chex ch; /* Color hextant this applies to */
+
+ /* Cusp alignment control */
+ struct {
+ iweight w; /* Component alignment weights, 0 - 1 */
+ double cx; /* Chroma expansion, 1 = none, > 1 = more */
+ } c;
+
+ /* Radial weighting */
+ /* Weight to give to minimizing delta E to source mapped radially */
+ struct {
+ double o; /* Overall Radial weight */
+ double h; /* Hue dominance vs l+c, 0 - 1 */
+ double l; /* l dominance vs, c, 0 - 1 */
+ } l;
+
+ /* Absolute error weighting */
+ /* Weight given to minimizing delta E to destination closest point */
+ struct {
+ double o; /* Overall Absolute weight */
+
+ double h; /* Hue dominance vs l+c, 0 - 1 */
+
+ double wl; /* White l dominance vs, c, 0 - 1 */
+ double gl; /* Grey l dominance vs, c, 0 - 1 */
+ double bl; /* Black l dominance vs, c, 0 - 1 */
+
+ double wlth; /* White l blend start radius, 0 - 1, at white = 0 */
+ double blpow; /* Black l blend power, linear = 1.0, enhance < 1.0 */
+
+ double lxpow; /* L error extra power, none = 1.0 */
+ double lxthr; /* L error xover threshold in DE */
+ } a;
+
+ /* Relative vector smoothing. */
+ struct {
+ double rdl; /* Direction smoothing radius L* dir. (delta E radius at src point)*/
+ double rdh; /* Direction smoothing radius H* (delta E radius at src point)*/
+ } r;
+
+ /* depth weighting */
+ /* Weighing to give to minimizing depth ratio by mapping to/from adequate dest/src depth */
+ struct {
+ double co; /* Overall compression weighting */
+ double xo; /* Overall expansion weighting */
+ } d;
+
+ /* Fine tuning destination gamut mapping */
+ struct {
+ double x; /* Final expansion weight, 0 - 1 */
+ } f; /* Weights fine tuning to expand rather than just compress */
+
+ /* Internal use */
+ iweight rl; /* Resolved radial weight */
+ iweight ra; /* Resolved absolute weight */
+
+ int set; /* Whether this has been set */
+} gammapweights;
+
+
+/* Blend a two groups of weights into one, given two weightings */
+void near_wblend(gammapweights *dst,
+ gammapweights *src1, double wgt1, gammapweights *src2, double wgt2);
+
+
+/* A neighbour and it's weight for relative error */
+typedef struct {
+ struct _nearsmth *n; /* Pointer to neigbor */
+ double rw; /* Raw neighbor interpolation weight (1.0 at point) */
+ double w; /* Neighbor interpolation weight (sums to 1.0) */
+} neighb;
+
+/* Returned point values. */
+/* Note that source == input point, destination == output point */
+/* (All the source values are in rotated L mapped partial gamut mapped space) */
+struct _nearsmth {
+
+ /* Public: */
+ int gflag; /* Gamut direction flag. 0 = not determinable, 1 = comp., 2 = exp. */
+ int vflag; /* Vector direction flag. 0 = not determinable, 1 = comp., 2 = exp. */
+ /* sv2 & dv2 are valid if vflag != 0 */
+
+ /* Gamut surface mapping guide point */
+ double sv[3]; /* Source value (input, cusp aligned during fwd optimization) */
+ double sr; /* Corresponding source radius from center */
+ double drv[3]; /* Destination radial value (starting point & reference) */
+ double drr; /* Destination radial value radius from center */
+ double dv[3]; /* Output destination value */
+ double dr; /* Output destination value radius from center */
+ double div[3]; /* gam[cx]pf moderated dv[] value */
+
+ /* Gamut sub-surface mapping guide point (knee shape controlled by gamcknf & gamxknf) */
+ double sv2[3]; /* Sub-surface source value */
+ double dv2[3]; /* Sub-surface destination value */
+ double div2[3]; /* gam[cx]pf moderated dv2[] value */
+ double w2; /* Sub-surface weight */
+
+ /* Diagnostic points */
+ double csv[3]; /* Non-cusp mapped source value */
+
+ /* Private to nearsmth: */
+ gammapweights wt; /* Weight for this point determined from original source location */
+ int swap; /* Swapped during nearest finding */
+ double mL, fmL; /* Mid point L value, filtered mid point L value */
+ double m2d[3][4]; /* Tangent alignment rotation for sv[] 3D -> 2D */
+ double m3d[3][4]; /* Tangent alignment rotation for 2D -> sv[] 3D */
+ double _sv[3]; /* Original (non cusp aligned) source value (input) */
+ double _sr; /* Original source radius */
+ double naxbf; /* Blend factor that goes to 0.0 at white & black points. */
+ double aodv[3]; /* Absolute error optimized destination value */
+ double nrdv[3]; /* No relative weight optimized destination value */
+ double anv[3]; /* Average neighborhood target point (relative target) */
+
+ double tdst[3]; /* Target destination on gamut */
+ double evect[3]; /* Accumulated extension vector direction */
+ double clen; /* Current correction length needed */
+ double coff[3]; /* Correction offset */
+ double rext;
+
+ double temp[3]; /* General temporary */
+ gamut *sgam; /* Source gamut sci_gam = intersection of src and img gamut gamut */
+ gamut *dgam; /* Destination gamut di_gam */
+
+ int nnd, _nnd; /* No & size of direction neighbour list */
+ neighb *nd; /* Allocated list of neighbours */
+
+ double dcratio; /* Depth compression ratio */
+ double dxratio; /* Depth expansion ratio */
+
+ int debug;
+ double dbgv[4]; /* Error components va, vr, vl, vd on last itteration */
+
+}; typedef struct _nearsmth nearsmth;
+
+
+/* Return the upper bound on the number of points that will be generated */
+int near_smooth_np(
+ gamut *sc_gam, /* Source colorspace gamut */
+ gamut *s_gam, /* Source image gamut (== sc_gam if none) */
+ gamut *d_gam, /* Destination colorspace gamut */
+ double xvra /* Extra vertex ratio */
+);
+
+/* Return a list of points. Call free_nearsmth() after use */
+/* Return NULL on error */
+nearsmth *near_smooth(
+ int verb, /* Verbose flag */
+ int *npp, /* Return the number of points returned */
+ gamut *sc_gam, /* Source colorspace gamut */
+ gamut *s_gam, /* Source image gamut (== sc_gam if none) */
+ gamut *d_gam, /* Destination colorspace gamut */
+ int src_kbp, /* Use K only black point as src gamut black point */
+ int dst_kbp, /* Use K only black point as dst gamut black point */
+ double d_bp[3], /* Destination target black point - may be NULL */
+ gammapweights *wh, /* Structure holding weights */
+ double gamcknf, /* Gamut compression knee factor, 0.0 - 1.0 */
+ double gamxknf, /* Gamut expansion knee factor, 0.0 - 1.0 */
+ int usecomp, /* Flag indicating whether smoothed compressed value will be used */
+ int useexp, /* Flag indicating whether smoothed expanded value will be used */
+ double xvra, /* Extra vertex ratio */
+ int mapres, /* Target grid res for 3D RSPL */
+ double mapsmooth, /* Target smoothing for 3D RSPL */
+ datai map_il, /* Preliminary rspl input range */
+ datai map_ih,
+ datao map_ol, /* Preliminary rspl output range */
+ datao map_oh
+);
+
+/* Free the list of points that was returned */
+void free_nearsmth(nearsmth *smp, int npp);
+
+/* Expand the compact form of weights into the explicit form. */
+int expand_weights(gammapweights out[14], gammapweights *in);
+
+/* Blend a two expanded groups of individual weights into one */
+void near_xwblend(
+gammapweights *dst,
+gammapweights *src1, double wgt1,
+gammapweights *src2, double wgt2
+);
+
+/* Tweak weights acording to extra cmy cusp flags or rel override */
+void tweak_weights(gammapweights out[14], int dst_cmymap, int rel_oride);
+
+#endif /* NEARSMTH_H */
+
diff --git a/gamut/smthtest.c b/gamut/smthtest.c
new file mode 100644
index 0000000..24fdfef
--- /dev/null
+++ b/gamut/smthtest.c
@@ -0,0 +1,460 @@
+
+/*
+ * nearsmth test code. Test the smoothed nearpoint routine.
+ *
+ * Author: Graeme W. Gill
+ * Date: 17/1/2002
+ * Version: 1.00
+ *
+ * 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.
+ */
+
+/* TTBD:
+ *
+ */
+
+#undef DEBUG /* test a single value out */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <fcntl.h>
+#include <string.h>
+#include <math.h>
+#if defined(__IBMC__) && defined(_M_IX86)
+#include <float.h>
+#endif
+
+#include "copyright.h"
+#include "aconfig.h"
+#include "numlib.h"
+#include "rspl.h"
+#include "gamut.h"
+#include "nearsmth.h"
+
+double m21po[3] = { 2.0, 1.0, 2.0 }; /* Many to 1 filter mixing power LCh (theoretically 2) */
+
+/* Mapping weights */
+gammapweights weights[] = {
+ {
+ gmm_default, /* Non hue specific defaults */
+ { /* Cusp alignment control */
+ {
+ 0.0, /* Cusp luminance alignment weighting 0 = none, 1 = full */
+ 0.0, /* Cusp chroma alignment weighting 0 = none, 1 = full */
+ 0.2 /* Cusp hue alignment weighting 0 = none, 1 = full */
+ },
+ 1.00 /* Chroma expansion 1 = none */
+ },
+ { /* Radial weighting */
+ 0.0, /* Radial error overall weight, 0 + */
+ 0.5, /* Radial hue dominance vs l+c, 0 - 1 */
+ 0.5 /* Radial l dominance vs, c, 0 - 1 */
+ },
+ { /* Weighting of absolute error of destination from source */
+ 1.0, /* Absolute error overall weight */
+ 0.5, /* Hue dominance vs l+c, 0 - 1 */
+
+ 0.9, /* Light l dominance vs, c, 0 - 1 */
+ 0.9, /* Medium l dominance vs, c, 0 - 1 */
+ 0.9, /* Dark l dominance vs, c, 0 - 1 */
+
+ 0.5, /* l/c dominance breakpoint, 0 - 1 */
+ 0.0, /* l dominance exageration, 0+ */
+ 0.0 /* c dominance exageration, 0+ */
+ },
+ { /* Relative vector smoothing */
+ 30.0, 20.0 /* Relative Smoothing radius L* H* */
+ },
+ { /* Weighting of excessive compression error, which is */
+ /* the src->dst vector length over the available dst depth. */
+ /* The depth is half the distance to the intersection of the */
+ /* vector to the other side of the gamut. (doesn't get triggered much ?) */
+ 100.0, /* Compression depth weight */
+ 100.0 /* Expansion depth weight */
+ }
+ }
+};
+
+#define OVERSHOOT 1.0
+
+void usage(void) {
+ fprintf(stderr,"Create smoothed near mapping between two gamuts, Version %s\n",ARGYLL_VERSION_STR);
+ fprintf(stderr,"Author: Graeme W. Gill, licensed under the AGPL Version 3\n");
+ fprintf(stderr,"usage: smthtest [options] ingamut outgamut diag_vrml\n");
+ fprintf(stderr," -v Verbose\n");
+// fprintf(stderr," -s nearf Absolute delta E weighting\n");
+ exit(1);
+}
+
+FILE *start_vrml(char *name, int doaxes);
+void start_line_set(FILE *wrl);
+void add_vertex(FILE *wrl, double pp[3]);
+void make_lines(FILE *wrl, int ppset);
+void end_vrml(FILE *wrl);
+
+int
+main(int argc, char *argv[]) {
+ int fa,nfa; /* argument we're looking at */
+ char *xl;
+ char in_name[100];
+ char out_name[100];
+ char diag_name[100];
+ int verb = 0;
+ double nearf = 1.0; /* Absolute delta E weightign */
+ datai il, ih; /* rspl input range */
+ datao ol, oh; /* rspl output range */
+
+ gamut *gin, *gout; /* Input and Output gamuts */
+ nearsmth *nsm; /* Returned list of near smooth points */
+ int nnsm; /* Number of near smoothed points */
+ FILE *wrl; /* VRML output file */
+
+ gammapweights xweights[14];
+
+ int i;
+
+#if defined(__IBMC__) && defined(_M_IX86)
+ _control87(EM_UNDERFLOW, EM_UNDERFLOW);
+#endif
+
+ error_program = argv[0];
+
+ if (argc < 3)
+ 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;
+ }
+
+ /* Smoothing factor */
+ else if (argv[fa][1] == 's' || argv[fa][1] == 'S') {
+ fa = nfa;
+ if (na == NULL) usage();
+ nearf = atof(na);
+ }
+ else
+ usage();
+ } else
+ break;
+ }
+
+ if (fa >= argc || argv[fa][0] == '-') usage();
+ strcpy(in_name,argv[fa++]);
+
+ if (fa >= argc || argv[fa][0] == '-') usage();
+ strcpy(out_name,argv[fa++]);
+
+ if (fa >= argc || argv[fa][0] == '-') usage();
+ strcpy(diag_name,argv[fa++]);
+
+ /* - - - - - - - - - - - - - - - - - - - */
+ /* read the input device gamut */
+
+ gin = new_gamut(0.0, 0, 0);
+
+ if ((xl = strrchr(in_name, '.')) == NULL) { /* Add .gam extention if there isn't one */
+ xl = in_name + strlen(in_name);
+ strcpy(xl,".gam");
+ }
+
+ if (gin->read_gam(gin, in_name))
+ error("Reading input gamut failed");
+
+ /* - - - - - - - - - - - - - - - - - - - */
+ /* read the output device gamut */
+
+ gout = new_gamut(0.0, 0, 0);
+
+ if ((xl = strrchr(out_name, '.')) == NULL) { /* Add .gam extention if there isn't one */
+ xl = out_name + strlen(out_name);
+ strcpy(xl,".gam");
+ }
+
+ if (gout->read_gam(gout, out_name))
+ error("Reading output gamut failed");
+
+ /* - - - - - - - - - - - - - - - - - - - */
+
+ il[0] = ol[0] = 0.0;
+ il[1] = ol[1] = -128.0;
+ il[2] = ol[2] = -128.0;
+ ih[0] = oh[0] = 100.0;
+ ih[1] = oh[1] = 128.0;
+ ih[2] = oh[2] = 128.0;
+
+ /* Convert from compact to explicit hextant weightings */
+ expand_weights(xweights, weights);
+
+ /* Create the near point mapping */
+ nsm = near_smooth(verb, &nnsm, gin, gin, gout, 0, 0, NULL, xweights,
+ 0.1, 0.1, 1, 1, 2.0, 17, 10.0, il, ih, ol, oh);
+ if (nsm == NULL)
+ error("Creating smoothed near points failed");
+
+ /* Output the src to smoothed near point vectors */
+ if ((xl = strrchr(diag_name, '.')) == NULL) { /* Add .wrl extention if there isn't one */
+ xl = diag_name + strlen(diag_name);
+ strcpy(xl,".wrl");
+ }
+
+ wrl = start_vrml(diag_name, 1);
+ start_line_set(wrl);
+
+ for (i = 0; i < nnsm; i++) {
+ add_vertex(wrl, nsm[i].sv); /* Source gamut point */
+ add_vertex(wrl, nsm[i].dv); /* Smoother destination value */
+
+// add_vertex(wrl, nsm[i].drv); /* Radial points */
+ }
+ make_lines(wrl, 2);
+ end_vrml(wrl);
+
+ /* Clean up */
+ free_nearsmth(nsm, nnsm);
+
+ gout->del(gout);
+ gin->del(gin);
+
+ return 0;
+}
+
+/* ------------------------------------------------ */
+/* Some simple functions to do basic VRML work */
+
+#ifndef GAMUT_LCENT
+#define GAMUT_LCENT 50.0
+#endif
+static int npoints = 0;
+static int paloc = 0;
+static struct { double pp[3]; } *pary;
+
+static void Lab2RGB(double *out, double *in);
+
+FILE *start_vrml(char *name, int doaxes) {
+ FILE *wrl;
+ struct {
+ double x, y, z;
+ double wx, wy, wz;
+ double r, g, b;
+ } axes[5] = {
+ { 0, 0, 50-GAMUT_LCENT, 2, 2, 100, .7, .7, .7 }, /* L axis */
+ { 50, 0, 0-GAMUT_LCENT, 100, 2, 2, 1, 0, 0 }, /* +a (red) axis */
+ { 0, -50, 0-GAMUT_LCENT, 2, 100, 2, 0, 0, 1 }, /* -b (blue) axis */
+ { -50, 0, 0-GAMUT_LCENT, 100, 2, 2, 0, 1, 0 }, /* -a (green) axis */
+ { 0, 50, 0-GAMUT_LCENT, 2, 100, 2, 1, 1, 0 }, /* +b (yellow) axis */
+ };
+ int i;
+
+ if ((wrl = fopen(name,"w")) == NULL)
+ error("Error opening VRML file '%s'\n",name);
+
+ npoints = 0;
+
+ fprintf(wrl,"#VRML V2.0 utf8\n");
+ fprintf(wrl,"\n");
+ fprintf(wrl,"# Created by the Argyll CMS\n");
+ fprintf(wrl,"Transform {\n");
+ fprintf(wrl,"children [\n");
+ fprintf(wrl," NavigationInfo {\n");
+ fprintf(wrl," type \"EXAMINE\" # It's an object we examine\n");
+ fprintf(wrl," } # We'll add our own light\n");
+ fprintf(wrl,"\n");
+ fprintf(wrl," DirectionalLight {\n");
+ fprintf(wrl," direction 0 0 -1 # Light illuminating the scene\n");
+ fprintf(wrl," direction 0 -1 0 # Light illuminating the scene\n");
+ fprintf(wrl," }\n");
+ fprintf(wrl,"\n");
+ fprintf(wrl," Viewpoint {\n");
+ fprintf(wrl," position 0 0 340 # Position we view from\n");
+ fprintf(wrl," }\n");
+ fprintf(wrl,"\n");
+ if (doaxes != 0) {
+ fprintf(wrl,"# Lab axes as boxes:\n");
+ for (i = 0; i < 5; i++) {
+ fprintf(wrl,"Transform { translation %f %f %f\n", axes[i].x, axes[i].y, axes[i].z);
+ fprintf(wrl,"\tchildren [\n");
+ fprintf(wrl,"\t\tShape{\n");
+ fprintf(wrl,"\t\t\tgeometry Box { size %f %f %f }\n",
+ axes[i].wx, axes[i].wy, axes[i].wz);
+ fprintf(wrl,"\t\t\tappearance Appearance { material Material ");
+ fprintf(wrl,"{ diffuseColor %f %f %f} }\n", axes[i].r, axes[i].g, axes[i].b);
+ fprintf(wrl,"\t\t}\n");
+ fprintf(wrl,"\t]\n");
+ fprintf(wrl,"}\n");
+ }
+ fprintf(wrl,"\n");
+ }
+
+ return wrl;
+}
+
+void
+start_line_set(FILE *wrl) {
+
+ fprintf(wrl,"\n");
+ fprintf(wrl,"Shape {\n");
+ fprintf(wrl," geometry IndexedLineSet { \n");
+ fprintf(wrl," coord Coordinate { \n");
+ fprintf(wrl," point [\n");
+}
+
+void add_vertex(FILE *wrl, double pp[3]) {
+
+ fprintf(wrl,"%f %f %f,\n",pp[1], pp[2], pp[0]-GAMUT_LCENT);
+
+ if (paloc < (npoints+1)) {
+ paloc = (paloc + 10) * 2;
+ if (pary == NULL)
+ pary = malloc(paloc * 3 * sizeof(double));
+ else
+ pary = realloc(pary, paloc * 3 * sizeof(double));
+
+ if (pary == NULL)
+ error ("Malloc failed");
+ }
+ pary[npoints].pp[0] = pp[0];
+ pary[npoints].pp[1] = pp[1];
+ pary[npoints].pp[2] = pp[2];
+ npoints++;
+}
+
+
+void make_lines(FILE *wrl, int ppset) {
+ int i, j;
+
+ fprintf(wrl," ]\n");
+ fprintf(wrl," }\n");
+ fprintf(wrl," coordIndex [\n");
+
+ for (i = 0; i < npoints;) {
+ for (j = 0; j < ppset; j++, i++) {
+ fprintf(wrl,"%d, ", i);
+ }
+ fprintf(wrl,"-1,\n");
+ }
+ fprintf(wrl," ]\n");
+
+ /* Color */
+ fprintf(wrl," colorPerVertex TRUE\n");
+ fprintf(wrl," color Color {\n");
+ fprintf(wrl," color [ # RGB colors of each vertex\n");
+
+ for (i = 0; i < npoints; i++) {
+ double rgb[3], Lab[3];
+ Lab[0] = pary[i].pp[0];
+ Lab[1] = pary[i].pp[1];
+ Lab[2] = pary[i].pp[2];
+ Lab2RGB(rgb, Lab);
+ fprintf(wrl," %f %f %f,\n", rgb[0], rgb[1], rgb[2]);
+ }
+ fprintf(wrl," ] \n");
+ fprintf(wrl," }\n");
+ /* End color */
+
+ fprintf(wrl," }\n");
+ fprintf(wrl,"} # end shape\n");
+
+}
+
+void end_vrml(FILE *wrl) {
+
+ fprintf(wrl,"\n");
+ fprintf(wrl," ] # end of children for world\n");
+ fprintf(wrl,"}\n");
+
+ if (fclose(wrl) != 0)
+ error("Error closing VRML file\n");
+}
+
+
+/* Convert a gamut Lab value to an RGB value for display purposes */
+static void
+Lab2RGB(double *out, double *in) {
+ double L = in[0], a = in[1], b = in[2];
+ double x,y,z,fx,fy,fz;
+ double R, G, B;
+
+ /* Scale so that black is visible */
+ L = L * (100 - 40.0)/100.0 + 40.0;
+
+ /* First convert to XYZ using D50 white point */
+ if (L > 8.0) {
+ fy = (L + 16.0)/116.0;
+ y = pow(fy,3.0);
+ } else {
+ y = L/903.2963058;
+ fy = 7.787036979 * y + 16.0/116.0;
+ }
+
+ fx = a/500.0 + fy;
+ if (fx > 24.0/116.0)
+ x = pow(fx,3.0);
+ else
+ x = (fx - 16.0/116.0)/7.787036979;
+
+ fz = fy - b/200.0;
+ if (fz > 24.0/116.0)
+ z = pow(fz,3.0);
+ else
+ z = (fz - 16.0/116.0)/7.787036979;
+
+ x *= 0.9642; /* Multiply by white point, D50 */
+ y *= 1.0;
+ z *= 0.8249;
+
+ /* Now convert to sRGB values */
+ R = x * 3.2410 + y * -1.5374 + z * -0.4986;
+ G = x * -0.9692 + y * 1.8760 + z * 0.0416;
+ B = x * 0.0556 + y * -0.2040 + z * 1.0570;
+
+ if (R < 0.0)
+ R = 0.0;
+ else if (R > 1.0)
+ R = 1.0;
+
+ if (G < 0.0)
+ G = 0.0;
+ else if (G > 1.0)
+ G = 1.0;
+
+ if (B < 0.0)
+ B = 0.0;
+ else if (B > 1.0)
+ B = 1.0;
+
+ R = pow(R, 1.0/2.2);
+ G = pow(G, 1.0/2.2);
+ B = pow(B, 1.0/2.2);
+
+ out[0] = R;
+ out[1] = G;
+ out[2] = B;
+}
+
+
diff --git a/gamut/surftest.c b/gamut/surftest.c
new file mode 100644
index 0000000..ca2a786
--- /dev/null
+++ b/gamut/surftest.c
@@ -0,0 +1,255 @@
+
+/*
+ * surftest
+ *
+ * Do a torture test of the gamut surfacing algorithm in gamut.
+ *
+ * Author: Graeme W. Gill
+ * Date: 11/11/2006
+ * Version: 1.00
+ *
+ * Copyright 2000, 2006 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 <stdarg.h>
+#include <fcntl.h>
+#include <string.h>
+#include <math.h>
+#include "copyright.h"
+#include "aconfig.h"
+#include "numlib.h"
+#include "icc.h"
+//#include "xicc.h"
+#include "gamut.h"
+
+#define DEF_POINTS 5
+#define DEF_TPOINTS 50
+#define DEF_HEIGHT 5.0
+
+void usage(char *diag) {
+ fprintf(stderr,"Do gamut surface torture test, Version %s\n",ARGYLL_VERSION_STR);
+ fprintf(stderr,"Author: Graeme W. Gill, licensed under the AGPL Version 3\n");
+ fprintf(stderr,"usage: surftest [options] npoints\n");
+ if (diag != NULL)
+ fprintf(stderr,"Diagnostic: %s\n",diag);
+ fprintf(stderr," -v Verbose\n");
+ fprintf(stderr," -r Random points\n");
+ fprintf(stderr," -n Don't add VRML axes or white/black point\n");
+ fprintf(stderr," -h height Height above sqhere (default %f)\n",DEF_HEIGHT);
+ fprintf(stderr," -f Do segemented maxima filtering (default is not)\n");
+ fprintf(stderr," -t ntpoints Number of test points (default %d)\n",DEF_TPOINTS);
+ fprintf(stderr," npoints Number of random points, default %d^3\n",DEF_POINTS);
+ fprintf(stderr," Outputs surftest.wrl");
+ fprintf(stderr,"\n");
+ exit(1);
+}
+
+int
+main(int argc, char *argv[]) {
+ int fa,nfa; /* argument we're looking at */
+ char out_name[100];
+ int npoints = DEF_POINTS;
+ int ntpoints = DEF_TPOINTS;
+ double height = DEF_HEIGHT;
+ gamut *gam;
+ int rand = 0;
+ int verb = 0;
+ int doaxes = 1;
+ int dofilt = 0;
+ int i;
+
+#ifdef NUMSUP_H
+ error_program = "surftest";
+#endif
+
+ /* Process the arguments */
+ for(fa = 1;fa < argc;fa++) {
+ nfa = fa; /* skip to nfa if next argument is used */
+ if (argv[fa][0] == '-') { /* Look for any flags */
+ char *na = NULL; /* next argument after flag, null if none */
+
+ if (argv[fa][2] != '\000')
+ na = &argv[fa][2]; /* next is directly after flag */
+ else {
+ if ((fa+1) < argc) {
+ if (argv[fa+1][0] != '-') {
+ nfa = fa + 1;
+ na = argv[nfa]; /* next is seperate non-flag argument */
+ }
+ }
+ }
+
+ if (argv[fa][1] == '?')
+ usage(NULL);
+
+ /* Verbosity */
+ else if (argv[fa][1] == 'v' || argv[fa][1] == 'V') {
+ verb = 1;
+ }
+ /* Random */
+ else if (argv[fa][1] == 'r' || argv[fa][1] == 'R') {
+ rand = 1;
+ }
+ /* No axis output */
+ else if (argv[fa][1] == 'n' || argv[fa][1] == 'N') {
+ doaxes = 0;
+ }
+ /* Segmented maxima filtering */
+ else if (argv[fa][1] == 'f' || argv[fa][1] == 'F') {
+ dofilt = 1;
+ }
+ /* Height */
+ else if (argv[fa][1] == 'h' || argv[fa][1] == 'H') {
+ fa = nfa;
+ if (na == NULL) usage("No parameter after flag -h");
+ height = atof(na);
+ }
+ /* Number of test points */
+ else if (argv[fa][1] == 't' || argv[fa][1] == 'T') {
+ fa = nfa;
+ if (na == NULL) usage("No parameter after flag -t");
+ ntpoints = atoi(na);
+ }
+ else
+ usage("Unknown flag");
+ } else
+ break;
+ }
+
+ if (fa < argc && argv[fa][0] != '-')
+ npoints = atoi(argv[fa]);
+
+ strcpy(out_name,"surftest.wrl");
+
+ if (verb) {
+ printf("Number of surface points = %d\n",npoints * npoints * npoints);
+ printf("Number of test points = %d\n",ntpoints);
+ }
+
+ /* Creat a gamut object */
+ if ((gam = new_gamut(0.0, 0, 0)) == NULL)
+ error("Failed to create aa gamut object\n");
+
+ if (dofilt == 0)
+ gam->setnofilt(gam);
+
+ if (rand) {
+ npoints = npoints * npoints * npoints;
+
+ /* Create and add our random test points */
+ for (i = 0; i < npoints;) {
+ int j;
+ double pp[3], sum, rad;
+
+ /* Make normalzed random direction vector on sphere radius 30 */
+ for (sum = 0.0, j = 0; j < 3; j++) {
+ double tt = d_rand(-1.0, 1.0);
+ pp[j] = tt;
+ sum += tt * tt;
+ }
+ if (sum < 1e-6)
+ continue;
+ sum = sqrt(sum);
+ rad = d_rand(30.0, 30.0 + height);
+ for (j = 0; j < 3; j++) {
+ pp[j] /= sum;
+ pp[j] *= rad;
+ }
+ pp[0] += 50.0;
+//printf("~1 point %f %f %f\n",pp[0],pp[1],pp[2]);
+// if (verb) printf("\r%d",i+1); fflush(stdout);
+ gam->expand(gam, pp);
+ i++;
+ }
+// if (verb) printf("\n");
+ } else {
+ int co[3];
+
+ /* Create and add our gridded test points */
+ for (co[0] = 0; co[0] < npoints; co[0]++) {
+ for (co[1] = 0; co[1] < npoints; co[1]++) {
+ for (co[2] = 0; co[2] < npoints; co[2]++) {
+ double pp[3], sum, rad;
+ int m, n;
+
+#ifndef NEVER
+ /* Make sure at least one coords are 0 & 1 */
+ for (n = m = 0; m < 3; m++) {
+ if (co[m] == 0 || co[m] == (npoints-1))
+ n++;
+ }
+ if (n < 1)
+ continue;
+#endif
+ pp[0] = 2.0 * (co[0]/(npoints-1.0) - 0.5);
+ pp[1] = 2.0 * (co[1]/(npoints-1.0) - 0.5);
+ pp[2] = 2.0 * (co[2]/(npoints-1.0) - 0.5);
+
+#ifdef NEVER
+ /* Make normalzed random direction vector on sphere radius 30 */
+ for (sum = 0.0, m = 0; m < 3; m++) {
+ sum += pp[m] * pp[m];
+ }
+ if (sum < 1e-6)
+ continue;
+ sum = sqrt(sum);
+#else
+
+ sum = 1.0;
+#endif /* NEVER */
+ rad = d_rand(30.0, 30.0 + height);
+ for (m = 0; m < 3; m++) {
+ pp[m] /= sum;
+ pp[m] *= rad;
+ }
+ pp[0] += 50.0;
+//printf("~1 point %f %f %f\n",pp[0],pp[1],pp[2]);
+ gam->expand(gam, pp);
+ }
+ }
+ }
+ }
+
+ /* Write out the gamut surface */
+ if (gam->write_vrml(gam, out_name, doaxes, 0))
+ error ("write vrml failed on '%s'",out_name);
+
+ if (verb)
+ printf("Written out the gamut surface\n");
+
+ /* Test the gamut surface */
+ for (i = 0; i < ntpoints;) {
+ int j;
+ double pp[3], sum;
+ double r, out[3];
+
+ /* Make normalzed random direction vector on sphere */
+ for (sum = 0.0, j = 0; j < 3; j++) {
+ double tt = d_rand(-1.0, 1.0);
+ pp[j] = tt;
+ sum += tt * tt;
+ }
+ if (sum < 1e-6)
+ continue;
+ sum = sqrt(sum);
+ for (j = 0; j < 3; j++) {
+ pp[j] /= sum;
+ }
+
+ /* Test surface */
+ r = gam->radial(gam, out, pp);
+ i++;
+ }
+
+ gam->del(gam);
+
+ return 0;
+}
+
+
diff --git a/gamut/viewgam.c b/gamut/viewgam.c
new file mode 100644
index 0000000..0b58269
--- /dev/null
+++ b/gamut/viewgam.c
@@ -0,0 +1,700 @@
+
+/*
+ * viewgam
+ *
+ * Gamut support routines.
+ *
+ * Author: Graeme W. Gill
+ * Date: 4/10/00
+ * Version: 1.00
+ *
+ * Copyright 2000 Graeme W. Gill
+ * All rights reserved.
+ *
+ * This material is licenced under the GNU AFFERO GENERAL PUBLIC LICENSE Version 3 :-
+ * see the License.txt file for licencing details.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <fcntl.h>
+#include <string.h>
+#include <math.h>
+#include "copyright.h"
+#include "aconfig.h"
+#include "numlib.h"
+#include "gamut.h"
+#include "cgats.h"
+
+/*
+ This program reads one or more CGATS format triangular gamut
+ surface descriptions, and combines them into a VRML file,
+ so that the gamuts can be visually compared.
+
+ */
+
+/* TTBD:
+ *
+ */
+
+#undef DEBUG
+
+#undef HALF_HACK /* 27.0 */
+
+void usage(char *diag, ...) {
+ fprintf(stderr,"View gamuts 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: viewgam { [-c color] [-t trans] [-w|s] infile.gam } ... outfile.wrl\n");
+ fprintf(stderr," -c color Color to make gamut, r = red, g = green, b = blue\n");
+ fprintf(stderr," c = cyan, m = magenta, y = yellow, e = grey, w = white\n");
+ fprintf(stderr," n = natural color\n");
+ fprintf(stderr," -t trans Set transparency from 0.0 (opaque) to 1.0 (invisible)\n");
+ fprintf(stderr," -w Show as a wireframe\n");
+ fprintf(stderr," -s Show as a solid surace\n");
+ fprintf(stderr," infile.gam Name of .gam file\n");
+ fprintf(stderr," Repeat above for each input file\n\n");
+ fprintf(stderr," -n Don't add Lab axes\n");
+ fprintf(stderr," -k Add markers for prim. & sec. \"cusp\" points\n");
+ fprintf(stderr," -i Compute and print intersecting volume of first 2 gamuts\n");
+ fprintf(stderr," -I isect.gam Same as -i, but save intersection gamut to isect.gam\n");
+ fprintf(stderr," outfile.wrl Name of output .wrl file\n");
+ fprintf(stderr,"\n");
+ exit(1);
+}
+
+#define GCENT 50.0 /* Center of object view */
+
+typedef enum {
+ gam_red = 0,
+ gam_green = 1,
+ gam_blue = 2,
+ gam_cyan = 3,
+ gam_magenta = 4,
+ gam_yellow = 5,
+ gam_grey = 6,
+ gam_white = 7,
+ gam_natural = 8
+} gam_colors;
+
+struct {
+ double r, g, b;
+} color_rgb[8] = {
+ { 1, 0, 0 }, /* gam_red */
+ { 0, 1, 0 }, /* gam_green */
+ { 0, 0, 1 }, /* gam_blue */
+ { 0, 1, 1 }, /* gam_cyan */
+ { 1, 0, 1 }, /* gam_magenta */
+ { 1, 1, 0 }, /* gam_yellow */
+ { .1, .1, .1 }, /* gam_grey */
+ { .7, .7, .7 } /* gam_white */
+};
+
+typedef enum {
+ gam_solid = 0,
+ gam_wire = 1,
+ gam_points = 2
+} gam_reps;
+
+struct _gamdisp {
+ char in_name[MAXNAMEL+1];
+ gam_colors in_colors; /* Color enum for each input */
+ double in_trans; /* Transparency for each input */
+ gam_reps in_rep; /* Representation enum for each input */
+}; typedef struct _gamdisp gamdisp;
+
+
+/* Set a default for a given gamut */
+static void set_default(gamdisp *gds, int n) {
+ gds[n].in_name[0] = '\000';
+ switch(n) {
+ case 0:
+ gds[n].in_colors = gam_natural;
+ gds[n].in_rep = gam_solid;
+ gds[n].in_trans = 0.0;
+ break;
+ case 1:
+ gds[n].in_colors = gam_white;
+ gds[n].in_rep = gam_wire;
+ gds[n].in_trans = 0.0;
+ break;
+ case 2:
+ gds[n].in_colors = gam_red;
+ gds[n].in_rep = gam_wire;
+ gds[n].in_trans = 0.0;
+ break;
+ case 3:
+ gds[n].in_colors = gam_cyan;
+ gds[n].in_rep = gam_wire;
+ gds[n].in_trans = 0.0;
+ break;
+ case 4:
+ gds[n].in_colors = gam_yellow;
+ gds[n].in_rep = gam_wire;
+ gds[n].in_trans = 0.2;
+ break;
+ case 5:
+ gds[n].in_colors = gam_green;
+ gds[n].in_rep = gam_wire;
+ gds[n].in_trans = 0.3;
+ break;
+ case 6:
+ gds[n].in_colors = gam_blue;
+ gds[n].in_rep = gam_wire;
+ gds[n].in_trans = 0.4;
+ break;
+ case 7:
+ gds[n].in_colors = gam_magenta;
+ gds[n].in_rep = gam_wire;
+ gds[n].in_trans = 0.5;
+ break;
+ default:
+ gds[n].in_colors = n % 6;
+ gds[n].in_rep = gam_wire;
+ gds[n].in_trans = 0.6;
+ break;
+ }
+}
+
+int
+main(int argc, char *argv[]) {
+ int fa, nfa, mfa; /* argument we're looking at */
+ int n, ng = 0; /* Current allocation, number of input gamuts */
+ gamdisp *gds; /* Definition of each gamut */
+ int doaxes = 1;
+ int docusps = 0;
+ int isect = 0;
+ FILE *wrl;
+ char out_name[MAXNAMEL+1];
+ char iout_name[MAXNAMEL+1] = "\000";;
+ if (argc < 3)
+ usage("Too few arguments, got %d expect at least 2",argc-1);
+
+ mfa = 1; /* Minimum final arguments */
+
+ if ((gds = (gamdisp *)malloc((ng+1) * sizeof(gamdisp))) == NULL)
+ error("Malloc failed on gamdisp");
+ set_default(gds, 0);
+
+ /* 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+mfa) < argc) {
+ if (argv[fa+1][0] != '-') {
+ nfa = fa + 1;
+ na = argv[nfa]; /* next is seperate non-flag argument */
+ }
+ }
+ }
+
+ if (argv[fa][1] == '?')
+ usage("Usage requested");
+
+ /* Color */
+ else if (argv[fa][1] == 'c' || argv[fa][1] == 'C') {
+ fa = nfa;
+ if (na == NULL) usage("Expect argument after flag -c");
+ switch (na[0]) {
+ case 'r':
+ case 'R':
+ gds[ng].in_colors = gam_red;
+ break;
+ case 'g':
+ case 'G':
+ gds[ng].in_colors = gam_green;
+ break;
+ case 'b':
+ case 'B':
+ gds[ng].in_colors = gam_blue;
+ break;
+ case 'c':
+ case 'C':
+ gds[ng].in_colors = gam_cyan;
+ break;
+ case 'm':
+ case 'M':
+ gds[ng].in_colors = gam_magenta;
+ break;
+ case 'y':
+ case 'Y':
+ gds[ng].in_colors = gam_yellow;
+ break;
+ case 'e':
+ case 'E':
+ gds[ng].in_colors = gam_grey;
+ break;
+ case 'w':
+ case 'W':
+ gds[ng].in_colors = gam_white;
+ break;
+ case 'n':
+ case 'N':
+ gds[ng].in_colors = gam_natural;
+ break;
+ default:
+ usage("Unknown argument after flag -c '%c'",na[0]);
+ }
+ }
+
+ /* Transparency */
+ else if (argv[fa][1] == 't' || argv[fa][1] == 'T') {
+ double v;
+ fa = nfa;
+ if (na == NULL) usage("Expect argument after flag -t");
+ v = atof(na);
+ if (v < 0.0)
+ v = 0.0;
+ else if (v > 1.0)
+ v = 1.0;
+ gds[ng].in_trans = v;
+ }
+
+ /* Solid output */
+ else if (argv[fa][1] == 's' || argv[fa][1] == 'S') {
+ gds[ng].in_rep = gam_solid;
+ }
+
+ /* Wireframe output */
+ else if (argv[fa][1] == 'w' || argv[fa][1] == 'W') {
+ gds[ng].in_rep = gam_wire;
+ }
+
+ /* No axis output */
+ else if (argv[fa][1] == 'n' || argv[fa][1] == 'N') {
+ doaxes = 0;
+ }
+
+ /* Add cusp markers */
+ else if (argv[fa][1] == 'k' || argv[fa][1] == 'K') {
+ docusps = 1;
+ }
+
+ /* Print intersecting volume */
+ else if (argv[fa][1] == 'i' || argv[fa][1] == 'I') {
+ isect = 1;
+
+ /* There is an intersection output gamut file */
+ if (argv[fa][1] == 'I' && na != NULL) {
+ fa = nfa;
+ strncpy(iout_name, na, MAXNAMEL); iout_name[MAXNAMEL] = '\000';
+ }
+ }
+
+ else
+ usage("Unknown flag '%c'",argv[fa][1]);
+
+ } else if (argv[fa][0] != '\000') { /* Got a non-flag */
+ strncpy(gds[ng].in_name,argv[fa],MAXNAMEL); gds[ng].in_name[MAXNAMEL] = '\000';
+
+ ng++;
+ if ((gds = (gamdisp *)realloc(gds, (ng+1) * sizeof(gamdisp))) == NULL)
+ error("Realloc failed on gamdisp");
+ set_default(gds, ng);
+ } else {
+ break;
+ }
+ }
+
+ /* The last "gamut" is actually the output VRML filename, */
+ /* so unwind it. */
+
+ if (ng < 2)
+ usage("Not enough arguments to specify output VRML files");
+
+ strncpy(out_name,gds[--ng].in_name,MAXNAMEL); out_name[MAXNAMEL] = '\000';
+
+#ifdef DEBUG
+ for (n = 0; n < ng; n++) {
+ printf("Input file %d is '%s'\n",n,gds[n].in_name);
+ printf("Input file %d has color %d\n",n,gds[n].in_colors);
+ printf("Input file %d has rep %d\n",n,gds[n].in_rep);
+ printf("Input file %d has trans %f\n",n,gds[n].in_trans);
+
+ }
+ printf("Output file is '%s'\n",out_name);
+#endif /* DEBUG */
+
+ /* Open up the output file */
+ if ((wrl = fopen(out_name,"w")) == NULL)
+ error("Error opening output file '%s'\n",out_name);
+
+ /* Write the header info */
+
+ fprintf(wrl,"#VRML V2.0 utf8\n");
+ fprintf(wrl,"\n");
+ fprintf(wrl,"# Created by the Argyll CMS\n");
+ fprintf(wrl,"Transform {\n");
+ fprintf(wrl," children [\n");
+ fprintf(wrl," NavigationInfo {\n");
+ fprintf(wrl," type \"EXAMINE\" # It's an object we examine\n");
+ fprintf(wrl," } # We'll add our own light\n");
+ fprintf(wrl,"\n");
+#ifdef NEVER
+ fprintf(wrl," DirectionalLight {\n");
+ fprintf(wrl," direction 0 0 -1 # Light illuminating the scene\n");
+ fprintf(wrl," direction 0 -1 0 # Light illuminating the scene\n");
+ fprintf(wrl," }\n");
+#else
+ fprintf(wrl," DirectionalLight {\n");
+ fprintf(wrl," intensity 0.2\n");
+ fprintf(wrl," ambientIntensity 0.1\n");
+ fprintf(wrl," direction -1 -1 -1\n");
+ fprintf(wrl," }\n");
+ fprintf(wrl," DirectionalLight {\n");
+ fprintf(wrl," intensity 0.6\n");
+ fprintf(wrl," ambientIntensity 0.2\n");
+ fprintf(wrl," direction 1 1 1\n");
+ fprintf(wrl," }\n");
+#endif
+ fprintf(wrl,"\n");
+ fprintf(wrl," Viewpoint {\n");
+ fprintf(wrl," position 0 0 340 # Position we view from\n");
+ fprintf(wrl," }\n");
+ fprintf(wrl,"\n");
+ if (doaxes) {
+ /* Define the axis boxes */
+ struct {
+ double x, y, z; /* Box center */
+ double wx, wy, wz; /* Box size */
+ double r, g, b; /* Box color */
+ } axes[5] = {
+ { 0, 0, 50-GCENT, 2, 2, 100, .7, .7, .7 }, /* L axis */
+ { 50, 0, 0-GCENT, 100, 2, 2, 1, 0, 0 }, /* +a (red) axis */
+ { 0, -50, 0-GCENT, 2, 100, 2, 0, 0, 1 }, /* -b (blue) axis */
+ { -50, 0, 0-GCENT, 100, 2, 2, 0, 1, 0 }, /* -a (green) axis */
+ { 0, 50, 0-GCENT, 2, 100, 2, 1, 1, 0 }, /* +b (yellow) axis */
+ };
+
+ /* Define the labels */
+ struct {
+ double x, y, z;
+ double size;
+ char *string;
+ double r, g, b;
+ } labels[6] = {
+ { -2, 2, -GCENT + 100 + 10, 10, "+L*", .7, .7, .7 }, /* Top of L axis */
+ { -2, 2, -GCENT - 10, 10, "0", .7, .7, .7 }, /* Bottom of L axis */
+ { 100 + 5, -3, 0-GCENT, 10, "+a*", 1, 0, 0 }, /* +a (red) axis */
+ { -5, -100 - 10, 0-GCENT, 10, "-b*", 0, 0, 1 }, /* -b (blue) axis */
+ { -100 - 15, -3, 0-GCENT, 10, "-a*", 0, 0, 1 }, /* -a (green) axis */
+ { -5, 100 + 5, 0-GCENT, 10, "+b*", 1, 1, 0 }, /* +b (yellow) axis */
+ };
+
+ fprintf(wrl," # Lab axes as boxes:\n");
+ for (n = 0; n < 5; n++) {
+ fprintf(wrl," Transform { translation %f %f %f\n", axes[n].x, axes[n].y, axes[n].z);
+ fprintf(wrl," children [\n");
+ fprintf(wrl," Shape{\n");
+ fprintf(wrl," geometry Box { size %f %f %f }\n",
+ axes[n].wx, axes[n].wy, axes[n].wz);
+ fprintf(wrl," appearance Appearance { material Material ");
+ fprintf(wrl,"{ diffuseColor %f %f %f} }\n", axes[n].r, axes[n].g, axes[n].b);
+ fprintf(wrl," }\n");
+ fprintf(wrl," ]\n");
+ fprintf(wrl," }\n");
+ }
+ fprintf(wrl," # Axes identification:\n");
+ for (n = 0; n < 6; n++) {
+ fprintf(wrl," Transform { translation %f %f %f\n", labels[n].x, labels[n].y, labels[n].z);
+ fprintf(wrl," children [\n");
+ fprintf(wrl," Shape{\n");
+ fprintf(wrl," geometry Text { string [\"%s\"]\n",labels[n].string);
+ fprintf(wrl," fontStyle FontStyle { family \"SANS\" style \"BOLD\" size %f }\n",
+ labels[n].size);
+ fprintf(wrl," }\n");
+ fprintf(wrl," appearance Appearance { material Material ");
+ fprintf(wrl,"{ diffuseColor %f %f %f} }\n", labels[n].r, labels[n].g, labels[n].b);
+ fprintf(wrl," }\n");
+ fprintf(wrl," ]\n");
+ fprintf(wrl," }\n");
+ }
+ }
+
+ /* Read each input in turn */
+ for (n = 0; n < ng; n++) {
+ int i;
+ cgats *pp;
+ int nverts;
+ int ntris;
+ int Lf, af, bf; /* Fields holding L, a & b data */
+ int v0f, v1f, v2f; /* Fields holding verticies 0, 1 & 2 */
+
+ pp = new_cgats(); /* Create a CGATS structure */
+
+ /* Setup to cope with a gamut file */
+ pp->add_other(pp, "GAMUT");
+
+ if (pp->read_name(pp, gds[n].in_name))
+ error("Input file '%s' error : %s",gds[n].in_name, pp->err);
+
+ if (pp->t[0].tt != tt_other || pp->t[0].oi != 0)
+ error("Input file isn't a GAMUT format file");
+ if (pp->ntables != 2)
+ error("Input file doesn't contain exactly two tables");
+
+ if ((nverts = pp->t[0].nsets) <= 0)
+ error("No verticies");
+ if ((ntris = pp->t[1].nsets) <= 0)
+ error("No triangles");
+
+ if ((Lf = pp->find_field(pp, 0, "LAB_L")) < 0)
+ error("Input file doesn't contain field LAB_L");
+ if (pp->t[0].ftype[Lf] != r_t)
+ error("Field LAB_L is wrong type");
+ if ((af = pp->find_field(pp, 0, "LAB_A")) < 0)
+ error("Input file doesn't contain field LAB_A");
+ if (pp->t[0].ftype[af] != r_t)
+ error("Field LAB_A is wrong type");
+ if ((bf = pp->find_field(pp, 0, "LAB_B")) < 0)
+ error("Input file doesn't contain field LAB_B");
+ if (pp->t[0].ftype[bf] != r_t)
+ error("Field LAB_B is wrong type");
+
+ /* Write the vertexes out */
+ fprintf(wrl,"\n");
+ fprintf(wrl," Transform {\n");
+ fprintf(wrl," translation 0 0 0\n");
+ fprintf(wrl," children [\n");
+ fprintf(wrl," Shape { \n");
+ if (gds[n].in_rep == gam_wire) {
+ fprintf(wrl," geometry IndexedLineSet {\n");
+ } else {
+ fprintf(wrl," geometry IndexedFaceSet {\n");
+ fprintf(wrl," ccw FALSE\n");
+ fprintf(wrl," convex TRUE\n");
+ }
+ fprintf(wrl,"\n");
+ fprintf(wrl," coord Coordinate { \n");
+ fprintf(wrl," point [ # Verticy coordinates\n");
+
+ /* Spit out the point values, in order. */
+ /* Note that a->x, b->y, L->z */
+ for (i = 0; i < nverts; i++) {
+ double L, a, b;
+ L = *((double *)pp->t[0].fdata[i][Lf]);
+ a = *((double *)pp->t[0].fdata[i][af]);
+ b = *((double *)pp->t[0].fdata[i][bf]);
+ fprintf(wrl," %f %f %f,\n",a, b, L - GCENT);
+ }
+ fprintf(wrl," ]\n");
+ fprintf(wrl," }\n");
+ fprintf(wrl,"\n");
+
+ /* Write the triangles/wires out */
+ if ((v0f = pp->find_field(pp, 1, "VERTEX_0")) < 0)
+ error("Input file doesn't contain field VERTEX_0");
+ if (pp->t[1].ftype[v0f] != i_t)
+ error("Field VERTEX_0 is wrong type");
+ if ((v1f = pp->find_field(pp, 1, "VERTEX_1")) < 0)
+ error("Input file doesn't contain field VERTEX_1");
+ if (pp->t[1].ftype[v1f] != i_t)
+ error("Field VERTEX_1 is wrong type");
+ if ((v2f = pp->find_field(pp, 1, "VERTEX_2")) < 0)
+ error("Input file doesn't contain field VERTEX_2");
+ if (pp->t[1].ftype[v2f] != i_t)
+ error("Field VERTEX_2 is wrong type");
+
+ fprintf(wrl," coordIndex [ # Indexes of poligon Verticies \n");
+
+ for (i = 0; i < ntris; i++) {
+ int v0, v1, v2;
+ v0 = *((int *)pp->t[1].fdata[i][v0f]);
+ v1 = *((int *)pp->t[1].fdata[i][v1f]);
+ v2 = *((int *)pp->t[1].fdata[i][v2f]);
+
+#ifdef HALF_HACK
+ if (*((double *)pp->t[0].fdata[v0][Lf]) < HALF_HACK
+ || *((double *)pp->t[0].fdata[v1][Lf]) < HALF_HACK
+ || *((double *)pp->t[0].fdata[v2][Lf]) < HALF_HACK)
+ continue;
+#endif /* HALF_HACK */
+
+ if (gds[n].in_rep == gam_wire) {
+ if (v0 < v1) /* Only output 1 wire of two on an edge */
+ fprintf(wrl," %d, %d, -1\n", v0, v1);
+ if (v1 < v2)
+ fprintf(wrl," %d, %d, -1\n", v1, v2);
+ if (v2 < v0)
+ fprintf(wrl," %d, %d, -1\n", v2, v0);
+ } else {
+ fprintf(wrl," %d, %d, %d, -1\n", v0, v1, v2);
+ }
+ }
+ fprintf(wrl," ]\n");
+ fprintf(wrl,"\n");
+
+ /* Write the colors out */
+ if (gds[n].in_colors == gam_natural) {
+ fprintf(wrl," colorPerVertex TRUE\n");
+ fprintf(wrl," color Color {\n");
+ fprintf(wrl," color [ # RGB colors of each vertex\n");
+
+ for (i = 0; i < nverts; i++) {
+ double rgb[3], Lab[3];
+ Lab[0] = *((double *)pp->t[0].fdata[i][Lf]);
+ Lab[1] = *((double *)pp->t[0].fdata[i][af]);
+ Lab[2] = *((double *)pp->t[0].fdata[i][bf]);
+ gamut_Lab2RGB(rgb, Lab);
+ fprintf(wrl," %f %f %f,\n", rgb[0], rgb[1], rgb[2]);
+ }
+ fprintf(wrl," ] \n");
+ fprintf(wrl," }\n");
+ }
+ fprintf(wrl," }\n");
+ fprintf(wrl," appearance Appearance { \n");
+ fprintf(wrl," material Material {\n");
+ if (gds[n].in_trans > 0.0) {
+ fprintf(wrl," transparency %f\n", gds[n].in_trans);
+ }
+ fprintf(wrl," ambientIntensity 0.3\n");
+ fprintf(wrl," shininess 0.5\n");
+ if (gds[n].in_colors != gam_natural) {
+ fprintf(wrl," emissiveColor %f %f %f\n",
+ color_rgb[gds[n].in_colors].r, color_rgb[gds[n].in_colors].g, color_rgb[gds[n].in_colors].b);
+ }
+ fprintf(wrl," }\n");
+ fprintf(wrl," }\n");
+ fprintf(wrl," } # end Shape\n");
+ fprintf(wrl," ] # end children\n");
+ fprintf(wrl," } # end Transform\n");
+ fprintf(wrl,"\n");
+
+ /* See if there are cusp values */
+ if (docusps) {
+ int kk;
+ double rgb[3], Lab[3];
+ char buf1[50];
+ char *cnames[6] = { "RED", "YELLOW", "GREEN", "CYAN", "BLUE", "MAGENTA" };
+
+ for (i = 0; i < 6; i++) {
+ sprintf(buf1,"CUSP_%s", cnames[i]);
+ if ((kk = pp->find_kword(pp, 0, buf1)) < 0)
+ break;
+
+ if (sscanf(pp->t[0].kdata[kk], "%lf %lf %lf",
+ &Lab[0], &Lab[1], &Lab[2]) != 3) {
+ break;
+ }
+
+ gamut_Lab2RGB(rgb, Lab);
+
+ fprintf(wrl,"\n");
+ fprintf(wrl," Transform {\n");
+ fprintf(wrl," translation %f %f %f\n",Lab[1], Lab[2], Lab[0]-GCENT);
+ fprintf(wrl," children [\n");
+ fprintf(wrl," Shape { \n");
+ fprintf(wrl," geometry Sphere { radius 2.0 }\n");
+ fprintf(wrl," appearance Appearance { material Material {\n");
+ if (gds[n].in_trans > 0.0)
+ fprintf(wrl," transparency %f\n", gds[n].in_trans);
+ if (gds[n].in_colors != gam_natural)
+ fprintf(wrl," diffuseColor %f %f %f\n", color_rgb[gds[n].in_colors].r, color_rgb[gds[n].in_colors].g, color_rgb[gds[n].in_colors].b);
+ else
+ fprintf(wrl," diffuseColor %f %f %f\n", rgb[0], rgb[1], rgb[2]);
+ fprintf(wrl," }\n");
+ fprintf(wrl," }\n");
+ fprintf(wrl," }\n");
+ fprintf(wrl," ]\n");
+ fprintf(wrl," }\n");
+ }
+ fprintf(wrl,"\n");
+ }
+
+ pp->del(pp); /* Clean up */
+ }
+
+ /* Write the trailer */
+ fprintf(wrl," ] # end of children for world\n");
+ fprintf(wrl,"}\n");
+
+ /* Close the file */
+ fclose(wrl);
+
+ if (isect && ng >= 2) {
+ gamut *s, *s1, *s2;
+ double v1, v2, vi;
+
+ if ((s = new_gamut(0.0, 0, 0)) == NULL)
+ error("Creating gamut object failed");
+
+ if ((s1 = new_gamut(0.0, 0, 0)) == NULL)
+ error("Creating gamut object failed");
+
+ if ((s2 = new_gamut(0.0, 0, 0)) == NULL)
+ error("Creating gamut object failed");
+
+ if (s1->read_gam(s1, gds[0].in_name))
+ error("Input file '%s' read failed",gds[n].in_name[0]);
+
+ if (s2->read_gam(s2, gds[1].in_name))
+ error("Input file '%s' read failed",gds[n].in_name[1]);
+
+ v1 = s1->volume(s1);
+ v2 = s2->volume(s2);
+
+ if (s->intersect(s, s1, s2))
+ error("Gamuts are not compatible! (Colorspace, gamut center ?)");
+ vi = s->volume(s);
+
+ if (iout_name[0] != '\000') {
+ if (s->write_gam(s, iout_name))
+ error("Writing intersection gamut to '%s' failed",iout_name);
+ }
+
+ printf("Intersecting volume = %.1f cubic units\n",vi);
+ printf("'%s' volume = %.1f cubic units, intersect = %.2f%%\n",gds[0].in_name,v1,100.0 * vi/v1);
+ printf("'%s' volume = %.1f cubic units, intersect = %.2f%%\n",gds[1].in_name,v2,100.0 * vi/v2);
+
+ s1->del(s1);
+ s2->del(s2);
+ }
+
+ if (ng > 0)
+ free(gds);
+
+ return 0;
+}
+
+#ifdef NEVER
+/* Basic printf type error() and warning() routines */
+
+void
+error(char *fmt, ...)
+{
+ va_list args;
+
+ fprintf(stderr,"icclu: 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,"icclu: Warning - ");
+ va_start(args, fmt);
+ vfprintf(stderr, fmt, args);
+ va_end(args);
+ fprintf(stderr, "\n");
+}
+
+#endif /* NEVER */