#include "ANSOpenCV.h"
#include "ANSMatRegistry.h"
#include <nivision.h>
#include <cstdint>
#include <cstdlib>
#include <memory>
#include <json.hpp>
#include "boost/property_tree/ptree.hpp"
#include "boost/property_tree/json_parser.hpp"
#include "boost/foreach.hpp"
#include "ReadBarcode.h"
#include "sys_inc.h"
#include "rtsp_cln.h"
#include "hqueue.h"
#include "http.h"
#include "http_parse.h"
#include "rtsp_player.h"
#include <filesystem>
#include <chrono>
#include <mutex>
#include <turbojpeg.h>
#include <nvjpeg.h>
#include <cuda_runtime.h>
#include "ANSCVVendorGate.h"
#include "ANSLicense.h"       // ANS_DBG macro
#include <thread>
#include <future>
#include <opencv2/imgproc.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/dnn.hpp>


// SIMD includes
#ifdef __AVX2__
#include <immintrin.h>
#elif defined(__SSE2__)
#include <emmintrin.h>
#endif
extern "C" {
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libswscale/swscale.h>
#include <libavutil/opt.h>
#include <libavutil/error.h>
#include <libavutil/imgutils.h>
}
std::mutex imageMutex;  // Global mutex for thread safety
std::timed_mutex timeImageMutex;

static bool ansCVLicenceValid = false;
// Global once_flag to protect license checking
static std::once_flag ansCVLicenseOnceFlag;

//template <typename T>
//T GetData(const boost::property_tree::ptree& pt, const std::string& key)
//{
//	T ret;
//	if (boost::optional<T> data = pt.get_optional<T>(key))
//	{
//		ret = data.get();
//	}
//	return ret;
//}
namespace fs = std::filesystem;

namespace ANSCENTER 
{
	// Global function to verify license only once
	static void VerifyGlobalLicense(const std::string& licenseKey) {
		ansCVLicenceValid = ANSCENTER::ANSLicenseHelper::LicenseVerification(licenseKey, 1007, "ANSCV");
		if (!ansCVLicenceValid) { // we also support ANSTS license
			ansCVLicenceValid = ANSCENTER::ANSLicenseHelper::LicenseVerification(licenseKey, 1003, "ANSVIS");//Default productId=1003 (ANSVIS)
		}
		if (!ansCVLicenceValid) { // we also support ANSTS license
			ansCVLicenceValid = ANSCENTER::ANSLicenseHelper::LicenseVerification(licenseKey, 1008, "ANSTS");//Default productId=1008 (ANSTS)
		}
	}
	TurboJpegCompressor::TurboJpegCompressor() {
		_handle = tjInitCompress();
		if (!_handle) {
			std::cerr << "Failed to initialize TurboJPEG: " << tjGetErrorStr() << std::endl;
			std::exit(1);
		}

		// Pre-allocate buffer to avoid tjAlloc/tjFree overhead
		_bufferSize = 2 * 1024 * 1024; // 2MB - should handle most images
		_buffer = static_cast<unsigned char*>(tjAlloc(_bufferSize));
		if (!_buffer) {
			std::cerr << "Failed to allocate JPEG buffer" << std::endl;
			tjDestroy(_handle);
			std::exit(1);
		}
	}
	TurboJpegCompressor::~TurboJpegCompressor() noexcept {
		if (_buffer) {
			tjFree(_buffer);
			_buffer = nullptr;
		}
		if (_handle) {
			tjDestroy(_handle);
			_handle = nullptr;
		}
	}

	// Your original logic with minimal optimizations
	std::string TurboJpegCompressor::compress(const cv::Mat& image, int quality) {
		if (image.empty()) {
			std::cerr << "Error: Input image is empty!" << std::endl;
			return "";
		}
		int pixelFormat;
		int subsampling;

		if (image.type() == CV_8UC1) {
			pixelFormat = TJPF_GRAY;
			subsampling = TJSAMP_GRAY;
		}
		else if (image.type() == CV_8UC3) {
			pixelFormat = TJPF_BGR;
			subsampling = TJSAMP_420;
		}
		else {
			std::cerr << "Error: Unsupported image format!" << std::endl;
			return "";
		}

		// Try with pre-allocated buffer first (fastest path)
		unsigned long jpegSize = _bufferSize;
		if (tjCompress2(_handle, image.data, image.cols, 0, image.rows, pixelFormat,
			&_buffer, &jpegSize, subsampling, quality,
			TJFLAG_FASTDCT | TJFLAG_FASTUPSAMPLE) >= 0) {
			return std::string(reinterpret_cast<char*>(_buffer), jpegSize);
		}

		// Fallback to dynamic allocation (your original method)
		unsigned char* jpegBuf = nullptr;
		jpegSize = 0;
		if (tjCompress2(_handle, image.data, image.cols, 0, image.rows, pixelFormat,
			&jpegBuf, &jpegSize, subsampling, quality,
			TJFLAG_FASTDCT | TJFLAG_FASTUPSAMPLE) < 0) {
			std::cerr << "JPEG compression failed: " << tjGetErrorStr() << std::endl;
			return "";
		}

		std::string jpegString(reinterpret_cast<char*>(jpegBuf), jpegSize);
		tjFree(jpegBuf);

		// If we needed more space, expand our buffer for next time
		if (jpegSize > _bufferSize) {
			tjFree(_buffer);
			_bufferSize = jpegSize * 2;
			_buffer = static_cast<unsigned char*>(tjAlloc(_bufferSize));
		}

		return jpegString;
	}
	// ── NvJpegCompressor: GPU-accelerated JPEG (NVIDIA only) ──

	NvJpegCompressor::NvJpegCompressor() {
		if (!anscv_vendor_gate::IsNvidiaGpuAvailable()) return;

		auto handle = reinterpret_cast<nvjpegHandle_t*>(&_nvHandle);
		auto state  = reinterpret_cast<nvjpegEncoderState_t*>(&_encState);
		auto params = reinterpret_cast<nvjpegEncoderParams_t*>(&_encParams);
		auto stream = reinterpret_cast<cudaStream_t*>(&_stream);

		if (nvjpegCreateSimple(handle) != NVJPEG_STATUS_SUCCESS) return;
		if (nvjpegEncoderStateCreate(*handle, state, nullptr) != NVJPEG_STATUS_SUCCESS) { cleanup(); return; }
		if (nvjpegEncoderParamsCreate(*handle, params, nullptr) != NVJPEG_STATUS_SUCCESS) { cleanup(); return; }
		if (cudaStreamCreate(stream) != cudaSuccess) { cleanup(); return; }

		_valid = true;
	}

	NvJpegCompressor::~NvJpegCompressor() noexcept { cleanup(); }

	void NvJpegCompressor::cleanup() noexcept {
		if (_gpuBuffer) { cudaFree(_gpuBuffer); _gpuBuffer = nullptr; _gpuBufferSize = 0; }
		if (_stream)    { cudaStreamDestroy(reinterpret_cast<cudaStream_t>(_stream)); _stream = nullptr; }
		if (_encParams) { nvjpegEncoderParamsDestroy(reinterpret_cast<nvjpegEncoderParams_t>(_encParams)); _encParams = nullptr; }
		if (_encState)  { nvjpegEncoderStateDestroy(reinterpret_cast<nvjpegEncoderState_t>(_encState)); _encState = nullptr; }
		if (_nvHandle)  { nvjpegDestroy(reinterpret_cast<nvjpegHandle_t>(_nvHandle)); _nvHandle = nullptr; }
		_valid = false;
	}

	std::string NvJpegCompressor::compress(const cv::Mat& image, int quality) {
		if (!_valid || image.empty()) return "";

		// Only support BGR 8-bit (the common path)
		if (image.type() != CV_8UC3) return "";

		auto handle = reinterpret_cast<nvjpegHandle_t>(_nvHandle);
		auto state  = reinterpret_cast<nvjpegEncoderState_t>(_encState);
		auto params = reinterpret_cast<nvjpegEncoderParams_t>(_encParams);
		auto stream = reinterpret_cast<cudaStream_t>(_stream);

		int width  = image.cols;
		int height = image.rows;
		size_t imageSize = static_cast<size_t>(width) * height * 3;

		// Reuse GPU buffer, grow if needed
		if (imageSize > _gpuBufferSize) {
			if (_gpuBuffer) cudaFree(_gpuBuffer);
			// Allocate with 25% headroom to reduce reallocations
			_gpuBufferSize = imageSize + imageSize / 4;
			if (cudaMalloc(&_gpuBuffer, _gpuBufferSize) != cudaSuccess) {
				_gpuBuffer = nullptr;
				_gpuBufferSize = 0;
				return "";
			}
		}

		// Upload interleaved BGR to GPU
		if (cudaMemcpy(_gpuBuffer, image.data, imageSize, cudaMemcpyHostToDevice) != cudaSuccess)
			return "";

		// Configure encoder
		if (nvjpegEncoderParamsSetQuality(params, quality, stream) != NVJPEG_STATUS_SUCCESS) return "";
		if (nvjpegEncoderParamsSetSamplingFactors(params, NVJPEG_CSS_420, stream) != NVJPEG_STATUS_SUCCESS) return "";
		if (nvjpegEncoderParamsSetOptimizedHuffman(params, 1, stream) != NVJPEG_STATUS_SUCCESS) return "";

		// Set up nvjpegImage_t for interleaved BGR
		nvjpegImage_t nv_image = {};
		nv_image.channel[0] = _gpuBuffer;
		nv_image.pitch[0]   = static_cast<unsigned int>(width * 3);

		// Encode
		if (nvjpegEncodeImage(handle, state, params, &nv_image,
				NVJPEG_INPUT_BGRI, width, height, stream) != NVJPEG_STATUS_SUCCESS)
			return "";

		// Get compressed size
		size_t jpegSize = 0;
		if (nvjpegEncodeRetrieveBitstream(handle, state, nullptr, &jpegSize, stream) != NVJPEG_STATUS_SUCCESS)
			return "";

		// Retrieve bitstream
		std::string jpegStr(jpegSize, '\0');
		if (nvjpegEncodeRetrieveBitstream(handle, state,
				reinterpret_cast<unsigned char*>(jpegStr.data()), &jpegSize, stream) != NVJPEG_STATUS_SUCCESS)
			return "";

		if (cudaStreamSynchronize(stream) != cudaSuccess)
			return "";

		jpegStr.resize(jpegSize);
		return jpegStr;
	}

	// ── NvJpegPool: VRAM-scaled pool of GPU encoders, lock-free acquire ──

	int NvJpegPool::detectPoolSize() {
		// Query VRAM via CUDA and scale: 1 encoder per 2 GB, min 1
		int deviceCount = 0;
		if (cudaGetDeviceCount(&deviceCount) != cudaSuccess || deviceCount <= 0)
			return 0;

		cudaDeviceProp prop{};
		if (cudaGetDeviceProperties(&prop, 0) != cudaSuccess)
			return 0;

		size_t vramGB = prop.totalGlobalMem / (1024ULL * 1024ULL * 1024ULL);
		int pool = static_cast<int>(vramGB / 2);
		if (pool < 1) pool = 1;

		ANS_DBG("ANSCV", "NvJpegPool: GPU=%s, VRAM=%zuGB, poolSize=%d", prop.name, vramGB, pool);
		return pool;
	}

	NvJpegPool& NvJpegPool::Instance() {
		static NvJpegPool instance;
		return instance;
	}

	NvJpegPool::NvJpegPool() {
		if (!anscv_vendor_gate::IsNvidiaGpuAvailable()) return;

		_poolSize = detectPoolSize();
		if (_poolSize <= 0) return;

		_encoders.resize(_poolSize);
		_inUse = std::make_unique<std::atomic<bool>[]>(_poolSize);

		for (int i = 0; i < _poolSize; ++i) {
			_inUse[i].store(false, std::memory_order_relaxed);
			_encoders[i] = std::make_unique<NvJpegCompressor>();
			if (!_encoders[i]->isValid()) {
				_encoders[i].reset();
			}
		}
		// Pool is available if at least one encoder initialized
		for (int i = 0; i < _poolSize; ++i) {
			if (_encoders[i]) { _available = true; break; }
		}

		ANS_DBG("ANSCV", "NvJpegPool: initialized %d encoder(s), available=%d", _poolSize, _available ? 1 : 0);
	}

	std::string NvJpegPool::tryCompress(const cv::Mat& image, int quality) {
		if (!_available) return "";

		// Lock-free slot acquisition: try each slot with compare_exchange
		for (int i = 0; i < _poolSize; ++i) {
			if (!_encoders[i]) continue;
			bool expected = false;
			if (_inUse[i].compare_exchange_strong(expected, true, std::memory_order_acquire)) {
				std::string result = _encoders[i]->compress(image, quality);
				_inUse[i].store(false, std::memory_order_release);
				return result;  // may be empty on encode failure — caller falls back
			}
		}
		return "";  // All slots busy — caller falls back to TurboJPEG
	}

	// ── Unified entry point: nvJPEG pool on NVIDIA, TurboJPEG otherwise ──

	std::string CompressJpegToString(const cv::Mat& image, int quality) {
		// Try GPU path first (returns "" if non-NVIDIA, pool full, or encode fails)
		//std::string result = NvJpegPool::Instance().tryCompress(image, quality);
		//if (!result.empty()) return result;

		// CPU fallback — always available
		static thread_local TurboJpegCompressor compressor;
		return compressor.compress(image, quality);
	}
	void ANSOPENCV::CheckLicense() {
		try {
			// Check once globally
			std::call_once(ansCVLicenseOnceFlag, [this]() {
				VerifyGlobalLicense(_licenseKey);
				});

			// Update this instance's local license flag
			_licenseValid = ansCVLicenceValid;
		}
		catch (const std::exception& e) {
			this->_logger.LogFatal("ANSOPENCV::CheckLicense. Error:", e.what(), __FILE__, __LINE__);
		}
	}
	bool ANSOPENCV::Init(std::string licenseKey) {
		if (ansCVLicenceValid) {  // Global check
			_licenseValid = true;
			return true;
		}

		{
			std::lock_guard<std::recursive_mutex> lock(_mutex);
			if (_licenseKey.empty()) {
				_licenseKey = licenseKey;
			}
			else if (_licenseKey != licenseKey) {
				std::cerr << "Warning: Attempt to reset license key with a different value!" << std::endl;
			}
		}

		CheckLicense();
		return _licenseValid;
	}
	//std::string ANSOPENCV::EncodeJpegString(const cv::Mat& img, int quality) {
	//	std::lock_guard<std::recursive_mutex> lock(_mutex);
	//	tjhandle _jpegCompressor = nullptr;
	//	unsigned char* jpegBuf = nullptr;
	//	try {
	//		_jpegCompressor = tjInitCompress();
	//		if (!_jpegCompressor) {
	//			this->_logger.LogError("ANSOPENCV::EncodeJpegString. Failed to initialize TurboJPEG compressor.", tjGetErrorStr(), __FILE__, __LINE__);
	//			return "";
	//		}
	//		int maxBufferSize = img.cols * img.rows * 3;
	//		jpegBuf = new unsigned char[maxBufferSize];  // Pre-allocated buffer
	//		long unsigned int jpegSize = maxBufferSize;  // Size of the JPEG image (output)
	//		int subsamp = TJSAMP_444;  // Chroma subsampling: TJSAMP_444, TJSAMP_422, TJSAMP_420, etc.
	//		int pixelFormat = img.channels() == 3 ? TJPF_BGR : TJPF_GRAY;  // Pixel format based on channels

	//		// Compress the image into the pre-allocated buffer
	//		int result = tjCompress2(_jpegCompressor, img.data, img.cols, 0, img.rows, pixelFormat,
	//			&jpegBuf, &jpegSize, subsamp, quality, TJFLAG_FASTDCT);

	//		// Handle compression errors
	//		if (result != 0) {
	//			this->_logger.LogError("ANSOPENCV::EncodeJpegString. Compression error:", tjGetErrorStr(), __FILE__, __LINE__);
	//			if (jpegBuf) {
	//				tjFree(jpegBuf);  // Free the buffer if allocated
	//			}
	//			tjDestroy(_jpegCompressor);  // Destroy the TurboJPEG compressor
	//			return "";
	//		}
	//		// Create a string from the JPEG buffer
	//		std::string jpegString(reinterpret_cast<char*>(jpegBuf), jpegSize);
	//		// Clean up resources
	//		tjFree(jpegBuf);
	//		tjDestroy(_jpegCompressor);
	//		return jpegString;
	//	}
	//	catch (std::exception& e) {
	//		this->_logger.LogError("ANSOPENCV::EncodeJpegString:", e.what(), __FILE__, __LINE__);
	//		// Clean up resources in case of an exception
	//		if (jpegBuf) {
	//			tjFree(jpegBuf);
	//		}
	//		if (_jpegCompressor) {
	//			tjDestroy(_jpegCompressor);
	//		}
	//	}
	//	// Return an empty string in case of failure
	//	return "";
	//}
	//std::string ANSOPENCV::EncodeJpegString(const cv::Mat& img, int quality) {
	//	std::lock_guard<std::recursive_mutex> lock(_mutex);
	//	tjhandle _jpegCompressor = nullptr;
	//	unsigned char* jpegBuf = nullptr;

	//	try {
	//		// Validate input
	//		if (img.empty()) {
	//			this->_logger.LogError("ANSOPENCV::EncodeJpegString. Empty image.", "", __FILE__, __LINE__);
	//			return "";
	//		}

	//		// Determine pixel format
	//		int pixelFormat;
	//		if (img.channels() == 1) {
	//			pixelFormat = TJPF_GRAY;
	//		}
	//		else if (img.channels() == 3) {
	//			pixelFormat = TJPF_BGR;
	//		}
	//		else if (img.channels() == 4) {
	//			pixelFormat = TJPF_BGRA;
	//		}
	//		else {
	//			this->_logger.LogError("ANSOPENCV::EncodeJpegString. Unsupported channel count: " + std::to_string(img.channels()), "", __FILE__, __LINE__);
	//			return "";
	//		}

	//		_jpegCompressor = tjInitCompress();
	//		if (!_jpegCompressor) {
	//			this->_logger.LogError("ANSOPENCV::EncodeJpegString. Failed to initialize TurboJPEG compressor.", tjGetErrorStr(), __FILE__, __LINE__);
	//			return "";
	//		}

	//		// Correct buffer size based on actual channels
	//		unsigned long maxBufferSize = tjBufSize(img.cols, img.rows, TJSAMP_444);
	//		jpegBuf = new unsigned char[maxBufferSize];
	//		unsigned long jpegSize = maxBufferSize;

	//		int subsamp = (pixelFormat == TJPF_GRAY) ? TJSAMP_GRAY : TJSAMP_444;

	//		int pitch = img.step[0];
	//		int result = tjCompress2(_jpegCompressor, img.data, img.cols, pitch, img.rows, pixelFormat,
	//			&jpegBuf, &jpegSize, subsamp, quality, TJFLAG_FASTDCT);

	//		if (result != 0) {
	//			this->_logger.LogError("ANSOPENCV::EncodeJpegString. Compression error:", tjGetErrorStr(), __FILE__, __LINE__);
	//			tjFree(jpegBuf);
	//			tjDestroy(_jpegCompressor);
	//			return "";
	//		}

	//		std::string jpegString(reinterpret_cast<char*>(jpegBuf), jpegSize);

	//		tjFree(jpegBuf);
	//		tjDestroy(_jpegCompressor);

	//		return jpegString;
	//	}
	//	catch (std::exception& e) {
	//		this->_logger.LogError("ANSOPENCV::EncodeJpegString:", e.what(), __FILE__, __LINE__);
	//		if (jpegBuf) tjFree(jpegBuf);
	//		if (_jpegCompressor) tjDestroy(_jpegCompressor);
	//	}

	//	return "";
	//}

	std::string ANSOPENCV::EncodeJpegString(const cv::Mat& img, int quality) {
		// Lock-free: each call creates its own tjInitCompress handle and local
		// buffers. No shared mutable state — safe to run concurrently.
		tjhandle _jpegCompressor = nullptr;
		unsigned char* jpegBuf = nullptr;

		try {
			// Validate input
			if (img.empty()) {
				this->_logger.LogError("ANSOPENCV::EncodeJpegString. Empty image.", "", __FILE__, __LINE__);
				return "";
			}

			// Ensure continuous memory layout
			cv::Mat continuous_img;
			if (!img.isContinuous()) {
				continuous_img = img.clone();
			}
			else {
				continuous_img = img;
			}

			// Determine pixel format
			int pixelFormat;
			int subsamp;

			if (continuous_img.channels() == 1) {
				pixelFormat = TJPF_GRAY;
				subsamp = TJSAMP_GRAY;
			}
			else if (continuous_img.channels() == 3) {
				pixelFormat = TJPF_BGR;
				subsamp = TJSAMP_444;
			}
			else if (continuous_img.channels() == 4) {
				pixelFormat = TJPF_BGRA;
				subsamp = TJSAMP_444;
			}
			else {
				this->_logger.LogError("ANSOPENCV::EncodeJpegString. Unsupported channel count: " +
					std::to_string(continuous_img.channels()), "", __FILE__, __LINE__);
				return "";
			}

			_jpegCompressor = tjInitCompress();
			if (!_jpegCompressor) {
				this->_logger.LogError("ANSOPENCV::EncodeJpegString. Failed to initialize TurboJPEG compressor.",
					tjGetErrorStr(), __FILE__, __LINE__);
				return "";
			}

			// Use tjAlloc instead of new[] - this is critical for TurboJPEG
			unsigned long maxBufferSize = tjBufSize(continuous_img.cols, continuous_img.rows, subsamp);
			jpegBuf = tjAlloc(maxBufferSize);
			if (!jpegBuf) {
				this->_logger.LogError("ANSOPENCV::EncodeJpegString. Failed to allocate JPEG buffer.", "", __FILE__, __LINE__);
				tjDestroy(_jpegCompressor);
				return "";
			}

			unsigned long jpegSize = maxBufferSize;

			// Use pitch = 0 since we ensured continuous memory
			int result = tjCompress2(_jpegCompressor, continuous_img.data, continuous_img.cols, 0,
				continuous_img.rows, pixelFormat,
				&jpegBuf, &jpegSize, subsamp, quality, TJFLAG_FASTDCT);

			if (result != 0) {
				this->_logger.LogError("ANSOPENCV::EncodeJpegString. Compression error (format=" +
					std::to_string(pixelFormat) + ", subsamp=" + std::to_string(subsamp) +
					", quality=" + std::to_string(quality) + "):",
					tjGetErrorStr(), __FILE__, __LINE__);
				tjFree(jpegBuf);
				tjDestroy(_jpegCompressor);
				return "";
			}

			// Create string from compressed buffer
			std::string jpegString(reinterpret_cast<char*>(jpegBuf), jpegSize);

			// Clean up
			tjFree(jpegBuf);
			tjDestroy(_jpegCompressor);

			return jpegString;
		}
		catch (const std::exception& e) {
			this->_logger.LogError("ANSOPENCV::EncodeJpegString:", e.what(), __FILE__, __LINE__);
			if (jpegBuf) tjFree(jpegBuf);
			if (_jpegCompressor) tjDestroy(_jpegCompressor);
		}
		catch (...) {
			this->_logger.LogError("ANSOPENCV::EncodeJpegString: Unknown exception", "", __FILE__, __LINE__);
			if (jpegBuf) tjFree(jpegBuf);
			if (_jpegCompressor) tjDestroy(_jpegCompressor);
		}

		return "";
	}
	std::string ANSOPENCV::MatToBinaryData(const cv::Mat& image) {
		// Lock-free: forwards to EncodeJpegString which is itself lock-free.
		// Check if the image is empty or has invalid data
		if (image.empty() || !image.data || !image.u) {
			return "";
		}
		try {
			// Encode the image to a memory buffer
			return EncodeJpegString(image, 100);
		}
		catch (const std::exception& e) {
			this->_logger.LogFatal("ANSOPENCV::MatToBinaryData. Exception occurred:", e.what(), __FILE__, __LINE__);
		}
		catch (...) {
			this->_logger.LogFatal("ANSWEBCAMPlayer::MatToBinaryData.", "Unknown exception occurred.", __FILE__, __LINE__);
		}

		// Return an empty string in case of failure
		return "";
	}
	void ANSOPENCV::ImageResize(const cv::Mat& inputFrame, int width, int height, cv::Mat& outputFrame) {
		// Lock-free: _licenseValid is std::atomic<bool>, cv::resize is reentrant,
		// all Mats here are local. Safe to call concurrently across threads.

		if (!_licenseValid) {
			outputFrame = inputFrame;
			return;
		}

		if (inputFrame.empty()) {
			outputFrame = inputFrame;
			return;
		}

		// Validate dimensions
		if (width <= 0 || height <= 0) {
			std::cerr << "Error: Invalid dimensions in ImageResize (" << width << "x" << height << ")" << std::endl;
			outputFrame = inputFrame;
			return;
		}

		// Check if resize is actually needed
		if (inputFrame.cols == width && inputFrame.rows == height) {
			outputFrame = inputFrame;
			return;
		}

		try {
			// Choose interpolation based on scaling direction
			int interpolation;
			const double scaleX = static_cast<double>(width) / inputFrame.cols;
			const double scaleY = static_cast<double>(height) / inputFrame.rows;
			const double scale = min(scaleX, scaleY);

			if (scale < 1.0) {
				// Downscaling - use INTER_AREA (best quality and fastest)
				interpolation = cv::INTER_AREA;
			}
			else {
				// Upscaling - use INTER_LINEAR (good balance)
				interpolation = cv::INTER_LINEAR;
			}

			cv::resize(inputFrame, outputFrame, cv::Size(width, height), 0, 0, interpolation);
		}
		catch (const cv::Exception& e) {
			std::cerr << "OpenCV exception in ImageResize: " << e.what() << std::endl;
			outputFrame = inputFrame;
		}
		catch (const std::exception& e) {
			std::cerr << "Exception in ImageResize: " << e.what() << std::endl;
			outputFrame = inputFrame;
		}
		catch (...) {
			std::cerr << "Unknown exception in ImageResize" << std::endl;
			outputFrame = inputFrame;
		}
	}
	void ANSOPENCV::ImageResizeWithRatio(const cv::Mat& inputFrame, int width, cv::Mat& outputFrame)
	{
		// Lock-free: _licenseValid is std::atomic<bool>, cv::resize is reentrant,
		// all Mats here are local. Safe to call concurrently across threads.
		if (!_licenseValid) {
			outputFrame = inputFrame;  // Shallow copy (fast)
			return;
		}
		// Validate inputs outside mutex
		if (inputFrame.empty() || width <= 0) {
			outputFrame = inputFrame;
			return;
		}

		// Check if resize is needed
		if (inputFrame.cols == width) {
			outputFrame = inputFrame;  // Already correct width, no resize needed
			return;
		}

		try {
			// Calculate new height maintaining aspect ratio
			double aspectRatio = static_cast<double>(inputFrame.cols) / inputFrame.rows;
			int height = static_cast<int>(std::round(width / aspectRatio));

			// Validate calculated height
			if (height <= 0) {
				std::cerr << "Error: Calculated height is invalid in ImageResizeWithRatio! "
					<< "Width: " << width << ", AspectRatio: " << aspectRatio << std::endl;
				outputFrame = inputFrame;
				return;
			}

			// Choose interpolation based on scaling direction
			int interpolation;
			if (width < inputFrame.cols) {
				// Downscaling: INTER_AREA is best (good quality, fast)
				interpolation = cv::INTER_AREA;
			}
			else {
				// Upscaling: INTER_LINEAR is good enough and fast
				interpolation = cv::INTER_LINEAR;
			}

			cv::resize(inputFrame, outputFrame, cv::Size(width, height), 0, 0, interpolation);
		}
		catch (const cv::Exception& e) {
			std::cerr << "OpenCV error in ImageResizeWithRatio: " << e.what() << std::endl;
			outputFrame = inputFrame;  // Shallow copy fallback
		}
		catch (...) {
			std::cerr << "Unknown error in ImageResizeWithRatio!" << std::endl;
			outputFrame = inputFrame;  // Shallow copy fallback
		}
	}
	cv::Mat ANSOPENCV::BlurObjects(const cv::Mat& image, const std::vector<cv::Rect>& objects) {
		// Lock-free: operates on local Mats only. Safe across threads.
		// Check for valid license and empty input
		if (!_licenseValid || image.empty()) return image;

		// Create a copy of the original image to apply the blurring
		cv::Mat outputImage = image.clone();

		// Define blur parameters
		const int blurKernelSize = 45;  // Kernel size for blurring
		cv::Size kernelSize(blurKernelSize, blurKernelSize);

		// Apply blur to each object ROI
		for (const auto& obj : objects) {
			// Ensure the ROI is within the image bounds
			cv::Rect boundedRect = obj & cv::Rect(0, 0, image.cols, image.rows);
			if (boundedRect.area() > 0) {  // Check if the bounded rect is valid
				cv::GaussianBlur(outputImage(boundedRect), outputImage(boundedRect), kernelSize, 0);
			}
		}

		return outputImage;
	}
	cv::Mat ANSOPENCV::BlurBackground(const cv::Mat& image, const std::vector<cv::Rect>& objects) {
		// Lock-free: operates on local Mats only. Safe across threads.
		// Check for valid license and empty input
		if (!_licenseValid || image.empty()) return image;

		// Define blur parameters
		const int blurKernelSize = 45;  // Kernel size for blurring
		cv::Size kernelSize(blurKernelSize, blurKernelSize);

		// Blur the entire image
		cv::Mat blurredImage;
		cv::GaussianBlur(image, blurredImage, kernelSize, 0);

		// Copy each ROI from the original image onto the blurred image
		for (const auto& obj : objects) {
			// Validate that the ROI is within image bounds
			cv::Rect boundedRect = obj & cv::Rect(0, 0, image.cols, image.rows);
			if (boundedRect.area() > 0) {  // Proceed if the rect is valid
				image(boundedRect).copyTo(blurredImage(boundedRect));
			}
		}

		return blurredImage;
	}
	cv::Mat ANSOPENCV::ToGray(const cv::Mat& image) {
		// Lock-free: operates on local Mats only. Safe across threads.
		// Check for valid license
		if (!_licenseValid) return image;

		// Return the original if the image is empty
		if (image.empty()) return image;

		// Prepare the grayscale output
		cv::Mat grayMat;

		// Convert based on channel count
		switch (image.channels()) {
		case 3:
			cv::cvtColor(image, grayMat, cv::COLOR_BGR2GRAY);
			break;
		case 4:
			cv::cvtColor(image, grayMat, cv::COLOR_BGRA2GRAY);
			break;
		case 1:
			grayMat = image.clone(); // Clone to ensure a copy is returned
			break;
		default:
			// Unsupported number of channels, return the original
			std::cerr << "Error: Unsupported image format. Expected 1, 3, or 4 channels." << std::endl;
			return image;
		}

		return grayMat;
	}
	cv::Mat ANSOPENCV::ImageDenoise(const cv::Mat& image) {
		// Lock-free: operates on local Mats only. Safe across threads.

		if (!_licenseValid || image.empty()) {
			return image;
		}

		cv::Mat denoised_image;

		// Bilateral filter: 10-50x faster than NLMeans
		const int diameter = 9;        // Kernel size
		const double sigmaColor = 75;  // Color space sigma
		const double sigmaSpace = 75;  // Coordinate space sigma

		cv::bilateralFilter(image, denoised_image, diameter, sigmaColor, sigmaSpace);

		return denoised_image;
	}
	cv::Mat ANSOPENCV::ImageCrop(const cv::Mat& inputImage, const cv::Rect& resizeROI, int originalImageSize) {
		// Lock-free: operates on local Mats only. Safe across threads.

		// License validation
		if (!_licenseValid) {
			std::cerr << "Error: License is not valid in ImageCrop." << std::endl;
			return cv::Mat();  // Return empty Mat explicitly
		}

		// Early validation checks
		if (inputImage.empty()) {
			std::cerr << "Error: Input image is empty!" << std::endl;
			return cv::Mat();
		}

		if (resizeROI.width <= 0 || resizeROI.height <= 0) {
			std::cerr << "Error: Invalid ROI size! Width=" << resizeROI.width
				<< ", Height=" << resizeROI.height << std::endl;
			return cv::Mat();
		}

		try {
			const int originalWidth = inputImage.cols;
			const int originalHeight = inputImage.rows;

			// Scale ROI if originalImageSize is provided and valid
			cv::Rect roi = resizeROI;

			if (originalImageSize > 0 && originalImageSize != originalWidth) {
				const double scale = static_cast<double>(originalWidth) / originalImageSize;

				// Use std::round for more accurate scaling
				roi.x = static_cast<int>(std::round(resizeROI.x * scale));
				roi.y = static_cast<int>(std::round(resizeROI.y * scale));
				roi.width = static_cast<int>(std::round(resizeROI.width * scale));
				roi.height = static_cast<int>(std::round(resizeROI.height * scale));

				// Ensure dimensions are still positive after rounding
				if (roi.width <= 0 || roi.height <= 0) {
					std::cerr << "Error: Scaled ROI has invalid dimensions!" << std::endl;
					return cv::Mat();
				}
			}

			// Pre-calculate image bounds
			const cv::Rect imageBounds(0, 0, originalWidth, originalHeight);

			// Check if ROI is within bounds (optimized check)
			if (roi.x < 0 || roi.y < 0 ||
				roi.x + roi.width > originalWidth ||
				roi.y + roi.height > originalHeight) {

				std::cerr << "Error: ROI exceeds image boundaries! "
					<< "ROI=[" << roi.x << "," << roi.y << ","
					<< roi.width << "," << roi.height << "], "
					<< "Image=[" << originalWidth << "x" << originalHeight << "]" << std::endl;
				return cv::Mat();
			}

			// Crop and return (shallow copy - fast)
			//return inputImage(roi);
			return inputImage(roi).clone();

		}
		catch (const cv::Exception& e) {
			std::cerr << "OpenCV exception in ANSOPENCV::ImageCrop: " << e.what() << std::endl;
			return cv::Mat();
		}
		catch (const std::exception& e) {
			std::cerr << "Exception in ANSOPENCV::ImageCrop: " << e.what() << std::endl;
			return cv::Mat();
		}
	}
	cv::Mat ANSOPENCV::ImageRepair(const cv::Mat& image) {
		// Lock-free: operates on local Mats only. Safe across threads.

		if (!_licenseValid || image.empty()) {
			return image;
		}

		try {
			cv::Mat grayImage;
			if (image.channels() == 1) {
				grayImage = image;
			}
			else {
				cv::cvtColor(image, grayImage, cv::COLOR_BGR2GRAY);
			}

			// Use more aggressive Canny thresholds to detect fewer edges
			cv::Mat edges;
			cv::Canny(grayImage, edges, 80, 200, 3, false);  // Higher thresholds = fewer edges

			cv::Mat mask;
			cv::bitwise_not(edges, mask);

			cv::Mat inpaintedImage;
			const int inpaintRadius = 2;  // Smaller radius = faster
			cv::inpaint(image, mask, inpaintedImage, inpaintRadius, cv::INPAINT_NS);  // NS is faster

			return inpaintedImage;
		}
		catch (const cv::Exception& e) {
			std::cerr << "OpenCV exception in ImageRepairFast: " << e.what() << std::endl;
			return image;
		}
	}
	std::string ANSOPENCV::PatternMatches(cv::Mat& image, cv::Mat& templateImage, double threshold) {
		// Lock-free: operates on local Mats only. Safe across threads.

		std::vector<DetectionObject> detectedObjects;

		// Early validation
		if (!_licenseValid || image.empty() || templateImage.empty()) {
			return VectorDetectionToJsonString(detectedObjects);
		}

		// Validate template size
		if (templateImage.cols > image.cols || templateImage.rows > image.rows) {
			std::cerr << "Error: Template is larger than image in PatternMatches!" << std::endl;
			return VectorDetectionToJsonString(detectedObjects);
		}

		try {
			// Calculate result dimensions
			const int result_cols = image.cols - templateImage.cols + 1;
			const int result_rows = image.rows - templateImage.rows + 1;

			// Perform template matching
			cv::Mat result;
			cv::matchTemplate(image, templateImage, result, cv::TM_CCOEFF_NORMED);

			// TM_CCOEFF_NORMED already returns normalized values in [-1, 1]
			// So we don't need to normalize again (major performance gain)

			// Reserve space to avoid repeated allocations
			detectedObjects.reserve(100);  // Reasonable default

			// Use OpenCV's threshold function instead of manual iteration
			cv::Mat thresholdedResult;
			cv::threshold(result, thresholdedResult, threshold, 1.0, cv::THRESH_BINARY);

			// Find non-zero locations (matches above threshold)
			std::vector<cv::Point> locations;
			cv::findNonZero(thresholdedResult, locations);

			// Convert to DetectionObjects
			const int templateWidth = templateImage.cols;
			const int templateHeight = templateImage.rows;

			for (const auto& loc : locations) {
				DetectionObject detectionObject;
				detectionObject.classId = 0;
				detectionObject.className = "Matched Object";
				detectionObject.confidence = result.at<float>(loc.y, loc.x);  // Actual confidence
				detectionObject.box = cv::Rect(loc.x, loc.y, templateWidth, templateHeight);
				detectedObjects.push_back(detectionObject);
			}

			// Apply Non-Maximum Suppression if we have matches
			if (!detectedObjects.empty()) {
				NonMaximumSuppression(detectedObjects, 0.5);
			}

			return VectorDetectionToJsonString(detectedObjects);
		}
		catch (const cv::Exception& e) {
			std::cerr << "OpenCV exception in PatternMatches: " << e.what() << std::endl;
			return VectorDetectionToJsonString(detectedObjects);
		}
	}
	std::string ANSOPENCV::QRDecoder(const cv::Mat& image) {
		// Lock-free: operates on local Mats only. Safe across threads.

		if (!_licenseValid || image.empty()) {
			return "";
		}

		try {
			// Convert to grayscale efficiently
			cv::Mat grayImage;
			if (image.channels() == 1) {
				grayImage = image;  // Already grayscale, no conversion needed
			}
			else if (image.channels() == 3) {
				cv::cvtColor(image, grayImage, cv::COLOR_BGR2GRAY);
			}
			else if (image.channels() == 4) {
				cv::cvtColor(image, grayImage, cv::COLOR_BGRA2GRAY);
			}
			else {
				std::cerr << "Error: Unsupported image format in QRDecoder" << std::endl;
				return "";
			}

			const int width = grayImage.cols;
			const int height = grayImage.rows;

			// Create ZXing image view
			const ZXing::ImageView barcodeImage(grayImage.data, width, height,
				ZXing::ImageFormat::Lum);

			// Configure options (keep Any format for flexibility)
			const auto options = ZXing::ReaderOptions()
				.setFormats(ZXing::BarcodeFormat::Any)
				.setTryHarder(false)  // Set false for faster decoding
				.setTryRotate(false); // Set false if rotation not needed

			// Decode barcodes
			const auto barcodes = ZXing::ReadBarcodes(barcodeImage, options);

			// Early exit if no barcodes found
			if (barcodes.empty()) {
				return "";
			}

			// Reserve space for detected objects
			std::vector<DetectionObject> detectedObjects;
			detectedObjects.reserve(barcodes.size());

			// Process each barcode
			for (const auto& b : barcodes) {
				DetectionObject detectedObject;
				detectedObject.classId = static_cast<int>(b.format());
				detectedObject.className = b.text();
				detectedObject.confidence = 1.0;
				detectedObject.extraInfo = ZXing::ToString(b.format());

				// Calculate bounding box from position
				const ZXing::Position& pos = b.position();
				if (pos.size() == 4) {
					// Use std::minmax_element for cleaner code
					auto [minX, maxX] = std::minmax_element(
						pos.begin(), pos.end(),
						[](const auto& a, const auto& b) { return a.x < b.x; }
					);
					auto [minY, maxY] = std::minmax_element(
						pos.begin(), pos.end(),
						[](const auto& a, const auto& b) { return a.y < b.y; }
					);

					const int xmin = minX->x;
					const int ymin = minY->y;
					const int bwidth = maxX->x - xmin;
					const int bheight = maxY->y - ymin;

					// Validate bounding box
					if (bwidth > 0 && bheight > 0) {
						detectedObject.box = cv::Rect(xmin, ymin, bwidth, bheight);
					}
					else {
						std::cerr << "Warning: Invalid bounding box calculated for barcode" << std::endl;
					}
				}

				detectedObjects.push_back(std::move(detectedObject));
			}

			// Apply NMS only if multiple detections
			if (detectedObjects.size() > 1) {
				NonMaximumSuppression(detectedObjects, 0.5);
			}

			return VectorDetectionToJsonString(detectedObjects);
		}
		catch (const std::exception& e) {
			std::cerr << "Exception in QRDecoder: " << e.what() << std::endl;
			return "";
		}
	}
	std::string ANSOPENCV::QRDecoderWithBBox(const cv::Mat& image, int maxImageSize, const std::vector<cv::Rect>& bBox) {
		// Lock-free: operates on local Mats only. Safe across threads.

		if (!_licenseValid || image.empty()) {
			return "";
		}

		// Early fallback if no bounding boxes
		if (bBox.empty()) {
			return QRDecoder(image);
		}

		try {
			const int originalWidth = image.cols;
			const int originalHeight = image.rows;
			const double scaleFactor = (maxImageSize > 0) ?
				static_cast<double>(originalWidth) / maxImageSize : 1.0;

			// Pre-calculate inverse scale factor (avoid repeated division)
			const double invScaleFactor = 1.0 / scaleFactor;

			// Pre-calculate image bounds
			const cv::Rect imageBounds(0, 0, originalWidth, originalHeight);

			// Convert to grayscale once for all crops (major optimization)
			cv::Mat grayImage;
			if (image.channels() == 1) {
				grayImage = image;
			}
			else if (image.channels() == 3) {
				cv::cvtColor(image, grayImage, cv::COLOR_BGR2GRAY);
			}
			else if (image.channels() == 4) {
				cv::cvtColor(image, grayImage, cv::COLOR_BGRA2GRAY);
			}
			else {
				std::cerr << "Error: Unsupported image format in QRDecoderWithBBox" << std::endl;
				return "";
			}

			// Reserve space for detected objects
			std::vector<DetectionObject> detectedObjects;
			detectedObjects.reserve(bBox.size() * 2);  // Assume ~2 codes per box on average

			// Configure ZXing options once
			const auto options = ZXing::ReaderOptions()
				.setFormats(ZXing::BarcodeFormat::Any)
				.setTryHarder(false)
				.setTryRotate(false);

			// Process each bounding box
			for (const auto& rect : bBox) {
				// Scale the bounding box with rounding for better accuracy
				cv::Rect scaledRect;
				scaledRect.x = static_cast<int>(std::round(rect.x * scaleFactor));
				scaledRect.y = static_cast<int>(std::round(rect.y * scaleFactor));
				scaledRect.width = static_cast<int>(std::round(rect.width * scaleFactor));
				scaledRect.height = static_cast<int>(std::round(rect.height * scaleFactor));

				// Clamp to image bounds
				scaledRect &= imageBounds;

				// Skip invalid regions
				if (scaledRect.width <= 0 || scaledRect.height <= 0) {
					continue;
				}

				// Crop from grayscale image (no need to convert again)
				const cv::Mat croppedGray = grayImage(scaledRect);

				// Create ZXing image view
				const ZXing::ImageView barcodeImage(croppedGray.data,
					croppedGray.cols,
					croppedGray.rows,
					ZXing::ImageFormat::Lum);

				// Decode barcodes in this region
				const auto barcodes = ZXing::ReadBarcodes(barcodeImage, options);

				// Process each detected barcode
				for (const auto& b : barcodes) {
					DetectionObject detectedObject;
					detectedObject.classId = static_cast<int>(b.format());
					detectedObject.className = b.text();
					detectedObject.confidence = 1.0;
					detectedObject.extraInfo = ZXing::ToString(b.format());

					const ZXing::Position& pos = b.position();
					if (pos.size() == 4) {
						// Calculate bounding box using minmax_element
						auto [minX, maxX] = std::minmax_element(
							pos.begin(), pos.end(),
							[](const auto& a, const auto& b) { return a.x < b.x; }
						);
						auto [minY, maxY] = std::minmax_element(
							pos.begin(), pos.end(),
							[](const auto& a, const auto& b) { return a.y < b.y; }
						);

						const int xmin = minX->x;
						const int ymin = minY->y;
						const int bwidth = maxX->x - xmin;
						const int bheight = maxY->y - ymin;

						// Validate dimensions
						if (bwidth > 0 && bheight > 0) {
							// Transform to global coordinates with proper rounding
							detectedObject.box = cv::Rect(
								static_cast<int>(std::round((xmin + scaledRect.x) * invScaleFactor)),
								static_cast<int>(std::round((ymin + scaledRect.y) * invScaleFactor)),
								static_cast<int>(std::round(bwidth * invScaleFactor)),
								static_cast<int>(std::round(bheight * invScaleFactor))
							);

							detectedObjects.push_back(std::move(detectedObject));
						}
					}
				}
			}

			// Apply NMS only if multiple detections
			if (detectedObjects.size() > 1) {
				NonMaximumSuppression(detectedObjects, 0.5);
			}

			return VectorDetectionToJsonString(detectedObjects);
		}
		catch (const std::exception& e) {
			std::cerr << "Exception in QRDecoderWithBBox: " << e.what() << std::endl;
			return "";
		}
	}
	std::string ANSOPENCV::MatToBase64(const cv::Mat& image) {
		// Lock-free: _licenseValid is std::atomic<bool>, and CompressJpegToString
		// uses a thread_local TurboJpegCompressor. Safe across threads.

		if (!_licenseValid || image.empty()) {
			return "";
		}

		try {
			// Use CompressJpegToString with quality parameter
			// Note: Despite the function name "MatToBase64", this returns JPEG binary
			// If you actually need Base64, see the alternatives below
			std::string jpegString = CompressJpegToString(image, 100);

			if (jpegString.empty()) {
				std::cerr << "Error: JPEG compression failed in MatToBase64!" << std::endl;
				return "";
			}

			return jpegString;
		}
		catch (const cv::Exception& e) {
			std::cerr << "OpenCV exception in MatToBase64: " << e.what() << std::endl;
			return "";
		}
		catch (const std::exception& e) {
			std::cerr << "Exception in MatToBase64: " << e.what() << std::endl;
			return "";
		}
	}
	cv::Mat ANSOPENCV::ImageDarkEnhancement(const cv::Mat& img, double brightnessScaleFactor) {
		// Lock-free: operates on local Mats only. Safe across threads.

		if (!_licenseValid || img.empty()) {
			return img;  // Shallow copy (fast)
		}

		// Validate and set default scale factor
		if (brightnessScaleFactor <= 1.0) {
			brightnessScaleFactor = 1.5;
		}

		try {
			cv::Mat enhancedImage;

			// Direct scaling for single-channel images (faster)
			if (img.channels() == 1) {
				img.convertTo(enhancedImage, -1, brightnessScaleFactor, 0);
				return enhancedImage;
			}

			// For multi-channel images, use convertTo (much faster than split/merge)
			img.convertTo(enhancedImage, -1, brightnessScaleFactor, 0);

			return enhancedImage;
		}
		catch (const cv::Exception& e) {
			std::cerr << "OpenCV exception in ImageDarkEnhancement: " << e.what() << std::endl;
			return img;
		}
	}
	cv::Mat ANSOPENCV::ImageContrastEnhancement(const cv::Mat& src) {
		// Lock-free: operates on local Mats only. Safe across threads.
		double clipLimit = 2.0;
		if (!_licenseValid || src.empty()) {
			return src;
		}

		try {
			cv::Mat dst;
			cv::Ptr<cv::CLAHE> clahe = cv::createCLAHE(clipLimit, cv::Size(8, 8));

			if (src.channels() == 1) {
				clahe->apply(src, dst);
			}
			else if (src.channels() == 3) {
				cv::Mat yuv;
				cv::cvtColor(src, yuv, cv::COLOR_BGR2YUV);

				std::vector<cv::Mat> channels;
				cv::split(yuv, channels);

				clahe->apply(channels[0], channels[0]);

				cv::merge(channels, yuv);
				cv::cvtColor(yuv, dst, cv::COLOR_YUV2BGR);
			}
			else if (src.channels() == 4) {
				cv::Mat bgr;
				cv::cvtColor(src, bgr, cv::COLOR_BGRA2BGR);

				cv::Mat yuv;
				cv::cvtColor(bgr, yuv, cv::COLOR_BGR2YUV);

				std::vector<cv::Mat> channels;
				cv::split(yuv, channels);

				clahe->apply(channels[0], channels[0]);

				cv::merge(channels, yuv);
				cv::cvtColor(yuv, dst, cv::COLOR_YUV2BGR);
			}
			else {
				return src;
			}

			return dst;
		}
		catch (const cv::Exception& e) {
			std::cerr << "OpenCV exception in ImageContrastEnhancementCLAHE: " << e.what() << std::endl;
			return src;
		}
	}
	
