How to shift image horizontally to create loop effect? - cocoa

How to shift NSImage horizontally that shifted pixels appear at the other side so it looks like a loop?
Currently I am using drawInRect. Is there any CIFilter or smarter way to do this?
- (CIImage *)image:(NSImage *)image shiftedBy:(CGFloat)shiftAmount
{
NSUInteger width = image.size.width;
NSUInteger height = image.size.height;
NSBitmapImageRep *rep = [[NSBitmapImageRep alloc] initWithBitmapDataPlanes:NULL
pixelsWide:width
pixelsHigh:height
bitsPerSample:8
samplesPerPixel:4
hasAlpha:YES
isPlanar:NO
colorSpaceName:NSDeviceRGBColorSpace
bytesPerRow:0
bitsPerPixel:0];
[rep setSize:NSMakeSize(width, height)];
[NSGraphicsContext saveGraphicsState];
NSGraphicsContext *context = [NSGraphicsContext graphicsContextWithBitmapImageRep:rep];
[NSGraphicsContext setCurrentContext:context];
CGRect rect0 = CGRectMake(0, 0, width, height);
CGRect leftSourceRect, rightSourceRect;
CGRectDivide(rect0, &leftSourceRect, &rightSourceRect, shiftAmount, CGRectMinXEdge);
CGRect rightDestinationRect = CGRectOffset(leftSourceRect, width - rightSourceRect.origin.x, 0);
CGRect leftDestinationRect = rightSourceRect;
leftDestinationRect.origin.x = 0;
[image drawInRect:leftDestinationRect fromRect:rightSourceRect operation:NSCompositingOperationSourceOver fraction:1.0];
[image drawInRect:rightDestinationRect fromRect:leftSourceRect operation:NSCompositingOperationSourceOver fraction:1.0];
[NSGraphicsContext restoreGraphicsState];
return [[CIImage alloc] initWithBitmapImageRep:rep];
}

