为 drawInRect 按比例缩放和旋转 NSImage

Proportionally scale and rotate NSImage for drawInRect

我正在尝试绘制按比例缩放并旋转 90 度的 NSImage,但不断得到裁剪后的结果图像。

在 NSView 子类中,这是我目前所做的:

- (void) drawRect:(NSRect)dirtyRect
{
    [super drawRect:dirtyRect];

    NSImage* image = /* get image */;

    CGRect drawRect = dirtyRect;
    CGRect imageRect = [self proportionallyScale:image.size toSize:drawRect.size];

    NSAffineTransform* rotation = [[NSAffineTransform alloc] init];
    [rotation translateXBy:NSWidth(drawRect) / 2 yBy:NSHeight(drawRect) / 2];
    [rotation rotateByDegrees:90];
    [rotation translateXBy:-NSWidth(drawRect) / 2 yBy:-NSHeight(drawRect) / 2];

    NSGraphicsContext* context = [NSGraphicsContext currentContext];
    [context saveGraphicsState];
    [rotation concat];
    [image drawInRect:imageRect fromRect:NSZeroRect operation:NSCompositeCopy fraction:1.0];
    [context restoreGraphicsState];
}

以及缩放逻辑:

- (CGRect) proportionallyScale:(CGSize)fromSize toSize:(CGSize)toSize
{
    CGPoint origin = CGPointZero;
    CGFloat width = fromSize.width, height = fromSize.height;
    CGFloat targetWidth = toSize.width, targetHeight = toSize.height;

    float widthFactor = targetWidth / width;
    float heightFactor = targetHeight / height;

    CGFloat scaleFactor = std::min(widthFactor, heightFactor);

    CGFloat scaledWidth = width * scaleFactor;
    CGFloat scaledHeight = height * scaleFactor;

    if (widthFactor < heightFactor)
        origin.y = (targetHeight - scaledHeight) / 2.0;
    else if (widthFactor > heightFactor)
        origin.x = (targetWidth - scaledWidth) / 2.0;
    return {origin, {scaledWidth, scaledHeight}};
}

这是没有应用旋转的结果(图像不是正方形,呈现红色背景以显示视图所在的位置):

当我通过启用 [rotation concat]:

应用转换时

问题是图像的缩放及其偏移量应该相对于旋转的视口大小而不是输入视图边界来计算。但是我似乎无法想出正确计算这个的逻辑。

任何人都可以帮助正确计算缩放和旋转图像的正确图像绘制矩形的逻辑吗?或者是否有更好的方法来解决整个问题?

精简版

添加:

imageRect.origin.x += NSWidth(drawRect) / 2 - NSHeight(drawRect) / 2;
imageRect.origin.y += NSHeight(drawRect) / 2 - NSWidth(drawRect) / 2;

drawRect

中调用 proportionallyScale 之后

长版

我分两步进行:对图像进行定界和缩放,然后在视口内重新定位。如果你想让所有的人都喜欢,你可以把它们结合起来。我认为这应该适用于各种视图大小。

让这成为我们的原始观点:

********************
*                  *
*       View       * 6
*                  *
*                  *
X*******************
        20

左下角的原点标记为X

假设图像为 6 x 8(旋转前)。

第一部分很简单,确定旋转后我们应该绘制多大的图像。只需将翻转的尺寸传递给 proportionallyScale:

CGRect imageRect = [self
    proportionallyScale:image.size
    toSize:CGSizeMake(
         drawRect.size.height, // 6
         drawRect.size.width)  // 20
];

这会产生一个矩形:

imageRect = { origin=(x=0, y=6) size=(width=6, height=8) };

但这目前没有多大意义:

  6
$$$$$$ 
$    $
$    $
$ IM $ 8
$    $
$    $
$    $
X$$$$$**************
*                  *
*       View       * 6
*                  *
*                  *
X*******************
         20

然后将视图绕中心顺时针旋转90度:

                        8
               X++++X$$$$$$$$       -
               +    +       $       |
Rot'd View >-- +    $  IM   $       |
               +    +       $ 6     | 7
               +    $       $       |
               +    +$$$$$$$$       |
        *******+****+*******        -
        * VP   +    +      *        |
        *      +    +      *        | 6
        *      +    +      *        |
        *      +    +      *        |
        X******+****+*******        -
               +    +               |
               +    +               |
               +    +               |
               +    +               | 7
               +    +               |
               ++++++               -

        |------|----|------|
           7     6     7

VP 是原始视图的位置。我们需要将原点从旋转 space 转换为原始视口 space.

这个变换可以计算为:

// note that drawRect hasn't been flipped here
CGFloat transX = NSWidth(drawRect) / 2 - NSHeight(drawRect) / 2 == 7;
CGFloat transY = NSHeight(drawRect) / 2 - NSWidth(drawRect) / 2 == -7

因此我们将变换应用于当前图像:

imageRect.origin.x += transX;
imageRect.origin.y += transY;

此转换产生:

               X++++X
               +    +
               +    +
               +    +
               +    +
               +    +
        ******$+$$$$+$******
        * VP  $+    +$     *
        *     $+    +$     *
        *     $+    +$     *
        *     $+    +$     * 
        X*****$+$$$$+$******
               +    +
               +    +
               +    + 
               +    +
               +    +
               ++++++

现在一切都很好地定位和缩放:

           6     8      6
        |-----|------|-----|

        ******$$$$$$$$******  -
        *     $      $     *  |
        *     $      $     *  | 6
        *     $      $     *  |
        *     $      $     *  |
        X*****$$$$$$$$******  -

至少在下一个黑暗邪恶力量到来之前...