/* -*- C++ -*-
 * File: dht_demosaic.cpp
 * Copyright 2013 Anton Petrusevich
 * Created: Tue Apr  9, 2013
 *
 * This code is licensed under one of three licenses as you choose:
 *
 * 1. GNU LESSER GENERAL PUBLIC LICENSE version 2.1
 *    (See file LICENSE.LGPL provided in LibRaw distribution archive for details).
 *
 * 2. COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL) Version 1.0
 *    (See file LICENSE.CDDL provided in LibRaw distribution archive for details).
 *
 * 3. LibRaw Software License 27032010
 *    (See file LICENSE.LibRaw.pdf provided in LibRaw distribution archive for details).
 *
 */

/*
 * функция вычисляет яркостную дистанцию.
 * если две яркости отличаются в два раза, например 10 и 20, то они имеют такой же вес
 * при принятии решения об интерполировании, как и 100 и 200 -- фотографическая яркость между ними 1 стоп.
 * дистанция всегда >=1
 */
static inline float calc_dist(float c1, float c2) {
	return c1 > c2 ? c1 / c2 : c2 / c1;
}

struct DHT {
	int nr_height, nr_width;
	static const int nr_topmargin = 4, nr_leftmargin = 4;
	float (*nraw)[3];
	ushort channel_maximum[3];
	float channel_minimum[3];
	LibRaw &libraw;
	enum {
		HVSH = 1,
		HOR = 2,
		VER = 4,
		HORSH = HOR | HVSH,
		VERSH = VER | HVSH,
		DIASH = 8,
		LURD = 16,
		RULD = 32,
		LURDSH = LURD | DIASH,
		RULDSH = RULD | DIASH,
		HOT = 64
	};
	static inline float Thot(void) throw () {
		return 64.0f;
	}
	static inline float Tg(void) throw () {
		return 256.0f;
	}
	static inline float T(void) throw () {
		return 1.4f;
	}
	char *ndir;
	inline int nr_offset(int row, int col) throw () {
		return (row * nr_width + col);
	}
	int get_hv_grb(int x, int y, int kc) {
		float hv1 = 2 * nraw[nr_offset(y - 1, x)][1]
				/ (nraw[nr_offset(y - 2, x)][kc] + nraw[nr_offset(y, x)][kc]);
		float hv2 = 2 * nraw[nr_offset(y + 1, x)][1]
				/ (nraw[nr_offset(y + 2, x)][kc] + nraw[nr_offset(y, x)][kc]);
		float kv = calc_dist(hv1, hv2)
				* calc_dist(nraw[nr_offset(y, x)][kc] * nraw[nr_offset(y, x)][kc],
						(nraw[nr_offset(y - 2, x)][kc] * nraw[nr_offset(y + 2, x)][kc]));
		kv *= kv;
		kv *= kv;
		kv *= kv;
		float dv = kv
				* calc_dist(nraw[nr_offset(y - 3, x)][1] * nraw[nr_offset(y + 3, x)][1],
						nraw[nr_offset(y - 1, x)][1] * nraw[nr_offset(y + 1, x)][1]);
		float hh1 = 2 * nraw[nr_offset(y, x - 1)][1]
				/ (nraw[nr_offset(y, x - 2)][kc] + nraw[nr_offset(y, x)][kc]);
		float hh2 = 2 * nraw[nr_offset(y, x + 1)][1]
				/ (nraw[nr_offset(y, x + 2)][kc] + nraw[nr_offset(y, x)][kc]);
		float kh = calc_dist(hh1, hh2)
				* calc_dist(nraw[nr_offset(y, x)][kc] * nraw[nr_offset(y, x)][kc],
						(nraw[nr_offset(y, x - 2)][kc] * nraw[nr_offset(y, x + 2)][kc]));
		kh *= kh;
		kh *= kh;
		kh *= kh;
		float dh = kh
				* calc_dist(nraw[nr_offset(y, x - 3)][1] * nraw[nr_offset(y, x + 3)][1],
						nraw[nr_offset(y, x - 1)][1] * nraw[nr_offset(y, x + 1)][1]);
		float e = calc_dist(dh, dv);
		char d = dh < dv ? (e > Tg() ? HORSH : HOR) : (e > Tg() ? VERSH : VER);
		return d;
	}
	int get_hv_rbg(int x, int y, int hc) {
		float hv1 = 2 * nraw[nr_offset(y - 1, x)][hc ^ 2]
				/ (nraw[nr_offset(y - 2, x)][1] + nraw[nr_offset(y, x)][1]);
		float hv2 = 2 * nraw[nr_offset(y + 1, x)][hc ^ 2]
				/ (nraw[nr_offset(y + 2, x)][1] + nraw[nr_offset(y, x)][1]);
		float kv = calc_dist(hv1, hv2)
				* calc_dist(nraw[nr_offset(y, x)][1] * nraw[nr_offset(y, x)][1],
						(nraw[nr_offset(y - 2, x)][1] * nraw[nr_offset(y + 2, x)][1]));
		kv *= kv;
		kv *= kv;
		kv *= kv;
		float dv = kv
				* calc_dist(nraw[nr_offset(y - 3, x)][hc ^ 2] * nraw[nr_offset(y + 3, x)][hc ^ 2],
						nraw[nr_offset(y - 1, x)][hc ^ 2] * nraw[nr_offset(y + 1, x)][hc ^ 2]);
		float hh1 = 2 * nraw[nr_offset(y, x - 1)][hc]
				/ (nraw[nr_offset(y, x - 2)][1] + nraw[nr_offset(y, x)][1]);
		float hh2 = 2 * nraw[nr_offset(y, x + 1)][hc]
				/ (nraw[nr_offset(y, x + 2)][1] + nraw[nr_offset(y, x)][1]);
		float kh = calc_dist(hh1, hh2)
				* calc_dist(nraw[nr_offset(y, x)][1] * nraw[nr_offset(y, x)][1],
						(nraw[nr_offset(y, x - 2)][1] * nraw[nr_offset(y, x + 2)][1]));
		kh *= kh;
		kh *= kh;
		kh *= kh;
		float dh = kh
				* calc_dist(nraw[nr_offset(y, x - 3)][hc] * nraw[nr_offset(y, x + 3)][hc],
						nraw[nr_offset(y, x - 1)][hc] * nraw[nr_offset(y, x + 1)][hc]);
		float e = calc_dist(dh, dv);
		char d = dh < dv ? (e > Tg() ? HORSH : HOR) : (e > Tg() ? VERSH : VER);
		return d;
	}
	int get_diag_grb(int x, int y, int kc) {
		float hlu = nraw[nr_offset(y - 1, x - 1)][1] / nraw[nr_offset(y - 1, x - 1)][kc];
		float hrd = nraw[nr_offset(y + 1, x + 1)][1] / nraw[nr_offset(y + 1, x + 1)][kc];
		float hru = nraw[nr_offset(y - 1, x + 1)][1] / nraw[nr_offset(y - 1, x + 1)][kc];
		float hld = nraw[nr_offset(y + 1, x - 1)][1] / nraw[nr_offset(y + 1, x - 1)][kc];
		float dlurd = calc_dist(hlu, hrd)
				* calc_dist(nraw[nr_offset(y - 1, x - 1)][1] * nraw[nr_offset(y + 1, x + 1)][1],
						nraw[nr_offset(y, x)][1] * nraw[nr_offset(y, x)][1]);
		float druld = calc_dist(hlu, hrd)
				* calc_dist(nraw[nr_offset(y - 1, x + 1)][1] * nraw[nr_offset(y + 1, x - 1)][1],
						nraw[nr_offset(y, x)][1] * nraw[nr_offset(y, x)][1]);
		float e = calc_dist(dlurd, druld);
		char d = druld < dlurd ? (e > T() ? RULDSH : RULD) : (e > T() ? LURDSH : LURD);
		return d;
	}
	int get_diag_rbg(int x, int y, int hc) {
		float dlurd = calc_dist(nraw[nr_offset(y - 1, x - 1)][1] * nraw[nr_offset(y + 1, x + 1)][1],
				nraw[nr_offset(y, x)][1] * nraw[nr_offset(y, x)][1]);
		float druld = calc_dist(nraw[nr_offset(y - 1, x + 1)][1] * nraw[nr_offset(y + 1, x - 1)][1],
				nraw[nr_offset(y, x)][1] * nraw[nr_offset(y, x)][1]);
		float e = calc_dist(dlurd, druld);
		char d = druld < dlurd ? (e > T() ? RULDSH : RULD) : (e > T() ? LURDSH : LURD);
		return d;
	}
	static inline float scale_over(float ec, float base) {
		float s = base * .4;
		float o = ec - base;
		return base + sqrt(s * (o + s)) - s;
	}
	static inline float scale_under(float ec, float base) {
		float s = base * .6;
		float o = base - ec;
		return base - sqrt(s * (o + s)) + s;
	}
	~DHT();
	DHT(LibRaw &_libraw);
	void copy_to_image();
	void make_greens();
	void make_diag_dirs();
	void make_hv_dirs();
	void refine_hv_dirs(int i, int js);
	void refine_diag_dirs(int i, int js);
	void refine_ihv_dirs(int i);
	void refine_idiag_dirs(int i);
	void illustrate_dirs();
	void illustrate_dline(int i);
	void make_hv_dline(int i);
	void make_diag_dline(int i);
	void make_gline(int i);
	void make_rbdiag(int i);
	void make_rbhv(int i);
	void make_rb();
	void hide_hots();
	void restore_hots();
};

