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