/* +------------------------------------------------------------------------+
   |                     Mobile Robot Programming Toolkit (MRPT)            |
   |                          https://www.mrpt.org/                         |
   |                                                                        |
   | Copyright (c) 2005-2022, Individual contributors, see AUTHORS file     |
   | See: https://www.mrpt.org/Authors - All rights reserved.               |
   | Released under BSD License. See: https://www.mrpt.org/License          |
   +------------------------------------------------------------------------+ */

#include "img-precomp.h"  // Precompiled headers
//
#include <mrpt/config.h>

// ---------------------------------------------------------------------------
//   This file contains the SSE3/SSSE3 optimized functions for
//   mrpt::img::CImage
//    See the sources and the doxygen documentation page "sse_optimizations" for
//    more details.
// ---------------------------------------------------------------------------
#if MRPT_ARCH_INTEL_COMPATIBLE

#include <mrpt/core/SSE_macros.h>
#include <mrpt/core/SSE_types.h>
#include <mrpt/img/CImage.h>
#include <mrpt/system/memory.h>

#include "CImage.SSEx.h"

/** \addtogroup sse_optimizations
 *  SSE optimized functions
 *  @{
 */

// This is the actual function behind image_SSSE3_scale_half_3c8u():
template <bool MemIsAligned>
void impl_image_SSSE3_scale_half_3c8u(
	const uint8_t* in, uint8_t* out, int w, int h, size_t step_in,
	size_t step_out)
{
	SSE_DISABLE_WARNINGS
	// clang-format off

	const __m128i m0 = _mm_set_epi8(0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x0E, 0x0D, 0x0C, 0x08, 0x07, 0x06, 0x02, 0x01, 0x00);
	const __m128i m1 = _mm_set_epi8(0x0E, 0x0A, 0x09, 0x08, 0x04, 0x03, 0x02, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80);
	const __m128i m2 = _mm_set_epi8(0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x0C, 0x0B, 0x0A, 0x06, 0x05, 0x04, 0x00, 0x80);
	const __m128i m3 = _mm_set_epi8(0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x0F);

	// clang-format on
	SSE_RESTORE_SIGN_WARNINGS

	const int sw = w / 16;	// This are the number of 3*16 blocks in each row
	const int sh = h / 2;
	const int rest_w = w - (16 * w);

	for (int i = 0; i < sh; i++)
	{
		const __m128i* inp = reinterpret_cast<const __m128i*>(in);
		uint8_t* outp = out;

		for (int j = 0; j < sw; j++)
		{
			// 16-byte blocks #0,#1,#2:
			__m128i d0 = mm_load_si128<MemIsAligned>(inp++);
			__m128i d1 = mm_load_si128<MemIsAligned>(inp++);

			// First 16 bytes:
			__m128i shuf0 = _mm_shuffle_epi8(d0, m0);
			__m128i shuf1 = _mm_shuffle_epi8(d1, m1);

			__m128i res0 = _mm_or_si128(shuf0, shuf1);

			_mm_storeu_si128(
				reinterpret_cast<__m128i*>(outp), res0);  // aligned output
			outp += 16;

			// Last 8 bytes:
			__m128i d2 = mm_load_si128<MemIsAligned>(inp++);

			// Write lower 8 bytes only
			_mm_storel_epi64(
				reinterpret_cast<__m128i*>(outp),
				_mm_or_si128(
					_mm_shuffle_epi8(d2, m2), _mm_shuffle_epi8(d1, m3)));
			outp += 8;
		}

		// Extra pixels? (w mod 16 != 0)
		if (rest_w != 0)
		{
			const uint8_t* in_rest = in + 3 * 16 * sw;
			for (int p = 0; p < rest_w / 2; p++)
			{
				outp[0] = in_rest[0];
				outp[1] = in_rest[1];
				outp[2] = in_rest[2];
				in_rest += 6;
				outp += 3;
			}
		}

		in += 2 * step_in;	// Skip one row
		out += step_out;
	}
}