typedef float float3[3];

/*
 * информация о цветах копируется во float в общем то с одной целью -- чтобы вместо 0 можно было писать 0.5,
 * что больше подходит для вычисления яркостной разницы.
 * причина: в целых числах разница в 1 стоп составляет ряд 8,4,2,1,0 -- последнее число должно быть 0.5,
 * которое непредствамио в целых числах.
 * так же это изменение позволяет не думать о специальных случаях деления на ноль.
 *
 * альтернативное решение: умножить все данные на 2 и в младший бит внести 1. правда,
 * всё равно придётся следить, чтобы при интерпретации зелёного цвета не получился 0 при округлении,
 * иначе проблема при интерпретации синих и красных.
 *
 */
DHT::DHT(LibRaw& _libraw) :
		libraw(_libraw) {
	nr_height = libraw.imgdata.sizes.iheight + nr_topmargin * 2;
	nr_width = libraw.imgdata.sizes.iwidth + nr_leftmargin * 2;
	nraw = (float3*) malloc(nr_height * nr_width * sizeof(float3));
	int iwidth = libraw.imgdata.sizes.iwidth;
	ndir = (char*) calloc(nr_height * nr_width, 1);
	channel_maximum[0] = channel_maximum[1] = channel_maximum[2] = 0;
	channel_minimum[0] = libraw.imgdata.image[0][0];
	channel_minimum[1] = libraw.imgdata.image[0][1];
	channel_minimum[2] = libraw.imgdata.image[0][2];
	for (int i = 0; i < nr_height * nr_width; ++i)
		nraw[i][0] = nraw[i][1] = nraw[i][2] = 0.5;
	for (int i = 0; i < libraw.imgdata.sizes.iheight; ++i) {
		int col_cache[48];
		for (int j = 0; j < 48; ++j) {
			int l = libraw.COLOR(i, j);
			if (l == 3)
				l = 1;
			col_cache[j] = l;
		}
		for (int j = 0; j < iwidth; ++j) {
			int l = col_cache[j % 48];
			unsigned short c = libraw.imgdata.image[i * iwidth + j][l];
			if (c != 0) {
				if (channel_maximum[l] < c)
					channel_maximum[l] = c;
				if (channel_minimum[l] > c)
					channel_minimum[l] = c;
				nraw[nr_offset(i + nr_topmargin, j + nr_leftmargin)][l] = (float) c;
			}
		}
	}
	channel_minimum[0] += .5;
	channel_minimum[1] += .5;
	channel_minimum[2] += .5;
}

