cxnet 发表于 2024-11-21 12:10:30

CImage图像dpi设置

在C++ MFC里怎样保持CImage图像总像素不变的情况下修改图像的dpi,我需要将图像发送到打印机进行打印,如果直接缩小图像尺寸而不提高图像dpi打印出来的图像质量会下降很多,而如果不修改dpi也不修改尺寸哪么打印出来的图像就打印不全,比如打印机的分辨率是300dpi,而CImage的图像是96dpi,所以我想要提高图像的dpi而自动降低图像的宽度和高度,请问怎样写代码

FishC 发表于 2024-11-21 12:10:44

在C++ MFC 中使用 `CImage` 修改图像的 DPI 而保持总像素不变,您需要更新图像的 DPI 信息。虽然 `CImage` 本身没有直接设置 DPI 的方法,但是可以通过修改图像的头信息来实现。以下是一个简单的步骤示例:

1. 加载图像
2. 获取图像头信息并修改 DPI 设置
3. 保存图像

以下是一个简单的代码示例:

#include <atlimage.h>

void ChangeImageDPI(const CString& inputPath, const CString& outputPath, int newDPI) {
    CImage image;
    image.Load(inputPath);

    // 获取图像头信息
    BITMAPINFOHEADER* pBMIH = image.GetBitmapInfoHeader();

    // 原始宽度和高度
    int originalWidth = pBMIH->biWidth;
    int originalHeight = pBMIH->biHeight;

    // 新的水平和垂直分辨率
    image.SetResolution(newDPI, newDPI);

    // 根据新的 DPI 自动调整比例
    float scaleFactor = 96.0f / newDPI;// 假设原始分辨率是 96 DPI
    int newWidth = static_cast<int>(originalWidth * scaleFactor);
    int newHeight = static_cast<int>(originalHeight * scaleFactor);

    // 更新图像尺寸
    CImage newImage;
    newImage.Create(newWidth, newHeight, image.GetBPP(), 0);
    image.StretchBlt(newImage.GetDC(), 0, 0, newWidth, newHeight, 0, 0, originalWidth, originalHeight, SRCCOPY);
    newImage.ReleaseDC();

    // 保存修改后的图像
    newImage.Save(outputPath);
    image.Destroy();
    newImage.Destroy();
}

请注意:

- 该示例假设原始图像的 DPI 是 96。如果原始 DPI 不同,需要相应调整 `scaleFactor`。
- 需要包括适当的错误检查,并处理图像格式兼容性问题。
- 在保存图像时,可能需要指定合适的格式以确 DPI 信息得以保留。

以上代码通过调整 DPI 和缩放图像的尺寸来保持总像素不变,这样可以在打印时获得更好的图像质量。希望这可以帮助到你!如果有其他问题,请继续询问。

以上回复来自 FishC AI 小助理,如未能正确解答您的问题,请继续追问。

cxnet 发表于 2024-11-21 12:15:54

FishC 发表于 2024-11-21 12:10
在C++ MFC 中使用 `CImage` 修改图像的 DPI 而保持总像素不变,您需要更新图像的 DPI 信息。虽然 `CImage`...

但是CImage库没有GetBitmapInfoHeader和SetResolution成员怎么办?

FishC 发表于 2024-11-21 12:22:18

cxnet 发表于 2024-11-21 12:15
但是CImage库没有GetBitmapInfoHeader和SetResolution ...

您说得对,`CImage` 类确实没有 `GetBitmapInfoHeader` 和 `SetResolution` 这样的成员函数。要处理 DPI 设置,可以使用其他方法。以下是如何使用 GDI+ 来完成这一任务:

#include <afxwin.h>
#include <gdiplus.h>
using namespace Gdiplus;

#pragma comment (lib,"Gdiplus.lib")

