core/indigo-core/common/utils/emf_utils.cpp (278 lines of code) (raw):

#include <png.h> #include <sstream> #include <vector> #include "emf_utils.h" using namespace indigo; // pnglib needs a custom write function for user-defined IO void png_write_to_stream(png_structp png_ptr, png_bytep data, png_size_t length) { std::stringstream* stream = reinterpret_cast<std::stringstream*>(png_get_io_ptr(png_ptr)); stream->write(reinterpret_cast<char*>(data), length); } void png_flush_noop(png_structp) { // No need to flush for a stringstream } // Error handling void png_error_handler(png_structp png_ptr, png_const_charp error_msg) { throw std::runtime_error(error_msg); } void png_warning_handler(png_structp png_ptr, png_const_charp warning_msg) { // Warnings can be ignored or logged } std::string indigo::dibToPNG(const std::string& dib_data) { if (dib_data.empty()) return ""; BITMAPINFOHEADER* pbmi = (BITMAPINFOHEADER*)dib_data.data(); int width = pbmi->biWidth; int height = pbmi->biHeight; int bpp = pbmi->biBitCount; if (bpp != 24 && bpp != 16) { return ""; } uint8_t* pixels = (uint8_t*)(dib_data.data() + pbmi->biSize); std::vector<uint8_t> png_data; png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); if (png_ptr == NULL) return ""; png_infop info_ptr = png_create_info_struct(png_ptr); if (info_ptr == NULL) { png_destroy_write_struct(&png_ptr, &info_ptr); return ""; } if (setjmp(png_jmpbuf(png_ptr))) { png_destroy_write_struct(&png_ptr, &info_ptr); return ""; } png_set_IHDR(png_ptr, info_ptr, width, height, 8, PNG_COLOR_TYPE_RGB, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE); std::vector<std::vector<uint8_t>> row_data(height, std::vector<uint8_t>(width * 3)); std::vector<png_bytep> row_pointers(height); for (int i = 0; i < height; i++) { for (int j = 0; j < width; j++) { if (bpp == 24) { row_data[height - 1 - i][3 * j + 0] = pixels[(i * width + j) * 3 + 2]; row_data[height - 1 - i][3 * j + 1] = pixels[(i * width + j) * 3 + 1]; row_data[height - 1 - i][3 * j + 2] = pixels[(i * width + j) * 3 + 0]; } else if (bpp == 16) { uint16_t pixel = ((uint16_t*)pixels)[i * width + j]; uint8_t r = (pixel & 0x7C00) >> 7; uint8_t g = (pixel & 0x3E0) >> 2; uint8_t b = (pixel & 0x1F) << 3; row_data[height - 1 - i][3 * j + 0] = r; row_data[height - 1 - i][3 * j + 1] = g; row_data[height - 1 - i][3 * j + 2] = b; } } row_pointers[height - 1 - i] = row_data[height - 1 - i].data(); } png_set_rows(png_ptr, info_ptr, row_pointers.data()); struct PngWriteCallback { static void callback(png_structp png_ptr, png_bytep data, png_size_t length) { auto p = reinterpret_cast<std::vector<uint8_t>*>(png_get_io_ptr(png_ptr)); p->insert(p->end(), data, data + length); } }; png_set_write_fn(png_ptr, &png_data, PngWriteCallback::callback, NULL); png_write_png(png_ptr, info_ptr, PNG_TRANSFORM_IDENTITY, NULL); png_destroy_write_struct(&png_ptr, &info_ptr); return std::string(png_data.begin(), png_data.end()); } std::vector<Bitmap> indigo::ripBitmapsFromEMF(const std::string& emf) { std::vector<Bitmap> bitmaps; const uint8_t* data = reinterpret_cast<const uint8_t*>(emf.data()); const uint8_t* ptr = data; const uint8_t* end = data + emf.size(); // Read EMF records while (ptr < end) { if (end - ptr < sizeof(EMFRecord)) break; const EMFRecord* record = reinterpret_cast<const EMFRecord*>(ptr); if (record->nSize < sizeof(EMFRecord) || ptr + record->nSize > end) break; switch (record->iType) { case EMR_HEADER: // Handle EMR_HEADER if needed break; case EMR_EOF: // Handle EMR_EOF if needed break; case EMR_STRETCHDIBITS: { // Extract bitmap data from EMR_STRETCHDIBITS const EMRStretchDIBits* dibits = reinterpret_cast<const EMRStretchDIBits*>(ptr); if (ptr + dibits->offBmiSrc + dibits->cbBmiSrc + dibits->cbBits > end) break; // Ensure we don't read out of bounds Bitmap bitmap; // bmi header + bitmap data bitmap.dibits = std::string(reinterpret_cast<const char*>(ptr + dibits->offBmiSrc), dibits->cbBmiSrc + dibits->cbBits); bitmap.x = dibits->xDest; bitmap.y = dibits->yDest; bitmap.width = dibits->cxDest; bitmap.height = dibits->cyDest; bitmap.bitmap_width = dibits->cxSrc; bitmap.bitmap_height = dibits->cySrc; bitmaps.push_back(bitmap); break; } case EMR_SETDIBITSTODEVICE: { // Extract bitmap data from EMR_SETDIBITSTODEVICE const EMRSetDIBitsToDevice* dibits = reinterpret_cast<const EMRSetDIBitsToDevice*>(ptr); if (ptr + dibits->offBits + dibits->cbBits > end) break; // Ensure we don't read out of bounds Bitmap bitmap; bitmap.dibits = std::string(reinterpret_cast<const char*>(ptr + dibits->offBits), dibits->cbBits); bitmap.x = dibits->xDest; bitmap.y = dibits->yDest; bitmap.width = bitmap.bitmap_width = dibits->cxSrc; bitmap.height = bitmap.bitmap_height = dibits->cySrc; bitmaps.push_back(bitmap); break; } default: // Skip other records break; } ptr += record->nSize; } return bitmaps; } void pngReadData(png_structp pngPtr, png_bytep data, png_size_t length) { png_voidp a = png_get_io_ptr(pngPtr); const char** p = (const char**)a; memcpy(data, *p, length); *p += length; } std::string indigo::decodePNG(const std::string& inputPNGData) { if (inputPNGData.empty()) return ""; png_const_charp pngDataPtr = inputPNGData.c_str(); png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr); if (!png_ptr) return ""; png_infop info_ptr = png_create_info_struct(png_ptr); if (!info_ptr) { png_destroy_read_struct(&png_ptr, nullptr, nullptr); return ""; } if (setjmp(png_jmpbuf(png_ptr))) { png_destroy_read_struct(&png_ptr, &info_ptr, nullptr); return ""; } png_set_read_fn(png_ptr, &pngDataPtr, pngReadData); png_read_info(png_ptr, info_ptr); int width = png_get_image_width(png_ptr, info_ptr); int height = png_get_image_height(png_ptr, info_ptr); int color_type = png_get_color_type(png_ptr, info_ptr); // Handle different PNG color types for conversion to RGBA if (color_type == PNG_COLOR_TYPE_PALETTE) png_set_palette_to_rgb(png_ptr); if (color_type == PNG_COLOR_TYPE_GRAY && png_get_bit_depth(png_ptr, info_ptr) < 8) png_set_expand_gray_1_2_4_to_8(png_ptr); if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) png_set_tRNS_to_alpha(png_ptr); if (!(color_type & PNG_COLOR_MASK_ALPHA)) png_set_filler(png_ptr, 0xff, PNG_FILLER_AFTER); png_read_update_info(png_ptr, info_ptr); BITMAPINFOHEADER bih; bih.biSize = sizeof(BITMAPINFOHEADER); bih.biWidth = width; bih.biHeight = height; bih.biPlanes = 1; bih.biBitCount = 32; // Always use 32 bits per pixel (RGBA) bih.biCompression = 0; // Use BI_RGB (no compression) bih.biSizeImage = width * height * 4; bih.biXPelsPerMeter = 0; // Not specifying resolution bih.biYPelsPerMeter = 0; bih.biClrUsed = 0; bih.biClrImportant = 0; std::vector<unsigned char> pixelData(bih.biSizeImage); std::vector<png_bytep> row_pointers(height); for (int y = 0; y < height; ++y) { row_pointers[y] = &pixelData[(height - 1 - y) * width * 4]; // Flip row order for correct BMP format } png_read_image(png_ptr, row_pointers.data()); png_destroy_read_struct(&png_ptr, &info_ptr, nullptr); std::string output; output.append(reinterpret_cast<const char*>(&bih), sizeof(bih)); output.append(reinterpret_cast<const char*>(pixelData.data()), pixelData.size()); return output; } // this function may be useful in the future. Just implement missing calculations for TBC fields. std::string indigo::createEMFFromBitmap(const std::string& bmpData) { const BITMAPINFOHEADER* bmpHeader = reinterpret_cast<const BITMAPINFOHEADER*>(bmpData.data()); std::string emfData; EMFHeader emfHeader = {}; emfHeader.emr.iType = 1; // EMR_HEADER emfHeader.emr.nSize = sizeof(EMFHeader); emfHeader.boundsLeft = 0; emfHeader.boundsTop = 0; emfHeader.boundsRight = static_cast<int32_t>(bmpHeader->biWidth - 1); emfHeader.boundsBottom = static_cast<int32_t>(bmpHeader->biHeight - 1); emfHeader.frameLeft = 0; emfHeader.frameTop = 0; const float dpi = 96.0f; emfHeader.frameRight = 0; // TBC: 13894 emfHeader.frameBottom = 0; // TBC: 13894 emfHeader.dSignature = 0x464D4520; emfHeader.nVersion = 0x00010000; emfHeader.nBytes = 0; // Will be updated later emfHeader.nRecords = 0; // Will be updated later emfHeader.nHandles = 1; // Not used emfHeader.sReserved = 0; emfHeader.nDescription = 0; emfHeader.offDescription = 0; emfHeader.nPalEntries = 0; // No palette emfHeader.deviceCx = 1920; // TBC: emfHeader.deviceCy = 1080; // TBC: emfHeader.millimetersCx = 521; // TBC: emfHeader.millimetersCy = 293; // TBC: emfHeader.micrometersCx = emfHeader.millimetersCx * 1000; emfHeader.micrometersCy = emfHeader.millimetersCy * 1000; emfHeader.cbPixelFormat = 0; emfHeader.offPixelFormat = 0; emfHeader.bOpenGL = 0; emfData.append(reinterpret_cast<const char*>(&emfHeader), sizeof(emfHeader)); emfHeader.nRecords++; uint32_t currentOffset = static_cast<uint32_t>(emfData.size()); EMRStretchDIBits emrStretchDIBits = {}; emrStretchDIBits.emr.iType = 81; // EMR_STRETCHDIBITS emrStretchDIBits.emr.nSize = sizeof(EMRStretchDIBits) + bmpHeader->biSize + bmpHeader->biSizeImage; emrStretchDIBits.boundsLeft = 0; emrStretchDIBits.boundsTop = 0; emrStretchDIBits.boundsRight = static_cast<int32_t>(bmpHeader->biWidth - 1); emrStretchDIBits.boundsBottom = static_cast<int32_t>(bmpHeader->biHeight - 1); emrStretchDIBits.xDest = 0; emrStretchDIBits.yDest = 0; emrStretchDIBits.xSrc = 0; emrStretchDIBits.ySrc = 0; emrStretchDIBits.cxSrc = static_cast<uint32_t>(bmpHeader->biWidth); emrStretchDIBits.cySrc = static_cast<uint32_t>(bmpHeader->biHeight); emrStretchDIBits.offBmiSrc = static_cast<uint32_t>(sizeof(EMRStretchDIBits)); emrStretchDIBits.cbBmiSrc = bmpHeader->biSize; emrStretchDIBits.offBits = static_cast<uint32_t>(emrStretchDIBits.offBmiSrc + bmpHeader->biSize); emrStretchDIBits.cbBits = bmpHeader->biSizeImage; emrStretchDIBits.iUsageSrc = 0; emrStretchDIBits.dwRop = 0x00CC0020; // SRCCOPY emrStretchDIBits.cxDest = static_cast<uint32_t>(bmpHeader->biWidth); emrStretchDIBits.cyDest = static_cast<uint32_t>(bmpHeader->biHeight); emfData.append(reinterpret_cast<const char*>(&emrStretchDIBits), sizeof(emrStretchDIBits)); emfData.append(reinterpret_cast<const char*>(bmpHeader), bmpHeader->biSize); emfData.append(bmpData.data() + sizeof(BITMAPINFOHEADER), bmpHeader->biSizeImage); emfHeader.nRecords++; EMREOF emrEOF = {}; emrEOF.emr.iType = 14; // EMR_EOF emrEOF.emr.nSize = sizeof(EMREOF); emfData.append(reinterpret_cast<const char*>(&emrEOF), sizeof(emrEOF)); emfHeader.nRecords++; emfHeader.nBytes = static_cast<uint32_t>(emfData.size()); emfData.replace(0, sizeof(emfHeader), reinterpret_cast<const char*>(&emfHeader), sizeof(emfHeader)); return emfData; }