void DHT::hide_hots() {
	int iwidth = libraw.imgdata.sizes.iwidth;
#if defined(LIBRAW_USE_OPENMP)
#pragma omp parallel for schedule(guided) firstprivate(iwidth)
#endif
	for (int i = 0; i < libraw.imgdata.sizes.iheight; ++i) {
		int js = libraw.COLOR(i, 0) & 1;
		int kc = libraw.COLOR(i, js);
		/*
		 * js -- начальная х-координата, которая попадает мимо известного зелёного
		 * kc -- известный цвет в точке интерполирования
		 */
		for (int j = js; j < iwidth; j += 2) {
			int x = j + nr_leftmargin;
			int y = i + nr_topmargin;
			float c = nraw[nr_offset(y, x)][kc];
			if ((c > nraw[nr_offset(y, x + 2)][kc] && c > nraw[nr_offset(y, x - 2)][kc]
					&& c > nraw[nr_offset(y - 2, x)][kc] && c > nraw[nr_offset(y + 2, x)][kc]
					&& c > nraw[nr_offset(y, x + 1)][1] && c > nraw[nr_offset(y, x - 1)][1]
					&& c > nraw[nr_offset(y - 1, x)][1] && c > nraw[nr_offset(y + 1, x)][1])
					|| (c < nraw[nr_offset(y, x + 2)][kc] && c < nraw[nr_offset(y, x - 2)][kc]
							&& c < nraw[nr_offset(y - 2, x)][kc]
							&& c < nraw[nr_offset(y + 2, x)][kc] && c < nraw[nr_offset(y, x + 1)][1]
							&& c < nraw[nr_offset(y, x - 1)][1] && c < nraw[nr_offset(y - 1, x)][1]
							&& c < nraw[nr_offset(y + 1, x)][1])) {
				float avg = 0;
				for (int k = -2; k < 3; k += 2)
					for (int m = -2; m < 3; m += 2)
						if (m == 0 && k == 0)
							continue;
						else
							avg += nraw[nr_offset(y + k, x + m)][kc];
				avg /= 8;
//				float dev = 0;
//				for (int k = -2; k < 3; k += 2)
//					for (int l = -2; l < 3; l += 2)
//						if (k == 0 && l == 0)
//							continue;
//						else {
//							float t = nraw[nr_offset(y + k, x + l)][kc] - avg;
//							dev += t * t;
//						}
//				dev /= 8;
//				dev = sqrt(dev);
				if (calc_dist(c, avg) > Thot()) {
					ndir[nr_offset(y, x)] |= HOT;
					float dv = calc_dist(
							nraw[nr_offset(y - 2, x)][kc] * nraw[nr_offset(y - 1, x)][1],
							nraw[nr_offset(y + 2, x)][kc] * nraw[nr_offset(y + 1, x)][1]);
					float dh = calc_dist(
							nraw[nr_offset(y, x - 2)][kc] * nraw[nr_offset(y, x - 1)][1],
							nraw[nr_offset(y, x + 2)][kc] * nraw[nr_offset(y, x + 1)][1]);
					if (dv > dh)
						nraw[nr_offset(y, x)][kc] = (nraw[nr_offset(y, x + 2)][kc]
								+ nraw[nr_offset(y, x - 2)][kc]) / 2;
					else
						nraw[nr_offset(y, x)][kc] = (nraw[nr_offset(y - 2, x)][kc]
								+ nraw[nr_offset(y + 2, x)][kc]) / 2;
				}
			}
		}
		for (int j = js ^ 1; j < iwidth; j += 2) {
			int x = j + nr_leftmargin;
			int y = i + nr_topmargin;
			float c = nraw[nr_offset(y, x)][1];
			if ((c > nraw[nr_offset(y, x + 2)][1] && c > nraw[nr_offset(y, x - 2)][1]
					&& c > nraw[nr_offset(y - 2, x)][1] && c > nraw[nr_offset(y + 2, x)][1]
					&& c > nraw[nr_offset(y, x + 1)][kc] && c > nraw[nr_offset(y, x - 1)][kc]
					&& c > nraw[nr_offset(y - 1, x)][kc ^ 2]
					&& c > nraw[nr_offset(y + 1, x)][kc ^ 2])
					|| (c < nraw[nr_offset(y, x + 2)][1] && c < nraw[nr_offset(y, x - 2)][1]
							&& c < nraw[nr_offset(y - 2, x)][1] && c < nraw[nr_offset(y + 2, x)][1]
							&& c < nraw[nr_offset(y, x + 1)][kc]
							&& c < nraw[nr_offset(y, x - 1)][kc]
							&& c < nraw[nr_offset(y - 1, x)][kc ^ 2]
							&& c < nraw[nr_offset(y + 1, x)][kc ^ 2])) {
				float avg = 0;
				for (int k = -2; k < 3; k += 2)
					for (int m = -2; m < 3; m += 2)
						if (k == 0 && m == 0)
							continue;
						else
							avg += nraw[nr_offset(y + k, x + m)][1];
				avg /= 8;
//				float dev = 0;
//				for (int k = -2; k < 3; k += 2)
//					for (int l = -2; l < 3; l += 2)
//						if (k == 0 && l == 0)
//							continue;
//						else {
//							float t = nraw[nr_offset(y + k, x + l)][1] - avg;
//							dev += t * t;
//						}
//				dev /= 8;
//				dev = sqrt(dev);
				if (calc_dist(c, avg) > Thot()) {
					ndir[nr_offset(y, x)] |= HOT;
					float dv = calc_dist(
							nraw[nr_offset(y - 2, x)][1] * nraw[nr_offset(y - 1, x)][kc ^ 2],
							nraw[nr_offset(y + 2, x)][1] * nraw[nr_offset(y + 1, x)][kc ^ 2]);
					float dh = calc_dist(
							nraw[nr_offset(y, x - 2)][1] * nraw[nr_offset(y, x - 1)][kc],
							nraw[nr_offset(y, x + 2)][1] * nraw[nr_offset(y, x + 1)][kc]);
					if (dv > dh)
						nraw[nr_offset(y, x)][1] = (nraw[nr_offset(y, x + 2)][1]
								+ nraw[nr_offset(y, x - 2)][1]) / 2;
					else
						nraw[nr_offset(y, x)][1] = (nraw[nr_offset(y - 2, x)][1]
								+ nraw[nr_offset(y + 2, x)][1]) / 2;
				}
			}
		}
	}
}

