I want to draw water mark on some documents like JPG, DOCX, PNG and PDF when printing. for this purpose, I Hook EndPage() function and use GDI+ DrawString to Draw Some Text. My code works well for Docx and JPG but the when I print PDF or PNG the water mark is not drawn. Any body know why this happened?
this my code:
I get water mark info trough RPC I check RPC the info received correctly in all formats
int EndPageHook::hookFunction(HDC hdc)
{
//Intialize GdiplusStartupInput and Graphics
Gdiplus::GdiplusStartupInput gdiplusStartupInput;
Gdiplus::GdiplusStartup(&m_gdiplusToken, &gdiplusStartupInput, NULL);
Graphics graphic_Obj(hdc);
//Get Watermark Information Throught RPC
WatermarkParameters = WaterMarkRPCClient::GetWaterMarkInfo(status, printInfo);
//Get Watermark Information and set in local variables
string s = std::to_string(WatermarkParameters.transparency);
s = std::to_string(WatermarkParameters.positions);
s = std::to_string(WatermarkParameters.font);
//Concatenation Text + User + Ip + Date
wcsncat_s(finalsenetce, WatermarkParameters._text, 512);
if (WatermarkParameters.Has_UserName)
{
wcsncat_s(finalsenetce, WatermarkParameters._username, 256);
}
if (WatermarkParameters.Has_Client)
{
wcsncat_s(finalsenetce, WatermarkParameters._client, 256);
}
if (WatermarkParameters.Has_Date)
{
wcsncat_s(finalsenetce, WatermarkParameters._date, 256);
}
{
//Set watermark position
StringAlignment WMLocation{};
StringFormat stringFormat;
switch (WatermarkParameters.positions)
{
case 1:
stringFormat.SetAlignment(StringAlignment::StringAlignmentNear);
stringFormat.SetLineAlignment(StringAlignment::StringAlignmentNear);
break;
case 2:
stringFormat.SetAlignment(StringAlignment::StringAlignmentCenter);
stringFormat.SetLineAlignment(StringAlignment::StringAlignmentCenter);
break;
case 3:
stringFormat.SetAlignment(StringAlignment::StringAlignmentFar);
stringFormat.SetLineAlignment(StringAlignment::StringAlignmentFar);
break;
default:
stringFormat.SetAlignment(StringAlignment::StringAlignmentCenter);
stringFormat.SetLineAlignment(StringAlignment::StringAlignmentCenter);
break;
}
// Claculate Page Size
double WidthsPixels = GetDeviceCaps(hdc, VERTRES);
double HeightsPixels = GetDeviceCaps(hdc, HORZRES);
double WidthsPixelsPerInch = GetDeviceCaps(hdc, LOGPIXELSX);
double HeightsPixelsPeInch = GetDeviceCaps(hdc, LOGPIXELSY);
////----------------------------------------------------------------------------------------------------------------
Gdiplus::FontFamily nameFontFamily(L"Arial");
Gdiplus::Font font(&nameFontFamily, WatermarkParameters.font, Gdiplus::FontStyleBold, Gdiplus::UnitPoint);
Gdiplus::RectF rectF(0, 0, 20, 20);
Gdiplus::SolidBrush solidBrush(Gdiplus::Color(WatermarkParameters.transparency, 250, 0, 250));
rectF.Width = (HeightsPixels / HeightsPixelsPeInch)* Screendpi;
rectF.Height = (WidthsPixels / WidthsPixelsPerInch)* Screendpi;
graphic_Obj.DrawString(finalsenetce, -1, &font, rectF, &stringFormat, &solidBrush);
}
GdiplusShutdown(m_gdiplusToken);
return getOriginalFunction()(hdc);```
}
I finally find the solution...
First I must return 1; instead of return getOriginalFunction()(hdc);.
Second I must add Scope { before Graphics graphic_Obj(hdc); and close it
after GdiplusShutdown(m_gdiplusToken); here is the correct code
int EndPageHook::hookFunction(HDC hdc)
{
//Intialize GdiplusStartupInput and Graphics
Gdiplus::GdiplusStartupInput gdiplusStartupInput;
Gdiplus::GdiplusStartup(&m_gdiplusToken, &gdiplusStartupInput, NULL);
{
Graphics graphic_Obj(hdc);
//Get Watermark Information Throught RPC
WatermarkParameters = WaterMarkRPCClient::GetWaterMarkInfo(status, printInfo);
//Get Watermark Information and set in local variables
string s = std::to_string(WatermarkParameters.transparency);
s = std::to_string(WatermarkParameters.positions);
s = std::to_string(WatermarkParameters.font);
//Concatenation Text + User + Ip + Date
wcsncat_s(finalsenetce, WatermarkParameters._text, 512);
if (WatermarkParameters.Has_UserName)
{
wcsncat_s(finalsenetce, WatermarkParameters._username, 256);
}
if (WatermarkParameters.Has_Client)
{
wcsncat_s(finalsenetce, WatermarkParameters._client, 256);
}
if (WatermarkParameters.Has_Date)
{
wcsncat_s(finalsenetce, WatermarkParameters._date, 256);
}
//Set watermark position
StringAlignment WMLocation{};
StringFormat stringFormat;
switch (WatermarkParameters.positions)
{
case 1:
stringFormat.SetAlignment(StringAlignment::StringAlignmentNear);
stringFormat.SetLineAlignment(StringAlignment::StringAlignmentNear);
break;
case 2:
stringFormat.SetAlignment(StringAlignment::StringAlignmentCenter);
stringFormat.SetLineAlignment(StringAlignment::StringAlignmentCenter);
break;
case 3:
stringFormat.SetAlignment(StringAlignment::StringAlignmentFar);
stringFormat.SetLineAlignment(StringAlignment::StringAlignmentFar);
break;
default:
stringFormat.SetAlignment(StringAlignment::StringAlignmentCenter);
stringFormat.SetLineAlignment(StringAlignment::StringAlignmentCenter);
break;
}
// Claculate Page Size
double WidthsPixels = GetDeviceCaps(hdc, VERTRES); // Get Device WIDTH
double HeightsPixels = GetDeviceCaps(hdc, HORZRES);//Get Device HEIGHT
double WidthsPixelsPerInch = GetDeviceCaps(hdc, LOGPIXELSX);// Get Document Pixel Per Inch in WIDTH of Screen
double HeightsPixelsPeInch = GetDeviceCaps(hdc, LOGPIXELSY);// Get Document Pixel Per Inch in HEIGHT of Screen
////----------------------------------------------------------------------------------------------------------------
Gdiplus::FontFamily nameFontFamily(L"Arial");
Gdiplus::Font font(&nameFontFamily, WatermarkParameters.font, Gdiplus::FontStyleBold, Gdiplus::UnitPoint);
Gdiplus::RectF rectF(0, 0, 200, 200);
Gdiplus::SolidBrush solidBrush(Gdiplus::Color(WatermarkParameters.transparency, 250, 0, 250));
rectF.Width = (HeightsPixels / HeightsPixelsPeInch)* Screendpi;
rectF.Height = (WidthsPixels / WidthsPixelsPerInch)* Screendpi;
graphic_Obj.DrawString(finalsenetce, -1, &font, rectF, &stringFormat, &solidBrush);
}
GdiplusShutdown(m_gdiplusToken);
return 1;
}
Related
I am trying to write a program for recording windows, but for some reason, after the program finishes, I get a corrupted .avi file.
I don't understand what the problem is. The hwnd2mat() and windowNames() functions work correctly, the error is clearly not in it. The code looks massive, but in fact, most of the code is occupied by the translation of the image from the HWND to the Mat. Also it should be noted that the resulting image after recording, always has the same size (irrespective of the recording time).
#include <iostream>
#include "opencv2/highgui.hpp"
#include "opencv2/imgproc.hpp"
#include <opencv2/videoio.hpp>
#include <Windows.h>
BOOL CALLBACK windowNames(HWND hwnd, LPARAM lParam) {
const DWORD TITLE_SIZE = 1024;
WCHAR windowTitle[TITLE_SIZE];
GetWindowTextW(hwnd, windowTitle, TITLE_SIZE);
int length = ::GetWindowTextLength(hwnd);
std::wstring title(&windowTitle[0]);
if (!IsWindowVisible(hwnd) || length == 0 || title == L"Program Manager") {
return TRUE;
}
// Retrieve the pointer passed into this callback, and re-'type' it.
// The only way for a C API to pass arbitrary data is by means of a void*.
std::vector<std::wstring>& titles = *reinterpret_cast<std::vector<std::wstring>*>(lParam);
titles.push_back(title);
return TRUE;
}
cv::Mat hwnd2mat(HWND hwnd)
{
HDC hwindowDC, hwindowCompatibleDC;
int height, width, srcheight, srcwidth;
HBITMAP hbwindow;
cv::Mat src;
BITMAPINFOHEADER bi;
HBITMAP bi2;
hwindowDC = GetDC(hwnd);
hwindowCompatibleDC = CreateCompatibleDC(hwindowDC);
SetStretchBltMode(hwindowCompatibleDC, COLORONCOLOR);
RECT windowsize; // get the height and width of the screen
GetClientRect(hwnd, &windowsize);
srcheight = windowsize.bottom;
srcwidth = windowsize.right;
height = windowsize.bottom / 1; //change this to whatever size you want to resize to
width = windowsize.right / 1;
// create a bitmap
hbwindow = CreateCompatibleBitmap(hwindowDC, width, height);
bi.biSize = sizeof(BITMAPINFOHEADER); //http://msdn.microsoft.com/en-us/library/windows/window/dd183402%28v=vs.85%29.aspx
bi.biWidth = width;
bi.biHeight = -height; //this is the line that makes it draw upside down or not
bi.biPlanes = 1;
bi.biBitCount = 32;
bi.biCompression = BI_RGB;
bi.biSizeImage = 0;
bi.biXPelsPerMeter = 1;
bi.biYPelsPerMeter = 2;
bi.biClrUsed = 3;
bi.biClrImportant = 4;
// use the previously created device context with the bitmap
SelectObject(hwindowCompatibleDC, hbwindow);
// copy from the window device context to the bitmap device context
StretchBlt(hwindowCompatibleDC, 0, 0, width, height, hwindowDC, 0, 0, srcwidth, srcheight, SRCCOPY); //change SRCCOPY to NOTSRCCOPY for wacky colors !
src.create(height, width, CV_8UC4);
GetDIBits(hwindowCompatibleDC, hbwindow, 0, height, src.data, (BITMAPINFO*)&bi, DIB_RGB_COLORS); //copy from hwindowCompatibleDC to hbwindow
// avoid memory leak
DeleteObject(hbwindow);
DeleteDC(hwindowCompatibleDC);
ReleaseDC(hwnd, hwindowDC);
return src;
}
int main(int argc, char** argv)
{
std::vector<std::wstring> titles; // we use std::wstring in place of std::string. This is necessary so that the entire character set can be represented.
EnumWindows(windowNames, reinterpret_cast<LPARAM>(&titles));
HWND hwndDesktop = GetDesktopWindow();
size_t number = 0;
int i = 0;
for (const auto& title : titles)
std::wcout << L"Title: " << i++ << title << std::endl;
std::cin >> number;
HWND hwndWindow = FindWindow(NULL, titles[number].c_str());
cv::namedWindow("output", cv::WINDOW_NORMAL);
cv::Mat src = hwnd2mat(/*hwndDesktop*/hwndWindow);
cv::VideoWriter outputVideo("output.avi", cv::VideoWriter::fourcc('M', 'J', 'P', 'G'), 1, cv::Size(src.cols, src.rows));
outputVideo.write(src);
int key = 0;
while (key != 27)
{
src = hwnd2mat(hwndWindow);
outputVideo.write(src);
cv::imshow("output", src);
key = cv::waitKey(60); //press ESC to end
}
return 0;
}
The problem lies in the fact that we need to transfer the Mat from BGRA to BGR during the transfer of the frame to the VideoWriter object.
For correct operation, it is necessary to write
Mat bgrImg; cvtColor(src, bgrImg, COLOR_BGRA2BGR);
in the range before sending a frame and send bgrImg as a frame.
VideoWriter instances need to be closed using the release() method. that finalizes the video container file.
OpenCV has no support for modern screen capture AFAIK. You’ll need to use platform-specific means of doing this (maybe encapsulated in some library). The problem here is that the screen data is already in the GPU, and by using OpenCV you’re forcing it to be copied to the main memory and processed with a relatively slow CPU. It won’t perform well. Instead, a platform-specific approach will process the data on the GPU, using it to both extract the window’s frames and encode them. It’ll be very efficient both in terms of speed as well as energy consumption (you’ll vastly improve battery life while the capture is running, and will prevent the fans from being annoying on notebooks).
I want the code below to take a screenshot of a specified window only, becuse this way BitBlt is faster.
The code below takes a screenshot of a window, specified by the name of window, loads the pixel data in a buffer and then re-draws the picture on your screen just to prove the copying worked.
I'd like to take screenshots of browser windows like Google Chrome, but it doesn't seem to work. It draws a black rectangle on my screen with the correct dimensions of the window.
Seems to be working all the time with the window of Minecraft.
Also worked with the Tor Browser.
I noticed that after querying window info, minecraft's window had no child-windows, but when it didn't work with the others, all of them had multiple child-windows.
#include<iostream>
#include<Windows.h>
#include<vector>
using namespace std;
int main() {
HWND wnd = FindWindow(NULL, "Minecraft 1.15.1");//Name of window to be screenshoted
if (!wnd) {
std::cout << "e1\n";
std::cin.get();
return 0;
}
WINDOWINFO wi = { 0 };
wi.cbSize = sizeof(WINDOWINFO);
GetWindowInfo(wnd, &wi);
RECT rect;
GetWindowRect(wnd, &rect);
int width = wi.rcClient.right - wi.rcClient.left;
int height = wi.rcClient.bottom - wi.rcClient.top;
cout << width << height;
/////Fetch window info^^^^^^^^
BYTE* ScreenData = new BYTE[4 * width*height];
// copy screen to bitmap
HDC hScreen = GetWindowDC(wnd);
HDC hDC = CreateCompatibleDC(hScreen);
HBITMAP hBitmap = CreateCompatibleBitmap(hScreen, width, height);
HGDIOBJ old_obj = SelectObject(hDC, hBitmap);
BitBlt(hDC, 0, 0, width, height, hScreen, 0, 0, SRCCOPY);
SelectObject(hDC, old_obj);
BITMAPINFO bmi = { 0 };
bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
std::cout << GetDIBits(hDC, hBitmap, 0, 0, NULL, &bmi, DIB_RGB_COLORS)<<endl;
//std::cout << bmi.bmiHeader.biHeight<< " "<< bmi.bmiHeader.biWidth<<endl;
BYTE*buffer = new BYTE[4* bmi.bmiHeader.biHeight*bmi.bmiHeader.biWidth];
bmi.bmiHeader.biHeight *= -1;
std::cout<<GetDIBits(hDC, hBitmap, 0, bmi.bmiHeader.biHeight, buffer, &bmi, DIB_RGB_COLORS)<<endl;//DIB_PAL_COLORS
/////Load window pixels into a buffer^^^^^^^^
POINT p;
int r = 0;
int g = 0;
int b = 0;
int x = 0;
int y = 0;
HDC sc = GetDC(NULL);
COLORREF color;
while (true) {
if ((y*-1) == bmi.bmiHeader.biHeight+1 && x == bmi.bmiHeader.biWidth-1) { break; }
r = (int)buffer[4 * ((y*bmi.bmiHeader.biWidth) + x)+2];
g = (int)buffer[4 * ((y*bmi.bmiHeader.biWidth) + x)+1];
b = (int)buffer[4 * ((y*bmi.bmiHeader.biWidth) + x) ];
color = RGB(r, g, b);
SetPixel(sc, x, y, color);
x++;
if (x == bmi.bmiHeader.biWidth) {
y++;
x = 0;
}
}
/////Prove that the copying was successful and buffer is full^^^^^^^^
Sleep(5000);
std::cout << "fin\n";
std::cin.get();
return 0;
}
I think the problem is the order you're doing things.
Before you put the bitmap on the clipboard, you should select it out of your memory device context.
Once you put the bitmaps on the clipboard, you no longer own it, so you shouldn't try to delete it.
The best way to do that is probably to move your // clean up section before the // save bitmap to clipboard section and to eliminate your DelectObject(hBitmap) statement. So the tail end of your code should probably be:
BitBlt(hDC, 0, 0, width, height, hScreen, 0, 0, SRCCOPY);
// clean up
SelectObject(hDC, old_obj); // selects hBitmap out of hDC
DeleteDC(hDC);
ReleaseDC(NULL, hScreen);
// save bitmap to clipboard
OpenClipboard(NULL);
EmptyClipboard();
SetClipboardData(CF_BITMAP, hBitmap); // clipboard now owns the bitmap
CloseClipboard();
If you still have a problem after those changes, I would check the return value of the SetClipboardData call. If that's failing, GetLastError may give a clue.
I have an application function that triggers screenshot capture of the said application's window.
It goes like this:
void PlatformWindow::captureScreenshot()
{
WIN32Window *window = (WIN32Window*)&g_window;
if (window) {
HWND handle = window->getWindow();
if (handle){
RECT client_rect = { 0 };
GetClientRect(handle, &client_rect);
int width = client_rect.right - client_rect.left;
int height = client_rect.bottom - client_rect.top;
HDC hdcScreen = GetDC(handle);
HDC hdc = CreateCompatibleDC(hdcScreen);
HBITMAP hbmp = CreateCompatibleBitmap(hdcScreen, width, height);
SelectObject(hdc, hbmp);
BitBlt(hdc, 0, 0, width, height, hdcScreen, 0, 0, SRCCOPY);
BITMAPINFO bmp_info = { 0 };
bmp_info.bmiHeader.biSize = sizeof(bmp_info.bmiHeader);
bmp_info.bmiHeader.biWidth = width;
bmp_info.bmiHeader.biHeight = height;
bmp_info.bmiHeader.biPlanes = 1;
bmp_info.bmiHeader.biBitCount = 24;
bmp_info.bmiHeader.biCompression = BI_RGB;
int bmp_padding = (width * 3) % 4;
if (bmp_padding != 0) bmp_padding = 4 - bmp_padding;
BYTE *bmp_pixels = new BYTE[(width * 3 + bmp_padding) * height];;
GetDIBits(hdc, hbmp, 0, height, bmp_pixels, &bmp_info, DIB_RGB_COLORS);
BITMAPFILEHEADER bmfHeader;
//Make screenshot name as a time
time_t currentTime = std::time(NULL);
std::ostringstream oss;
auto tm = *std::localtime(¤tTime);
oss << std::put_time(&tm, "%d-%m-%Y_%H-%M");
auto time_string = oss.str();
uint id = 0;
std::string name = "screens\\" + time_string +"_0.bmp";
//Loop over its indexes
while(true){
name = "screens\\" + time_string + "_" + std::to_string(id) +".bmp";
if (!file_exists(name)){
break;
}
id++;
}
LPSTR fileName = const_cast<char *>(name.c_str());
HANDLE bmp_file_handle = CreateFile(fileName, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
// Add the size of the headers to the size of the bitmap to get the total file size
DWORD dwSizeofDIB = (width * 3 + bmp_padding) * height + sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);
//Offset to where the actual bitmap bits start.
bmfHeader.bfOffBits = (DWORD)sizeof(BITMAPFILEHEADER) + (DWORD)sizeof(BITMAPINFOHEADER);
//Size of the file
bmfHeader.bfSize = dwSizeofDIB;
//bfType must always be BM for Bitmaps
bmfHeader.bfType = 0x4D42; //BM
DWORD dwBytesWritten = 0;
WriteFile(bmp_file_handle, (LPSTR)&bmfHeader, sizeof(BITMAPFILEHEADER), &dwBytesWritten, NULL);
WriteFile(bmp_file_handle, (LPSTR)&bmp_info.bmiHeader, sizeof(BITMAPINFOHEADER), &dwBytesWritten, NULL);
WriteFile(bmp_file_handle, (LPSTR)bmp_pixels, (width * 3 + bmp_padding) * height, &dwBytesWritten, NULL);
//Close the handle for the file that was created
CloseHandle(bmp_file_handle);
DeleteDC(hdc);
DeleteObject(hbmp);
ReleaseDC(NULL, hdcScreen);
delete[] bmp_pixels;
}
}
}
And it works fine on several machines (Windows 10, XP and so on).
There is, however, a rare case on Windows 7 (and maybe others, I don't know if that's just a bad luck or whatever) that makes screenshot's blank. Just all white.
I ran some diagnosis and am pretty convenient that it, for sure, captures right window, but somehow it does not capture pixels well.
I dig deeper and found out, that whenever I set this option in windows -> performance options -> "Adjust for best performance", it suddenly starts to work and a screenshot is positively taken (no more white screen, which is great).
What I am wondering right now is if I can somehow make my code better to cover up those situations since forcing user to change his Window's options is not an ideal scenario.
#EDIT:
I found out that this is the very option that makes it works, if I disable desktop composition, it works just fine.
What I need to do is very simple, I need to plot a vector using CIMG and then save the graph ina jpg and add the jpg to a PDF document using JAGPDF. In order to save CIMG as JPG, the program uses an external program called Image Magick.
I wanted to avoid using that program and use GDI+ instead by first saving the CIMG as a BMP (it does that natively) and then saving the jpg from the bmp.
MCVE program looks like this
#include "CImg.h"
#include <jagpdf/api.h>
#include <vector>
using namespace jag;
using namespace cimg_library;
int main(int argc, char** const argv)
{
const float x0 = 0;
const float x1 = 9;
const int resolution = 5000;
// Create plot data.
CImg<double> values(1, resolution, 1, 1, 0);
const unsigned int r = resolution - 1;
for (int i1 = 0; i1 < resolution; ++i1)
{
double xtime = x0 + i1*(x1 - x0) / r;
values(0, i1) = 2 * sin(xtime);
}
CImg<unsigned char> graph;
graph.assign(750, 240, 1, 3, 255);
static const unsigned char black[] = { 0, 0, 0 }, white[] = { 255, 255, 255 };
static const unsigned char red[] = { 255, 200, 200 }, bred[] = { 255, 0, 0 };
graph.draw_grid(6, 6, 0, 0, false, true, red, 10.0f, 0xFFFFFFFF, 0xFFFFFFFF);
graph.draw_grid(30, 30, 0, 0, false, true, bred, 10.0f, 0xFFFFFFFF, 0xFFFFFFFF);
graph.draw_graph(values, black, 1, 1, 1, 2, -2, 0xFFFFFFFF);;
//////////////Method 1: Using Image Magick////////////////
graph.save_jpeg("plot2.jpg");
pdf::Document doc(pdf::create_file("report.pdf"));
doc.page_start(848.68, 597.6);
pdf::Image imag2 = doc.image_load_file("plot2.jpg");
doc.page().canvas().image(imag2, 50, 50);
doc.page_end();
doc.finalize();
//////////////Method 2: Using GDI+////////////////
graph.save("plot.bmp");
SaveFile();
pdf::Document doc2(pdf::create_file("report2.pdf"));
doc2.page_start(848.68, 597.6);
pdf::Image imag = doc2.image_load_file("plot.jpg");
doc2.page().canvas().image(imag, 50, 50);
doc2.page_end();
doc2.finalize();
return 0;
}
With SaveFile() being the following function using GDI+ to convert from plot.bmp to plot.jpg
#include <windows.h>
#include <objidl.h>
#include <gdiplus.h>
#include "GdiplusHelperFunctions.h"
#pragma comment (lib,"Gdiplus.lib")
VOID SaveFile()
{
// Initialize GDI+.
Gdiplus::GdiplusStartupInput gdiplusStartupInput;
ULONG_PTR gdiplusToken;
GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
CLSID encoderClsid;
Status stat;
Image* image = new Gdiplus::Image(L"plot.bmp");
// Get the CLSID of the PNG encoder.
GetEncoderClsid(L"image/jpeg", &encoderClsid);
stat = image->Save(L"plot.jpg", &encoderClsid, NULL);
if (stat == Ok)
printf("plot.jpg was saved successfully\n");
else
printf("Failure: stat = %d\n", stat);
delete image;
GdiplusShutdown(gdiplusToken);
}
Both methods save jpgs that in properties seems to have the same size but the first put the image correctly in the pdf while the second puts a huge image in the pdf even though they are supossed to be the same size. How can I fix this?
Attached is scrrenshots of report1 and report2
SOLUTION
With your suggestions, I was able to modify the SaveFile function in order to be able to control de DPI, I post the new code in case someone needs it.
VOID SaveFile()
{
// Initialize GDI+.
Gdiplus::GdiplusStartupInput gdiplusStartupInput;
ULONG_PTR gdiplusToken;
GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
CLSID encoderClsid;
Status stat;
EncoderParameters encoderParameters;
ULONG quality;
Gdiplus::Bitmap* bitmap = new Gdiplus::Bitmap(L"plot.bmp");
Gdiplus::REAL dpi = 96;
bitmap->SetResolution(dpi,dpi);
// Get the CLSID of the PNG encoder.
GetEncoderClsid(L"image/jpeg", &encoderClsid);
encoderParameters.Count = 1;
encoderParameters.Parameter[0].Guid = EncoderQuality;
encoderParameters.Parameter[0].Type = EncoderParameterValueTypeLong;
encoderParameters.Parameter[0].NumberOfValues = 1;
quality = 100;
encoderParameters.Parameter[0].Value = &quality;
stat = bitmap->Save(L"plot.jpg", &encoderClsid, &encoderParameters);
if (stat == Ok)
printf("plot.jpg was saved successfully\n");
else
printf("Failure: stat = %d\n", stat);
delete bitmap;
GdiplusShutdown(gdiplusToken);
return;
}
I would guess ImageMagick include some perks that filter the image to fit the canvas. The smartass.
I'd try resizing the image before exporting to JPEG. You might give a go to this guide. It basically says you can resize the bmp (in the example it checks w/h ratio but well...). THe goal should be to specify the size you need for the canvas is exactly that.
Gdiplus::Bitmap* GDIPlusImageProcessor::ResizeClone(Bitmap *bmp, INT width, INT height)
{
UINT o_height = bmp->GetHeight();
UINT o_width = bmp->GetWidth();
INT n_width = width;
INT n_height = height;
double ratio = ((double)o_width) / ((double)o_height);
if (o_width > o_height) {
// Resize down by width
n_height = static_cast<UINT>(((double)n_width) / ratio);
} else {
n_width = static_cast<UINT>(n_height * ratio);
}
Gdiplus::Bitmap* newBitmap = new Gdiplus::Bitmap(n_width, n_height, bmp->GetPixelFormat());
Gdiplus::Graphics graphics(newBitmap);
graphics.DrawImage(bmp, 0, 0, n_width, n_height);
return newBitmap;
}
And then, save it using the encoder. ALso, you'd like to check whether you might need to set the quality of the resulting JPEG using encoderparameters as shown in the official documentation.
// Get the CLSID of the JPEG encoder.
GetEncoderClsid(L"image/jpeg", &encoderClsid);
// Before we call Image::Save, we must initialize an
// EncoderParameters object. The EncoderParameters object
// has an array of EncoderParameter objects. In this
// case, there is only one EncoderParameter object in the array.
// The one EncoderParameter object has an array of values.
// In this case, there is only one value (of type ULONG)
// in the array. We will let this value vary from 0 to 100.
encoderParameters.Count = 1;
encoderParameters.Parameter[0].Guid = EncoderQuality;
encoderParameters.Parameter[0].Type = EncoderParameterValueTypeLong;
encoderParameters.Parameter[0].NumberOfValues = 1;
// Save the image as a JPEG with quality level 0.
quality = 0;
encoderParameters.Parameter[0].Value = &quality;
stat = image->Save(L"Shapes001.jpg", &encoderClsid, &encoderParameters);
if(stat == Ok)
wprintf(L"%s saved successfully.\n", L"Shapes001.jpg");
else
wprintf(L"%d Attempt to save %s failed.\n", stat, L"Shapes001.jpg");
// Save the image as a JPEG with quality level 50.
quality = 50;
encoderParameters.Parameter[0].Value = &quality;
stat = image->Save(L"Shapes050.jpg", &encoderClsid, &encoderParameters);
if(stat == Ok)
wprintf(L"%s saved successfully.\n", L"Shapes050.jpg");
else
wprintf(L"%d Attempt to save %s failed.\n", stat, L"Shapes050.jpg");
EDIT: JAGPDF also says image DPI is taken into account when painting. SO we probably are on the right path.
Let's say we would like to tile a region of the page with our image.
To do so we need to know the image dimensions. Because width() and
width() return size in pixels we need to recalculate these to user
space units.
Image DPI is taken into account when the image is painted onto a
canvas. An image usually specifies its DPI. If it is not so a value of
images.default_dpi is used
img_width = img.width() / img.dpi_x() * 72
img_height = img.height() / img.dpi_y() * 72
for x in range(7):
for y in range(15):
canvas.image(img, 90 + x * img_width, 100 + y * img_height)
You might try changing DPI using this SO answer.
If I understand your question correctly, your aim is to remove the dependency on ImageMagick.
You can do that more simply by telling CImg to use its built-in support for JPEG. All you need to do is
define cimg_use_jpeg
link with libjpeg
So your compilation command becomes:
g++ -Dcimg_use_jpeg ... -ljpeg
I am creating desktop application using C++ and pure WinApi. I need to display image that was given to me as SVG.
Since WinAPI supports only EMF files as vector format, I have used Inkscape to convert the file into EMF. My graphics design skills are at beginner level, but I have managed to convert SVG file into EMF successfully. However, the result is not looking as the original one, it is less "precise" so to say.
If I export the SVG as PNG and display it with GDI+, the result is the same as the original file. Unfortunately I need vector format.
To see exactly what I mean, download SVG, and EMF and PNG that I made here. Just click on Download:test.rar above 5 yellow stars ( see image below ).
Here are the instructions for creating minimal application that reproduces the problem:
1) Create default Win32 project in Visual Studio ( I use VS 2008, but this shouldn't be the problem );
2) Rewrite WM_PAINT like this:
case WM_PAINT:
{
hdc = BeginPaint(hWnd, &ps);
// TODO: Add any drawing code here...
RECT rcClient;
GetClientRect( hWnd, &rcClient );
FillRect( hdc, &rcClient, (HBRUSH)GetStockObject( LTGRAY_BRUSH) );
// put metafile in the same place your app is
HENHMETAFILE hemf = GetEnhMetaFile( L".\\test.emf" );
ENHMETAHEADER emh;
GetEnhMetaFileHeader( hemf, sizeof(emh), &emh );
// rescale metafile, and keep proportion
UINT o_height = emh.rclFrame.bottom - emh.rclFrame.top,
o_width = emh.rclFrame.right - emh.rclFrame.left;
float scale = 0.5;
scale = (float)( rcClient.right - rcClient.left ) / o_width;
if( (float)( rcClient.bottom - rcClient.top ) / o_height < scale )
scale = (float)( rcClient.bottom - rcClient.top ) / o_height;
int marginX = ( rcClient.right - rcClient.left ) - (int)( o_width * scale );
int marginY = ( rcClient.bottom - rcClient.top ) - (int)( o_height * scale );
marginX /= 2;
marginY /= 2;
rcClient.left = rcClient.left + marginX;
rcClient.right = rcClient.right - marginX;
rcClient.top = rcClient.top + marginY;
rcClient.bottom = rcClient.bottom - marginY;
// Draw the picture.
PlayEnhMetaFile( hdc, hemf, &rcClient );
// Release the metafile handle.
DeleteEnhMetaFile(hemf);
EndPaint(hWnd, &ps);
}
break;
3) Add following handlers for WM_SIZE and WM_ERASEBKGND just below WM_PAINT :
case WM_SIZE:
InvalidateRect( hWnd, NULL, FALSE );
return 0L;
case WM_ERASEBKGND:
return 1L;
4) Resize the window to the smallest possible size, and then maximize it.
Notice that the bigger the window gets, the better the image quality is, but the smaller it gets the "less precise" the image gets. I tested this on Windows XP.
I am asking your help to get the same graphic quality of the EMF file as the original SVG.
Thank you for your time and efforts. Best regards.
Solved it!
The solution makes much, if not all of the solution I've submitted redundant. I've therefore decided to replace it with this one.
There's a number of things to take into account and a number of concepts that are employed to get the desired result. These include (in no particular order)
The need to set a maskColour that closely matches the background, while also not being present in the final computed image. Pixels that straddle the border between transparent/opaque areas are blended values of the mask and the EMF's colour at that point.
The need to choose a scaling rate that's appropriate for the source image - in the case of this image and the code I've used, I chose 8. This means that we're drawing this particular EMF at about a megapixel, even though the destination is likely to be in the vicinity of about 85k pixels.
The need to manually set the alpha channel of the generated 32bit HBITMAP, since the GDI stretching/drawing functions disregard this channel, yet the AlphaBlend function requires them to be accurate.
I also note that I've used old code to draw the background manually each time the screen is refreshed. A much better approach would be to create a patternBrush once which is then simply copied using the FillRect function. This is much faster than filling the rect with a solid colour and then drawing the lines over the top. I can't be bothered to re-write that part of the code, though I'll include a snippet for reference that I've used in other projects in the past.
Here's a couple of shots of the result I get from the code below:
Here's the code I used to achieve it:
#define WINVER 0x0500 // for alphablend stuff
#include <windows.h>
#include <commctrl.h>
#include <stdio.h>
#include <stdint.h>
#include "resource.h"
HINSTANCE hInst;
HBITMAP mCreateDibSection(HDC hdc, int width, int height, int bitCount)
{
BITMAPINFO bi;
ZeroMemory(&bi, sizeof(bi));
bi.bmiHeader.biSize = sizeof(bi.bmiHeader);
bi.bmiHeader.biWidth = width;
bi.bmiHeader.biHeight = height;
bi.bmiHeader.biPlanes = 1;
bi.bmiHeader.biBitCount = bitCount;
bi.bmiHeader.biCompression = BI_RGB;
return CreateDIBSection(hdc, &bi, DIB_RGB_COLORS, 0,0,0);
}
void makePixelsTransparent(HBITMAP bmp, byte r, byte g, byte b)
{
BITMAP bm;
GetObject(bmp, sizeof(bm), &bm);
int x, y;
for (y=0; y<bm.bmHeight; y++)
{
uint8_t *curRow = (uint8_t *)bm.bmBits;
curRow += y * bm.bmWidthBytes;
for (x=0; x<bm.bmWidth; x++)
{
if ((curRow[x*4 + 0] == b) && (curRow[x*4 + 1] == g) && (curRow[x*4 + 2] == r))
{
curRow[x*4 + 0] = 0; // blue
curRow[x*4 + 1] = 0; // green
curRow[x*4 + 2] = 0; // red
curRow[x*4 + 3] = 0; // alpha
}
else
curRow[x*4 + 3] = 255; // alpha
}
}
}
// Note: maskCol should be as close to the colour of the background as is practical
// this is because the pixels that border transparent/opaque areas will have
// their colours derived from a blending of the image colour and the maskColour
//
// I.e - if drawing to a white background (255,255,255), you should NOT use a mask of magenta (255,0,255)
// this would result in a magenta-ish border
HBITMAP HbitmapFromEmf(HENHMETAFILE hEmf, int width, int height, COLORREF maskCol)
{
ENHMETAHEADER emh;
GetEnhMetaFileHeader(hEmf, sizeof(emh), &emh);
int emfWidth, emfHeight;
emfWidth = emh.rclFrame.right - emh.rclFrame.left;
emfHeight = emh.rclFrame.bottom - emh.rclFrame.top;
// these are arbitrary and selected to give a good mix of speed and accuracy
// it may be worth considering passing this value in as a parameter to allow
// fine-tuning
emfWidth /= 8;
emfHeight /= 8;
// draw at 'native' size
HBITMAP emfSrcBmp = mCreateDibSection(NULL, emfWidth, emfHeight, 32);
HDC srcDC = CreateCompatibleDC(NULL);
HBITMAP oldSrcBmp = (HBITMAP)SelectObject(srcDC, emfSrcBmp);
RECT tmpEmfRect, emfRect;
SetRect(&tmpEmfRect, 0,0,emfWidth,emfHeight);
// fill background with mask colour
HBRUSH bkgBrush = CreateSolidBrush(maskCol);
FillRect(srcDC, &tmpEmfRect, bkgBrush);
DeleteObject(bkgBrush);
// draw emf
PlayEnhMetaFile(srcDC, hEmf, &tmpEmfRect);
HDC dstDC = CreateCompatibleDC(NULL);
HBITMAP oldDstBmp;
HBITMAP result;
result = mCreateDibSection(NULL, width, height, 32);
oldDstBmp = (HBITMAP)SelectObject(dstDC, result);
SetStretchBltMode(dstDC, HALFTONE);
StretchBlt(dstDC, 0,0,width,height, srcDC, 0,0, emfWidth,emfHeight, SRCCOPY);
SelectObject(srcDC, oldSrcBmp);
DeleteDC(srcDC);
DeleteObject(emfSrcBmp);
SelectObject(dstDC, oldDstBmp);
DeleteDC(dstDC);
makePixelsTransparent(result, GetRValue(maskCol),GetGValue(maskCol),GetBValue(maskCol));
return result;
}
int rectWidth(RECT &r)
{
return r.right - r.left;
}
int rectHeight(RECT &r)
{
return r.bottom - r.top;
}
void onPaintEmf(HWND hwnd, HENHMETAFILE srcEmf)
{
PAINTSTRUCT ps;
RECT mRect, drawRect;
HDC hdc;
double scaleWidth, scaleHeight, scale;
int spareWidth, spareHeight;
int emfWidth, emfHeight;
ENHMETAHEADER emh;
GetClientRect( hwnd, &mRect );
hdc = BeginPaint(hwnd, &ps);
// calculate the draw-size - retain aspect-ratio.
GetEnhMetaFileHeader(srcEmf, sizeof(emh), &emh );
emfWidth = emh.rclFrame.right - emh.rclFrame.left;
emfHeight = emh.rclFrame.bottom - emh.rclFrame.top;
scaleWidth = (double)rectWidth(mRect) / emfWidth;
scaleHeight = (double)rectHeight(mRect) / emfHeight;
scale = min(scaleWidth, scaleHeight);
int drawWidth, drawHeight;
drawWidth = emfWidth * scale;
drawHeight = emfHeight * scale;
spareWidth = rectWidth(mRect) - drawWidth;
spareHeight = rectHeight(mRect) - drawHeight;
drawRect = mRect;
InflateRect(&drawRect, -spareWidth/2, -spareHeight/2);
// create a HBITMAP from the emf and draw it
// **** note that the maskCol matches the background drawn by the below function ****
HBITMAP srcImg = HbitmapFromEmf(srcEmf, drawWidth, drawHeight, RGB(230,230,230) );
HDC memDC;
HBITMAP old;
memDC = CreateCompatibleDC(hdc);
old = (HBITMAP)SelectObject(memDC, srcImg);
byte alpha = 255;
BLENDFUNCTION bf = {AC_SRC_OVER,0,alpha,AC_SRC_ALPHA};
AlphaBlend(hdc, drawRect.left,drawRect.top, drawWidth,drawHeight,
memDC, 0,0,drawWidth,drawHeight, bf);
SelectObject(memDC, old);
DeleteDC(memDC);
DeleteObject(srcImg);
EndPaint(hwnd, &ps);
}
void drawHeader(HDC dst, RECT headerRect)
{
HBRUSH b1;
int i,j;//,headerHeight = (headerRect.bottom - headerRect.top)+1;
b1 = CreateSolidBrush(RGB(230,230,230));
FillRect(dst, &headerRect,b1);
DeleteObject(b1);
HPEN oldPen, curPen;
curPen = CreatePen(PS_SOLID, 1, RGB(216,216,216));
oldPen = (HPEN)SelectObject(dst, curPen);
for (j=headerRect.top;j<headerRect.bottom;j+=10)
{
MoveToEx(dst, headerRect.left, j, NULL);
LineTo(dst, headerRect.right, j);
}
for (i=headerRect.left;i<headerRect.right;i+=10)
{
MoveToEx(dst, i, headerRect.top, NULL);
LineTo(dst, i, headerRect.bottom);
}
SelectObject(dst, oldPen);
DeleteObject(curPen);
MoveToEx(dst, headerRect.left,headerRect.bottom,NULL);
LineTo(dst, headerRect.right,headerRect.bottom);
}
BOOL CALLBACK DlgMain(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
static HENHMETAFILE hemf;
switch(uMsg)
{
case WM_INITDIALOG:
{
hemf = GetEnhMetaFile( "test.emf" );
}
return TRUE;
case WM_PAINT:
onPaintEmf(hwndDlg, hemf);
return 0;
case WM_ERASEBKGND:
{
RECT mRect;
GetClientRect(hwndDlg, &mRect);
drawHeader( (HDC)wParam, mRect);
}
return true;
case WM_SIZE:
InvalidateRect( hwndDlg, NULL, true );
return 0L;
case WM_CLOSE:
{
EndDialog(hwndDlg, 0);
}
return TRUE;
case WM_COMMAND:
{
switch(LOWORD(wParam))
{
}
}
return TRUE;
}
return FALSE;
}
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd)
{
hInst=hInstance;
InitCommonControls();
return DialogBox(hInst, MAKEINTRESOURCE(DLG_MAIN), NULL, (DLGPROC)DlgMain);
}
Finally, here's an example of creating a patternBrush for filling the background using the FillRect function. This approach is suitable for any tileable background.
HBRUSH makeCheckerBrush(int squareSize, COLORREF col1, COLORREF col2)
{
HDC memDC, tmpDC = GetDC(NULL);
HBRUSH result, br1, br2;
HBITMAP old, bmp;
RECT rc, r1, r2;
br1 = CreateSolidBrush(col1);
br2 = CreateSolidBrush(col2);
memDC = CreateCompatibleDC(tmpDC);
bmp = CreateCompatibleBitmap(tmpDC, 2*squareSize, 2*squareSize);
old = (HBITMAP)SelectObject(memDC, bmp);
SetRect(&rc, 0,0, squareSize*2, squareSize*2);
FillRect(memDC, &rc, br1);
// top right
SetRect(&r1, squareSize, 0, 2*squareSize, squareSize);
FillRect(memDC, &r1, br2);
// bot left
SetRect(&r2, 0, squareSize, squareSize, 2*squareSize);
FillRect(memDC, &r2, br2);
SelectObject(memDC, old);
DeleteObject(br1);
DeleteObject(br2);
ReleaseDC(0, tmpDC);
DeleteDC(memDC);
result = CreatePatternBrush(bmp);
DeleteObject(bmp);
return result;
}
Example of result, created with:
HBRUSH bkBrush = makeCheckerBrush(8, RGB(153,153,153), RGB(102,102,102));