	cv::Mat ANSOPENCV::ImageWhiteBalance(const cv::Mat& src) {
		// Lock-free: operates on local Mats only. Safe across threads.

		if (!_licenseValid || src.empty()) {
			return src;  // Shallow copy (fast)
		}

		// Only works with 3-channel (BGR) images
		if (src.channels() != 3) {
			std::cerr << "Warning: ImageWhiteBalance only works with 3-channel images" << std::endl;
			return src;
		}

		try {
			// Calculate mean for each channel directly (no split needed)
			cv::Scalar meanValues = cv::mean(src);

			double avgB = meanValues[0];
			double avgG = meanValues[1];
			double avgR = meanValues[2];

			// Calculate total average
			double avgTotal = (avgB + avgG + avgR) / 3.0;

			// Validate averages to prevent division by zero
			if (avgB < 1.0 || avgG < 1.0 || avgR < 1.0) {
				std::cerr << "Warning: Very dark image in ImageWhiteBalance, skipping" << std::endl;
				return src;
			}

			// Calculate scale factors
			double scaleB = avgTotal / avgB;
			double scaleG = avgTotal / avgG;
			double scaleR = avgTotal / avgR;

			// Apply scaling using convertTo with per-channel scales
			cv::Mat dst;
			std::vector<cv::Mat> channels(3);
			cv::split(src, channels);

			// Scale each channel in-place (8-bit, no float conversion needed)
			channels[0].convertTo(channels[0], CV_8U, scaleB);
			channels[1].convertTo(channels[1], CV_8U, scaleG);
			channels[2].convertTo(channels[2], CV_8U, scaleR);

			cv::merge(channels, dst);

			return dst;
		}
		catch (const cv::Exception& e) {
			std::cerr << "OpenCV exception in ImageWhiteBalance: " << e.what() << std::endl;
			return src;
		}
	}
	std::vector<cv::Rect> ANSOPENCV::GetBoundingBoxes(std::string strBBoxes) {
		// Lock-free: operates on local data only. Safe across threads.
		std::vector<cv::Rect> bBoxes;
		if (!_licenseValid) return bBoxes;

		bBoxes.clear();

		try {
			// Parse JSON string using nlohmann::json
			nlohmann::json j = nlohmann::json::parse(strBBoxes);

			// Check if "results" exists and is an array
			if (j.contains("results") && j["results"].is_array()) {
				// Iterate through the results array
				for (const auto& result : j["results"]) {
					// Extract values with type checking
					if (result.contains("x") && result.contains("y") &&
						result.contains("width") && result.contains("height")) {

						const auto x = result["x"].get<float>();
						const auto y = result["y"].get<float>();
						const auto width = result["width"].get<float>();
						const auto height = result["height"].get<float>();

						cv::Rect rectTemp;
						rectTemp.x = static_cast<int>(x);
						rectTemp.y = static_cast<int>(y);
						rectTemp.width = static_cast<int>(width);
						rectTemp.height = static_cast<int>(height);
						bBoxes.push_back(rectTemp);
					}
				}
			}
		}
		catch (const nlohmann::json::exception& e) {
			// Handle JSON parsing errors
			// You might want to log this error or handle it according to your needs
			// For now, we'll just return an empty vector
			bBoxes.clear();
		}

		return bBoxes;
	}
	
	cv::Mat ANSOPENCV::RotateImage(const cv::Mat& image, double angle) {
		// Lock-free: operates on local Mats only. Safe across threads.

		if (!_licenseValid || image.empty()) {
			return image;  // Shallow copy (fast)
		}

		try {
			// Normalize angle to [-180, 180] range
			angle = std::fmod(angle, 360.0);
			if (angle > 180.0) angle -= 360.0;
			if (angle < -180.0) angle += 360.0;

			// Fast path for common angles (exact 90-degree multiples)
			if (std::abs(angle) < 0.01) {
				return image;  // No rotation needed
			}

			// Use faster built-in functions for 90-degree rotations
			if (std::abs(angle - 90.0) < 0.01) {
				cv::Mat rotated;
				cv::rotate(image, rotated, cv::ROTATE_90_CLOCKWISE);
				return rotated;
			}
			if (std::abs(angle + 90.0) < 0.01 || std::abs(angle - 270.0) < 0.01) {
				cv::Mat rotated;
				cv::rotate(image, rotated, cv::ROTATE_90_COUNTERCLOCKWISE);
				return rotated;
			}
			if (std::abs(std::abs(angle) - 180.0) < 0.01) {
				cv::Mat rotated;
				cv::rotate(image, rotated, cv::ROTATE_180);
				return rotated;
			}

			// General rotation for arbitrary angles
			const cv::Point2f center(image.cols / 2.0f, image.rows / 2.0f);

			// Get rotation matrix
			cv::Mat rotationMatrix = cv::getRotationMatrix2D(center, angle, 1.0);

			// Calculate bounding box for rotated image
			const cv::Rect2f bbox = cv::RotatedRect(cv::Point2f(), image.size(), angle).boundingRect2f();

			// Adjust transformation matrix to center the rotated image
			rotationMatrix.at<double>(0, 2) += bbox.width / 2.0 - center.x;
			rotationMatrix.at<double>(1, 2) += bbox.height / 2.0 - center.y;

			// Perform rotation with optimized interpolation
			cv::Mat rotatedImage;
			cv::warpAffine(image, rotatedImage, rotationMatrix, bbox.size(),
				cv::INTER_LINEAR,  // Faster than INTER_CUBIC, still good quality
				cv::BORDER_CONSTANT,
				cv::Scalar(0, 0, 0));

			return rotatedImage;
		}
		catch (const cv::Exception& e) {
			std::cerr << "OpenCV exception in RotateImage: " << e.what() << std::endl;
			return image;
		}
	}

	cv::Mat ANSOPENCV::FlipImage(const cv::Mat& image, int flipCode) {
		// Lock-free: operates on local Mats only. Safe across threads.

		if (!_licenseValid || image.empty()) {
			return image;  // Shallow copy (fast)
		}

		// Validate flipCode
		// flipCode > 0: flip horizontally (mirror left-right)
		// flipCode = 0: flip vertically (mirror top-bottom)
		// flipCode < 0: flip both horizontally and vertically (180° rotation)
		if (flipCode < -1 || flipCode > 1) {
			std::cerr << "Warning: flipCode should be -1, 0, or 1. Using modulo." << std::endl;
			// Normalize to valid range
			if (flipCode > 1) flipCode = 1;
			if (flipCode < -1) flipCode = -1;
		}

		try {
			cv::Mat flippedImage;
			cv::flip(image, flippedImage, flipCode);
			return flippedImage;
		}
		catch (const cv::Exception& e) {
			std::cerr << "OpenCV exception in FlipImage: " << e.what() << std::endl;
			return image;
		}
	}

	cv::Mat ANSOPENCV::ShiftImage(const cv::Mat& image, int shiftX, int shiftY) {
		// Lock-free: operates on local Mats only. Safe across threads.
		if (!_licenseValid) return image;
		if (image.empty()) return image;

		// Calculate new dimensions to accommodate the shift
		int newWidth = image.cols + std::abs(shiftX);
		int newHeight = image.rows + std::abs(shiftY);

		// Adjust translation to account for negative shifts
		int translateX = (shiftX < 0) ? std::abs(shiftX) : 0;
		int translateY = (shiftY < 0) ? std::abs(shiftY) : 0;

		// Create translation matrix with adjusted offsets
		cv::Mat translationMatrix = (cv::Mat_<double>(2, 3) <<
			1, 0, translateX + shiftX,
			0, 1, translateY + shiftY);

		// Create output with expanded size
		cv::Mat shiftedImage;
		cv::Size newSize(newWidth, newHeight);
		cv::warpAffine(image, shiftedImage, translationMatrix, newSize,
			cv::INTER_LINEAR, cv::BORDER_CONSTANT, cv::Scalar(0, 0, 0));

		return shiftedImage;
	}

	cv::Mat ANSOPENCV::AddGaussianNoise(const cv::Mat& image, double mean, double stddev) {
		// Lock-free: operates on local Mats only. Safe across threads.

		if (!_licenseValid || image.empty()) {
			return image;
		}

		if (stddev < 0) {
			stddev = 0;
		}

		if (stddev == 0 && mean == 0) {
			return image;
		}

		try {
			// Convert to float for better precision
			cv::Mat floatImage;
			image.convertTo(floatImage, CV_32F);

			// Generate noise
			cv::Mat noise(image.size(), CV_32FC(image.channels()));
			cv::randn(noise, mean, stddev);

			// Add noise
			floatImage += noise;

			// Convert back with saturation
			cv::Mat noisyImage;
			floatImage.convertTo(noisyImage, image.type());

			return noisyImage;
		}
		catch (const cv::Exception& e) {
			std::cerr << "OpenCV exception in AddGaussianNoiseFast: " << e.what() << std::endl;
			return image;
		}
	}

	cv::Mat ANSOPENCV::AddSaltAndPepperNoise(const cv::Mat& image, double amount) {
		// Lock-free: operates on local Mats only. Safe across threads.

		if (!_licenseValid || image.empty()) {
			return image;
		}

		if (amount <= 0 || amount >= 1) {
			if (amount <= 0) return image;
			amount = 0.99;  // Clamp to valid range
		}

		try {
			cv::Mat noisyImage = image.clone();

			// Generate random mask for entire image (vectorized!)
			cv::Mat randMat(image.size(), CV_32F);
			cv::randu(randMat, 0, 1.0);

			// Salt (white pixels) - top amount/2 percentile
			double saltThreshold = 1.0 - (amount / 2.0);
			cv::Mat saltMask = randMat > saltThreshold;

			// Pepper (black pixels) - bottom amount/2 percentile
			double pepperThreshold = amount / 2.0;
			cv::Mat pepperMask = randMat < pepperThreshold;

			// Apply noise using masks (MUCH faster than loops!)
			noisyImage.setTo(cv::Scalar::all(255), saltMask);
			noisyImage.setTo(cv::Scalar::all(0), pepperMask);

			return noisyImage;
		}
		catch (const cv::Exception& e) {
			std::cerr << "OpenCV exception in AddSaltAndPepperNoiseFast: " << e.what() << std::endl;
			return image;
		}
	}

	cv::Mat ANSOPENCV::AddSpeckleNoise(const cv::Mat& image, double stddev) {
		// Lock-free: operates on local Mats only. Safe across threads.

		if (!_licenseValid || image.empty()) {
			return image;  // Shallow copy (fast)
		}

		// Validate stddev parameter
		if (stddev < 0) {
			std::cerr << "Error: stddev must be non-negative in AddSpeckleNoise" << std::endl;
			return image;
		}

		// Early exit if no noise
		if (stddev == 0) {
			return image;
		}

		try {
			// Convert to float for proper multiplicative noise calculation
			cv::Mat floatImage;
			image.convertTo(floatImage, CV_32F);

			// Generate Gaussian noise with mean 0 and given stddev
			cv::Mat noise(image.size(), CV_32FC(image.channels()));
			cv::randn(noise, 0, stddev);

			// Add 1.0 to noise so it becomes a multiplicative factor around 1.0
			// noise in range [1-3σ, 1+3σ] for ~99.7% of values
			noise += 1.0;

			// Apply multiplicative noise: output = input * (1 + noise)
			cv::Mat noisyFloat;
			cv::multiply(floatImage, noise, noisyFloat);

			// Convert back to original type with saturation
			cv::Mat noisyImage;
			noisyFloat.convertTo(noisyImage, image.type());

			return noisyImage;
		}
		catch (const cv::Exception& e) {
			std::cerr << "OpenCV exception in AddSpeckleNoise: " << e.what() << std::endl;
			return image;
		}
	}

	void ANSOPENCV::InitCameraNetwork() {
		network_init();
		sys_buf_init(200);
		rtsp_parse_buf_init(200);
		http_msg_buf_init(200);
	}
	void ANSOPENCV::DeinitCameraNetwork() {
		http_msg_buf_deinit();
		rtsp_parse_buf_deinit();
		sys_buf_deinit();
		network_deinit();
	}

	cv::Mat ANSOPENCV::resizeImageToFit(const cv::Mat& inputImage, int maxWidth, int maxHeight, int& newWidth, int& newHeight) {

		if (inputImage.empty()) {
			std::cerr << "Error: Input image is empty or license invalid!" << std::endl;
			newWidth = 0;
			newHeight = 0;
			return cv::Mat();
		}

		// Validate max dimensions
		if (maxWidth <= 0 || maxHeight <= 0) {
			std::cerr << "Error: Invalid max dimensions in resizeImageToFit" << std::endl;
			newWidth = inputImage.cols;
			newHeight = inputImage.rows;
			return inputImage;
		}

		const int width = inputImage.cols;
		const int height = inputImage.rows;

		// If image already fits, return as-is (no clone needed - shallow copy is fine)
		if (width <= maxWidth && height <= maxHeight) {
			newWidth = width;
			newHeight = height;
			return inputImage;  // Shallow copy (fast, safe for read-only)
		}

		try {
			// Compute optimal scale factor maintaining aspect ratio
			const double scale = min(static_cast<double>(maxWidth) / width,
				static_cast<double>(maxHeight) / height);

			// Use rounding for more accurate dimensions
			newWidth = static_cast<int>(std::round(width * scale));
			newHeight = static_cast<int>(std::round(height * scale));

			// Ensure dimensions are at least 1x1
			newWidth = max(1, newWidth);
			newHeight =max(1, newHeight);

			// Choose interpolation method based on scaling direction
			const int interpolation = (scale < 1.0) ? cv::INTER_AREA : cv::INTER_LINEAR;

			// Resize (no need to pre-allocate - cv::resize handles it efficiently)
			cv::Mat resizedImage;
			cv::resize(inputImage, resizedImage, cv::Size(newWidth, newHeight), 0, 0, interpolation);

			return resizedImage;
		}
		catch (const cv::Exception& e) {
			std::cerr << "OpenCV exception in resizeImageToFit: " << e.what() << std::endl;
			newWidth = width;
			newHeight = height;
			return inputImage;
		}
	}
	
	std::string ANSOPENCV::VectorDetectionToJsonString(const std::vector<DetectionObject>& dets)
	{
		if (dets.empty()) {
			return R"({"results":[]})";
		}
		try {
			nlohmann::json root;
			auto& results = root["results"] = nlohmann::json::array();

			for (const auto& det : dets) {
				results.push_back({
					{"class_id", std::to_string(det.classId)},
					{"track_id", std::to_string(det.trackId)},
					{"class_name", det.className},
					{"prob", std::to_string(det.confidence)},
					{"x", std::to_string(det.box.x)},
					{"y", std::to_string(det.box.y)},
					{"width", std::to_string(det.box.width)},
					{"height", std::to_string(det.box.height)},
					{"mask", ""},  // TODO: convert masks to comma separated string
					{"extra_info",det.extraInfo}
					});
			}
			return root.dump();
		}
		catch (const std::exception& e) {
			// Add your error logging here if needed
			return R"({"results":[],"error":"Serialization failed"})";
		}
	}

	double ANSOPENCV::CalculateIoU(const cv::Rect& box1, const cv::Rect& box2) {
		// Lock-free: pure computation over inputs. Safe across threads.
		int x1 = max(box1.x, box2.x);
		int y1 = max(box1.y, box2.y);
		int x2 = min(box1.x + box1.width, box2.x + box2.width);
		int y2 = min(box1.y + box1.height, box2.y + box2.height);

		int intersectionArea = max(0, x2 - x1) * max(0, y2 - y1);
		int box1Area = box1.width * box1.height;
		int box2Area = box2.width * box2.height;

		double iou = static_cast<double>(intersectionArea) / (box1Area + box2Area - intersectionArea);
		return iou;
	}
	void ANSOPENCV::NonMaximumSuppression(std::vector<DetectionObject>& detectedObjects, double iouThreshold) {
		// Lock-free: operates on caller-owned vector. Safe across threads.
		std::sort(detectedObjects.begin(), detectedObjects.end(),
			[](const DetectionObject& a, const DetectionObject& b) {
				return a.confidence > b.confidence;
			});

		std::vector<DetectionObject> finalDetections;
		while (!detectedObjects.empty()) {
			finalDetections.push_back(detectedObjects.front());
			detectedObjects.erase(detectedObjects.begin());

			for (auto it = detectedObjects.begin(); it != detectedObjects.end(); ) {
				if (CalculateIoU(finalDetections.back().box, it->box) > iouThreshold) {
					it = detectedObjects.erase(it);
				}
				else {
					++it;
				}
			}
		}

		detectedObjects = std::move(finalDetections);
	}	
	
	cv::Mat ANSOPENCV::ImageResizeV2(const cv::Mat& inputImage, int resizeWidth, int originalImageSize) {
		// Lock-free: operates on local Mats only. Safe across threads.

		if (!_licenseValid) {
			std::cerr << "Error: License is not valid in ImageResizeV2." << std::endl;
			return cv::Mat();
		}

		if (inputImage.empty()) {
			std::cerr << "Error: Input image is empty!" << std::endl;
			return cv::Mat();
		}

		if (resizeWidth <= 0) {
			std::cerr << "Error: Invalid width provided for resizing!" << std::endl;
			return cv::Mat();
		}

		try {
			const int originalWidth = inputImage.cols;
			const int originalHeight = inputImage.rows;

			// Calculate target width with scaling
			int targetWidth = resizeWidth;

			if (originalImageSize > 0 && originalImageSize != originalWidth) {
				const double scale = static_cast<double>(originalWidth) / originalImageSize;
				targetWidth = static_cast<int>(std::round(resizeWidth * scale));
			}

			// Skip resize if target is same or larger
			if (targetWidth >= originalWidth) {
				return inputImage;  // Shallow copy (fast, safe for read-only)
			}

			// Calculate target height maintaining aspect ratio
			const int targetHeight = static_cast<int>(std::round(
				targetWidth * static_cast<double>(originalHeight) / originalWidth
			));

			// Validate computed dimensions
			if (targetWidth <= 0 || targetHeight <= 0) {
				std::cerr << "Error: Computed dimensions invalid ("
					<< targetWidth << "x" << targetHeight << ")" << std::endl;
				return cv::Mat();
			}

			// Choose interpolation method (INTER_AREA best for downscaling)
			cv::Mat resizedImage;
			cv::resize(inputImage, resizedImage, cv::Size(targetWidth, targetHeight),
				0, 0, cv::INTER_AREA);

			return resizedImage;
		}
		catch (const cv::Exception& e) {
			std::cerr << "OpenCV exception in ImageResizeV2: " << e.what() << std::endl;
			return cv::Mat();
		}
		catch (const std::exception& e) {
			std::cerr << "Exception in ImageResizeV2: " << e.what() << std::endl;
			return cv::Mat();
		}
	}
	
	bool ANSOPENCV::resizeImage(cv::Mat& inputImage, int resizeWidth, int originalImageSize) {
		if (inputImage.empty()) {
			std::cerr << "Error: Input image is empty!" << std::endl;
			return false;
		}

		if (resizeWidth <= 0) {
			std::cerr << "Error: Invalid width provided for resizing!" << std::endl;
			return false;
		}

		try {
			const int originalWidth = inputImage.cols;
			const int originalHeight = inputImage.rows;

			// Calculate target width with scaling
			int targetWidth = resizeWidth;

			if (originalImageSize > 0 && originalImageSize != originalWidth) {
				const double scale = static_cast<double>(originalWidth) / originalImageSize;
				targetWidth = static_cast<int>(std::round(resizeWidth * scale));
			}

			// Skip resize if target is same or larger
			if (targetWidth >= originalWidth) {
				return true;  // No resize needed, success
			}

			// Calculate target height maintaining aspect ratio
			const int targetHeight = static_cast<int>(std::round(
				targetWidth * static_cast<double>(originalHeight) / originalWidth
			));

			// Validate computed dimensions
			if (targetWidth <= 0 || targetHeight <= 0) {
				std::cerr << "Error: Computed dimensions invalid ("
					<< targetWidth << "x" << targetHeight << ")" << std::endl;
				return false;
			}

			// CRITICAL: Use INTER_AREA for downscaling (much faster and better quality)
			cv::resize(inputImage, inputImage, cv::Size(targetWidth, targetHeight),
				0, 0, cv::INTER_AREA);

			return true;
		}
		catch (const cv::Exception& e) {
			std::cerr << "OpenCV exception in resizeImage: " << e.what() << std::endl;
			return false;
		}
		catch (const std::exception& e) {
			std::cerr << "Exception in resizeImage: " << e.what() << std::endl;
			return false;
		}
	}
	
	bool ANSOPENCV::cropImage(cv::Mat& inputImage, const cv::Rect& resizeROI, int originalImageSize) {
		if (inputImage.empty()) {
			std::cerr << "Error: Input image is empty!" << std::endl;
			return false;
		}

		if (resizeROI.width <= 0 || resizeROI.height <= 0) {
			std::cerr << "Error: Invalid ROI size! Width and height must be greater than zero." << std::endl;
			return false;
		}

		try {
			const int originalWidth = inputImage.cols;
			const int originalHeight = inputImage.rows;

			// Scale ROI if needed
			cv::Rect roi = resizeROI;

			if (originalImageSize > 0 && originalImageSize != originalWidth) {
				const double scale = static_cast<double>(originalWidth) / originalImageSize;

				// Use rounding for more accurate scaling
				roi.x = static_cast<int>(std::round(resizeROI.x * scale));
				roi.y = static_cast<int>(std::round(resizeROI.y * scale));
				roi.width = static_cast<int>(std::round(resizeROI.width * scale));
				roi.height = static_cast<int>(std::round(resizeROI.height * scale));
			}

			// Validate and clamp ROI to image bounds
			roi.x = max(0, roi.x);
			roi.y = max(0, roi.y);
			roi.width = min(roi.width, originalWidth - roi.x);
			roi.height = min(roi.height, originalHeight - roi.y);

			// Final validation
			if (roi.width <= 0 || roi.height <= 0) {
				std::cerr << "Error: ROI exceeds image boundaries! ROI("
					<< roi.x << "," << roi.y << "," << roi.width << "," << roi.height
					<< ") vs Image(" << originalWidth << "x" << originalHeight << ")" << std::endl;
				return false;
			}

			// CRITICAL BUG FIX: Clone to avoid dangling reference
			// Without clone(), inputImage references old memory that may be freed
			inputImage = inputImage(roi).clone();

			return true;
		}
		catch (const cv::Exception& e) {
			std::cerr << "OpenCV exception in cropImage: " << e.what() << std::endl;
			return false;
		}
		catch (const std::exception& e) {
			std::cerr << "Exception in cropImage: " << e.what() << std::endl;
			return false;
		}
	}

