Saving entire NSView contents, which is inside an NSScrollView - objective-c

I'm trying to save contents of an NSView as an image, but, only the image visible inside the scrollview is saved.
Here is the actual image loaded in the view:
.
But this is how I'm able to save the image:
Is there any way to save the entire image in the NSView?
This is how I'm saving my NSView subclass:
- (void)save
{
[self lockFocus];
NSBitmapImageRep* rep = [[NSBitmapImageRep alloc] initWithFocusedViewRect:[self bounds]];
[self unlockFocus];
NSData* data = [rep representationUsingType:NSPNGFileType properties:nil];
NSString* string = NSHomeDirectory();
NSString* pth1 = [string stringByAppendingPathComponent:#"ttx.png"];
[data writeToFile:pth1 atomically:YES];
}
This method is in the View i want to save.

Use -[NSView cacheDisplayInRect:toBitmapImageRep:]
NSBitmapImageRep* rep = [self bitmapImageRepForCachingDisplayInRect:self.bounds];
[self cacheDisplayInRect:self.bounds toBitmapImageRep:rep];

Related

the image of NSTextAttachment is flipped

NSTextAttachment *attachment = [[NSTextAttachment alloc] init];
NSImage *image = [NSImage imageNamed:#"emotion"];;
attachment.image = image;
NSAttributedString *attributedString = [NSAttributedString attributedStringWithAttachment: attachment];
[[_feedbackContent textStorage] appendAttributedString:attributedString];
after add the image to NSTextAttachment, it is vertical flipped. Anybody know how to resolve this issue.
NSTextAttachment seems to use NSFileWrapper filename to get the UTI and has different behaviour based on the UTI.
I was able to fix it with using NSFileWrapper instead:
NSFileWrapper *fileWrapper = [[NSFileWrapper alloc] initRegularFileWithContents:data];
// Without a filename (which is used to get the UTI by NSTextAttachment)
// the image is displayed flipped.
[fileWrapper setPreferredFilename:#"Attachment.png"];
NSTextAttachment *attachment = [[NSTextAttachment alloc] initWithFileWrapper:fileWrapper];
You could also try to set the fileType property to kUTTypePNG or other image type to get it working.
radar://47170950
Assign the image to an NSTextAttachmentCell, not the NSTextAttachment.
id <NSTextAttachmentCell> cell = [[NSTextAttachmentCell alloc] initImageCell:image];
NSTextAttachment *attachment = [[NSTextAttachment alloc] initWithData:nil ofType:nil];
[attachment setAttachmentCell:cell];
When drawing into a flipped view (which is why the image gets flipped), the only workaround I've found is to create a flipped image so it's then drawn the :
textAttachment.image = [NSImage imageWithSize:image.size flipped:YES drawingHandler:^BOOL(NSRect dstRect) {
[image drawInRect:dstRect fromRect:NSZeroRect operation:NSCompositeSourceOver fraction:1.0];
return YES;
}];

Objective-C: Uploading too many images memory pressure causing app to quit

I am using QBImagePicker to allow multiple image upload. It works fine for up to 25 images being downloaded, but more than that, and the app will quit do to memory pressure while uploading. I would like to allow infinite image upload, and am uncertain how to do so where memory would not be an issue (i.e. perhaps clearing memory after each save). Here is my method to save images (which is called from a loop within the main QBImagePickerController method to save all the selected images):
- (void) saveTheImage:(UIImage *)image fileName:(NSString *)name width:(CGFloat) width height:(CGFloat) height quality:(CGFloat) quality extension:(int)fileNumberExtension
{
UIImage *resizedImage = [self resizeImage:image width:width height:height]; //this is a simple method I have to resize the image sent from the picker
NSData *data = UIImageJPEGRepresentation(resizedImage, quality); //save as a jpeg
NSString *fileName = [NSString stringWithFormat:#"%#%d", name, fileNumberExtension]; //set the filename
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0]; //will be saved in documents
NSString *tempPath = [documentsDirectory stringByAppendingPathComponent:fileName]; //with the filename given
//create a block operation to save
NSBlockOperation* saveOp = [NSBlockOperation blockOperationWithBlock: ^{
[data writeToFile:tempPath atomically:YES];
}];
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperation:saveOp];
}
Thanks in advance!
EDIT
My method to resize the image:
- (UIImage *) resizeImage:(UIImage *)image width:(CGFloat) width height:(CGFloat) height
{
UIImage *resizedImage;
CGSize size = CGSizeMake(width, height);
UIGraphicsBeginImageContextWithOptions(size, NO, 0.0f);
[image drawInRect:CGRectMake(0, 0, width, height)];
resizedImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return resizedImage;
}
EDIT 2
Additional methods:
- (void) imagePickerController:(QBImagePickerController *)imagePickerController didSelectAssets:(NSArray *)assets
{
for (int i=0;i<assets.count;i++)
{
ALAssetRepresentation *rep = [[assets objectAtIndex:i] defaultRepresentation];
CGImageRef iref = [rep fullResolutionImage];
UIImage *pickedImage = [UIImage imageWithCGImage:iref scale:[rep scale] orientation:(UIImageOrientation)[rep orientation]];
int fileNumberExtension = [self getHighestImageNumber] + 1; //new images all have a higher file name
//set the ratio (width of image is 294)
CGFloat ratio = pickedImage.size.width / 294;
CGFloat newHeight = pickedImage.size.height / ratio;
if (newHeight < 430) //image is too wide
{
[self saveTheImage:pickedImage fileName:#"img" width:294 height:newHeight quality:0.8f extension:fileNumberExtension];
}
else //if the image is too narrow
{
//set the ratio (height of image is 430)
CGFloat ratio = pickedImage.size.height / 430;
CGFloat newWidth = pickedImage.size.width / ratio;
[self saveTheImage:pickedImage fileName:#"img" width:newWidth height:430 quality:0.8f extension:fileNumberExtension];
}
[self saveTheImage:pickedImage fileName:#"thm" width:78 height:78 quality:0.0f extension:fileNumberExtension]; //save the thumbnail
}
[self dismissImagePickerController];
}
- (void)dismissImagePickerController
{
[self dismissViewControllerAnimated:YES completion:nil];
}
- (void) addImageClicked
{
QBImagePickerController *imagePickerController = [[QBImagePickerController alloc] init];
imagePickerController.delegate = self;
imagePickerController.allowsMultipleSelection = YES;
imagePickerController.maximumNumberOfSelection = 20; //allow up to 20 photos at once
imagePickerController.filterType = QBImagePickerControllerFilterTypePhotos;
UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:imagePickerController];
[self presentViewController:navigationController animated:YES completion:nil];
}
Solved this issue by adding by using #autoreleasepool around my for loop in this method:
- (void) imagePickerController:(QBImagePickerController *)imagePickerController didSelectAssets:(NSArray *)assets
This thread was very useful.
You have a memory leak. Leaks usually don't happen because ARC takes care of it for you. (every time you finish using an image, it gets cleared from memory). However, NOT ALL objects are governed by ARC. There are some object types (like CGColorSpaceRef, etc.) that need to be freed manually.
You can check this by running Static Analysis in Xcode. In the top menu bar, select Product -> Analyze. If there are places where you need to free your objects, it will tell you.
To free an object, do:
CGColorSpaceRelease(ref); //where ref is a CGColorSpaceRef.
CGImageRelease(iref); //where iref is a CGImageRef.
or the corresponding method that pertains to your object.

NSButton with CALayer flips the image when clicked

I've got an NSButton with an attached CALayer and an image set on it. When I click the button, the image flips!
_zoomButton = [NSButton new];
[_zoomButton setBordered:NO];
[_zoomButton setButtonType:NSMomentaryChangeButton];
[_zoomButton setTitle:#""];
[_zoomButton setWantsLayer:YES];
[_zoomButton setImage:s_zoomImage];
[_zoomButton sizeToFit];
s_zoomImage is just an NSImage loaded from resources:
namespace
{
NSImage *s_zoomImage; // No static variables in Objective-C.
}
+ (void)initialize
{
[super initialize];
NSString *const zoomImagePath = [[NSBundle mainBundle] pathForResource:#"Zoom.png" ofType:nil];
s_zoomImage = [[[NSImage alloc] initWithContentsOfFile:zoomImagePath] autorelease];
}
At first the button displays correctly, but if I click it, the image (s_zoomImage) flips vertically! Any ideas?

How to get the QLPreviewView Content and write to an image

I just write a small demo. All of its feature is use the QLPreviewView to give a quick look of a Pages file.
When the App runs, you can scroll to view the Pages file content, and when you click save to PNG button, the app will save the current content displayed into a PNG image file. You can get the implementation in the save method. I just tried two implementation in that method, neither of them worked.
I just got a blank image filled with the window background color. Some advise here? Thanks.
The code and the App screen shot can be found here http://dr.ibuick.com/updU
#import <Cocoa/Cocoa.h>
#import <Quartz/Quartz.h>
#import <QuickLook/QuickLook.h>
#import "IBAppDelegate.h"
#interface IBAppDelegate (QLPreviewItem) <QLPreviewItem>
#end
#implementation IBAppDelegate (QLPreviewItem)
- (NSURL *)previewItemURL
{
return self.resolvedFileURL;
}
- (NSString *)previewItemTitle
{
return [self.originalURL absoluteString];
}
#end
#implementation IBAppDelegate
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
_resolvedFileURL = [NSURL fileURLWithPath:#"/Users/buick/Desktop/1.pages"];
_originalURL = [NSURL fileURLWithPath:#"/Users/buick/Desktop/1.pages"];
_previewView = [[QLPreviewView alloc] initWithFrame:NSMakeRect(0, 50, 480, 360)
style:QLPreviewViewStyleNormal];
[_previewView setPreviewItem:self];
[self.window.contentView addSubview:_previewView];
}
- (IBAction)save:(id)sender {
// Method 1
[_previewView lockFocus];
NSBitmapImageRep* rep = [_previewView
bitmapImageRepForCachingDisplayInRect:_previewView.bounds];
[_previewView cacheDisplayInRect:_previewView.bounds toBitmapImageRep:rep];
[_previewView unlockFocus];
[[rep representationUsingType:NSPNGFileType properties:nil]
writeToFile:#"/Users/buick/Desktop/1.png" atomically:YES];
// Method 2
[_previewView lockFocus];
NSBitmapImageRep *bits;
bits = [[NSBitmapImageRep alloc]
initWithFocusedViewRect:[_previewView visibleRect]];
[_previewView unlockFocus];
NSData *imageData;
NSDictionary *imageProps = [NSDictionary dictionaryWithObject:[NSNumber
numberWithFloat:0.9] forKey:NSImageCompressionFactor];
imageData = [bits representationUsingType:NSJPEGFileType
properties:imageProps];
[imageData writeToFile:#"/Users/buick/Desktop/1.png" atomically:YES];
}
#end
OK I think I got it. Try setting your QLPreviewView type to QLPreviewViewStyleCompact when you init it.
pv = [[QLPreviewView alloc] initWithFrame:pv.bounds
style:QLPreviewViewStyleCompact];
Then use this category
NSImage+QuickLook from Matt Gemmel...
github
NSImage *image = [NSImage imageWithPreviewOfFileAtPath:[[self.files objectAtIndex:i] path] ofSize:NSMakeSize(1200, 1920) asIcon:NO];

Drag & Drop creation of drag image

I'm implementing drag & drop for a customView; this customView is a subclass of NSView and include some elements.
When I start drag operation on it, the dragImage it's just an rectangular gray box of the same size of the customView.
This is the code I wrote:
-(void) mouseDragged:(NSEvent *)theEvent
{
NSPoint downWinLocation = [mouseDownEvent locationInWindow];
NSPoint dragWinLocation = [theEvent locationInWindow];
float distance = hypotf(downWinLocation.x - dragWinLocation.x, downWinLocation.y - downWinLocation.x);
if (distance < 3) {
return;
}
NSImage *viewImage = [self getSnapshotOfView];
NSSize viewImageSize = [viewImage size];
//Get Location of mouseDown event
NSPoint p = [self convertPoint:downWinLocation fromView:nil];
//Drag from the center of image
p.x = p.x - viewImageSize.width / 2;
p.y = p.y - viewImageSize.height / 2;
//Write on PasteBoard
NSPasteboard *pb = [NSPasteboard pasteboardWithName:NSDragPboard];
[pb declareTypes:[NSArray arrayWithObject:NSFilenamesPboardType]
owner:nil];
//Assume fileList is list of files been readed
NSArray *fileList = [NSArray arrayWithObjects:#"/tmp/ciao.txt", #"/tmp/ciao2.txt", nil];
[pb setPropertyList:fileList forType:NSFilenamesPboardType];
[self dragImage:viewImage at:p offset:NSMakeSize(0, 0) event:mouseDownEvent pasteboard:pb source:self slideBack:YES];
}
And this is the function I use to create the snapshot:
- (NSImage *) getSnapshotOfView
{
NSRect rect = [self bounds] ;
NSImage *image = [[[NSImage alloc] initWithSize: rect.size] autorelease];
NSRect imageBounds;
imageBounds.origin = NSZeroPoint;
imageBounds.size = rect.size;
[self lockFocus];
NSBitmapImageRep *rep = [[NSBitmapImageRep alloc] initWithFocusedViewRect:imageBounds];
[self unlockFocus];
[image addRepresentation:rep];
[rep release];
return image;
}
This is an image of a drag operation on my customView (the one with the icon and the label "drag me")
Why my dragImage it's just a gray box?
From the screenshot of IB in your comment, it looks like your view is layer backed. Layer backed views draw to their own graphics area that is separate from the normal window backing store.
This code:
[self lockFocus];
NSBitmapImageRep *rep = [[NSBitmapImageRep alloc] initWithFocusedViewRect:imageBounds];
[self unlockFocus];
Effectively reads pixels from the window backing store. Since your view is layer backed, its content is not picked up.
Try this without a layer backed view.