/** Subsample each 2x2 pixel block into 1x1 pixel, taking the first pixel &
 * ignoring the other 3
 *  - <b>Input format:</b> uint8_t, 3 channels (RGB or BGR)
 *  - <b>Output format:</b> uint8_t, 3 channels (RGB or BGR)
 *  - <b>Preconditions:</b> in & out may be aligned to 16bytes (faster) or not,
 * step may be k*16 (faster) or not.
 *  - <b>Notes:</b>
 *  - <b>Requires:</b> SSSE3
 *  - <b>Invoked from:</b> mrpt::img::CImage::scaleHalf()
 */
void image_SSSE3_scale_half_3c8u(
	const uint8_t* in, uint8_t* out, int w, int h, size_t step_in,
	size_t step_out)
{
	if (mrpt::system::is_aligned<16>(in) && mrpt::system::is_aligned<16>(out) &&
		is_multiple<16>(step_in) && is_multiple<16>(step_out))
	{
		impl_image_SSSE3_scale_half_3c8u<true>(
			in, out, w, h, step_in, step_out);
	}
	else
	{
		impl_image_SSSE3_scale_half_3c8u<false>(
			in, out, w, h, step_in, step_out);
	}
}

// This is the actual function behind both: image_SSSE3_rgb_to_gray_8u() and
// image_SSSE3_bgr_to_gray_8u():
template <bool IS_RGB, bool MemIsAligned>
void impl_image_SSSE3_rgb_or_bgr_to_gray_8u(
	const uint8_t* in, uint8_t* out, int w, int h, size_t step_in,
	size_t step_out)
{
	SSE_DISABLE_WARNINGS
	// clang-format off

	// Masks:                            0       1    2    3     4      5     6    7     8      9     A     B      C    D    E     F
	// reds[0-7] from D0
	const __m128i mask0 = _mm_setr_epi8(0x80, 0x00, 0x80, 0x03, 0x80, 0x06, 0x80, 0x09, 0x80, 0x0C, 0x80, 0x0F, 0x80, 0x80, 0x80, 0x80);
	// reds[0-7] from D1
	const __m128i mask1 = _mm_setr_epi8(0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x02, 0x80, 0x05);
	// greens[0-7] from D0
	const __m128i mask2 = _mm_setr_epi8(0x80, 0x01, 0x80, 0x04, 0x80, 0x07, 0x80, 0x0A, 0x80, 0x0D, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80);
	// greens[0-7] from D1
	const __m128i mask3 = _mm_setr_epi8(0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x00, 0x80, 0x03, 0x80, 0x06);
	// blues[0-7] from D0
	const __m128i mask4 = _mm_setr_epi8(0x80, 0x02, 0x80, 0x05, 0x80, 0x08, 0x80, 0x0B, 0x80, 0x0E, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80);
	// blues[0-7] from D1
	const __m128i mask5 = _mm_setr_epi8(0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x01, 0x80, 0x04, 0x80, 0x07);
	// reds[8-15] from D1
	const __m128i mask6 = _mm_setr_epi8(0x80, 0x08, 0x80, 0x0B, 0x80, 0x0E, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80);
	// reds[8-15] from D2
	const __m128i mask7 = _mm_setr_epi8(0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x01, 0x80, 0x04, 0x80, 0x07, 0x80, 0x0A, 0x80, 0x0D);
	// greens[8-15] from D1
	const __m128i mask8 = _mm_setr_epi8(0x80, 0x09, 0x80, 0x0C, 0x80, 0x0F, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80);
	// greens[8-15] from D2
	const __m128i mask9 = _mm_setr_epi8(0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x02, 0x80, 0x05, 0x80, 0x08, 0x80, 0x0B, 0x80, 0x0E);
	// blues[8-15] from D1
	const __m128i mask10 = _mm_setr_epi8(0x80, 0x0A, 0x80, 0x0D, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80);
	// blues[8-15] from D2
	const __m128i mask11 = _mm_setr_epi8(0x80, 0x80, 0x80, 0x80, 0x80, 0x00, 0x80, 0x03, 0x80, 0x06, 0x80, 0x09, 0x80, 0x0C, 0x80, 0x0F);
	// Conversion factors for RGB->Y
	const __m128i VAL_R = _mm_setr_epi8(0x00, 0x1D, 0x00, 0x1D, 0x00, 0x1D, 0x00, 0x1D, 0x00, 0x1D, 0x00, 0x1D, 0x00, 0x1D, 0x00, 0x1D);
	const __m128i VAL_G = _mm_setr_epi8(0x00, 0x96, 0x00, 0x96, 0x00, 0x96, 0x00, 0x96, 0x00, 0x96, 0x00, 0x96, 0x00, 0x96, 0x00, 0x96);
	const __m128i VAL_B = _mm_setr_epi8(0x00, 0x4D, 0x00, 0x4D, 0x00, 0x4D, 0x00, 0x4D, 0x00, 0x4D, 0x00, 0x4D, 0x00, 0x4D, 0x00, 0x4D);
	// mask:
	const __m128i mask_low = _mm_setr_epi8(0x01, 0x03, 0x05, 0x07, 0x09, 0x0B, 0x0D, 0x0F, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80);

	// clang-format on
	SSE_RESTORE_SIGN_WARNINGS

	const __m128i m0 = IS_RGB ? mask4 : mask0;
	const __m128i m1 = IS_RGB ? mask5 : mask1;
	const __m128i m2 = mask2;
	const __m128i m3 = mask3;
	const __m128i m4 = IS_RGB ? mask0 : mask4;
	const __m128i m5 = IS_RGB ? mask1 : mask5;
	const __m128i m6 = IS_RGB ? mask10 : mask6;
	const __m128i m7 = IS_RGB ? mask11 : mask7;
	const __m128i m8 = mask8;
	const __m128i m9 = mask9;
	const __m128i m10 = IS_RGB ? mask6 : mask10;
	const __m128i m11 = IS_RGB ? mask7 : mask11;

	const int sw = w >> 4;	// This are the number of 3*16 blocks in each row
	const int sh = h;

	for (int i = 0; i < sh; i++)
	{
		const __m128i* inp = reinterpret_cast<const __m128i*>(in);
		uint8_t* outp = out;

		for (int j = 0; j < sw; j++)
		{
			// We process RGB data in blocks of 3 x 16byte blocks:
			const __m128i d0 = mm_load_si128<MemIsAligned>(inp++);
			const __m128i d1 = mm_load_si128<MemIsAligned>(inp++);
			const __m128i d2 = mm_load_si128<MemIsAligned>(inp++);

			// First 8 bytes of gray levels:
			{
				const __m128i BLUES_0_7 = _mm_or_si128(
					_mm_shuffle_epi8(d0, m0), _mm_shuffle_epi8(d1, m1));
				const __m128i GREENS_0_7 = _mm_or_si128(
					_mm_shuffle_epi8(d0, m2), _mm_shuffle_epi8(d1, m3));
				const __m128i REDS_0_7 = _mm_or_si128(
					_mm_shuffle_epi8(d0, m4), _mm_shuffle_epi8(d1, m5));

				// _mm_mulhi_epu16(): Multiplies the 8 unsigned 16-bit integers
				// from a by the 8 unsigned 16-bit integers from b.
				// r0 := (a0 * b0)[31:16]
				// r1 := (a1 * b1)[31:16]
				//...
				// r7 := (a7 * b7)[31:16]
				//
				const __m128i GRAYS_0_7 = _mm_adds_epu16(
					_mm_mulhi_epu16(REDS_0_7, VAL_R),
					_mm_adds_epu16(
						_mm_mulhi_epu16(GREENS_0_7, VAL_G),
						_mm_mulhi_epu16(BLUES_0_7, VAL_B)));

				_mm_storel_epi64(
					reinterpret_cast<__m128i*>(outp),
					_mm_shuffle_epi8(GRAYS_0_7, mask_low));
				outp += 8;
			}

			// Second 8 bytes of gray levels:
			{
				const __m128i BLUES_8_15 = _mm_or_si128(
					_mm_shuffle_epi8(d1, m6), _mm_shuffle_epi8(d2, m7));
				const __m128i GREENS_8_15 = _mm_or_si128(
					_mm_shuffle_epi8(d1, m8), _mm_shuffle_epi8(d2, m9));
				const __m128i REDS_8_15 = _mm_or_si128(
					_mm_shuffle_epi8(d1, m10), _mm_shuffle_epi8(d2, m11));

				const __m128i GRAYS_8_15 = _mm_adds_epu16(
					_mm_mulhi_epu16(REDS_8_15, VAL_R),
					_mm_adds_epu16(
						_mm_mulhi_epu16(GREENS_8_15, VAL_G),
						_mm_mulhi_epu16(BLUES_8_15, VAL_B)));

				_mm_storel_epi64(
					reinterpret_cast<__m128i*>(outp),
					_mm_shuffle_epi8(GRAYS_8_15, mask_low));
				outp += 8;
			}
		}
		in += step_in;
		out += step_out;
	}

}  // end impl_image_SSSE3_rgb_or_bgr_to_gray_8u()

