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

时间:2022-08-27 10:46:26

I'm putting this here because the algorithm for doing this is more difficult to find than it should be. Hopefully Google will cache this.

我把它放在这里是因为这样做的算法比它应该更难找到。希望谷歌能缓存此问题。

The problem is: you have a bitmap and a window. You want to draw the bitmap inside a window, filling the window, keeping the aspect ratio, as the window resizes.

问题是:你有一个位图和一个窗口。您希望在窗口调整大小时在窗口内绘制位图,填充窗口,保持纵横比。

You may also want to be able to fit it the other way, so that you can draw the image "over" the window, and all the area in the window will be filled. This will clip out some of the image. I present in the answer a simple algorithm for doing so.

您可能还希望能够以其他方式适应它,以便您可以在窗口“上方”绘制图像,并且窗口中的所有区域都将被填充。这将剪掉一些图像。我在答案中提出了一个简单的算法。

2 个解决方案

#1


1  

Here's an implementation that uses integer math only.

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

The algorithm first stretches both dimensions, preserving aspect ratio. The new size is calculated, assuming that the respective other dimension occupies the entire space. Of these new dimensions, the one that overshoots the available area is set to the maximum possible value, while the other is scaled back, preserving aspect ratio. (For pan and scan (bScale is set to true) mode, the dimension that doesn't overshoot the available space is set to occupy the entire range.)

该算法首先拉伸两个维度,保留纵横比。假设相应的其他维度占据整个空间,计算新的大小。在这些新尺寸中,超出可用区域的尺寸被设置为最大可能值,而另一个尺寸被缩小,保持纵横比。 (对于平移和扫描(bScale设置为true)模式,不会超出可用空间的维度将设置为占据整个范围。)

(Note: If sizePicture is an empty rectangle, this function returns a rectangle that stretches one pixel to the left and one pixel up, either from the origin, or the center.)

(注意:如果sizePicture是一个空矩形,则此函数返回一个矩形,从原点或中心向上拉伸一个像素,向上拉伸一个像素。)

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

#2


0  

Make two RECT. One is the window you wish to fit to (passed into rcScreen), and the other holds the dimensions of the picture:

制作两个RECT。一个是您希望适合的窗口(传递到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);

Then StretchBlt it:

然后StretchBlt它:

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

This is the function: (note there is no case for bCenter = false and Scale = true). **bCenter is flag for "center picture in window." Scale is flag for "pan and scan mode" instead of "letterbox," useful if you are using an image as a window background that you want resized but don't want to have letterboxes. **

这是函数:(注意bCenter = false和Scale = true没有情况)。 ** bCenter是“窗口*图片”的标志。缩放是“平移和扫描模式”而不是“信箱”的标志,如果您将图像用作要调整大小但不想拥有信箱的窗口背景,则非常有用。 **

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

#1


1  

Here's an implementation that uses integer math only.

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

The algorithm first stretches both dimensions, preserving aspect ratio. The new size is calculated, assuming that the respective other dimension occupies the entire space. Of these new dimensions, the one that overshoots the available area is set to the maximum possible value, while the other is scaled back, preserving aspect ratio. (For pan and scan (bScale is set to true) mode, the dimension that doesn't overshoot the available space is set to occupy the entire range.)

该算法首先拉伸两个维度,保留纵横比。假设相应的其他维度占据整个空间,计算新的大小。在这些新尺寸中,超出可用区域的尺寸被设置为最大可能值,而另一个尺寸被缩小,保持纵横比。 (对于平移和扫描(bScale设置为true)模式,不会超出可用空间的维度将设置为占据整个范围。)

(Note: If sizePicture is an empty rectangle, this function returns a rectangle that stretches one pixel to the left and one pixel up, either from the origin, or the center.)

(注意:如果sizePicture是一个空矩形,则此函数返回一个矩形,从原点或中心向上拉伸一个像素,向上拉伸一个像素。)

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

#2


0  

Make two RECT. One is the window you wish to fit to (passed into rcScreen), and the other holds the dimensions of the picture:

制作两个RECT。一个是您希望适合的窗口(传递到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);

Then StretchBlt it:

然后StretchBlt它:

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

This is the function: (note there is no case for bCenter = false and Scale = true). **bCenter is flag for "center picture in window." Scale is flag for "pan and scan mode" instead of "letterbox," useful if you are using an image as a window background that you want resized but don't want to have letterboxes. **

这是函数:(注意bCenter = false和Scale = true没有情况)。 ** bCenter是“窗口*图片”的标志。缩放是“平移和扫描模式”而不是“信箱”的标志,如果您将图像用作要调整大小但不想拥有信箱的窗口背景,则非常有用。 **

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