	bool ANSOPENCV::ImagesToMP4(const std::string& imageFolder,
		const std::string& outputVideoPath,
		int maxWidth, int fps) {
		// Per-output-file mutex for thread safety across concurrent conversions
		static std::mutex mapMutex;
		static std::map<std::string, std::unique_ptr<std::timed_mutex>> fileMutexes;

		std::shared_ptr<std::timed_mutex> fileMutex;
		{
			std::lock_guard<std::mutex> lock(mapMutex);
			std::string canonicalPath = std::filesystem::canonical(
				std::filesystem::path(outputVideoPath).parent_path()).string() +
				"/" + std::filesystem::path(outputVideoPath).filename().string();

			if (fileMutexes.find(canonicalPath) == fileMutexes.end()) {
				fileMutexes[canonicalPath] = std::make_unique<std::timed_mutex>();
			}
			fileMutex = std::shared_ptr<std::timed_mutex>(
				fileMutexes[canonicalPath].get(), [](std::timed_mutex*) {});
		}

		std::unique_lock<std::timed_mutex> lock(*fileMutex, std::defer_lock);
		if (!lock.try_lock_for(std::chrono::milliseconds(MUTEX_TIMEOUT_MS))) {
			std::cerr << "Error: Another thread is writing to " << outputVideoPath << std::endl;
			return false;
		}

		cv::VideoWriter videoWriter;

		try {
			// Clamp FPS to [1, 60]
			fps = max(1, min(60, fps));

			// Collect all image files
			std::vector<cv::String> imageFiles;
			const std::vector<std::string> extensions = { "*.jpg", "*.jpeg", "*.png", "*.bmp" };

			for (const auto& ext : extensions) {
				std::vector<cv::String> temp;
				cv::glob(imageFolder + "/" + ext, temp, false);
				imageFiles.insert(imageFiles.end(),
					std::make_move_iterator(temp.begin()),
					std::make_move_iterator(temp.end()));
			}

			if (imageFiles.empty()) {
				std::cerr << "Error: No images found in folder: " << imageFolder << std::endl;
				return false;
			}

			// Sort by filename (lexicographic, case-sensitive) so playback order
			// follows the on-disk name order. Sorting by std::filesystem::path::
			// filename() is explicit and robust even if cv::glob ever returns
			// entries with different path prefixes or separators.
			// Example input:
			//   20260114_181439.703.jpg
			//   20260114_181439.703_000.jpg
			//   20260114_181439.703_001.jpg
			//   20260114_181439.703_002.jpg
			//   20260114_181439.803.jpg
			// Byte-wise '.' (0x2E) < '_' (0x5F), so "...703.jpg" correctly
			// precedes "...703_000.jpg".
			std::sort(imageFiles.begin(), imageFiles.end(),
				[](const cv::String& a, const cv::String& b) {
					return std::filesystem::path(a).filename().string() <
					       std::filesystem::path(b).filename().string();
				});

			// Cap at 5 minutes max duration
			const int maxFrames = fps * 300;
			if (static_cast<int>(imageFiles.size()) > maxFrames) {
				std::cout << "Warning: Truncating from " << imageFiles.size()
					<< " to " << maxFrames << " images (5-minute limit at "
					<< fps << " FPS)" << std::endl;
				imageFiles.resize(maxFrames);
			}

			const int numImages = static_cast<int>(imageFiles.size());

			// Read first image to determine dimensions
			cv::Mat firstImage = cv::imread(imageFiles[0], cv::IMREAD_COLOR);
			if (firstImage.empty()) {
				std::cerr << "Error: Could not read first image: " << imageFiles[0] << std::endl;
				return false;
			}

			int videoWidth = firstImage.cols;
			int videoHeight = firstImage.rows;
			bool needsResize = false;

			if (maxWidth > 0 && firstImage.cols > maxWidth) {
				// Scale down to fit within maxWidth, preserving aspect ratio
				double scale = static_cast<double>(maxWidth) / firstImage.cols;
				videoWidth = static_cast<int>(std::round(firstImage.cols * scale));
				videoHeight = static_cast<int>(std::round(firstImage.rows * scale));
				needsResize = true;
			}

			// Force even dimensions (required for H.264)
			videoWidth = (videoWidth / 2) * 2;
			videoHeight = (videoHeight / 2) * 2;

			if (videoWidth < 2 || videoHeight < 2) {
				std::cerr << "Error: Resulting video dimensions too small: "
					<< videoWidth << "x" << videoHeight << std::endl;
				return false;
			}

			std::cout << "[Thread " << std::this_thread::get_id() << "] "
				<< "Image: " << firstImage.cols << "x" << firstImage.rows
				<< " -> Video: " << videoWidth << "x" << videoHeight
				<< " | " << numImages << " frames @ " << fps << " FPS"
				<< " (~" << (numImages / fps) << "s)" << std::endl;

			firstImage.release();

			// Ensure .mp4 extension
			std::string mp4OutputPath = outputVideoPath;
			if (mp4OutputPath.size() < 4 ||
				mp4OutputPath.substr(mp4OutputPath.size() - 4) != ".mp4") {
				mp4OutputPath += ".mp4";
			}

			// ---- libx264 tuning for smaller files at preserved quality ----
			// OpenCV's FFmpeg wrapper (>= 4.6) reads OPENCV_FFMPEG_WRITER_OPTIONS
			// at open() time and forwards the options to the encoder. Key points:
			//
			//   video_codec;libx264 — FORCE libx264 encoder by name. On Windows,
			//     the default H.264 encoder registered in opencv_videoio_ffmpeg is
			//     often OpenH264, which silently ignores crf/preset/tune. Without
			//     this override, the tuning below has no effect.
			//   crf;26              — smaller than default 23, still visually good
			//   preset;slow         — better compression efficiency
			//   tune;stillimage     — optimised for mostly-static frames
			//   movflags;+faststart — moov atom at front (good for HTTP streaming)
			//
			// We set the env var only around the H.264 codec attempts and restore
			// it immediately so the MP4V/MJPG fallback path is never polluted by
			// video_codec;libx264 (which would be a codec-id mismatch).
			constexpr const char* kWriterOptsEnv = "OPENCV_FFMPEG_WRITER_OPTIONS";
			constexpr const char* kX264WriterOpts =
				"video_codec;libx264|crf;26|preset;slow|tune;stillimage|movflags;+faststart";

			// Try codecs in order of preference.
			// avc1 / H264 / x264 route to libx264 via FFmpeg when forced via the
			// video_codec option above. MP4V and MJPG are last-resort fallbacks —
			// they produce substantially larger files.
			const std::vector<std::pair<std::string, int>> codecs = {
				{"avc1", cv::VideoWriter::fourcc('a', 'v', 'c', '1')},
				{"H264", cv::VideoWriter::fourcc('H', '2', '6', '4')},
				{"x264", cv::VideoWriter::fourcc('x', '2', '6', '4')},
				{"MP4V", cv::VideoWriter::fourcc('M', 'P', '4', 'V')},
				{"MJPG", cv::VideoWriter::fourcc('M', 'J', 'P', 'G')}
			};

			bool codecFound = false;
			std::string usedCodec;

			// ──────────────────────────────────────────────────────────────
			// PARALLEL-SAFETY: OPENCV_FFMPEG_WRITER_OPTIONS is process-wide.
			// Two workers racing through this block would clobber each
			// other's env var between setX264Opts() and VideoWriter::open(),
			// silently mis-tuning or mis-picking a codec for one of them.
			// Serialize the env-var + open() window with a function-local
			// static mutex. The frame-write loop after this block is fully
			// parallel because `videoWriter` is self-contained once opened.
			// ──────────────────────────────────────────────────────────────
			{
				static std::mutex g_writerOptsMutex;
				std::lock_guard<std::mutex> optsLock(g_writerOptsMutex);

				std::string prevWriterOpts;
				bool hadPrevWriterOpts = false;
				if (const char* prev = std::getenv(kWriterOptsEnv)) {
					prevWriterOpts = prev;
					hadPrevWriterOpts = true;
				}

				auto setX264Opts = [&]() {
					_putenv_s(kWriterOptsEnv, kX264WriterOpts);
				};
				auto restoreOpts = [&]() {
					if (hadPrevWriterOpts) {
						_putenv_s(kWriterOptsEnv, prevWriterOpts.c_str());
					} else {
						_putenv_s(kWriterOptsEnv, "");
					}
				};

				for (const auto& [name, fourcc] : codecs) {
					const bool isH264Family =
						(name == "avc1" || name == "H264" || name == "x264");

					if (isH264Family) {
						setX264Opts();
					} else {
						restoreOpts();
					}

					videoWriter.open(mp4OutputPath, fourcc, fps,
						cv::Size(videoWidth, videoHeight), true);

					if (videoWriter.isOpened()) {
						std::cout << "Using codec: " << name
							<< (isH264Family ? " (libx264 forced, crf=26, preset=slow, tune=stillimage)" : "")
							<< std::endl;
						usedCodec = name;
						codecFound = true;
						break;
					}
					videoWriter.release();
				}

				// Always restore the env var before releasing the mutex
				// — don't leak the libx264 override into the rest of the
				// process.
				restoreOpts();
			}

			if (!codecFound) {
				std::cerr << "Error: Could not open video writer with any codec!" << std::endl;
				return false;
			}

			// Warn loudly if we fell through to a non-H.264 fallback — these
			// produce files many times larger than H.264 at similar quality.
			if (usedCodec == "MP4V" || usedCodec == "MJPG") {
				std::cerr << "Warning: H.264 (libx264) encoder unavailable, fell back to "
					<< usedCodec << ". Output file will be significantly larger. "
					<< "Check that opencv_videoio_ffmpeg is present and that the "
					<< "bundled FFmpeg was built with libx264 support." << std::endl;
			}

			// Hint for non-FFmpeg backends (e.g. MJPG fallback on some platforms).
			// Ignored by libx264 which is controlled via the env var above.
			videoWriter.set(cv::VIDEOWRITER_PROP_QUALITY, 85.0);

			// Pre-allocate reusable matrix
			cv::Mat img;
			cv::Mat resizedImg;

			// 1 image = 1 frame
			for (int i = 0; i < numImages; ++i) {
				img = cv::imread(imageFiles[i], cv::IMREAD_COLOR);
				if (img.empty()) {
					std::cerr << "Warning: Could not read: " << imageFiles[i] << std::endl;
					continue;
				}

				if (needsResize || img.cols != videoWidth || img.rows != videoHeight) {
					cv::resize(img, resizedImg, cv::Size(videoWidth, videoHeight),
						0, 0, cv::INTER_AREA);
					videoWriter.write(resizedImg);
				}
				else {
					videoWriter.write(img);
				}

				img.release();
			}

			resizedImg.release();
			videoWriter.release();

			std::cout << "Video created: " << mp4OutputPath
				<< " (" << numImages << " frames, "
				<< fps << " FPS, ~" << (numImages / fps) << "s)" << std::endl;

			return true;
		}
		catch (const cv::Exception& e) {
			videoWriter.release();
			std::cerr << "OpenCV exception: " << e.what() << std::endl;
			return false;
		}
		catch (const std::exception& e) {
			videoWriter.release();
			std::cerr << "Exception: " << e.what() << std::endl;
			return false;
		}
	}
	// ================================================================
	// ImagesToMP4FF — Direct FFmpeg (libav*) encoder pipeline
	// ================================================================
	// Encoder preference: libx265 (HEVC) > libx264 (H.264) > mpeg4.
	// Produces substantially smaller files than ImagesToMP4 at
	// equivalent quality because we drive libx265/libx264 directly,
	// bypassing OpenCV's VideoWriter + opencv_videoio_ffmpeg wrapper
	// (which on Windows often ends up using OpenH264 with no tunables).
	//
	// Threading: mutex-per-output-file, independent of ImagesToMP4's
	// mutex map, so the two functions can coexist without interference.
	// ================================================================
	bool ANSOPENCV::ImagesToMP4FF(const std::string& imageFolder,
		const std::string& outputVideoPath,
		int maxWidth, int fps) {

		// ---- Per-output-file mutex (independent of ImagesToMP4's map) ----
		static std::mutex mapMutexFF;
		static std::map<std::string, std::unique_ptr<std::timed_mutex>> fileMutexesFF;

		std::shared_ptr<std::timed_mutex> fileMutex;
		{
			std::lock_guard<std::mutex> lock(mapMutexFF);
			std::string canonicalPath = std::filesystem::canonical(
				std::filesystem::path(outputVideoPath).parent_path()).string() +
				"/" + std::filesystem::path(outputVideoPath).filename().string();

			if (fileMutexesFF.find(canonicalPath) == fileMutexesFF.end()) {
				fileMutexesFF[canonicalPath] = std::make_unique<std::timed_mutex>();
			}
			fileMutex = std::shared_ptr<std::timed_mutex>(
				fileMutexesFF[canonicalPath].get(), [](std::timed_mutex*) {});
		}

		std::unique_lock<std::timed_mutex> lock(*fileMutex, std::defer_lock);
		if (!lock.try_lock_for(std::chrono::milliseconds(MUTEX_TIMEOUT_MS))) {
			std::cerr << "Error: Another thread is writing to " << outputVideoPath << std::endl;
			return false;
		}

		// ---- RAII bag for FFmpeg resources -------------------------------
		struct FFState {
			AVFormatContext* fmt_ctx   = nullptr;
			AVCodecContext*  codec_ctx = nullptr;
			AVFrame*         frame     = nullptr;
			AVPacket*        pkt       = nullptr;
			SwsContext*      sws       = nullptr;
			~FFState() {
				if (sws)       { sws_freeContext(sws); sws = nullptr; }
				if (frame)     { av_frame_free(&frame); }
				if (pkt)       { av_packet_free(&pkt); }
				if (codec_ctx) { avcodec_free_context(&codec_ctx); }
				if (fmt_ctx) {
					if (fmt_ctx->pb && !(fmt_ctx->oformat->flags & AVFMT_NOFILE)) {
						avio_closep(&fmt_ctx->pb);
					}
					avformat_free_context(fmt_ctx);
					fmt_ctx = nullptr;
				}
			}
		} ff;

		auto ffErr = [](int err) -> std::string {
			char buf[AV_ERROR_MAX_STRING_SIZE] = {0};
			av_strerror(err, buf, sizeof(buf));
			return std::string(buf);
		};

		try {
			// Clamp FPS to [1, 60]
			fps = max(1, min(60, fps));

			// ---- Collect image files ----
			std::vector<cv::String> imageFiles;
			const std::vector<std::string> extensions = { "*.jpg", "*.jpeg", "*.png", "*.bmp" };
			for (const auto& ext : extensions) {
				std::vector<cv::String> temp;
				cv::glob(imageFolder + "/" + ext, temp, false);
				imageFiles.insert(imageFiles.end(),
					std::make_move_iterator(temp.begin()),
					std::make_move_iterator(temp.end()));
			}
			if (imageFiles.empty()) {
				std::cerr << "Error: No images found in folder: " << imageFolder << std::endl;
				return false;
			}
			// Sort by filename (lexicographic, case-sensitive). See matching
			// comment in ImagesToMP4() for rationale and ordering example.
			std::sort(imageFiles.begin(), imageFiles.end(),
				[](const cv::String& a, const cv::String& b) {
					return std::filesystem::path(a).filename().string() <
					       std::filesystem::path(b).filename().string();
				});

			// Cap at 5 minutes max duration
			const int maxFrames = fps * 300;
			if (static_cast<int>(imageFiles.size()) > maxFrames) {
				std::cout << "Warning: Truncating from " << imageFiles.size()
					<< " to " << maxFrames << " images (5-minute limit at "
					<< fps << " FPS)" << std::endl;
				imageFiles.resize(maxFrames);
			}
			const int numImages = static_cast<int>(imageFiles.size());

			// ---- First image -> dimensions ----
			cv::Mat firstImage = cv::imread(imageFiles[0], cv::IMREAD_COLOR);
			if (firstImage.empty()) {
				std::cerr << "Error: Could not read first image: " << imageFiles[0] << std::endl;
				return false;
			}

			int videoWidth = firstImage.cols;
			int videoHeight = firstImage.rows;
			bool needsResize = false;
			if (maxWidth > 0 && firstImage.cols > maxWidth) {
				double scale = static_cast<double>(maxWidth) / firstImage.cols;
				videoWidth  = static_cast<int>(std::round(firstImage.cols * scale));
				videoHeight = static_cast<int>(std::round(firstImage.rows * scale));
				needsResize = true;
			}
			// Force even dims (required for YUV420P)
			videoWidth  = (videoWidth  / 2) * 2;
			videoHeight = (videoHeight / 2) * 2;
			if (videoWidth < 2 || videoHeight < 2) {
				std::cerr << "Error: Resulting video dimensions too small: "
					<< videoWidth << "x" << videoHeight << std::endl;
				return false;
			}

			std::cout << "[FF Thread " << std::this_thread::get_id() << "] "
				<< "Image: " << firstImage.cols << "x" << firstImage.rows
				<< " -> Video: " << videoWidth << "x" << videoHeight
				<< " | " << numImages << " frames @ " << fps << " FPS"
				<< " (~" << (numImages / fps) << "s)" << std::endl;
			firstImage.release();

			// Ensure .mp4 extension
			std::string mp4OutputPath = outputVideoPath;
			if (mp4OutputPath.size() < 4 ||
				mp4OutputPath.substr(mp4OutputPath.size() - 4) != ".mp4") {
				mp4OutputPath += ".mp4";
			}

			// ---- Encoder selection ----
			struct EncChoice {
				const char* name;
				const char* display;
				const char* crf;     // empty = no crf (bitrate-targeted)
				const char* preset;  // empty = no preset
				const char* tune;    // empty = no tune
				bool        isHEVC;
			};
			const std::vector<EncChoice> encChoices = {
				// HEVC: CRF 28 ≈ H.264 CRF 23 in perceived quality.
				// libx265 has no 'stillimage' tune; default is fine.
				{ "libx265", "HEVC/H.265 (libx265)",          "28", "slow", "",            true  },
				// H.264: CRF 26 with stillimage tune for slideshow content.
				{ "libx264", "H.264 (libx264)",               "26", "slow", "stillimage",  false },
				// MPEG-4 Part 2 fallback: uses bitrate, not CRF. Larger output.
				{ "mpeg4",   "MPEG-4 Part 2 (native FFmpeg)", "",   "",     "",            false },
			};

			const AVCodec* codec = nullptr;
			const EncChoice* chosen = nullptr;
			for (const auto& e : encChoices) {
				if (const AVCodec* c = avcodec_find_encoder_by_name(e.name)) {
					codec = c;
					chosen = &e;
					break;
				}
			}
			if (!codec) {
				std::cerr << "[FFmpeg] Error: no suitable encoder available "
					"(tried libx265, libx264, mpeg4). Bundled FFmpeg was built "
					"without any of these encoders." << std::endl;
				return false;
			}
			std::cout << "[FFmpeg] Using encoder: " << chosen->display;
			if (chosen->crf[0])    std::cout << " crf=" << chosen->crf;
			if (chosen->preset[0]) std::cout << " preset=" << chosen->preset;
			if (chosen->tune[0])   std::cout << " tune=" << chosen->tune;
			std::cout << std::endl;

			int ret = 0;

			// ---- 1. Allocate output format context (MP4 muxer) ----
			ret = avformat_alloc_output_context2(&ff.fmt_ctx, nullptr, "mp4", mp4OutputPath.c_str());
			if (ret < 0 || !ff.fmt_ctx) {
				std::cerr << "[FFmpeg] avformat_alloc_output_context2 failed: " << ffErr(ret) << std::endl;
				return false;
			}

			// ---- 2. New stream (owned by fmt_ctx) ----
			AVStream* stream = avformat_new_stream(ff.fmt_ctx, nullptr);
			if (!stream) {
				std::cerr << "[FFmpeg] avformat_new_stream failed" << std::endl;
				return false;
			}

			// ---- 3. Codec context ----
			ff.codec_ctx = avcodec_alloc_context3(codec);
			if (!ff.codec_ctx) {
				std::cerr << "[FFmpeg] avcodec_alloc_context3 failed" << std::endl;
				return false;
			}
			ff.codec_ctx->width        = videoWidth;
			ff.codec_ctx->height       = videoHeight;
			ff.codec_ctx->pix_fmt      = AV_PIX_FMT_YUV420P;
			ff.codec_ctx->time_base    = AVRational{ 1, fps };
			ff.codec_ctx->framerate    = AVRational{ fps, 1 };
			ff.codec_ctx->gop_size     = fps * 2;   // keyframe every ~2s
			ff.codec_ctx->max_b_frames = 2;

			// HEVC in MP4: force 'hvc1' codec tag so QuickTime and Apple
			// players accept the file. Without this, libx265 defaults to
			// 'hev1' which some players refuse.
			if (chosen->isHEVC) {
				ff.codec_ctx->codec_tag = MKTAG('h', 'v', 'c', '1');
			}

			// MP4 muxer requires global header flag for most codecs
			if (ff.fmt_ctx->oformat->flags & AVFMT_GLOBALHEADER) {
				ff.codec_ctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
			}

			// MPEG-4 fallback: set a modest target bitrate (no CRF support)
			if (std::strcmp(chosen->name, "mpeg4") == 0) {
				ff.codec_ctx->bit_rate = 1500000; // ~1.5 Mbps
			}

			// ---- 4. Encoder-private options ----
			AVDictionary* encOpts = nullptr;
			if (chosen->crf[0])    av_dict_set(&encOpts, "crf",    chosen->crf,    0);
			if (chosen->preset[0]) av_dict_set(&encOpts, "preset", chosen->preset, 0);
			if (chosen->tune[0])   av_dict_set(&encOpts, "tune",   chosen->tune,   0);

			// ---- 5. Open encoder ----
			ret = avcodec_open2(ff.codec_ctx, codec, &encOpts);
			if (ret < 0) {
				std::cerr << "[FFmpeg] avcodec_open2 failed: " << ffErr(ret) << std::endl;
				av_dict_free(&encOpts);
				return false;
			}
			// Report any options the encoder silently ignored
			if (encOpts) {
				AVDictionaryEntry* e = nullptr;
				while ((e = av_dict_get(encOpts, "", e, AV_DICT_IGNORE_SUFFIX))) {
					std::cerr << "[FFmpeg] Warning: encoder ignored option "
						<< e->key << "=" << e->value << std::endl;
				}
				av_dict_free(&encOpts);
			}

			// ---- 6. Copy codec params -> stream ----
			ret = avcodec_parameters_from_context(stream->codecpar, ff.codec_ctx);
			if (ret < 0) {
				std::cerr << "[FFmpeg] avcodec_parameters_from_context failed: " << ffErr(ret) << std::endl;
				return false;
			}
			stream->time_base = ff.codec_ctx->time_base;

			// ---- 7. Open output file ----
			if (!(ff.fmt_ctx->oformat->flags & AVFMT_NOFILE)) {
				ret = avio_open(&ff.fmt_ctx->pb, mp4OutputPath.c_str(), AVIO_FLAG_WRITE);
				if (ret < 0) {
					std::cerr << "[FFmpeg] avio_open('" << mp4OutputPath << "') failed: " << ffErr(ret) << std::endl;
					return false;
				}
			}

			// ---- 8. Write MP4 header with +faststart ----
			{
				AVDictionary* muxOpts = nullptr;
				av_dict_set(&muxOpts, "movflags", "+faststart", 0);
				ret = avformat_write_header(ff.fmt_ctx, &muxOpts);
				av_dict_free(&muxOpts);
				if (ret < 0) {
					std::cerr << "[FFmpeg] avformat_write_header failed: " << ffErr(ret) << std::endl;
					return false;
				}
			}

			// ---- 9. Allocate AVFrame (YUV420P) + AVPacket ----
			ff.frame = av_frame_alloc();
			ff.pkt   = av_packet_alloc();
			if (!ff.frame || !ff.pkt) {
				std::cerr << "[FFmpeg] av_frame_alloc / av_packet_alloc failed" << std::endl;
				return false;
			}
			ff.frame->format = AV_PIX_FMT_YUV420P;
			ff.frame->width  = videoWidth;
			ff.frame->height = videoHeight;
			ret = av_frame_get_buffer(ff.frame, 0);
			if (ret < 0) {
				std::cerr << "[FFmpeg] av_frame_get_buffer failed: " << ffErr(ret) << std::endl;
				return false;
			}

			// ---- 10. BGR24 -> YUV420P converter ----
			ff.sws = sws_getContext(
				videoWidth, videoHeight, AV_PIX_FMT_BGR24,
				videoWidth, videoHeight, AV_PIX_FMT_YUV420P,
				SWS_BILINEAR, nullptr, nullptr, nullptr);
			if (!ff.sws) {
				std::cerr << "[FFmpeg] sws_getContext failed" << std::endl;
				return false;
			}

			// ---- Helper: drain any packets the encoder has ready ----
			auto drainPackets = [&]() -> bool {
				for (;;) {
					int r = avcodec_receive_packet(ff.codec_ctx, ff.pkt);
					if (r == AVERROR(EAGAIN) || r == AVERROR_EOF) return true;
					if (r < 0) {
						std::cerr << "[FFmpeg] avcodec_receive_packet failed: " << ffErr(r) << std::endl;
						return false;
					}
					av_packet_rescale_ts(ff.pkt, ff.codec_ctx->time_base, stream->time_base);
					ff.pkt->stream_index = stream->index;
					r = av_interleaved_write_frame(ff.fmt_ctx, ff.pkt);
					av_packet_unref(ff.pkt);
					if (r < 0) {
						std::cerr << "[FFmpeg] av_interleaved_write_frame failed: " << ffErr(r) << std::endl;
						return false;
					}
				}
			};

			// ---- 11. Encoding loop ----
			cv::Mat img;
			cv::Mat resizedImg;
			int64_t framesEncoded = 0;

			for (int i = 0; i < numImages; ++i) {
				img = cv::imread(imageFiles[i], cv::IMREAD_COLOR);
				if (img.empty()) {
					std::cerr << "Warning: Could not read: " << imageFiles[i] << std::endl;
					continue;
				}

				cv::Mat* src = &img;
				if (needsResize || img.cols != videoWidth || img.rows != videoHeight) {
					cv::resize(img, resizedImg, cv::Size(videoWidth, videoHeight),
						0, 0, cv::INTER_AREA);
					src = &resizedImg;
				}

				ret = av_frame_make_writable(ff.frame);
				if (ret < 0) {
					std::cerr << "[FFmpeg] av_frame_make_writable failed: " << ffErr(ret) << std::endl;
					return false;
				}

				const uint8_t* srcSlices[4] = { src->data, nullptr, nullptr, nullptr };
				int srcStride[4] = { static_cast<int>(src->step[0]), 0, 0, 0 };
				sws_scale(ff.sws, srcSlices, srcStride, 0, videoHeight,
					ff.frame->data, ff.frame->linesize);

				ff.frame->pts = framesEncoded;

				ret = avcodec_send_frame(ff.codec_ctx, ff.frame);
				if (ret < 0) {
					std::cerr << "[FFmpeg] avcodec_send_frame failed: " << ffErr(ret) << std::endl;
					return false;
				}
				if (!drainPackets()) return false;

				framesEncoded++;
				img.release();
			}

			// ---- 12. Flush encoder ----
			ret = avcodec_send_frame(ff.codec_ctx, nullptr);
			if (ret < 0 && ret != AVERROR_EOF) {
				std::cerr << "[FFmpeg] flush send_frame failed: " << ffErr(ret) << std::endl;
				return false;
			}
			if (!drainPackets()) return false;

			// ---- 13. Write trailer (finalises moov; with +faststart,
			//          FFmpeg rewrites the file to move moov to the front) ----
			ret = av_write_trailer(ff.fmt_ctx);
			if (ret < 0) {
				std::cerr << "[FFmpeg] av_write_trailer failed: " << ffErr(ret) << std::endl;
				return false;
			}

			std::cout << "[FFmpeg] Video created: " << mp4OutputPath
				<< " (" << framesEncoded << " frames, "
				<< fps << " FPS, ~" << (framesEncoded / fps) << "s)"
				<< " via " << chosen->display << std::endl;

			return true;
		}
		catch (const cv::Exception& e) {
			std::cerr << "[FFmpeg] OpenCV exception: " << e.what() << std::endl;
			return false;
		}
		catch (const std::exception& e) {
			std::cerr << "[FFmpeg] Exception: " << e.what() << std::endl;
			return false;
		}
	}

	// ================================================================
	// ImagesToMP4HW — Hardware-accelerated FFmpeg encoder pipeline
	// ================================================================
	// Preference order: NVIDIA NVENC > Intel QSV > AMD AMF, HEVC first,
	// then H.264 at each vendor, then software (libx265/libx264/mpeg4).
	// Each encoder is probed by attempting a real avcodec_open2(); the
	// first one to succeed is used. This avoids guessing based on GPU
	// presence — we just try and let FFmpeg tell us what works.
	//
	// Per-encoder quality targets:
	//   NVENC:   rc=vbr, cq=28 (HEVC) / cq=24 (H.264), preset=p5, tune=hq
	//   QSV:     global_quality=28 (HEVC) / 24 (H.264), preset=slower
	//   AMF:     quality=quality, rc=cqp, qp ~24/26/28 (HEVC) / 22/24/26 (H.264)
	//   libx265: crf=28, preset=slow
	//   libx264: crf=26, preset=slow, tune=stillimage
	//   mpeg4:   bit_rate=1.5 Mbps (no CRF support)
	//
	// Pixel format: NV12 for QSV/AMF (native), YUV420P everywhere else.
	// ================================================================
	bool ANSOPENCV::ImagesToMP4HW(const std::string& imageFolder,
		const std::string& outputVideoPath,
		int maxWidth, int fps) {

		// ---- Per-output-file mutex (independent of the other two) ----
		static std::mutex mapMutexHW;
		static std::map<std::string, std::unique_ptr<std::timed_mutex>> fileMutexesHW;

		std::shared_ptr<std::timed_mutex> fileMutex;
		{
			std::lock_guard<std::mutex> lock(mapMutexHW);
			std::string canonicalPath = std::filesystem::canonical(
				std::filesystem::path(outputVideoPath).parent_path()).string() +
				"/" + std::filesystem::path(outputVideoPath).filename().string();

			if (fileMutexesHW.find(canonicalPath) == fileMutexesHW.end()) {
				fileMutexesHW[canonicalPath] = std::make_unique<std::timed_mutex>();
			}
			fileMutex = std::shared_ptr<std::timed_mutex>(
				fileMutexesHW[canonicalPath].get(), [](std::timed_mutex*) {});
		}

		std::unique_lock<std::timed_mutex> lock(*fileMutex, std::defer_lock);
		if (!lock.try_lock_for(std::chrono::milliseconds(MUTEX_TIMEOUT_MS))) {
			std::cerr << "Error: Another thread is writing to " << outputVideoPath << std::endl;
			return false;
		}

		// ---- RAII bag for FFmpeg resources ----
		struct FFState {
			AVFormatContext* fmt_ctx   = nullptr;
			AVCodecContext*  codec_ctx = nullptr;
			AVFrame*         frame     = nullptr;
			AVPacket*        pkt       = nullptr;
			SwsContext*      sws       = nullptr;
			~FFState() {
				if (sws)       { sws_freeContext(sws); sws = nullptr; }
				if (frame)     { av_frame_free(&frame); }
				if (pkt)       { av_packet_free(&pkt); }
				if (codec_ctx) { avcodec_free_context(&codec_ctx); }
				if (fmt_ctx) {
					if (fmt_ctx->pb && !(fmt_ctx->oformat->flags & AVFMT_NOFILE)) {
						avio_closep(&fmt_ctx->pb);
					}
					avformat_free_context(fmt_ctx);
					fmt_ctx = nullptr;
				}
			}
		} ff;

		auto ffErr = [](int err) -> std::string {
			char buf[AV_ERROR_MAX_STRING_SIZE] = {0};
			av_strerror(err, buf, sizeof(buf));
			return std::string(buf);
		};

		try {
			// Clamp FPS to [1, 60]
			fps = max(1, min(60, fps));

			// ---- Collect image files ----
			std::vector<cv::String> imageFiles;
			const std::vector<std::string> extensions = { "*.jpg", "*.jpeg", "*.png", "*.bmp" };
			for (const auto& ext : extensions) {
				std::vector<cv::String> temp;
				cv::glob(imageFolder + "/" + ext, temp, false);
				imageFiles.insert(imageFiles.end(),
					std::make_move_iterator(temp.begin()),
					std::make_move_iterator(temp.end()));
			}
			if (imageFiles.empty()) {
				std::cerr << "Error: No images found in folder: " << imageFolder << std::endl;
				return false;
			}
			// Sort by filename (lexicographic, case-sensitive). See matching
			// comment in ImagesToMP4() for rationale and ordering example.
			std::sort(imageFiles.begin(), imageFiles.end(),
				[](const cv::String& a, const cv::String& b) {
					return std::filesystem::path(a).filename().string() <
					       std::filesystem::path(b).filename().string();
				});

			const int maxFrames = fps * 300;
			if (static_cast<int>(imageFiles.size()) > maxFrames) {
				std::cout << "Warning: Truncating from " << imageFiles.size()
					<< " to " << maxFrames << " images (5-minute limit at "
					<< fps << " FPS)" << std::endl;
				imageFiles.resize(maxFrames);
			}
			const int numImages = static_cast<int>(imageFiles.size());

			// ---- First image -> dimensions ----
			cv::Mat firstImage = cv::imread(imageFiles[0], cv::IMREAD_COLOR);
			if (firstImage.empty()) {
				std::cerr << "Error: Could not read first image: " << imageFiles[0] << std::endl;
				return false;
			}

			int videoWidth = firstImage.cols;
			int videoHeight = firstImage.rows;
			bool needsResize = false;
			if (maxWidth > 0 && firstImage.cols > maxWidth) {
				double scale = static_cast<double>(maxWidth) / firstImage.cols;
				videoWidth  = static_cast<int>(std::round(firstImage.cols * scale));
				videoHeight = static_cast<int>(std::round(firstImage.rows * scale));
				needsResize = true;
			}
			videoWidth  = (videoWidth  / 2) * 2;
			videoHeight = (videoHeight / 2) * 2;
			if (videoWidth < 2 || videoHeight < 2) {
				std::cerr << "Error: Resulting video dimensions too small: "
					<< videoWidth << "x" << videoHeight << std::endl;
				return false;
			}

			std::cout << "[FF-HW Thread " << std::this_thread::get_id() << "] "
				<< "Image: " << firstImage.cols << "x" << firstImage.rows
				<< " -> Video: " << videoWidth << "x" << videoHeight
				<< " | " << numImages << " frames @ " << fps << " FPS"
				<< " (~" << (numImages / fps) << "s)" << std::endl;
			firstImage.release();

			// Ensure .mp4 extension
			std::string mp4OutputPath = outputVideoPath;
			if (mp4OutputPath.size() < 4 ||
				mp4OutputPath.substr(mp4OutputPath.size() - 4) != ".mp4") {
				mp4OutputPath += ".mp4";
			}

			int ret = 0;

			// ---- Allocate output format context (needed before codec-open probe) ----
			ret = avformat_alloc_output_context2(&ff.fmt_ctx, nullptr, "mp4", mp4OutputPath.c_str());
			if (ret < 0 || !ff.fmt_ctx) {
				std::cerr << "[FF-HW] avformat_alloc_output_context2 failed: " << ffErr(ret) << std::endl;
				return false;
			}

			// ---- Encoder preference list ----
			struct KV { const char* k; const char* v; };
			struct EncChoice {
				const char*    name;       // avcodec encoder name
				const char*    display;    // human-readable
				bool           isHEVC;     // affects codec_tag for MP4
				AVPixelFormat  pixFmt;     // NV12 for QSV/AMF, YUV420P else
				int            maxBFrames; // 0 for hardware encoders that don't like B-frames
				std::vector<KV> opts;
			};

			const std::vector<EncChoice> encoders = {
				// ---- HEVC hardware ----
				// NOTE: using LEGACY NVENC preset names (slow/medium/fast/hq/...) not
				// the newer p1..p7 naming, because the latter requires FFmpeg >= 4.4
				// + NVIDIA Video Codec SDK 10.0. Legacy names work on both old and
				// new builds. The newer 'tune' option doesn't exist in older NVENC
				// wrappers either, so we omit it and rely on the preset for quality.
				{ "hevc_nvenc", "NVIDIA HEVC (NVENC)", true, AV_PIX_FMT_YUV420P, 0, {
					{"preset", "slow"}, {"rc", "vbr"}, {"cq", "28"}
				}},
				{ "hevc_qsv",   "Intel HEVC (QSV)",    true, AV_PIX_FMT_NV12,    0, {
					{"global_quality", "28"}, {"preset", "slower"}
				}},
				{ "hevc_amf",   "AMD HEVC (AMF)",      true, AV_PIX_FMT_NV12,    0, {
					{"quality", "quality"}, {"rc", "cqp"},
					{"qp_i", "24"}, {"qp_p", "26"}, {"qp_b", "28"}
				}},
				// ---- H.264 hardware ----
				{ "h264_nvenc", "NVIDIA H.264 (NVENC)", false, AV_PIX_FMT_YUV420P, 0, {
					{"preset", "slow"}, {"rc", "vbr"}, {"cq", "24"}
				}},
				{ "h264_qsv",   "Intel H.264 (QSV)",    false, AV_PIX_FMT_NV12,    0, {
					{"global_quality", "24"}, {"preset", "slower"}
				}},
				{ "h264_amf",   "AMD H.264 (AMF)",      false, AV_PIX_FMT_NV12,    0, {
					{"quality", "quality"}, {"rc", "cqp"},
					{"qp_i", "22"}, {"qp_p", "24"}, {"qp_b", "26"}
				}},
				// ---- Software fallbacks ----
				{ "libx265",    "HEVC/H.265 (libx265)",          true,  AV_PIX_FMT_YUV420P, 2, {
					{"crf", "28"}, {"preset", "slow"}
				}},
				{ "libx264",    "H.264 (libx264)",               false, AV_PIX_FMT_YUV420P, 2, {
					{"crf", "26"}, {"preset", "slow"}, {"tune", "stillimage"}
				}},
				{ "mpeg4",      "MPEG-4 Part 2 (native FFmpeg)", false, AV_PIX_FMT_YUV420P, 2, {} },
			};

			const AVCodec* codec = nullptr;
			const EncChoice* chosen = nullptr;

			// ---- Probe loop: try each encoder until one opens successfully ----
			for (const auto& e : encoders) {
				const AVCodec* c = avcodec_find_encoder_by_name(e.name);
				if (!c) {
					std::cout << "[FF-HW]   skip " << e.display
						<< " (not compiled into FFmpeg)" << std::endl;
					continue;
				}

				AVCodecContext* ctx = avcodec_alloc_context3(c);
				if (!ctx) continue;

				ctx->width        = videoWidth;
				ctx->height       = videoHeight;
				ctx->pix_fmt      = e.pixFmt;
				ctx->time_base    = AVRational{ 1, fps };
				ctx->framerate    = AVRational{ fps, 1 };
				ctx->gop_size     = fps * 2;
				ctx->max_b_frames = e.maxBFrames;

				if (e.isHEVC) {
					ctx->codec_tag = MKTAG('h', 'v', 'c', '1');
				}
				if (ff.fmt_ctx->oformat->flags & AVFMT_GLOBALHEADER) {
					ctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
				}
				if (std::strcmp(e.name, "mpeg4") == 0) {
					ctx->bit_rate = 1500000; // ~1.5 Mbps fallback
				}

				AVDictionary* opts = nullptr;
				for (const auto& kv : e.opts) {
					av_dict_set(&opts, kv.k, kv.v, 0);
				}

				int r = avcodec_open2(ctx, c, &opts);
				av_dict_free(&opts);

				if (r < 0) {
					std::cout << "[FF-HW]   skip " << e.display
						<< " (open failed: " << ffErr(r) << ")" << std::endl;
					avcodec_free_context(&ctx);
					continue;
				}

				// Success — commit this context to the RAII bag
				ff.codec_ctx = ctx;
				codec = c;
				chosen = &e;
				std::cout << "[FF-HW] Using encoder: " << e.display;
				for (const auto& kv : e.opts) std::cout << " " << kv.k << "=" << kv.v;
				std::cout << std::endl;
				break;
			}

			if (!ff.codec_ctx) {
				std::cerr << "[FF-HW] Error: no encoder could be opened. "
					"Bundled FFmpeg has neither hardware (NVENC/QSV/AMF) nor "
					"software (libx265/libx264/mpeg4) encoders available." << std::endl;
				return false;
			}

			// ---- Create output stream, copy codec params ----
			AVStream* stream = avformat_new_stream(ff.fmt_ctx, nullptr);
			if (!stream) {
				std::cerr << "[FF-HW] avformat_new_stream failed" << std::endl;
				return false;
			}
			ret = avcodec_parameters_from_context(stream->codecpar, ff.codec_ctx);
			if (ret < 0) {
				std::cerr << "[FF-HW] avcodec_parameters_from_context failed: " << ffErr(ret) << std::endl;
				return false;
			}
			stream->time_base = ff.codec_ctx->time_base;

			// ---- Open output file ----
			if (!(ff.fmt_ctx->oformat->flags & AVFMT_NOFILE)) {
				ret = avio_open(&ff.fmt_ctx->pb, mp4OutputPath.c_str(), AVIO_FLAG_WRITE);
				if (ret < 0) {
					std::cerr << "[FF-HW] avio_open('" << mp4OutputPath << "') failed: " << ffErr(ret) << std::endl;
					return false;
				}
			}

			// ---- Write MP4 header with +faststart ----
			{
				AVDictionary* muxOpts = nullptr;
				av_dict_set(&muxOpts, "movflags", "+faststart", 0);
				ret = avformat_write_header(ff.fmt_ctx, &muxOpts);
				av_dict_free(&muxOpts);
				if (ret < 0) {
					std::cerr << "[FF-HW] avformat_write_header failed: " << ffErr(ret) << std::endl;
					return false;
				}
			}

			// ---- Allocate AVFrame with chosen pix_fmt + AVPacket ----
			ff.frame = av_frame_alloc();
			ff.pkt   = av_packet_alloc();
			if (!ff.frame || !ff.pkt) {
				std::cerr << "[FF-HW] av_frame_alloc / av_packet_alloc failed" << std::endl;
				return false;
			}
			ff.frame->format = chosen->pixFmt;
			ff.frame->width  = videoWidth;
			ff.frame->height = videoHeight;
			ret = av_frame_get_buffer(ff.frame, 0);
			if (ret < 0) {
				std::cerr << "[FF-HW] av_frame_get_buffer failed: " << ffErr(ret) << std::endl;
				return false;
			}

			// ---- BGR24 -> chosen pix_fmt converter ----
			ff.sws = sws_getContext(
				videoWidth, videoHeight, AV_PIX_FMT_BGR24,
				videoWidth, videoHeight, chosen->pixFmt,
				SWS_BILINEAR, nullptr, nullptr, nullptr);
			if (!ff.sws) {
				std::cerr << "[FF-HW] sws_getContext failed" << std::endl;
				return false;
			}

			// ---- Drain helper ----
			auto drainPackets = [&]() -> bool {
				for (;;) {
					int r = avcodec_receive_packet(ff.codec_ctx, ff.pkt);
					if (r == AVERROR(EAGAIN) || r == AVERROR_EOF) return true;
					if (r < 0) {
						std::cerr << "[FF-HW] avcodec_receive_packet failed: " << ffErr(r) << std::endl;
						return false;
					}
					av_packet_rescale_ts(ff.pkt, ff.codec_ctx->time_base, stream->time_base);
					ff.pkt->stream_index = stream->index;
					r = av_interleaved_write_frame(ff.fmt_ctx, ff.pkt);
					av_packet_unref(ff.pkt);
					if (r < 0) {
						std::cerr << "[FF-HW] av_interleaved_write_frame failed: " << ffErr(r) << std::endl;
						return false;
					}
				}
			};

			// ---- Encoding loop ----
			cv::Mat img;
			cv::Mat resizedImg;
			int64_t framesEncoded = 0;

			for (int i = 0; i < numImages; ++i) {
				img = cv::imread(imageFiles[i], cv::IMREAD_COLOR);
				if (img.empty()) {
					std::cerr << "Warning: Could not read: " << imageFiles[i] << std::endl;
					continue;
				}

				cv::Mat* src = &img;
				if (needsResize || img.cols != videoWidth || img.rows != videoHeight) {
					cv::resize(img, resizedImg, cv::Size(videoWidth, videoHeight),
						0, 0, cv::INTER_AREA);
					src = &resizedImg;
				}

				ret = av_frame_make_writable(ff.frame);
				if (ret < 0) {
					std::cerr << "[FF-HW] av_frame_make_writable failed: " << ffErr(ret) << std::endl;
					return false;
				}

				const uint8_t* srcSlices[4] = { src->data, nullptr, nullptr, nullptr };
				int srcStride[4] = { static_cast<int>(src->step[0]), 0, 0, 0 };
				sws_scale(ff.sws, srcSlices, srcStride, 0, videoHeight,
					ff.frame->data, ff.frame->linesize);

				ff.frame->pts = framesEncoded;

				ret = avcodec_send_frame(ff.codec_ctx, ff.frame);
				if (ret < 0) {
					std::cerr << "[FF-HW] avcodec_send_frame failed: " << ffErr(ret) << std::endl;
					return false;
				}
				if (!drainPackets()) return false;

				framesEncoded++;
				img.release();
			}

			// ---- Flush encoder ----
			ret = avcodec_send_frame(ff.codec_ctx, nullptr);
			if (ret < 0 && ret != AVERROR_EOF) {
				std::cerr << "[FF-HW] flush send_frame failed: " << ffErr(ret) << std::endl;
				return false;
			}
			if (!drainPackets()) return false;

			// ---- Write trailer ----
			ret = av_write_trailer(ff.fmt_ctx);
			if (ret < 0) {
				std::cerr << "[FF-HW] av_write_trailer failed: " << ffErr(ret) << std::endl;
				return false;
			}

			std::cout << "[FF-HW] Video created: " << mp4OutputPath
				<< " (" << framesEncoded << " frames, "
				<< fps << " FPS, ~" << (framesEncoded / fps) << "s)"
				<< " via " << chosen->display << std::endl;

			return true;
		}
		catch (const cv::Exception& e) {
			std::cerr << "[FF-HW] OpenCV exception: " << e.what() << std::endl;
			return false;
		}
		catch (const std::exception& e) {
			std::cerr << "[FF-HW] Exception: " << e.what() << std::endl;
			return false;
		}
	}