void DHT::restore_hots() {
	int iwidth = libraw.imgdata.sizes.iwidth;
#if defined(LIBRAW_USE_OPENMP)
#ifdef WIN32
#pragma omp parallel for firstprivate(iwidth)
#else
#pragma omp parallel for schedule(guided) firstprivate(iwidth) collapse(2)
#endif
#endif
	for (int i = 0; i < libraw.imgdata.sizes.iheight; ++i) {
		for (int j = 0; j < iwidth; ++j) {
			int x = j + nr_leftmargin;
			int y = i + nr_topmargin;
			if (ndir[nr_offset(y, x)] & HOT) {
				int l = libraw.COLOR(i, j);
				nraw[nr_offset(i + nr_topmargin, j + nr_leftmargin)][l] = libraw.imgdata.image[i
						* iwidth + j][l];
			}
		}
	}
}

void DHT::make_diag_dirs() {
#if defined(LIBRAW_USE_OPENMP)
#pragma omp parallel for schedule(guided)
#endif
	for (int i = 0; i < libraw.imgdata.sizes.iheight; ++i) {
		make_diag_dline(i);
	}
//#if defined(LIBRAW_USE_OPENMP)
//#pragma omp parallel for schedule(guided)
//#endif
//	for (int i = 0; i < libraw.imgdata.sizes.iheight; ++i) {
//		refine_diag_dirs(i, i & 1);
//	}
//#if defined(LIBRAW_USE_OPENMP)
//#pragma omp parallel for schedule(guided)
//#endif
//	for (int i = 0; i < libraw.imgdata.sizes.iheight; ++i) {
//		refine_diag_dirs(i, (i & 1) ^ 1);
//	}
#if defined(LIBRAW_USE_OPENMP)
#pragma omp parallel for schedule(guided)
#endif
	for (int i = 0; i < libraw.imgdata.sizes.iheight; ++i) {
		refine_idiag_dirs(i);
	}
}

void DHT::make_hv_dirs() {
#if defined(LIBRAW_USE_OPENMP)
#pragma omp parallel for schedule(guided)
#endif
	for (int i = 0; i < libraw.imgdata.sizes.iheight; ++i) {
		make_hv_dline(i);
	}
#if defined(LIBRAW_USE_OPENMP)
#pragma omp parallel for schedule(guided)
#endif
	for (int i = 0; i < libraw.imgdata.sizes.iheight; ++i) {
		refine_hv_dirs(i, i & 1);
	}
#if defined(LIBRAW_USE_OPENMP)
#pragma omp parallel for schedule(guided)
#endif
	for (int i = 0; i < libraw.imgdata.sizes.iheight; ++i) {
		refine_hv_dirs(i, (i & 1) ^ 1);
	}
#if defined(LIBRAW_USE_OPENMP)
#pragma omp parallel for schedule(guided)
#endif
	for (int i = 0; i < libraw.imgdata.sizes.iheight; ++i) {
		refine_ihv_dirs(i);
	}
}

