使用 winGDI 时,解压缩的位图图像由于没有明显原因而无法正确渲染

问题描述 投票:0回答:1

我一直在尝试制作自己的自定义 PDF 编辑器,并且制作了一个 PDF 解析器,它对于任何 PDF 功能都工作得非常好,直到图像出现。首先,我的 PDF 中的图像是使用标准 DEFLATE/INFLATE 算法进行压缩的,并且它们都具有 FlateDecode 过滤器。压缩和解压缩的图像将具有大量原始字节,这些字节可能会被损坏为字符串或字符,因此为了安全起见,我使用了 uint8_t 向量。这是我的减压/充气功能:

std::vector<uint8_t> parser::inflate_stream_to_raw(const std::vector<uint8_t>& deflated_stream) {
    z_stream stream{};
    int ret = inflateInit(&stream);
    if (ret != Z_OK) std::cout << "\ncould not init zlib\n";

    std::vector<uint8_t> inflated_stream;
    const int chunk_size = 16384; // 16 KB chunk size
    std::vector<uint8_t> buffer(chunk_size);

    stream.next_in = const_cast<Bytef*>(deflated_stream.data());
    stream.avail_in = static_cast<uInt>(deflated_stream.size());

    while (ret != Z_STREAM_END) {
        stream.next_out = reinterpret_cast<Bytef*>(buffer.data());
        stream.avail_out = chunk_size;

        ret = inflate(&stream, Z_NO_FLUSH);
        if (ret == Z_STREAM_ERROR || ret == Z_NEED_DICT || ret == Z_DATA_ERROR || ret == Z_MEM_ERROR) {
            std::cerr << "Error: zlib stream error. Error code: " << ret << std::endl;
            inflateEnd(&stream);
            return {}; // Return an empty vector on error
        }

        inflated_stream.insert(inflated_stream.end(), buffer.data(), buffer.data() + chunk_size - stream.avail_out);
    }

    inflateEnd(&stream);

    return inflated_stream;
}

这应该工作得很好,以字符串形式返回数据的版本对于 PDF 内容来说工作得很好,没有任何问题。因此,当需要渲染它时,我有一个 .bmp,它与我正在测试的 PDF 中的图像相同,其大小约为 12 MB,并且测试我的解压缩图像流的大小,它也约为 12 MB MB,所以我不明白解压失败是怎么回事。但是当我渲染它时,它看起来像这样:enter image description here

我已经尝试了很多方法来渲染位图图像,这是唯一可行的方法,所以如果 winGDI 出现问题或我如何渲染它,我不知道如何修复它。这是我用来渲染它的代码:

#include <C:/code-libs/pdfCoder/pdfCoder.hpp>
#include <Windows.h>
#include <wingdi.h>
#include <string>
#include <sstream>
#include <vector>

#pragma comment(lib, "C:/code-libs/pdfCoder/pdfCoder.lib")
#pragma comment(lib, "gdi32.lib")

LRESULT CALLBACK main_wnd_proc(HWND hwnd, UINT msg, WPARAM w_param, LPARAM l_param);
HWND main_wnd;

std::vector<image_object> img_objs;
parser pdf;

int pt_to_dip(double pt, double dpi) {
    if (dpi == 96) return static_cast<int>(std::round((pt / 72) * dpi)); // if DPI is 96, then PX & DIP are equal
    else return static_cast<int>(std::round(((pt / 72) * dpi) / (dpi / 96))); // else find PX first then convert to DIP
}

