我一直在尝试制作自己的自定义 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,所以我不明白解压失败是怎么回事。但是当我渲染它时,它看起来像这样:
我已经尝试了很多方法来渲染位图图像,这是唯一可行的方法,所以如果 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 个颜色三元组。
编辑:其他一些研究告诉我,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单位测量,当我使用这些值而不进行任何转换时,条纹就会消失,因为不需要任何转换,彩色方形图像是现在很好,但一个奇怪的问题是狐狸图像现在失去了所有颜色。我明白为什么需要填充和步幅,图像因此而扭曲:扭曲,无色图像
图像失真确实是由于不考虑图像步幅造成的,既然我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;
}
感谢您的所有帮助,这是图像,现在已正确渲染:最终代码,完美运行