Replace NSTextattachement Image on NSMutableattributedString - download

I'm building an UITextView with text and images (Subclassing NSTextstorage for displaying my content)
I'm having textcontent with images URLs.
So my problem is that i need download all the images if they're not cached.
So i want to first insert a placeholder image, download the image and then replace the placeholder image by the downloaded one.
Here's how i do my stuff.
First, i'm formatting my text with images url by replacing all urls with this tag :
[IMG]url[/IMG]
Then i'm using a regex to get all these tags.
I'm testing if there's a cached image or not. If not, i extract all the urls, download them and cache them.
I've created an NSObject class ImageCachingManager and declared a delegate method called when an image has been downloaded :
#protocol ImageCachingManagerDelegate <NSObject>
- (void)managerDidCacheImage:(UIImage *)image forUrl:(NSString *)url;
#end
Like this, I tough that I could use the url of the image got by the delegate method to search the matching url in my NSTextstorage attributedString and replace the current NSTextattachement image by the downloaded one.
But I don't know how to do that...
Thanks for help !

I'm working on something very similar to this at the moment and think this might help. The code is very much alpha but hopefully it will get you to the next step - I'll step through:
Overall Cycle
1. Find you image tags in the full text piece using Reg Ex or XPath - personally i find Hppl to be more powerful but if your content is well structured and reliable, regex is probably fine.
https://github.com/topfunky/hpple
Reduce the space of this match to 1 character and store that range - A textAttachment occupies only 1 character of space within a textview so it's best to reduce this to 1 otherwise when you replace your first match of characters in a range with the first textattachment the next range marker becomes out of date which will lead to issues. Depending on how much processing you need to do this text input during init, this is an important step, i have to do a lot of processing on the text and the ranges change during this parsing so I created an array of special characters that I know is never going to be in the inputs and push these single characters into the reserved space, at the same time i store this special character and the src of the image in an array of a very simple NSObject subclass that stores the SpecialChar, ImgSrc plus has space for the NSRange but i basically find the special character later in the process again because it has been moved about since this point and then set the nsrange at the very end of processing - this may not be necessary in your case but the principle is the same; You need a custom object with NsRange (which will become a text attachment) and the imgSource.
Loop through this array to add placeholder imageAttachments to your attributed string. You can do this by adding a transparent image or a 'loading' image. You could also check your cache for existing images during this point and skipping the placeholder if it exists in cache.
Using your delegate, when the image is successfully downloaded, you need to replace the current attachment with your new one. By replacing the placeholder in the range you've already stored in your object. Create a placeholder attributedString with the NSTextAttachment and then replace that range as below.
Some sample code:
Steps 1 & 2:
specialCharsArray = [[NSArray alloc]initWithObjects:#"Û", #"±", #"¥", #"å", #"æ", #"Æ", #"Ç", #"Ø", #"õ", nil];
//using Hppl
NSString *allImagesXpathQueryString = #"//img/#src";
NSArray *imageArray = [bodyTextParser searchWithXPathQuery:allImagesXpathQueryString];
//
imageRanges = [[NSMutableArray alloc] init];
if([imageArray count]){
for (TFHppleElement *element in imageArray) {
int i = 0;
NSString *imgSource = [[[element children] objectAtIndex:0] content];
NSString *replacementString = [specialCharsArray objectAtIndex:i];
UIImage *srcUIImage = [UIImage imageNamed:imgSource];
[srcUIImage setAccessibilityIdentifier:imgSource]; //only needed if you need to reference the image filename later as it's lost in a UIImage if stored directly
//imagePlacement is NSObject subclass to store the range, replacement and image as above
imagePlacement *foundImage = [[imagePlacement alloc]init] ;
[foundImage initWithSrc:srcUIImage replacement:replacementString];
[imageRanges addObject:foundImage];
i++;
}
Step 3:
-(void)insertImages{
if ([imageRanges count]) {
[self setScrollEnabled:NO]; //seems buggy with scrolling on
int i = 0; //used to track the array placement for tag
for(imagePlacement *myImagePlacement in imageRanges){
// creates a text attachment with an image
NSMutableAttributedString *placeholderAttString = [[NSMutableAttributedString alloc]initWithAttributedString:self.attributedText];
NSTextAttachment *attachment = [[NSTextAttachment alloc] init];
//scales image down to ration of width of view - you probably don't need this
CGSize scaleToView = imagePlacement.imgSrc.size;
scaleToView.width = self.frame.size.width;
scaleToView.height = (self.frame.size.width/imagePlacement.imgSrc.size.width)*imagePlacement.imgSrc.size.height;
attachment.image = [self imageWithColor:[UIColor clearColor] andSize:scaleToView];
NSMutableAttributedString *imageAttrString = [[NSAttributedString attributedStringWithAttachment:attachment] mutableCopy];
[self setAttributedText:placeholderAttString];
i++;
}
}
[self setScrollEnabled:YES];
}
- (UIImage *)imageWithColor:(UIColor *)color andSize:(CGSize) size {
CGRect rect = CGRectMake(0.0f, 0.0f, size.width, size.height);
UIGraphicsBeginImageContext(rect.size);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetFillColorWithColor(context, [color CGColor]);
CGContextFillRect(context, rect);
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
}

Related

OS X, Cocoa: How to highlight burned-out areas of a photo?

I've got an app that displays photos using NSImage – specifically, -[NSImage drawInRect:fromRect:operation:fraction:]. I want to highlight areas of the photo that are completely burned out (maximum values in all components, pure white) using a color like red, as some digital cameras and image processing apps do, to help the user see whether the image is overexposed, and how badly.
I've been scratching my head as to how to do this. Options I've considered:
I could probably write a Core Image filter to do it; none of the built-in filters look up to the task. That seems like overkill, though; I've been reading through the docs, and it looks fairly complicated. Big learning curve.
I could scan through the bitmap data for the image and modify it as necessary. This is easy enough to code for one bitmap format, but the multitude of bitmap formats make it a rather annoying exercise, and speed is important here, so writing general-purpose code that renders the image up to some maximal common format and works on that bitmap would be too big a speed penalty.
As it happens, I am already scanning through images (handling all the different bitmap formats) at an earlier point in the code, to generate histogram data for the images. I could pretty easily add code at that point that would remember the burned-out pixels for later use. I'm not quite sure what the best way is to do that, though. A 1-bit-per-pixel NSBitmapImageRep? How would I draw it later, making the 1-pixels draw red and the 0-pixels draw transparent, for example? I don't want to make a 32-bit NSBitmapImageRep with an alpha channel and everything just for this purpose, as memory is not infinite and images are large. But there must be a way to draw a 1-bit mask in a given color, somehow.
Before forging ahead with one of these approaches, I thought I'd see whether anybody here has a better idea. Or maybe has implemented the CI filter in question already? Apart from the learning curve, that seems like the best approach I've thought of so far – no memory overhead, and probably faster than other options, too.
Thanks...
Ben Haller
Stick Software
OK, I implemented my own Core Image filter to do this. Wasn't as hard as I expected, although the documentation is not great for this stuff. The doc examples all assume you're using ARC, so if you're not, following those examples will give you various retain/release bugs. There was also a little weirdness with the CIFilterConstructor stuff, which did not quite go as documented. But overall pretty easy. CI is cool. My code is below, for anybody who might find it useful:
Header:
#import
#interface SSTintHighlightsFilter : CIFilter
{
CIImage *inputImage;
CIColor *highlightColor;
}
#end
Implementation file:
#import "SSTintHighlightsFilter.h"
static CIKernel *tintHighlightsFilter = nil;
#implementation SSTintHighlightsFilter
+ (void)initialize
{
[CIFilter registerFilterName:#"SSTintHighlightsFilter" constructor:(id )self
classAttributes:[NSDictionary dictionaryWithObjectsAndKeys:#"Tint Highlights", kCIAttributeFilterDisplayName, [NSArray arrayWithObjects:kCICategoryColorAdjustment, kCICategoryStillImage, nil], kCIAttributeFilterCategories, nil]];
}
+ (CIFilter *)filterWithName:(NSString *)name
{
CIFilter *filter = [[self alloc] init];
return [filter autorelease];
}
- (id)init
{
if (!tintHighlightsFilter)
{
NSBundle *bundle = [NSBundle bundleForClass:[self class]];
NSString *code = [NSString stringWithContentsOfFile:[bundle pathForResource:#"tintHighlightsAndShadows" ofType:#"cikernel"] encoding:NSASCIIStringEncoding error:NULL];
NSArray *kernels = [CIKernel kernelsWithString:code];
tintHighlightsFilter = [[kernels objectAtIndex:0] retain];
}
return [super init];
}
- (NSDictionary *)customAttributes
{
NSDictionary *attrs = #{
#"highlightColor" : #{ kCIAttributeClass : [CIColor class], kCIAttributeType : kCIAttributeTypeOpaqueColor }
};
return attrs;
}
- (CIImage *)outputImage
{
CISampler *src = [CISampler samplerWithImage:inputImage];
return [self apply:tintHighlightsFilter
arguments:[NSArray arrayWithObjects:src, highlightColor, nil]
options:[NSDictionary dictionaryWithObjectsAndKeys:[src definition], kCIApplyOptionDefinition, nil]];
}
#end
tintHighlights.cikernel:
kernel vec4 tintHighlights(sampler inputImage, __color highlightColor)
{
vec4 originalColor, tintedColor;
float sum;
// fetch the source pixel
originalColor = sample(inputImage, samplerCoord(inputImage));
// calculate the color component sum as a way of testing whether we are black or white
sum = originalColor.r + originalColor.g + originalColor.b;
// replace pixels that are white with the highlight color
tintedColor = (sum > 2.99999999999999999999999) ? highlightColor : originalColor;
// preserve alpha
tintedColor.a = originalColor.a;
return tintedColor;
}
using the filter:
+ (NSImage *)showHighlightsInImage:(NSImage *)img dstRect:(NSRect)dstRect
{
NSGraphicsContext *currentContext = [NSGraphicsContext currentContext];
NSRect dstRectForCGImage = dstRect; // because the method below wants a pointer, and I don't trust it not to modify my rect...
CGImageRef cgImage = [img CGImageForProposedRect:&dstRectForCGImage context:currentContext hints:nil];
CIImage *inputImage = [[CIImage alloc] initWithCGImage:cgImage];
[SSTintHighlightsFilter class]; // get my filter initialized
CIFilter *highlightFilter = [CIFilter filterWithName:#"SSTintHighlightsFilter"];
[highlightFilter setValue:inputImage forKey:#"inputImage"];
[highlightFilter setValue:[CIColor colorWithRed:1.0 green:0.0 blue:0.0] forKey: #"highlightColor"];
[inputImage release];
CIImage *outputImage = [highlightFilter valueForKey:#"outputImage"];
NSImage *resultImage = [[NSImage alloc] initWithSize:[img size]];
[resultImage addRepresentation:[NSCIImageRep imageRepWithCIImage:outputImage]];
return [resultImage autorelease];
}
I'm not sure that I'm handling the alpha entirely robustly, with premultiplication issues and so forth, but apart from that possible glitch it is working great.

Loading retina images from a subdirectory

I'm using NSDrawNinePartImage() to draw a stretchable control. Since this of course requires nine separate images to draw the parts (plus some extras that are drawn over that), I have a directory full of files like top-left.png and top-left#2x.png. I include this directory in my app bundle as a folder reference.
Unfortunately, the usual image-loading APIs like -[NSImage imageNamed:] and -[NSBundle imageForResource:] don't seem to support subdirectories, even if you put a slash in the name. Instead, I'm loading the images with this method:
- (NSImage*)popoverImage:(NSString*)name {
NSURL * url = [[NSBundle bundleForClass:self.class] URLForResource:name withExtension:#"png" subdirectory:#"popover"];
return [[NSImage alloc] initWithContentsOfURL:url];
}
This works fine for normal displays, but it ignores the 2x images for retina displays. How can I get it to load the retina images as well? Is there a better way than loading the two reps separately and combining them by hand? I'd rather not use TIFFs as my source files for these resources, because I use Acorn as my image editor and last time I checked, it doesn't really understand compound image formats like that very well.
The simple answer is to use TIFF's, your concern is misplaced. In Xcode set the project preference "Combine High Resolution Artwork" and continue to produce your two PNG's with Acorn as you do now. During build Xcode will combine those two PNG's into a single TIFF and store that in your bundle.
I ended up biting the bullet and combining the images manually at runtime. I did so by adding this method in a category on NSImage, and then using it in place of -initWithContentsOfURL::
- (id)initRetinaImageWithContentsOfURL:(NSURL *)url {
if((self = [self initWithContentsOfURL:url])) {
NSURL * baseURL = url.URLByDeletingLastPathComponent;
NSString * baseName = url.lastPathComponent.stringByDeletingPathExtension;
NSString * extension = url.lastPathComponent.pathExtension;
NSString * retinaBaseName = [NSString stringWithFormat:#"%##2x", baseName];
NSURL * retinaURL = [baseURL URLByAppendingPathComponent:[retinaBaseName stringByAppendingPathExtension:extension]];
if([retinaURL checkResourceIsReachableAndReturnError:NULL]) {
NSData * data = [[NSData alloc] initWithContentsOfURL:retinaURL];
NSBitmapImageRep * rep = [[NSBitmapImageRep alloc] initWithData:data];
rep.size = self.size;
[self addRepresentation:rep];
}
}
return self;
}
This is what I do:
NSArray *paths = [NSBundle.mainBundle pathsForResourcesOfType: #"icns" inDirectory: #"Collections/1"];
for (NSString *path in paths) {
NSString *fileName = [[path lastPathComponent] stringByDeletingPathExtension];
NSImage *image = [[NSImage alloc] initWithContentsOfFile: path];
image.name = fileName;
[iconCollectionController addObject: #{#"icon": image}];
}
but as CRD already pointed out you need to combine your artwork (either as tiff or icns files). But it frees you from manually selecting an image resolution (which also requires you to listen to backing store changes).

Combine more than one image to get a single image

I've to capture the contents of a UIView. There are plenty of methods to capture contents.The problem is,, if the size of the view becomes too big, the app crashes as it takes huge amount of memory.So is there any possibility to merge the raw image data (performing byte-by-byte operation) in a single file and make image from that??
`enter code here`Merging two images to create one single image file
Suppose we have two images with name file1.png and file2.png The code below merges two images.
#implementation LandscapeImage
- (void) createLandscapeImages
{
CGSize offScreenSize = CGSizeMake(2048, 1380);
UIGraphicsBeginImageContext(offScreenSize);
//Fetch First image and draw in rect
UIImage* imageLeft = [UIImage imageNamed:#"file1.png"];
CGRect rect = CGRectMake(0, 0, 1024, 1380);
[imageLeft drawInRect:rect];
[imageLeft release];
//Fetch second image and draw in rect
UIImage* imageRight = [UIImage imageNamed:#"file2.jpg"];
rect.origin.x += 1024;
[imageRight drawInRect:rect];
[imageRight release];
UIImage* imagez = UIGraphicsGetImageFromCurrentImageContext();// it will returns an image based on the contents of the current bitmap-based graphics context.
if (imageLeft && imageRight)
{
//Write code for save imagez in local cache
}
UIGraphicsEndImageContext();
}
#end

converting a pointer to unsigned char to an NSImage

I have a structure which includes a pointer to a data set, which in this case is a 16-bit grayscale image. I want to convert this data to an NSImage so that I can display it, and then save it as a .TIF file. The route from the manuals appears to be something like:
(Create *myNSImData from frame->image, which is a pointer)
NSImage *TestImage = [[NSImage alloc] initWithData : myNSImData];
(display TestImage, save it, whatever else)
[TestImage release];
I am lost as to how to create the NSData object and assure it contains the array of 16-bit data. Attempts to recast the pointer give warnings and no data. I could simply increment the pointers, transferring one byte at a time from frame->image to the data object, but I don't understand how to communicate the array structure to the data object. Any ideas?
Thanks
MORE ATTEMPTS USING YOUR SUGGESTION
I can convert this data to a .TIF file in the following manner:
for (uint32 row = 0 ; row < MaxHeight ; row++)
{
for (uint32 column = 0;column< MaxWidth;column++)
{
tempData = (uint8_t)*frame->image; //first byte
*frame->image++;
buf[2 * column + 1] = (unsigned char) tempData;
tempData = (uint8_t)*frame->image; //second byte
*frame->image++;
buf[2 * column] = (unsigned char) tempData;
}
TIFFWriteScanline(tiffile,buf,row,0);
}
With the .TIF file thus generated, I can create an NSImage and display it:
NSImage *TestImage = [[[NSImage alloc] initWithContentsOfFile:inFilePath] autorelease];
[viewWindow setImage: TestImage];
My question now becomes - can I create an NSData object that I can display in the same way? I have tried the following (product is the height*width of the image):
NSData *ReadImage = [[[NSData alloc] initWithBytes: frame->image length:2*product] autorelease] ;
NSImage *NewImage = [[[NSImage alloc] initWithData:ReadImage] autorelease];
NSSize newSize;
newSize.height = MaxHeight; //height of the image
newSize.width = MaxWidth; //width of the image
[NewImage setSize:newSize];
[viewWindow setImage: NewImage];
When I try this, nothing displays. I have also tried creating an array of uint16_t which has the data, and serving up the pointer to that - again, nothing displays. Any ideas? E.g. do I have to tell the NSData that I am using 2 bytes per pixel, or something like that? Thanks Monty Wood
To create an NSData object containing a block to which you have a pointer, you should use one of the three methods that start with initWithBytes:, or, to create an autoreleased NSData object, use one of the class methods that start with dataWithBytes:
UPDATE: I think that if you want to create an NSImage directly from an NSData, the data needs to include the appropriate headers/magic numbers so that NSImage can figure out what the representation is. You should look at NSBitmapImageRep and the Images chapter of the Cocoa Drawing Guide for raw image data.

How to- NSAttributedString to CGImageRef

I'm writing a QuickLook plugin. Well, everything works. Just want to try it make better ;).
Thus the question.
Here is a function that returns thumbnail image and that I'm using now.
QLThumbnailRequestSetImageWithData(
QLThumbnailRequestRef thumbnail,
CFDataRef data,
CFDictionaryRef properties);
);
http://developer.apple.com/mac/library/documentation/UserExperience/Reference/QLThumbnailRequest_Ref/Reference/reference.html#//apple_ref/c/func/QLThumbnailRequestSetImageWithData
Right now I'm creating a TIFF -> encapsulated it into NSData. An example
// Setting CFDataRef
CGSize thumbnailMaxSize = QLThumbnailRequestGetMaximumSize(thumbnail);
NSMutableAttributedString *attributedString = [[[NSMutableAttributedString alloc]
initWithString:#"dummy"
attributes:[NSDictionary dictionaryWithObjectsAndKeys:
[NSFont fontWithName:#"Monaco" size:10], NSFontAttributeName,
[NSColor colorWithCalibratedRed:0.0 green:0.0 blue:0.0 alpha:1.0], NSForegroundColorAttributeName,
nil]
] autorelease];
NSImage *thumbnailImage = [[[NSImage alloc] initWithSize:NSMakeSize(thumbnailMaxSize.width, thumbnailMaxSize.height)] autorelease];
[thumbnailImage lockFocus];
[[NSColor whiteColor] set];
NSRectFill(NSMakeRect(0, 0, thumbnailMaxSize.width, thumbnailMaxSize.height));
[attributedString drawInRect:NSMakeRect(0, 0, thumbnailMaxSize.width, thumbnailMaxSize.height)];
[thumbnailImage unlockFocus];
(CFDataRef)[thumbnailImage TIFFRepresentation]; // This is data
// Setting CFDictionaryRef
(CFDictionaryRef)[NSDictionary dictionaryWithObjectsAndKeys:#"kUTTypeTIFF", (NSString *)kCGImageSourceTypeIdentifierHint, nil ]; // this is properties
However QuickLook provides another function to return thumbnail image, namely
QLThumbnailRequestSetImage(
QLThumbnailRequestRef thumbnail,
CGImageRef image,
CFDictionaryRef properties);
);
http://developer.apple.com/mac/library/documentation/UserExperience/Reference/QLThumbnailRequest_Ref/Reference/reference.html#//apple_ref/c/func/QLThumbnailRequestSetImage
I have a feeling that passing CGImage to the QL instead of TIFF data would help in speeding things up.
However- I have never worked with CG context before. I know, the documentation is there :), but anyways- could anyone give an example how to turn that NSAttributed string into CGImageRef. An example is worth 10 times reading the documentation ;)
Any help appreciated. Thanks in advance!
could anyone give an example how to turn that NSAttributed string into CGImageRef.
You can't turn a string into an image; they're two completely different kinds of data, and one is two dimensional (characters over time) while the other is at-least-three dimensional (color over x and y).
What you need to do is draw the string and produce an image of the drawing. That's what you're doing now with NSImage: Creating an image and drawing the string into it.
You're asking about creating a CGImage. Creating a bitmap context, using Core Text to draw the string into it, and creating an image of the contents of the bitmap context is one way to do that.
However, you're already much closer to another solution, assuming you can require Snow Leopard. Instead of asking the NSImage for a TIFF representation, ask it for a CGImage.

Resources