/** Convert a RGB image (3cu8) into a GRAYSCALE (1c8u) image, using
 * Y=77*R+150*G+29*B
 *  - <b>Input format:</b> uint8_t, 3 channels (BGR order)
 *  - <b>Output format:</b> uint8_t, 1 channel
 *  - <b>Preconditions:</b> in & out aligned to 16bytes (faster) or not, step =
 * k*16
 *  - <b>Notes:</b>
 *  - <b>Requires:</b> SSSE3
 *  - <b>Invoked from:</b> mrpt::img::CImage::grayscale(),
 * mrpt::img::CImage::grayscaleInPlace()
 */
void image_SSSE3_bgr_to_gray_8u(
	const uint8_t* in, uint8_t* out, int w, int h, size_t step_in,
	size_t step_out)
{
	ASSERTMSG_((step_in & 0x0f) == 0, "step of input image must be 16*k");
	ASSERTMSG_((step_out & 0x0f) == 0, "step of output image must be 16*k");

	if (mrpt::system::is_aligned<16>(in) && mrpt::system::is_aligned<16>(out))
	{
		impl_image_SSSE3_rgb_or_bgr_to_gray_8u<false, true>(
			in, out, w, h, step_in, step_out);
	}
	else
	{
		impl_image_SSSE3_rgb_or_bgr_to_gray_8u<false, false>(
			in, out, w, h, step_in, step_out);
	}
}

