Flip NSImage on both axes
Asked Answered
D

4

9

I'm trying to flip an NSImage created with a NSImageBitmapRep representation. After some digging (Flipping Quicktime preview & capture and Mirroring CIImage/NSImage) I tried two ways via a CIImage and applying a scaling transformation with -1 for both factors.

First using CIImage imageByApplyingTransform:

    NSBitmapImageRep *imgRep = ...
    CGImageRef cgi = [imgRep CGImage];
    CIImage *cii = [CIImage imageWithCGImage:cgi];
    CGAffineTransform at = CGAffineTransformTranslate(CGAffineTransformMakeScale(-1, -1), 0, 0);
    NSCIImageRep *ciiRep = [NSCIImageRep imageRepWithCIImage:[cii imageByApplyingTransform:at]];

    NSImage *img = [[[NSImage alloc] init] autorelease];
    [img addRepresentation:ciiRep];
    [self.ivImage setImage:img];

then using a CIAffineTransform filter:

    NSBitmapImageRep *imgRep = ...
    CGImageRef cgi = [imgRep CGImage];
    CIImage *cii = [CIImage imageWithCGImage:cgi];
    CIFilter *f = [CIFilter filterWithName:@"CIAffineTransform"];
    NSAffineTransform *t = [NSAffineTransform transform];
    [t scaleXBy:1.0 yBy:1.0];
    //[t translateXBy:width yBy:0];
    [f setValue:t forKey:@"inputTransform"];
    [f setValue:cii forKey:@"inputImage"];
    CIImage *cii2 = [f valueForKey:@"outputImage"];
    NSCIImageRep *ciiRep = [NSCIImageRep imageRepWithCIImage:cii2];

    NSImage *img = [[[NSImage alloc] init] autorelease];
    [img addRepresentation:ciiRep];
    [self.ivImage setImage:img];

Both ways produce a blank image. I've also tried to translate the image, in case it is out of screen because of the -1 scalings, but to no avail. If I use positive values for scaling I can see that the transformation is applied correctly.

self.ivImage is an NSImageView. I want an actual NSImage which is flipped, so applying a transformation to the NSImageView is not an option.

This is 32bits, Xcode 4.3.2 on Lion.

Dogmatize answered 7/6, 2012 at 17:12 Comment(0)
A
10

You should init your NSImage with a size.

Show your attempt with translation, because that's the right way. Typically, it's easiest to translate first and then scale. Your code snippets seem to have vestigial traces of attempts to translate, but they're not right. You translate by 0,0 in one case and width,0 in another. Also, in your second code snippet, you're scaling by 1,1 (positive), so not flipping.

Also, it may be simpler to simply lock focus on a new image of the appropriate size, set up the transform, and draw the image rep. Avoids all of that stuff with CIImage.

NSBitmapImageRep *imgRep = ...
NSImage* img = [[[NSImage alloc] initWithSize:NSMakeSize(imgRep.pixelsWide, imgRep.pixelsHigh)] autorelease];
[img lockFocus];
NSAffineTransform* t = [NSAffineTransform transform];
[t translateXBy:imgRep.pixelsWide yBy:imgRep.pixelsHigh];
[t scaleXBy:-1 yBy:-1];
[t concat];
[imgRep drawInRect:NSMakeRect(0, 0, imgRep.pixelsWide, imgRep.pixelsHigh)];
[img unlockFocus];
Aeschylus answered 8/6, 2012 at 8:47 Comment(1)
Thanks, that worked right out of the box! :) And it's much cleaner without the detour via CIImage! My trials with translation and positive scaling were left to test whether the transformation was applied at all.Dogmatize
P
8

Anyway, I think a good idea should be posting a complete function. Here is my solution based on Ken post

- (NSImage *)flipImage:(NSImage *)image
{
    NSImage *existingImage = image;
    NSSize existingSize = [existingImage size];
    NSSize newSize = NSMakeSize(existingSize.width, existingSize.height);
    NSImage *flipedImage = [[[NSImage alloc] initWithSize:newSize] autorelease];

    [flipedImage lockFocus];
    [[NSGraphicsContext currentContext] setImageInterpolation:NSImageInterpolationHigh];

    NSAffineTransform *t = [NSAffineTransform transform];
    [t translateXBy:existingSize.width yBy:existingSize.height];
    [t scaleXBy:-1 yBy:-1];
    [t concat];

    [existingImage drawAtPoint:NSZeroPoint fromRect:NSMakeRect(0, 0, newSize.width, newSize.height) operation:NSCompositeSourceOver fraction:1.0];

    [flipedImage unlockFocus];

    return flipedImage;
}
Permissible answered 10/12, 2012 at 8:24 Comment(0)
G
7

Extension for Swift, based on the solution of Alejandro Lugeno:

extension NSImage {
    func flipped(flipHorizontally: Bool = false, flipVertically: Bool = false) -> NSImage {
        let flippedImage = NSImage(size: size)

        flippedImage.lockFocus()

        NSGraphicsContext.current?.imageInterpolation = .high

        let transform = NSAffineTransform()
        transform.translateX(by: flipHorizontally ? size.width : 0, yBy: flipVertically ? size.height : 0)
        transform.scaleX(by: flipHorizontally ? -1 : 1, yBy: flipVertically ? -1 : 1)
        transform.concat()

        draw(at: .zero, from: NSRect(origin: .zero, size: size), operation: .sourceOver, fraction: 1)

        flippedImage.unlockFocus()

        return flippedImage
    }
}
Generalize answered 31/3, 2019 at 22:37 Comment(3)
Where is size defined? You are not passing it in.Eads
@Eads NSImage has a size property: developer.apple.com/documentation/appkit/nsimage/1519987-size Since the function is in an NSImage extension, it doesn't have to be passed in.Phillane
To put it simply: size here is a shorter form of self.size.Phillane
D
5

for vertical flip

- (NSImage *)flipImageVertically:(NSImage *)inputImage {

    NSImage *tmpImage;
    NSAffineTransform *transform = [NSAffineTransform transform];

    NSSize dimensions = [inputImage size];
    NSAffineTransformStruct flip = {1.0, 0.0, 0.0, -1.0, 0.0,
        dimensions.height};
    tmpImage = [[NSImage alloc] initWithSize:dimensions];
    [tmpImage lockFocus];
    [transform setTransformStruct:flip];
    [transform concat];
    [inputImage drawAtPoint:NSMakePoint(0,0)
                         fromRect:NSMakeRect(0,0, dimensions.width, dimensions.height)
                        operation:NSCompositeCopy fraction:1.0];
    [tmpImage unlockFocus];


    return [tmpImage autorelease];

}

for horizontal flip

+ (NSImage *)flipImageHorizontally:(NSImage *)inputImage {

    NSImage *tmpImage;
    NSAffineTransform *transform = [NSAffineTransform transform];

    NSSize dimensions = [inputImage size];
    NSAffineTransformStruct flip = {-1.0, 0.0, 0.0, 1.0,
        dimensions.width, 0.0 };
    tmpImage = [[NSImage alloc] initWithSize:dimensions];
    [tmpImage lockFocus];
    [transform setTransformStruct:flip];
    [transform concat];
    [inputImage drawAtPoint:NSMakePoint(0,0)
                         fromRect:NSMakeRect(0,0, dimensions.width, dimensions.height)
                        operation:NSCompositeCopy fraction:1.0];
    [tmpImage unlockFocus];

    return [tmpImage autorelease];

}
Discoverer answered 6/4, 2016 at 12:23 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.