void ChangeImageDPI(const CString& inputPath, const CString& outputPath, int newDPI) {
    GdiplusStartupInput gdiplusStartupInput;
    ULONG_PTR gdiplusToken;
    GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);

    Bitmap* image = new Bitmap(inputPath);

    // 获取原始宽度和高度
    UINT originalWidth = image->GetWidth();
    UINT originalHeight = image->GetHeight();

    // 计算新的宽度和高度以保持图像总像素数不变
    float scaleFactor = newDPI / 96.0f;// 假设原始分辨率是 96 DPI
    UINT newWidth = static_cast<UINT>(originalWidth / scaleFactor);
    UINT newHeight = static_cast<UINT>(originalHeight / scaleFactor);

    // 创建新的图像并设置分辨率
    Bitmap* newImage = new Bitmap(newWidth, newHeight, image->GetPixelFormat());
    newImage->SetResolution(static_cast<REAL>(newDPI), static_cast<REAL>(newDPI));

    // 绘制缩放的图像
    Graphics graphics(newImage);
    graphics.DrawImage(image, 0, 0, newWidth, newHeight);

    // 保存修改后的图像
    CLSID pngClsid;
    GetEncoderClsid(L"image/png", &pngClsid);
    newImage->Save(outputPath, &pngClsid, NULL);

    delete image;
    delete newImage;
    GdiplusShutdown(gdiplusToken);
}

int GetEncoderClsid(const WCHAR* format, CLSID* pClsid) {
    UINT num = 0;
    UINT size = 0;

    GetImageEncodersSize(&num, &size);
    if(size == 0)
      return -1;

    ImageCodecInfo* pImageCodecInfo = (ImageCodecInfo*)(malloc(size));
    if(pImageCodecInfo == NULL)
      return -1;

    GetImageEncoders(num, size, pImageCodecInfo);
    for(UINT j = 0; j < num; ++j) {
      if( wcscmp(pImageCodecInfo.MimeType, format) == 0 ) {
            *pClsid = pImageCodecInfo.Clsid;
            free(pImageCodecInfo);
            return j;
      }
    }

    free(pImageCodecInfo);
    return -1;
}

请注意:
- 这段代码示范了如何使用 GDI+ 进行图像操作,包括加载、调整大小和保存。
- 假设原始图像 DPI 为 96,若实际值不同,则需要更准确的 DPI 信息。
- 在加载和保存图像时,可能需要根据具体情况选择合适格式。
- 确保程序初始化和关闭 GDI+。

希望这能帮助到您!如有其他问题,请随时询问。

以上回复来自 FishC AI 小助理,如未能正确解答您的问题,请继续追问。

cxnet 发表于 2024-11-21 12:42:41

FishC 发表于 2024-11-21 12:22
您说得对,`CImage` 类确实没有 `GetBitmapInfoHeader` 和 `SetResolution` 这样的成员函数。要处理 DP ...

“GetEncoderClsid”: 找不到标识符

FishC 发表于 2024-11-21 12:42:57

cxnet 发表于 2024-11-21 12:42
“GetEncoderClsid”: 找不到标识符

对于错误“GetEncoderClsid”: 找不到标识符,您需要手动为其编写一个功能以获取指定格式的编码器 CLSID。这个功能可以用于将图像保存为特定格式。请确保您已经将以下辅助函数添加到您的代码中:

int GetEncoderClsid(const WCHAR* format, CLSID* pClsid) {
    UINT num = 0;          // 编码器的数量
    UINT size = 0;         // ImageCodecInfo 数组的大小(字节)

    GetImageEncodersSize(&num, &size);
    if(size == 0) {
      return -1;// 无编码器
    }

    ImageCodecInfo* pImageCodecInfo = (ImageCodecInfo*)(malloc(size));
    if(pImageCodecInfo == NULL) {
      return -1;// 内存分配失败
    }

    GetImageEncoders(num, size, pImageCodecInfo);
    for(UINT j = 0; j < num; ++j) {
      if(wcscmp(pImageCodecInfo.MimeType, format) == 0) {
            *pClsid = pImageCodecInfo.Clsid;
            free(pImageCodecInfo);
            return j;// 找到匹配的编码器
      }
    }

    free(pImageCodecInfo);
    return -1;// 未找到编码器
}

这是完整的代码,包括此辅助函数。确保将此函数包括在您的代码中,并在调用 `newImage->Save(outputPath, &pngClsid, NULL);` 之前获得正确的编码器 CLSID。