/** Convert a RGB image (3cu8) into a GRAYSCALE (1c8u) image, using
 * Y=77*R+150*G+29*B
 *  - <b>Input format:</b> uint8_t, 3 channels (RGB order)
 *  - <b>Output format:</b> uint8_t, 1 channel
 *  - <b>Preconditions:</b> in & out aligned to 16bytes (faster) or not, step =
 * k*16
 *  - <b>Notes:</b>
 *  - <b>Requires:</b> SSSE3
 *  - <b>Invoked from:</b> mrpt::img::CImage::grayscale(),
 * mrpt::img::CImage::grayscaleInPlace()
 */
void image_SSSE3_rgb_to_gray_8u(
	const uint8_t* in, uint8_t* out, int w, int h, size_t step_in,
	size_t step_out)
{
	ASSERTMSG_((step_in & 0x0f) == 0, "step of input image must be 16*k");
	ASSERTMSG_((step_out & 0x0f) == 0, "step of output image must be 16*k");

	if (mrpt::system::is_aligned<16>(in) && mrpt::system::is_aligned<16>(out))
	{
		impl_image_SSSE3_rgb_or_bgr_to_gray_8u<true, true>(
			in, out, w, h, step_in, step_out);
	}
	else
	{
		impl_image_SSSE3_rgb_or_bgr_to_gray_8u<true, false>(
			in, out, w, h, step_in, step_out);
	}
}

/**  @} */

#endif	// end of MRPT_ARCH_INTEL_COMPATIBLE
