0

I'm struggling with the problem to draw an eps file on a NSView. When I first load the eps file from a file and draw it with drawInRect: the image is displayed correctly. However, the image will not be drawn when I load it from an archive file.

I've prepared a dirty small example that you can copy/paste and try out. Create a new Cocoa App project and add this to the delegate method.

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
  // Just a sample eps file
  NSURL *url = [NSURL URLWithString: @"http://embedded.eecs.berkeley.edu/concurrency/latex/figure.eps"];
  NSImage *epsImage = [[[NSImage alloc] initWithContentsOfURL: url] autorelease];

  // Encode data
  NSMutableData *mutableData = [[[NSMutableData alloc] init] autorelease];
  NSKeyedArchiver *coder = [[[NSKeyedArchiver alloc] initForWritingWithMutableData: mutableData] autorelease];
  [coder encodeObject: epsImage forKey: @"image"];
  [coder finishEncoding];
  NSString *dataFile = [@"~/Desktop/image.data" stringByExpandingTildeInPath];
  [mutableData writeToFile: dataFile atomically: YES];

  // Decode data
  NSData *data = [NSData dataWithContentsOfFile: dataFile];
  NSKeyedUnarchiver *decoder = [[NSKeyedUnarchiver alloc] initForReadingWithData: data];
  NSImage *loadedImage = [decoder decodeObjectForKey: @"image"];

  // Draw image
  NSRect rect;
  rect.origin = NSZeroPoint;
  rect.size = loadedImage.size;

  NSView *view = [[NSApp mainWindow] contentView];
  [view lockFocus];
  [loadedImage drawInRect: rect fromRect: rect operation: NSCompositeSourceOver fraction: 1.0];
  [view unlockFocus];
}

To prove that the first loaded image draws correctly just change the line [loadedImage drawInRect:...] to [epsImage drawInRect:...].

I'm using NSKeyedArchiver and NSKeyedUnarchiver here for simulating encodeWithCoder: and initWithCoder:. So please focus on the fact that NSImage with NSEPSImageRep representation, which does not contain a preview (from a resource fork?) and loaded purely as eps commands, is not drawn on a NSView correctly.

Any help is appreciated.

cocoafan
  • 4,884
  • 4
  • 37
  • 45
  • After you load in the `loadedImage`, what `NSImageRep`s are attached to it? You should be able to `NSLog` the description of both `loadedImage` and its image reps to see what is there. – gaige Jun 10 '13 at 09:27
  • Hi @gaige, it is exactly the same NSImageRep (NSEPSImageRep) with exactly the same data content. Sorry that I didn't mention it before. Both images contain only one representation, NSEPSImageRep. – cocoafan Jun 10 '13 at 09:33
  • That's definitely strange. We have an app that saves/loads EPS from an `NSKeyedArchiver` and we haven't had trouble with it. However, we do burst the `NSImage` and save the `NSEPSImageRep` directly to make sure that we're getting the highest fidelity (once cacheing got involved, we had issues getting the best version as opposed to the smallest version). Some of those issues may have been on older versions of OSX, as that code is 3-4 years old at this point. Do you know the file is going to be EPS? In that case, can you just save the `NSData` version instead of the `NSImage`? – gaige Jun 10 '13 at 10:18
  • I tried that too. The given code is a complete sample to reproduce it. It will definitely not draw when loaded from `NSKeyedArchiver`. I tried to archive `NSEPSImageRep` instead of `NSImage` and constructed `NSImage` with the loaded `NSEPSImageRep` again, with no luck. I also didn't encounter this kind of problem before. The actual code is pretty old and worked before. I guess it started not to work after setting the Base SDK to 10.8 in Xcode (but not sure about that, changing it to 10.7 SDK didn't help either). – cocoafan Jun 10 '13 at 10:40
  • To clarify, I wasn't actually saving the `NSEPSImageRep`, I was saving the EPS data using `[(NSEPSImageRep*)rep EPSRepresentation]`, basically the equivalent of saving the original EPS data (in theory). This continues to work for me. – gaige Jun 10 '13 at 11:28
  • @gaige. you're my hero. Thanks. That did the trick. I will accept your answer when you write one. Also My `NSImage` may contain any `NSImageRep` representation. Is there an elegant and more general way (not specific to EPS) to save and load the representations? – cocoafan Jun 10 '13 at 11:49
  • I added an answer, along with the code we use to do preferential saving based on some heuristics we used. – gaige Jun 10 '13 at 12:00

1 Answers1

2

Due to the way that cacheing works on NSImage, I've often found it more effective to actually grab the NSImageRep if I know what the type is.

In our code, we found that the most reliable way to save off images is in their original format, but that requires either saving off the data in its original format somewhere else, or requesting the data from the NSImageRep. Unfortunately, there's not a generic -(NSData*)data method of NSImageRep, so we ended up specifically checking for various types of NSImageRep and saving them off depending on what we knew them to be.

Fortunately, loading is simple, as NSImage::initWithData: will figure out the type based on the data.

Here's our long-standing code for doing this. Basically, it prefers PDF then EPS then it makes a TIFF of anything it doesn't understand.

+ (NSData*) dataWithImage:(NSImage*)image kindString:( NSString**)kindStringPtr
{
    if (!image)
        return nil;

    NSData *pdfData=nil, *newData=nil, *epsData=nil, *imageData=nil;;
    NSString *kindString=nil;
    NSArray *reps = [image representations];
    for (NSImageRep *rep in reps) {
        if ([rep isKindOfClass: [NSPDFImageRep class]]) {
            // we have a PDF, so save that
            pdfData = [(NSPDFImageRep*)rep PDFRepresentation];
            PDFDocument *doc = [[PDFDocument alloc] initWithData:pdfData];
            newData = [doc dataRepresentation];
            if (newData && ([newData length]<[pdfData length])) {
                pdfData = newData;
            }
            break;
        }
        if ([rep isKindOfClass: [NSEPSImageRep class]]) {
            epsData = [(NSEPSImageRep*)rep EPSRepresentation];
            break;
        }
    }

    if (pdfData) {
        imageData=pdfData;
        kindString= @"pdfImage";
    } else if (epsData) {
        imageData=epsData;
        kindString=@"epsImage";
    } else {
        // make a big copy
        NSBitmapImageRep *rep0 = [reps objectAtIndex:0];
        if ([rep0 isKindOfClass: [NSBitmapImageRep class]]) {
            [image setSize: NSMakeSize( [rep0 pixelsWide], [rep0 pixelsHigh])];
        }

        imageData = [image TIFFRepresentation];
        kindString=@"tiffImage";
    }

    if (kindStringPtr)
        *kindStringPtr=kindString;
    return imageData;
}

Once we have the NSData*, it can be saved in a keyed archive, written to disk or whatever.

On the way back in, load in the NSData* and then

NSImage *image = [[NSImage alloc] initWithData: savedData];

and you should be all set.

gaige
  • 17,263
  • 6
  • 57
  • 68