【问题标题】:EMF quality diminishes when window is shrinked, but is good when window dimensions are high当窗口缩小时,EMF 质量会降低,但在窗口尺寸较大时,EMF 质量会很好
【发布时间】:2014-09-28 09:36:58
【问题描述】:

我正在使用C++pure WinApi 创建桌面应用程序。我需要显示以SVG 形式提供给我的图像。

由于WinAPI 仅支持EMF 文件作为矢量格式,我使用Inkscape 将文件转换为EMF。我的图形设计技能处于初级水平,但我成功地将SVG 文件转换为EMF。但是,结果看起来不像原来的那样,可以说是不那么“精确”。

如果我将SVG 导出为PNG 并用GDI+ 显示,结果与原始文件相同。不幸的是,我需要矢量格式。

要确切了解我的意思,请下载我制作的 SVGEMFPNG here。只需点击Download:test.rar上面5个黄色星星(见下图)。

以下是创建重现问题的最小应用程序的说明:

1) 在Visual Studio 中创建默认Win32 project(我用的是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_PAINT 下方为WM_SIZEWM_ERASEBKGND 添加以下处理程序:

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

4) 将窗口调整为尽可能小的尺寸,然后将其最大化。

请注意,窗口越大,图像质量越好,但窗口越小,图像就越“不精确”。我在Windows XP 上对此进行了测试。

我请求您帮助获得与原始 SVG 相同的 EMF 文件的图形质量。

感谢您的时间和努力。最好的问候。

【问题讨论】:

  • 从问题标题中想到可能是你。 :D 我记得那张图片(还有那张地图)。我现在要睡觉了,虽然如果你还没有看到这个问题,这似乎提供了一些很好的提示:stackoverflow.com/questions/1783130/draw-emf-antialiased - 特别是 MSOffice 采用的假定方法 - 以 2 倍的大小绘制然后下采样 - 你也许可以如果使用 gdi+ 将大结果缩放到所需大小,则利用抗锯齿功能 - 此外,emf 和 svg 上传似乎已损坏 - 我得到每个 371 字节的文本响应(每个尝试两次)。问候,S. :)
  • @enhzflep:我相信该链接现在已修复(将所有文件上传为.rar),感谢您的帮助。我不想打扰你,因为我相信每个人都应该在工作中好好休息(显然除了我 :smile: )。 以 2 倍的大小绘制然后下采样Super User 处得到了类似的建议(但他们说在 Inkscape 中这样做)但没有运气......也许你可以尝试一下在即将到来的周末(我不想打扰你)?谢谢,晚安:)
  • @enhzflep: This disscussion 似乎与我有关。有空的时候请看一下,告诉我你的想法,好吗?
  • 看起来在超级用户的那个线程中有一些优点。我注意到mpy 所做的关于转换为 emf 的 cmets 很好,尤其是关于使用的缩放算法的评论。我试图以 rclBounds(而不是 rclFrame)中指定的大小绘制 emf,然后使用 StretchBlt 来调整大小 - 结果很糟糕。然后我将 SetStretchBltMode 与 HALFTONE 一起使用,得到了与 png 几乎像素相同的结果。仍在熨烫徽标周围的透明度。我从这次会议回来后会再看一眼。 :)

标签: c++ winapi svg .emf


【解决方案1】:

解决了!

如果不是我提交的所有解决方案,该解决方案会产生很多冗余。因此,我决定用这个替换它。

要获得所需的结果,需要考虑许多因素和一些概念。这些包括(不分先后)

  • 需要设置一个与背景非常匹配的 maskColour,同时也不存在于最终计算的图像中。跨越透明/不透明区域边界的像素是蒙版和该点 EMF 颜色的混合值。
  • 需要选择适合源图像的缩放率 - 在此图像和我使用的代码的情况下,我选择了 8。这意味着我们正在以大约 1 百万像素绘制这个特定的 EMF,即使目的地可能在大约 85k 像素附近。
  • 需要手动设置生成的 32 位 HBITMAP 的 Alpha 通道,因为 GDI 拉伸/绘图函数忽略此通道,但 AlphaBlend 函数要求它们准确无误。

我还注意到,每次刷新屏幕时,我都使用旧代码手动绘制背景。更好的方法是创建一个 patternBrush 一次,然后使用 FillRect 函数简单地复制它。这比用纯色填充矩形然后在顶部绘制线条要快得多。我懒得重写那部分代码,不过我会包含一个我过去在其他项目中使用过的 sn-p 以供参考。

这是我从下面的代码中得到的结果的几张照片:

这是我用来实现它的代码:

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

最后,这是一个使用 FillRect 函数创建用于填充背景的 patternBrush 的示例。这种方法适用于任何可平铺的背景。

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

【讨论】:

  • 我遇到了一个问题,因为 Visual Studio 2008 找不到 #include&lt;stdint.h&gt;,但通过搜索 SO 设法解决了这个问题。代码有效,调整窗口大小后图片一致,因此我已正式接受该解决方案(我之前已投票)。我们可以“在聊天中见面”(当你有空闲时间),所以我可以“当面”问你一些问题(他们在阅读你的答案后弹出)?如果有兴趣,请在适合您的时间和日期给我留言(我保证不会让您感到厌烦,而且我认为这些问题足够聪明,可以吸引您的注意力)。最好的问候!
  • 嗨!这是艰难的一周。哦,是的,很抱歉使用 gcc 并没有意识到 uintX_t 类型在 VS 中不可用。很高兴听到您发现结果可以接受 - 这是一项有趣且有趣的问题解决任务。 (很像 code-golf StackExchange 网站上的许多内容)。快速检查告诉我,这里的下午 3.57 = 上午 7.57(我认为是)您所在的世界。 - 我们目前使用 GMT+10 作为我们的时间。我会在今晚 8 点左右检查聊天室,如果这对你来说不是一个好时间,我可以在你指定的时间再试一次。有兴趣,但有点忙。 :)
  • 慢慢来。我自己做了一些研究,我认为您使用了 Wu 算法(8x8 还是 8x4?我不确定...)。我尝试将元文件绘制成 8 倍大的位图,然后使用 StretchBlt 将其缩小,并取得了相同的结果。最大化窗口时出现问题,图像不显示!我猜这是因为无法分配位图(太大)。在我的情况下,我注意到显着的性能开销(在你的情况下略有相同),但这一定是因为内存使用量过多......在澳大利亚时间晚上 8 点见(在我的时区是下午 12 点)。跨度>
  • 我将参加我们的聊天,也就是您在这篇文章顶部的评论中的那个(让我们在聊天中继续讨论)。希望你能在澳大利亚时间晚上 8 点到达那里(如果没有,没问题,我们将在其他时间讨论)。再次感谢并致以最诚挚的问候!
  • 不好意思打扰了,你能看看我遇到过的this problem吗?我希望您会发现它的解决方案很有用。最好的问候:)
猜你喜欢
  • 2016-04-07
  • 1970-01-01
  • 1970-01-01
  • 2016-09-16
  • 1970-01-01
  • 1970-01-01
  • 2017-02-15
  • 2011-02-20
  • 2016-07-04
相关资源
最近更新 更多