int WINAPI wWinMain(HINSTANCE cur_instance, HINSTANCE prev_instance, LPWSTR cmd_line, int cmd_count) {
    const wchar_t* MAIN_WND = L"main_window";
    WNDCLASS wc{};
    wc.hInstance = cur_instance;
    wc.lpszClassName = MAIN_WND;
    wc.hCursor = LoadCursor(nullptr, IDC_ARROW);
    wc.hbrBackground = (HBRUSH)COLOR_WINDOW;
    wc.lpfnWndProc = main_wnd_proc;
    RegisterClass(&wc);

    pdf.open("C:\\Coding Projects\\test field\\test4.pdf");
    objects_root pdf_root = pdf.get_root();
    page_object page = pdf.parse_page(pdf_root.pages[0]);

    img_objs = pdf.parse_page_images(page);

    main_wnd = CreateWindow(MAIN_WND, L"File Organiser",
        WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN,
        CW_USEDEFAULT, CW_USEDEFAULT,
        1500, 1500,
        nullptr, nullptr, nullptr, nullptr);

    if (main_wnd == NULL) return 1;

    ShowWindow(main_wnd, SW_SHOW);
    UpdateWindow(main_wnd);

    MSG msg;
    while (GetMessage(&msg, nullptr, 0, 0)) {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    return 0;
}

LRESULT CALLBACK main_wnd_proc(HWND hwnd, UINT msg, WPARAM w_param, LPARAM l_param) {
    HDC hdc;
    switch (msg) {
    case WM_PAINT:
        OutputDebugStringA("\nPAINTING...");
        PAINTSTRUCT ps;
        hdc = BeginPaint(hwnd, &ps);

        for (const image_object& img_obj : img_objs) {
            OutputDebugStringA("looping images");

            std::stringstream ss;
            ss << img_obj.image_stream.size();
            OutputDebugStringA(ss.str().c_str());

            BITMAPINFOHEADER bmp_header = {};
            bmp_header.biSize = sizeof(BITMAPINFOHEADER);
            bmp_header.biWidth = pt_to_dip(img_obj.width, 96);
            bmp_header.biHeight = -pt_to_dip(img_obj.height, 96);
            bmp_header.biPlanes = 1;
            bmp_header.biBitCount = img_obj.bits_per_component * 3;
            bmp_header.biCompression = BI_RGB;
            bmp_header.biSizeImage = 0;
            BITMAPINFO bmi = {};
            bmi.bmiHeader = bmp_header;

            void* raw_bits = malloc(img_obj.image_stream.size());
            if (raw_bits != nullptr) {
                memcpy(raw_bits, img_obj.image_stream.data(), img_obj.image_stream.size());
            }

            StretchDIBits(hdc, 0, 0, 1500, 1500,
                0, 0, pt_to_dip(img_obj.width, 96), pt_to_dip(img_obj.height, 96),
                raw_bits, &bmi,
                BI_RGB, 
                SRCCOPY);

            free(raw_bits);
            HDC hdc = GetDC(hwnd);
            ReleaseDC(hwnd, hdc);
        }

        EndPaint(hwnd, &ps);
        break;
    case WM_DESTROY:
        PostQuitMessage(0);
        return 0;
    default:
        return DefWindowProc(hwnd, msg, w_param, l_param);
    }
    return 0;
}

任何关于到底发生了什么的帮助都是值得赞赏的,我在这里找不到太多关于显示原始位图数据而不是来自 .bmp 文件的信息。 澄清一下,该图像并不是一只坐在雪地里、背景模糊的狐狸

这是 K J 以 PDF 形式发送给我的原始 24 位(3x8 位)位图图片,顶部 1/3 为窄色带(842 像素为 2562 字节)红色 (#FF0000) 1/3 绿色 (#00FF00) 1/ 3 个蓝色 (#0000FF),因此每行 7,686 字节,2562 个颜色三元组。

enter image description here

上述代码尝试的输出
enter image description here

编辑:其他一些研究告诉我,BI_RGB 标志期望数据采用 BGR 而不是 RGB?这是真的?从 PDF 中提取的 RGB 数据仍在 /DeviceRGB 颜色空间中,我是否需要转换为其他颜色空间(如 sRBG)才能正确渲染图像?有人可以澄清吗?

EDIT2:感谢您的澄清,似乎 BI_RGB 和 DeviceRGB 以及术语 RGB 通常意味着红绿蓝值用于表示颜色,但不是它们使用的顺序,它可以是 RGB 或BGR 与 sRGB 一样,是 RGB 的一种校准类型,但不相关。当我将狐狸图像的字节顺序从 RGB 切换为 BGR 后,狐狸的颜色大部分已自行纠正,但这给我留下了一个问题:如果我只有图像流,我应该如何检查字节顺序是什么,压缩和解压缩以及 PDF 图像对象 ( 4 0 obj<> ) 可以使用吗?例如,K J 发送给我的带有彩色框的测试图像仍然是 RGB 格式,如果我的代码设置为 BGR,则该图像无法正确渲染,反之亦然,我需要一种可靠的方法来执行此操作并且我需要它提高效率,因为我的程序最好小于 5MB(现在是 0.8MB)。此外,我仍然遇到图像上挥之不去的条纹效果和图像重复的问题(尽管这可能是由于调整大小错误造成的),这里是狐狸图像,程序设置为从 BGR 字节顺序读取:狐狸读取为 BGR 而不是 RGB 时的图像 一如既往,感谢所有帮助。

编辑3:我迟早需要完成这项工作,如果我以图像的原始尺寸渲染而不是缩小窗口区域的尺寸,那么条纹效果不太明显,但仍然存在,现在有这些白色的运行,我是不确定这些东西到底是什么,但我的图像有可能有 Alpha 通道吗?我测试的任何图像的黑白部分总是以这种方式结束条纹,就像彩色框图像一样,现在是狐狸图像:未调整大小时的狐狸图像,条纹现在是黑白的,另外,为什么狐狸会这样重复吗?这不应该发生吗?对于那些需要它的人,这里是原始图像:它应该是什么样子我需要考虑步幅和填充并将这些因素添加到 raw_bits 流中吗?这是我现在的代码:


    BITMAPINFOHEADER bmp_header = {};
            bmp_header.biSize = sizeof(BITMAPINFOHEADER);
            bmp_header.biWidth = pt_to_dip(img_obj.width, 96);
            bmp_header.biHeight = -pt_to_dip(img_obj.height, 96);
            bmp_header.biPlanes = 1;
            bmp_header.biBitCount = img_obj.bits_per_component * 3;
            bmp_header.biCompression = BI_RGB;
            bmp_header.biSizeImage = 0;
            BITMAPINFO bmi = {};
            bmi.bmiHeader = bmp_header;
            int stride = ((bmp_header.biWidth * bmp_header.biBitCount + 31) / 32) * 4;
    
            void* raw_bits = malloc(img_obj.image_stream.size());
            if (raw_bits != nullptr) {
                memcpy(raw_bits, img_obj.image_stream.data(), img_obj.image_stream.size());
                // this switches a BGR byte order image back to an RGB byte order if needed, so far I have no way to detect which byte order an image is in
                uint8_t* pixel_data = static_cast<uint8_t*>(raw_bits);
                for (size_t i = 0; i < img_obj.image_stream.size(); i += 3) {
                    std::swap(pixel_data[i], pixel_data[i + 2]); // Swap red and blue bytes
                }
            }
            // StretchDIBits will 'resize' the image to fit the window but that isn't really what it seems to be doing
            StretchDIBits(hdc, 0, 0, bmp_header.biWidth, pt_to_dip(img_obj.height, 96),
                0, 0, bmp_header.biWidth, pt_to_dip(img_obj.height, 96),
                raw_bits, &bmi,
                BI_RGB, SRCCOPY);
            free(raw_bits);
            HDC hdc = GetDC(hwnd);
            ReleaseDC(hwnd, hdc);
            break;

编辑4:对所有这些错误感到抱歉,但图像的/高度和/宽度并不是像我最初想象的那样以PDF单位测量,当我使用这些值而不进行任何转换时,条纹就会消失,因为不需要任何转换,彩色方形图像是现在很好,但一个奇怪的问题是狐狸图像现在失去了所有颜色。我明白为什么需要填充和步幅,图像因此而扭曲:扭曲,无色图像

c++ pdf winapi gdi
1个回答
0
投票

图像失真确实是由于不考虑图像步幅造成的,既然我1.考虑了图像步幅 2.将宽度和高度设置为正确的值 3. 如果需要,将图像数据从 BGR 正确转换为 RGB 4.正确使用winGDI函数 两个图像都会渲染得很好,这是完整的代码:

LRESULT CALLBACK main_wnd_proc(HWND hwnd, UINT msg, WPARAM w_param, LPARAM l_param) {
    HDC hdc;
    switch (msg) {
    case WM_PAINT:
        OutputDebugStringA("\nPAINTING...");
        PAINTSTRUCT ps;
        hdc = BeginPaint(hwnd, &ps);

        for (const image_object& img_obj : img_objs) {
            // init image info
            BITMAPINFOHEADER bmp_header = {};
            bmp_header.biSize = sizeof(BITMAPINFOHEADER);
            bmp_header.biWidth = img_obj.width;
            bmp_header.biHeight = -img_obj.height; // Negative height for top-down DIB
            bmp_header.biPlanes = 1; // must be set to 1
            bmp_header.biBitCount = img_obj.bits_per_component * 3; // BPP = BPC * num components
            bmp_header.biCompression = BI_RGB; // RGB-type data, no compression

            /* calculates image stride, which must be a multiple of 4 bytes for Windows DIBs
            bits per row = bmp_header.biWidth * bmp_header.biBitCount
            when this value is divided by 32 to convert bit count, we need to round up to the next multiple of 32 bits so + 31 to BPR
            multiply 32-bit unit count by 4 to get unit count back in bytes for final stride value */
            int stride = ((bmp_header.biWidth * bmp_header.biBitCount + 31) / 32) * 4;
            bmp_header.biSizeImage = stride * abs(bmp_header.biHeight);

            BITMAPINFO bmi = {};
            bmi.bmiHeader = bmp_header;

            // copy raw image bits to buffer for rendering
            void* raw_bits = malloc(bmp_header.biSizeImage);
            if (raw_bits != nullptr) {
                // Copy the image data row by row, accounting for stride
                const unsigned char* src = img_obj.image_stream.data(); // source stream
                unsigned char* dst = static_cast<unsigned char*>(raw_bits); // destination stream
                int row_size = img_obj.width * (img_obj.bits_per_component * 3 / 8); // Bytes per row without padding
                for (int y = 0; y < img_obj.height; ++y) {
                    memcpy(dst, src, row_size);
                    // this converts BGR byte order image data into RGB byte order for rendering
                    // (I still need a way to tell which byte order an image is, BGR or RGB) 
                    for (int i = 0; i < row_size; i += 3) { 
                        std::swap(dst[i], dst[i + 2]); // Swap R & B type bytes
                    }
                    src += row_size; // Move to next row in source data
                    dst += stride;  // Move to next row in destination, including padding
                }
            }

            SetStretchBltMode(hdc, HALFTONE); // to even out image colours
            // DI Bits will be set to the current device context & stretched to fit the window area
            StretchDIBits(hdc, 0, 0, 1500, 900,
                0, 0, bmp_header.biWidth, abs(bmp_header.biHeight),
                raw_bits, &bmi,
                BI_RGB, SRCCOPY);

            // free buffer memory & render image by releasing device context
            free(raw_bits);
            HDC hdc = GetDC(hwnd);
            ReleaseDC(hwnd, hdc);
            break;
        }

        EndPaint(hwnd, &ps);
        break;
    case WM_DESTROY:
        PostQuitMessage(0);
        return 0;
    default:
        return DefWindowProc(hwnd, msg, w_param, l_param);
    }
    return 0;
} 

感谢您的所有帮助,这是图像,现在已正确渲染:最终代码,完美运行

© www.soinside.com 2019 - 2024. All rights reserved.