text: #include <algorithm>
  #include <climits>
  #include <cstdint>
  #include "common/cpuinfo.h"
  #include "common/except.h"
  #include "common/make_unique.h"
  #include "common/pixel.h"
  #include "common/zassert.h"
  #include "graph/image_filter.h"
  #include "filter.h"
  #include "resize_impl.h"
  
  #if defined(ZIMG_X86)
    #include "x86/resize_impl_x86.h"
  #elif defined(ZIMG_ARM)
    #include "arm/resize_impl_arm.h"
  #endif
  
  namespace zimg {
  namespace resize {
  
  namespace {
  
  int32_t unpack_pixel_u16(uint16_t x) noexcept
  {
  	return static_cast<int32_t>(x) + INT16_MIN;
  }
  
  uint16_t pack_pixel_u16(int32_t x, int32_t pixel_max) noexcept
  {
  	x = ((x + (1 << 13)) >> 14) - INT16_MIN;
  	x = std::max(std::min(x, pixel_max), static_cast<int32_t>(0));
  
  	return static_cast<uint16_t>(x);
  }
  
  void resize_line_h_u16_c(const FilterContext &filter, const uint16_t *src, uint16_t *dst, unsigned left, unsigned right, unsigned pixel_max)
  {
  	for (unsigned j = left; j < right; ++j) {
  		unsigned left = filter.left[j];
  		int32_t accum = 0;
  
  		for (unsigned k = 0; k < filter.filter_width; ++k) {
  			int32_t coeff = filter.data_i16[j * filter.stride_i16 + k];
  			int32_t x = unpack_pixel_u16(src[left + k]);
  
  			accum += coeff * x;
  		}
  
  		dst[j] = pack_pixel_u16(accum, pixel_max);
  	}
  }
  
  void resize_line_h_f32_c(const FilterContext &filter, const float *src, float *dst, unsigned left, unsigned right)
  {
  	for (unsigned j = left; j < right; ++j) {
  		unsigned top = filter.left[j];
  		float accum = 0;
  
  		for (unsigned k = 0; k < filter.filter_width; ++k) {
  			float coeff = filter.data[j * filter.stride + k];
  			float x = src[top + k];
  
  			accum += coeff * x;
  		}
  
  		dst[j] = accum;
  	}
  }
  
  void resize_line_v_u16_c(const FilterContext &filter, const graph::ImageBuffer<const uint16_t> &src, const graph::ImageBuffer<uint16_t> &dst, unsigned i, unsigned left, unsigned right, unsigned pixel_max)
  {
  	const int16_t *filter_coeffs = &filter.data_i16[i * filter.stride_i16];
  	unsigned top = filter.left[i];
  
  	for (unsigned j = left; j < right; ++j) {
  		int32_t accum = 0;
  
  		for (unsigned k = 0; k < filter.filter_width; ++k) {
  			int32_t coeff = filter_coeffs[k];
  			int32_t x = unpack_pixel_u16(src[top + k][j]);
  
  			accum += coeff * x;
  		}
  
  		dst[i][j] = pack_pixel_u16(accum, pixel_max);
  	}
  }
  
  void resize_line_v_f32_c(const FilterContext &filter, const graph::ImageBuffer<const float> &src, const graph::ImageBuffer<float> &dst, unsigned i, unsigned left, unsigned right)
  {
  	const float *filter_coeffs = &filter.data[i * filter.stride];
  	unsigned top = filter.left[i];
  
  	for (unsigned j = left; j < right; ++j) {
  		float accum = 0;
  
  		for (unsigned k = 0; k < filter.filter_width; ++k) {
  			float coeff = filter_coeffs[k];
  			float x = src[top + k][j];
  
  			accum += coeff * x;
  		}
  
  		dst[i][j] = accum;
  	}
  }
  
  
  class ResizeImplH_C : public ResizeImplH {
  	PixelType m_type;
  	int32_t m_pixel_max;
  public:
  	ResizeImplH_C(const FilterContext &filter, unsigned height, PixelType type, unsigned depth) :
  		ResizeImplH(filter, image_attributes{ filter.filter_rows, height, type }),
  		m_type{ type },
  		m_pixel_max{ static_cast<int32_t>(1UL << depth) - 1 }
  	{
  		if (m_type != PixelType::WORD && m_type != PixelType::FLOAT)
  			error::throw_<error::InternalError>("pixel type not supported");
  	}
  
  	void process(void *, const graph::ImageBuffer<const void> *src, const graph::ImageBuffer<void> *dst, void *, unsigned i, unsigned left, unsigned right) const override
  	{
  		if (m_type == PixelType::WORD)
  			resize_line_h_u16_c(m_filter, static_cast<const uint16_t *>((*src)[i]), static_cast<uint16_t *>((*dst)[i]), left, right, m_pixel_max);
  		else
  			resize_line_h_f32_c(m_filter, static_cast<const float *>((*src)[i]), static_cast<float *>((*dst)[i]), left, right);
  	}
  };
  
  class ResizeImplV_C : public ResizeImplV {
  	PixelType m_type;
  	int32_t m_pixel_max;
  public:
  	ResizeImplV_C(const FilterContext &filter, unsigned width, PixelType type, unsigned depth) :
  		ResizeImplV(filter, image_attributes{ width, filter.filter_rows, type}),
  		m_type{ type },
  		m_pixel_max{ static_cast<int32_t>(1UL << depth) - 1 }
  	{
  		if (m_type != PixelType::WORD && m_type != PixelType::FLOAT)
  			error::throw_<error::InternalError>("pixel type not supported");
  	}
  
  	void process(void *, const graph::ImageBuffer<const void> *src, const graph::ImageBuffer<void> *dst, void *, unsigned i, unsigned left, unsigned right) const override
  	{
  		if (m_type == PixelType::WORD)
  			resize_line_v_u16_c(m_filter, graph::static_buffer_cast<const uint16_t>(*src), graph::static_buffer_cast<uint16_t>(*dst), i, left, right, m_pixel_max);
  		else
  			resize_line_v_f32_c(m_filter, graph::static_buffer_cast<const float>(*src), graph::static_buffer_cast<float>(*dst), i, left, right);
  	}
  };
  
  } // namespace
  
  
  ResizeImplH::ResizeImplH(const FilterContext &filter, const image_attributes &attr) :
  	m_filter(filter),
  	m_attr(attr),
  	m_is_sorted{ std::is_sorted(m_filter.left.begin(), m_filter.left.end()) }
  {
  	zassert_d(m_filter.input_width <= pixel_max_width(attr.type), "overflow");
  	zassert_d(attr.width <= pixel_max_width(attr.type), "overflow");
  }
  
  graph::ImageFilter::filter_flags ResizeImplH::get_flags() const
  {
  	graph::ImageFilter::filter_flags flags{};
  
  	flags.same_row = true;
  	flags.entire_row = !m_is_sorted;
  
  	return flags;
  }
  
  graph::ImageFilter::image_attributes ResizeImplH::get_image_attributes() const { return m_attr; }
  
  graph::ImageFilter::pair_unsigned ResizeImplH::get_required_row_range(unsigned i) const
  {
  	unsigned lines = get_simultaneous_lines();
  	unsigned last = std::min(i, UINT_MAX - lines) + lines;
  
  	return{ i, std::min(last, get_image_attributes().height) };
  }
  
  graph::ImageFilter::pair_unsigned ResizeImplH::get_required_col_range(unsigned left, unsigned right) const
  {
  	if (m_is_sorted) {
  		unsigned col_left = m_filter.left[left];
  		unsigned col_right = m_filter.left[right - 1] + m_filter.filter_width;
  
  		return{ col_left, col_right };
  	} else {
  		return{ 0, m_filter.input_width };
  	}
  }
  
  unsigned ResizeImplH::get_max_buffering() const
  {
  	return get_simultaneous_lines();
  }
  
  
  ResizeImplV::ResizeImplV(const FilterContext &filter, const image_attributes &attr) :
  	m_filter(filter),
  	m_attr(attr),
  	m_is_sorted{ std::is_sorted(m_filter.left.begin(), m_filter.left.end()) }
  {
  	zassert_d(m_filter.input_width <= pixel_max_width(attr.type), "overflow");
  	zassert_d(attr.width <= pixel_max_width(attr.type), "overflow");
  }
  
  graph::ImageFilter::filter_flags ResizeImplV::get_flags() const
  {
  	graph::ImageFilter::filter_flags flags{};
  
  	flags.entire_row = !m_is_sorted;
  
  	return flags;
  }
  
  graph::ImageFilter::image_attributes ResizeImplV::get_image_attributes() const { return m_attr; }
  
  graph::ImageFilter::pair_unsigned ResizeImplV::get_required_row_range(unsigned i) const
  {
  	if (m_is_sorted) {
  		unsigned lines = get_simultaneous_lines();
  		unsigned last = std::min(i, UINT_MAX - lines) + lines;
  		unsigned bot = std::min(last, get_image_attributes().height);
  
  		unsigned row_top = m_filter.left[i];
  		unsigned row_bot = m_filter.left[bot - 1];
  
  		zassert_d(row_bot <= UINT_MAX - m_filter.filter_width, "overflow");
  
  		return{ row_top, row_bot + m_filter.filter_width };
  	} else {
  		return{ 0, m_filter.input_width };
  	}
  }
  
  unsigned ResizeImplV::get_max_buffering() const
  {
  	unsigned step = get_flags().has_state ? get_simultaneous_lines() : 1;
  	unsigned buffering = 0;
  
  	for (unsigned i = 0; i < get_image_attributes().height; i += step) {
  		auto range = get_required_row_range(i);
  		buffering = std::max(buffering, range.second - range.first);
  	}
  
  	return buffering;
  }
  
  
  ResizeImplBuilder::ResizeImplBuilder(unsigned src_width, unsigned src_height, PixelType type) :
  	src_width{ src_width },
  	src_height{ src_height },
  	type{ type },
  	horizontal{},
  	dst_dim{},
  	depth{},
  	filter{},
  	shift{},
  	subwidth{},
  	cpu{ CPUClass::NONE }
  {}
  
  std::unique_ptr<graph::ImageFilter> ResizeImplBuilder::create() const
  {
  	std::unique_ptr<graph::ImageFilter> ret;
  
  	unsigned src_dim = horizontal ? src_width : src_height;
  	FilterContext filter_ctx = compute_filter(*filter, src_dim, dst_dim, shift, subwidth);
  
  #if defined(ZIMG_X86)
  	ret = horizontal ?
  		create_resize_impl_h_x86(filter_ctx, src_height, type, depth, cpu) :
  		create_resize_impl_v_x86(filter_ctx, src_width, type, depth, cpu);
  #elif defined(ZIMG_ARM)
  	ret = horizontal ?
  		create_resize_impl_h_arm(filter_ctx, src_height, type, depth, cpu) :
  		create_resize_impl_v_arm(filter_ctx, src_width, type, depth, cpu);
  #endif
  	if (!ret && horizontal)
  		ret = ztd::make_unique<ResizeImplH_C>(filter_ctx, src_height, type, depth);
  	if (!ret && !horizontal)
  		ret = ztd::make_unique<ResizeImplV_C>(filter_ctx, src_width, type, depth);
  
  	return ret;
  }
  
  } // namespace resize
  } // namespace zimg
  