	//bool ANSOPENCV::ImagesToMP4(const std::string& imageFolder, const std::string& outputVideoPath, int targetDurationSec) {
	//	std::unique_lock<std::timed_mutex> lock(timeImageMutex, std::defer_lock);
	//	if (!lock.try_lock_for(std::chrono::milliseconds(MUTEX_TIMEOUT_MS))) {
	//		std::cerr << "Error: Mutex timeout in ImagesToMP4!" << std::endl;
	//		return -6;
	//	}
	//	cv::VideoWriter videoWriter;

	//	try {
	//		// Collect all image files efficiently
	//		std::vector<cv::String> imageFiles;
	//		const std::vector<std::string> extensions = { "*.jpg", "*.jpeg", "*.png", "*.bmp" };

	//		for (const auto& ext : extensions) {
	//			std::vector<cv::String> temp;
	//			cv::glob(imageFolder + "/" + ext, temp, false);
	//			imageFiles.insert(imageFiles.end(), temp.begin(), temp.end());
	//		}

	//		if (imageFiles.empty()) {
	//			std::cerr << "Error: No images found in folder: " << imageFolder << std::endl;
	//			return false;
	//		}

	//		// Sort for consistent ordering
	//		std::sort(imageFiles.begin(), imageFiles.end());

	//		// Read first image to determine dimensions
	//		cv::Mat firstImage = cv::imread(imageFiles[0]);
	//		if (firstImage.empty()) {
	//			std::cerr << "Error: Could not read first image: " << imageFiles[0] << std::endl;
	//			return false;
	//		}

	//		// Target video dimensions (1920x1080)
	//		const int targetWidth = 1920;
	//		const int targetHeight = 1080;

	//		// Calculate scaling to fit within bounds while maintaining aspect ratio
	//		const double scaleX = static_cast<double>(targetWidth) / firstImage.cols;
	//		const double scaleY = static_cast<double>(targetHeight) / firstImage.rows;
	//		const double scale = min(scaleX, scaleY);

	//		// Calculate scaled dimensions (ensure even for H.264)
	//		int scaledWidth = static_cast<int>(std::round(firstImage.cols * scale));
	//		int scaledHeight = static_cast<int>(std::round(firstImage.rows * scale));

	//		// Make dimensions even (required for H.264)
	//		scaledWidth = (scaledWidth / 2) * 2;
	//		scaledHeight = (scaledHeight / 2) * 2;

	//		// Calculate centering padding
	//		const int padLeft = (targetWidth - scaledWidth) / 2;
	//		const int padTop = (targetHeight - scaledHeight) / 2;

	//		std::cout << "Original image size: " << firstImage.cols << "x" << firstImage.rows << std::endl;
	//		std::cout << "Scaled image size: " << scaledWidth << "x" << scaledHeight << std::endl;
	//		std::cout << "Final video size: " << targetWidth << "x" << targetHeight << std::endl;
	//		std::cout << "Padding: left=" << padLeft << ", top=" << padTop << std::endl;

	//		// Release firstImage to free memory
	//		firstImage.release();

	//		// Video parameters
	//		const int targetFPS = 25;
	//		const int videoDurationSec = max(3, targetDurationSec);
	//		const int totalFrames = videoDurationSec * targetFPS;

	//		// Ensure .mp4 extension
	//		std::string mp4OutputPath = outputVideoPath;
	//		if (mp4OutputPath.size() < 4 ||
	//			mp4OutputPath.substr(mp4OutputPath.size() - 4) != ".mp4") {
	//			mp4OutputPath += ".mp4";
	//		}

	//		// Try codecs in order of preference
	//		const std::vector<std::tuple<std::string, int>> codecs = {
	//			{"x264 (best for web)", cv::VideoWriter::fourcc('x', '2', '6', '4')},
	//			{"H264 (good for web)", cv::VideoWriter::fourcc('H', '2', '6', '4')},
	//			{"MP4V (web compatible)", cv::VideoWriter::fourcc('M', 'P', '4', 'V')},
	//			{"MJPG (large files)", cv::VideoWriter::fourcc('M', 'J', 'P', 'G')}
	//		};

	//		bool codecFound = false;
	//		for (const auto& [name, fourcc] : codecs) {
	//			videoWriter.open(mp4OutputPath, fourcc, targetFPS,
	//				cv::Size(targetWidth, targetHeight), true);
	//			if (videoWriter.isOpened()) {
	//				std::cout << "Using codec: " << name << std::endl;
	//				codecFound = true;
	//				break;
	//			}
	//		}

	//		if (!codecFound) {
	//			videoWriter.release(); // Ensure cleanup
	//			std::cerr << "Error: Could not open video writer with any codec!" << std::endl;
	//			std::cerr << "Install OpenCV with FFMPEG support for H.264 encoding." << std::endl;
	//			return false;
	//		}

	//		const int numImages = static_cast<int>(imageFiles.size());

	//		// Pre-create black canvas and ROI (reuse for all frames)
	//		cv::Mat canvas = cv::Mat::zeros(targetHeight, targetWidth, CV_8UC3);
	//		const cv::Rect roi(padLeft, padTop, scaledWidth, scaledHeight);

	//		// Pre-allocate matrices for reuse (reduces allocations)
	//		cv::Mat img;
	//		cv::Mat resizedImg;

	//		if (numImages <= totalFrames) {
	//			// Case 1: Few images - each shown for multiple frames
	//			const int framesPerImage = totalFrames / numImages;
	//			const int remainingFrames = totalFrames % numImages;

	//			for (int i = 0; i < numImages; ++i) {
	//				img = cv::imread(imageFiles[i]);
	//				if (img.empty()) {
	//					std::cerr << "Warning: Could not read image: " << imageFiles[i] << std::endl;
	//					continue;
	//				}

	//				// Reset canvas to black
	//				canvas.setTo(cv::Scalar(0, 0, 0));

	//				// Resize and place on canvas
	//				cv::resize(img, resizedImg, cv::Size(scaledWidth, scaledHeight),
	//					0, 0, cv::INTER_AREA);
	//				resizedImg.copyTo(canvas(roi));

	//				// Release img to free memory immediately
	//				img.release();

	//				// Calculate frames for this image
	//				int framesToWrite = framesPerImage + (i < remainingFrames ? 1 : 0);

	//				// Write frames
	//				for (int j = 0; j < framesToWrite; ++j) {
	//					videoWriter.write(canvas);
	//				}
	//			}
	//		}
	//		else {
	//			// Case 2: Many images - sample to fit total frames
	//			for (int frame = 0; frame < totalFrames; ++frame) {
	//				// Calculate which image to use
	//				const double imageIndex = (static_cast<double>(frame) * (numImages - 1)) /
	//					(totalFrames - 1);
	//				const int imageIdx = static_cast<int>(std::round(imageIndex));

	//				img = cv::imread(imageFiles[imageIdx]);
	//				if (img.empty()) {
	//					std::cerr << "Warning: Could not read image: " << imageFiles[imageIdx] << std::endl;
	//					continue;
	//				}

	//				// Reset canvas to black
	//				canvas.setTo(cv::Scalar(0, 0, 0));

	//				// Resize and place on canvas
	//				cv::resize(img, resizedImg, cv::Size(scaledWidth, scaledHeight),
	//					0, 0, cv::INTER_AREA);
	//				resizedImg.copyTo(canvas(roi));

	//				// Release img to free memory immediately
	//				img.release();

	//				videoWriter.write(canvas);
	//			}
	//		}

	//		// Explicit cleanup
	//		canvas.release();
	//		resizedImg.release();
	//		videoWriter.release();

	//		std::cout << "Video created successfully: " << mp4OutputPath << std::endl;
	//		std::cout << "Dimensions: " << targetWidth << "x" << targetHeight << std::endl;
	//		std::cout << "Frame rate: " << targetFPS << " fps" << std::endl;
	//		std::cout << "Duration: " << videoDurationSec << " seconds" << std::endl;
	//		std::cout << "Total frames: " << totalFrames << std::endl;
	//		std::cout << "Images used: " << numImages << std::endl;

	//		return true;
	//	}
	//	catch (const cv::Exception& e) {
	//		if (videoWriter.isOpened()) {
	//			videoWriter.release();
	//		}
	//		std::cerr << "OpenCV exception in ImagesToMP4: " << e.what() << std::endl;
	//		return false;
	//	}
	//	catch (const std::exception& e) {
	//		if (videoWriter.isOpened()) {
	//			videoWriter.release();
	//		}
	//		std::cerr << "Exception in ImagesToMP4: " << e.what() << std::endl;
	//		return false;
	//	}
	//}
//	bool ANSOPENCV::ImagesToMP4(const std::string& imageFolder, const std::string& outputVideoPath, int targetDurationSec) {
//	try {
//		// Collect all image files efficiently
//		std::vector<cv::String> imageFiles;
//		const std::vector<std::string> extensions = { "*.jpg", "*.jpeg", "*.png", "*.bmp" };
//
//		for (const auto& ext : extensions) {
//			std::vector<cv::String> temp;
//			cv::glob(imageFolder + "/" + ext, temp, false);
//			imageFiles.insert(imageFiles.end(), temp.begin(), temp.end());
//		}
//
//		if (imageFiles.empty()) {
//			std::cerr << "Error: No images found in folder: " << imageFolder << std::endl;
//			return false;
//		}
//
//		// Sort for consistent ordering
//		std::sort(imageFiles.begin(), imageFiles.end());
//
//		// Read first image to determine dimensions
//		cv::Mat firstImage = cv::imread(imageFiles[0]);
//		if (firstImage.empty()) {
//			std::cerr << "Error: Could not read first image: " << imageFiles[0] << std::endl;
//			return false;
//		}
//
//		// Target video dimensions (1920x1080)
//		const int targetWidth = 1920;
//		const int targetHeight = 1080;
//
//		// Calculate scaling to fit within bounds while maintaining aspect ratio
//		const double scaleX = static_cast<double>(targetWidth) / firstImage.cols;
//		const double scaleY = static_cast<double>(targetHeight) / firstImage.rows;
//		const double scale = min(scaleX, scaleY);
//
//		// Calculate scaled dimensions (ensure even for H.264)
//		int scaledWidth = static_cast<int>(std::round(firstImage.cols * scale));
//		int scaledHeight = static_cast<int>(std::round(firstImage.rows * scale));
//
//		// Make dimensions even (required for H.264)
//		scaledWidth = (scaledWidth / 2) * 2;
//		scaledHeight = (scaledHeight / 2) * 2;
//
//		// Calculate centering padding
//		const int padLeft = (targetWidth - scaledWidth) / 2;
//		const int padTop = (targetHeight - scaledHeight) / 2;
//
//		std::cout << "Original image size: " << firstImage.cols << "x" << firstImage.rows << std::endl;
//		std::cout << "Scaled image size: " << scaledWidth << "x" << scaledHeight << std::endl;
//		std::cout << "Final video size: " << targetWidth << "x" << targetHeight << std::endl;
//		std::cout << "Padding: left=" << padLeft << ", top=" << padTop << std::endl;
//
//		// Video parameters
//		const int targetFPS = 25;
//		const int videoDurationSec = max(3, targetDurationSec);
//		const int totalFrames = videoDurationSec * targetFPS;
//
//		// Ensure .mp4 extension
//		std::string mp4OutputPath = outputVideoPath;
//		if (mp4OutputPath.size() < 4 ||
//			mp4OutputPath.substr(mp4OutputPath.size() - 4) != ".mp4") {
//			mp4OutputPath += ".mp4";
//		}
//
//		// Try codecs in order of preference
//		cv::VideoWriter videoWriter;
//		const std::vector<std::tuple<std::string, int>> codecs = {
//			{"x264 (best for web)", cv::VideoWriter::fourcc('x', '2', '6', '4')},
//			{"H264 (good for web)", cv::VideoWriter::fourcc('H', '2', '6', '4')},
//			{"MP4V (web compatible)", cv::VideoWriter::fourcc('M', 'P', '4', 'V')},
//			{"MJPG (large files)", cv::VideoWriter::fourcc('M', 'J', 'P', 'G')}
//		};
//
//		bool codecFound = false;
//		for (const auto& [name, fourcc] : codecs) {
//			videoWriter.open(mp4OutputPath, fourcc, targetFPS,
//				cv::Size(targetWidth, targetHeight), true);
//			if (videoWriter.isOpened()) {
//				std::cout << "Using codec: " << name << std::endl;
//				codecFound = true;
//				break;
//			}
//		}
//
//		if (!codecFound) {
//			std::cerr << "Error: Could not open video writer with any codec!" << std::endl;
//			std::cerr << "Install OpenCV with FFMPEG support for H.264 encoding." << std::endl;
//			return false;
//		}
//
//		const int numImages = static_cast<int>(imageFiles.size());
//
//		// Pre-create black canvas and ROI (reuse for all frames)
//		cv::Mat canvas = cv::Mat::zeros(targetHeight, targetWidth, CV_8UC3);
//		const cv::Rect roi(padLeft, padTop, scaledWidth, scaledHeight);
//
//		if (numImages <= totalFrames) {
//			// Case 1: Few images - each shown for multiple frames
//			const int framesPerImage = totalFrames / numImages;
//			const int remainingFrames = totalFrames % numImages;
//
//			for (int i = 0; i < numImages; ++i) {
//				cv::Mat img = cv::imread(imageFiles[i]);
//				if (img.empty()) {
//					std::cerr << "Warning: Could not read image: " << imageFiles[i] << std::endl;
//					continue;
//				}
//
//				// Reset canvas to black
//				canvas.setTo(cv::Scalar(0, 0, 0));
//
//				// Resize and place on canvas
//				cv::Mat resizedImg;
//				cv::resize(img, resizedImg, cv::Size(scaledWidth, scaledHeight),
//					0, 0, cv::INTER_AREA);
//				resizedImg.copyTo(canvas(roi));
//
//				// Calculate frames for this image
//				int framesToWrite = framesPerImage + (i < remainingFrames ? 1 : 0);
//
//				// Write frames
//				for (int j = 0; j < framesToWrite; ++j) {
//					videoWriter.write(canvas);
//				}
//			}
//		}
//		else {
//			// Case 2: Many images - sample to fit total frames
//			for (int frame = 0; frame < totalFrames; ++frame) {
//				// Calculate which image to use
//				const double imageIndex = (static_cast<double>(frame) * (numImages - 1)) /
//					(totalFrames - 1);
//				const int imageIdx = static_cast<int>(std::round(imageIndex));
//
//				cv::Mat img = cv::imread(imageFiles[imageIdx]);
//				if (img.empty()) {
//					std::cerr << "Warning: Could not read image: " << imageFiles[imageIdx] << std::endl;
//					continue;
//				}
//
//				// Reset canvas to black
//				canvas.setTo(cv::Scalar(0, 0, 0));
//
//				// Resize and place on canvas
//				cv::Mat resizedImg;
//				cv::resize(img, resizedImg, cv::Size(scaledWidth, scaledHeight),
//					0, 0, cv::INTER_AREA);
//				resizedImg.copyTo(canvas(roi));
//
//				videoWriter.write(canvas);
//			}
//		}
//
//		videoWriter.release();
//
//		std::cout << "Video created successfully: " << mp4OutputPath << std::endl;
//		std::cout << "Dimensions: " << targetWidth << "x" << targetHeight << std::endl;
//		std::cout << "Frame rate: " << targetFPS << " fps" << std::endl;
//		std::cout << "Duration: " << videoDurationSec << " seconds" << std::endl;
//		std::cout << "Total frames: " << totalFrames << std::endl;
//		std::cout << "Images used: " << numImages << std::endl;
//
//		return true;
//	}
//	catch (const cv::Exception& e) {
//		std::cerr << "OpenCV exception in ImagesToMP4: " << e.what() << std::endl;
//		return false;
//	}
//	catch (const std::exception& e) {
//		std::cerr << "Exception in ImagesToMP4: " << e.what() << std::endl;
//		return false;
//	}
//}
}
extern "C" __declspec(dllexport) int CreateANSCVHandle(ANSCENTER::ANSOPENCV** Handle, const char* licenseKey) {
	if (!Handle || !licenseKey) return -1;
	try {
		auto ptr = std::make_unique<ANSCENTER::ANSOPENCV>();
		bool result = ptr->Init(licenseKey);
		if (result) {
			*Handle = ptr.release();
			return 1;
		}
		*Handle = nullptr;
		return 0;
	}
	catch (...) { return -1; }
}
extern "C" __declspec(dllexport) int ReleaseANSCVHandle(ANSCENTER::ANSOPENCV** Handle) {
	if (Handle == nullptr || *Handle == nullptr) return -1;
	try {
		std::unique_ptr<ANSCENTER::ANSOPENCV> ptr(*Handle);
		*Handle = nullptr;
		return 0;
	}
	catch (...) {
		if (Handle) *Handle = nullptr;
		return -1;
	}
}
extern "C" __declspec(dllexport) int ANSCV_ImageResize(ANSCENTER::ANSOPENCV** Handle,unsigned char* inputImage,unsigned int bufferLength,int width,int height,LStrHandle outputImage)
{
	// Validate inputs
	if (!Handle || !*Handle) {
		std::cerr << "Error: Invalid Handle in ANSCV_ImageResize" << std::endl;
		return 0;
	}

	if (!inputImage || bufferLength == 0) {
		std::cerr << "Error: Invalid input image in ANSCV_ImageResize" << std::endl;
		return 0;
	}

	if (!outputImage) {
		std::cerr << "Error: Invalid output handle in ANSCV_ImageResize" << std::endl;
		return 0;
	}

	if (width <= 0 || height <= 0) {
		std::cerr << "Error: Invalid dimensions in ANSCV_ImageResize" << std::endl;
		return 0;
	}

	try {
		// Decode input image (no copy needed - uses existing buffer)
		cv::Mat inputFrame = cv::imdecode(
			cv::Mat(1, bufferLength, CV_8UC1, inputImage),
			cv::IMREAD_COLOR
		);

		if (inputFrame.empty()) {
			std::cerr << "Error: Failed to decode image in ANSCV_ImageResize" << std::endl;
			return 0;
		}

		// Resize image
		cv::Mat outputFrame;
		(*Handle)->ImageResize(inputFrame, width, height, outputFrame);

		if (outputFrame.empty()) {
			std::cerr << "Error: ImageResize failed in ANSCV_ImageResize" << std::endl;
			return 0;
		}

		// Convert to binary data
		std::string binaryData = (*Handle)->MatToBinaryData(outputFrame);

		// CRITICAL: No need to manually release - RAII handles it
		// inputFrame.release();  // Removed - automatic
		// outputFrame.release(); // Removed - automatic

		const int size = static_cast<int>(binaryData.length());

		if (size <= 0) {
			std::cerr << "Error: MatToBinaryData returned empty in ANSCV_ImageResize" << std::endl;
			return 0;
		}

		// Resize LabVIEW string handle
		MgErr error = DSSetHandleSize(outputImage, sizeof(int32) + size * sizeof(uChar));

		if (error != noErr) {
			std::cerr << "Error: DSSetHandleSize failed in ANSCV_ImageResize (error code: "
				<< error << ")" << std::endl;
			return 0;
		}

		// Copy data to LabVIEW string
		(*outputImage)->cnt = size;
		memcpy((*outputImage)->str, binaryData.c_str(), size);

		return 1;  // Success
	}
	catch (const cv::Exception& e) {
		std::cerr << "OpenCV exception in ANSCV_ImageResize: " << e.what() << std::endl;
		return 0;
	}
	catch (const std::exception& e) {
		std::cerr << "Exception in ANSCV_ImageResize: " << e.what() << std::endl;
		return 0;
	}
	catch (...) {
		std::cerr << "Unknown exception in ANSCV_ImageResize" << std::endl;
		return 0;
	}
}

