Качество EMF ухудшается при уменьшении размера окна, но хорошо при больших размерах окна.

Я создаю настольное приложение, используя 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.

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


person AlwaysLearningNewStuff    schedule 05.08.2014    source источник
comment
Думал, что это может быть вы из заголовка вопроса. :D Я помню это изображение (и изображение карты). Теперь мне пора спать, хотя, если вы не видели вопрос, это, кажется, дает несколько полезных советов: stackoverflow.com/questions/1783130/draw-emf-antialiased — особенно предполагаемый подход, используемый MSOffice — рисовать в 2 раза больше, а затем уменьшать — вы можете воспользоваться сглаживанием, если масштабируете большой результат до желаемый размер с помощью gdi+. Кроме того, загрузки emf и svg кажутся неработающими – я получаю текстовый ответ размером 371 байт для каждого (пробовал дважды каждый). С уважением, С. :)   -  person enhzflep    schedule 05.08.2014
comment
@enhzflep: Я считаю, что ссылка теперь исправлена ​​(все файлы загружены как .rar), спасибо за попытку помочь. Я не хотел вас беспокоить, так как считаю, что все заслуживают хорошего отдыха от работы (кроме меня видимо :smile: ). рисовать в 2 раза больше, а затем уменьшать разрешение получил аналогичный совет от Super User (но они сказали сделать это в Inkscape), но не повезло... Возможно, вы могли бы попробовать что-нибудь в ближайшие выходные (не хочу вас беспокоить)? Спасибо и спокойной ночи :)   -  person AlwaysLearningNewStuff    schedule 05.08.2014
comment
@enhzflep: Это обсуждение мне кажется актуальным. Пожалуйста, взгляните и скажите мне, что вы думаете, когда у вас есть свободная минутка, хорошо?   -  person AlwaysLearningNewStuff    schedule 06.08.2014
comment
похоже, в этой ветке о суперпользователе есть несколько хороших замечаний. Я отмечаю комментарии, сделанные mpy о том, что преобразование в ЭДС просто прекрасно, и, в частности, комментарий относительно используемого алгоритма масштабирования. Я попытался нарисовать ЭДС с размером, указанным в rclBounds (а не в rclFrame), а затем использовать StretchBlt для изменения размера - ужасный результат. Затем я использовал SetStretchBltMode с HALFTONE и получил почти идентичный по пикселям результат png. Все еще сглаживаю перегибы прозрачности вокруг логотипа. Я посмотрю еще раз, когда вернусь с этой встречи. :)   -  person enhzflep    schedule 06.08.2014
comment
Я забыл спросить еще несколько вещей ранее: 1. Будет ли изображение нарисовано на сплошном фоне, или оно будет на узорчатом (т.е. - должна ли прозрачность снаружи логотипа быть прозрачной? , или его можно залить сплошным цветом) и 2. Будет ли логотип отображаться в фиксированном размере или его необходимо изменить? - Я спрашиваю, потому что в вашей демонстрации при определенных масштабах некоторые линии имеют толщину 1 пиксель, а другие значительно толще, но при других масштабах эти же линии имеют одинаковую ширину. В любом случае - примерно насколько большой? (во избежание повторной выборки артефактов)   -  person enhzflep    schedule 06.08.2014
comment
@enhzflep: 1. Будет ли изображение нарисовано на сплошном фоне, или оно будет на узорчатом (т.е. - прозрачность вне логотипа должна быть прозрачной, или ее можно залить сплошным цветом) Будет узорчатым один (серая заштрихованная кисть с белыми линиями — вы уже видели изображение в одном из моих вопросов). Прозрачность снаружи логотипа должна оставаться. 2. Будет ли логотип нарисован в фиксированном размере или его нужно будет изменить? На данный момент логотип будет фиксированного размера (90 x 120), но мои работодатели могут передумать, поэтому я должен сохранить изменение размера вариант открыт.   -  person AlwaysLearningNewStuff    schedule 06.08.2014
comment
@enhzflep: Логотип не мой и не принадлежит моим работодателям. Он принадлежит третьей стороне, участвующей в проекте, и, на мой взгляд, качество рисунка не очень хорошее, но это единственное, что у меня есть. Я не знаю, как улучшить качество изображения, и еще более странным для меня является тот факт, что мне нужно выполнить передискретизацию в векторном формате файла. Невероятный...   -  person AlwaysLearningNewStuff    schedule 06.08.2014
comment
Давайте продолжим это обсуждение в чате.   -  person enhzflep    schedule 06.08.2014
comment
Думаю, я вижу выход. То есть нарисуйте изображение намного больше и уменьшите выборку. Затем замаскируйте прозрачные области и AlphaBlend. Я рассказал об этом больше в чате и обновил свой ответ новыми фотографиями. :)   -  person enhzflep    schedule 07.08.2014


