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

由于WinAPI仅支持EMF文件作为 vector 格式,因此我已使用Inkscape将文件转换为EMF。我的图形设计技能是初学者,但是我已经成功地将SVG文件转换为EMF。但是,结果看起来不像原始结果,可以这么说是“精确”的。

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

要准确了解我的意思,请下载here以及我制作的ojit_a和SVG以及EMF。只需单击5个黄色星星上方的Download:test.rar(请参见下图)。

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

1)在PNG中创建默认的Win32 project(我使用VS 2008,但这不是问题);

2)像这样重写Visual Studio:

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)将窗口调整为最小尺寸,然后将其最大化。

请注意,窗口越大,图像质量越好,但是窗口越小,图像获得的“精度就越差”。我在WM_PAINT上进行了测试。

我要求您的帮助来获得与原始Windows XP相同的EMF文件图形质量。

感谢您的时间和精力。最好的祝福。

最佳答案

解决了!

该解决方案非常重要,即使不是我提交的所有解决方案都多余。因此,我决定用此替代它。

需要考虑很多因素,并采用许多概念来获得理想的结果。这些包括(不分先后)

  • 需要设置一个与背景紧密匹配的maskColour,同时它也不会出现在最终的计算图像中。跨越透明/不透明区域之间边界的像素是该点处 mask 和EMF颜色的混合值。
  • 需要选择适合源图像的缩放比例-在此图像和我使用的代码的情况下,我选择了8。这意味着我们将这个特定的EMF绘制为大约一个百万像素,即使目的地可能在大约85k像素附近。
  • 由于GDI拉伸(stretch)/绘图功能忽略了该通道,因此需要手动设置生成的32位HBITMAP的Alpha通道,但是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);
    }
    

    最后,这是一个使用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));

    关于c++ - 当窗口缩小时,EMF质量会下降,但当窗口尺寸较大时,EMF质量会下降,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/25143649/

    10-10 18:39