diff --git a/modules/ANSCV/ANSOpenCV.cpp b/modules/ANSCV/ANSOpenCV.cpp index 7eb0c75..e326307 100644 --- a/modules/ANSCV/ANSOpenCV.cpp +++ b/modules/ANSCV/ANSOpenCV.cpp @@ -5304,4 +5304,282 @@ extern "C" __declspec(dllexport) int ANSCV_ImageToArray(cv::Mat** imageIn, LVArr 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(&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 buf; + std::vector 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(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) { + 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(maxWidth) / mat.cols; + int targetHeight = static_cast(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; + + // Decode BMP from memory + int bmpSize = (*bmpInput)->cnt; + std::vector bmpData((*bmpInput)->str, (*bmpInput)->str + 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); + + // Encode to JPEG using TurboJPEG if available, else cv::imencode + std::string jpegStr = ANSCENTER::CompressJpegToString(mat, quality); + if (jpegStr.empty()) { + ANS_DBG("ANSCV", "BmpToJpeg: JPEG encode failed"); + return -4; + } + + int size = static_cast(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 - %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; + } } \ No newline at end of file diff --git a/modules/ANSCV/ANSOpenCV.h b/modules/ANSCV/ANSOpenCV.h index 953419c..c2d0a8c 100644 --- a/modules/ANSCV/ANSOpenCV.h +++ b/modules/ANSCV/ANSOpenCV.h @@ -178,5 +178,15 @@ typedef LVArray2D_U32** LVArray2D_U32Hdl; extern "C" __declspec(dllexport) int ANSCV_ImageToArray(cv::Mat** imageIn, LVArray2D_U32Hdl arrayOut); // cv::Mat -> IMAQ via LStrHandle (lossless PNG) extern "C" __declspec(dllexport) int ANSCV_Image2IMAQ(cv::Mat** imageIn, LStrHandle outputImage); +// cv::Mat -> 1D U8 array (raw BGR pixels, row-major) + width, height, channels +typedef struct { int32 dimSizes[1]; uInt8 elt[1]; } LVArray1D_U8; +typedef LVArray1D_U8** LVArray1D_U8Hdl; +extern "C" __declspec(dllexport) int ANSCV_ImageToFlatArray(cv::Mat** imageIn, LVArray1D_U8Hdl arrayOut, int* width, int* height, int* channels); +// 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); +// cv::Mat -> BMP binary string (no compression, fastest possible, for .NET Image.FromStream) +extern "C" __declspec(dllexport) int ANSCV_ImageToBmp(cv::Mat** imageIn, int maxWidth, int& newWidth, int& newHeight, LStrHandle outputImage); +// BMP string -> JPEG string (for storage/network) +extern "C" __declspec(dllexport) int ANSCV_BmpToJpeg(LStrHandle bmpInput, int quality, LStrHandle jpegOutput); #endif \ No newline at end of file