I tried it with CIFilter however the performance hit is 3-4x slower. However, the code is more readable.
- (CIImage *)image:(NSImage *)image shiftXBy:(CGFloat)shiftX YBy:(CGFloat)shiftY
{
//avoid calling TIFFRepresentation here cause the performance hit is even bigger
CIImage *ciImage = [[CIImage alloc] initWithData:[image TIFFRepresentation]];
CGAffineTransform xform = CGAffineTransformIdentity;
NSValue *xformObj = [NSValue valueWithBytes:&xform objCType:#encode(CGAffineTransform)];
ciImage = [ciImage imageByApplyingFilter:#"CIAffineTile"
withInputParameters:#{kCIInputTransformKey : xformObj} ];
ciImage = [ciImage imageByCroppingToRect:CGRectMake(shiftX, shiftY, image.size.width, image.size.height)];
return ciImage;
}

For optimum performance you need to use CALayer. Here’s the basic concept:
Your NSView subclass should have wantsLayer and layerUsesCoreImageFilters set to true.
Assign your image to content property of NSView.layer (or add a new sublayer).
Create CIAffineTile filter and add it to the layer.
Now you can change values of the filter without reloading or redrawing the image. This all would be hardware accelerated.

Related

Is it possible to print IKImageBrowserView In Cocoa programmatically?

I want to take print of IKImageBrowserView with (content) images. I tried the following code
if (code == NSOKButton) {
NSPrintInfo *printInfo;
NSPrintInfo *sharedInfo;
NSPrintOperation *printOp;
NSMutableDictionary *printInfoDict;
NSMutableDictionary *sharedDict;
sharedInfo = [NSPrintInfo sharedPrintInfo];
sharedDict = [sharedInfo dictionary];
printInfoDict = [NSMutableDictionary dictionaryWithDictionary: sharedDict];
[printInfoDict setObject:NSPrintSaveJob
forKey:NSPrintJobDisposition];
[printInfoDict setObject:[sheet filename] forKey:NSPrintSavePath];
printInfo = [[NSPrintInfo alloc] initWithDictionary:printInfoDict];
[printInfo setHorizontalPagination: NSAutoPagination];
[printInfo setVerticalPagination: NSAutoPagination];
[printInfo setVerticallyCentered:NO];
printOp = [NSPrintOperation printOperationWithView:imageBrowser
printInfo:printInfo];
[printOp setShowsProgressPanel:NO];
[printOp runOperation];
}
because IKImageBrowserView is Inherits from NSView but print preview is showing null image. Please help me to over come this problem. Thanks in advance.....
/*
1) allocate a c buffer at the size of the visible rect of the image
browser
*/
NSRect vRect = [imageBrowser visibleRect];
NSSize size = vRect.size;
NSLog(#"Size W = %f and H = %f", size.width, size.height);
void *buffer = malloc(size.width * size.height * 4);
//2) read the pixels using openGL
[imageBrowser lockFocus];
glReadPixels(0,
0,
size.width,
size.height,
GL_RGBA,
GL_UNSIGNED_BYTE,
buffer);
[imageBrowser unlockFocus];
//3) create a bitmap with those pixels
unsigned char *planes[2];
planes[0] = (unsigned char *) (buffer);
NSBitmapImageRep *imageRep = [[NSBitmapImageRep alloc]
initWithBitmapDataPlanes:planes pixelsWide:size.width
pixelsHigh:size.height bitsPerSample:8 samplesPerPixel:4 hasAlpha:YES
isPlanar:NO colorSpaceName:NSDeviceRGBColorSpace bitmapFormat:0
bytesPerRow:size.width*4 bitsPerPixel:32];
/*
4) create a temporary image with this bitmap and set it flipped
(because openGL and the AppKit don't have the same pixels coordinate
system)
*/
NSImage *img = [[NSImage alloc] initWithSize:size];
[img addRepresentation:imageRep];
[img setFlipped:YES];
[imageRep release];
/*
5) draw this temporary image into another image so that we get an
image without any reference to our "buffer" buffer so that we can
release it after that
*/
NSImage *finalImage = [[NSImage alloc] initWithSize:size];
[finalImage lockFocus];
[img drawAtPoint:NSZeroPoint
fromRect:NSMakeRect(0,0,size.width,size.height)
operation:NSCompositeCopy fraction:1.0];
[finalImage unlockFocus];
//[NSString stringWithFormat:#"/tmp/%#.tiff", marker]
NSData *imageData = [finalImage TIFFRepresentation];
NSString *writeToFileName = [NSString stringWithFormat:#"/Users/Desktop/%#.png", [NSDate date]];
[imageData writeToFile:writeToFileName atomically:NO];
//6) release intermediate objects
[img release];
free(buffer);
After this I send imageData for print, which works great for me.

get the width and height of NSView and NSImageView

I have a NSView, there is a NSImageView on the NSView
in the source codes of the NSView
I wrote:
NSImage *image = [[[NSImage alloc] initWithContentsOfURL:url] autorelease];
NSImageView *newImageView = nil;
newImageView = [[NSImageView alloc] initWithFrame:[self bounds]];
[newImageView setImage:image];
[newImageView setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
Is there a way to read the width and height of the NSView and NSImageView?
The size of any view (NSView, NSImageView, etc.) is view.frame.size or view.bounds.size.
In both cases it is an identical NSSize struct. You can write code like this:
NSSize size = newImageView.frame.size;
NSLog(#"size: %#", NSStringFromSize(size));
NSLog(#"size: %f x %f", size.width, size.height);
To change it, you need to update the view frame property:
NSRect frame = view.frame;
frame.size = NSMakeSize(<#new width#>, <#new height#>);
view.frame = frame;
Try the - (NSRect)bounds method.

Drawing text with gradient fill in Cocoa

I have a project that needs to draw text in a view with a gradient fill in a custom subclass of NSView, like this example below.
I'm wondering how I can achieve this, as I'm pretty new to Cocoa drawing.
Try creating an alpha mask from the text then using NSGradient to draw into it. Here's a simple example based on the linked code:
- (void)drawRect:(NSRect)rect
{
// Create a grayscale context for the mask
CGColorSpaceRef colorspace = CGColorSpaceCreateDeviceGray();
CGContextRef maskContext =
CGBitmapContextCreate(
NULL,
self.bounds.size.width,
self.bounds.size.height,
8,
self.bounds.size.width,
colorspace,
0);
CGColorSpaceRelease(colorspace);
// Switch to the context for drawing
NSGraphicsContext *maskGraphicsContext =
[NSGraphicsContext
graphicsContextWithGraphicsPort:maskContext
flipped:NO];
[NSGraphicsContext saveGraphicsState];
[NSGraphicsContext setCurrentContext:maskGraphicsContext];
// Draw the text right-way-up (non-flipped context)
[text
drawInRect:rect
withAttributes:
[NSDictionary dictionaryWithObjectsAndKeys:
[NSFont fontWithName:#"HelveticaNeue-Bold" size:124], NSFontAttributeName,
[NSColor whiteColor], NSForegroundColorAttributeName,
nil]];
// Switch back to the window's context
[NSGraphicsContext restoreGraphicsState];
// Create an image mask from what we've drawn so far
CGImageRef alphaMask = CGBitmapContextCreateImage(maskContext);
// Draw a white background in the window
CGContextRef windowContext = [[NSGraphicsContext currentContext] graphicsPort];
[[NSColor whiteColor] setFill];
CGContextFillRect(windowContext, rect);
// Draw the gradient, clipped by the mask
CGContextSaveGState(windowContext);
CGContextClipToMask(windowContext, NSRectToCGRect(self.bounds), alphaMask);
NSGradient *gradient = [[NSGradient alloc] initWithStartingColor:[NSColor blackColor] endingColor:[NSColor grayColor]];
[gradient drawInRect:rect angle:-90];
[gradient release];
CGContextRestoreGState(windowContext);
CGImageRelease(alphaMask);
}
This uses the view bounds as the gradient bounds; if you wanted to be more accurate, you'd need to get the text height (information about that here).

Trying to resize an NSImage which turns into NSData

I have an NSImage which I am trying to resize like so;
NSImage *capturePreviewFill = [[NSImage alloc] initWithData:previewData];
NSSize newSize;
newSize.height = 160;
newSize.width = 120;
[capturePreviewFill setScalesWhenResized:YES];
[capturePreviewFill setSize:newSize];
NSData *resizedPreviewData = [capturePreviewFill TIFFRepresentation];
resizedCaptureImageBitmapRep = [[NSBitmapImageRep alloc] initWithData:resizedPreviewData];
saveData = [resizedCaptureImageBitmapRep representationUsingType:NSJPEGFileType properties:nil];
[saveData writeToFile:#"/Users/ricky/Desktop/Photo.jpg" atomically:YES];
My first issue is that my image gets squashed when I try to resize it and don't conform to the aspect ratio. I read that using -setScalesWhenResized would resolve this problem but it didn't.
My second issue is that when I try to write the image to a file, the image isn't actually resized at all.
Thanks in advance,
Ricky.
I found this blog post to be very helpful for resizing my image: http://weblog.scifihifi.com/2005/06/25/how-to-resize-an-nsimage/
You will need to enforce the aspect ratio on the image resizing yourself, it won't be done for you. This is how I did it when I was trying to fit the image into the printable area on the default paper:
NSImage *image = ... // get your image
NSPrintInfo *printInfo = [NSPrintInfo sharedPrintInfo];
NSSize paperSize = printInfo.paperSize;
CGFloat usablePaperWidth = paperSize.width - printInfo.leftMargin - printInfo.rightMargin;
CGFloat resizeWidth = usablePaperWidth;
CGFloat resizeHeight = usablePaperWidth * (image.size.height / image.size.width);
Here is a slightly modified version of his code from the blog:
NSData *sourceData = [image TIFFRepresentation];
float resizeWidth = ... // your desired width;
float resizeHeight = ... // your desired height;
NSImage *sourceImage = [[NSImage alloc] initWithData: sourceData];
NSImage *resizedImage = [[NSImage alloc] initWithSize: NSMakeSize(resizeWidth, resizeHeight)];
NSSize originalSize = [sourceImage size];
[resizedImage lockFocus];
[sourceImage drawInRect: NSMakeRect(0, 0, resizeWidth, resizeHeight) fromRect: NSMakeRect(0, 0, originalSize.width, originalSize.height) operation: NSCompositeSourceOver fraction: 1.0];
[resizedImage unlockFocus];
NSData *resizedData = [resizedImage TIFFRepresentation];
[sourceImage release];
[resizedImage release];
If you can require Mac OS X 10.6 or later, send your image a CGImageForProposedRect:context:hints: message, then write the CGImage out using a CGImageDestination object.
The rectangle should have NSZeroPoint as its origin, and its size be the size you want.
This still won't scale the image proportionally (maintaining aspect ratio); you have to do that yourself.
The pre-10.6 way to do this (without going through a TIFF representation) is to lock focus on the resized image, create an NSBitmapImageRep for the extent of the image (that is, a rectangle with zero origin and the image's size), unlock focus, and then ask that bitmap image rep for JPEG data.

Strange artifacts after CILanczosScaleTransform

I'm trying to do image scaling using Core Image, using Lanczos Scale Transform filter.
It is fine when I'm doing scaleup. But on scaledown and saving to JPEG I found a class of images which produces strange noise artifacts and behavior.
For inputScale multiples to 0.5: 0.5, 0.25, 0.125 etc it is always fine.
For others inputScale values,it's broken.
When I'm saving to TIFF,PNG,JPEG2000 or draw on screen - it's fine.
When I'm saving to JPG or BMP - it's broken.
I uploaded sample images:
(original, 4272x2848, 3.4Mb)
[http://www.zoomfoot.com/get/package/517e2423-7795-4239-a166-03d507ec51d8]
(scaled with noise, 1920x1280, 2.2Mb)
[http://www.zoomfoot.com/get/package/6eb64d33-3a30-4e8d-9953-67ce1e7d7ef9]
It also, pretty good reproducible on 'sunset' images.
I've tried couple more ways to scale images. Using CIAffineTransform and drawing using NSImage itself. Both provides results without any noise.
Here is the code I was using for scaling & saving:
================= Lanczos ==============
- (NSImage *) scaleLanczosImage:(NSString *)path
Width:(int) desiredWidth
Height:(int) desiredHeight
{
CIImage *image=[CIImage imageWithContentsOfURL:
[NSURL fileURLWithPath:path]];
CIFilter *scaleFilter = [CIFilter filterWithName:#"CILanczosScaleTransform"];
int originalHeight=[image extent].size.height;
int originalWidth=[image extent].size.width;
float yScale=(float)desiredHeight / (float)originalHeight;
float xScale=(float)desiredWidth / (float)originalWidth;
float scale=fminf(yScale, xScale);
[scaleFilter setValue:[NSNumber numberWithFloat:scale]
forKey:#"inputScale"];
[scaleFilter setValue:[NSNumber numberWithFloat:1.0]
forKey:#"inputAspectRatio"];
[scaleFilter setValue: image
forKey:#"inputImage"];
image = [scaleFilter valueForKey:#"outputImage"];
NSBitmapImageRep* rep = [[NSBitmapImageRep alloc] initWithCIImage:image];
NSMutableDictionary *options=[NSMutableDictionary dictionaryWithObject:[NSDecimalNumber numberWithFloat:1.0]
forKey:NSImageCompressionFactor];
[options setValue:[NSNumber numberWithBool:YES] forKey:NSImageProgressive];
NSData* jpegData = [rep representationUsingType:NSJPEGFileType
properties:options];
[jpegData writeToFile:#"/Users/dmitry/tmp/img_4389-800x600.jpg" atomically:YES];
}
================= NSImage ==============
- (void) scaleNSImage:(NSString *)path
Width:(int) desiredWidth
Height:(int) desiredHeight
{
NSImage *sourceImage = [[NSImage alloc] initWithContentsOfFile:path];
NSImage *resizedImage = [[NSImage alloc] initWithSize:
NSMakeSize(desiredWidth, desiredHeight)];
NSSize originalSize = [sourceImage size];
[resizedImage lockFocus];
[sourceImage drawInRect: NSMakeRect(0, 0, desiredWidth, desiredHeight)
fromRect: NSMakeRect(0, 0, originalSize.width, originalSize.height)
operation: NSCompositeSourceOver fraction: 1.0];
[resizedImage unlockFocus];
NSMutableDictionary *options=[NSMutableDictionary dictionaryWithObject:[NSDecimalNumber numberWithFloat:1.0]
forKey:NSImageCompressionFactor];
[options setValue:[NSNumber numberWithBool:YES] forKey:NSImageProgressive];
NSBitmapImageRep *rep = [[NSBitmapImageRep alloc]
initWithData:[resizedImage TIFFRepresentation]];
NSData* jpegData = [rep representationUsingType:NSJPEGFileType
properties:options];
[jpegData writeToFile:#"~/nsimg_4389-800x600.jpg" atomically:YES];
}
If anyone can explain or suggest something here, i'm really appreciate this.
Thanks.
There are some quirks with scaling with CIImage. Will this post by Dan Wood help?
My understanding is that due to its nature, the hardware Core Image renderer has some odd quirks when rendering for anything but on-screen display. So, you are recommended to use the software renderer for other situations.
http://developer.apple.com/mac/library/qa/qa2005/qa1416.html
That's definitely a CoreImage bug. File it at bugreport.apple.com

Resources