What's the best way to produce thumbnails?

Discussion in 'Mac Programming' started by wilbur07, Aug 5, 2008.

  1. wilbur07 macrumors newbie

    Joined:
    Jun 18, 2008
    #1
    I'm trying to write a Cocoa application that will run from command line that does this:

    read any picture file format (including .pdf and .eps files!), generate a thumbnail and save the thumbnail as a .jpg file

    Basically I want to call this cocoa app from a PHP webpage so that I can view thumbnails and previews of 1 Gig files saved as .eps files. Using normal php doesn't work with .eps files, and simply reading a very large .eps file to convert it to a thumbnail crashes the system or at least takes a very long time.

    I want to use the Quartz that generates thumbnails in Finder out of .eps files and use it to generate PHP readable .jpg files to view on a web browser.

    What is the best way to do this (I'm somewhat of a newbie at Cocoa)? The NSImage class doesn't seem to have the method that simply reads files for thumbnails -- you have to load the whole file before you can process an image -- or correct me if I'm wrong in this.

    thanks in advance
     
  2. kainjow Moderator emeritus

    kainjow

    Joined:
    Jun 15, 2000
    #2
    First you should check to see if there's a thumbnail stored in the image (very unlikely). Next, you could use Quick Look to get an pre-existing thumbnail with QLThumbnailImageCreate(). If those options don't work out, you'll just have to load the image and draw it at a smaller size manually.
     
  3. AlmostThere macrumors 6502a

    #3
    Isn't it easier to use one of the existing command line programs like sips or convert (imagemagick)?

    (edit)also epstopdf, you could write a shell script to select the right utility if you need multiple programs to cover all formats, I don't know the full list support by the first two examples
     
  4. plinden macrumors 68040

    plinden

    Joined:
    Apr 8, 2004
    #4
    Or, select multiple pictures, open in Preview - go to Tools/Adjust Size, select Other and set pixel size. All the pictures you selected get resized at the same time. To do PDFs, save as images and reload and resize.

    Ok, it's not command line, but why do you need a command line program?

    Edit: do you want to do it on the fly?
     
  5. lee1210 macrumors 68040

    lee1210

    Joined:
    Jan 10, 2005
    Location:
    Dallas, TX
    #5
    the OP said it's for use in a PHP script, so the PHP will run the command, then display the result (so yes to on the fly, also).

    This seems questionable at best, but maybe it's for an intranet thing?

    The issue is that were you will need to host the PHP on a Mac (not a big deal, Apache should be up to the job), the Mac will need to have (preferably via local storage) access to the image, and it will need to have lots of CPU to spare for each request to actually generate the thumbnail. it will also need to have the photoshop, etc. quick look plugins installed, which probably require a license to photoshop. This means the machine will need to have a photoshop license, be a mac, have local access to the very large files, and have lots of spare CPU. This seems to be a big demand to show some thumbnails.

    It's an interesting challenge, but probably a better idea is that when the files are saved, the user simply saves a tiny JPG, too, and names it in a predictable fashion compared to the original file (i.e. every filename file.eps will have a thumbnail named file_thumb.jpg).

    -Lee
     
  6. wilbur07 thread starter macrumors newbie

    Joined:
    Jun 18, 2008
    #6
    The only problem with this is that preview images for 1 Gb files are hardly available and also the fact that I'm using Tiger so no Quick Look methods for me.

    No, this avenue has been explored. The server hangs when you try to use Image Magick or Ghostscript on files larger than 200 MB, and at my company we work with very large graphics files. The avenue I'm exploring right now uses the Quartz functionality found in the finder which generates preview thumbnails very fast -- that's the reason for Cocoa.

    Have to do it on the fly because I'm writing a PHP web application that allows users to edit advertising pages from Adobe Indesign -- swapping in .eps files and so forth. All I have to work with are the original graphics, there's nobody here to open each file and generate thumbnails manually.

    We are using Macs here at the company with plenty of CPU on the webserver.

    I have the app written only it's too slow with very large files since it uses NSImage method to read files and then resize them into a thumbnail. What I'm looking for are the methods in a particular framework that would generate thumbnails very quickly. I know this can be done because when you look at the files in Finder it generates the thumbnails quickly on the fly; I was hoping someone could suggest what the Quartz methods were that are used in Finder or something like ImageIO framework known to do the same thing very quickly.
     
  7. lazydog macrumors 6502a

    Joined:
    Sep 3, 2005
    Location:
    Cramlington, UK
    #7
    Hi

    You could write a cgi that uses the Image framework to read an image and return a thumbnail of it. There would be no need to go through PHP (but you could if you really wanted to). In your html page, the urls for your thumbnails would consist of the path to the cgi and the name of the image file to process as an url parameter. Your cgi would pick up this parameter, read the appropriate image file, resize the image to thumbnail size and then spit it out with the appropriate htpp headers. The Image framework can read PDF and EPS files so I don't think you need to worry about anything else.

    1G image files might take a bit of time to process. I guess you'll know for yourself whether performance is acceptable for your web site. If not, then you could cache the results of the cgi. A simple way would be to save each thumbnail into a thumbnail directory after it is created. That way the next time the thumbnail is requested, you can read the thumbnail directly from the thumbnail directory thus avoiding regenerate it all over again.

    The other alternative is to write a tool or script that rips through a directory and builds thumbnails of all images. You could even use a Photoshop batch script to do this. The problem with this approach is you need to remember to rerun the tool or script whenever you add new images.

    b e n
     
  8. lee1210 macrumors 68040

    lee1210

    Joined:
    Jan 10, 2005
    Location:
    Dallas, TX
    #8
    Here are the breakpoints finder hit when I brought up Get Info, then chose to preview a TIFF:

    Code:
    Breakpoint 115, 0x92d5f65d in NavCreatePreviewReference ()
    Breakpoint 140, 0x92d5fc64 in TPreview::TPreview ()
    Breakpoint 116, 0x92d5fd93 in NavSetupPreview ()
    Breakpoint 142, 0x92d5fe15 in TPreview::SetWindow ()
    Breakpoint 143, 0x92d5ff97 in TPreview::SetFile ()
    Breakpoint 144, 0x92d60078 in TPreview::ClearPreviewData ()
    Breakpoint 150, 0x92d783c2 in TPreview::CanPreview ()
    Breakpoint 117, 0x92d982a0 in NewNavLoadPreviewUPP ()
    Breakpoint 118, 0x92d8fad0 in NavLoadPreview ()
    Breakpoint 211, 0x92d92cf4 in TPreview::LoadPreview ()
    Breakpoint 210, 0x92d92ac1 in TPreview::CachePreviewData ()
    Breakpoint 184, 0x92d914ad in TPreview::GetPreviewTypeInfo ()
    Breakpoint 196, 0x92d92064 in TPreview::CreatePreviewData ()
    Breakpoint 206, 0x92d92979 in TPicturePreviewHandler::TPicturePreviewHandler ()
    Breakpoint 202, 0x92d92834 in TPnotPreviewHandler::TPnotPreviewHandler ()
    Breakpoint 174, 0x92d9120b in TPnotPreviewHandler::InitComponent ()
    Breakpoint 358, 0x99767933 in PictPreviewComponentDispatch ()
    Breakpoint 354, 0x997676f1 in PictPreviewOpen ()
    Breakpoint 176, 0x92d91300 in TPnotPreviewHandler::SetPreviewDimensions ()
    Breakpoint 229, 0x92d9e3f5 in TPreviewDataHandler::Status ()
    Breakpoint 238, 0x92d9e451 in TPicturePreviewHandler::IsPreviewValid ()
    [Switching to process 217 thread 0x947f]
    Breakpoint 200, 0x92d923fa in TPreview::LoadPreviewProc ()
    Breakpoint 224, 0x92d982b0 in InvokeNavLoadPreviewUPP ()
    Breakpoint 15, 0x9190c4a2 in CGImageSourceCreateThumbnailAtIndex ()
    Breakpoint 176, 0x92d91300 in TPnotPreviewHandler::SetPreviewDimensions ()
    Breakpoint 224, 0x92d982b0 in InvokeNavLoadPreviewUPP ()
    [Switching to process 217 thread 0xd03]
    Breakpoint 113, 0x92d8fb58 in NavSetPreviewArea ()
    Breakpoint 193, 0x92d91acb in TPreview::SetBounds ()
    Breakpoint 181, 0x92d913ef in TPicturePreviewHandler::AllowFrameNegotiation ()
    Breakpoint 198, 0x92d92179 in TPicturePreviewHandler::HeightForWidth ()
    Breakpoint 197, 0x92d9212f in TPicturePreviewHandler::WidthForHeight ()
    Breakpoint 113, 0x92d8fb58 in NavSetPreviewArea ()
    Breakpoint 193, 0x92d91acb in TPreview::SetBounds ()
    Breakpoint 181, 0x92d913ef in TPicturePreviewHandler::AllowFrameNegotiation ()
    Breakpoint 185, 0x92d9158e in TPreview::DrawPreview ()
    Breakpoint 195, 0x92d91ee2 in TPicturePreviewHandler::Draw ()
    Breakpoint 176, 0x92d91300 in TPnotPreviewHandler::SetPreviewDimensions ()
    CGImageSourceCreateThumbnailAtIndex seems like a good place to start:
    http://developer.apple.com/document...ef/c/func/CGImageSourceCreateThumbnailAtIndex

    Doesn't seem like Finder is doing any magic. You should definitely cache thumbnails, but you have to be sure to check if the source was modified before relying on the cached thumbnail. TPicturePreviewHandler::IsPreviewValid seems to do this when you're looking in the finder.

    BTW, the only breakpoints i set were ones that matched "humbn" and "review". There might be some other juicy stuff in there, but I figured these were my best bets.

    -Lee
     
  9. wilbur07 thread starter macrumors newbie

    Joined:
    Jun 18, 2008
    #9
    Re: What's the best way to produce thumbnails?

    I managed to solve my problem using resource forks.

    If you look in the resource fork of adobe saved files (such as .eps which is what I'm specifically programming for) there's a PICT representation of each file that you can extract which is much smaller than the original file. Using that PICT data you can import as image in Carbon/Cocoa framework and then save as a resized jpeg file very quickly, for the generation of thumbnails. I am including the code here:

    //
    // main.m
    // imageconverter2
    //
    // Created by James Constantino on 8/5/08.
    // Copyright __MyCompanyName__ 2008. All rights reserved.
    //

    #import <Cocoa/Cocoa.h>

    #import <Carbon/Carbon.h>

    #import <ApplicationServices/ApplicationServices.h>

    @interface NSImage (Extras)

    // creates a copy of the current image while maintaining
    // proportions. also centers image, if necessary

    - (NSImage*)imageByScalingProportionallyToSize:(NSSize)aSize;

    @end

    @implementation NSImage (Extras)

    - (NSImage*)imageByScalingProportionallyToSize:(NSSize)targetSize
    {
    NSImage* sourceImage = self;
    NSImage* newImage = nil;

    NSAutoreleasePool* poool = [[NSAutoreleasePool alloc] init];

    if ([sourceImage isValid])
    {
    NSSize imageSize = [sourceImage size];
    float width = imageSize.width;
    float height = imageSize.height;

    float targetWidth = targetSize.width;
    float targetHeight = targetSize.height;

    // scaleFactor will be the fraction that we'll
    // use to adjust the size. For example, if we shrink
    // an image by half, scaleFactor will be 0.5. the
    // scaledWidth and scaledHeight will be the original,
    // multiplied by the scaleFactor.
    //
    // IMPORTANT: the "targetHeight" is the size of the space
    // we're drawing into. The "scaledHeight" is the height that
    // the image actually is drawn at, once we take into
    // account the ideal of maintaining proportions

    float scaleFactor = 0.0;
    float scaledWidth = targetWidth;
    float scaledHeight = targetHeight;

    NSPoint thumbnailPoint = NSMakePoint(0,0);

    // since not all images are square, we want to scale
    // proportionately. To do this, we find the longest
    // edge and use that as a guide.

    if ( NSEqualSizes( imageSize, targetSize ) == NO )
    {
    // use the longeset edge as a guide. if the
    // image is wider than tall, we'll figure out
    // the scale factor by dividing it by the
    // intended width. Otherwise, we'll use the
    // height.

    float widthFactor = targetWidth / width;
    float heightFactor = targetHeight / height;

    if ( widthFactor < heightFactor )
    scaleFactor = widthFactor;
    else
    scaleFactor = heightFactor;

    // ex: 500 * 0.5 = 250 (newWidth)

    scaledWidth = width * scaleFactor;
    scaledHeight = height * scaleFactor;

    // center the thumbnail in the frame. if
    // wider than tall, we need to adjust the
    // vertical drawing point (y axis)

    if ( widthFactor < heightFactor )
    thumbnailPoint.y = (targetHeight - scaledHeight) * 0.5;

    else if ( widthFactor > heightFactor )
    thumbnailPoint.x = (targetWidth - scaledWidth) * 0.5;
    }


    // create a new image to draw into
    newImage = [[NSImage alloc] initWithSize:targetSize];

    // once focus is locked, all drawing goes into this NSImage instance
    // directly, not to the screen. It also receives its own graphics
    // context.
    //
    // Also, keep in mind that we're doing this in a background thread.
    // You only want to draw to the screen in the main thread, but
    // drawing to an offscreen image is (apparently) okay.

    [newImage lockFocus];

    NSRect thumbnailRect;
    thumbnailRect.origin = thumbnailPoint;
    thumbnailRect.size.width = scaledWidth;
    thumbnailRect.size.height = scaledHeight;

    [sourceImage drawInRect: thumbnailRect
    fromRect: NSZeroRect
    operation: NSCompositeSourceOver
    fraction: 1.0];

    [newImage unlockFocus];

    }
    [poool release];

    return [newImage autorelease];
    }

    @end


    int main(int argc, char *argv[])
    {
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

    NSApp = [NSApplication sharedApplication];

    NSString *filePath = [[NSString alloc] init];

    NSString *outPath = [[NSString alloc] init];

    NSData *photoData = [[NSData alloc] init];

    NSArray *args = [[NSProcessInfo processInfo] arguments];

    if ([args count]>1) {

    filePath = [args objectAtIndex:1];

    if ([args count]>2) {
    outPath = [args objectAtIndex:2];
    }
    else outPath = @"/Users/jamesc/desktop/temporarypic.jpg";

    NSFileManager *fileManager = [NSFileManager defaultManager];

    if (![fileManager fileExistsAtPath:filePath]) {
    NSLog(@"Image does not already exist!");
    }

    [fileManager release];
    }
    else filePath = nil;

    NSLog(@"view should show %@",filePath);

    char filepth[256];
    strcpy(filepth, [filePath cString]);
    FSSpec ResFileSpec;
    FSRef ResFileRef;
    OSErr err;
    err = FSPathMakeRef((const UInt8*)filepth, &ResFileRef, false);
    OSErr SpecErr;
    SpecErr = FSGetCatalogInfo(&ResFileRef, kFSCatInfoNone, NULL, NULL, &ResFileSpec, NULL);
    short Ref;
    Ref = FSpOpenResFile(&ResFileSpec, fsRdWrPerm);
    OSErr ResErr = ResError();
    NSLog(@"ResErr is: %i",ResErr);
    NSLog(@"Ref is: %i",Ref);
    //if (ResErr < 0) return(ResErr);
    //else return Ref;

    OSType resTypePict = 'PICT';

    UseResFile(Ref);
    Handle theResHandle = Get1Resource(resTypePict, 256); // Where TYPE_DESC is a constant defined to equal the dësc ResType, which happens to be 1687253859.

    NSData *theResourceData;

    theResourceData = [[NSData alloc] initWithBytes:*theResHandle length:GetMaxResourceSize(theResHandle)];


    NSImage *imageFromBundle = [[NSImage alloc] initWithData:theResourceData];

    if ([imageFromBundle isValid])
    {
    //NSImage* thumbnail = [imageFromBundle imageByScalingProportionallyToSize:NSMakeSize(150,150)];

    NSImage* thumbnail = imageFromBundle;

    NSBitmapImageRep *imageRep = [NSBitmapImageRep imageRepWithData:[thumbnail TIFFRepresentation]];

    [thumbnail release];

    NSDictionary *imageProps = [NSDictionary dictionaryWithObject:[NSNumber numberWithFloat:0.9] forKey:NSImageCompressionFactor];

    photoData = [imageRep representationUsingType:NSJPEGFileType properties:imageProps];

    [imageProps release];

    [imageRep release];

    //save file
    [photoData writeToFile:eek:utPath atomically:NO];

    [photoData release];
    }
    else NSLog(@"imagefromBundle is not valid");

    [imageFromBundle release];



    [NSApp release];
    [pool drain];
    return(EXIT_SUCCESS);
    }
     

Share This Page