extern "C" __declspec(dllexport) int ANSCV_ImageResizeWithRatio(ANSCENTER::ANSOPENCV** Handle,unsigned char* inputImage,unsigned int bufferLength,int width,LStrHandle outputImage)
{
	// Validate inputs
	if (!Handle || !*Handle) {
		std::cerr << "Error: Invalid Handle in ANSCV_ImageResizeWithRatio" << std::endl;
		return 0;
	}

	if (!inputImage || bufferLength == 0) {
		std::cerr << "Error: Invalid input image in ANSCV_ImageResizeWithRatio" << std::endl;
		return 0;
	}

	if (!outputImage) {
		std::cerr << "Error: Invalid output handle in ANSCV_ImageResizeWithRatio" << std::endl;
		return 0;
	}

	if (width <= 0) {
		std::cerr << "Error: Invalid width in ANSCV_ImageResizeWithRatio" << std::endl;
		return 0;
	}

	try {
		// Decode input image
		cv::Mat inputFrame = cv::imdecode(
			cv::Mat(1, bufferLength, CV_8UC1, inputImage),
			cv::IMREAD_COLOR
		);

		if (inputFrame.empty()) {
			std::cerr << "Error: Failed to decode image in ANSCV_ImageResizeWithRatio" << std::endl;
			return 0;
		}

		// Resize with aspect ratio preservation
		cv::Mat outputFrame;
		(*Handle)->ImageResizeWithRatio(inputFrame, width, outputFrame);

		if (outputFrame.empty()) {
			std::cerr << "Error: ImageResizeWithRatio failed" << std::endl;
			return 0;
		}

		// Convert to binary data
		std::string binaryData = (*Handle)->MatToBinaryData(outputFrame);

		// RAII handles cleanup automatically - no manual .release() needed

		const int size = static_cast<int>(binaryData.length());

		if (size <= 0) {
			std::cerr << "Error: MatToBinaryData returned empty" << std::endl;
			return 0;
		}

		// Resize LabVIEW string handle
		MgErr error = DSSetHandleSize(outputImage, sizeof(int32) + size * sizeof(uChar));

		if (error != noErr) {
			std::cerr << "Error: DSSetHandleSize failed (error code: " << error << ")" << std::endl;
			return 0;
		}

		// Copy data to LabVIEW string
		(*outputImage)->cnt = size;
		memcpy((*outputImage)->str, binaryData.c_str(), size);

		return 1;  // Success
	}
	catch (const cv::Exception& e) {
		std::cerr << "OpenCV exception in ANSCV_ImageResizeWithRatio: " << e.what() << std::endl;
		return 0;
	}
	catch (const std::exception& e) {
		std::cerr << "Exception in ANSCV_ImageResizeWithRatio: " << e.what() << std::endl;
		return 0;
	}
	catch (...) {
		std::cerr << "Unknown exception in ANSCV_ImageResizeWithRatio" << std::endl;
		return 0;
	}
}
extern "C" __declspec(dllexport) int ANSCV_ImageToBase64(ANSCENTER::ANSOPENCV** Handle,unsigned char* inputImage,unsigned int bufferLength,LStrHandle outputImage)
{
	// Validate inputs
	if (!Handle || !*Handle) {
		std::cerr << "Error: Invalid Handle in ANSCV_ImageToBase64" << std::endl;
		return 0;
	}

	if (!inputImage || bufferLength == 0) {
		std::cerr << "Error: Invalid input image in ANSCV_ImageToBase64" << std::endl;
		return 0;
	}

	if (!outputImage) {
		std::cerr << "Error: Invalid output handle in ANSCV_ImageToBase64" << std::endl;
		return 0;
	}

	try {
		// Decode input image
		cv::Mat inputFrame = cv::imdecode(
			cv::Mat(1, bufferLength, CV_8UC1, inputImage),
			cv::IMREAD_COLOR
		);

		if (inputFrame.empty()) {
			std::cerr << "Error: Failed to decode image in ANSCV_ImageToBase64" << std::endl;
			return 0;
		}

		// Convert to Base64
		std::string base64Data = (*Handle)->MatToBase64(inputFrame);

		// RAII handles cleanup automatically - no manual .release() needed

		const int size = static_cast<int>(base64Data.length());

		if (size <= 0) {
			std::cerr << "Error: MatToBase64 returned empty string" << std::endl;
			return 0;
		}

		// Resize LabVIEW string handle
		MgErr error = DSSetHandleSize(outputImage, sizeof(int32) + size * sizeof(uChar));

		if (error != noErr) {
			std::cerr << "Error: DSSetHandleSize failed in ANSCV_ImageToBase64 (error code: "
				<< error << ")" << std::endl;
			return 0;
		}

		// Copy data to LabVIEW string
		(*outputImage)->cnt = size;
		memcpy((*outputImage)->str, base64Data.c_str(), size);

		return 1;  // Success
	}
	catch (const cv::Exception& e) {
		std::cerr << "OpenCV exception in ANSCV_ImageToBase64: " << e.what() << std::endl;
		return 0;
	}
	catch (const std::exception& e) {
		std::cerr << "Exception in ANSCV_ImageToBase64: " << e.what() << std::endl;
		return 0;
	}
	catch (...) {
		std::cerr << "Unknown exception in ANSCV_ImageToBase64" << std::endl;
		return 0;
	}
}
extern "C" __declspec(dllexport) int ANSCV_ImageToGray(ANSCENTER::ANSOPENCV** Handle,unsigned char* inputImage,unsigned int bufferLength,LStrHandle outputImage)
{
	if (Handle == nullptr || *Handle == nullptr) return -1;
	try {
		cv::Mat inputFrame = cv::imdecode(cv::Mat(1, bufferLength, CV_8UC1, inputImage), cv::IMREAD_COLOR);
		cv::Mat outputFrame = (*Handle)->ToGray(inputFrame);
		std::string st = (*Handle)->MatToBinaryData(outputFrame);
		inputFrame.release();
		outputFrame.release();
		int size = st.length();
		if (size > 0) {
			MgErr error;
			error = DSSetHandleSize(outputImage, sizeof(int32) + size * sizeof(uChar));
			if (error == noErr)
			{
				(*outputImage)->cnt = size;
				memcpy((*outputImage)->str, st.c_str(), size);
				return 1;
			}
			else return 0;
		}
		else return 0;
	}
	catch (...) { return -1; }
}
extern "C" __declspec(dllexport) int ANSCV_ImageDenoise(ANSCENTER::ANSOPENCV** Handle, unsigned char* inputImage, unsigned int bufferLength, LStrHandle outputImage) {
	if (Handle == nullptr || *Handle == nullptr) return -1;
	try {
		cv::Mat inputFrame = cv::imdecode(cv::Mat(1, bufferLength, CV_8UC1, inputImage), cv::IMREAD_COLOR);
		cv::Mat outputFrame = (*Handle)->ImageDenoise(inputFrame);
		std::string st = (*Handle)->MatToBinaryData(outputFrame);
		//inputFrame.release();
		//outputFrame.release();
		int size = st.length();
		if (size > 0) {
			MgErr error;
			error = DSSetHandleSize(outputImage, sizeof(int32) + size * sizeof(uChar));
			if (error == noErr)
			{
				(*outputImage)->cnt = size;
				memcpy((*outputImage)->str, st.c_str(), size);
				return 1;
			}
			else return 0;
		}
		else return 0;
	}
	catch (...) { return -1; }
}
extern "C" __declspec(dllexport) int ANSCV_ImageRepair(ANSCENTER::ANSOPENCV** Handle, unsigned char* inputImage, unsigned int bufferLength, LStrHandle outputImage) {
	if (Handle == nullptr || *Handle == nullptr) return -1;
	try {
		cv::Mat inputFrame = cv::imdecode(cv::Mat(1, bufferLength, CV_8UC1, inputImage), cv::IMREAD_COLOR);
		cv::Mat outputFrame = (*Handle)->ImageRepair(inputFrame);
		std::string st = (*Handle)->MatToBinaryData(outputFrame);
		//inputFrame.release();
		//outputFrame.release();
		int size = st.length();
		if (size > 0) {
			MgErr error;
			error = DSSetHandleSize(outputImage, sizeof(int32) + size * sizeof(uChar));
			if (error == noErr)
			{
				(*outputImage)->cnt = size;
				memcpy((*outputImage)->str, st.c_str(), size);
				return 1;
			}
			else return 0;
		}
		else return 0;
	}
	catch (...) { return -1; }
}
extern "C" __declspec(dllexport) int ANSCV_ImageAutoWhiteBalance(ANSCENTER::ANSOPENCV** Handle, unsigned char* inputImage, unsigned int bufferLength, LStrHandle outputImage) {
	if (Handle == nullptr || *Handle == nullptr) return -1;
	try {
		cv::Mat inputFrame = cv::imdecode(cv::Mat(1, bufferLength, CV_8UC1, inputImage), cv::IMREAD_COLOR);
		cv::Mat outputFrame = (*Handle)->ImageWhiteBalance(inputFrame);
		std::string st = (*Handle)->MatToBinaryData(outputFrame);
		//inputFrame.release();
		//outputFrame.release();
		int size = st.length();
		if (size > 0) {
			MgErr error;
			error = DSSetHandleSize(outputImage, sizeof(int32) + size * sizeof(uChar));
			if (error == noErr)
			{
				(*outputImage)->cnt = size;
				memcpy((*outputImage)->str, st.c_str(), size);
				return 1;
			}
			else return 0;
		}
		else return 0;
	}
	catch (...) { return -1; }
}
extern "C" __declspec(dllexport) int ANSCV_ImageBrightEnhance(ANSCENTER::ANSOPENCV** Handle, unsigned char* inputImage, unsigned int bufferLength, double brightnessScaleFactor, LStrHandle outputImage) {
	if (Handle == nullptr || *Handle == nullptr) return -1;
	try {
		cv::Mat inputFrame = cv::imdecode(cv::Mat(1, bufferLength, CV_8UC1, inputImage), cv::IMREAD_COLOR);
		cv::Mat outputFrame = (*Handle)->ImageDarkEnhancement(inputFrame, brightnessScaleFactor);
		std::string st = (*Handle)->MatToBinaryData(outputFrame);
		//inputFrame.release();
		//outputFrame.release();
		int size = st.length();
		if (size > 0) {
			MgErr error;
			error = DSSetHandleSize(outputImage, sizeof(int32) + size * sizeof(uChar));
			if (error == noErr)
			{
				(*outputImage)->cnt = size;
				memcpy((*outputImage)->str, st.c_str(), size);
				return 1;
			}
			else return 0;
		}
		else return 0;
	}
	catch (...) { return -1; }
}
extern "C" __declspec(dllexport) int ANSCV_ImageContrastEnhance(ANSCENTER::ANSOPENCV** Handle, unsigned char* inputImage, unsigned int bufferLength, LStrHandle outputImage) {
	if (Handle == nullptr || *Handle == nullptr) return -1;
	try {
		cv::Mat inputFrame = cv::imdecode(cv::Mat(1, bufferLength, CV_8UC1, inputImage), cv::IMREAD_COLOR);
		cv::Mat outputFrame = (*Handle)->ImageContrastEnhancement(inputFrame);
		std::string st = (*Handle)->MatToBinaryData(outputFrame);
		//inputFrame.release();
		//outputFrame.release();
		int size = st.length();
		if (size > 0) {
			MgErr error;
			error = DSSetHandleSize(outputImage, sizeof(int32) + size * sizeof(uChar));
			if (error == noErr)
			{
				(*outputImage)->cnt = size;
				memcpy((*outputImage)->str, st.c_str(), size);
				return 1;
			}
			else return 0;
		}
		else return 0;
	}
	catch (...) { return -1; }
}
extern "C" __declspec(dllexport) int ANSCV_BlurObjects(ANSCENTER::ANSOPENCV** Handle, unsigned char* inputImage, unsigned int bufferLength, const char* strBboxes, LStrHandle outputImage) {
	if (Handle == nullptr || *Handle == nullptr) return -1;
	try {
		cv::Mat inputFrame = cv::imdecode(cv::Mat(1, bufferLength, CV_8UC1, inputImage), cv::IMREAD_COLOR);
		std::vector<cv::Rect> objects = (*Handle)->GetBoundingBoxes(strBboxes);
		cv::Mat outputFrame = (*Handle)->BlurObjects(inputFrame, objects);
		std::string st = (*Handle)->MatToBinaryData(outputFrame);
		//inputFrame.release();
		//outputFrame.release();
		int size = st.length();
		if (size > 0) {
			MgErr error;
			error = DSSetHandleSize(outputImage, sizeof(int32) + size * sizeof(uChar));
			if (error == noErr)
			{
				(*outputImage)->cnt = size;
				memcpy((*outputImage)->str, st.c_str(), size);
				return 1;
			}
			else return 0;
		}
		else return 0;
	}
	catch (...) { return -1; }
}
extern "C" __declspec(dllexport) int ANSCV_BlurBackground(ANSCENTER::ANSOPENCV** Handle, unsigned char* inputImage, unsigned int bufferLength, const char* strBboxes, LStrHandle outputImage) {
	if (Handle == nullptr || *Handle == nullptr) return -1;
	try {
		cv::Mat inputFrame = cv::imdecode(cv::Mat(1, bufferLength, CV_8UC1, inputImage), cv::IMREAD_COLOR);
		std::vector<cv::Rect> objects = (*Handle)->GetBoundingBoxes(strBboxes);
		cv::Mat outputFrame = (*Handle)->BlurBackground(inputFrame, objects);
		std::string st = (*Handle)->MatToBinaryData(outputFrame);
		//outputFrame.release();
		//inputFrame.release();
		int size = st.length();
		if (size > 0) {
			MgErr error;
			error = DSSetHandleSize(outputImage, sizeof(int32) + size * sizeof(uChar));
			if (error == noErr)
			{
				(*outputImage)->cnt = size;
				memcpy((*outputImage)->str, st.c_str(), size);
				return 1;
			}
			else return 0;
		}
		else return 0;
	}
	catch (...) { return -1; }
}
extern "C" __declspec(dllexport) int ANSCV_QRDecoder(ANSCENTER::ANSOPENCV** Handle, unsigned char* inputImage, unsigned int bufferLength, LStrHandle detectedQRText) {
	if (Handle == nullptr || *Handle == nullptr) return -1;
	try {
		cv::Mat inputFrame = cv::imdecode(cv::Mat(1, bufferLength, CV_8UC1, inputImage), cv::IMREAD_COLOR);
		std::string st = (*Handle)->QRDecoder(inputFrame);
		//inputFrame.release();
		int size = st.length();
		if (size > 0) {
			MgErr error;
			error = DSSetHandleSize(detectedQRText, sizeof(int32) + size * sizeof(uChar));
			if (error == noErr)
			{
				(*detectedQRText)->cnt = size;
				memcpy((*detectedQRText)->str, st.c_str(), size);
				return 1;
			}
			else return 0;
		}
		else return 0;
	}
	catch (...) { return -1; }
}
extern "C" __declspec(dllexport) int ANSCV_QRDecoderCV(ANSCENTER::ANSOPENCV** Handle, const cv::Mat& image, std::string& detectedQRText) {
	if (Handle == nullptr || *Handle == nullptr) return -1;
	if (image.empty()) return 0; // Indicate failure if image is empty
	try {
		detectedQRText = (*Handle)->QRDecoder(image);
		return 1; // Indicate success
	}
	catch (const std::exception& e) {
		std::cerr << "Error in ANSCV_QRDecoderCV: " << e.what() << std::endl;
		return -1; // Indicate failure
	}
	catch (...) { return -1; }
}
extern "C" __declspec(dllexport) int ANSCV_PatternMatchs(ANSCENTER::ANSOPENCV** Handle, unsigned char* inputImage, unsigned int bufferLength, const char* templateFilePath, double threshold, LStrHandle detectedMatchedLocations) {
	if (Handle == nullptr || *Handle == nullptr) return -1;
	try {
		cv::Mat inputFrame = cv::imdecode(cv::Mat(1, bufferLength, CV_8UC1, inputImage), cv::IMREAD_COLOR);
		cv::Mat templateImage = cv::imread(templateFilePath, cv::IMREAD_COLOR);
		std::string st = (*Handle)->PatternMatches(inputFrame, templateImage, threshold);
		//inputFrame.release();
		//templateImage.release();
		int size = st.length();
		if (size > 0) {
			MgErr error;
			error = DSSetHandleSize(detectedMatchedLocations, sizeof(int32) + size * sizeof(uChar));
			if (error == noErr)
			{
				(*detectedMatchedLocations)->cnt = size;
				memcpy((*detectedMatchedLocations)->str, st.c_str(), size);
				return 1;
			}
			else return 0;
		}
		else return 0;
	}
	catch (...) { return -1; }
}
extern "C" __declspec(dllexport) int ANSCV_ImageCrop(ANSCENTER::ANSOPENCV** Handle, unsigned char* inputImage, unsigned int bufferLength, int x, int y, int width, int height, LStrHandle outputImage)
{
	if (Handle == nullptr || *Handle == nullptr) return -1;
	try {
		cv::Mat inputFrame = cv::imdecode(cv::Mat(1, bufferLength, CV_8UC1, inputImage), cv::IMREAD_COLOR);
		cv::Rect roi(x, y, width, height);
		cv::Mat outputFrame = (*Handle)->ImageCrop(inputFrame, roi);
		std::string st = (*Handle)->MatToBinaryData(outputFrame);
		outputFrame.release();
		int size = st.length();
		if (size > 0) {
			MgErr error;
			error = DSSetHandleSize(outputImage, sizeof(int32) + size * sizeof(uChar));
			if (error == noErr)
			{
				(*outputImage)->cnt = size;
				memcpy((*outputImage)->str, st.c_str(), size);
				return 1;
			}
			else return 0;
		}
		else return 0;
	}
	catch (...) { return -1; }
}
extern "C" __declspec(dllexport) int ANSCV_GetImageSize(ANSCENTER::ANSOPENCV** Handle, unsigned char* inputImage, unsigned int bufferLength, LStrHandle imageSize) {
	if (Handle == nullptr || *Handle == nullptr) return -1;
	try {
		cv::Mat inputFrame = cv::imdecode(cv::Mat(1, bufferLength, CV_8UC1, inputImage), cv::IMREAD_COLOR);
		int width, height;
		width = inputFrame.cols;
		height = inputFrame.rows;
		//inputFrame.release();
		std::string st = std::to_string(width) + "x" + std::to_string(height);
		int size = st.length();
		if (size > 0) {
			MgErr error;
			error = DSSetHandleSize(imageSize, sizeof(int32) + size * sizeof(uChar));
			if (error == noErr)
			{
				(*imageSize)->cnt = size;
				memcpy((*imageSize)->str, st.c_str(), size);
				return 1;
			}
			else return 0;
		}
		else return 0;
	}
	catch (...) { return -1; }
}
extern "C" __declspec(dllexport) int ANSCV_GetImageSizeFromImageFile(ANSCENTER::ANSOPENCV** Handle, const char* imageFilePath, LStrHandle imageSize) {
	if (Handle == nullptr || *Handle == nullptr) return -1;
	try {
		cv::VideoCapture cap(imageFilePath);
		int width, height;
		if (!cap.isOpened()) {
			std::cerr << "Error opening file: " << imageFilePath << std::endl;
			width = 0;
			height = 0;
		}
		else {
			width = static_cast<int>(cap.get(cv::CAP_PROP_FRAME_WIDTH));
			height = static_cast<int>(cap.get(cv::CAP_PROP_FRAME_HEIGHT));
		}
		cap.release();
		std::string st = std::to_string(width) + "x" + std::to_string(height);
		int size = st.length();
		if (size > 0) {
			MgErr error;
			error = DSSetHandleSize(imageSize, sizeof(int32) + size * sizeof(uChar));
			if (error == noErr)
			{
				(*imageSize)->cnt = size;
				memcpy((*imageSize)->str, st.c_str(), size);
				return 1;
			}
			else return 0;
		}
		else return 0;
	}
	catch (...) { return -1; }
}
extern "C" __declspec(dllexport) int ANSCV_RotateImage(ANSCENTER::ANSOPENCV** Handle, unsigned char* inputImage, unsigned int bufferLength, double angle, LStrHandle outputImage) {
	if (Handle == nullptr || *Handle == nullptr) return -1;
	try {
		cv::Mat inputFrame = cv::imdecode(cv::Mat(1, bufferLength, CV_8UC1, inputImage), cv::IMREAD_COLOR);
		cv::Mat outputFrame = (*Handle)->RotateImage(inputFrame, angle);
		std::string st = (*Handle)->MatToBinaryData(outputFrame);
		int size = st.length();
		if (size > 0) {
			MgErr error;
			error = DSSetHandleSize(outputImage, sizeof(int32) + size * sizeof(uChar));
			if (error == noErr)
			{
				(*outputImage)->cnt = size;
				memcpy((*outputImage)->str, st.c_str(), size);
				return 1;
			}
			else return 0;
		}
		else return 0;
	}
	catch (...) { return -1; }
}
extern "C" __declspec(dllexport) int ANSCV_FlipImage(ANSCENTER::ANSOPENCV** Handle, unsigned char* inputImage, unsigned int bufferLength, int flipCode, LStrHandle outputImage) {
	if (Handle == nullptr || *Handle == nullptr) return -1;
	try {
		cv::Mat inputFrame = cv::imdecode(cv::Mat(1, bufferLength, CV_8UC1, inputImage), cv::IMREAD_COLOR);
		cv::Mat outputFrame = (*Handle)->FlipImage(inputFrame, flipCode);
		std::string st = (*Handle)->MatToBinaryData(outputFrame);
		int size = st.length();
		if (size > 0) {
			MgErr error;
			error = DSSetHandleSize(outputImage, sizeof(int32) + size * sizeof(uChar));
			if (error == noErr)
			{
				(*outputImage)->cnt = size;
				memcpy((*outputImage)->str, st.c_str(), size);
				return 1;
			}
			else return 0;
		}
		else return 0;
	}
	catch (...) { return -1; }
}
extern "C" __declspec(dllexport) int ANSCV_ShiftImage(ANSCENTER::ANSOPENCV** Handle, unsigned char* inputImage, unsigned int bufferLength, int shiftX, int shiftY, LStrHandle outputImage) {
	if (Handle == nullptr || *Handle == nullptr) return -1;
	try {
		cv::Mat inputFrame = cv::imdecode(cv::Mat(1, bufferLength, CV_8UC1, inputImage), cv::IMREAD_COLOR);
		cv::Mat outputFrame = (*Handle)->ShiftImage(inputFrame, shiftX, shiftY);
		std::string st = (*Handle)->MatToBinaryData(outputFrame);
		int size = st.length();
		if (size > 0) {
			MgErr error;
			error = DSSetHandleSize(outputImage, sizeof(int32) + size * sizeof(uChar));
			if (error == noErr)
			{
				(*outputImage)->cnt = size;
				memcpy((*outputImage)->str, st.c_str(), size);
				return 1;
			}
			else return 0;
		}
		else return 0;
	}
	catch (...) { return -1; }
}
extern "C" __declspec(dllexport) int ANSCV_AddGaussianNoise(ANSCENTER::ANSOPENCV** Handle, unsigned char* inputImage, unsigned int bufferLength, double mean, double stddev, LStrHandle outputImage) {
	if (Handle == nullptr || *Handle == nullptr) return -1;
	try {
		cv::Mat inputFrame = cv::imdecode(cv::Mat(1, bufferLength, CV_8UC1, inputImage), cv::IMREAD_COLOR);
		cv::Mat outputFrame = (*Handle)->AddGaussianNoise(inputFrame, mean, stddev);
		std::string st = (*Handle)->MatToBinaryData(outputFrame);
		int size = st.length();
		if (size > 0) {
			MgErr error;
			error = DSSetHandleSize(outputImage, sizeof(int32) + size * sizeof(uChar));
			if (error == noErr)
			{
				(*outputImage)->cnt = size;
				memcpy((*outputImage)->str, st.c_str(), size);
				return 1;
			}
			else return 0;
		}
		else return 0;
	}
	catch (...) { return -1; }
}
extern "C" __declspec(dllexport) int ANSCV_AddSaltAndPepperNoise(ANSCENTER::ANSOPENCV** Handle, unsigned char* inputImage, unsigned int bufferLength, double amount, LStrHandle outputImage) {
	if (Handle == nullptr || *Handle == nullptr) return -1;
	try {
		cv::Mat inputFrame = cv::imdecode(cv::Mat(1, bufferLength, CV_8UC1, inputImage), cv::IMREAD_COLOR);
		cv::Mat outputFrame = (*Handle)->AddSaltAndPepperNoise(inputFrame, amount);
		std::string st = (*Handle)->MatToBinaryData(outputFrame);
		int size = st.length();
		if (size > 0) {
			MgErr error;
			error = DSSetHandleSize(outputImage, sizeof(int32) + size * sizeof(uChar));
			if (error == noErr)
			{
				(*outputImage)->cnt = size;
				memcpy((*outputImage)->str, st.c_str(), size);
				return 1;
			}
			else return 0;
		}
		else return 0;
	}
	catch (...) { return -1; }
}
extern "C" __declspec(dllexport) int ANSCV_AddSpeckleNoise(ANSCENTER::ANSOPENCV** Handle, unsigned char* inputImage, unsigned int bufferLength, double stddev, LStrHandle outputImage) {
	if (Handle == nullptr || *Handle == nullptr) return -1;
	try {
		cv::Mat inputFrame = cv::imdecode(cv::Mat(1, bufferLength, CV_8UC1, inputImage), cv::IMREAD_COLOR);
		cv::Mat outputFrame = (*Handle)->AddSpeckleNoise(inputFrame, stddev);
		std::string st = (*Handle)->MatToBinaryData(outputFrame);
		int size = st.length();
		if (size > 0) {
			MgErr error;
			error = DSSetHandleSize(outputImage, sizeof(int32) + size * sizeof(uChar));
			if (error == noErr)
			{
				(*outputImage)->cnt = size;
				memcpy((*outputImage)->str, st.c_str(), size);
				return 1;
			}
			else return 0;
		}
		else return 0;
	}
	catch (...) { return -1; }
}


extern "C" __declspec(dllexport) void ANSCV_InitCameraResource() {
	try {
		std::lock_guard<std::mutex> lock(imageMutex);  // Automatically locks and unlocks
		ANSCENTER::ANSOPENCV::InitCameraNetwork();
	}
	catch (...) { }
}
extern "C" __declspec(dllexport) void ANSCV_FreeCameraResource() {
	try {
		std::lock_guard<std::mutex> lock(imageMutex);  // Automatically locks and unlocks
		ANSCENTER::ANSOPENCV::DeinitCameraNetwork();
	}
	catch (...) { }
}

extern "C" __declspec(dllexport) int  ANSCV_ResizeImage_Static(unsigned char* inputImage, unsigned int bufferLength, int width, int height, int& newWidth, int& newHeight, LStrHandle outputImage) {
	// Lock-free: operates on caller-owned input/output buffers only. No
	// registered cv::Mat is touched, so the global timeImageMutex would
	// serialize calls without protecting any shared state.
	try {
		cv::Mat inputFrame = cv::imdecode(cv::Mat(1, bufferLength, CV_8UC1, inputImage), cv::IMREAD_COLOR);
		cv::Mat outputFrame = ANSCENTER::ANSOPENCV::resizeImageToFit(inputFrame, width, height, newWidth, newHeight);
		std::vector<uchar> imageData;
		bool success = cv::imencode(".jpg", outputFrame, imageData);
		inputFrame.release();
		outputFrame.release();
		if (success) {
			std::string st(imageData.begin(), imageData.end());
			int size = st.length();
			if (size > 0) {
				MgErr error;
				error = DSSetHandleSize(outputImage, sizeof(int32) + size * sizeof(uChar));
				if (error == noErr)
				{
					(*outputImage)->cnt = size;
					memcpy((*outputImage)->str, st.c_str(), size);
					return 1;
				}
				else return 0;
			}
			else return 0;
		}
		else return 0;
	}
	catch (const std::exception& e) {
		std::cerr << "Error: Exception occurred in ANSCV_ResizeImage_Static: " << e.what() << std::endl;
		return 0;
	}
}


// Image Reference Management

extern "C" __declspec(dllexport) int ANSCV_CloneImage_S(cv::Mat** imageIn, cv::Mat** imageOut) {
	ANS_DBG("ANSCV_CloneImage", "ENTRY imageIn=%p imageOut=%p src=%p",
		(void*)imageIn, (void*)imageOut, (imageIn ? (void*)*imageIn : nullptr));
	try {
		//std::lock_guard<std::mutex> lock(imageMutex);
		std::unique_lock<std::timed_mutex> lock(timeImageMutex, std::defer_lock);
		if (!lock.try_lock_for(std::chrono::milliseconds(MUTEX_TIMEOUT_MS))) {
			ANS_DBG("ANSCV_CloneImage", "ERROR mutex timeout (%dms)", (int)MUTEX_TIMEOUT_MS);
			std::cerr << "Error: Mutex timeout in ANSCV_CloneImage_S!" << std::endl;
			return -6;
		}
		try {
			if (!imageIn || !(*imageIn) || (*imageIn)->empty() || !(*imageIn)->data) {
				ANS_DBG("ANSCV_CloneImage", "ERROR invalid input imageIn=%p src=%p empty=%d data=%p",
					(void*)imageIn,
					(imageIn ? (void*)*imageIn : nullptr),
					(imageIn && *imageIn) ? (int)(*imageIn)->empty() : -1,
					(imageIn && *imageIn) ? (void*)(*imageIn)->data : nullptr);
				std::cerr << "Error: Invalid or empty input image in ANSCV_CloneImage_S!" << std::endl;
				return -2;
			}
			else {
				*imageOut = anscv_mat_new(**imageIn);
				// Link clone to same NV12 frame data (refcount++)
				gpu_frame_addref(*imageIn, *imageOut);
				ANS_DBG("ANSCV_CloneImage", "SUCCESS dst=%p size=%dx%d type=%d",
					(void*)*imageOut, (*imageOut)->cols, (*imageOut)->rows, (*imageOut)->type());
				return 1;  // Success
			}
		}
		catch (const std::bad_alloc& e) {
			ANS_DBG("ANSCV_CloneImage", "ERROR bad_alloc inner: %s", e.what());
			std::cerr << "Memory allocation failed in ANSCV_CloneImage_S: " << e.what() << std::endl;
			return -1;
		}
		catch (const std::exception& e) {
			ANS_DBG("ANSCV_CloneImage", "ERROR exception inner: %s", e.what());
			std::cerr << "Error: Exception occurred in ANSCV_CloneImage_S: " << e.what() << std::endl;
			return -3;
		}
	}
	catch (const std::bad_alloc& e) {
		ANS_DBG("ANSCV_CloneImage", "ERROR bad_alloc outer: %s", e.what());
		std::cerr << "Memory allocation failed in ANSCV_CloneImage_S: " << e.what() << std::endl;
		return -1;
	}
	catch (const std::exception& e) {
		ANS_DBG("ANSCV_CloneImage", "ERROR exception outer: %s", e.what());
		std::cerr << "Error: Exception occurred in ANSCV_CloneImage_S: " << e.what() << std::endl;
		return -3;
	}
}
extern "C" __declspec(dllexport) int ANSCV_ReleaseImage_S(cv::Mat** imageIn) {
	void* matPtr = (imageIn ? (void*)*imageIn : nullptr);
	ANS_DBG("ANSCV_ReleaseImage", "ENTRY imageIn=%p mat=%p", (void*)imageIn, matPtr);
	try {
		if (!imageIn || !(*imageIn)) {
			ANS_DBG("ANSCV_ReleaseImage", "SKIP null imageIn=%p mat=%p", (void*)imageIn, matPtr);
			return -2;
		}
		// anscv_mat_delete is thread-safe: checks the registry, only deletes if
		// the pointer is still registered (not already freed by a stream source).
		bool deleted = anscv_mat_delete(imageIn);
		ANS_DBG("ANSCV_ReleaseImage", "%s mat=%p", deleted ? "DELETED" : "NOT_REGISTERED", matPtr);
		return deleted ? 1 : -4;
	}
	catch (const std::exception& e) {
		ANS_DBG("ANSCV_ReleaseImage", "ERROR exception mat=%p: %s", matPtr, e.what());
		std::cerr << "Error: Exception occurred in ANSCV_ReleaseImage_S: " << e.what() << std::endl;
		return -3;
	}
	catch (...) {
		ANS_DBG("ANSCV_ReleaseImage", "ERROR unknown exception mat=%p", matPtr);
		std::cerr << "Error: Unknown exception in ANSCV_ReleaseImage_S." << std::endl;
		return -3;
	}
}
extern "C" __declspec(dllexport) int ANSCV_CropImage_S(cv::Mat** imageIn, int x, int y, int width, int height, int originalImageSize) {
	void* matPtr = (imageIn ? (void*)*imageIn : nullptr);
	ANS_DBG("ANSCV_CropImage", "ENTRY mat=%p roi=(%d,%d,%dx%d) origSize=%d",
		matPtr, x, y, width, height, originalImageSize);
	gpu_frame_invalidate(imageIn ? *imageIn : nullptr);
	try {
		// Step 1: Validate and copy input image safely
		cv::Mat localImage;
		{
			//std::lock_guard<std::mutex> lock(imageMutex);
			std::unique_lock<std::timed_mutex> lock(timeImageMutex, std::defer_lock);
			if (!lock.try_lock_for(std::chrono::milliseconds(MUTEX_TIMEOUT_MS))) {
				ANS_DBG("ANSCV_CropImage", "ERROR mutex timeout (copy phase) mat=%p", matPtr);
				std::cerr << "Error: Mutex timeout in ANSCV_CropImage_S!" << std::endl;
				return -6;
			}
			try {
				if (!imageIn || !(*imageIn) || (*imageIn)->empty() || !(*imageIn)->data) {
					ANS_DBG("ANSCV_CropImage", "ERROR invalid input mat=%p", matPtr);
					std::cerr << "Error: Invalid or empty input image in ANSCV_CloneImage_S!" << std::endl;
					return -2;
				}
				else {
					localImage = (**imageIn).clone();  // shallow copy (ref-counted)
				}
			}
			catch (const std::bad_alloc& e) {
				ANS_DBG("ANSCV_CropImage", "ERROR bad_alloc on copy mat=%p: %s", matPtr, e.what());
				std::cerr << "Memory allocation failed in ANSCV_CloneImage_S: " << e.what() << std::endl;
				return -1;
			}
			catch (const std::exception& e) {
				ANS_DBG("ANSCV_CropImage", "ERROR exception on copy mat=%p: %s", matPtr, e.what());
				std::cerr << "Error: Exception occurred in ANSCV_CloneImage_S: " << e.what() << std::endl;
				return -3;
			}
		}

		// Step 2: Validate cropping parameters
		if (width <= 0 || height <= 0) {
			ANS_DBG("ANSCV_CropImage", "ERROR invalid width/height w=%d h=%d", width, height);
			std::cerr << "Error: Invalid width or height for cropping image in ANSCV_CropImage_S!" << std::endl;
			return -2;
		}

		int originalWidth = localImage.cols;
		int originalHeight = localImage.rows;

		x = max(0, x);
		y = max(0, y);
		width = min(width, originalWidth - x);
		height = min(height, originalHeight - y);

		cv::Rect roi(x, y, width, height);
		ANS_DBG("ANSCV_CropImage", "CLAMPED roi=(%d,%d,%dx%d) src=%dx%d",
			x, y, width, height, originalWidth, originalHeight);

		// Step 3: Process crop outside lock
		ANSCENTER::ANSOPENCV ansCVInstance;
		if (!ansCVInstance.Init("")) {
			ANS_DBG("ANSCV_CropImage", "ERROR ansCVInstance.Init failed");
			std::cerr << "Error: Failed to initialize ANSCV instance!" << std::endl;
			return -5;
		}

		cv::Mat croppedImage = ansCVInstance.ImageCrop(localImage, roi, originalImageSize);
		// Step 4: Replace original image safely
		{
			std::unique_lock<std::timed_mutex> lock(timeImageMutex, std::defer_lock);
			if (!lock.try_lock_for(std::chrono::milliseconds(MUTEX_TIMEOUT_MS))) {
				ANS_DBG("ANSCV_CropImage", "ERROR mutex timeout (writeback) mat=%p", matPtr);
				std::cerr << "Error: Mutex timeout in ANSCV_CropImage_S!" << std::endl;
				return -6;
			}
			//std::lock_guard<std::mutex> lock(imageMutex);
			if (croppedImage.empty()) {
				ANS_DBG("ANSCV_CropImage", "ERROR cropped image empty mat=%p", matPtr);
				std::cerr << "Error: Failed to crop image in ANSCV_CropImage_S!" << std::endl;
				return 0;
			}
			else {
				if (!imageIn || !(*imageIn) || (*imageIn)->empty() || !(*imageIn)->data) {
					ANS_DBG("ANSCV_CropImage", "ERROR input invalid at writeback mat=%p",
						(imageIn ? (void*)*imageIn : nullptr));
					std::cerr << "Error: Invalid or empty input image in ANSCV_CloneImage_S!" << std::endl;
					return -2;
				}
				**imageIn = std::move(croppedImage);
			}

		}

		ANS_DBG("ANSCV_CropImage", "SUCCESS mat=%p new=%dx%d",
			(imageIn && *imageIn) ? (void*)*imageIn : nullptr,
			(imageIn && *imageIn) ? (*imageIn)->cols : -1,
			(imageIn && *imageIn) ? (*imageIn)->rows : -1);
		return 1; // Success
	}
	catch (const std::exception& e) {
		ANS_DBG("ANSCV_CropImage", "ERROR exception outer mat=%p: %s", matPtr, e.what());
		std::cerr << "Exception in ANSCV_CropImage_S: " << e.what() << std::endl;
		return -3;
	}
}

extern "C" __declspec(dllexport) int ANSCV_GetImage_CPP(cv::Mat** imageIn, int width, int quality,
	int& newWidth, int& newHeight, std::string& outputImage) {
	void* matPtr = (imageIn ? (void*)*imageIn : nullptr);
	ANS_DBG("ANSCV_GetImage_CPP", "ENTRY mat=%p targetW=%d quality=%d", matPtr, width, quality);
	try {
		if (!imageIn || !(*imageIn) || (*imageIn)->empty() || !(*imageIn)->data) {
			ANS_DBG("ANSCV_GetImage_CPP", "ERROR invalid input mat=%p", matPtr);
			std::cerr << "Error: Invalid or empty input image!" << std::endl;
			return -2;
		}

		cv::Mat* img = *imageIn;
		int originalWidth, originalHeight;

		// Quick dimension check under lock
		{
			std::unique_lock<std::timed_mutex> lock(timeImageMutex, std::chrono::milliseconds(1000));
			if (!lock.owns_lock()) {
				ANS_DBG("ANSCV_GetImage_CPP", "ERROR mutex timeout (dims) mat=%p", matPtr);
				std::cerr << "Error: Mutex timeout!" << std::endl;
				return -6;
			}

			if (img->empty() || !img->data) {
				ANS_DBG("ANSCV_GetImage_CPP", "ERROR invalid after lock mat=%p", matPtr);
				std::cerr << "Error: Invalid image!" << std::endl;
				return -2;
			}

			originalWidth = img->cols;
			originalHeight = img->rows;
		} // Release lock early

		int imageMaxSize = max(originalWidth, originalHeight);
		cv::Mat processedImage;

		if (width > 0 && width < imageMaxSize) {
			ANS_DBG("ANSCV_GetImage_CPP", "RESIZE mat=%p src=%dx%d targetW=%d",
				matPtr, originalWidth, originalHeight, width);
			// Only lock when we need to resize
			std::unique_lock<std::timed_mutex> lock(timeImageMutex, std::chrono::milliseconds(1000));
			if (!lock.owns_lock()) {
				ANS_DBG("ANSCV_GetImage_CPP", "ERROR mutex timeout (resize) mat=%p", matPtr);
				return -6;
			}

			ANSCENTER::ANSOPENCV ansCVInstance;
			if (!ansCVInstance.Init("")) {
				ANS_DBG("ANSCV_GetImage_CPP", "ERROR ansCVInstance.Init failed");
				return -5;
			}

			processedImage = ansCVInstance.ImageResizeV2(*img, width);
		}
		else {
			ANS_DBG("ANSCV_GetImage_CPP", "CLONE_ONLY mat=%p src=%dx%d targetW=%d",
				matPtr, originalWidth, originalHeight, width);
			// No resize needed - just copy quickly
			std::unique_lock<std::timed_mutex> lock(timeImageMutex, std::chrono::milliseconds(1000));
			if (!lock.owns_lock()) {
				ANS_DBG("ANSCV_GetImage_CPP", "ERROR mutex timeout (clone) mat=%p", matPtr);
				return -6;
			}

			processedImage = img->clone(); // Slightly faster than copyTo
		}

		if (processedImage.empty()) {
			ANS_DBG("ANSCV_GetImage_CPP", "ERROR processedImage empty mat=%p", matPtr);
			return -8;
		}

		newWidth = processedImage.cols;
		newHeight = processedImage.rows;

		outputImage = ANSCENTER::CompressJpegToString(processedImage, quality);
		if (outputImage.empty()) {
			ANS_DBG("ANSCV_GetImage_CPP", "ERROR JPEG compression empty mat=%p %dx%d q=%d",
				matPtr, newWidth, newHeight, quality);
			return -9;
		}
		ANS_DBG("ANSCV_GetImage_CPP", "SUCCESS mat=%p out=%dx%d jpegBytes=%zu",
			matPtr, newWidth, newHeight, outputImage.size());
		return 1;

	}
	catch (...) {
		ANS_DBG("ANSCV_GetImage_CPP", "ERROR unknown exception mat=%p", matPtr);
		return -4;
	}
}
//extern "C" __declspec(dllexport) int ANSCV_GetImage_S(cv::Mat** imageIn, int width, int quality, int& newWidth, int& newHeight, LStrHandle outputImage) {
//	try {
//		// Initial validation
//		if (!imageIn || !(*imageIn) || (*imageIn)->empty() || !(*imageIn)->data) {
//			std::cerr << "Error: Invalid or empty input image in ANSCV_GetImage_S!" << std::endl;
//			return -2;
//		}
//		if (!outputImage) {
//			std::cerr << "Error: Output image handle is null!" << std::endl;
//			return -2;
//		}
//
//		cv::Mat imgCopy;
//
//		// Critical section: Only lock during image copy
//		{
//			auto lockStartTime = std::chrono::steady_clock::now();
//			std::unique_lock<std::timed_mutex> lock(timeImageMutex, std::defer_lock);
//
//			// Increased timeout to 30 seconds
//			if (!lock.try_lock_for(std::chrono::milliseconds(MUTEX_TIMEOUT_MS))) {
//				auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
//					std::chrono::steady_clock::now() - lockStartTime).count();
//				std::cerr << "Error: Mutex timeout after " << elapsed << "ms in ANSCV_GetImage_S!" << std::endl;
//				return -6;
//			}
//
//			auto lockAcquiredTime = std::chrono::duration_cast<std::chrono::milliseconds>(
//				std::chrono::steady_clock::now() - lockStartTime).count();
//			if (lockAcquiredTime > 1000) {
//				std::cerr << "Warning: Lock acquisition took " << lockAcquiredTime << "ms" << std::endl;
//			}
//
//			// Re-validate after acquiring lock
//			if (!imageIn || !(*imageIn) || (*imageIn)->empty() || !(*imageIn)->data) {
//				std::cerr << "Error: Image became invalid after mutex acquisition!" << std::endl;
//				return -2;
//			}
//
//			// Copy the image while holding the lock
//			auto copyStartTime = std::chrono::steady_clock::now();
//			try {
//				(*imageIn)->copyTo(imgCopy);
//			}
//			catch (const cv::Exception& e) {
//				std::cerr << "Error: OpenCV exception during image copy: " << e.what() << std::endl;
//				return -7;
//			}
//
//			auto copyElapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
//				std::chrono::steady_clock::now() - copyStartTime).count();
//			if (copyElapsed > 500) {
//				std::cerr << "Warning: Image copy took " << copyElapsed << "ms (size: "
//					<< (*imageIn)->cols << "x" << (*imageIn)->rows << ")" << std::endl;
//			}
//		} // Lock released here - all subsequent operations run without holding the mutex
//
//		// Validate copied image
//		if (imgCopy.empty() || !imgCopy.data) {
//			std::cerr << "Error: Copied image is invalid in ANSCV_GetImage_S!" << std::endl;
//			return -2;
//		}
//
//		// Store original dimensions
//		int originalWidth = imgCopy.cols;
//		int originalHeight = imgCopy.rows;
//		int imageMaxSize = max(originalWidth, originalHeight);
//
//		// Resize if requested
//		if (width > 0 && width < imageMaxSize) {
//			auto resizeStartTime = std::chrono::steady_clock::now();
//
//			ANSCENTER::ANSOPENCV ansCVInstance;
//			if (!ansCVInstance.Init("")) {
//				std::cerr << "Error: Failed to initialize ANSOPENCV instance!" << std::endl;
//				return -5;
//			}
//
//			cv::Mat resized = ansCVInstance.ImageResizeV2(imgCopy, width);
//			if (resized.empty()) {
//				std::cerr << "Error: Resizing failed!" << std::endl;
//				return -8;
//			}
//
//			imgCopy = std::move(resized);
//
//			auto resizeElapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
//				std::chrono::steady_clock::now() - resizeStartTime).count();
//			if (resizeElapsed > 500) {
//				std::cerr << "Warning: Image resize took " << resizeElapsed << "ms (from "
//					<< originalWidth << "x" << originalHeight << " to "
//					<< imgCopy.cols << "x" << imgCopy.rows << ")" << std::endl;
//			}
//		}
//
//		// Update output dimensions
//		newWidth = imgCopy.cols;
//		newHeight = imgCopy.rows;
//
//		// Compress to JPEG
//		auto compressStartTime = std::chrono::steady_clock::now();
//		std::string jpegString = ANSCENTER::CompressJpegToString(imgCopy, quality);
//		if (jpegString.empty()) {
//			std::cerr << "Error: JPEG compression failed!" << std::endl;
//			return -9;
//		}
//
//		auto compressElapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
//			std::chrono::steady_clock::now() - compressStartTime).count();
//		if (compressElapsed > 500) {
//			std::cerr << "Warning: JPEG compression took " << compressElapsed << "ms (quality: "
//				<< quality << ", size: " << jpegString.size() << " bytes)" << std::endl;
//		}
//
//		// Validate compressed size
//		int32_t size = static_cast<int32_t>(jpegString.size());
//		if (size > 50 * 1024 * 1024) {
//			std::cerr << "Error: Compressed image size too large: " << size << " bytes" << std::endl;
//			return -10;
//		}
//
//		// Allocate memory for output
//		MgErr error = DSSetHandleSize(outputImage, sizeof(int32) + size);
//		if (error != noErr) {
//			std::cerr << "Error: Failed to allocate memory for output image! Error code: " << error << std::endl;
//			return -10;
//		}
//
//		// Copy data to output handle
//		(*outputImage)->cnt = size;
//		memcpy((*outputImage)->str, jpegString.data(), size);
//
//		return 1;
//	}
//	catch (const std::exception& e) {
//		std::cerr << "Exception in ANSCV_GetImage_S: " << e.what() << std::endl;
//		return -3;
//	}
//	catch (...) {
//		std::cerr << "Unknown exception in ANSCV_GetImage_S!" << std::endl;
//		return -4;
//	}
//}

