Support BMP and JPG conversion

This commit is contained in:
2026-04-13 14:12:47 +10:00
parent 3a4320f253
commit 56a8f09adf
2 changed files with 288 additions and 0 deletions

View File

@@ -5305,3 +5305,281 @@ extern "C" __declspec(dllexport) int ANSCV_ImageToArray(cv::Mat** imageIn, LVArr
return -1; 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) {
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;
// Decode BMP from memory
int bmpSize = (*bmpInput)->cnt;
std::vector<unsigned char> 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<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 - %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;
}
}

View File

@@ -178,5 +178,15 @@ typedef LVArray2D_U32** LVArray2D_U32Hdl;
extern "C" __declspec(dllexport) int ANSCV_ImageToArray(cv::Mat** imageIn, LVArray2D_U32Hdl arrayOut); extern "C" __declspec(dllexport) int ANSCV_ImageToArray(cv::Mat** imageIn, LVArray2D_U32Hdl arrayOut);
// cv::Mat -> IMAQ via LStrHandle (lossless PNG) // cv::Mat -> IMAQ via LStrHandle (lossless PNG)
extern "C" __declspec(dllexport) int ANSCV_Image2IMAQ(cv::Mat** imageIn, LStrHandle outputImage); 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 #endif