3

My end goal is to fill an arbitrarily sized rectangle with an NSImage. I want to:

  1. Fill the entire rectangle
  2. Preserve the aspect ratio of the image
  3. Show as much as possible of the image while maintaining 1) and 2)
  4. When not all the image can be shown, crop toward the center.

This demonstrates what I'm trying to do. The original image of the boat at the top is drawn into various sized rectangles below.

enter image description here

Okay, so far so good. I added a category to NSImage to do this.

@implementation NSImage (Fill)

/**
 * Crops source to best fit the destination
 *
 * destRect is the rect in which we want to draw the image
 * sourceRect is the rect of the image
 */
-(NSRect)scaleAspectFillRect:(NSRect)destRect fromRect:(NSRect)sourceRect
{
    NSSize sourceSize = sourceRect.size;
    NSSize destSize = destRect.size;

    CGFloat sourceAspect = sourceSize.width / sourceSize.height;
    CGFloat destAspect = destSize.width / destSize.height;

    NSRect cropRect = NSZeroRect;

    if (sourceAspect > destAspect) { // source is proportionally wider than dest
        cropRect.size.height = sourceSize.height;
        cropRect.size.width = cropRect.size.height * destAspect;
        cropRect.origin.x = (sourceSize.width - cropRect.size.width) / 2;        
    } else { // dest is proportionally wider than source (or they are equal)
        cropRect.size.width = sourceSize.width;
        cropRect.size.height = cropRect.size.width / destAspect;
        cropRect.origin.y = (sourceSize.height - cropRect.size.height) / 2;
    }

    return cropRect;
}

- (void)drawScaledAspectFilledInRect:(NSRect)rect
{
    NSRect imageRect = NSMakeRect(0, 0, [self size].width, [self size].height);
    NSRect sourceRect = [self scaleAspectFillRect:rect fromRect:imageRect];

    [[NSGraphicsContext currentContext]
         setImageInterpolation:NSImageInterpolationHigh];

    [self drawInRect:rect
            fromRect:sourceRect
           operation:NSCompositeSourceOver
            fraction:1.0 respectFlipped:YES hints:nil];
}

@end

When I want to draw the image into a certain rectangle I call:

[myImage drawScaledAspectFilledInRect:onScreenRect];

Works really well except for one problem. At certain sizes the image looks quite blurry:

enter image description here

My first thought was that I need to draw on integral pixels, so I used NSIntegralRect() before drawing. No luck.

As I thought about it I figured that it's probably a result of the interpolation. To draw from the larger image to the smaller draw rect NSImage has to interpolate. The blurry images are likely just a case where the values don't map very well and we end up with some undesirable artifacts that can't be avoided.

So, the question is this: How do I choose an optimal rect that avoids those artifacts? I can adjust either the draw rect or the crop rect before drawing to avoid this, but I don't know how or when to adjust them.

Ben Dolman
  • 3,165
  • 3
  • 25
  • 25

0 Answers0