/** NSImage Load, manipulate and display images Copyright (C) 1996 Free Software Foundation, Inc. Author: Adam Fedor Date: Feb 1996 This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include "AppKit/NSImage.h" #include "AppKit/AppKitExceptions.h" #include "AppKit/NSBitmapImageRep.h" #include "AppKit/NSCachedImageRep.h" #include "AppKit/NSView.h" #include "AppKit/NSWindow.h" #include "AppKit/NSScreen.h" #include "AppKit/NSColor.h" #include "AppKit/NSPasteboard.h" #include "AppKit/NSPrintOperation.h" #include "AppKit/PSOperators.h" #include "GNUstepGUI/GSDisplayServer.h" BOOL NSImageForceCaching = NO; /* use on missmatch */ @implementation NSBundle (NSImageAdditions) - (NSString*) pathForImageResource: (NSString*)name { NSString *ext = [name pathExtension]; NSString *path = nil; if ((ext == nil) || [ext isEqualToString:@""]) { NSArray *types = [NSImage imageUnfilteredFileTypes]; unsigned c = [types count]; unsigned i; for (i = 0; path == nil && i < c; i++) { ext = [types objectAtIndex: i]; path = [self pathForResource: name ofType: ext]; } } else { name = [name stringByDeletingPathExtension]; path = [self pathForResource: name ofType: ext]; } return path; } @end @interface GSRepData : NSObject { @public NSImageRep *rep; NSImageRep *original; NSColor *bg; } @end @implementation GSRepData - (id) copyWithZone: (NSZone*)z { GSRepData *c = (GSRepData*)NSCopyObject(self, 0, z); if (c->rep) c->rep = [c->rep copyWithZone: z]; if (c->bg) c->bg = [c->bg copyWithZone: z]; return c; } - (void) dealloc { TEST_RELEASE(rep); TEST_RELEASE(bg); NSDeallocateObject(self); } @end /* Class variables and functions for class methods */ static NSMutableDictionary *nameDict = nil; static NSDictionary *nsmapping = nil; static NSColor *clearColor = nil; static Class cachedClass = 0; static Class bitmapClass = 0; static NSArray *iterate_reps_for_types(NSArray *imageReps, SEL method); /* Find the GSRepData object holding a representation */ static GSRepData* repd_for_rep(NSArray *_reps, NSImageRep *rep) { NSEnumerator *enumerator = [_reps objectEnumerator]; IMP nextImp = [enumerator methodForSelector: @selector(nextObject)]; GSRepData *repd; while ((repd = (*nextImp)(enumerator, @selector(nextObject))) != nil) { if (repd->rep == rep) { return repd; } } [NSException raise: NSInternalInconsistencyException format: @"Cannot find stored representation"]; /* NOT REACHED */ return nil; } @interface NSImage (Private) - (BOOL) _useFromFile: (NSString *)fileName; - (BOOL) _loadFromData: (NSData *)data; - (BOOL) _loadFromFile: (NSString *)fileName; - (NSImageRep*) _cacheForRep: (NSImageRep*)rep; - (NSImageRep*) _doImageCache; @end @implementation NSImage + (void) initialize { if (self == [NSImage class]) { NSString *path = [NSBundle pathForLibraryResource: @"nsmapping" ofType: @"strings" inDirectory: @"Images"]; // Initial version [self setVersion: 1]; // initialize the class variables nameDict = [[NSMutableDictionary alloc] initWithCapacity: 10]; if (path) nsmapping = RETAIN([[NSString stringWithContentsOfFile: path] propertyListFromStringsFileFormat]); clearColor = RETAIN([NSColor clearColor]); cachedClass = [NSCachedImageRep class]; bitmapClass = [NSBitmapImageRep class]; } } + (id) imageNamed: (NSString *)aName { NSString *realName = [nsmapping objectForKey: aName]; NSImage *image; if (realName) aName = realName; image = (NSImage*)[nameDict objectForKey: aName]; if (image == nil) { NSString *ext; NSString *path = nil; NSBundle *main_bundle; NSArray *array; NSString *the_name = aName; // FIXME: This should use [NSBundle pathForImageResource], but this will // only allow imageUnfilteredFileTypes. /* If there is no image with that name, search in the main bundle */ main_bundle = [NSBundle mainBundle]; ext = [aName pathExtension]; if (ext != nil && [ext length] == 0) { ext = nil; } /* Check if extension is one of the image types */ array = [self imageFileTypes]; if ([array indexOfObject: ext] != NSNotFound) { /* Extension is one of the image types So remove from the name */ the_name = [aName stringByDeletingPathExtension]; } else { /* Otherwise extension is not an image type So leave it alone */ the_name = aName; ext = nil; } /* First search locally */ if (ext) path = [main_bundle pathForResource: the_name ofType: ext]; else { id o, e; e = [array objectEnumerator]; while ((o = [e nextObject])) { path = [main_bundle pathForResource: the_name ofType: o]; if (path != nil && [path length] != 0) break; } } /* If not found then search in system */ if (!path) { if (ext) { path = [NSBundle pathForLibraryResource: the_name ofType: ext inDirectory: @"Images"]; } else { id o, e; e = [array objectEnumerator]; while ((o = [e nextObject])) { path = [NSBundle pathForLibraryResource: the_name ofType: o inDirectory: @"Images"]; if (path != nil && [path length] != 0) break; } } } if ([path length] != 0) { image = [[self allocWithZone: NSDefaultMallocZone()] initByReferencingFile: path]; if (image != nil) { [image setName: aName]; RELEASE(image); // Retained in dictionary. image->_flags.archiveByName = YES; } return image; } } return image; } - (id) init { return [self initWithSize: NSMakeSize(0, 0)]; } // Designated initializer for nearly everything. - (id) initWithSize: (NSSize)aSize { [super init]; //_flags.archiveByName = NO; //_flags.scalable = NO; //_flags.dataRetained = NO; //_flags.flipDraw = NO; if (aSize.width && aSize.height) { _size = aSize; _flags.sizeWasExplicitlySet = YES; } //_flags.usesEPSOnResolutionMismatch = NO; _flags.colorMatchPreferred = YES; _flags.multipleResolutionMatching = YES; //_flags.cacheSeparately = NO; //_flags.unboundedCacheDepth = NO; //_flags.syncLoad = NO; _reps = [[NSMutableArray alloc] initWithCapacity: 2]; ASSIGN(_color, clearColor); _cacheMode = NSImageCacheDefault; return self; } - (id) initByReferencingFile: (NSString *)fileName { self = [self init]; if (![self _useFromFile: fileName]) { RELEASE(self); return nil; } _flags.archiveByName = YES; return self; } - (id) initWithContentsOfFile: (NSString *)fileName { self = [self init]; _flags.dataRetained = YES; if (![self _loadFromFile: fileName]) { RELEASE(self); return nil; } return self; } - (id) initWithData: (NSData *)data { self = [self init]; _flags.dataRetained = YES; if (![self _loadFromData: data]) { RELEASE(self); return nil; } return self; } - (id)initWithBitmapHandle:(void *)bitmap { NSImageRep *rep = [[NSBitmapImageRep alloc] initWithBitmapHandle: bitmap]; if (rep == nil) { RELEASE(self); return nil; } self = [self init]; [self addRepresentation: rep]; RELEASE(rep); return self; } - (id)initWithIconHandle:(void *)icon { // Only needed on MS Windows NSImageRep *rep = [[NSBitmapImageRep alloc] initWithIconHandle: icon]; if (rep == nil) { RELEASE(self); return nil; } self = [self init]; [self addRepresentation: rep]; RELEASE(rep); return self; } - (id)initWithContentsOfURL:(NSURL *)anURL { NSArray *array = [NSImageRep imageRepsWithContentsOfURL: anURL]; if (!array) { RELEASE(self); return nil; } self = [self init]; _flags.dataRetained = YES; [self addRepresentations: array]; return self; } - (id) initWithPasteboard: (NSPasteboard *)pasteboard { NSArray *reps = [NSImageRep imageRepsWithPasteboard: pasteboard]; self = [self init]; if (reps != nil) [self addRepresentations: reps]; else { NSArray *array = [pasteboard propertyListForType: NSFilenamesPboardType]; NSString* file; if ((array == nil) || ([array count] == 0) || (file = [array objectAtIndex: 0]) == nil || ![self _loadFromFile: file]) { RELEASE(self); return nil; } } _flags.dataRetained = YES; return self; } - (void) dealloc { RELEASE(_reps); /* Make sure we don't remove name from the nameDict if we are just a copy of the named image, not the original image */ if (_name && self == [nameDict objectForKey: _name]) [nameDict removeObjectForKey: _name]; RELEASE(_name); TEST_RELEASE(_fileName); RELEASE(_color); [super dealloc]; } - (id) copyWithZone: (NSZone *)zone { NSImage *copy; NSArray *reps = [self representations]; NSEnumerator *enumerator = [reps objectEnumerator]; NSImageRep *rep; copy = (NSImage*)NSCopyObject (self, 0, zone); RETAIN(_name); RETAIN(_fileName); RETAIN(_color); copy->_lockedView = nil; // FIXME: maybe we should retain if _flags.dataRetained = NO copy->_reps = [[NSMutableArray alloc] initWithCapacity: [_reps count]]; // Only copy non-cached reps. while ((rep = [enumerator nextObject]) != nil) { if (![rep isKindOfClass: cachedClass]) { [copy addRepresentation: rep]; } } return copy; } - (BOOL) setName: (NSString *)aName { BOOL retained = NO; if (!aName || [nameDict objectForKey: aName]) return NO; if (_name && self == [nameDict objectForKey: _name]) { /* We retain self in case removing from the dictionary releases us */ RETAIN (self); retained = YES; [nameDict removeObjectForKey: _name]; } ASSIGN(_name, aName); [nameDict setObject: self forKey: _name]; if (retained) { RELEASE (self); } return YES; } - (NSString *) name { return _name; } - (void) setSize: (NSSize)aSize { // Optimized as this is called very often from NSImageCell if (NSEqualSizes(_size, aSize)) return; _size = aSize; _flags.sizeWasExplicitlySet = YES; [self recache]; } - (NSSize) size { if (_size.width == 0) { NSImageRep *rep = [self bestRepresentationForDevice: nil]; _size = [rep size]; } return _size; } - (BOOL) isFlipped { return _flags.flipDraw; } - (void) setFlipped: (BOOL)flag { _flags.flipDraw = flag; } // Choosing Which Image Representation to Use - (void) setUsesEPSOnResolutionMismatch: (BOOL)flag { _flags.useEPSOnResolutionMismatch = flag; } - (BOOL) usesEPSOnResolutionMismatch { return _flags.useEPSOnResolutionMismatch; } - (void) setPrefersColorMatch: (BOOL)flag { _flags.colorMatchPreferred = flag; } - (BOOL) prefersColorMatch { return _flags.colorMatchPreferred; } - (void) setMatchesOnMultipleResolution: (BOOL)flag { _flags.multipleResolutionMatching = flag; } - (BOOL) matchesOnMultipleResolution { return _flags.multipleResolutionMatching; } // Determining How the Image is Stored - (void) setCachedSeparately: (BOOL)flag { _flags.cacheSeparately = flag; } - (BOOL) isCachedSeparately { return _flags.cacheSeparately; } - (void) setDataRetained: (BOOL)flag { _flags.dataRetained = flag; } - (BOOL) isDataRetained { return _flags.dataRetained; } - (void) setCacheDepthMatchesImageDepth: (BOOL)flag { _flags.unboundedCacheDepth = flag; } - (BOOL) cacheDepthMatchesImageDepth { return _flags.unboundedCacheDepth; } - (void) setCacheMode: (NSImageCacheMode)mode { _cacheMode = mode; } - (NSImageCacheMode) cacheMode { return _cacheMode; } // Determining How the Image is Drawn - (BOOL) isValid { BOOL valid = NO; unsigned i, count; /* Go through all our representations and determine if at least one is a valid cache */ // FIXME: Not sure if this is correct count = [_reps count]; for (i = 0; i < count; i++) { GSRepData *repd = (GSRepData*)[_reps objectAtIndex: i]; if (repd->bg != nil || [repd->rep isKindOfClass: cachedClass] == NO) { valid = YES; break; } } return valid; } - (void) recache { unsigned i; i = [_reps count]; while(i--) { GSRepData *repd; repd = (GSRepData*)[_reps objectAtIndex: i]; if (repd->original != nil) { [_reps removeObjectAtIndex: i]; } } } - (void) setScalesWhenResized: (BOOL)flag { _flags.scalable = flag; } - (BOOL) scalesWhenResized { return _flags.scalable; } - (void) setBackgroundColor: (NSColor *)aColor { if (aColor == nil) { aColor = clearColor; } ASSIGN(_color, aColor); } - (NSColor *) backgroundColor { return _color; } // Using the Image - (void) compositeToPoint: (NSPoint)aPoint operation: (NSCompositingOperation)op { NSRect rect; // Might not be computed up to now NSSize size = [self size]; rect = NSMakeRect(0, 0, size.width, size.height); [self compositeToPoint: aPoint fromRect: rect operation: op]; } - (void) compositeToPoint: (NSPoint)aPoint fromRect: (NSRect)aRect operation: (NSCompositingOperation)op { NSImageRep *rep = nil; NS_DURING { if ([GSCurrentContext() isDrawingToScreen] == YES) rep = [self _doImageCache]; if (rep &&_cacheMode != NSImageCacheNever && [rep isKindOfClass: cachedClass]) { NSRect rect; float y = aPoint.y; rect = [(NSCachedImageRep *)rep rect]; NSDebugLLog(@"NSImage", @"composite rect %@ in %@", NSStringFromRect(rect), NSStringFromRect(aRect)); // Move the drawing rectangle to the origin of the image rep // and intersect the two rects. aRect.origin.x += rect.origin.x; aRect.origin.y += rect.origin.y; rect = NSIntersectionRect(aRect, rect); PScomposite(NSMinX(rect), NSMinY(rect), NSWidth(rect), NSHeight(rect), [[(NSCachedImageRep *)rep window] gState], aPoint.x, y, op); } else { NSRect rect; rep = [self bestRepresentationForDevice: nil]; rect = NSMakeRect(aPoint.x, aPoint.y, _size.width, _size.height); [self drawRepresentation: rep inRect: rect]; } } NS_HANDLER { NSLog(@"NSImage: compositeToPoint:fromRect:operation: failed due to %@: %@", [localException name], [localException reason]); if ([_delegate respondsToSelector: @selector(imageDidNotDraw:inRect:)]) { NSImage *image = [_delegate imageDidNotDraw: self inRect: aRect]; if (image != nil) [image compositeToPoint: aPoint fromRect: aRect operation: op]; } } NS_ENDHANDLER } - (void) compositeToPoint: (NSPoint)aPoint operation: (NSCompositingOperation)op fraction: (float)delta { NSRect rect; NSSize size = [self size]; rect = NSMakeRect(0, 0, size.width, size.height); [self compositeToPoint: aPoint fromRect: rect operation: op fraction: delta]; } - (void) compositeToPoint: (NSPoint)aPoint fromRect: (NSRect)srcRect operation: (NSCompositingOperation)op fraction: (float)delta { // FIXME We need another PS command for this } - (void) dissolveToPoint: (NSPoint)aPoint fraction: (float)aFloat { NSRect rect; NSSize size = [self size]; rect = NSMakeRect(0, 0, size.width, size.height); [self dissolveToPoint: aPoint fromRect: rect fraction: aFloat]; } - (void) dissolveToPoint: (NSPoint)aPoint fromRect: (NSRect)aRect fraction: (float)aFloat { NSImageRep *rep = nil; NS_DURING { if ([GSCurrentContext() isDrawingToScreen] == YES) rep = [self _doImageCache]; if (rep &&_cacheMode != NSImageCacheNever && [rep isKindOfClass: cachedClass]) { NSRect rect; float y = aPoint.y; rect = [(NSCachedImageRep *)rep rect]; // Move the drawing rectangle to the origin of the image rep // and intersect the two rects. aRect.origin.x += rect.origin.x; aRect.origin.y += rect.origin.y; rect = NSIntersectionRect(aRect, rect); PSdissolve(NSMinX(rect), NSMinY(rect), NSWidth(rect), NSHeight(rect), [[(NSCachedImageRep *)rep window] gState], aPoint.x, y, aFloat); } else { NSRect rect; /* FIXME: Here we are supposed to composite directly from the source but how do you do that? */ rep = [self bestRepresentationForDevice: nil]; rect = NSMakeRect(aPoint.x, aPoint.y, _size.width, _size.height); [self drawRepresentation: rep inRect: rect]; } } NS_HANDLER { NSLog(@"NSImage: dissolve failed due to %@: %@", [localException name], [localException reason]); if ([_delegate respondsToSelector: @selector(imageDidNotDraw:inRect:)]) { NSImage *image = [_delegate imageDidNotDraw: self inRect: aRect]; if (image != nil) [image dissolveToPoint: aPoint fromRect: aRect fraction: aFloat]; } } NS_ENDHANDLER } - (BOOL) drawRepresentation: (NSImageRep *)imageRep inRect: (NSRect)aRect { if (_color != nil) { NSRect fillrect = aRect; [_color set]; if ([[NSView focusView] isFlipped]) fillrect.origin.y -= _size.height; NSRectFill(fillrect); if ([GSCurrentContext() isDrawingToScreen] == NO) { /* Reset alpha for image drawing. */ [[NSColor whiteColor] set]; } } if (!_flags.scalable) return [imageRep drawAtPoint: aRect.origin]; return [imageRep drawInRect: aRect]; } - (void) drawAtPoint: (NSPoint)point fromRect: (NSRect)srcRect operation: (NSCompositingOperation)op fraction: (float)delta { //FIXME We need another PS command for this } - (void) drawInRect: (NSRect)dstRect fromRect: (NSRect)srcRect operation: (NSCompositingOperation)op fraction: (float)delta { //FIXME We need another PS command for this } - (void) addRepresentation: (NSImageRep *)imageRep { GSRepData *repd; repd = [GSRepData new]; repd->rep = RETAIN(imageRep); [_reps addObject: repd]; RELEASE(repd); } - (void) addRepresentations: (NSArray *)imageRepArray { unsigned i, count; GSRepData *repd; count = [imageRepArray count]; for (i = 0; i < count; i++) { repd = [GSRepData new]; repd->rep = RETAIN([imageRepArray objectAtIndex: i]); [_reps addObject: repd]; RELEASE(repd); } } - (void) removeRepresentation: (NSImageRep *)imageRep { unsigned i; GSRepData *repd; i = [_reps count]; while (i-- > 0) { repd = (GSRepData*)[_reps objectAtIndex: i]; if (repd->rep == imageRep) { [_reps removeObjectAtIndex: i]; } else if (repd->original == imageRep) { repd->original = nil; } } } - (void) lockFocus { [self lockFocusOnRepresentation: nil]; } - (void) lockFocusOnRepresentation: (NSImageRep *)imageRep { if (_cacheMode != NSImageCacheNever) { NSWindow *window; GSRepData *repd; if (imageRep == nil) imageRep = [self bestRepresentationForDevice: nil]; imageRep = [self _cacheForRep: imageRep]; repd = repd_for_rep(_reps, imageRep); window = [(NSCachedImageRep *)imageRep window]; _lockedView = [window contentView]; if (_lockedView == nil) [NSException raise: NSImageCacheException format: @"Cannot lock focus on nil rep"]; [_lockedView lockFocus]; /* Validate cached image */ if (repd->bg == nil) { repd->bg = [_color copy]; [_color set]; if ([_color alphaComponent] < 1) { /* With a Quartz-like alpha model, alpha can't be cleared with a rectfill, so we need to clear the alpha channel explictly. (A compositerect with NSCompositeCopy would be more efficient, but it doesn't seem like it's implemented correctly in all backends yet (as of 2002-08-23). Also, this will work with both the Quartz- and DPS-model.) */ PScompositerect(0, 0, _size.width, _size.height, NSCompositeClear); } NSRectFill(NSMakeRect(0, 0, _size.width, _size.height)); } } } - (void) unlockFocus { if (_lockedView != nil) { [_lockedView unlockFocus]; _lockedView = nil; } } /* Determine if the device is color or gray scale and find the reps of the same type */ - (NSMutableArray *) _bestRep: (NSArray *)reps withColorMatch: (NSDictionary*)deviceDescription { int colors = 3; NSImageRep* rep; NSMutableArray *breps; NSEnumerator *enumerator = [reps objectEnumerator]; NSString *colorSpace = [deviceDescription objectForKey: NSDeviceColorSpaceName]; if (colorSpace != nil) colors = NSNumberOfColorComponents(colorSpace); breps = [NSMutableArray array]; while ((rep = [enumerator nextObject]) != nil) { if ([rep colorSpaceName] || abs(NSNumberOfColorComponents([rep colorSpaceName]) - colors) <= 1) [breps addObject: rep]; } /* If there are no matches, pass all the reps */ if ([breps count] == 0) return (NSMutableArray *)reps; return breps; } /* Find reps that match the resolution of the device or return the rep that has the highest resolution */ - (NSMutableArray *) _bestRep: (NSArray *)reps withResolutionMatch: (NSDictionary*)deviceDescription { NSImageRep* rep; NSMutableArray *breps; NSSize dres; NSEnumerator *enumerator = [reps objectEnumerator]; NSValue *resolution = [deviceDescription objectForKey: NSDeviceResolution]; if (resolution) dres = [resolution sizeValue]; else dres = NSMakeSize(0, 0); breps = [NSMutableArray array]; while ((rep = [enumerator nextObject]) != nil) { /* FIXME: Not sure about checking resolution */ [breps addObject: rep]; } /* If there are no matches, pass all the reps */ if ([breps count] == 0) return (NSMutableArray *)reps; return breps; } /* Find reps that match the bps of the device or return the rep that has the highest bps */ - (NSMutableArray *) _bestRep: (NSArray *)reps withBpsMatch: (NSDictionary*)deviceDescription { NSImageRep* rep, *max_rep; NSMutableArray *breps; NSEnumerator *enumerator = [reps objectEnumerator]; int bps = [[deviceDescription objectForKey: NSDeviceBitsPerSample] intValue]; int max_bps; breps = [NSMutableArray array]; max_bps = 0; max_rep = nil; while ((rep = [enumerator nextObject]) != nil) { int rep_bps = 0; if ([rep respondsToSelector: @selector(bitsPerPixel)]) rep_bps = [(NSBitmapImageRep *)rep bitsPerPixel]; if (rep_bps > max_bps) { max_bps = rep_bps; max_rep = rep; } if (rep_bps == bps) [breps addObject: rep]; } if ([breps count] == 0 && max_rep != nil) [breps addObject: max_rep]; /* If there are no matches, pass all the reps */ if ([breps count] == 0) return (NSMutableArray *)reps; return breps; } - (NSMutableArray *) _representationsWithCachedImages: (BOOL)flag { unsigned count; if (_flags.syncLoad) { /* Make sure any images that were added with _useFromFile: are loaded in and added to the representation list. */ [self _loadFromFile: _fileName]; _flags.syncLoad = NO; } count = [_reps count]; if (count == 0) { return [NSArray array]; } else { id repList[count]; unsigned i, j; [_reps getObjects: repList]; j = 0; for (i = 0; i < count; i++) { if (flag || ((GSRepData*)repList[i])->original == nil) { repList[j] = ((GSRepData*)repList[i])->rep; j++; } } return [NSArray arrayWithObjects: repList count: j]; } } - (NSImageRep*) bestRepresentationForDevice: (NSDictionary*)deviceDescription { NSMutableArray *reps = [self _representationsWithCachedImages: NO]; if (deviceDescription == nil) { if ([GSCurrentContext() isDrawingToScreen] == YES) { int screen = [[[GSCurrentServer() attributes] objectForKey: GSScreenNumber] intValue]; deviceDescription = [[[NSScreen screens] objectAtIndex: screen] deviceDescription]; } else if ([NSPrintOperation currentOperation]) { /* FIXME: We could try to use the current printer, but there are many cases where might not be printing (EPS, PDF, etc) to a specific device */ } } if (_flags.colorMatchPreferred == YES) { reps = [self _bestRep: reps withColorMatch: deviceDescription]; reps = [self _bestRep: reps withResolutionMatch: deviceDescription]; } else { reps = [self _bestRep: reps withResolutionMatch: deviceDescription]; reps = [self _bestRep: reps withColorMatch: deviceDescription]; } reps = [self _bestRep: reps withBpsMatch: deviceDescription]; /* Pick an arbitrary representation if there is more than one */ return [reps lastObject]; } - (NSArray *) representations { return [self _representationsWithCachedImages: YES]; } - (void) setDelegate: anObject { _delegate = anObject; } - (id) delegate { return _delegate; } // Producing TIFF Data for the Image - (NSData *) TIFFRepresentation { return [bitmapClass TIFFRepresentationOfImageRepsInArray: [self representations]]; } - (NSData *) TIFFRepresentationUsingCompression: (NSTIFFCompression)comp factor: (float)aFloat { return [bitmapClass TIFFRepresentationOfImageRepsInArray: [self representations] usingCompression: comp factor: aFloat]; } // NSCoding - (void) encodeWithCoder: (NSCoder*)coder { BOOL flag; flag = _flags.archiveByName; [coder encodeValueOfObjCType: @encode(BOOL) at: &flag]; if (flag == YES) { /* * System image - just encode the name. */ [coder encodeValueOfObjCType: @encode(id) at: &_name]; } else { NSMutableArray *a; NSEnumerator *e; NSImageRep *r; /* * Normal image - encode the ivars */ [coder encodeValueOfObjCType: @encode(NSSize) at: &_size]; [coder encodeValueOfObjCType: @encode(id) at: &_color]; flag = _flags.scalable; [coder encodeValueOfObjCType: @encode(BOOL) at: &flag]; flag = _flags.dataRetained; [coder encodeValueOfObjCType: @encode(BOOL) at: &flag]; flag = _flags.flipDraw; [coder encodeValueOfObjCType: @encode(BOOL) at: &flag]; flag = _flags.sizeWasExplicitlySet; [coder encodeValueOfObjCType: @encode(BOOL) at: &flag]; flag = _flags.useEPSOnResolutionMismatch; [coder encodeValueOfObjCType: @encode(BOOL) at: &flag]; flag = _flags.colorMatchPreferred; [coder encodeValueOfObjCType: @encode(BOOL) at: &flag]; flag = _flags.multipleResolutionMatching; [coder encodeValueOfObjCType: @encode(BOOL) at: &flag]; flag = _flags.cacheSeparately; [coder encodeValueOfObjCType: @encode(BOOL) at: &flag]; flag = _flags.unboundedCacheDepth; [coder encodeValueOfObjCType: @encode(BOOL) at: &flag]; // FIXME: The documentation says to archive only the file name, // if not data retained! /* * Now encode an array of all the image reps (excluding cache) */ a = [NSMutableArray arrayWithCapacity: 2]; e = [[self representations] objectEnumerator]; while ((r = [e nextObject]) != nil) { if ([r isKindOfClass: cachedClass] == NO) { [a addObject: r]; } } [coder encodeValueOfObjCType: @encode(id) at: &a]; } } - (id) initWithCoder: (NSCoder*)coder { BOOL flag; _reps = [[NSMutableArray alloc] initWithCapacity: 2]; [coder decodeValueOfObjCType: @encode(BOOL) at: &flag]; if (flag == YES) { NSString *theName = [coder decodeObject]; RELEASE(self); self = RETAIN([NSImage imageNamed: theName]); } else { NSArray *a; [coder decodeValueOfObjCType: @encode(NSSize) at: &_size]; [coder decodeValueOfObjCType: @encode(id) at: &_color]; [coder decodeValueOfObjCType: @encode(BOOL) at: &flag]; _flags.scalable = flag; [coder decodeValueOfObjCType: @encode(BOOL) at: &flag]; _flags.dataRetained = flag; [coder decodeValueOfObjCType: @encode(BOOL) at: &flag]; _flags.flipDraw = flag; [coder decodeValueOfObjCType: @encode(BOOL) at: &flag]; _flags.sizeWasExplicitlySet = flag; [coder decodeValueOfObjCType: @encode(BOOL) at: &flag]; _flags.useEPSOnResolutionMismatch = flag; [coder decodeValueOfObjCType: @encode(BOOL) at: &flag]; _flags.colorMatchPreferred = flag; [coder decodeValueOfObjCType: @encode(BOOL) at: &flag]; _flags.multipleResolutionMatching = flag; [coder decodeValueOfObjCType: @encode(BOOL) at: &flag]; _flags.cacheSeparately = flag; [coder decodeValueOfObjCType: @encode(BOOL) at: &flag]; _flags.unboundedCacheDepth = flag; /* * get the image reps and add them. */ a = [coder decodeObject]; [self addRepresentations: a]; } return self; } - (id) awakeAfterUsingCoder: (NSCoder*)aDecoder { if (_name && [nameDict objectForKey: _name]) { return [nameDict objectForKey: _name]; } return self; } + (BOOL) canInitWithPasteboard: (NSPasteboard *)pasteboard { int i, count; NSArray* array = [NSImageRep registeredImageRepClasses]; count = [array count]; for (i = 0; i < count; i++) if ([[array objectAtIndex: i] canInitWithPasteboard: pasteboard]) return YES; return NO; } + (NSArray *) imageUnfilteredFileTypes { return iterate_reps_for_types([NSImageRep registeredImageRepClasses], @selector(imageUnfilteredFileTypes)); } + (NSArray *) imageFileTypes { return iterate_reps_for_types([NSImageRep registeredImageRepClasses], @selector(imageFileTypes)); } + (NSArray *) imageUnfilteredPasteboardTypes { return iterate_reps_for_types([NSImageRep registeredImageRepClasses], @selector(imageUnfilteredPasteboardTypes)); } + (NSArray *) imagePasteboardTypes { return iterate_reps_for_types([NSImageRep registeredImageRepClasses], @selector(imagePasteboardTypes)); } @end /* For every image rep, call the specified method to obtain an array of objects. Add these together, with duplicates weeded out. Used by imageUnfilteredPasteboardTypes, imageUnfilteredFileTypes, etc. */ static NSArray * iterate_reps_for_types(NSArray* imageReps, SEL method) { NSImageRep *rep; NSEnumerator *e; NSMutableArray *types; types = [NSMutableArray arrayWithCapacity: 2]; // Iterate through all the image reps e = [imageReps objectEnumerator]; rep = [e nextObject]; while (rep) { id e1; id obj; NSArray* pb_list; // Have the image rep perform the operation pb_list = [rep performSelector: method]; // Iterate through the returned array // and add elements to types list, duplicates weeded. e1 = [pb_list objectEnumerator]; obj = [e1 nextObject]; while (obj) { if ([types indexOfObject: obj] == NSNotFound) [types addObject: obj]; obj = [e1 nextObject]; } rep = [e nextObject]; } return (NSArray *)types; } @implementation NSImage (Private) - (BOOL)_loadFromData: (NSData *)data { BOOL ok; Class rep; ok = NO; rep = [NSImageRep imageRepClassForData: data]; if (rep && [rep respondsToSelector: @selector(imageRepsWithData:)]) { NSArray* array; array = [rep imageRepsWithData: data]; if (array) ok = YES; [self addRepresentations: array]; } else if (rep) { NSImageRep* image; image = [rep imageRepWithData: data]; if (image) ok = YES; [self addRepresentation: image]; } return ok; } - (BOOL) _loadFromFile: (NSString *)fileName { NSArray* array; array = [NSImageRep imageRepsWithContentsOfFile: fileName]; if (array) [self addRepresentations: array]; return (array) ? YES : NO; } - (BOOL) _useFromFile: (NSString *)fileName { NSArray *array; NSString *ext; NSFileManager *manager = [NSFileManager defaultManager]; if ([manager fileExistsAtPath: fileName] == NO) { return NO; } ext = [fileName pathExtension]; if (!ext) return NO; array = [isa imageFileTypes]; if ([array indexOfObject: ext] == NSNotFound) return NO; ASSIGN(_fileName, fileName); _flags.syncLoad = YES; return YES; } // Cache the bestRepresentation. If the bestRepresentation is not itself // a cache and no cache exists, create one and draw the representation in it // If a cache exists, but is not valid, redraw the cache from the original // image (if there is one). - (NSImageRep *)_doImageCache { NSImageRep *rep = [self bestRepresentationForDevice: nil]; if (_cacheMode != NSImageCacheNever) { GSRepData *repd; rep = [self _cacheForRep: rep]; repd = repd_for_rep(_reps, rep); NSDebugLLog(@"NSImage", @"Cached image rep is %d", (int)rep); /* * if the cache is not valid, it's background color will not exist * and we must draw the background then render from the original * image rep into the cache. */ if (repd->bg == nil) { [self lockFocusOnRepresentation: rep]; [self drawRepresentation: repd->original inRect: NSMakeRect(0, 0, _size.width, _size.height)]; [self unlockFocus]; if (_color != nil && [_color alphaComponent] != 0.0) { repd->bg = [_color copy]; } else { repd->bg = [clearColor copy]; } if ([repd->bg alphaComponent] == 1.0) { [rep setOpaque: YES]; } else { [rep setOpaque: [repd->original isOpaque]]; } NSDebugLLog(@"NSImage", @"Rendered rep %d on background %@", (int)rep, repd->bg); } } return rep; } - (NSImageRep*) _cacheForRep: (NSImageRep*)rep { /* * If this is not a cached image rep - create a cache to be used to * render the image rep into, and switch to the cached rep. */ if ([rep isKindOfClass: cachedClass] == NO) { NSImageRep *cacheRep = nil; unsigned count = [_reps count]; if (count > 0) { GSRepData *invalidCache = nil; GSRepData *partialCache = nil; GSRepData *validCache = nil; GSRepData *reps[count]; unsigned partialCount = 0; unsigned i; BOOL opaque = [rep isOpaque]; [_reps getObjects: reps]; /* * Search the cached image reps for any whose original is our * 'best' image rep. See if we can notice any invalidated * cache as we go - if we don't find a valid cache, we want to * re-use an invalidated one rather than creating a new one. * NB. If the image rep is opaque, then any cached rep is valid * irrespective of the background color it was drawn with. */ for (i = 0; i < count; i++) { GSRepData *repd = reps[i]; if (repd->original == rep && repd->rep != rep) { if (repd->bg == nil) { NSDebugLLog(@"NSImage", @"Invalid %@ ... %@ %d", repd->bg, _color, repd->rep); invalidCache = repd; } else if (opaque == YES || [repd->bg isEqual: _color] == YES) { NSDebugLLog(@"NSImage", @"Exact %@ ... %@ %d", repd->bg, _color, repd->rep); validCache = repd; break; } else { NSDebugLLog(@"NSImage", @"Partial %@ ... %@ %d", repd->bg, _color, repd->rep); partialCache = repd; partialCount++; } } } if (validCache != nil) { if (NSImageForceCaching == NO && [rep isOpaque] == NO) { /* * If the image rep is not opaque and we are drawing * without an opaque background then the cache can't * really be valid 'cos we might be drawing transparency * on top of anything. So we invalidate the cache by * removing the background color information. */ if ([validCache->bg alphaComponent] != 1.0) { DESTROY(validCache->bg); } } cacheRep = validCache->rep; } else if (partialCache != nil && partialCount > 2) { /* * Only re-use partially correct caches if there are already * a few partial matches - otherwise we fall default to * creating a new cache. */ if (NSImageForceCaching == NO && [rep isOpaque] == NO) { if (invalidCache != nil) { /* * If there is an unused cache - use it rather than * re-using this one, since we might get a request * to draw with this color again. */ partialCache = invalidCache; } else { DESTROY(partialCache->bg); } } cacheRep = partialCache->rep; } else if (invalidCache != nil) { cacheRep = invalidCache->rep; } } if (cacheRep == nil) { NSScreen *cur = [NSScreen mainScreen]; NSSize imageSize; GSRepData *repd; imageSize = [self size]; if (imageSize.width == 0 || imageSize.height == 0) return nil; cacheRep = [[cachedClass alloc] initWithSize: _size depth: [cur depth] separate: NO alpha: NO]; [self addRepresentation: cacheRep]; RELEASE(cacheRep); /* Retained in _reps array. */ repd = repd_for_rep(_reps, cacheRep); repd->original = rep; } return cacheRep; } else { return rep; } } @end