extern "C" __declspec(dllexport) int ANSCV_GetImage_S(cv::Mat** imageIn, int width, int quality, int& newWidth, int& newHeight, LStrHandle outputImage) {
	void* matPtr = (imageIn ? (void*)*imageIn : nullptr);
	ANS_DBG("ANSCV_GetImage", "ENTRY mat=%p targetW=%d quality=%d outHandle=%p",
		matPtr, width, quality, (void*)outputImage);
	try {
		if (!imageIn || !(*imageIn) || (*imageIn)->empty() || !(*imageIn)->data) {
			ANS_DBG("ANSCV_GetImage", "ERROR invalid input mat=%p", matPtr);
			std::cerr << "Error: Invalid or empty input image in ANSCV_GetImage_S!" << std::endl;
			return -2;
		}

		if (!outputImage) {
			ANS_DBG("ANSCV_GetImage", "ERROR null LStrHandle");
			std::cerr << "Error: Output image handle is null!" << std::endl;
			return -2;
		}

		cv::Mat imgCopy;
		{
			std::unique_lock<std::timed_mutex> lock(timeImageMutex, std::defer_lock);
			if (!lock.try_lock_for(std::chrono::milliseconds(MUTEX_TIMEOUT_MS))) {
				ANS_DBG("ANSCV_GetImage", "ERROR mutex timeout mat=%p", matPtr);
				std::cerr << "Error: Mutex timeout in ANSCV_GetImage_S!" << std::endl;
				return -6;
			}

			if (!imageIn || !(*imageIn) || (*imageIn)->empty() || !(*imageIn)->data) {
				ANS_DBG("ANSCV_GetImage", "ERROR invalid after lock mat=%p",
					(imageIn ? (void*)*imageIn : nullptr));
				std::cerr << "Error: Image became invalid after mutex acquisition!" << std::endl;
				return -2;
			}

			try {
				(*imageIn)->copyTo(imgCopy);
			}
			catch (const cv::Exception& e) {
				ANS_DBG("ANSCV_GetImage", "ERROR cv::Exception on copyTo mat=%p: %s", matPtr, e.what());
				std::cerr << "Error: OpenCV exception during image copy: " << e.what() << std::endl;
				return -7;
			}
		}

		if (imgCopy.empty() || !imgCopy.data) {
			ANS_DBG("ANSCV_GetImage", "ERROR imgCopy empty after copy mat=%p", matPtr);
			std::cerr << "Error: Copied image is invalid in ANSCV_GetImage_S!" << std::endl;
			return -2;
		}

		int originalWidth = imgCopy.cols;
		int originalHeight = imgCopy.rows;
		int imageMaxSize = max(originalWidth, originalHeight);

		if (width > 0 && width < imageMaxSize) {
			ANS_DBG("ANSCV_GetImage", "RESIZE mat=%p src=%dx%d targetW=%d",
				matPtr, originalWidth, originalHeight, width);
			ANSCENTER::ANSOPENCV ansCVInstance;
			if (!ansCVInstance.Init("")) {
				ANS_DBG("ANSCV_GetImage", "ERROR ansCVInstance.Init failed");
				std::cerr << "Error: Failed to initialize ANSOPENCV instance!" << std::endl;
				return -5;
			}

			cv::Mat resized = ansCVInstance.ImageResizeV2(imgCopy, width);
			if (resized.empty()) {
				ANS_DBG("ANSCV_GetImage", "ERROR resize result empty mat=%p", matPtr);
				std::cerr << "Error: Resizing failed!" << std::endl;
				return -8;
			}
			imgCopy = std::move(resized);
		}

		newWidth = imgCopy.cols;
		newHeight = imgCopy.rows;
		std::string jpegString = ANSCENTER::CompressJpegToString(imgCopy, quality);
		if (jpegString.empty()) {
			ANS_DBG("ANSCV_GetImage", "ERROR JPEG compress empty mat=%p %dx%d q=%d",
				matPtr, newWidth, newHeight, quality);
			std::cerr << "Error: JPEG compression failed!" << std::endl;
			return -9;
		}

		int32_t size = static_cast<int32_t>(jpegString.size());

		if (size > 50 * 1024 * 1024) {
			ANS_DBG("ANSCV_GetImage", "ERROR jpeg too large mat=%p bytes=%d", matPtr, (int)size);
			std::cerr << "Error: Compressed image size too large: " << size << " bytes" << std::endl;
			return -10;
		}

		MgErr error = DSSetHandleSize(outputImage, sizeof(int32) + size);
		if (error != noErr) {
			ANS_DBG("ANSCV_GetImage", "ERROR DSSetHandleSize failed code=%d bytes=%d",
				(int)error, (int)size);
			std::cerr << "Error: Failed to allocate memory for output image! Error code: " << error << std::endl;
			return -10;
		}

		(*outputImage)->cnt = size;
		memcpy((*outputImage)->str, jpegString.data(), size);

		ANS_DBG("ANSCV_GetImage", "SUCCESS mat=%p out=%dx%d jpegBytes=%d",
			matPtr, newWidth, newHeight, (int)size);
		return 1;

	}
	catch (const std::exception& e) {
		ANS_DBG("ANSCV_GetImage", "ERROR exception mat=%p: %s", matPtr, e.what());
		std::cerr << "Exception in ANSCV_GetImage_S: " << e.what() << std::endl;
		return -3;
	}
	catch (...) {
		ANS_DBG("ANSCV_GetImage", "ERROR unknown exception mat=%p", matPtr);
		std::cerr << "Unknown exception in ANSCV_GetImage_S!" << std::endl;
		return -4;
	}
}

extern "C" __declspec(dllexport) int ANSCV_ReSizeImage_S(cv::Mat** imageIn, int width, int originalImageSize) {
	void* matPtr = (imageIn ? (void*)*imageIn : nullptr);
	ANS_DBG("ANSCV_ReSizeImage", "ENTRY mat=%p width=%d origSize=%d",
		matPtr, width, originalImageSize);
	gpu_frame_invalidate(imageIn ? *imageIn : nullptr);
	try {
		cv::Mat localImage;
		{
			std::unique_lock<std::timed_mutex> lock(timeImageMutex, std::defer_lock);
			if (!lock.try_lock_for(std::chrono::milliseconds(MUTEX_TIMEOUT_MS))) {
				ANS_DBG("ANSCV_ReSizeImage", "ERROR mutex timeout (copy phase) mat=%p", matPtr);
				std::cerr << "Error: Mutex timeout in ANSCV_ReSizeImage_S!" << std::endl;
				return -6;
			}
			//std::lock_guard<std::mutex> lock(imageMutex);
			try {
				if (!imageIn || !(*imageIn) || (*imageIn)->empty() || !(*imageIn)->data) {
					ANS_DBG("ANSCV_ReSizeImage", "ERROR invalid input mat=%p", matPtr);
					std::cerr << "Error: Invalid or empty input image in ANSCV_CloneImage_S!" << std::endl;
					return -2;
				}
				else {
					localImage = **imageIn;  // shallow copy (ref-counted)
				}
			}
			catch (const std::bad_alloc& e) {
				ANS_DBG("ANSCV_ReSizeImage", "ERROR bad_alloc mat=%p: %s", matPtr, e.what());
				std::cerr << "Memory allocation failed in ANSCV_CloneImage_S: " << e.what() << std::endl;
				return -1;
			}
			catch (const std::exception& e) {
				ANS_DBG("ANSCV_ReSizeImage", "ERROR exception copy mat=%p: %s", matPtr, e.what());
				std::cerr << "Error: Exception occurred in ANSCV_CloneImage_S: " << e.what() << std::endl;
				return -3;
			}
		}

		if (width <= 0) {
			ANS_DBG("ANSCV_ReSizeImage", "ERROR invalid target width=%d", width);
			std::cerr << "Error: Invalid target width in ANSCV_ReSizeImage_S!" << std::endl;
			return -2;
		}
		const int originalWidth = localImage.cols;
		const int originalHeight = localImage.rows;
		int targetWidth = width;
		cv::Mat resizedImage;
		// Scale width based on original image size, if provided
		if (originalImageSize > 0 && originalImageSize != originalWidth) {
			double scale = static_cast<double>(originalWidth) / originalImageSize;
			targetWidth = static_cast<int>(width * scale);
		}
		// Skip resizing if target size is greater or equal to original
		if (targetWidth < originalWidth) {

			// Maintain aspect ratio
			int targetHeight = static_cast<int>(std::round(targetWidth * static_cast<double>(originalHeight) / originalWidth));

			if (targetHeight <= 0) {
				ANS_DBG("ANSCV_ReSizeImage", "ERROR computed height invalid targetW=%d origW=%d origH=%d",
					targetWidth, originalWidth, originalHeight);
				std::cerr << "Error: Computed height is invalid!" << std::endl;
				return -2;
			}
			ANS_DBG("ANSCV_ReSizeImage", "RESIZE mat=%p src=%dx%d -> %dx%d",
				matPtr, originalWidth, originalHeight, targetWidth, targetHeight);
			cv::resize(localImage, resizedImage, cv::Size(targetWidth, targetHeight), 0, 0, cv::INTER_LANCZOS4);
		}
		else {
			ANS_DBG("ANSCV_ReSizeImage", "SKIP_RESIZE mat=%p targetW=%d >= origW=%d",
				matPtr, targetWidth, originalWidth);
			resizedImage = localImage.clone();  // No resizing needed
		}
		{
			std::unique_lock<std::timed_mutex> lock(timeImageMutex, std::defer_lock);
			if (!lock.try_lock_for(std::chrono::milliseconds(MUTEX_TIMEOUT_MS))) {
				ANS_DBG("ANSCV_ReSizeImage", "ERROR mutex timeout (writeback) mat=%p", matPtr);
				std::cerr << "Error: Mutex timeout in ANSCV_ReSizeImage_S!" << std::endl;
				return -6;
			}
			//std::lock_guard<std::mutex> lock(imageMutex);
			if (resizedImage.empty()) {
				ANS_DBG("ANSCV_ReSizeImage", "ERROR resized empty mat=%p", matPtr);
				std::cerr << "Error: Resizing failed!" << std::endl;
				return 0;
			}
			else {
				if (!imageIn || !(*imageIn) || (*imageIn)->empty() || !(*imageIn)->data) {
					ANS_DBG("ANSCV_ReSizeImage", "ERROR input invalid at writeback mat=%p",
						(imageIn ? (void*)*imageIn : nullptr));
					std::cerr << "Error: Invalid or empty input image in ANSCV_CloneImage_S!" << std::endl;
					return -2;
				}
				**imageIn = std::move(resizedImage);
			}

		}

		ANS_DBG("ANSCV_ReSizeImage", "SUCCESS mat=%p new=%dx%d",
			(imageIn && *imageIn) ? (void*)*imageIn : nullptr,
			(imageIn && *imageIn) ? (*imageIn)->cols : -1,
			(imageIn && *imageIn) ? (*imageIn)->rows : -1);
		return 1;
	}
	catch (const std::exception& e) {
		ANS_DBG("ANSCV_ReSizeImage", "ERROR exception outer mat=%p: %s", matPtr, e.what());
		std::cerr << "Exception in ANSCV_ReSizeImage_S: " << e.what() << std::endl;
		return -3;
	}
}

extern "C" __declspec(dllexport) int ANSCV_GetImageAndRemoveImgRef_S(cv::Mat** imageIn, int width, int quality, int& newWidth, int& newHeight, LStrHandle outputImage) {
	void* matPtr = (imageIn ? (void*)*imageIn : nullptr);
	ANS_DBG("ANSCV_GetImageAndRemove", "ENTRY mat=%p targetW=%d quality=%d outHandle=%p",
		matPtr, width, quality, (void*)outputImage);
	bool cleanupRequired = true;
	try {
		cv::Mat imgCopy;
		{
			//std::lock_guard<std::mutex> lock(imageMutex);
			std::unique_lock<std::timed_mutex> lock(timeImageMutex, std::defer_lock);
			if (!lock.try_lock_for(std::chrono::milliseconds(MUTEX_TIMEOUT_MS))) {
				ANS_DBG("ANSCV_GetImageAndRemove", "ERROR mutex timeout mat=%p", matPtr);
				std::cerr << "Error: Mutex timeout in ANSCV_ReSizeImage_S!" << std::endl;
				return -6;
			}
			if (!imageIn || !(*imageIn) || (*imageIn)->empty() || !(*imageIn)->data) {
				ANS_DBG("ANSCV_GetImageAndRemove", "ERROR invalid input mat=%p",
					(imageIn ? (void*)*imageIn : nullptr));
				std::cerr << "Error: Invalid or empty input image in ANSCV_GetImageAndRemoveImgRef_S!" << std::endl;
				return -2;
			}
			(*imageIn)->copyTo(imgCopy);
			ANS_DBG("ANSCV_GetImageAndRemove", "DELETING mat=%p (after copy)", matPtr);
			anscv_mat_delete(imageIn);  // Safe delete + null
			cleanupRequired = false; // We already deleted inside lock
		}

		if (imgCopy.empty() || !imgCopy.data) {
			ANS_DBG("ANSCV_GetImageAndRemove", "ERROR imgCopy invalid (orig mat=%p)", matPtr);
			std::cerr << "Error: Copied image is invalid in ANSCV_GetImageAndRemoveImgRef_S!" << std::endl;
			return 0;
		}

		int originalWidth = imgCopy.cols;
		int originalHeight = imgCopy.rows;
		int imageMaxSize = max(originalWidth, originalHeight);

		if (width > 0 && width < imageMaxSize) {
			ANS_DBG("ANSCV_GetImageAndRemove", "RESIZE src=%dx%d targetW=%d",
				originalWidth, originalHeight, width);
			ANSCENTER::ANSOPENCV ansCVInstance;
			if (!ansCVInstance.Init("")) {
				ANS_DBG("ANSCV_GetImageAndRemove", "ERROR ansCVInstance.Init failed");
				std::cerr << "Error: Failed to initialize ANSOPENCV instance!" << std::endl;
				return -5;
			}
			cv::Mat resized = ansCVInstance.ImageResizeV2(imgCopy, width);
			if (resized.empty()) {
				ANS_DBG("ANSCV_GetImageAndRemove", "ERROR resize empty");
				std::cerr << "Error: Resizing failed!" << std::endl;
				return 0;
			}
			imgCopy = std::move(resized);
		}

		newWidth = imgCopy.cols;
		newHeight = imgCopy.rows;

		std::string jpegString = ANSCENTER::CompressJpegToString(imgCopy, quality);
		if (jpegString.empty()) {
			ANS_DBG("ANSCV_GetImageAndRemove", "ERROR JPEG compress empty %dx%d q=%d",
				newWidth, newHeight, quality);
			std::cerr << "Error: JPEG compression failed in ANSCV_GetImageAndRemoveImgRef_S!" << std::endl;
			return 0;
		}

		int32_t size = static_cast<int32_t>(jpegString.size());
		MgErr error = DSSetHandleSize(outputImage, sizeof(int32) + size);
		if (error != noErr) {
			ANS_DBG("ANSCV_GetImageAndRemove", "ERROR DSSetHandleSize failed code=%d bytes=%d",
				(int)error, (int)size);
			std::cerr << "Error: Failed to allocate memory for output image!" << std::endl;
			return 0;
		}

		(*outputImage)->cnt = size;
		memcpy((*outputImage)->str, jpegString.data(), size);

		ANS_DBG("ANSCV_GetImageAndRemove", "SUCCESS out=%dx%d jpegBytes=%d (deleted mat=%p)",
			newWidth, newHeight, (int)size, matPtr);
		return 1; // Success
	}
	catch (const std::exception& e) {
		ANS_DBG("ANSCV_GetImageAndRemove", "ERROR exception mat=%p: %s", matPtr, e.what());
		std::cerr << "Exception in ANSCV_GetImageAndRemoveImgRef_S: " << e.what() << std::endl;
	}
	catch (...) {
		ANS_DBG("ANSCV_GetImageAndRemove", "ERROR unknown exception mat=%p", matPtr);
		std::cerr << "Unknown exception in ANSCV_GetImageAndRemoveImgRef_S!" << std::endl;
	}

	// Cleanup in case of exception and still pending
	if (cleanupRequired) {
		ANS_DBG("ANSCV_GetImageAndRemove", "CLEANUP delete pending mat=%p",
			(imageIn ? (void*)*imageIn : nullptr));
		std::unique_lock<std::timed_mutex> lock(timeImageMutex, std::defer_lock);
		if (!lock.try_lock_for(std::chrono::milliseconds(MUTEX_TIMEOUT_MS))) {
			ANS_DBG("ANSCV_GetImageAndRemove", "ERROR cleanup mutex timeout mat=%p",
				(imageIn ? (void*)*imageIn : nullptr));
			std::cerr << "Error: Mutex timeout in ANSCV_ReSizeImage_S!" << std::endl;
			return -6;
		}
		//std::lock_guard<std::mutex> lock(imageMutex);
		anscv_mat_delete(imageIn);
	}
	return -3;
}

extern "C" __declspec(dllexport) int ANSCV_GetImageInfo_S(cv::Mat** imageIn, int& width, int& height) {
	void* matPtr = (imageIn ? (void*)*imageIn : nullptr);
	ANS_DBG("ANSCV_GetImageInfo", "ENTRY mat=%p", matPtr);
	try {
		cv::Mat imgCopy;
		{
			std::unique_lock<std::timed_mutex> lock(timeImageMutex, std::defer_lock);
			if (!lock.try_lock_for(std::chrono::milliseconds(MUTEX_TIMEOUT_MS))) {
				ANS_DBG("ANSCV_GetImageInfo", "ERROR mutex timeout mat=%p", matPtr);
				std::cerr << "Error: Mutex timeout in ANSCV_ReSizeImage_S!" << std::endl;
				return -6;
			}
			//std::lock_guard<std::mutex> lock(imageMutex);
			try {
				if (!imageIn || !(*imageIn) || (*imageIn)->empty() || !(*imageIn)->data) {
					ANS_DBG("ANSCV_GetImageInfo", "ERROR invalid input mat=%p", matPtr);
					std::cerr << "Error: Invalid or empty input image in ANSCV_CloneImage_S!" << std::endl;
					return -2;
				}
				else {
					imgCopy = **imageIn;  // shallow copy (ref-counted)
				}
			}
			catch (const std::bad_alloc& e) {
				ANS_DBG("ANSCV_GetImageInfo", "ERROR bad_alloc mat=%p: %s", matPtr, e.what());
				std::cerr << "Memory allocation failed in ANSCV_CloneImage_S: " << e.what() << std::endl;
				return -1;
			}
			catch (const std::exception& e) {
				ANS_DBG("ANSCV_GetImageInfo", "ERROR exception inner mat=%p: %s", matPtr, e.what());
				std::cerr << "Error: Exception occurred in ANSCV_CloneImage_S: " << e.what() << std::endl;
				return -3;
			}
		}
		if (imgCopy.empty() || !imgCopy.data) {
			ANS_DBG("ANSCV_GetImageInfo", "ERROR imgCopy invalid mat=%p", matPtr);
			std::cerr << "Error: Dereferenced image is invalid in ANSCV_GetImageInfo_S!" << std::endl;
			return 0;
		}

		// Assign the width and height once the image is validated
		width = imgCopy.cols;
		height = imgCopy.rows;
		ANS_DBG("ANSCV_GetImageInfo", "SUCCESS mat=%p %dx%d", matPtr, width, height);
		return 1;  // Success
	}
	catch (const std::exception& e) {
		ANS_DBG("ANSCV_GetImageInfo", "ERROR exception outer mat=%p: %s", matPtr, e.what());
		std::cerr << "Exception in ANSCV_GetImageInfo_S: " << e.what() << std::endl;
		return -3;
	}
	catch (...) {
		ANS_DBG("ANSCV_GetImageInfo", "ERROR unknown exception mat=%p", matPtr);
		std::cerr << "Unknown exception in ANSCV_GetImageInfo_S!" << std::endl;
		return -4;
	}
}
extern "C" __declspec(dllexport) int ANSCV_CreateImageFromJpegString_S(
	unsigned char* inputImage,
	unsigned int bufferLength,
	cv::Mat** image)
{
	ANS_DBG("ANSCV_CreateFromJpeg", "ENTRY buf=%p bytes=%u outPtr=%p",
		(void*)inputImage, bufferLength, (void*)image);
	// Validate input parameters
	if (!inputImage || bufferLength == 0 || image == nullptr) {
		ANS_DBG("ANSCV_CreateFromJpeg", "ERROR invalid params buf=%p bytes=%u outPtr=%p",
			(void*)inputImage, bufferLength, (void*)image);
		std::cerr << "Error: Invalid input parameters in ANSCV_CreateImageFromJpegString_S!" << std::endl;
		return -2;
	}

	try {
		// Copy input buffer for decoding
		std::vector<uchar> buffer(inputImage, inputImage + bufferLength);
		cv::Mat decodedImage = cv::imdecode(buffer, cv::IMREAD_COLOR);
		// Allocate image safely
		{
			std::unique_lock<std::timed_mutex> lock(timeImageMutex, std::defer_lock);
			if (!lock.try_lock_for(std::chrono::milliseconds(MUTEX_TIMEOUT_MS))) {
				ANS_DBG("ANSCV_CreateFromJpeg", "ERROR mutex timeout bytes=%u", bufferLength);
				std::cerr << "Error: Mutex timeout in ANSCV_ReSizeImage_S!" << std::endl;
				return -6;
			}
			//std::lock_guard<std::mutex> lock(imageMutex);
			// Check if decoding was successful
			if (decodedImage.empty()) {
				ANS_DBG("ANSCV_CreateFromJpeg", "ERROR imdecode empty bytes=%u", bufferLength);
				std::cerr << "Error: Failed to decode JPEG image in ANSCV_CreateImageFromJpegString_S!" << std::endl;
				return 0;
			}
			else {
				*image = anscv_mat_new(decodedImage);
				ANS_DBG("ANSCV_CreateFromJpeg", "SUCCESS mat=%p %dx%d type=%d (jpeg=%u bytes)",
					(void*)*image, (*image)->cols, (*image)->rows, (*image)->type(), bufferLength);
				return 1; // Success
			}
		}

	}
	catch (const std::bad_alloc& e) {
		ANS_DBG("ANSCV_CreateFromJpeg", "ERROR bad_alloc bytes=%u: %s", bufferLength, e.what());
		std::cerr << "Memory allocation failed in ANSCV_CreateImageFromJpegString_S: " << e.what() << std::endl;
		return -1;
	}
	catch (const std::exception& e) {
		ANS_DBG("ANSCV_CreateFromJpeg", "ERROR exception bytes=%u: %s", bufferLength, e.what());
		std::cerr << "Exception in ANSCV_CreateImageFromJpegString_S: " << e.what() << std::endl;
		return -3;
	}
	catch (...) {
		ANS_DBG("ANSCV_CreateFromJpeg", "ERROR unknown exception bytes=%u", bufferLength);
		std::cerr << "Unknown exception in ANSCV_CreateImageFromJpegString_S!" << std::endl;
		return -4;
	}
}
extern "C" __declspec(dllexport) int ANSCV_CreateImageFromFile_S(const char* imageFilePath, cv::Mat** image)
{
	ANS_DBG("ANSCV_CreateFromFile", "ENTRY path=%s outPtr=%p",
		imageFilePath ? imageFilePath : "(null)", (void*)image);
	try {
		if (!imageFilePath) {
			ANS_DBG("ANSCV_CreateFromFile", "ERROR null path");
			std::cerr << "Error: Null input parameter in ANSCV_CreateImageFromFile_S!" << std::endl;
			return -1;  // Null pointer input
		}

		std::string stImageFileName(imageFilePath);

		if (stImageFileName.empty()) {
			ANS_DBG("ANSCV_CreateFromFile", "ERROR empty path");
			std::cerr << "Error: Empty file path in ANSCV_CreateImageFromFile_S!" << std::endl;
			return -2;  // Empty path
		}

		// Check if file exists
		std::ifstream fileCheck(stImageFileName);
		if (!fileCheck.good()) {
			ANS_DBG("ANSCV_CreateFromFile", "ERROR file missing/inaccessible path=%s",
				stImageFileName.c_str());
			std::cerr << "Error: File does not exist or is inaccessible: " << stImageFileName << std::endl;
			return -4;  // File does not exist
		}

		// Load the image using OpenCV
		cv::Mat loadedImage = cv::imread(stImageFileName, cv::ImreadModes::IMREAD_COLOR);
		{
			// Allocate and assign the image
			//std::lock_guard<std::mutex> lock(imageMutex);  // Automatically locks and unlocks
			std::unique_lock<std::timed_mutex> lock(timeImageMutex, std::defer_lock);
			if (!lock.try_lock_for(std::chrono::milliseconds(MUTEX_TIMEOUT_MS))) {
				ANS_DBG("ANSCV_CreateFromFile", "ERROR mutex timeout path=%s",
					stImageFileName.c_str());
				std::cerr << "Error: Mutex timeout in ANSCV_ReSizeImage_S!" << std::endl;
				return -6;
			}
			if (loadedImage.empty()) {
				ANS_DBG("ANSCV_CreateFromFile", "ERROR imread empty path=%s",
					stImageFileName.c_str());
				std::cerr << "Error: Failed to load image from file in ANSCV_CreateImageFromFile_S!" << std::endl;
				return 0;  // Load failed
			}
			else {
				if (image == nullptr) {
					ANS_DBG("ANSCV_CreateFromFile", "WARN outPtr was null - allocating internal pointer (caller will leak it)");
					image = new cv::Mat*;  // Allocate pointer to cv::Mat*
					*image = nullptr;      // Initialize to nullptr
				}
				*image = anscv_mat_new(loadedImage);  // Use registry for safe lifecycle
				ANS_DBG("ANSCV_CreateFromFile", "SUCCESS path=%s mat=%p %dx%d type=%d",
					stImageFileName.c_str(), (void*)*image,
					(*image)->cols, (*image)->rows, (*image)->type());
				return 1;  // Success
			}
		}

	}
	catch (const std::exception& e) {
		ANS_DBG("ANSCV_CreateFromFile", "ERROR exception path=%s: %s",
			imageFilePath ? imageFilePath : "(null)", e.what());
		std::cerr << "Error: Exception occurred in ANSCV_CreateImageFromFile_S: " << e.what() << std::endl;
		return -3;  // Exception
	}
	catch (...) {
		ANS_DBG("ANSCV_CreateFromFile", "ERROR unknown exception path=%s",
			imageFilePath ? imageFilePath : "(null)");
		std::cerr << "Unknown error occurred in ANSCV_CreateImageFromFile_S!" << std::endl;
		return -5;  // Unknown error
	}
}

// Image Preprocessing
extern "C" __declspec(dllexport) int ANSCV_ImageAutoWhiteBalance_S(cv::Mat** imageIn) {
	gpu_frame_invalidate(imageIn ? *imageIn : nullptr);
	try {
		// Shallow-copy input under lock so the processor sees a stable Mat
		// even if another thread writes to *imageIn concurrently.
		cv::Mat localInput;
		{
			std::unique_lock<std::timed_mutex> lock(timeImageMutex, std::defer_lock);
			if (!lock.try_lock_for(std::chrono::milliseconds(MUTEX_TIMEOUT_MS))) {
				std::cerr << "Error: Mutex timeout in ANSCV_ImageAutoWhiteBalance_S!" << std::endl;
				return -6;
			}
			if (!imageIn || !(*imageIn) || (*imageIn)->empty() || !(*imageIn)->data) {
				std::cerr << "Error: Invalid or empty input image in ANSCV_ImageAutoWhiteBalance_S!" << std::endl;
				return -2;
			}
			localInput = **imageIn;  // ref-counted shallow copy
		}

		ANSCENTER::ANSOPENCV ansCVInstance;
		if (!ansCVInstance.Init("")) {
			std::cerr << "Error: Failed to initialize ANSCV instance!" << std::endl;
			return -5;
		}

		cv::Mat imOut = ansCVInstance.ImageWhiteBalance(localInput);
		if (imOut.empty()) {
			std::cerr << "Error: White balance processing failed in ANSCV_ImageAutoWhiteBalance_S!" << std::endl;
			return 0;
		}

		// Swap back under lock.
		{
			std::unique_lock<std::timed_mutex> lock(timeImageMutex, std::defer_lock);
			if (!lock.try_lock_for(std::chrono::milliseconds(MUTEX_TIMEOUT_MS))) {
				std::cerr << "Error: Mutex timeout in ANSCV_ImageAutoWhiteBalance_S!" << std::endl;
				return -6;
			}
			if (!imageIn || !(*imageIn)) {
				std::cerr << "Error: Image became invalid in ANSCV_ImageAutoWhiteBalance_S!" << std::endl;
				return -2;
			}
			**imageIn = std::move(imOut);
			return 1;
		}
	}
	catch (const std::exception& e) {
		std::cerr << "Error: Exception occurred in ANSCV_ImageAutoWhiteBalance_S: " << e.what() << std::endl;
		return -3;
	}
	catch (...) {
		std::cerr << "Error: Unknown exception occurred in ANSCV_ImageAutoWhiteBalance_S!" << std::endl;
		return -4;
	}
}

extern "C" __declspec(dllexport) int ANSCV_ImageBrightEnhance_S(cv::Mat** imageIn, double brightnessScaleFactor) {
	gpu_frame_invalidate(imageIn ? *imageIn : nullptr);
	try {
		cv::Mat localInput;
		{
			std::unique_lock<std::timed_mutex> lock(timeImageMutex, std::defer_lock);
			if (!lock.try_lock_for(std::chrono::milliseconds(MUTEX_TIMEOUT_MS))) {
				std::cerr << "Error: Mutex timeout in ANSCV_ImageBrightEnhance_S!" << std::endl;
				return -6;
			}
			if (!imageIn || !(*imageIn) || (*imageIn)->empty() || !(*imageIn)->data) {
				std::cerr << "Error: Invalid or empty input image in ANSCV_ImageBrightEnhance_S!" << std::endl;
				return -2;
			}
			localInput = **imageIn;
		}

		ANSCENTER::ANSOPENCV ansCVInstance;
		ansCVInstance.Init("");
		cv::Mat imOut = ansCVInstance.ImageDarkEnhancement(localInput, brightnessScaleFactor);
		if (imOut.empty()) {
			std::cerr << "Error: Brightness enhancement failed in ANSCV_ImageBrightEnhance_S!" << std::endl;
			return 0;
		}

		{
			std::unique_lock<std::timed_mutex> lock(timeImageMutex, std::defer_lock);
			if (!lock.try_lock_for(std::chrono::milliseconds(MUTEX_TIMEOUT_MS))) {
				std::cerr << "Error: Mutex timeout in ANSCV_ImageBrightEnhance_S!" << std::endl;
				return -6;
			}
			if (!imageIn || !(*imageIn)) {
				std::cerr << "Error: Image became invalid in ANSCV_ImageBrightEnhance_S!" << std::endl;
				return -2;
			}
			**imageIn = std::move(imOut);
			return 1;
		}
	}
	catch (const std::exception& e) {
		std::cerr << "Error: Exception occurred in ANSCV_ImageBrightEnhance_S: " << e.what() << std::endl;
		return -3;
	}
	catch (...) {
		std::cerr << "Error: Unknown exception occurred in ANSCV_ImageBrightEnhance_S!" << std::endl;
		return -4;
	}
}


extern "C" __declspec(dllexport) int  ANSCV_ImageContrastEnhance_S(cv::Mat** imageIn) {
	gpu_frame_invalidate(imageIn ? *imageIn : nullptr);
	try {
		cv::Mat localInput;
		{
			std::unique_lock<std::timed_mutex> lock(timeImageMutex, std::defer_lock);
			if (!lock.try_lock_for(std::chrono::milliseconds(MUTEX_TIMEOUT_MS))) {
				std::cerr << "Error: Mutex timeout in ANSCV_ImageContrastEnhance_S!" << std::endl;
				return -6;
			}
			if (!imageIn || !(*imageIn) || (*imageIn)->empty() || !(*imageIn)->data) {
				std::cerr << "Error: Invalid or empty input image in ANSCV_ImageContrastEnhance_S!" << std::endl;
				return -2;
			}
			localInput = **imageIn;
		}

		ANSCENTER::ANSOPENCV ansCVInstance;
		ansCVInstance.Init("");
		cv::Mat imOut = ansCVInstance.ImageContrastEnhancement(localInput);
		if (imOut.empty()) {
			std::cerr << "Error: Contrast enhancement failed in ANSCV_ImageContrastEnhance_S!" << std::endl;
			return 0;
		}

		{
			std::unique_lock<std::timed_mutex> lock(timeImageMutex, std::defer_lock);
			if (!lock.try_lock_for(std::chrono::milliseconds(MUTEX_TIMEOUT_MS))) {
				std::cerr << "Error: Mutex timeout in ANSCV_ImageContrastEnhance_S!" << std::endl;
				return -6;
			}
			if (!imageIn || !(*imageIn)) {
				std::cerr << "Error: Image became invalid in ANSCV_ImageContrastEnhance_S!" << std::endl;
				return -2;
			}
			**imageIn = std::move(imOut);
			return 1;
		}
	}
	catch (const std::exception& e) {
		std::cerr << "Error: Exception occurred in ANSCV_ImageContrastEnhance_S: " << e.what() << std::endl;
		return -3;
	}
	catch (...) {
		std::cerr << "Error: Unknown exception occurred in ANSCV_ImageContrastEnhance_S!" << std::endl;
		return -4;
	}
}