希望这可以解决您的问题!如果还有其他问题,请继续提问。

以上回复来自 FishC AI 小助理,如未能正确解答您的问题,请继续追问。

cxnet 发表于 2024-11-21 12:47:12

FishC 发表于 2024-11-21 12:42
对于错误“GetEncoderClsid”: 找不到标识符,您需要手动为其编写一个功能以获取指定格式的编码器 CLSI ...

0x00007FF81382A652 (GdiPlus.dll)处(位于 MFCApplication1.exe 中)引发的异常: 0xC0000005: 写入位置 0x0000015AD0F78C90 时发生访问冲突。

FishC 发表于 2024-11-21 12:47:26

cxnet 发表于 2024-11-21 12:47
0x00007FF81382A652 (GdiPlus.dll)处(位于 MFCApplica ...

访问冲突通常是由于对无效指针或内存的操作引起的。在使用 GDI+ 时,尤其需要注意以下几点:

1. **初始化 GDI+**:确保在使用 GDI+ 之前正确初始化,它通常在程序启动时进行。确保 `GdiplusStartup` 的返回值表示成功。

2. **检查图像路径**:确认输入路径和输出路径正确,并且输入图像文件存在。

3. **指针正确性**:在使用指针的地方,确保所有指针都被正确初始化且指向有效的内存。

4. **图像格式**:确保你对于图像的操作是支持的格式,比如说 BMP, PNG, JPEG 等。

以下是一个完整的代码进行检查,同时增加一些守护代码:

#include <afxwin.h>
#include <GdiPlus.h>
using namespace Gdiplus;
#pragma comment (lib,"Gdiplus.lib")

int GetEncoderClsid(const WCHAR* format, CLSID* pClsid);

void ChangeImageDPI(const CString& inputPath, const CString& outputPath, int newDPI) {
    GdiplusStartupInput gdiplusStartupInput;
    ULONG_PTR gdiplusToken;
    if (GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL) != Ok) {
      AfxMessageBox(_T("Failed to initialize GDI+"));
      return;
    }

    Bitmap* image = new Bitmap(inputPath);
    if (image->GetLastStatus() != Ok) {
      AfxMessageBox(_T("Failed to load image"));
      delete image;
      GdiplusShutdown(gdiplusToken);
      return;
    }

    UINT originalWidth = image->GetWidth();
    UINT originalHeight = image->GetHeight();
    float scaleFactor = newDPI / 96.0f;
    UINT newWidth = static_cast<UINT>(originalWidth / scaleFactor);
    UINT newHeight = static_cast<UINT>(originalHeight / scaleFactor);

    Bitmap* newImage = new Bitmap(newWidth, newHeight, image->GetPixelFormat());
    if (newImage->GetLastStatus() != Ok) {
      AfxMessageBox(_T("Failed to create new image"));
      delete image;
      delete newImage;
      GdiplusShutdown(gdiplusToken);
      return;
    }

    newImage->SetResolution(static_cast<REAL>(newDPI), static_cast<REAL>(newDPI));

    Graphics graphics(newImage);
    if (graphics.DrawImage(image, 0, 0, newWidth, newHeight) != Ok) {
      AfxMessageBox(_T("Failed to draw image"));
      delete image;
      delete newImage;
      GdiplusShutdown(gdiplusToken);
      return;
    }

    CLSID pngClsid;
    if (GetEncoderClsid(L"image/png", &pngClsid) == -1) {
      AfxMessageBox(_T("Failed to get image encoder"));
    } else if (newImage->Save(outputPath, &pngClsid, NULL) != Ok) {
      AfxMessageBox(_T("Failed to save image"));
    }

    delete image;
    delete newImage;
    GdiplusShutdown(gdiplusToken);
}

