I'm writing a PDF parser for work, and we're using Core Graphics to read in all of the data with callbacks and then writing it out with Lib Haru because our client needs to write out "real" annotations and CG can't do it.
Well, I've gotten to the point where I am getting images (and saving them out to a file to make sure I'm doing it right before I start to draw them) and I've run into an issue. I'm getting all of the Image XObjects out of the Resource dictionary and then attempting to save them out with this code
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDir = [paths objectAtIndex:0];
NSData *imageFileData = (NSData *)CGPDFStreamCopyData(objectStream, CGPDFDataFormatRaw);
NSString *fileName = [NSString stringWithFormat:#"%#/%s.png", documentsDir, name];
[imageFileData writeToFile:fileName atomically:YES];
where the objectStream is using the CGPDFDictionaryGetStream to extract the XObject. Well, it works fine when the Filter is "DCTDecode", but whenever the Filter is "FlateDecode", the image that is saved is corrupt and won't open.
I read in this post that CGPDFStreamCopyData can decode text with FlateDecode (all the way to the bottom of the post in the comments), but there are only 3 data formats in the CGPDFDataFormats, and none of them work.
I believe I'm also having issues with text that is encoded with FlatDecode. Does anyone have any suggestions on how to go about decoding this? Surely CGPDF has something that handles this since it appears in almost every pdf that I've tried to open (though I haven't been able to locate it).
Edit: I read in a few places that I could decompress it using zlib, so I tried this code that I was able to find about how to do that:
NSData* uncompressedImageData;
if ([imageFileData length] == 0)
uncompressedImageData = imageFileData;
else
{
z_stream strm;
strm.zalloc = Z_NULL;
strm.zfree = Z_NULL;
strm.opaque = Z_NULL;
strm.total_out = 0;
strm.next_in=(Bytef*)[imageFileData bytes];
strm.avail_in = [imageFileData length];
// Compresssion Levels: // Z_NO_COMPRESSION // Z_BEST_SPEED // Z_BEST_COMPRESSION // Z_DEFAULT_COMPRESSION
if (deflateInit(&strm, Z_DEFAULT_COMPRESSION) != Z_OK)
uncompressedImageData = nil;
NSMutableData *compressed = [NSMutableData dataWithLength:16384]; // 16K chuncks for expansion
do
{
if (strm.total_out >= [compressed length])
[compressed increaseLengthBy: 16384];
strm.next_out = [compressed mutableBytes] + strm.total_out; strm.avail_out = [compressed length] - strm.total_out;
deflate(&strm, Z_FINISH);
}
while (strm.avail_out == 0);
deflateEnd(&strm);
[compressed setLength: strm.total_out];
uncompressedImageData = [NSData dataWithData: compressed];
}
if(uncompressedImageData != nil)
[uncompressedImageData writeToFile:fileName atomically:YES];
The code didn't throw any exceptions when I ran it, but the resulting images were still unreadable.
Your use of CGPDFStreamCopyData seems to suggest that you have a misunderstanding there: You don't set the format that you want, the function sets this to the format it encounters in the stream. A typical use would be:
CGPDFDataFormat format;
CGPDFStreamCopyData(objectStream, &format);
if (format == CGPDFDataFormatRaw) {
//handle raw data...
} else if (format == CGPDFDataFormatJPEGEncoded) {
//handle jpeg data...
} else if (format == CGPDFDataFormatJPEG2000) {
//handle jpeg 2000 data
}
PNG images are not supported at all by the PDF standard, so you'll never get a valid PNG file out of an image data stream. The options are JPEG, JPEG2K and raw images (see the spec for details on those).
Quartz handles zlib compression transparently, so you'll never get zlib-compressed data yourself.
Related
I am working on an app similar to photo app in iPhone. The flow is I get images through UIImagePickerController. When I use Actual image and further save that image to documents,
My app hangs. I saw on profile tool that allocation goes to 19 to 20 mb. If there is any solution to that.
Getting image from UIImagepicker controller
Saving it to documents.
-(NSString *)saveImageInDocumentsAtPath:(NSNumber *)number
{
NSString *fileName=[NSString stringWithFormat:#"image%#.png",number];
NSString *imagePath= [[NSString alloc] initWithFormat:#"%#/%#", [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0],fileName];
UIImage *image = self.selectedImage.image;
NSData *imageData = UIImagePNGRepresentation(image);
[imageData writeToFile:imagePath atomically:YES];
return imagePath;
}
A typical image is going to be around 3Megabytes, if I am not mistaken (depending on the iPhone and its camera). So going from 19 to 20 is not unreasonable. Why you are at 19 to begin with ay be another question. See if you're loading in too many other images, large files etc.
If you use AV Foundation instead of ImagePicker, you have some other options for handling the images.
And you can write the image to the filesystem asynchronously.
This looks like a great opportunity for some error checking in your code.
Why not try this?
-(NSString *)saveImageInDocumentsAtPath:(NSNumber *)number
{
NSString *fileName=[NSString stringWithFormat:#"image%#.png",number];
NSArray * docDirectoryArray = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
if(docDirectoryArray)
{
NSString *imagePath= [[NSString alloc] initWithFormat:#"%#/%#", [docDirectoryArray objectAtIndex:0],fileName];
UIImage *image = self.selectedImage.image;
if(image)
{
NSData *imageData = UIImagePNGRepresentation(image);
if(imageData)
{
BOOL success = [imageData writeToFile:imagePath atomically:YES];
if(success)
{
// only return the image path in the success == YES case
return imagePath;
} else {
NSLog( #"did not save file to %#", imagePath);
}
} else {
NSLog( #"could not get image data out of the image");
}
} else {
NSLog( #"no image selected");
}
}
// you might want to check in the caller to make certain the imagePath is NULL
return NULL;
}
I have an application which donwloads several images and stores them on the phone. In total it will probably required around 20 images tops. I need to be able to retrieve any of these images at will depending on what screen the user is on. These images will be stored indefinitely, so I don't want to use temp directory.
At present I have a class named Images with these methods
- (void) cacheImage: (NSString *) ImageURLString : (NSString *)imageName
{
NSURL *ImageURL = [NSURL URLWithString: ImageURLString];
// Generate a unique path to a resource representing the image you want
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *docDir = [paths objectAtIndex: 0];
NSString *docFile = [docDir stringByAppendingPathComponent: imageName];
// Check for file existence
if(![[NSFileManager defaultManager] fileExistsAtPath: docFile])
{
// The file doesn't exist, we should get a copy of it
// Fetch image
NSData *data = [[NSData alloc] initWithContentsOfURL: ImageURL];
UIImage *image = [[UIImage alloc] initWithData: data];
// Is it PNG or JPG/JPEG?
// Running the image representation function writes the data from the image to a file
if([ImageURLString rangeOfString: #".png" options: NSCaseInsensitiveSearch].location != NSNotFound)
{
[UIImagePNGRepresentation(image) writeToFile: docFile atomically: YES];
}
else if([ImageURLString rangeOfString: #".jpg" options: NSCaseInsensitiveSearch].location != NSNotFound ||
[ImageURLString rangeOfString: #".jpeg" options: NSCaseInsensitiveSearch].location != NSNotFound)
{
[UIImageJPEGRepresentation(image, 100) writeToFile: docFile atomically: YES];
}
}
}
- (UIImage *) getCachedImage : (NSString *)imageName
{
NSArray *paths = NSSearchPathForDirectoriesInDomains
(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString* cachedPath = [documentsDirectory stringByAppendingPathComponent:imageName];
UIImage *image;
// Check for a cached version
if([[NSFileManager defaultManager] fileExistsAtPath: cachedPath])
{
image = [UIImage imageWithContentsOfFile: cachedPath]; // this is the cached image
}
else
{
NSLog(#"Error getting image %#", imageName);
}
return image;
}
-(void)getImages
{
//example
NSString *image1URL = #"http://test/image1.png";
NSString *image2URL = #"http://test/image2.png";
NSString *image3URL = #"http://test/image3.png";
[self cacheImage:sLogo: #"Image1"];
[self cacheImage:sBlankNav: #"Image2"];
[self cacheImage:buttonLarge :#"Image3"];
}
-(void) storeImages
{
image1 = [self getCachedImage:#"Image1"];
image2 = [self getCachedImage:#"Image2"];
image3 = [self getCachedImage:#"Image3"];
}
So I use the code like this
Images *cache = [[Images alloc]init];
[cache storeImages];
The get images method is called once when the app first starts to get the images, it isn't called again after that, unless the images on the server are updated and I need to retrieve the updated ones.
The code works, but the problem is when I navigate to a screen that uses it, there is a very slight delay before the screen loads as it is loading the images.
My application is a tabbed application, so it begins on tab 1, I click tab 2 which implements the code, there will be a slight pause the first time it loads. It doesn't last very long, but it is noticeable and is very annoying. After that it is fine, as it is already loaded. However with navigation controller, every time you move from the first VC to the second VC, the method will be called again, so each time you navigate the delay will be there.
The images are not very big, biggest one is 68kb, others are much smaller than that. At present I am just testing with 5 images. Is there a more efficient way of storing and retrieving images, or am I doing something wrong with my code? I need to be able to retrieve these images without any noticeable delay in order for my application to remain fluid and not jerky or clunky.
Thanks in advance!!
You have two options to do the image loading work on a background thread - use Grand Central Dispatch or NSInvocationOperation. GCD might be considered the cleaner of the two:
dispatch_queue_t q = dispatch_get_global_queue(0, 0);
dispatch_queue_t main = dispatch_get_main_queue();
dispatch_async(q, ^{
//load images here
dispatch_async(main, ^{
// show on main thread here
});
});
you have delay because you're downloading data synchronously
// NSData *data = [[NSData alloc] initWithContentsOfURL: ImageURL];
Try some smart library like SDWebImage:
it lets you download image asynchronously while you still can display a local image (a proxy image). By the way, you still get cache image for free. So even if u are on local, you can still catch previously downloaded images
https://github.com/rs/SDWebImage
A must have
I'm currently working on an application that has to upload large files (mainly movies/videos) to the web. After reading what I can, I went the the approach of converting the movie to NSData and then including that as the NSURLConnection's HTTPBody. However, upon converting the movie (which was originally an ALAsset) into NSData, I receive a memory warning and then a subsequent crash.
I have no idea how I would go about uploading these types of large files, if that data just causes an instant crash. One solution that I was thinking of is writing to the filesystem and then uploading a file directly from there, but I have not been able to find any information on how one would accomplish this.
Here is the relevant code that I use. If there is something that I'm doing wrong right here, I'd love to know.
ALAssetRepresentation *representation = [asset defaultRepresentation];
Byte *buffer = (Byte *)malloc([representation size]);
NSUInteger buffered = [representation getBytes:buffer fromOffset:0.0 length:[representation size] error:nil];
uploadData = [NSData dataWithBytes:buffer length:buffered];
free(buffer);
Assuming that it makes sense to upload the movie in its native format, you can really make this easier using the BSD (ie Unix) section 3 interface:
given a filePath, open the file and get an int file descriptor (fd)
with fd, get the length of the file
keep track of how much you've loaded so you know where to get more data
use mmap(3) to map in JUST the data you want to upload at any time, and use the void * pointer returned by mmap as the location of the data
when the data has been sent, munmap the old data chunk and mmap a new chunk
after all data is sent, munmap the last chunk, the close(fd).
No temporary memory - no mallocs. I use mmap whenever I have to deal with huge files.
Edit: you can also use NSData dataWithContentsOfFile:options with options set to use mmap. You would then use the byte pointer to read small chunks as you need them.
In case anyone got here and couldn't solve your problems, I figured out a way to do this.
You have to firstly write your ALAssetRepresentation to disk (as described here):
NSUInteger chunkSize = 100 * 1024;
NSString *tempFile = [NSTemporaryDirectory() stringByAppendingPathComponent:#"temp.tmp"];
uint8_t *chunkBuffer = malloc(chunkSize * sizeof(uint8_t));
NSUInteger length = [rep size];
NSFileHandle *fileHandle = [[NSFileHandle fileHandleForWritingAtPath: tempFile] retain];
if(fileHandle == nil) {
[[NSFileManager defaultManager] createFileAtPath:tempFile contents:nil attributes:nil];
fileHandle = [[NSFileHandle fileHandleForWritingAtPath:tempFile] retain];
}
NSUInteger offset = 0;
do {
NSUInteger bytesCopied = [rep getBytes:chunkBuffer fromOffset:offset length:chunkSize error:nil];
offset += bytesCopied;
NSData *data = [[NSData alloc] initWithBytes:chunkBuffer length:bytesCopied];
[fileHandle writeData:data];
[data release];
} while (offset < length);
[fileHandle closeFile];
[fileHandle release];
free(chunkBuffer);
chunkBuffer = NULL;
Then you have to create an NSData object that can map the disk without using memory resources (kind of like David's answer, but inspired by this answer):
NSError *error;
NSData *fileData = [NSData dataWithContentsOfFile:tempFile options:NSDataReadingMappedIfSafe error:&error];
if (!fileData) {
NSLog(#"Error %# %#", error, [error description]);
NSLog(#"%#", tempFile);
//do what you need with the error
}
EDIT Although, if you are uploading the file somewhere, you should open a connection and send small buffers of the file, kind of like what I did above. I had to write a C++ class to handle the socket and the connection
You probably shouldn't be trying to read the whole asset in one shot:
Byte *buffer = (Byte *)malloc([representation size]);
NSUInteger buffered = [representation getBytes:buffer fromOffset:0.0 length:[representation size] error:nil];
Instead, set up a loop and read from the asset in chunks. I've outlined the basic approach. You'll need to fill in a few gaps, but it should solve the memory issue.
You might also want to consider running this in a thread so you don't lock up the UI.
NSError error;
int bufferSize = 1000;
float offset=0.0;
//TODO: Open Connection
while (1)
{
Byte *buffer = (Byte *)malloc(bufferSize);
NSUInteger buffered = [representation getBytes:buffer fromOffset:offset length:bufferSize error:&error];
//TODO: Write data
//TODO: Increment offset, check errors
free(buffer);
//if (done){
//break;
//}
}
//TODO close eonnection
I want to convert the contents of NSMutableArray to NSData and then convert it to pdf.
I am using following code to conver NSdata but it gives error .I have searched many article but not getting anything
myArray=[[NSMutableArray alloc]init];
[myArray addObject:#"Jamshed"];
[myArray addObject:#"Imran"];
[myArray addObject:#"Ali"];
[myArray addObject:#"Hussain"];
[myArray addObject:#"Faisal"];
for (int i=0; i<[myArray count]; i++)
{
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:[myArray objectAtIndex:i]];
NSLog(#"data %#",data);
//create code for pdf file for write b4 read and concatenate readed string with data to write in pdf file.
}
- (NSData*) pdfDataWithSomeText;
{
// For more on generating PDFs, see http://developer.apple.com/library/ios/#documentation/2DDrawing/Conceptual/DrawingPrintingiOS/GeneratingPDF/GeneratingPDF.html
// The PDF content will be accumulated into this data object.
NSMutableData *pdfData = [NSMutableData data];
// Use the system default font.
UIFont *font = [UIFont systemFontOfSize:[UIFont systemFontSize]];
// Use the default page size of 612*792.
CGRect pageRect = CGRectMake(0, 0, 612, 792);
// Use the defaults for the document, and no metadata.
UIGraphicsBeginPDFContextToData(pdfData, CGRectZero, nil);
// Create a page.
UIGraphicsBeginPDFPageWithInfo(pageRect, nil);
// Store some placeholder text.
NSString *topLine = #"PDF Sample from";
NSString *bottomLine = #"http://stackoverflow.com/q/10122216/1318452";
// Draw that placeholder text, starting from the top left.
CGPoint topLeft = CGPointZero;
CGSize lineSize = [topLine sizeWithFont:font];
[topLine drawAtPoint:topLeft withFont:font];
// Move down by the size of the first line before drawing the second.
topLeft.y += lineSize.height;
[bottomLine drawAtPoint:topLeft withFont:font];
// Close the PDF context.
UIGraphicsEndPDFContext();
// The pdfData object has now had a complete PDF file written to it.
return pdfData;
}
Writing strings to a PDF is not as simple as generating NSData from those strings. Look at the Drawing and Printing Guide for iOS - Generating PDF Content. Yes, it is a big document. Read it. Try the examples from it. Try adding your own strings to their example. Then, if you have something that almost works, come back here to ask about it.
Generating the PDF
So here is the code from the link above, made even simpler by drawing with NSString instead of Core Text. It draws the input array, but will probably need to some better arithmetic. Can you make it draw in a more structured way?
- (NSData*) pdfDataWithStrings:(NSArray*) strings;
{
// For more on generating PDFs, see http://developer.apple.com/library/ios/#documentation/2DDrawing/Conceptual/DrawingPrintingiOS/GeneratingPDF/GeneratingPDF.html
strings = [strings arrayByAddingObject:#"https://stackoverflow.com/q/10122216/1318452"];
// The PDF content will be accumulated into this data object.
NSMutableData *pdfData = [NSMutableData data];
// Use the system default font.
UIFont *font = [UIFont systemFontOfSize:[UIFont systemFontSize]];
// Use the default page size of 612*792.
CGRect pageRect = CGRectMake(0, 0, 612, 792);
// Use the defaults for the document, and no metadata.
UIGraphicsBeginPDFContextToData(pdfData, CGRectZero, nil);
// Create a page.
UIGraphicsBeginPDFPageWithInfo(pageRect, nil);
// Add the strings within the space of the pageRect.
// If you want to draw the strings in a column or row, in order, you will need to change this bit.
for (NSString *line in strings)
{
// Hint: you will still need to know the lineSize.
CGSize lineSize = [line sizeWithFont:font];
CGFloat x = pageRect.origin.x + (arc4random_uniform(RAND_MAX)/(CGFloat) RAND_MAX*(pageRect.size.width-lineSize.width));
CGFloat y = pageRect.origin.y + (arc4random_uniform(RAND_MAX)/(CGFloat) RAND_MAX*(pageRect.size.height-lineSize.height));
CGPoint lineTopLeft = CGPointMake(x, y);
// Having worked out coordinates, draw the line.
[line drawAtPoint:lineTopLeft withFont:font];
}
// Close the PDF context.
UIGraphicsEndPDFContext();
// The pdfData object has now had a complete PDF file written to it.
return pdfData;
}
Saving to the Documents Directory
To save a document, you need to find the path where the user's documents are kept:
NSArray* paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsPath = [paths objectAtIndex:0];
You will be saving a file within that path. For your final app, let the user select their own name. For this example, the name will be build into the program.
NSString *pdfFilename = #"StackOverflow.pdf";
NSString has some excellent path manipulation methods, making it easy to construct the path you will be writing to.
NSString *pdfPath = [documentsPath stringByAppendingPathComponent:pdfFilename];
Then get the data you'll be writing to that path. Here I'll call the method declared above.
NSData *pdfData = [self pdfDataWithStrings:myArray];
Write those data to the path. For a better app, you may at some point want to call [pdfData writeToFile:options:error:] so you can display anything that went wrong.
[pdfData writeToFile:pdfPath atomically:NO];
How do you know if this worked? On the simulator, you can log the path you wrote to. Open this path in the Finder, and see if it contains the PDF you expect.
NSLog(#"Wrote PDF to %#", pdfPath);
On actual devices, you can enable iTunes File Sharing. See How to enable file sharing for my app?
You ca convert into json using
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:finalDict options:NSJSONWritingPrettyPrinted error:nil];
Before doing like
NSData *data = [NSKeyedArchiver archivedDataWithRootObject: myArray];
You convert array in to json string first
NSString *jsonString = [myArray JSONRepresentation];
You must Import json.h api first
myArray=[[NSMutableArray alloc]init];
[myArray addObject:#"Jamshed"];
[myArray addObject:#"Imran"];
[myArray addObject:#"Ali"];
[myArray addObject:#"Hussain"];
[myArray addObject:#"Faisal"];
for (int i=0; i<[myArray count]; i++)
{
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:[myArray objectAtIndex:i]];
NSLog(#"data %#",data);
//create code for pdf file for write b4 read and concatenate readed string with data to write in pdf file.
}
I am trying to implement the AudioFileStreamSeek feature on my streaming app. But there is no way I can get this running. Even Matt Gallagher said on his blog:
Icidentally, the AudioFileStreamSeek function seems completely broken. If you can't get it to work (as I couldn't) just seek to a new point in the file, set discontinuous to true and let AudioFileStream deal with it.
My code kindda looks like this but I can't get it to work:
NSString *path = [[NSString alloc] initWithContentsOfURL: url];
NSLog(#"url = %#", path);
SInt64 currentOffset;
UInt32 flags = 0;
OSStatus status = AudioFileStreamSeek( audioFileStream, 150, ¤tOffset, &flags );
NSLog(#"Setting next byte offset to: %qi, flags: %d", (long long)currentOffset, flags);
NSFileHandle *fileHandle = [NSFileHandle fileHandleForReadingAtPath: path];
// then read data from the new offset set by AudioFileStreamSeek
[fileHandle seekToFileOffset:currentOffset];
NSData * data = [fileHandle readDataOfLength:4096];
NSLog(#"data length %d, bytes %d", [data length], [data bytes]);
if (discontinuous)
{
err = AudioFileStreamParseBytes(audioFileStream, length, bytes, kAudioFileStreamParseFlag_Discontinuity);
if (err)
{
[self failWithErrorCode:AS_FILE_STREAM_PARSE_BYTES_FAILED];
return;
}
}
else
{
err = AudioFileStreamParseBytes(audioFileStream, length, bytes, 0);
if (err)
{
[self failWithErrorCode:AS_FILE_STREAM_PARSE_BYTES_FAILED];
return;
}
}
Please help...
Lacking any other solutions, what you can do is create an NSConnection and then as you receive the NSData you can effectively create streaming by processing each new chunk of NSData that you receive to your NSConnectionDelegate. NSConnection will make sure to send it to you in order, so you won't have to worry about getting it ordered correctly. Note though that, depending on your application, you may need to do this outside the main application thread, so that the user can still work with your app even if the download stalls and you have to rebuffer.