Качество ЭДС снижается при уменьшении окна, но хорошо при больших размерах окна

Я создаю настольное приложение, используя C++ а также pure WinApi, Мне нужно отобразить изображение, которое было дано мне как SVG,

поскольку WinAPI поддерживает только EMF файлы в векторном формате, я использовал Inkscape преобразовать файл в EMF, Мои навыки графического дизайна находятся на начальном уровне, но мне удалось преобразовать SVG подать в EMF успешно. Однако результат не выглядит как исходный, он, так сказать, менее «точен».

Если я экспортирую SVG как PNG и отобразить его с GDI+, результат совпадает с исходным файлом. К сожалению мне нужен векторный формат.

Чтобы точно понять, что я имею в виду, загрузите SVG, а также EMF а также PNG что я сделал Вот. Просто нажмите на Скачать: test.rar выше 5 желтых звезд (см. изображение ниже).

введите описание изображения здесь

Вот инструкции по созданию минимального приложения, которое воспроизводит проблему:

1) Создать по умолчанию Win32 project в Visual Studio (Я использую VS 2008, но это не должно быть проблемой);

2) переписать WM_PAINT как это:

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) Добавьте следующие обработчики для WM_SIZE а также WM_ERASEBKGND ниже WM_PAINT :

case WM_SIZE:
InvalidateRect( hWnd, NULL, FALSE );
return 0L;
case WM_ERASEBKGND:
return 1L;

4) Измените размер окна до минимально возможного, а затем увеличьте его.

Обратите внимание, что чем больше окно, тем лучше качество изображения, но чем оно меньше, тем менее точным становится изображение. Я проверял это на Windows XP,

Я прошу вашей помощи, чтобы получить то же графическое качество файла EMF, что и оригинал SVG,

Спасибо за ваше время и усилия. С наилучшими пожеланиями.

1

Решение

Решил это!

Решение делает много, если не все решение, которое я представил, излишним. Поэтому я решил заменить его на этот.

Есть ряд вещей, которые необходимо принять во внимание, и ряд понятий, которые используются для получения желаемого результата. К ним относятся (в произвольном порядке)

  • Необходимость установить maskColour, который близко соответствует фону, но также не присутствует в конечном вычисленном изображении. Пиксели, которые пересекают границу между прозрачными / непрозрачными областями, представляют собой смешанные значения маски и цвета ЭДС в этой точке.
  • Необходимость выбора скорости масштабирования, подходящей для исходного изображения — в случае этого изображения и кода, который я использовал, я выбрал 8. Это означает, что мы рисуем эту конкретную ЭДС примерно на мегапикселе, даже если пункт назначения, вероятно, будет около 85 тыс. пикселей.
  • Необходимость вручную устанавливать альфа-канал сгенерированного 32-битного HBITMAP, поскольку функции растяжения / рисования GDI не учитывают этот канал, однако функция AlphaBlend требует их точности.

Я также отмечаю, что я использовал старый код для рисования фона вручную каждый раз, когда обновляется экран. Гораздо лучшим подходом было бы создать patternBrush один раз, который затем просто копируется с использованием функции FillRect. Это намного быстрее, чем заполнение прямоугольника сплошным цветом и затем рисование линий поверх. Я не могу быть обеспокоен переписать эту часть кода, хотя я включу фрагмент для справки, который я использовал в других проектах в прошлом.

Вот пара снимков результата, который я получаю из кода ниже:

введите описание изображения здесь
введите описание изображения здесь
введите описание изображения здесь

Вот код, который я использовал для достижения этой цели:

#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);
}

Наконец, вот пример создания patternBrush для заливки фона с использованием функции FillRect. Этот подход подходит для любого мозаичного фона.

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;
}

Пример результата, созданного с помощью:

HBRUSH bkBrush = makeCheckerBrush(8, RGB(153,153,153), RGB(102,102,102));
введите описание изображения здесь

3

Другие решения