int GetEncoderClsid(const WCHAR* format, CLSID* pClsid) {
    UINT num = 0;
    UINT size = 0;
    GetImageEncodersSize(&num, &size);
    if (size == 0) return -1;

    ImageCodecInfo* pImageCodecInfo = (ImageCodecInfo*)(malloc(size));
    if (pImageCodecInfo == NULL) return -1;

    GetImageEncoders(num, size, pImageCodecInfo);
    for (UINT j = 0; j < num; ++j) {
      if (wcscmp(pImageCodecInfo.MimeType, format) == 0) {
            *pClsid = pImageCodecInfo.Clsid;
            free(pImageCodecInfo);
            return j;
      }
    }
   
    free(pImageCodecInfo);
    return -1;
}

**建议**:
- 检查代码的所有路径(特别是确保输入图像存在)。
- 检查您使用的图像格式是否受支持。
- 使用异常处理(`try/catch`)来捕获可能出现的异常。

以上这些建议和代码可以帮助您定位并解决访问冲突问题。如果仍然存在问题,还请检查环境配置或者其他依赖库的版本问题。

以上回复来自 FishC AI 小助理,如未能正确解答您的问题,请继续追问。

cxnet 发表于 2024-11-21 13:00:56

FishC 发表于 2024-11-21 12:47
访问冲突通常是由于对无效指针或内存的操作引起的。在使用 GDI+ 时,尤其需要注意以下几点:

1. ** ...

运行会跳到    gdiplusgraphics.h的下面这一行出错,也能生成一个转换后的文件,dpi是可以改变,但是图像的总像素会变小,我是希望总像素保持不变,就像photoshop里的重定图像像素功能
DllExports::GdipDeleteGraphics(nativeGraphics);

FishC 发表于 2024-11-21 13:01:11

cxnet 发表于 2024-11-21 13:00
运行会跳到    gdiplusgraphics.h的下面这一行出错,也 ...

如果您希望在调整 DPI 时保持图像的总像素数不变,可以通过修改图像物理尺寸(宽度和高度)以适应新 DPI,而不是缩放图像。这意味着调整 DPI 信息而不改变图像的像素数据。

以下是一个带有更正逻辑的例子,确保图像的总像素数保持不变:

#include <afxwin.h>
#include <gdiplus.h>
using namespace Gdiplus;
#pragma comment (lib,"Gdiplus.lib")

int GetEncoderClsid(const WCHAR* format, CLSID* pClsid);

void ChangeImageDPI(const CString& inputPath, const CString& outputPath, int newDPI) {
    GdiplusStartupInput gdiplusStartupInput;
    ULONG_PTR gdiplusToken;
    GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);

    Bitmap* image = new Bitmap(inputPath);
    if (image->GetLastStatus() != Ok) {
      AfxMessageBox(_T("Failed to load image"));
      delete image;
      GdiplusShutdown(gdiplusToken);
      return;
    }

    // 设定新的DPI
    image->SetResolution(static_cast<REAL>(newDPI), static_cast<REAL>(newDPI));

    // 直接保存图像,不改变像素数据
    CLSID pngClsid;
    if (GetEncoderClsid(L"image/png", &pngClsid) == -1) {
      AfxMessageBox(_T("Failed to get image encoder"));
    } else if (image->Save(outputPath, &pngClsid, NULL) != Ok) {
      AfxMessageBox(_T("Failed to save image"));
    }

    delete image;
    GdiplusShutdown(gdiplusToken);
}

int GetEncoderClsid(const WCHAR* format, CLSID* pClsid) {
    UINT num = 0;
    UINT size = 0;
    GetImageEncodersSize(&num, &size);
    if (size == 0) return -1;

    ImageCodecInfo* pImageCodecInfo = (ImageCodecInfo*)(malloc(size));
    if (pImageCodecInfo == NULL) return -1;

    GetImageEncoders(num, size, pImageCodecInfo);
    for (UINT j = 0; j < num; ++j) {
      if (wcscmp(pImageCodecInfo.MimeType, format) == 0) {
            *pClsid = pImageCodecInfo.Clsid;
            free(pImageCodecInfo);
            return j;
      }
    }

    free(pImageCodecInfo);
    return -1;
}

### 说明:
1. 我们只更新图像的 DPI 而不改变其实际像素数据。这会影响图像的打印尺寸(即物理尺寸),但不会影响其数字像素大小。
2. 确保您使用合适的格式来保存图像,这里假设为 PNG 格式。
3. 确保正确处理图像加载和保存的错误,以避免进入无效状态。