void DHT::refine_hv_dirs(int i, int js) {
	int iwidth = libraw.imgdata.sizes.iwidth;
	for (int j = js; j < iwidth; j += 2) {
		int x = j + nr_leftmargin;
		int y = i + nr_topmargin;
		if (ndir[nr_offset(y, x)] & HVSH)
			continue;
		int nv = (ndir[nr_offset(y - 1, x)] & VER) + (ndir[nr_offset(y + 1, x)] & VER)
				+ (ndir[nr_offset(y, x - 1)] & VER) + (ndir[nr_offset(y, x + 1)] & VER);
		int nh = (ndir[nr_offset(y - 1, x)] & HOR) + (ndir[nr_offset(y + 1, x)] & HOR)
				+ (ndir[nr_offset(y, x - 1)] & HOR) + (ndir[nr_offset(y, x + 1)] & HOR);
		bool codir =
				(ndir[nr_offset(y, x)] & VER) ?
						((ndir[nr_offset(y - 1, x)] & VER) || (ndir[nr_offset(y + 1, x)] & VER)) :
						((ndir[nr_offset(y, x - 1)] & HOR) || (ndir[nr_offset(y, x + 1)] & HOR));
		nv /= VER;
		nh /= HOR;
		if ((ndir[nr_offset(y, x)] & VER) && (nh > 2 && !codir)) {
			ndir[nr_offset(y, x)] &= ~VER;
			ndir[nr_offset(y, x)] |= HOR;
		}
		if ((ndir[nr_offset(y, x)] & HOR) && (nv > 2 && !codir)) {
			ndir[nr_offset(y, x)] &= ~HOR;
			ndir[nr_offset(y, x)] |= VER;
		}
	}
}

void DHT::refine_ihv_dirs(int i) {
	int iwidth = libraw.imgdata.sizes.iwidth;
	for (int j = 0; j < iwidth; j++) {
		int x = j + nr_leftmargin;
		int y = i + nr_topmargin;
		if (ndir[nr_offset(y, x)] & HVSH)
			continue;
		int nv = (ndir[nr_offset(y - 1, x)] & VER) + (ndir[nr_offset(y + 1, x)] & VER)
				+ (ndir[nr_offset(y, x - 1)] & VER) + (ndir[nr_offset(y, x + 1)] & VER);
		int nh = (ndir[nr_offset(y - 1, x)] & HOR) + (ndir[nr_offset(y + 1, x)] & HOR)
				+ (ndir[nr_offset(y, x - 1)] & HOR) + (ndir[nr_offset(y, x + 1)] & HOR);
		nv /= VER;
		nh /= HOR;
		if ((ndir[nr_offset(y, x)] & VER) && nh > 3) {
			ndir[nr_offset(y, x)] &= ~VER;
			ndir[nr_offset(y, x)] |= HOR;
		}
		if ((ndir[nr_offset(y, x)] & HOR) && nv > 3) {
			ndir[nr_offset(y, x)] &= ~HOR;
			ndir[nr_offset(y, x)] |= VER;
		}
	}
}
void DHT::make_hv_dline(int i) {
	int iwidth = libraw.imgdata.sizes.iwidth;
	int js = libraw.COLOR(i, 0) & 1;
	int kc = libraw.COLOR(i, js);
	/*
	 * js -- начальная х-координата, которая попадает мимо известного зелёного
	 * kc -- известный цвет в точке интерполирования
	 */
	for (int j = 0; j < iwidth; j++) {
		int x = j + nr_leftmargin;
		int y = i + nr_topmargin;
		char d = 0;
		if ((j & 1) == js) {
			d = get_hv_grb(x, y, kc);
		} else {
			d = get_hv_rbg(x, y, kc);
		}
		ndir[nr_offset(y, x)] |= d;
	}
}

void DHT::make_diag_dline(int i) {
	int iwidth = libraw.imgdata.sizes.iwidth;
	int js = libraw.COLOR(i, 0) & 1;
	int kc = libraw.COLOR(i, js);
	/*
	 * js -- начальная х-координата, которая попадает мимо известного зелёного
	 * kc -- известный цвет в точке интерполирования
	 */
	for (int j = 0; j < iwidth; j++) {
		int x = j + nr_leftmargin;
		int y = i + nr_topmargin;
		char d = 0;
		if ((j & 1) == js) {
			d = get_diag_grb(x, y, kc);
		} else {
			d = get_diag_rbg(x, y, kc);
		}
		ndir[nr_offset(y, x)] |= d;
	}
}