extern "C" __declspec(dllexport) int ANSCV_ImageDenoise_S(cv::Mat** imageIn) {
	gpu_frame_invalidate(imageIn ? *imageIn : nullptr);
	try {
		cv::Mat localInput;
		{
			std::unique_lock<std::timed_mutex> lock(timeImageMutex, std::defer_lock);
			if (!lock.try_lock_for(std::chrono::milliseconds(MUTEX_TIMEOUT_MS))) {
				std::cerr << "Error: Mutex timeout in ANSCV_ImageDenoise_S!" << std::endl;
				return -6;
			}
			if (!imageIn || !(*imageIn) || (*imageIn)->empty() || !(*imageIn)->data) {
				std::cerr << "Error: Invalid or empty input image in ANSCV_ImageDenoise_S!" << std::endl;
				return -2;
			}
			localInput = **imageIn;
		}

		ANSCENTER::ANSOPENCV ansCVInstance;
		ansCVInstance.Init("");
		cv::Mat imOut = ansCVInstance.ImageDenoise(localInput);
		if (imOut.empty()) {
			std::cerr << "Error: Denoising failed in ANSCV_ImageDenoise_S!" << std::endl;
			return 0;
		}

		{
			std::unique_lock<std::timed_mutex> lock(timeImageMutex, std::defer_lock);
			if (!lock.try_lock_for(std::chrono::milliseconds(MUTEX_TIMEOUT_MS))) {
				std::cerr << "Error: Mutex timeout in ANSCV_ImageDenoise_S!" << std::endl;
				return -6;
			}
			if (!imageIn || !(*imageIn)) {
				std::cerr << "Error: Image became invalid in ANSCV_ImageDenoise_S!" << std::endl;
				return -2;
			}
			**imageIn = std::move(imOut);
			return 1;
		}
	}
	catch (const std::exception& e) {
		std::cerr << "Error: Exception occurred in ANSCV_ImageDenoise_S: " << e.what() << std::endl;
		return -3;
	}
	catch (...) {
		std::cerr << "Error: Unknown exception occurred in ANSCV_ImageDenoise_S!" << std::endl;
		return -4;
	}
}
extern "C" __declspec(dllexport) int ANSCV_ImageRepair_S(cv::Mat** imageIn) {
	gpu_frame_invalidate(imageIn ? *imageIn : nullptr);
	try {
		cv::Mat localInput;
		{
			std::unique_lock<std::timed_mutex> lock(timeImageMutex, std::defer_lock);
			if (!lock.try_lock_for(std::chrono::milliseconds(MUTEX_TIMEOUT_MS))) {
				std::cerr << "Error: Mutex timeout in ANSCV_ImageRepair_S!" << std::endl;
				return -6;
			}
			if (!imageIn || !(*imageIn) || (*imageIn)->empty() || !(*imageIn)->data) {
				std::cerr << "Error: Invalid or empty input image in ANSCV_ImageRepair_S!" << std::endl;
				return -2;
			}
			localInput = **imageIn;
		}

		ANSCENTER::ANSOPENCV ansCVInstance;
		ansCVInstance.Init("");
		cv::Mat imOut = ansCVInstance.ImageRepair(localInput);
		if (imOut.empty()) {
			std::cerr << "Error: Image repair failed in ANSCV_ImageRepair_S!" << std::endl;
			return 0;
		}

		{
			std::unique_lock<std::timed_mutex> lock(timeImageMutex, std::defer_lock);
			if (!lock.try_lock_for(std::chrono::milliseconds(MUTEX_TIMEOUT_MS))) {
				std::cerr << "Error: Mutex timeout in ANSCV_ImageRepair_S!" << std::endl;
				return -6;
			}
			if (!imageIn || !(*imageIn)) {
				std::cerr << "Error: Image became invalid in ANSCV_ImageRepair_S!" << std::endl;
				return -2;
			}
			**imageIn = std::move(imOut);
			return 1;
		}
	}
	catch (const std::exception& e) {
		std::cerr << "Error: Exception occurred in ANSCV_ImageRepair_S: " << e.what() << std::endl;
		return -3;
	}
	catch (...) {
		std::cerr << "Error: Unknown exception occurred in ANSCV_ImageRepair_S!" << std::endl;
		return -4;
	}
}
extern "C" __declspec(dllexport) int  ANSCV_ImageToGray_S(cv::Mat** imageIn) {
	gpu_frame_invalidate(imageIn ? *imageIn : nullptr);
	try {
		cv::Mat localInput;
		{
			std::unique_lock<std::timed_mutex> lock(timeImageMutex, std::defer_lock);
			if (!lock.try_lock_for(std::chrono::milliseconds(MUTEX_TIMEOUT_MS))) {
				std::cerr << "Error: Mutex timeout in ANSCV_ImageToGray_S!" << std::endl;
				return -6;
			}
			if (!imageIn || !(*imageIn) || (*imageIn)->empty() || !(*imageIn)->data) {
				std::cerr << "Error: Invalid or empty input image in ANSCV_ImageToGray_S!" << std::endl;
				return -2;
			}
			localInput = **imageIn;
		}

		ANSCENTER::ANSOPENCV ansCVInstance;
		ansCVInstance.Init("");
		cv::Mat imOut = ansCVInstance.ToGray(localInput);
		if (imOut.empty()) {
			std::cerr << "Error: Gray conversion failed in ANSCV_ImageToGray_S!" << std::endl;
			return 0;
		}

		{
			std::unique_lock<std::timed_mutex> lock(timeImageMutex, std::defer_lock);
			if (!lock.try_lock_for(std::chrono::milliseconds(MUTEX_TIMEOUT_MS))) {
				std::cerr << "Error: Mutex timeout in ANSCV_ImageToGray_S!" << std::endl;
				return -6;
			}
			if (!imageIn || !(*imageIn)) {
				std::cerr << "Error: Image became invalid in ANSCV_ImageToGray_S!" << std::endl;
				return -2;
			}
			**imageIn = std::move(imOut);
			return 1;
		}
	}
	catch (const std::exception& e) {
		std::cerr << "Error: Exception occurred in ANSCV_ImageToGray_S: " << e.what() << std::endl;
		return -3;
	}
	catch (...) {
		std::cerr << "Error: Unknown exception occurred in ANSCV_ImageToGray_S!" << std::endl;
		return -4;
	}
}
extern "C" __declspec(dllexport) int  ANSCV_ImageRotate_S(cv::Mat** imageIn, double angle) {
	gpu_frame_invalidate(imageIn ? *imageIn : nullptr);
	try {
		cv::Mat localInput;
		{
			std::unique_lock<std::timed_mutex> lock(timeImageMutex, std::defer_lock);
			if (!lock.try_lock_for(std::chrono::milliseconds(MUTEX_TIMEOUT_MS))) {
				std::cerr << "Error: Mutex timeout in ANSCV_ImageRotate_S!" << std::endl;
				return -6;
			}
			if (!imageIn || !(*imageIn) || (*imageIn)->empty() || !(*imageIn)->data) {
				std::cerr << "Error: Invalid or empty input image in ANSCV_ImageRotate_S!" << std::endl;
				return -2;
			}
			localInput = **imageIn;
		}

		ANSCENTER::ANSOPENCV ansCVInstance;
		ansCVInstance.Init("");
		cv::Mat imOut = ansCVInstance.RotateImage(localInput, angle);
		if (imOut.empty()) {
			std::cerr << "Error: Rotation failed in ANSCV_ImageRotate_S!" << std::endl;
			return 0;
		}

		{
			std::unique_lock<std::timed_mutex> lock(timeImageMutex, std::defer_lock);
			if (!lock.try_lock_for(std::chrono::milliseconds(MUTEX_TIMEOUT_MS))) {
				std::cerr << "Error: Mutex timeout in ANSCV_ImageRotate_S!" << std::endl;
				return -6;
			}
			if (!imageIn || !(*imageIn)) {
				std::cerr << "Error: Image became invalid in ANSCV_ImageRotate_S!" << std::endl;
				return -2;
			}
			**imageIn = std::move(imOut);
			return 1;
		}
	}
	catch (const std::exception& e) {
		std::cerr << "Error: Exception occurred in ANSCV_ImageRotate_S: " << e.what() << std::endl;
		return -3;
	}
	catch (...) {
		std::cerr << "Error: Unknown exception occurred in ANSCV_ImageRotate_S!" << std::endl;
		return -4;
	}
}

extern "C" __declspec(dllexport) int  ANSCV_ImageFlip_S(cv::Mat** imageIn, int flipCode) {
	gpu_frame_invalidate(imageIn ? *imageIn : nullptr);
	try {
		cv::Mat localInput;
		{
			std::unique_lock<std::timed_mutex> lock(timeImageMutex, std::defer_lock);
			if (!lock.try_lock_for(std::chrono::milliseconds(MUTEX_TIMEOUT_MS))) {
				std::cerr << "Error: Mutex timeout in ANSCV_ImageFlip_S!" << std::endl;
				return -6;
			}
			if (!imageIn || !(*imageIn) || (*imageIn)->empty() || !(*imageIn)->data) {
				std::cerr << "Error: Invalid or empty input image in ANSCV_ImageFlip_S!" << std::endl;
				return -2;
			}
			localInput = **imageIn;
		}

		ANSCENTER::ANSOPENCV ansCVInstance;
		ansCVInstance.Init("");
		cv::Mat imOut = ansCVInstance.FlipImage(localInput, flipCode);
		if (imOut.empty()) {
			std::cerr << "Error: Flip failed in ANSCV_ImageFlip_S!" << std::endl;
			return 0;
		}

		{
			std::unique_lock<std::timed_mutex> lock(timeImageMutex, std::defer_lock);
			if (!lock.try_lock_for(std::chrono::milliseconds(MUTEX_TIMEOUT_MS))) {
				std::cerr << "Error: Mutex timeout in ANSCV_ImageFlip_S!" << std::endl;
				return -6;
			}
			if (!imageIn || !(*imageIn)) {
				std::cerr << "Error: Image became invalid in ANSCV_ImageFlip_S!" << std::endl;
				return -2;
			}
			**imageIn = std::move(imOut);
			return 1;
		}
	}
	catch (const std::exception& e) {
		std::cerr << "Error: Exception occurred in ANSCV_ImageFlip_S: " << e.what() << std::endl;
		return -3;
	}
	catch (...) {
		std::cerr << "Error: Unknown exception occurred in ANSCV_ImageFlip_S!" << std::endl;
		return -4;
	}
}

// Post processing
extern "C" __declspec(dllexport) int  ANSCV_ImageBlurObjects_S(cv::Mat** imageIn, const char* strBboxes) {
	gpu_frame_invalidate(imageIn ? *imageIn : nullptr);
	try {
		cv::Mat localInput;
		{
			std::unique_lock<std::timed_mutex> lock(timeImageMutex, std::defer_lock);
			if (!lock.try_lock_for(std::chrono::milliseconds(MUTEX_TIMEOUT_MS))) {
				std::cerr << "Error: Mutex timeout in ANSCV_ImageBlurObjects_S!" << std::endl;
				return -6;
			}
			if (!imageIn || !(*imageIn) || (*imageIn)->empty() || !(*imageIn)->data) {
				std::cerr << "Error: Invalid or empty input image in ANSCV_ImageBlurObjects_S!" << std::endl;
				return -2;
			}
			localInput = **imageIn;
		}

		ANSCENTER::ANSOPENCV ansCVInstance;
		ansCVInstance.Init("");
		std::vector<cv::Rect> objects = ansCVInstance.GetBoundingBoxes(strBboxes);
		cv::Mat imOut = ansCVInstance.BlurObjects(localInput, objects);
		if (imOut.empty()) {
			std::cerr << "Error: BlurObjects failed in ANSCV_ImageBlurObjects_S!" << std::endl;
			return 0;
		}

		{
			std::unique_lock<std::timed_mutex> lock(timeImageMutex, std::defer_lock);
			if (!lock.try_lock_for(std::chrono::milliseconds(MUTEX_TIMEOUT_MS))) {
				std::cerr << "Error: Mutex timeout in ANSCV_ImageBlurObjects_S!" << std::endl;
				return -6;
			}
			if (!imageIn || !(*imageIn)) {
				std::cerr << "Error: Image became invalid in ANSCV_ImageBlurObjects_S!" << std::endl;
				return -2;
			}
			**imageIn = std::move(imOut);
			return 1;
		}
	}
	catch (const std::exception& e) {
		std::cerr << "Error: Exception occurred in ANSCV_ImageBlurObjects_S: " << e.what() << std::endl;
		return -3;
	}
	catch (...) {
		std::cerr << "Error: Unknown exception occurred in ANSCV_ImageBlurObjects_S!" << std::endl;
		return -4;
	}
}

extern "C" __declspec(dllexport) int  ANSCV_ImageBlurBackground_S(cv::Mat** imageIn, const char* strBboxes) {
	gpu_frame_invalidate(imageIn ? *imageIn : nullptr);
	try {
		cv::Mat localInput;
		{
			std::unique_lock<std::timed_mutex> lock(timeImageMutex, std::defer_lock);
			if (!lock.try_lock_for(std::chrono::milliseconds(MUTEX_TIMEOUT_MS))) {
				std::cerr << "Error: Mutex timeout in ANSCV_ImageBlurBackground_S!" << std::endl;
				return -6;
			}
			if (!imageIn || !(*imageIn) || (*imageIn)->empty() || !(*imageIn)->data) {
				std::cerr << "Error: Invalid or empty input image in ANSCV_ImageBlurBackground_S!" << std::endl;
				return -2;
			}
			localInput = **imageIn;
		}

		ANSCENTER::ANSOPENCV ansCVInstance;
		ansCVInstance.Init("");
		std::vector<cv::Rect> objects = ansCVInstance.GetBoundingBoxes(strBboxes);
		cv::Mat imOut = ansCVInstance.BlurBackground(localInput, objects);
		if (imOut.empty()) {
			std::cerr << "Error: BlurBackground failed in ANSCV_ImageBlurBackground_S!" << std::endl;
			return 0;
		}

		{
			std::unique_lock<std::timed_mutex> lock(timeImageMutex, std::defer_lock);
			if (!lock.try_lock_for(std::chrono::milliseconds(MUTEX_TIMEOUT_MS))) {
				std::cerr << "Error: Mutex timeout in ANSCV_ImageBlurBackground_S!" << std::endl;
				return -6;
			}
			if (!imageIn || !(*imageIn)) {
				std::cerr << "Error: Image became invalid in ANSCV_ImageBlurBackground_S!" << std::endl;
				return -2;
			}
			**imageIn = std::move(imOut);
			return 1;
		}
	}
	catch (const std::exception& e) {
		std::cerr << "Error: Exception occurred in ANSCV_ImageBlurBackground_S: " << e.what() << std::endl;
		return -3;
	}
	catch (...) {
		std::cerr << "Error: Unknown exception occurred in ANSCV_ImageBlurBackground_S!" << std::endl;
		return -4;
	}
}

extern "C" __declspec(dllexport) int ANSCV_ImageQRDecoder_S(cv::Mat** imageIn, int maxImageWidth, const char* strBboxes, LStrHandle detectedQRText) {
	try {
		cv::Mat localInput;
		{
			std::unique_lock<std::timed_mutex> lock(timeImageMutex, std::defer_lock);
			if (!lock.try_lock_for(std::chrono::milliseconds(MUTEX_TIMEOUT_MS))) {
				std::cerr << "Error: Mutex timeout in ANSCV_ImageQRDecoder_S!" << std::endl;
				return -6;
			}
			if (!imageIn || !(*imageIn) || (*imageIn)->empty() || !(*imageIn)->data) {
				std::cerr << "Error: Invalid or empty input image in ANSCV_ImageQRDecoder_S!" << std::endl;
				return -2;
			}
			localInput = **imageIn;
		}

		ANSCENTER::ANSOPENCV ansCVInstance;
		ansCVInstance.Init("");
		std::vector<cv::Rect> Bboxes = ansCVInstance.GetBoundingBoxes(strBboxes);
		std::string qrText = ansCVInstance.QRDecoderWithBBox(localInput, maxImageWidth, Bboxes);
		if (qrText.empty()) {
			std::cerr << "Error: QR decoding failed in ANSCV_ImageQRDecoder_S!" << std::endl;
			return 0;
		}

		// detectedQRText is a caller-owned LabVIEW handle; no global lock needed.
		const int size = static_cast<int>(qrText.length());
		if (size <= 0) return 0;
		MgErr error = DSSetHandleSize(detectedQRText, sizeof(int32) + size * sizeof(uChar));
		if (error != noErr) return 0;
		(*detectedQRText)->cnt = size;
		memcpy((*detectedQRText)->str, qrText.c_str(), size);
		return 1;
	}
	catch (const std::exception& e) {
		std::cerr << "Error: Exception occurred in ANSCV_ImageQRDecoder_S: " << e.what() << std::endl;
		return -3;
	}
	catch (...) {
		std::cerr << "Error: Unknown exception occurred in ANSCV_ImageQRDecoder_S!" << std::endl;
		return -4;
	}
}

extern "C" __declspec(dllexport) int ANSCV_ImagePatternMatchs_S(cv::Mat** imageIn, const char* templateFilePath, double threshold, LStrHandle detectedMatchedLocations) {
	try {
		cv::Mat localInput;
		{
			std::unique_lock<std::timed_mutex> lock(timeImageMutex, std::defer_lock);
			if (!lock.try_lock_for(std::chrono::milliseconds(MUTEX_TIMEOUT_MS))) {
				std::cerr << "Error: Mutex timeout in ANSCV_ImagePatternMatchs_S!" << std::endl;
				return -6;
			}
			if (!imageIn || !(*imageIn) || (*imageIn)->empty() || !(*imageIn)->data) {
				std::cerr << "Error: Invalid or empty input image in ANSCV_ImagePatternMatchs_S!" << std::endl;
				return -2;
			}
			localInput = **imageIn;
		}

		cv::Mat templateImage = cv::imread(templateFilePath, cv::IMREAD_COLOR);
		if (templateImage.empty()) {
			std::cerr << "Error: Failed to load template image from " << templateFilePath << std::endl;
			return -2;
		}

		ANSCENTER::ANSOPENCV ansCVInstance;
		ansCVInstance.Init("");
		std::string strMatchedLocations = ansCVInstance.PatternMatches(localInput, templateImage, threshold);

		// detectedMatchedLocations is a caller-owned LabVIEW handle; no global lock needed.
		const int size = static_cast<int>(strMatchedLocations.length());
		if (size <= 0) return 0;
		MgErr error = DSSetHandleSize(detectedMatchedLocations, sizeof(int32) + size * sizeof(uChar));
		if (error != noErr) {
			std::cerr << "Error: Failed to set handle size for detectedMatchedLocations!" << std::endl;
			return 0;
		}
		(*detectedMatchedLocations)->cnt = size;
		memcpy((*detectedMatchedLocations)->str, strMatchedLocations.c_str(), size);
		return 1;
	}
	catch (const std::exception& e) {
		std::cerr << "Error: Exception occurred in ANSCV_ImagePatternMatchs_S: " << e.what() << std::endl;
		return -3; // Exception occurred
	}
	catch (...) {
		std::cerr << "Error: Unknown exception occurred in ANSCV_ImagePatternMatchs_S!" << std::endl;
		return -4; // Unknown exception
	}
}

//extern "C" __declspec(dllexport) int  ANSCV_ImagesToMP4_S(const char* imageFolder, const char* outputVideoPath, int targetDurationSec) {
//	try {
//		std::unique_lock<std::timed_mutex> lock(timeImageMutex, std::defer_lock);
//		if (!lock.try_lock_for(std::chrono::milliseconds(MUTEX_TIMEOUT_MS))) {
//			std::cerr << "Error: Mutex timeout in ANSCV_ReSizeImage_S!" << std::endl;
//			return -6;
//		}
//		if (!imageFolder || strlen(imageFolder) == 0) {
//			std::cerr << "Error: Invalid image folder path in ANSCV_ImagesToMP4_S!" << std::endl;
//			return -1; // Invalid input
//		}
//
//		// Create MP4 video from images in the specified folder
//		bool success = ANSCENTER::ANSOPENCV::ImagesToMP4(imageFolder, outputVideoPath, targetDurationSec);
//		if (!success) {
//			std::cerr << "Error: Failed to create MP4 video from images in folder: " << imageFolder << std::endl;
//			return 0; // Failure
//		}
//
//		return 1; // Success
//	}
//	catch (const std::exception& e) {
//		std::cerr << "Error: Exception occurred in ANSCV_ImagesToMP4_S: " << e.what() << std::endl;
//		return -2; // Exception occurred
//	}
//	catch (...) {
//		std::cerr << "Error: Unknown exception occurred in ANSCV_ImagesToMP4_S!" << std::endl;
//		return -3; // Unknown exception
//	}
//
//}
extern "C" __declspec(dllexport) int ANSCV_ImagesToMP4_S(
	const char* imageFolder,
	const char* outputVideoPath,
	int maxWidth, int fps) {
	//return ANSCV_ImagesToMP4FF_S(imageFolder,outputVideoPath,maxWidth,fps);
	try {
		if (!imageFolder || strlen(imageFolder) == 0) {
			std::cerr << "Error: Invalid image folder path!" << std::endl;
			return -1;
		}

		if (!outputVideoPath || strlen(outputVideoPath) == 0) {
			std::cerr << "Error: Invalid output video path!" << std::endl;
			return -1;
		}

		fps = 10;

		bool success = ANSCENTER::ANSOPENCV::ImagesToMP4(
			imageFolder, outputVideoPath, maxWidth, fps);

		if (!success) {
			std::cerr << "Error: Failed to create MP4 from: "
				<< imageFolder << std::endl;
			return 0;
		}

		return 1;
	}
	catch (const std::exception& e) {
		std::cerr << "Error: Exception in ANSCV_ImagesToMP4_S: "
			<< e.what() << std::endl;
		return -2;
	}
	catch (...) {
		std::cerr << "Error: Unknown exception!" << std::endl;
		return -3;
	}
}

// ----------------------------------------------------------------------------
// Direct-FFmpeg variant — routes to ANSCENTER::ANSOPENCV::ImagesToMP4FF which
// encodes with libx265 (preferred) / libx264 / mpeg4 through the libav* API.
// Same fps=10 hardcoding as ANSCV_ImagesToMP4_S for consistency.
// ----------------------------------------------------------------------------
extern "C" __declspec(dllexport) int ANSCV_ImagesToMP4FF_S(
	const char* imageFolder,
	const char* outputVideoPath,
	int maxWidth, int fps) {

	try {
		if (!imageFolder || strlen(imageFolder) == 0) {
			std::cerr << "Error: Invalid image folder path!" << std::endl;
			return -1;
		}

		if (!outputVideoPath || strlen(outputVideoPath) == 0) {
			std::cerr << "Error: Invalid output video path!" << std::endl;
			return -1;
		}

		fps = 10;

		bool success = ANSCENTER::ANSOPENCV::ImagesToMP4FF(
			imageFolder, outputVideoPath, maxWidth, fps);

		if (!success) {
			std::cerr << "Error: Failed to create MP4 (FFmpeg) from: "
				<< imageFolder << std::endl;
			return 0;
		}

		return 1;
	}
	catch (const std::exception& e) {
		std::cerr << "Error: Exception in ANSCV_ImagesToMP4FF_S: "
			<< e.what() << std::endl;
		return -2;
	}
	catch (...) {
		std::cerr << "Error: Unknown exception in ANSCV_ImagesToMP4FF_S!" << std::endl;
		return -3;
	}
}

// ----------------------------------------------------------------------------
// Prints the copyright license of the FFmpeg libraries actually linked into
// ANSCV.dll. The FFmpeg symbols are resolved here (inside the DLL) where
// libavcodec / libavformat / libavutil are linked, so callers that don't link
// FFmpeg themselves (e.g. ANSCV-UnitTest) can still get the info.
//
// LGPL v2.1+ → commercial/closed-source distribution OK (subject to LGPL
//              requirements like allowing relinking with a modified FFmpeg).
// GPL  v2+   → ANSCV.dll is a derivative work and must be GPL-compatible.
// ----------------------------------------------------------------------------
extern "C" __declspec(dllexport) void ANSCV_PrintFFmpegLicense_S() {
	std::cout << "[FFmpeg] avutil   license: " << avutil_license()   << std::endl;
	std::cout << "[FFmpeg] avcodec  license: " << avcodec_license()  << std::endl;
	std::cout << "[FFmpeg] avformat license: " << avformat_license() << std::endl;
	std::cout << "[FFmpeg] swscale  license: " << swscale_license()  << std::endl;
}

// ----------------------------------------------------------------------------
// Hardware-accelerated variant — routes to ANSCENTER::ANSOPENCV::ImagesToMP4HW
// which probes NVIDIA NVENC, Intel QSV, and AMD AMF HEVC/H.264 encoders in
// order, then falls back to software (libx265/libx264/mpeg4) if none work.
// Same fps=10 hardcoding as the other two variants.
// ----------------------------------------------------------------------------
extern "C" __declspec(dllexport) int ANSCV_ImagesToMP4HW_S(
	const char* imageFolder,
	const char* outputVideoPath,
	int maxWidth, int fps) {

	try {
		if (!imageFolder || strlen(imageFolder) == 0) {
			std::cerr << "Error: Invalid image folder path!" << std::endl;
			return -1;
		}

		if (!outputVideoPath || strlen(outputVideoPath) == 0) {
			std::cerr << "Error: Invalid output video path!" << std::endl;
			return -1;
		}

		fps = 10;

		bool success = ANSCENTER::ANSOPENCV::ImagesToMP4HW(
			imageFolder, outputVideoPath, maxWidth, fps);

		if (!success) {
			std::cerr << "Error: Failed to create MP4 (FFmpeg-HW) from: "
				<< imageFolder << std::endl;
			return 0;
		}

		return 1;
	}
	catch (const std::exception& e) {
		std::cerr << "Error: Exception in ANSCV_ImagesToMP4HW_S: "
			<< e.what() << std::endl;
		return -2;
	}
	catch (...) {
		std::cerr << "Error: Unknown exception in ANSCV_ImagesToMP4HW_S!" << std::endl;
		return -3;
	}
}

// ============================================================================
// V2 functions: accept uint64_t handleVal by value instead of ANSOPENCV**
// This eliminates the LabVIEW buffer reuse bug with double-pointer handles.
// ============================================================================

extern "C" __declspec(dllexport) int ANSCV_ImageResize_V2(uint64_t handleVal, unsigned char* inputImage, unsigned int bufferLength, int width, int height, LStrHandle outputImage)
{
	auto* h = reinterpret_cast<ANSCENTER::ANSOPENCV*>(handleVal);
	if (!h) return -1;

	if (!inputImage || bufferLength == 0) return 0;
	if (!outputImage) return 0;
	if (width <= 0 || height <= 0) return 0;

	try {
		cv::Mat inputFrame = cv::imdecode(cv::Mat(1, bufferLength, CV_8UC1, inputImage), cv::IMREAD_COLOR);
		if (inputFrame.empty()) return 0;

		cv::Mat outputFrame;
		h->ImageResize(inputFrame, width, height, outputFrame);
		if (outputFrame.empty()) return 0;

		std::string binaryData = h->MatToBinaryData(outputFrame);
		const int size = static_cast<int>(binaryData.length());
		if (size <= 0) return 0;

		MgErr error = DSSetHandleSize(outputImage, sizeof(int32) + size * sizeof(uChar));
		if (error != noErr) return 0;

		(*outputImage)->cnt = size;
		memcpy((*outputImage)->str, binaryData.c_str(), size);
		return 1;
	}
	catch (const cv::Exception& e) {
		std::cerr << "OpenCV exception in ANSCV_ImageResize_V2: " << e.what() << std::endl;
		return 0;
	}
	catch (const std::exception& e) {
		std::cerr << "Exception in ANSCV_ImageResize_V2: " << e.what() << std::endl;
		return 0;
	}
	catch (...) {
		std::cerr << "Unknown exception in ANSCV_ImageResize_V2" << std::endl;
		return 0;
	}
}

extern "C" __declspec(dllexport) int ANSCV_ImageResizeWithRatio_V2(uint64_t handleVal, unsigned char* inputImage, unsigned int bufferLength, int width, LStrHandle outputImage)
{
	auto* h = reinterpret_cast<ANSCENTER::ANSOPENCV*>(handleVal);
	if (!h) return -1;

	if (!inputImage || bufferLength == 0) return 0;
	if (!outputImage) return 0;
	if (width <= 0) return 0;

	try {
		cv::Mat inputFrame = cv::imdecode(cv::Mat(1, bufferLength, CV_8UC1, inputImage), cv::IMREAD_COLOR);
		if (inputFrame.empty()) return 0;

		cv::Mat outputFrame;
		h->ImageResizeWithRatio(inputFrame, width, outputFrame);
		if (outputFrame.empty()) return 0;

		std::string binaryData = h->MatToBinaryData(outputFrame);
		const int size = static_cast<int>(binaryData.length());
		if (size <= 0) return 0;

		MgErr error = DSSetHandleSize(outputImage, sizeof(int32) + size * sizeof(uChar));
		if (error != noErr) return 0;

		(*outputImage)->cnt = size;
		memcpy((*outputImage)->str, binaryData.c_str(), size);
		return 1;
	}
	catch (const cv::Exception& e) {
		std::cerr << "OpenCV exception in ANSCV_ImageResizeWithRatio_V2: " << e.what() << std::endl;
		return 0;
	}
	catch (const std::exception& e) {
		std::cerr << "Exception in ANSCV_ImageResizeWithRatio_V2: " << e.what() << std::endl;
		return 0;
	}
	catch (...) {
		std::cerr << "Unknown exception in ANSCV_ImageResizeWithRatio_V2" << std::endl;
		return 0;
	}
}

extern "C" __declspec(dllexport) int ANSCV_ImageToBase64_V2(uint64_t handleVal, unsigned char* inputImage, unsigned int bufferLength, LStrHandle outputImage)
{
	auto* h = reinterpret_cast<ANSCENTER::ANSOPENCV*>(handleVal);
	if (!h) return -1;

	if (!inputImage || bufferLength == 0) return 0;
	if (!outputImage) return 0;

	try {
		cv::Mat inputFrame = cv::imdecode(cv::Mat(1, bufferLength, CV_8UC1, inputImage), cv::IMREAD_COLOR);
		if (inputFrame.empty()) return 0;

		std::string base64Data = h->MatToBase64(inputFrame);
		const int size = static_cast<int>(base64Data.length());
		if (size <= 0) return 0;

		MgErr error = DSSetHandleSize(outputImage, sizeof(int32) + size * sizeof(uChar));
		if (error != noErr) return 0;

		(*outputImage)->cnt = size;
		memcpy((*outputImage)->str, base64Data.c_str(), size);
		return 1;
	}
	catch (const cv::Exception& e) {
		std::cerr << "OpenCV exception in ANSCV_ImageToBase64_V2: " << e.what() << std::endl;
		return 0;
	}
	catch (const std::exception& e) {
		std::cerr << "Exception in ANSCV_ImageToBase64_V2: " << e.what() << std::endl;
		return 0;
	}
	catch (...) {
		std::cerr << "Unknown exception in ANSCV_ImageToBase64_V2" << std::endl;
		return 0;
	}
}

extern "C" __declspec(dllexport) int ANSCV_ImageToGray_V2(uint64_t handleVal, unsigned char* inputImage, unsigned int bufferLength, LStrHandle outputImage)
{
	auto* h = reinterpret_cast<ANSCENTER::ANSOPENCV*>(handleVal);
	if (!h) return -1;
	try {
		cv::Mat inputFrame = cv::imdecode(cv::Mat(1, bufferLength, CV_8UC1, inputImage), cv::IMREAD_COLOR);
		cv::Mat outputFrame = h->ToGray(inputFrame);
		std::string st = h->MatToBinaryData(outputFrame);
		int size = st.length();
		if (size > 0) {
			MgErr error = DSSetHandleSize(outputImage, sizeof(int32) + size * sizeof(uChar));
			if (error == noErr) {
				(*outputImage)->cnt = size;
				memcpy((*outputImage)->str, st.c_str(), size);
				return 1;
			}
			else return 0;
		}
		else return 0;
	}
	catch (...) { return -1; }
}

extern "C" __declspec(dllexport) int ANSCV_ImageDenoise_V2(uint64_t handleVal, unsigned char* inputImage, unsigned int bufferLength, LStrHandle outputImage) {
	auto* h = reinterpret_cast<ANSCENTER::ANSOPENCV*>(handleVal);
	if (!h) return -1;
	try {
		cv::Mat inputFrame = cv::imdecode(cv::Mat(1, bufferLength, CV_8UC1, inputImage), cv::IMREAD_COLOR);
		cv::Mat outputFrame = h->ImageDenoise(inputFrame);
		std::string st = h->MatToBinaryData(outputFrame);
		int size = st.length();
		if (size > 0) {
			MgErr error = DSSetHandleSize(outputImage, sizeof(int32) + size * sizeof(uChar));
			if (error == noErr) {
				(*outputImage)->cnt = size;
				memcpy((*outputImage)->str, st.c_str(), size);
				return 1;
			}
			else return 0;
		}
		else return 0;
	}
	catch (...) { return -1; }
}

extern "C" __declspec(dllexport) int ANSCV_ImageRepair_V2(uint64_t handleVal, unsigned char* inputImage, unsigned int bufferLength, LStrHandle outputImage) {
	auto* h = reinterpret_cast<ANSCENTER::ANSOPENCV*>(handleVal);
	if (!h) return -1;
	try {
		cv::Mat inputFrame = cv::imdecode(cv::Mat(1, bufferLength, CV_8UC1, inputImage), cv::IMREAD_COLOR);
		cv::Mat outputFrame = h->ImageRepair(inputFrame);
		std::string st = h->MatToBinaryData(outputFrame);
		int size = st.length();
		if (size > 0) {
			MgErr error = DSSetHandleSize(outputImage, sizeof(int32) + size * sizeof(uChar));
			if (error == noErr) {
				(*outputImage)->cnt = size;
				memcpy((*outputImage)->str, st.c_str(), size);
				return 1;
			}
			else return 0;
		}
		else return 0;
	}
	catch (...) { return -1; }
}

extern "C" __declspec(dllexport) int ANSCV_ImageAutoWhiteBalance_V2(uint64_t handleVal, unsigned char* inputImage, unsigned int bufferLength, LStrHandle outputImage) {
	auto* h = reinterpret_cast<ANSCENTER::ANSOPENCV*>(handleVal);
	if (!h) return -1;
	try {
		cv::Mat inputFrame = cv::imdecode(cv::Mat(1, bufferLength, CV_8UC1, inputImage), cv::IMREAD_COLOR);
		cv::Mat outputFrame = h->ImageWhiteBalance(inputFrame);
		std::string st = h->MatToBinaryData(outputFrame);
		int size = st.length();
		if (size > 0) {
			MgErr error = DSSetHandleSize(outputImage, sizeof(int32) + size * sizeof(uChar));
			if (error == noErr) {
				(*outputImage)->cnt = size;
				memcpy((*outputImage)->str, st.c_str(), size);
				return 1;
			}
			else return 0;
		}
		else return 0;
	}
	catch (...) { return -1; }
}

extern "C" __declspec(dllexport) int ANSCV_ImageBrightEnhance_V2(uint64_t handleVal, unsigned char* inputImage, unsigned int bufferLength, double brightnessScaleFactor, LStrHandle outputImage) {
	auto* h = reinterpret_cast<ANSCENTER::ANSOPENCV*>(handleVal);
	if (!h) return -1;
	try {
		cv::Mat inputFrame = cv::imdecode(cv::Mat(1, bufferLength, CV_8UC1, inputImage), cv::IMREAD_COLOR);
		cv::Mat outputFrame = h->ImageDarkEnhancement(inputFrame, brightnessScaleFactor);
		std::string st = h->MatToBinaryData(outputFrame);
		int size = st.length();
		if (size > 0) {
			MgErr error = DSSetHandleSize(outputImage, sizeof(int32) + size * sizeof(uChar));
			if (error == noErr) {
				(*outputImage)->cnt = size;
				memcpy((*outputImage)->str, st.c_str(), size);
				return 1;
			}
			else return 0;
		}
		else return 0;
	}
	catch (...) { return -1; }
}

extern "C" __declspec(dllexport) int ANSCV_ImageContrastEnhance_V2(uint64_t handleVal, unsigned char* inputImage, unsigned int bufferLength, LStrHandle outputImage) {
	auto* h = reinterpret_cast<ANSCENTER::ANSOPENCV*>(handleVal);
	if (!h) return -1;
	try {
		cv::Mat inputFrame = cv::imdecode(cv::Mat(1, bufferLength, CV_8UC1, inputImage), cv::IMREAD_COLOR);
		cv::Mat outputFrame = h->ImageContrastEnhancement(inputFrame);
		std::string st = h->MatToBinaryData(outputFrame);
		int size = st.length();
		if (size > 0) {
			MgErr error = DSSetHandleSize(outputImage, sizeof(int32) + size * sizeof(uChar));
			if (error == noErr) {
				(*outputImage)->cnt = size;
				memcpy((*outputImage)->str, st.c_str(), size);
				return 1;
			}
			else return 0;
		}
		else return 0;
	}
	catch (...) { return -1; }
}

extern "C" __declspec(dllexport) int ANSCV_ImageCrop_V2(uint64_t handleVal, unsigned char* inputImage, unsigned int bufferLength, int x, int y, int width, int height, LStrHandle outputImage)
{
	auto* h = reinterpret_cast<ANSCENTER::ANSOPENCV*>(handleVal);
	if (!h) return -1;
	try {
		cv::Mat inputFrame = cv::imdecode(cv::Mat(1, bufferLength, CV_8UC1, inputImage), cv::IMREAD_COLOR);
		cv::Rect roi(x, y, width, height);
		cv::Mat outputFrame = h->ImageCrop(inputFrame, roi);
		std::string st = h->MatToBinaryData(outputFrame);
		int size = st.length();
		if (size > 0) {
			MgErr error = DSSetHandleSize(outputImage, sizeof(int32) + size * sizeof(uChar));
			if (error == noErr) {
				(*outputImage)->cnt = size;
				memcpy((*outputImage)->str, st.c_str(), size);
				return 1;
			}
			else return 0;
		}
		else return 0;
	}
	catch (...) { return -1; }
}