这种方式在修改图像的 DPI 信息时,不改变图像中实际像素数,因而保持了总像素不变。如果有其他需求或问题,请随时提问!

以上回复来自 FishC AI 小助理,如未能正确解答您的问题,请继续追问。

cxnet 发表于 2024-11-21 13:06:34

FishC 发表于 2024-11-21 13:01
如果您希望在调整 DPI 时保持图像的总像素数不变,可以通过修改图像物理尺寸(宽度和高度)以适应新 DP ...

这次是可以了,但是我发现不是直接将CImage图像进行的修改dpi,而是以Bitmap* image加载的图片进行的dpi修改

FishC 发表于 2024-11-21 13:06:48

cxnet 发表于 2024-11-21 13:06
这次是可以了,但是我发现不是直接将CImage图像进行的 ...

在使用 GDI+ 修改 DPI 时,确实是通过 `Bitmap` 类来处理图像的。`CImage` 类没有直接的方法来修改图像的 DPI,因为 `CImage` 是一个简单的封装类,没有提供访问和设置 DPI 的方法。

不过,可以利用 `CImage` 和 `Bitmap` 之间的转换来实现目的。具体来说,通过 `CImage` 转换为 `Bitmap`,然后利用 `Bitmap` 修改 DPI 后再转换回 `CImage`。

#include <afxwin.h>
#include <gdiplus.h>
using namespace Gdiplus;
#pragma comment(lib, "Gdiplus.lib")

int GetEncoderClsid(const WCHAR* format, CLSID* pClsid);

void ChangeCImageDPI(CImage& img, const CString& outputPath, int newDPI) {
    // 初始化 GDI+
    GdiplusStartupInput gdiplusStartupInput;
    ULONG_PTR gdiplusToken;
    GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);

    // 从 CImage 创建 Bitmap
    Bitmap bitmap(img.GetWidth(), img.GetHeight(), img.GetPitch(), PixelFormat32bppARGB, (BYTE*)img.GetBits());

    // 设置新 DPI
    bitmap.SetResolution(static_cast<REAL>(newDPI), static_cast<REAL>(newDPI));

    // 保存 Bitmap 为文件
    CLSID pngClsid;
    if (GetEncoderClsid(L"image/png", &pngClsid) == -1) {
      AfxMessageBox(_T("Failed to get image encoder"));
    } else if (bitmap.Save(outputPath, &pngClsid, NULL) != Ok) {
      AfxMessageBox(_T("Failed to save image"));
    }

    // 关闭 GDI+
    GdiplusShutdown(gdiplusToken);
}

int GetEncoderClsid(const WCHAR* format, CLSID* pClsid) {
    UINT num = 0, size = 0;
    GetImageEncodersSize(&num, &size);
    if (size == 0) return -1;

    ImageCodecInfo* pImageCodecInfo = (ImageCodecInfo*)(malloc(size));
    if (pImageCodecInfo == NULL) return -1;

    GetImageEncoders(num, size, pImageCodecInfo);
    for (UINT j = 0; j < num; ++j) {
      if (wcscmp(pImageCodecInfo.MimeType, format) == 0) {
            *pClsid = pImageCodecInfo.Clsid;
            free(pImageCodecInfo);
            return j;
      }
    }
    free(pImageCodecInfo);
    return -1;
}

### 说明:
1. **CImage 转 Bitmap**: 创建 `CImage` 对象后,将其数据转为 `Bitmap` 进行 DPI 设置。
2. **DPI 修改**: 使用 `Bitmap` 的 `SetResolution` 方法来设置新的 DPI。
3. **保存修改后的图像**: 使用 GDI+ 提供的保存功能,不会改变图像的像素数据,只是在图像的头信息中修改了 DPI。

通过这种方式,能够保持 `CImage` 作为输入和处理,确保兼容性,并利用 `GDI+ Bitmap` 达成修改 DPI 的目的。如果还有其他问题,请随时问!

以上回复来自 FishC AI 小助理,如未能正确解答您的问题,请继续追问。

