如何将一个矩形缩放到另一个矩形(例如将图片缩放到 window),同时保留纵横比和填充选项?

How do I scale a rectangle to another rectangle (such as a picture to a window) preserving aspect ratio with the option to fill?

我把它放在这里是因为这样做的算法比它应该的更难找到。希望 Google 会缓存这个。

问题是:您有一个位图和一个 window。您想要在 window 内绘制位图,填充 window,并在 window 调整大小时保持纵横比。

您可能还希望能够以其他方式适应它,这样您就可以绘制图像 "over" window,并且 window 中的所有区域都会变得充实。这将剪掉一些图像。我在答案中提出了一个简单的算法。

做两个RECT。一个是你希望适应的window(传递给rcScreen),另一个是图片的尺寸:

(pseudo-code)
RECT window;
GetClientRect(hwnd,&window)
RECT bitmap_rect;
BITMAP bitmap;
bitmap_rect.left = bitmap_rect.top = 0;
bitmap_rect.right = bitmap.bmWidth;
bitmap_rect.bottom = bitmap.bmHeight;
RECT draw_rect = size_rect(window,bitmap_rect,true,true);

然后拉伸它:

StretchBlt(toDC, draw_rect.left, draw_rect.top, draw_rect.right, draw_rect.bottom, fromDC, 0, 0, bitmap.bmWidth, bitmap.bmHeight, SRCCOPY);

这是函数:(注意不存在 bCenter = false 和 Scale = true 的情况)。 **bCenter is flag for "center picture in window." Scale is flag for "pan and scan mode" instead of "letterbox," 如果您将图像用作 window 背景,但您不想调整大小想要有信箱。 **

RECT size_rect(RECT& rcScreen,
    RECT& sizePicture,
    bool bCenter,
    bool Scale)
{
    RECT rect = rcScreen;
    double dWidth = rcScreen.right - rcScreen.left;
    double dHeight = rcScreen.bottom - rcScreen.top;
    double dAspectRatio = dWidth / dHeight;

    double dPictureWidth = sizePicture.right - sizePicture.left;
    double dPictureHeight = sizePicture.bottom - sizePicture.top;
    double dPictureAspectRatio = dPictureWidth / dPictureHeight;

    double nNewHeight = dHeight;
    double nNewWidth = dWidth;
    double nHeightCenteringFactor = 0;
    double nWidthCenteringFactor = 0;

    double xstart = rcScreen.left;
    double ystart = rcScreen.top;

    if (dPictureAspectRatio > dAspectRatio)
    {
        if (bCenter && Scale) {
            nNewWidth = dPictureWidth*(1 / (dPictureHeight / dHeight));
            xstart = rcScreen.left - ((nNewWidth / 2) - (dWidth / 2));
        }
        else {
            nNewHeight = (int)(dWidth / dPictureWidth*dPictureHeight);
            if (bCenter)
                ystart = ((dHeight - nNewHeight) / 2) + rcScreen.top;
        }

    }
    else if (dPictureAspectRatio < dAspectRatio)
    {
        if (bCenter && Scale) {
            nNewHeight = dPictureHeight*(1 / (dPictureWidth / dWidth));
            ystart = rcScreen.top - ((nNewHeight / 2) - (dHeight / 2));
        }
        else{
            nNewWidth = (dHeight / dPictureHeight*dPictureWidth);

            if (bCenter)
                xstart = ((dWidth - nNewWidth) / 2) + rcScreen.left;
        }
    }
    SetRect(&rect, xstart, ystart, nNewWidth, nNewHeight);
    return rect;
}

这是一个仅使用整数数学的实现。

该算法首先拉伸两个维度,保持纵横比。计算新的大小,假设各自的其他维度占据整个space。在这些新尺寸中,超过可用区域的尺寸被设置为最大可能值,而另一个尺寸被缩小,保持纵横比。 (对于 pan 和 scanbScale 设置为 true)模式,不超过可用 space设置为占据整个范围。)

(注意:如果sizePicture是一个空的矩形,这个函数returns一个矩形向左拉伸一个像素,向上拉伸一个像素,或者从原点, 或中心。)

RECT size_rect( RECT& rcScreen,
                RECT& sizePicture,
                bool bCenter/*,
                bool bScale*/ ) {

    int clientWidth = rcScreen.right - rcScreen.left;
    int clientHeight = rcScreen.bottom - rcScreen.top;
    int picWidth = sizePicture.right - sizePicture.left;
    int picHeight = sizePicture.bottom - sizePicture.top;
    // Calculate new content size
    int contentWidth = ::MulDiv( clientHeight, picWidth, picHeight );
    int contentHeight = ::MulDiv( clientWidth, picHeight, picWidth );

    // Adjust dimensions to fit inside client area
    if ( contentWidth > clientWidth ) {
    // To use the bScale parameter that allows the image to fill the entire
    // client area, use the following if-clause instead.
    //if (    ( bScale && ( contentWidth < clientWidth ) )
    //     || ( !bScale && ( contentWidth > clientWidth ) ) ) {
        contentWidth = clientWidth;
        contentHeight = ::MulDiv( contentWidth, picHeight, picWidth );
    } else {
        contentHeight = clientHeight;
        contentWidth = ::MulDiv( contentHeight, picWidth, picHeight );
    }

    RECT rect = { 0 };
    ::SetRect( &rect, 0, 0, contentWidth, contentHeight );
    if ( bCenter ) {
        // Calculate offsets to center content
        int offsetX = ( clientWidth - contentWidth ) / 2;
        int offsetY = ( clientHeight - contentHeight ) / 2;
        ::OffsetRect( &rect, offsetX, offsetY );
    }
    return rect;
}