void DHT::refine_diag_dirs(int i, int js) {
	int iwidth = libraw.imgdata.sizes.iwidth;
	for (int j = js; j < iwidth; j += 2) {
		int x = j + nr_leftmargin;
		int y = i + nr_topmargin;
		if (ndir[nr_offset(y, x)] & DIASH)
			continue;
		int nv = (ndir[nr_offset(y - 1, x)] & LURD) + (ndir[nr_offset(y + 1, x)] & LURD)
				+ (ndir[nr_offset(y, x - 1)] & LURD) + (ndir[nr_offset(y, x + 1)] & LURD)
				+ (ndir[nr_offset(y - 1, x - 1)] & LURD) + (ndir[nr_offset(y - 1, x + 1)] & LURD)
				+ (ndir[nr_offset(y + 1, x - 1)] & LURD) + (ndir[nr_offset(y + 1, x + 1)] & LURD);
		int nh = (ndir[nr_offset(y - 1, x)] & RULD) + (ndir[nr_offset(y + 1, x)] & RULD)
				+ (ndir[nr_offset(y, x - 1)] & RULD) + (ndir[nr_offset(y, x + 1)] & RULD)
				+ (ndir[nr_offset(y - 1, x - 1)] & RULD) + (ndir[nr_offset(y - 1, x + 1)] & RULD)
				+ (ndir[nr_offset(y + 1, x - 1)] & RULD) + (ndir[nr_offset(y + 1, x + 1)] & RULD);
		bool codir =
				(ndir[nr_offset(y, x)] & LURD) ?
						((ndir[nr_offset(y - 1, x - 1)] & LURD)
								|| (ndir[nr_offset(y + 1, x + 1)] & LURD)) :
						((ndir[nr_offset(y - 1, x + 1)] & RULD)
								|| (ndir[nr_offset(y + 1, x - 1)] & RULD));
		nv /= LURD;
		nh /= RULD;
		if ((ndir[nr_offset(y, x)] & LURD) && (nh > 4 && !codir)) {
			ndir[nr_offset(y, x)] &= ~LURD;
			ndir[nr_offset(y, x)] |= RULD;
		}
		if ((ndir[nr_offset(y, x)] & RULD) && (nv > 4 && !codir)) {
			ndir[nr_offset(y, x)] &= ~RULD;
			ndir[nr_offset(y, x)] |= LURD;
		}
	}
}

void DHT::refine_idiag_dirs(int i) {
	int iwidth = libraw.imgdata.sizes.iwidth;
	for (int j = 0; j < iwidth; j++) {
		int x = j + nr_leftmargin;
		int y = i + nr_topmargin;
		if (ndir[nr_offset(y, x)] & DIASH)
			continue;
		int nv = (ndir[nr_offset(y - 1, x)] & LURD) + (ndir[nr_offset(y + 1, x)] & LURD)
				+ (ndir[nr_offset(y, x - 1)] & LURD) + (ndir[nr_offset(y, x + 1)] & LURD)
				+ (ndir[nr_offset(y - 1, x - 1)] & LURD) + (ndir[nr_offset(y - 1, x + 1)] & LURD)
				+ (ndir[nr_offset(y + 1, x - 1)] & LURD) + (ndir[nr_offset(y + 1, x + 1)] & LURD);
		int nh = (ndir[nr_offset(y - 1, x)] & RULD) + (ndir[nr_offset(y + 1, x)] & RULD)
				+ (ndir[nr_offset(y, x - 1)] & RULD) + (ndir[nr_offset(y, x + 1)] & RULD)
				+ (ndir[nr_offset(y - 1, x - 1)] & RULD) + (ndir[nr_offset(y - 1, x + 1)] & RULD)
				+ (ndir[nr_offset(y + 1, x - 1)] & RULD) + (ndir[nr_offset(y + 1, x + 1)] & RULD);
		bool codir =
				(ndir[nr_offset(y, x)] & LURD) ?
						((ndir[nr_offset(y - 1, x - 1)] & LURD)
								|| (ndir[nr_offset(y + 1, x + 1)] & LURD)) :
						((ndir[nr_offset(y - 1, x + 1)] & RULD)
								|| (ndir[nr_offset(y + 1, x - 1)] & RULD));
		nv /= LURD;
		nh /= RULD;
		if ((ndir[nr_offset(y, x)] & LURD) && nh > 7) {
			ndir[nr_offset(y, x)] &= ~LURD;
			ndir[nr_offset(y, x)] |= RULD;
		}
		if ((ndir[nr_offset(y, x)] & RULD) && nv > 7) {
			ndir[nr_offset(y, x)] &= ~RULD;
			ndir[nr_offset(y, x)] |= LURD;
		}
	}
}

/*
 * вычисление недостающих зелёных точек.
 */
void DHT::make_greens() {
#if defined(LIBRAW_USE_OPENMP)
#pragma omp parallel for schedule(guided)
#endif
	for (int i = 0; i < libraw.imgdata.sizes.iheight; ++i) {
		make_gline(i);
	}
}

void DHT::make_gline(int i) {
	int iwidth = libraw.imgdata.sizes.iwidth;
	int js = libraw.COLOR(i, 0) & 1;
	int kc = libraw.COLOR(i, js);
	/*
	 * js -- начальная х-координата, которая попадает мимо известного зелёного
	 * kc -- известный цвет в точке интерполирования
	 */
	for (int j = js; j < iwidth; j += 2) {
		int x = j + nr_leftmargin;
		int y = i + nr_topmargin;
		int dx, dy, dx2, dy2;
		float h1, h2;
		if (ndir[nr_offset(y, x)] & VER) {
			dx = dx2 = 0;
			dy = -1;
			dy2 = 1;
			h1 = 2 * nraw[nr_offset(y - 1, x)][1]
					/ (nraw[nr_offset(y - 2, x)][kc] + nraw[nr_offset(y, x)][kc]);
			h2 = 2 * nraw[nr_offset(y + 1, x)][1]
					/ (nraw[nr_offset(y + 2, x)][kc] + nraw[nr_offset(y, x)][kc]);
		} else {
			dy = dy2 = 0;
			dx = 1;
			dx2 = -1;
			h1 = 2 * nraw[nr_offset(y, x + 1)][1]
					/ (nraw[nr_offset(y, x + 2)][kc] + nraw[nr_offset(y, x)][kc]);
			h2 = 2 * nraw[nr_offset(y, x - 1)][1]
					/ (nraw[nr_offset(y, x - 2)][kc] + nraw[nr_offset(y, x)][kc]);
		}
		float b1 = 1
				/ calc_dist(nraw[nr_offset(y, x)][kc], nraw[nr_offset(y + dy * 2, x + dx * 2)][kc]);
		float b2 = 1
				/ calc_dist(nraw[nr_offset(y, x)][kc],
						nraw[nr_offset(y + dy2 * 2, x + dx2 * 2)][kc]);
		b1 *= b1;
		b2 *= b2;
		float eg = nraw[nr_offset(y, x)][kc] * (b1 * h1 + b2 * h2) / (b1 + b2);
		float min, max;
		min = MIN(nraw[nr_offset(y + dy, x + dx)][1], nraw[nr_offset(y + dy2, x + dx2)][1]);
		max = MAX(nraw[nr_offset(y + dy, x + dx)][1], nraw[nr_offset(y + dy2, x + dx2)][1]);
		min /= 1.2;
		max *= 1.2;
		if (eg < min)
			eg = scale_under(eg, min);
		else if (eg > max)
			eg = scale_over(eg, max);
		if (eg > channel_maximum[1])
			eg = channel_maximum[1];
		else if (eg < channel_minimum[1])
			eg = channel_minimum[1];
		nraw[nr_offset(y, x)][1] = eg;
	}
}