cxnet 发表于 2024-11-21 13:08:26

FishC 发表于 2024-11-21 13:06
在使用 GDI+ 修改 DPI 时,确实是通过 `Bitmap` 类来处理图像的。`CImage` 类没有直接的方法来修改图像 ...

CImage不可以直接修改图像DPI,Mat图像能不能修改dpi呢?能直接修改Mat图像的dpi也可以

FishC 发表于 2024-11-21 13:08:43

cxnet 发表于 2024-11-21 13:08
CImage不可以直接修改图像DPI,Mat图像能不能修改dpi ...

在 OpenCV 中,`Mat` 对象本身没有 DPI 的直接概念。`Mat` 主要用于处理图像数据,因此不包含与 DPI 相关的元数据。然而,你可以在保存图像时,使用自定义的方式来设置 DPI 信息。

如果需要将 OpenCV 的 `Mat` 对象保存为图像文件并修改其 DPI,可以通过将其转换为其他格式并使用支持元数据操作的库(如 GDI+)来实现。

以下是如何通过 OpenCV 和 GDI+ 的结合来调整图像 DPI 的示例:

#include <opencv2/opencv.hpp>
#include <objidl.h>
#include <gdiplus.h>
using namespace Gdiplus;
#pragma comment (lib,"Gdiplus.lib")

int GetEncoderClsid(const WCHAR* format, CLSID* pClsid);

void ChangeMatDPI(const cv::Mat& img, const std::string& outputPath, int newDPI) {
    // 初始化 GDI+
    GdiplusStartupInput gdiplusStartupInput;
    ULONG_PTR gdiplusToken;
    GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);

    // 将 OpenCV 的 Mat 转换为 GDI+ 的 Bitmap
    Bitmap bitmap(img.cols, img.rows, img.step, PixelFormat24bppRGB, img.data);

    // 设置新的 DPI
    bitmap.SetResolution(static_cast<REAL>(newDPI), static_cast<REAL>(newDPI));

    // 保存 Bitmap 为文件
    CLSID pngClsid;
    if (GetEncoderClsid(L"image/png", &pngClsid) == -1) {
      std::cerr << "Failed to get image encoder" << std::endl;
    } else if (bitmap.Save(std::wstring(outputPath.begin(), outputPath.end()).c_str(), &pngClsid, NULL) != Ok) {
      std::cerr << "Failed to save image" << std::endl;
    }

    // 关闭 GDI+
    GdiplusShutdown(gdiplusToken);
}

int GetEncoderClsid(const WCHAR* format, CLSID* pClsid) {
    UINT num = 0, size = 0;
    GetImageEncodersSize(&num, &size);
    if (size == 0) return -1;

    ImageCodecInfo* pImageCodecInfo = (ImageCodecInfo*)(malloc(size));
    if (pImageCodecInfo == NULL) return -1;

    GetImageEncoders(num, size, pImageCodecInfo);
    for (UINT j = 0; j < num; ++j) {
      if (wcscmp(pImageCodecInfo.MimeType, format) == 0) {
            *pClsid = pImageCodecInfo.Clsid;
            free(pImageCodecInfo);
            return j;
      }
    }
    free(pImageCodecInfo);
    return -1;
}

### 说明:
1. **Mat 转 Bitmap**: 将 `cv::Mat` 转换为 `Bitmap`,需要确保 Mat 格式与 GDI+ 兼容(例如,24位RGB)。
2. **设置和保存 DPI**: 使用 `Bitmap` 的 `SetResolution` 方法设置图像的 DPI。
3. **保存文件**: 通过 GDI+ 保存图像,并且可以明确指定编码格式。

这种方法结合使用 OpenCV 和 GDI+ 来实现对图像 DPI 的设置。同样,这不会更改图像的实际像素数据,只是更新图像文件中的元数据,以便应用于打印或其他需要 DPI 信息的操作。

如果还有其他问题,请随时询问!

以上回复来自 FishC AI 小助理,如未能正确解答您的问题,请继续追问。
页: [1]
查看完整版本: CImage图像dpi设置