Ответы (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)); введите здесь описание изображения

person enhzflep    schedule 06.08.2014
comment
У меня возникла проблема, так как Visual Studio 2008 не может найти #include<stdint.h>, но мне удалось решить ее путем поиска через SO. Код работает, изображение остается неизменным после изменения размера окна, поэтому я официально принял решение (я проголосовал ранее). Можем ли мы встретиться в чате (когда у вас будет свободное время), чтобы я мог задать вам несколько вопросов лично (они появились после прочтения вашего ответа)? Если интересно, оставьте мне комментарий с удобным для вас временем и днем ​​(обещаю не утомлять вас, и я думаю, что вопросы достаточно умны, чтобы привлечь ваше внимание). С наилучшими пожеланиями! - person AlwaysLearningNewStuff; 17.08.2014
comment
Привет! Это была тяжелая неделя. О да, извините за использование gcc и не понимание того, что типы uintX_t не будут доступны в VS. Рад слышать, что вы нашли результат приемлемым - это было веселое и интересное решение проблемы. (Как и многие из них на сайте StackExchange для гольфа). Быстрая проверка говорит мне, что 15:57 здесь = 7:57 утра в (как я думаю) вашей части мира. - В настоящее время мы используем время по Гринвичу +10. Я проверю чат сегодня около 8 вечера и могу попробовать еще раз в то время, которое вы укажете, если это не подходящее время для вас. Заинтересован, но немного занят. :) - person enhzflep; 20.08.2014
comment
Не торопись. Я провел небольшое собственное исследование и думаю, что вы использовали алгоритм Ву (8x8 или 8x4? Я не уверен...). Я попытался нарисовать метафайл в растровое изображение в 8 раз больше, а затем уменьшить его с помощью StretchBlt и добился тех же результатов. Проблема возникает при максимизации окна, изображение не появляется! Я думаю, это потому, что растровое изображение не может быть выделено (слишком большое). Я заметил значительное снижение производительности в моем случае (немного то же самое в вашем), но это должно быть из-за слишком большого использования памяти... Увидимся в 8 часов вечера по австралийскому времени (то есть в 12 часов дня в моем часовом поясе). - person AlwaysLearningNewStuff; 20.08.2014
comment
Я буду в нашем чате, который указан в вашем комментарии вверху этого поста (Давайте продолжим обсуждение в чате). Надеюсь, вам удастся быть там в 20:00 по австралийскому времени (если нет, не проблема, поговорим в другой раз). Еще раз спасибо и всего наилучшего до тех пор! - person AlwaysLearningNewStuff; 20.08.2014
comment
Прошу прощения за беспокойство, не могли бы вы взглянуть на с этой проблемой я сталкивался? Я надеюсь, что вы найдете его решение полезным. С наилучшими пожеланиями :) - person AlwaysLearningNewStuff; 17.09.2014
comment
Большое спасибо! Но будьте осторожны: это может быть очень сложно! Пожалуйста, внимательно прочитайте вопрос (я также разместил его на CP). Я на 100% уверен, что вы найдете это полезным. С наилучшими пожеланиями. - person AlwaysLearningNewStuff; 17.09.2014
comment
Удовольствие мое. Я уверен, что здесь есть полезный трюк, чтобы я мог припрятать его где-нибудь до другого раза. Здоровья мой друг. :) - person enhzflep; 17.09.2014