/*
 * отладочная функция
 */

void DHT::illustrate_dirs() {
#if defined(LIBRAW_USE_OPENMP)
#pragma omp parallel for schedule(guided)
#endif
	for (int i = 0; i < libraw.imgdata.sizes.iheight; ++i) {
		illustrate_dline(i);
	}
}

void DHT::illustrate_dline(int i) {
	int iwidth = libraw.imgdata.sizes.iwidth;
	for (int j = 0; j < iwidth; j++) {
		int x = j + nr_leftmargin;
		int y = i + nr_topmargin;
		nraw[nr_offset(y, x)][0] = nraw[nr_offset(y, x)][1] = nraw[nr_offset(y, x)][2] = 0.5;
		int l = ndir[nr_offset(y, x)] & 8;
		// l >>= 3; // WTF?
		l = 1;
		if (ndir[nr_offset(y, x)] & HOT)
			nraw[nr_offset(y, x)][0] = l * channel_maximum[0] / 4 + channel_maximum[0] / 4;
		else
			nraw[nr_offset(y, x)][2] = l * channel_maximum[2] / 4 + channel_maximum[2] / 4;
	}

}

/*
 * интерполяция красных и синих.
 *
 * сначала интерполируются недостающие цвета, по диагональным направлениям от которых находятся известные,
 * затем ситуация сводится к тому как интерполировались зелёные.
 */

void DHT::make_rbdiag(int i) {
	int iwidth = libraw.imgdata.sizes.iwidth;
	int js = libraw.COLOR(i, 0) & 1;
	int uc = libraw.COLOR(i, js);
	int cl = uc ^ 2;
	/*
	 * js -- начальная х-координата, которая попадает на уже интерполированный зелёный
	 * al -- известный цвет (кроме зелёного) в точке интерполирования
	 * cl -- неизвестный цвет
	 */
	for (int j = js; j < iwidth; j += 2) {
		int x = j + nr_leftmargin;
		int y = i + nr_topmargin;
		int dx, dy, dx2, dy2;
		if (ndir[nr_offset(y, x)] & LURD) {
			dx = -1;
			dx2 = 1;
			dy = -1;
			dy2 = 1;
		} else {
			dx = -1;
			dx2 = 1;
			dy = 1;
			dy2 = -1;
		}
		float g1 = 1 / calc_dist(nraw[nr_offset(y, x)][1], nraw[nr_offset(y + dy, x + dx)][1]);
		float g2 = 1 / calc_dist(nraw[nr_offset(y, x)][1], nraw[nr_offset(y + dy2, x + dx2)][1]);
		g1 *= g1 * g1;
		g2 *= g2 * g2;

		float eg;
		eg = nraw[nr_offset(y, x)][1]
				* (g1 * nraw[nr_offset(y + dy, x + dx)][cl] / nraw[nr_offset(y + dy, x + dx)][1]
						+ g2 * nraw[nr_offset(y + dy2, x + dx2)][cl]
								/ nraw[nr_offset(y + dy2, x + dx2)][1]) / (g1 + g2);
		float min, max;
		min = MIN(nraw[nr_offset(y + dy, x + dx)][cl], nraw[nr_offset(y + dy2, x + dx2)][cl]);
		max = MAX(nraw[nr_offset(y + dy, x + dx)][cl], nraw[nr_offset(y + dy2, x + dx2)][cl]);
		min /= 1.2;
		max *= 1.2;
		if (eg < min)
			eg = scale_under(eg, min);
		else if (eg > max)
			eg = scale_over(eg, max);
		if (eg > channel_maximum[cl])
			eg = channel_maximum[cl];
		else if (eg < channel_minimum[cl])
			eg = channel_minimum[cl];
		nraw[nr_offset(y, x)][cl] = eg;
	}

}

/*
 * интерполяция красных и синих в точках где был известен только зелёный,
 * направления горизонтальные или вертикальные
 */