extern "C" __declspec(dllexport) int ANSCV_GetImageSize_V2(uint64_t handleVal, unsigned char* inputImage, unsigned int bufferLength, LStrHandle imageSize) {
	auto* h = reinterpret_cast<ANSCENTER::ANSOPENCV*>(handleVal);
	if (!h) return -1;
	try {
		cv::Mat inputFrame = cv::imdecode(cv::Mat(1, bufferLength, CV_8UC1, inputImage), cv::IMREAD_COLOR);
		int width = inputFrame.cols;
		int height = inputFrame.rows;
		std::string st = std::to_string(width) + "x" + std::to_string(height);
		int size = st.length();
		if (size > 0) {
			MgErr error = DSSetHandleSize(imageSize, sizeof(int32) + size * sizeof(uChar));
			if (error == noErr) {
				(*imageSize)->cnt = size;
				memcpy((*imageSize)->str, st.c_str(), size);
				return 1;
			}
			else return 0;
		}
		else return 0;
	}
	catch (...) { return -1; }
}

extern "C" __declspec(dllexport) int ANSCV_GetImageSizeFromImageFile_V2(uint64_t handleVal, const char* imageFilePath, LStrHandle imageSize) {
	auto* h = reinterpret_cast<ANSCENTER::ANSOPENCV*>(handleVal);
	if (!h) return -1;
	try {
		cv::VideoCapture cap(imageFilePath);
		int width, height;
		if (!cap.isOpened()) {
			std::cerr << "Error opening file: " << imageFilePath << std::endl;
			width = 0;
			height = 0;
		}
		else {
			width = static_cast<int>(cap.get(cv::CAP_PROP_FRAME_WIDTH));
			height = static_cast<int>(cap.get(cv::CAP_PROP_FRAME_HEIGHT));
		}
		cap.release();
		std::string st = std::to_string(width) + "x" + std::to_string(height);
		int size = st.length();
		if (size > 0) {
			MgErr error = DSSetHandleSize(imageSize, sizeof(int32) + size * sizeof(uChar));
			if (error == noErr) {
				(*imageSize)->cnt = size;
				memcpy((*imageSize)->str, st.c_str(), size);
				return 1;
			}
			else return 0;
		}
		else return 0;
	}
	catch (...) { return -1; }
}

extern "C" __declspec(dllexport) int ANSCV_BlurObjects_V2(uint64_t handleVal, unsigned char* inputImage, unsigned int bufferLength, const char* strBboxes, LStrHandle outputImage) {
	auto* h = reinterpret_cast<ANSCENTER::ANSOPENCV*>(handleVal);
	if (!h) return -1;
	try {
		cv::Mat inputFrame = cv::imdecode(cv::Mat(1, bufferLength, CV_8UC1, inputImage), cv::IMREAD_COLOR);
		std::vector<cv::Rect> objects = h->GetBoundingBoxes(strBboxes);
		cv::Mat outputFrame = h->BlurObjects(inputFrame, objects);
		std::string st = h->MatToBinaryData(outputFrame);
		int size = st.length();
		if (size > 0) {
			MgErr error = DSSetHandleSize(outputImage, sizeof(int32) + size * sizeof(uChar));
			if (error == noErr) {
				(*outputImage)->cnt = size;
				memcpy((*outputImage)->str, st.c_str(), size);
				return 1;
			}
			else return 0;
		}
		else return 0;
	}
	catch (...) { return -1; }
}

extern "C" __declspec(dllexport) int ANSCV_BlurBackground_V2(uint64_t handleVal, unsigned char* inputImage, unsigned int bufferLength, const char* strBboxes, LStrHandle outputImage) {
	auto* h = reinterpret_cast<ANSCENTER::ANSOPENCV*>(handleVal);
	if (!h) return -1;
	try {
		cv::Mat inputFrame = cv::imdecode(cv::Mat(1, bufferLength, CV_8UC1, inputImage), cv::IMREAD_COLOR);
		std::vector<cv::Rect> objects = h->GetBoundingBoxes(strBboxes);
		cv::Mat outputFrame = h->BlurBackground(inputFrame, objects);
		std::string st = h->MatToBinaryData(outputFrame);
		int size = st.length();
		if (size > 0) {
			MgErr error = DSSetHandleSize(outputImage, sizeof(int32) + size * sizeof(uChar));
			if (error == noErr) {
				(*outputImage)->cnt = size;
				memcpy((*outputImage)->str, st.c_str(), size);
				return 1;
			}
			else return 0;
		}
		else return 0;
	}
	catch (...) { return -1; }
}

extern "C" __declspec(dllexport) int ANSCV_QRDecoder_V2(uint64_t handleVal, unsigned char* inputImage, unsigned int bufferLength, LStrHandle detectedQRText) {
	auto* h = reinterpret_cast<ANSCENTER::ANSOPENCV*>(handleVal);
	if (!h) return -1;
	try {
		cv::Mat inputFrame = cv::imdecode(cv::Mat(1, bufferLength, CV_8UC1, inputImage), cv::IMREAD_COLOR);
		std::string st = h->QRDecoder(inputFrame);
		int size = st.length();
		if (size > 0) {
			MgErr error = DSSetHandleSize(detectedQRText, sizeof(int32) + size * sizeof(uChar));
			if (error == noErr) {
				(*detectedQRText)->cnt = size;
				memcpy((*detectedQRText)->str, st.c_str(), size);
				return 1;
			}
			else return 0;
		}
		else return 0;
	}
	catch (...) { return -1; }
}

extern "C" __declspec(dllexport) int ANSCV_PatternMatchs_V2(uint64_t handleVal, unsigned char* inputImage, unsigned int bufferLength, const char* templateFilePath, double threshold, LStrHandle detectedMatchedLocations) {
	auto* h = reinterpret_cast<ANSCENTER::ANSOPENCV*>(handleVal);
	if (!h) return -1;
	try {
		cv::Mat inputFrame = cv::imdecode(cv::Mat(1, bufferLength, CV_8UC1, inputImage), cv::IMREAD_COLOR);
		cv::Mat templateImage = cv::imread(templateFilePath, cv::IMREAD_COLOR);
		std::string st = h->PatternMatches(inputFrame, templateImage, threshold);
		int size = st.length();
		if (size > 0) {
			MgErr error = DSSetHandleSize(detectedMatchedLocations, sizeof(int32) + size * sizeof(uChar));
			if (error == noErr) {
				(*detectedMatchedLocations)->cnt = size;
				memcpy((*detectedMatchedLocations)->str, st.c_str(), size);
				return 1;
			}
			else return 0;
		}
		else return 0;
	}
	catch (...) { return -1; }
}

extern "C" __declspec(dllexport) int ANSCV_RotateImage_V2(uint64_t handleVal, unsigned char* inputImage, unsigned int bufferLength, double angle, LStrHandle outputImage) {
	auto* h = reinterpret_cast<ANSCENTER::ANSOPENCV*>(handleVal);
	if (!h) return -1;
	try {
		cv::Mat inputFrame = cv::imdecode(cv::Mat(1, bufferLength, CV_8UC1, inputImage), cv::IMREAD_COLOR);
		cv::Mat outputFrame = h->RotateImage(inputFrame, angle);
		std::string st = h->MatToBinaryData(outputFrame);
		int size = st.length();
		if (size > 0) {
			MgErr error = DSSetHandleSize(outputImage, sizeof(int32) + size * sizeof(uChar));
			if (error == noErr) {
				(*outputImage)->cnt = size;
				memcpy((*outputImage)->str, st.c_str(), size);
				return 1;
			}
			else return 0;
		}
		else return 0;
	}
	catch (...) { return -1; }
}

extern "C" __declspec(dllexport) int ANSCV_FlipImage_V2(uint64_t handleVal, unsigned char* inputImage, unsigned int bufferLength, int flipCode, LStrHandle outputImage) {
	auto* h = reinterpret_cast<ANSCENTER::ANSOPENCV*>(handleVal);
	if (!h) return -1;
	try {
		cv::Mat inputFrame = cv::imdecode(cv::Mat(1, bufferLength, CV_8UC1, inputImage), cv::IMREAD_COLOR);
		cv::Mat outputFrame = h->FlipImage(inputFrame, flipCode);
		std::string st = h->MatToBinaryData(outputFrame);
		int size = st.length();
		if (size > 0) {
			MgErr error = DSSetHandleSize(outputImage, sizeof(int32) + size * sizeof(uChar));
			if (error == noErr) {
				(*outputImage)->cnt = size;
				memcpy((*outputImage)->str, st.c_str(), size);
				return 1;
			}
			else return 0;
		}
		else return 0;
	}
	catch (...) { return -1; }
}

extern "C" __declspec(dllexport) int ANSCV_ShiftImage_V2(uint64_t handleVal, unsigned char* inputImage, unsigned int bufferLength, int shiftX, int shiftY, LStrHandle outputImage) {
	auto* h = reinterpret_cast<ANSCENTER::ANSOPENCV*>(handleVal);
	if (!h) return -1;
	try {
		cv::Mat inputFrame = cv::imdecode(cv::Mat(1, bufferLength, CV_8UC1, inputImage), cv::IMREAD_COLOR);
		cv::Mat outputFrame = h->ShiftImage(inputFrame, shiftX, shiftY);
		std::string st = h->MatToBinaryData(outputFrame);
		int size = st.length();
		if (size > 0) {
			MgErr error = DSSetHandleSize(outputImage, sizeof(int32) + size * sizeof(uChar));
			if (error == noErr) {
				(*outputImage)->cnt = size;
				memcpy((*outputImage)->str, st.c_str(), size);
				return 1;
			}
			else return 0;
		}
		else return 0;
	}
	catch (...) { return -1; }
}

extern "C" __declspec(dllexport) int ANSCV_AddGaussianNoise_V2(uint64_t handleVal, unsigned char* inputImage, unsigned int bufferLength, double mean, double stddev, LStrHandle outputImage) {
	auto* h = reinterpret_cast<ANSCENTER::ANSOPENCV*>(handleVal);
	if (!h) return -1;
	try {
		cv::Mat inputFrame = cv::imdecode(cv::Mat(1, bufferLength, CV_8UC1, inputImage), cv::IMREAD_COLOR);
		cv::Mat outputFrame = h->AddGaussianNoise(inputFrame, mean, stddev);
		std::string st = h->MatToBinaryData(outputFrame);
		int size = st.length();
		if (size > 0) {
			MgErr error = DSSetHandleSize(outputImage, sizeof(int32) + size * sizeof(uChar));
			if (error == noErr) {
				(*outputImage)->cnt = size;
				memcpy((*outputImage)->str, st.c_str(), size);
				return 1;
			}
			else return 0;
		}
		else return 0;
	}
	catch (...) { return -1; }
}

extern "C" __declspec(dllexport) int ANSCV_AddSaltAndPepperNoise_V2(uint64_t handleVal, unsigned char* inputImage, unsigned int bufferLength, double amount, LStrHandle outputImage) {
	auto* h = reinterpret_cast<ANSCENTER::ANSOPENCV*>(handleVal);
	if (!h) return -1;
	try {
		cv::Mat inputFrame = cv::imdecode(cv::Mat(1, bufferLength, CV_8UC1, inputImage), cv::IMREAD_COLOR);
		cv::Mat outputFrame = h->AddSaltAndPepperNoise(inputFrame, amount);
		std::string st = h->MatToBinaryData(outputFrame);
		int size = st.length();
		if (size > 0) {
			MgErr error = DSSetHandleSize(outputImage, sizeof(int32) + size * sizeof(uChar));
			if (error == noErr) {
				(*outputImage)->cnt = size;
				memcpy((*outputImage)->str, st.c_str(), size);
				return 1;
			}
			else return 0;
		}
		else return 0;
	}
	catch (...) { return -1; }
}

extern "C" __declspec(dllexport) int ANSCV_AddSpeckleNoise_V2(uint64_t handleVal, unsigned char* inputImage, unsigned int bufferLength, double stddev, LStrHandle outputImage) {
	auto* h = reinterpret_cast<ANSCENTER::ANSOPENCV*>(handleVal);
	if (!h) return -1;
	try {
		cv::Mat inputFrame = cv::imdecode(cv::Mat(1, bufferLength, CV_8UC1, inputImage), cv::IMREAD_COLOR);
		cv::Mat outputFrame = h->AddSpeckleNoise(inputFrame, stddev);
		std::string st = h->MatToBinaryData(outputFrame);
		int size = st.length();
		if (size > 0) {
			MgErr error = DSSetHandleSize(outputImage, sizeof(int32) + size * sizeof(uChar));
			if (error == noErr) {
				(*outputImage)->cnt = size;
				memcpy((*outputImage)->str, st.c_str(), size);
				return 1;
			}
			else return 0;
		}
		else return 0;
	}
	catch (...) { return -1; }
}

// ── IMAQ <-> cv::Mat conversion ──────────────────────────────────

extern "C" __declspec(dllexport) int ANSCV_IMAQ2Image(void* imaqHandle, cv::Mat** imageOut) {
	try {
		if (!imaqHandle || !imageOut) {
			ANS_DBG("ANSCV", "IMAQ2Image: null pointer - imaqHandle=%p imageOut=%p", imaqHandle, (void*)imageOut);
			return -2;
		}

		// Try as Image* first (direct pointer)
		Image* imaqImage = static_cast<Image*>(imaqHandle);
		ImageInfo info = {};
		if (!imaqGetImageInfo(imaqImage, &info)) {
			// Try as Image** (pointer-to-pointer, LabVIEW handle indirection)
			ANS_DBG("ANSCV", "IMAQ2Image: Image* failed (err=%d), trying Image**", imaqGetLastError());
			Image** imaqImagePtr = static_cast<Image**>(imaqHandle);
			imaqImage = *imaqImagePtr;
			if (!imaqImage || !imaqGetImageInfo(imaqImage, &info)) {
				int errCode = imaqGetLastError();
				const char* errFunc = imaqGetLastErrorFunc();
				ANS_DBG("ANSCV", "IMAQ2Image: FAILED both Image* and Image** - err=%d func=%s",
				         errCode, errFunc ? errFunc : "unknown");
				return -4;
			}
			ANS_DBG("ANSCV", "IMAQ2Image: resolved as Image** OK");
		} else {
			ANS_DBG("ANSCV", "IMAQ2Image: resolved as Image* OK");
		}

		if (!info.imageStart || info.xRes <= 0 || info.yRes <= 0) {
			ANS_DBG("ANSCV", "IMAQ2Image: invalid image - start=%p xRes=%d yRes=%d",
			         info.imageStart, info.xRes, info.yRes);
			return -2;
		}

		int width  = info.xRes;
		int height = info.yRes;
		int stride = info.pixelsPerLine;

		ANS_DBG("ANSCV", "IMAQ2Image: %dx%d stride=%d type=%d", width, height, stride, info.imageType);

		cv::Mat result;
		switch (info.imageType) {
			case IMAQ_IMAGE_U8: {
				cv::Mat wrapper(height, width, CV_8UC1, info.imageStart, stride * sizeof(unsigned char));
				result = wrapper.clone();
				break;
			}
			case IMAQ_IMAGE_U16: {
				cv::Mat wrapper(height, width, CV_16UC1, info.imageStart, stride * sizeof(unsigned short));
				result = wrapper.clone();
				break;
			}
			case IMAQ_IMAGE_RGB: {
				cv::Mat bgra(height, width, CV_8UC4, info.imageStart, stride * sizeof(RGBValue));
				cv::cvtColor(bgra, result, cv::COLOR_BGRA2BGR);
				break;
			}
			case IMAQ_IMAGE_SGL: {
				cv::Mat wrapper(height, width, CV_32FC1, info.imageStart, stride * sizeof(float));
				result = wrapper.clone();
				break;
			}
			default:
				ANS_DBG("ANSCV", "IMAQ2Image: unsupported IMAQ type %d", info.imageType);
				return -5;
		}

		*imageOut = anscv_mat_new(result);
		ANS_DBG("ANSCV", "IMAQ2Image: SUCCESS - Mat=%p %dx%d type=%d",
		         (void*)*imageOut, result.cols, result.rows, result.type());
		return 1;
	}
	catch (const std::exception& e) {
		ANS_DBG("ANSCV", "IMAQ2Image: EXCEPTION - %s", e.what());
		return -3;
	}
	catch (...) {
		ANS_DBG("ANSCV", "IMAQ2Image: UNKNOWN EXCEPTION");
		return -1;
	}
}

extern "C" __declspec(dllexport) int ANSCV_Image2IMAQ(cv::Mat** imageIn, LStrHandle outputImage) {
	try {
		if (!imageIn || !(*imageIn) || (*imageIn)->empty() || !outputImage) {
			ANS_DBG("ANSCV", "Image2IMAQ: null input - imageIn=%p outputImage=%p",
			         (void*)imageIn, (void*)outputImage);
			return -2;
		}

		const cv::Mat& mat = **imageIn;
		ANS_DBG("ANSCV", "Image2IMAQ: Mat=%p (%dx%d type=%d)",
		         (void*)*imageIn, mat.cols, mat.rows, mat.type());

		// Encode as lossless PNG
		std::vector<unsigned char> buf;
		std::vector<int> params = {cv::IMWRITE_PNG_COMPRESSION, 1};
		if (!cv::imencode(".png", mat, buf, params)) {
			ANS_DBG("ANSCV", "Image2IMAQ: imencode PNG failed");
			return -4;
		}

		int size = static_cast<int>(buf.size());
		MgErr error = DSSetHandleSize(outputImage, sizeof(int32) + size * sizeof(uChar));
		if (error != noErr) {
			ANS_DBG("ANSCV", "Image2IMAQ: DSSetHandleSize failed - err=%d", error);
			return -4;
		}
		(*outputImage)->cnt = size;
		memcpy((*outputImage)->str, buf.data(), size);

		ANS_DBG("ANSCV", "Image2IMAQ: SUCCESS - %d bytes PNG (%dx%d)", size, mat.cols, mat.rows);
		return 1;
	}
	catch (const std::exception& e) {
		ANS_DBG("ANSCV", "Image2IMAQ: EXCEPTION - %s", e.what());
		return -3;
	}
	catch (...) {
		ANS_DBG("ANSCV", "Image2IMAQ: UNKNOWN EXCEPTION");
		return -1;
	}
}

// ── cv::Mat -> LabVIEW 2D U32 array for IMAQ ArrayToColorImage VI ───

extern "C" __declspec(dllexport) int ANSCV_ImageToArray(cv::Mat** imageIn, LVArray2D_U32Hdl arrayOut) {
	// Outputs 2D U32 array (height x width), each U32 = packed XRGB
	// Wire to IMAQ ArrayToColorImage "Image Pixels (U32)" input
	try {
		if (!imageIn || !(*imageIn) || (*imageIn)->empty() || !arrayOut) {
			ANS_DBG("ANSCV", "ImageToArray: invalid input");
			return -2;
		}

		const cv::Mat& mat = **imageIn;
		int rows = mat.rows;
		int cols = mat.cols;
		ANS_DBG("ANSCV", "ImageToArray: Mat=%p (%dx%d type=%d)",
		         (void*)*imageIn, cols, rows, mat.type());

		// Convert to BGRA with alpha=0 (IMAQ convention)
		cv::Mat bgra;
		switch (mat.type()) {
			case CV_8UC3: {
				cv::cvtColor(mat, bgra, cv::COLOR_BGR2BGRA);
				// Force alpha to 0 (cvtColor may set it to 255)
				std::vector<cv::Mat> ch;
				cv::split(bgra, ch);
				ch[3].setTo(0);
				cv::merge(ch, bgra);
				break;
			}
			case CV_8UC4: {
				bgra = mat.clone();
				// Force alpha to 0 for IMAQ
				std::vector<cv::Mat> ch;
				cv::split(bgra, ch);
				ch[3].setTo(0);
				cv::merge(ch, bgra);
				break;
			}
			case CV_8UC1:
				cv::cvtColor(mat, bgra, cv::COLOR_GRAY2BGRA);
				// cvtColor sets alpha to 0, which is correct for IMAQ
				break;
			default:
				ANS_DBG("ANSCV", "ImageToArray: unsupported type %d", mat.type());
				return -5;
		}

		int totalPixels = rows * cols;

		// Resize LabVIEW 2D U32 array
		MgErr err = NumericArrayResize(uL, 2, reinterpret_cast<UHandle*>(&arrayOut), totalPixels);
		if (err != noErr) {
			ANS_DBG("ANSCV", "ImageToArray: NumericArrayResize failed - err=%d", err);
			return -4;
		}
		(*arrayOut)->dimSizes[0] = rows;
		(*arrayOut)->dimSizes[1] = cols;

		// IMAQ RGBValue layout is {B, G, R, alpha} which is the same memory layout as BGRA
		// Copy directly — each 4 bytes of BGRA = one U32 in the array
		memcpy((*arrayOut)->elt, bgra.data, totalPixels * sizeof(uInt32));

		ANS_DBG("ANSCV", "ImageToArray: SUCCESS - %dx%d (%d pixels)", cols, rows, totalPixels);
		return 1;
	}
	catch (const std::exception& e) {
		ANS_DBG("ANSCV", "ImageToArray: EXCEPTION - %s", e.what());
		return -3;
	}
	catch (...) {
		ANS_DBG("ANSCV", "ImageToArray: UNKNOWN EXCEPTION");
		return -1;
	}
}

// ── cv::Mat -> 1D U8 flat array (for .NET MemoryStream / Picture control) ───

extern "C" __declspec(dllexport) int ANSCV_ImageToFlatArray(
		cv::Mat** imageIn, LVArray1D_U8Hdl arrayOut, int* width, int* height, int* channels) {
	// Outputs raw BGR pixel data as a flat 1D U8 array (row-major, no padding)
	// width/height/channels describe the image layout
	try {
		if (!imageIn || !(*imageIn) || (*imageIn)->empty() || !arrayOut || !width || !height || !channels) {
			ANS_DBG("ANSCV", "ImageToFlatArray: invalid input");
			return -2;
		}

		const cv::Mat& mat = **imageIn;
		*width    = mat.cols;
		*height   = mat.rows;
		*channels = mat.channels();

		ANS_DBG("ANSCV", "ImageToFlatArray: Mat=%p (%dx%d ch=%d type=%d)",
		         (void*)*imageIn, mat.cols, mat.rows, mat.channels(), mat.type());

		// Ensure continuous memory layout
		cv::Mat continuous = mat.isContinuous() ? mat : mat.clone();

		int totalBytes = continuous.rows * continuous.cols * continuous.channels();

		MgErr err = NumericArrayResize(uB, 1, reinterpret_cast<UHandle*>(&arrayOut), totalBytes);
		if (err != noErr) {
			ANS_DBG("ANSCV", "ImageToFlatArray: NumericArrayResize failed - err=%d", err);
			return -4;
		}
		(*arrayOut)->dimSizes[0] = totalBytes;
		memcpy((*arrayOut)->elt, continuous.data, totalBytes);

		ANS_DBG("ANSCV", "ImageToFlatArray: SUCCESS - %d bytes (%dx%dx%d)",
		         totalBytes, *width, *height, *channels);
		return 1;
	}
	catch (const std::exception& e) {
		ANS_DBG("ANSCV", "ImageToFlatArray: EXCEPTION - %s", e.what());
		return -3;
	}
	catch (...) {
		ANS_DBG("ANSCV", "ImageToFlatArray: UNKNOWN EXCEPTION");
		return -1;
	}
}

// ── 1D U8 raw pixel array -> JPEG string ───

extern "C" __declspec(dllexport) int ANSCV_FlatArrayToJpeg(
		LVArray1D_U8Hdl arrayIn, int width, int height, int channels, int quality, LStrHandle outputImage) {
	try {
		if (!arrayIn || !outputImage || width <= 0 || height <= 0 || channels <= 0) {
			ANS_DBG("ANSCV", "FlatArrayToJpeg: invalid input - w=%d h=%d ch=%d", width, height, channels);
			return -2;
		}

		int expectedSize = width * height * channels;
		int actualSize = (*arrayIn)->dimSizes[0];
		if (actualSize < expectedSize) {
			ANS_DBG("ANSCV", "FlatArrayToJpeg: array too small - expected=%d actual=%d", expectedSize, actualSize);
			return -2;
		}

		if (quality <= 0 || quality > 100) quality = 95;

		// Wrap raw pixel data as cv::Mat (no copy)
		int cvType = (channels == 1) ? CV_8UC1 : (channels == 3) ? CV_8UC3 : CV_8UC4;
		cv::Mat mat(height, width, cvType, (*arrayIn)->elt);

		ANS_DBG("ANSCV", "FlatArrayToJpeg: %dx%d ch=%d quality=%d", width, height, channels, quality);

		// Encode to JPEG
		std::vector<unsigned char> buf;
		std::vector<int> params = {cv::IMWRITE_JPEG_QUALITY, quality};
		if (!cv::imencode(".jpg", mat, buf, params)) {
			ANS_DBG("ANSCV", "FlatArrayToJpeg: imencode failed");
			return -4;
		}

		int size = static_cast<int>(buf.size());
		MgErr error = DSSetHandleSize(outputImage, sizeof(int32) + size * sizeof(uChar));
		if (error != noErr) {
			ANS_DBG("ANSCV", "FlatArrayToJpeg: DSSetHandleSize failed - err=%d", error);
			return -4;
		}
		(*outputImage)->cnt = size;
		memcpy((*outputImage)->str, buf.data(), size);

		ANS_DBG("ANSCV", "FlatArrayToJpeg: SUCCESS - %d bytes JPEG", size);
		return 1;
	}
	catch (const std::exception& e) {
		ANS_DBG("ANSCV", "FlatArrayToJpeg: EXCEPTION - %s", e.what());
		return -3;
	}
	catch (...) {
		ANS_DBG("ANSCV", "FlatArrayToJpeg: UNKNOWN EXCEPTION");
		return -1;
	}
}

// ── cv::Mat -> BMP binary (no compression, zero-cost encode for .NET) ───

#pragma pack(push, 1)
struct BmpFileHeader {
	uint16_t type{0x4D42};    // "BM"
	uint32_t fileSize{0};
	uint16_t reserved1{0};
	uint16_t reserved2{0};
	uint32_t offsetData{0};
};
struct BmpInfoHeader {
	uint32_t size{40};
	int32_t  width{0};
	int32_t  height{0};       // negative = top-down (no flip needed)
	uint16_t planes{1};
	uint16_t bitCount{0};
	uint32_t compression{0};
	uint32_t sizeImage{0};
	int32_t  xPPM{0};
	int32_t  yPPM{0};
	uint32_t colorsUsed{0};
	uint32_t colorsImportant{0};
};
#pragma pack(pop)

extern "C" __declspec(dllexport) int ANSCV_ImageToBmp(cv::Mat** imageIn, int maxWidth, int& newWidth, int& newHeight, LStrHandle outputImage) {

	//return ANSCV_GetImage_S(imageIn,maxWidth,80,newWidth,newHeight,outputImage);
	try {
		if (!imageIn || !(*imageIn) || (*imageIn)->empty() || !outputImage) {
			ANS_DBG("ANSCV", "ImageToBmp: invalid input");
			return -2;
		}

		cv::Mat mat = **imageIn;

		ANS_DBG("ANSCV", "ImageToBmp: Mat=%p (%dx%d type=%d) maxWidth=%d",
		         (void*)*imageIn, mat.cols, mat.rows, mat.type(), maxWidth);

		// Resize if maxWidth > 0 and image is wider
		if (maxWidth > 0 && mat.cols > maxWidth) {
			double scale = static_cast<double>(maxWidth) / mat.cols;
			int targetHeight = static_cast<int>(mat.rows * scale);
			cv::resize(mat, mat, cv::Size(maxWidth, targetHeight), 0, 0, cv::INTER_AREA);
			ANS_DBG("ANSCV", "ImageToBmp: resized to %dx%d", mat.cols, mat.rows);
		}

		int width  = mat.cols;
		int height = mat.rows;
		newWidth  = width;
		newHeight = height;

		// Convert to BGR 24-bit
		cv::Mat bgr;
		switch (mat.type()) {
			case CV_8UC3:
				bgr = mat;
				break;
			case CV_8UC4:
				cv::cvtColor(mat, bgr, cv::COLOR_BGRA2BGR);
				break;
			case CV_8UC1:
				cv::cvtColor(mat, bgr, cv::COLOR_GRAY2BGR);
				break;
			default:
				ANS_DBG("ANSCV", "ImageToBmp: unsupported type %d", mat.type());
				return -5;
		}

		// BMP rows must be aligned to 4 bytes
		int rowBytes = width * 3;
		int stride = (rowBytes + 3) & ~3; // round up to 4-byte boundary
		int padding = stride - rowBytes;
		int imageSize = stride * height;

		// Build BMP file in memory
		BmpFileHeader fileHeader;
		BmpInfoHeader infoHeader;
		int headerSize = sizeof(BmpFileHeader) + sizeof(BmpInfoHeader);

		fileHeader.offsetData = headerSize;
		fileHeader.fileSize = headerSize + imageSize;
		infoHeader.width = width;
		infoHeader.height = -height; // negative = top-down, no flip needed
		infoHeader.bitCount = 24;
		infoHeader.sizeImage = imageSize;

		int totalSize = headerSize + imageSize;
		MgErr error = DSSetHandleSize(outputImage, sizeof(int32) + totalSize);
		if (error != noErr) {
			ANS_DBG("ANSCV", "ImageToBmp: DSSetHandleSize failed - err=%d", error);
			return -4;
		}
		(*outputImage)->cnt = totalSize;

		unsigned char* dst = (*outputImage)->str;

		// Write headers
		memcpy(dst, &fileHeader, sizeof(BmpFileHeader));
		dst += sizeof(BmpFileHeader);
		memcpy(dst, &infoHeader, sizeof(BmpInfoHeader));
		dst += sizeof(BmpInfoHeader);

		// Write pixel rows with padding
		unsigned char pad[4] = {0, 0, 0, 0};
		for (int y = 0; y < height; y++) {
			memcpy(dst, bgr.ptr(y), rowBytes);
			dst += rowBytes;
			if (padding > 0) {
				memcpy(dst, pad, padding);
				dst += padding;
			}
		}

		ANS_DBG("ANSCV", "ImageToBmp: SUCCESS - %d bytes BMP (%dx%d)", totalSize, width, height);
		return 1;
	}
	catch (const std::exception& e) {
		ANS_DBG("ANSCV", "ImageToBmp: EXCEPTION - %s", e.what());
		return -3;
	}
	catch (...) {
		ANS_DBG("ANSCV", "ImageToBmp: UNKNOWN EXCEPTION");
		return -1;
	}
}

// ── BMP string -> JPEG string ───

extern "C" __declspec(dllexport) int ANSCV_BmpToJpeg(LStrHandle bmpInput, int quality, LStrHandle jpegOutput) {
	try {
		if (!bmpInput || !jpegOutput || (*bmpInput)->cnt <= 0) {
			ANS_DBG("ANSCV", "BmpToJpeg: invalid input");
			return -2;
		}

		if (quality <= 0 || quality > 100) quality = 85;

		int bmpSize = (*bmpInput)->cnt;
		unsigned char* raw = reinterpret_cast<unsigned char*>((*bmpInput)->str);

		// ── Passthrough: input is already JPEG (starts with FF D8 FF) ──
		if (bmpSize >= 3 && raw[0] == 0xFF && raw[1] == 0xD8 && raw[2] == 0xFF) {
			MgErr error = DSSetHandleSize(jpegOutput, sizeof(int32) + bmpSize * sizeof(uChar));
			if (error != noErr) {
				ANS_DBG("ANSCV", "BmpToJpeg: DSSetHandleSize failed (passthrough) - err=%d", error);
				return -4;
			}
			(*jpegOutput)->cnt = bmpSize;
			memcpy((*jpegOutput)->str, raw, bmpSize);
			ANS_DBG("ANSCV", "BmpToJpeg: PASSTHROUGH - input is already JPEG (%d bytes)", bmpSize);
			return 1;
		}

		// ── Fast path: parse BMP header directly, zero-copy ──
		// Minimum BMP = file header (14) + info header (40) + some pixels
		constexpr int kMinBmpSize = sizeof(BmpFileHeader) + sizeof(BmpInfoHeader) + 1;
		if (bmpSize >= kMinBmpSize && raw[0] == 'B' && raw[1] == 'M') {

			const auto& fh = *reinterpret_cast<const BmpFileHeader*>(raw);
			const auto& ih = *reinterpret_cast<const BmpInfoHeader*>(raw + sizeof(BmpFileHeader));

			int width  = ih.width;
			int height = ih.height;  // negative = top-down
			bool topDown = (height < 0);
			if (height < 0) height = -height;

			// Only handle 24-bit uncompressed (the format ImageToBmp produces)
			if (ih.bitCount == 24 && ih.compression == 0 && width > 0 && height > 0) {
				int rowBytes = width * 3;
				int stride   = (rowBytes + 3) & ~3;  // BMP rows are 4-byte aligned

				// Verify the buffer is large enough
				int pixelOffset = static_cast<int>(fh.offsetData);
				int64_t neededSize = static_cast<int64_t>(pixelOffset) + static_cast<int64_t>(stride) * height;
				if (bmpSize >= neededSize) {
					unsigned char* pixels = raw + pixelOffset;

					cv::Mat mat;
					if (topDown) {
						// Top-down BMP: rows are already in correct order
						// If no padding, wrap directly; otherwise need to handle stride
						if (stride == rowBytes) {
							mat = cv::Mat(height, width, CV_8UC3, pixels);
						} else {
							mat = cv::Mat(height, width, CV_8UC3, pixels, stride);
						}
					} else {
						// Bottom-up BMP: flip to top-down for JPEG encoding
						// Create Mat pointing at the last row with negative step
						// OpenCV doesn't support negative step, so flip
						cv::Mat bottomUp(height, width, CV_8UC3, pixels, stride);
						cv::flip(bottomUp, mat, 0);
					}

					ANS_DBG("ANSCV", "BmpToJpeg: fast-path %dx%d, encoding JPEG q=%d", width, height, quality);

					std::string jpegStr = ANSCENTER::CompressJpegToString(mat, quality);
					if (!jpegStr.empty()) {
						int size = static_cast<int>(jpegStr.size());
						MgErr error = DSSetHandleSize(jpegOutput, sizeof(int32) + size * sizeof(uChar));
						if (error != noErr) {
							ANS_DBG("ANSCV", "BmpToJpeg: DSSetHandleSize failed - err=%d", error);
							return -4;
						}
						(*jpegOutput)->cnt = size;
						memcpy((*jpegOutput)->str, jpegStr.data(), size);
						ANS_DBG("ANSCV", "BmpToJpeg: SUCCESS (fast) - %d bytes BMP -> %d bytes JPEG", bmpSize, size);
						return 1;
					}
					// If fast-path encode failed, fall through to imdecode path
				}
			}
		}

		// ── Fallback: use imdecode for non-standard BMP formats ──
		ANS_DBG("ANSCV", "BmpToJpeg: using imdecode fallback for %d bytes", bmpSize);
		std::vector<unsigned char> bmpData(raw, raw + bmpSize);
		cv::Mat mat = cv::imdecode(bmpData, cv::IMREAD_COLOR);
		if (mat.empty()) {
			ANS_DBG("ANSCV", "BmpToJpeg: imdecode failed - %d bytes input", bmpSize);
			return -4;
		}

		ANS_DBG("ANSCV", "BmpToJpeg: decoded %dx%d, encoding JPEG q=%d", mat.cols, mat.rows, quality);

		std::string jpegStr = ANSCENTER::CompressJpegToString(mat, quality);
		if (jpegStr.empty()) {
			ANS_DBG("ANSCV", "BmpToJpeg: JPEG encode failed");
			return -4;
		}

		int size = static_cast<int>(jpegStr.size());
		MgErr error = DSSetHandleSize(jpegOutput, sizeof(int32) + size * sizeof(uChar));
		if (error != noErr) {
			ANS_DBG("ANSCV", "BmpToJpeg: DSSetHandleSize failed - err=%d", error);
			return -4;
		}
		(*jpegOutput)->cnt = size;
		memcpy((*jpegOutput)->str, jpegStr.data(), size);

		ANS_DBG("ANSCV", "BmpToJpeg: SUCCESS (fallback) - %d bytes BMP -> %d bytes JPEG", bmpSize, size);
		return 1;
	}
	catch (const std::exception& e) {
		ANS_DBG("ANSCV", "BmpToJpeg: EXCEPTION - %s", e.what());
		return -3;
	}
	catch (...) {
		ANS_DBG("ANSCV", "BmpToJpeg: UNKNOWN EXCEPTION");
		return -1;
	}
}