void DHT::make_rbhv(int i) {
	int iwidth = libraw.imgdata.sizes.iwidth;
	int js = (libraw.COLOR(i, 0) & 1) ^ 1;
	for (int j = js; j < iwidth; j += 2) {
		int x = j + nr_leftmargin;
		int y = i + nr_topmargin;
		/*
		 * поскольку сверху-снизу и справа-слева уже есть все необходимые красные и синие,
		 * то можно выбрать наилучшее направление исходя из информации по обоим цветам.
		 */
		int dx, dy, dx2, dy2;
		float h1, h2;
		if (ndir[nr_offset(y, x)] & VER) {
			dx = dx2 = 0;
			dy = -1;
			dy2 = 1;
		} else {
			dy = dy2 = 0;
			dx = 1;
			dx2 = -1;
		}
		float g1 = 1 / calc_dist(nraw[nr_offset(y, x)][1], nraw[nr_offset(y + dy, x + dx)][1]);
		float g2 = 1 / calc_dist(nraw[nr_offset(y, x)][1], nraw[nr_offset(y + dy2, x + dx2)][1]);
		g1 *= g1;
		g2 *= g2;
		float eg_r, eg_b;
		eg_r = nraw[nr_offset(y, x)][1]
				* (g1 * nraw[nr_offset(y + dy, x + dx)][0] / nraw[nr_offset(y + dy, x + dx)][1]
						+ g2 * nraw[nr_offset(y + dy2, x + dx2)][0]
								/ nraw[nr_offset(y + dy2, x + dx2)][1]) / (g1 + g2);
		eg_b = nraw[nr_offset(y, x)][1]
				* (g1 * nraw[nr_offset(y + dy, x + dx)][2] / nraw[nr_offset(y + dy, x + dx)][1]
						+ g2 * nraw[nr_offset(y + dy2, x + dx2)][2]
								/ nraw[nr_offset(y + dy2, x + dx2)][1]) / (g1 + g2);
		float min_r, max_r;
		min_r = MIN(nraw[nr_offset(y + dy, x + dx)][0], nraw[nr_offset(y + dy2, x + dx2)][0]);
		max_r = MAX(nraw[nr_offset(y + dy, x + dx)][0], nraw[nr_offset(y + dy2, x + dx2)][0]);
		float min_b, max_b;
		min_b = MIN(nraw[nr_offset(y + dy, x + dx)][2], nraw[nr_offset(y + dy2, x + dx2)][2]);
		max_b = MAX(nraw[nr_offset(y + dy, x + dx)][2], nraw[nr_offset(y + dy2, x + dx2)][2]);
		min_r /= 1.2;
		max_r *= 1.2;
		min_b /= 1.2;
		max_b *= 1.2;

		if (eg_r < min_r)
			eg_r = scale_under(eg_r, min_r);
		else if (eg_r > max_r)
			eg_r = scale_over(eg_r, max_r);
		if (eg_b < min_b)
			eg_b = scale_under(eg_b, min_b);
		else if (eg_b > max_b)
			eg_b = scale_over(eg_b, max_b);

		if (eg_r > channel_maximum[0])
			eg_r = channel_maximum[0];
		else if (eg_r < channel_minimum[0])
			eg_r = channel_minimum[0];
		if (eg_b > channel_maximum[2])
			eg_b = channel_maximum[2];
		else if (eg_b < channel_minimum[2])
			eg_b = channel_minimum[2];
		nraw[nr_offset(y, x)][0] = eg_r;
		nraw[nr_offset(y, x)][2] = eg_b;
	}
}

void DHT::make_rb() {
#if defined(LIBRAW_USE_OPENMP)
#pragma omp barrier
#pragma omp parallel for schedule(guided)
#endif
	for (int i = 0; i < libraw.imgdata.sizes.iheight; ++i) {
		make_rbdiag(i);
	}
#if defined(LIBRAW_USE_OPENMP)
#pragma omp barrier
#pragma omp parallel for schedule(guided)
#endif
	for (int i = 0; i < libraw.imgdata.sizes.iheight; ++i) {
		make_rbhv(i);
	}

}

/*
 * перенос изображения в выходной массив
 */
void DHT::copy_to_image() {
	int iwidth = libraw.imgdata.sizes.iwidth;
#if defined(LIBRAW_USE_OPENMP)
#ifdef WIN32
#pragma omp parallel for
#else
#pragma omp parallel for schedule(guided) collapse(2)
#endif
#endif
	for (int i = 0; i < libraw.imgdata.sizes.iheight; ++i) {
		for (int j = 0; j < iwidth; ++j) {
			libraw.imgdata.image[i * iwidth + j][0] = (unsigned short) (nraw[nr_offset(
					i + nr_topmargin, j + nr_leftmargin)][0]);
			libraw.imgdata.image[i * iwidth + j][2] = (unsigned short) (nraw[nr_offset(
					i + nr_topmargin, j + nr_leftmargin)][2]);
			libraw.imgdata.image[i * iwidth + j][1] = libraw.imgdata.image[i * iwidth + j][3] =
					(unsigned short) (nraw[nr_offset(i + nr_topmargin, j + nr_leftmargin)][1]);
		}
	}
}

DHT::~DHT() {
	free(nraw);
	free(ndir);
}

void LibRaw::dht_interpolate() {
	printf("DHT interpolating\n");
	DHT dht(*this);
	dht.hide_hots();
	dht.make_hv_dirs();
//	dht.illustrate_dirs();
	dht.make_greens();
	dht.make_diag_dirs();
//	dht.illustrate_dirs();
	dht.make_rb();
	dht.restore_hots();
	dht.copy_to_image();

}
