/** NSImage Load, manipulate and display images Copyright (C) 1996-2016 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 Lesser 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; see the file COPYING.LIB. If not, see or write to the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "config.h" #include #include #import #import #import #import #import #import #import #import #import #import #import #import "AppKit/NSImage.h" #import "AppKit/AppKitExceptions.h" #import "AppKit/NSAffineTransform.h" #import "AppKit/NSBitmapImageRep.h" #import "AppKit/NSCachedImageRep.h" #import "AppKit/NSColor.h" #import "AppKit/NSPasteboard.h" #import "AppKit/NSPrintOperation.h" #import "AppKit/NSScreen.h" #import "AppKit/NSView.h" #import "AppKit/NSWindow.h" #import "AppKit/DPSOperators.h" #import "GNUstepGUI/GSDisplayServer.h" #import "GSThemePrivate.h" BOOL NSImageForceCaching = NO; /* use on missmatch */ static NSDictionary *nsmapping = nil; // OS_API_VERSION(MAC_OS_X_VERSION_10_5, GS_API_LATEST) NSString *const NSImageNameQuickLookTemplate = @"NSQuickLookTemplate"; NSString *const NSImageNameBluetooth = @"NSBluetoothTemplate"; NSString *const NSImageNameIChatTheater = @"NSIChatTheaterTemplate"; NSString *const NSImageNameSlideshow = @"NSSlideshowTemplate"; NSString *const NSImageNameAction = @"NSActionTemplate"; NSString *const NSImageNameSmartBadge = @"NSSmartBadgeTemplate"; NSString *const NSImageNameIconView = @"NSIconViewTemplate"; NSString *const NSImageNameListView = @"NSListViewTemplate"; NSString *const NSImageNameColumnView = @"NSColumnViewTemplate"; NSString *const NSImageNameFlowView = @"NSFlowViewTemplate"; NSString *const NSImageNamePath = @"NSPathTemplate"; NSString *const NSImageNameInvalidDataFreestanding = @"NSInvalidDataFreestandingTemplate"; NSString *const NSImageNameLockLocked = @"NSLockLockedTemplate"; NSString *const NSImageNameLockUnlocked = @"NSLockUnlockedTemplate"; NSString *const NSImageNameGoRight = @"NSGoRightTemplate"; NSString *const NSImageNameGoLeft = @"NSGoLeftTemplate"; NSString *const NSImageNameRightFacingTriangle = @"NSRightFacingTriangleTemplate"; NSString *const NSImageNameLeftFacingTriangle = @"NSLeftFacingTriangleTemplate"; NSString *const NSImageNameAdd = @"NSAddTemplate"; NSString *const NSImageNameRemove = @"NSRemoveTemplate"; NSString *const NSImageNameRevealFreestanding = @"NSRevealFreestandingTemplate"; NSString *const NSImageNameFollowLinkFreestanding = @"NSFollowLinkFreestandingTemplate"; NSString *const NSImageNameEnterFullScreen = @"NSEnterFullScreenTemplate"; NSString *const NSImageNameExitFullScreen = @"NSExitFullScreenTemplate"; NSString *const NSImageNameStopProgress = @"NSStopProgressTemplate"; NSString *const NSImageNameStopProgressFreestanding = @"NSStopProgressFreestandingTemplate"; NSString *const NSImageNameRefresh = @"NSRefreshTemplate"; NSString *const NSImageNameRefreshFreestanding = @"NSRefreshFreestandingTemplate"; NSString *const NSImageNameBonjour = @"NSBonjour"; NSString *const NSImageNameComputer = @"NSComputer"; NSString *const NSImageNameFolderBurnable = @"NSFolderBurnable"; NSString *const NSImageNameFolderSmart = @"NSFolderSmart"; NSString *const NSImageNameNetwork = @"NSNetwork"; @interface NSView (Private) - (void) _lockFocusInContext: (NSGraphicsContext *)ctxt inRect: (NSRect)rect; @end @implementation NSBundle (NSImageAdditions) static NSArray* imageTypes() { NSArray *types; /* If the extension is one of the image types, * remove it from the name and place it in the * type argument. */ types = [[[GSTheme theme] imageClass] imageUnfilteredFileTypes]; if (nil == types) { types = [NSImage imageUnfilteredFileTypes]; } return types; } static void fixupImageNameAndType(NSString **name, NSString **type) { NSString *ext = [*name pathExtension]; if ([ext length] > 0) { /* If the extension is one of the image types, * remove it from the name and place it in the * type argument. */ if ([imageTypes() indexOfObject: ext] != NSNotFound) { *type = ext; *name = [*name stringByDeletingPathExtension]; } } } - (NSString *) _pathForImageNamed: (NSString *)aName ofType: (NSString *)ext subdirectory: (NSString *)aDir inBundle: (NSBundle *)aBundle { NSEnumerator *e; id o; if (ext != nil) { return [aBundle pathForResource: aName ofType: ext inDirectory: aDir]; } e = [imageTypes() objectEnumerator]; while ((o = [e nextObject]) != nil) { NSString *path; path = [aBundle pathForResource: aName ofType: o inDirectory: aDir]; if ([path length] > 0) { return path; } } return nil; } - (NSString *) _pathForLibraryImageNamed: (NSString *)aName ofType: (NSString *)ext inDirectory: (NSString *)aDir { NSEnumerator *e; id o; if (ext != nil) { return [NSBundle pathForLibraryResource: aName ofType: ext inDirectory: aDir]; } e = [imageTypes() objectEnumerator]; while ((o = [e nextObject]) != nil) { NSString *path; path = [NSBundle pathForLibraryResource: aName ofType: o inDirectory: aDir]; if ([path length] > 0) { return path; } } return nil; } - (NSString *) _pathForSystemImageNamed: (NSString *)realName ofType: (NSString *)ext { NSString *path; path = [self _pathForLibraryImageNamed: realName ofType: ext inDirectory: @"Images"]; /* If not found then search in system using the reverse NSImage nsmapping */ if (nil == path) { NSEnumerator *e; NSString *aliasName; e = [[nsmapping allKeysForObject: realName] objectEnumerator]; while ((aliasName = [e nextObject]) != nil) { path = [self _pathForLibraryImageNamed: aliasName ofType: ext inDirectory: @"Images"]; if (path != nil) { break; } } } return path; } /* * nsmapping.strings maps alternative image naming schemes to the GSTheme * standard image naming scheme. For example, NSSwitch (from OpenStep) and * common_SwitchOff (from GNUstep) are mapped to GSSwitch. In nameDict that * tracks image instances, the keys are image names from GSTheme such as * GSSwitch or additional icon names such NSApplicationIcon or * NSToolbarShowColors. In the long run, it would be cleaner to move built-in * theme images into a GNUstep.theme bundle. * * If you pass NSSwitch to +imageNamed:, nsmapping is used to get GSSwitch as * the real name, then _pathForImageNamed: will look up the image first in the * theme and fall back on the Library images. For the library images, we do a * reverse lookup in nsmapping (using allKeysForObject:) to get the image file * name (e.g. from GSSwitch to common_SwitchOff). This reverse lookup is * similar to the one supported for getting image file names from the * bundle, this reverse lookup could be handled by GSTheme rather than being * * The type received in argument is meaningfull for searching image files * using the proposed image name, but useless otherwise. */ - (NSString *) _pathForThemeImageNamed: (NSString *)name ofType: (NSString *)ext { GSTheme *theme; NSDictionary *themeMapping; NSString *mappedName; NSString *path = nil; theme = [GSTheme theme]; themeMapping = [[theme infoDictionary] objectForKey: @"GSThemeImages"]; mappedName = [themeMapping objectForKey: name]; /* First search among the theme images using the GSTheme mapping */ if (mappedName != nil) { NSString *extension = nil; NSString *proposedName = mappedName; fixupImageNameAndType(&proposedName, &extension); /* If the image file name from the theme mapping uses an extension, * this extension is used to look up the path. If the image file * cannot be found, _pathForImageNamed:ofType:subdirectory:inBundle: * searches an image file for the file extensions from -imageFileTypes. */ path = [self _pathForImageNamed: proposedName ofType: extension subdirectory: @"ThemeImages" inBundle: [theme bundle]]; } /* If not found, search among the theme images using the reverse NSImage * mapping (for GNUstep and OpenStep image names such as common_SwitchOff * or NSSwitch) */ if (nil == path) { NSEnumerator *e; NSString *aliasName; e = [[nsmapping allKeysForObject: name] objectEnumerator]; while (nil == path && (aliasName = [e nextObject]) != nil) { NSAssert([[aliasName pathExtension] length] == 0, @"nsmapping.strings " "must include no extensions in image file names"); path = [self _pathForImageNamed: aliasName ofType: nil subdirectory: @"ThemeImages" inBundle: [theme bundle]]; } } /* If not found, search among the theme images using the image name directly */ if (path == nil) { path = [self _pathForImageNamed: name ofType: ext subdirectory: @"ThemeImages" inBundle: [theme bundle]]; } return path; } - (NSString*) pathForImageResource: (NSString*)name { NSString *ext = nil; NSString *path = nil; NSString *ident; fixupImageNameAndType(&name, &ext); if (nil != (ident = [self bundleIdentifier])) { NSString *subdir; subdir = [@"ThemeImages" stringByAppendingPathComponent: ident]; path = [self _pathForImageNamed: name ofType: ext subdirectory: subdir inBundle: [[GSTheme theme] bundle]]; } if (nil == path) { path = [self _pathForImageNamed: name ofType: ext subdirectory: nil inBundle: self]; if (nil == path) { path = [self _pathForThemeImageNamed: name ofType: ext]; if (nil == path) { path = [self _pathForSystemImageNamed: 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); [super dealloc]; } @end /* Class variables and functions for class methods */ static NSRecursiveLock *imageLock = nil; static NSMutableDictionary *nameDict = nil; static NSColor *clearColor = nil; static Class cachedClass = 0; static Class bitmapClass = 0; // Cache for the supported file types static NSArray *imageUnfilteredFileTypes = nil; static NSArray *imageFileTypes = nil; static NSArray *imageUnfilteredPasteboardTypes = nil; static NSArray *imagePasteboardTypes = nil; 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) + (void) _clearFileTypeCaches: (NSNotification*)notif; + (void) _reloadCachedImages; - (BOOL) _useFromFile: (NSString *)fileName; - (BOOL) _loadFromData: (NSData *)data; - (BOOL) _loadFromFile: (NSString *)fileName; - (BOOL) _resetAndUseFromFile: (NSString *)fileName; - (GSRepData*) _cacheForRep: (NSImageRep*)rep; - (NSCachedImageRep*) _doImageCache: (NSImageRep *)rep; @end @implementation NSImage + (void) initialize { if (imageLock == nil) { NSString *path; imageLock = [NSRecursiveLock new]; [imageLock lock]; // Initial version [self setVersion: 1]; // initialize the class variables nameDict = [[NSMutableDictionary alloc] initWithCapacity: 10]; path = [NSBundle pathForLibraryResource: @"nsmapping" ofType: @"strings" inDirectory: @"Images"]; if (path) nsmapping = RETAIN([[NSString stringWithContentsOfFile: path] propertyListFromStringsFileFormat]); clearColor = RETAIN([NSColor clearColor]); cachedClass = [NSCachedImageRep class]; bitmapClass = [NSBitmapImageRep class]; [[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(_clearFileTypeCaches:) name: NSImageRepRegistryChangedNotification object: [NSImageRep class]]; [imageLock unlock]; } } + (id) imageNamed: (NSString *)aName { NSImage *image; NSString *realName; [imageLock lock]; realName = [nsmapping objectForKey: aName]; if (realName == nil) { realName = aName; } image = (NSImage*)[nameDict objectForKey: realName]; if (image == nil && realName != nil) { NSString *path = [[NSBundle mainBundle] pathForImageResource: realName]; if ([path length] != 0) { image = [[[[GSTheme theme] imageClass] alloc] initByReferencingFile: path]; if (image != nil) { [image setName: realName]; image->_flags.archiveByName = YES; AUTORELEASE(image); } } } IF_NO_GC([[image retain] autorelease]); [imageLock unlock]; return image; } + (NSImage *) _standardImageWithName: (NSString *)name { NSImage *image = nil; image = [NSImage imageNamed: name]; if (image == nil) image = [NSImage imageNamed: [@"common_" stringByAppendingString: name]]; return image; } - (id) init { return [self initWithSize: NSMakeSize(0, 0)]; } - (id) initWithSize: (NSSize)aSize { if (!(self = [super init])) return nil; //_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 { if (!(self = [self init])) return nil; if (![self _useFromFile: fileName]) { RELEASE(self); return nil; } _flags.archiveByName = YES; return self; } - (id) initWithContentsOfFile: (NSString *)fileName { if (!(self = [self init])) return nil; _flags.dataRetained = YES; if (![self _loadFromFile: fileName]) { RELEASE(self); return nil; } return self; } - (id) initWithData: (NSData *)data { if (!(self = [self init])) return nil; _flags.dataRetained = YES; if (![self _loadFromData: data]) { RELEASE(self); return nil; } return self; } - (id) initWithBitmapHandle: (void *)bitmap { NSImageRep *rep; if (!(self = [self init])) return nil; rep = [[NSBitmapImageRep alloc] initWithBitmapHandle: bitmap]; if (rep == nil) { RELEASE(self); return nil; } [self addRepresentation: rep]; RELEASE(rep); return self; } - (id)initWithIconHandle:(void *)icon { // Only needed on MS Windows NSImageRep *rep; if (!(self = [self init])) return nil; rep = [[NSBitmapImageRep alloc] initWithIconHandle: icon]; if (rep == nil) { RELEASE(self); return nil; } [self addRepresentation: rep]; RELEASE(rep); return self; } - (id) initWithContentsOfURL: (NSURL *)anURL { NSArray *array; if (!(self = [self init])) return nil; array = [NSImageRep imageRepsWithContentsOfURL: anURL]; if (!array) { RELEASE(self); return nil; } _flags.dataRetained = YES; [self addRepresentations: array]; return self; } - (id) initWithPasteboard: (NSPasteboard *)pasteboard { NSArray *reps; if (!(self = [self init])) return nil; reps = [NSImageRep imageRepsWithPasteboard: pasteboard]; 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 { if (_name == nil) { RELEASE(_reps); TEST_RELEASE(_fileName); RELEASE(_color); [super dealloc]; } else { [self retain]; NSLog(@"Warning ... attempt to deallocate image with name: %@", _name); } } - (id) copyWithZone: (NSZone *)zone { NSImage *copy; NSArray *reps = [self representations]; NSEnumerator *enumerator = [reps objectEnumerator]; NSImageRep *rep; copy = (NSImage*)NSCopyObject (self, 0, zone); copy->_name = nil; 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) isEqual: (id)anObject { if (self == anObject) return YES; if (![anObject isKindOfClass: [NSImage class]]) return NO; // FIXME return NO; } - (NSString*) description { return [NSString stringWithFormat: @"<%@ %p Name=%@ Size=%@ Reps=%@>", [self class], self, [self name], NSStringFromSize([self size]), [self representations]]; } /* This methd sets the name of an image, updating the global name dictionary * to point to the image (or removing an image from the dictionary if the * new name is nil). */ - (BOOL) setName: (NSString *)aName { [imageLock lock]; /* The name is already set... nothing to do. */ if (aName == _name || [aName isEqual: _name] == YES) { [imageLock unlock]; return YES; } /* If the new name is already in use by another image, * we must do nothing. */ if (aName != nil && [nameDict objectForKey: aName] != nil) { [imageLock unlock]; return NO; } /* If this image had another name, we remove it. */ if (_name != nil) { /* We retain self in case removing from the dictionary releases us */ IF_NO_GC([[self retain] autorelease]); [nameDict removeObjectForKey: _name]; DESTROY(_name); } /* If the new name is null, there is nothing more to do. */ if (aName == nil) { [imageLock unlock]; return NO; } ASSIGN(_name, aName); [nameDict setObject: self forKey: _name]; [imageLock unlock]; return YES; } - (NSString *) name { NSString *name; [imageLock lock]; name = [[_name retain] autorelease]; [imageLock unlock]; return name; } - (void) setSize: (NSSize)aSize { _size = aSize; _flags.sizeWasExplicitlySet = YES; } - (NSSize) size { if (_size.width == 0) { NSImageRep *rep = [self bestRepresentationForDevice: nil]; if (rep) _size = [rep size]; else _size = NSZeroSize; } 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; NSUInteger i, count; if (_flags.syncLoad) { /* Make sure any images that were added with _useFromFile: are loaded in and added to the representation list. */ if (![self _loadFromFile: _fileName]) return NO; _flags.syncLoad = NO; } /* 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 { NSUInteger i; i = [_reps count]; while (i--) { GSRepData *repd; repd = (GSRepData*)[_reps objectAtIndex: i]; if (repd->original != nil) { [_reps removeObjectAtIndex: i]; } } } - (void) setScalesWhenResized: (BOOL)flag { // FIXME: This currently breaks NSImage. // See the test case in GSTest/Image-test that uses this method. // _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 { [self compositeToPoint: aPoint fromRect: NSZeroRect operation: op fraction: 1.0]; } - (void) compositeToPoint: (NSPoint)aPoint fromRect: (NSRect)aRect operation: (NSCompositingOperation)op { [self compositeToPoint: aPoint fromRect: aRect operation: op fraction: 1.0]; } - (void) compositeToPoint: (NSPoint)aPoint operation: (NSCompositingOperation)op fraction: (CGFloat)delta { [self compositeToPoint: aPoint fromRect: NSZeroRect operation: op fraction: delta]; } - (void) compositeToPoint: (NSPoint)aPoint fromRect: (NSRect)srcRect operation: (NSCompositingOperation)op fraction: (CGFloat)delta { NSGraphicsContext *ctxt = GSCurrentContext(); // Calculate the user space scale factor of the current window NSView *focusView = [NSView focusView]; CGFloat scaleFactor = 1.0; if (focusView != nil) { scaleFactor = [[focusView window] userSpaceScaleFactor]; } // Set the CTM to the identity matrix with the current translation // and the user space scale factor { NSAffineTransform *backup = [[ctxt GSCurrentCTM] retain]; NSAffineTransform *newTransform = [NSAffineTransform transform]; NSPoint translation = [backup transformPoint: aPoint]; [newTransform translateXBy: translation.x yBy: translation.y]; [newTransform scaleBy: scaleFactor]; [ctxt GSSetCTM: newTransform]; [self drawAtPoint: NSMakePoint(0, 0) fromRect: srcRect operation: op fraction: delta]; [ctxt GSSetCTM: backup]; [backup release]; } } - (void) dissolveToPoint: (NSPoint)aPoint fraction: (CGFloat)aFloat { [self dissolveToPoint: aPoint fromRect: NSZeroRect fraction: aFloat]; } - (void) dissolveToPoint: (NSPoint)aPoint fromRect: (NSRect)aRect fraction: (CGFloat)aFloat { [self compositeToPoint: aPoint fromRect: aRect operation: NSCompositeSourceOver fraction: aFloat]; } - (BOOL) drawRepresentation: (NSImageRep *)imageRep inRect: (NSRect)aRect { BOOL r; NSGraphicsContext *ctxt = GSCurrentContext(); DPSgsave(ctxt); if (_color != nil) { NSRect fillrect = aRect; [_color set]; NSRectFill(fillrect); if ([GSCurrentContext() isDrawingToScreen] == NO) { /* Reset alpha for image drawing. */ [[NSColor colorWithCalibratedWhite: 1.0 alpha: 1.0] set]; } } if (!_flags.scalable) r = [imageRep drawAtPoint: aRect.origin]; else r = [imageRep drawInRect: aRect]; DPSgrestore(ctxt); return r; } - (void) drawAtPoint: (NSPoint)point fromRect: (NSRect)srcRect operation: (NSCompositingOperation)op fraction: (CGFloat)delta { [self drawInRect: NSMakeRect(point.x, point.y, srcRect.size.width, srcRect.size.height) fromRect: srcRect operation: op fraction: delta respectFlipped: NO hints: nil]; } - (void) drawInRect: (NSRect)rect { [self drawInRect: rect fromRect: NSZeroRect operation: NSCompositeSourceOver fraction: 1.0]; } - (void) drawInRect: (NSRect)dstRect fromRect: (NSRect)srcRect operation: (NSCompositingOperation)op fraction: (CGFloat)delta { [self drawInRect: dstRect fromRect: srcRect operation: op fraction: delta respectFlipped: NO hints: nil]; } /** * Base drawing method in NSImage; all other draw methods call this one */ - (void) drawInRect: (NSRect)dstRect // Negative width/height => Nothing draws. fromRect: (NSRect)srcRect operation: (NSCompositingOperation)op fraction: (CGFloat)delta respectFlipped: (BOOL)respectFlipped hints: (NSDictionary*)hints { NSImageRep *rep; NSGraphicsContext *ctxt; NSSize imgSize, repSize; NSRect repSrcRect; ctxt = GSCurrentContext(); imgSize = [self size]; // Handle abbreviated parameters if (NSEqualRects(srcRect, NSZeroRect)) { srcRect.size = imgSize; } if (NSEqualSizes(dstRect.size, NSZeroSize)) // For -drawAtPoint:fromRect:operation:fraction: { dstRect.size = imgSize; } if (imgSize.width <= 0 || imgSize.height <= 0) return; // Select a rep rep = [self bestRepresentationForRect: dstRect context: ctxt hints: hints]; if (rep == nil) return; // Try to cache / get a cached version of the best rep /** * We only use caching on backends that can efficiently draw a rect from the cache * onto the current graphics context respecting the CTM, which is currently cairo. */ if (_cacheMode != NSImageCacheNever && [ctxt supportsDrawGState]) { NSCachedImageRep *cache = [self _doImageCache: rep]; if (cache != nil) { rep = cache; } } repSize = [rep size]; // Convert srcRect from image coordinate space to rep coordinate space { const CGFloat imgToRepWidthScaleFactor = repSize.width / imgSize.width; const CGFloat imgToRepHeightScaleFactor = repSize.height / imgSize.height; repSrcRect = NSMakeRect(srcRect.origin.x * imgToRepWidthScaleFactor, srcRect.origin.y * imgToRepHeightScaleFactor, srcRect.size.width * imgToRepWidthScaleFactor, srcRect.size.height * imgToRepHeightScaleFactor); } // FIXME: Draw background? [rep drawInRect: dstRect fromRect: repSrcRect operation: op fraction: delta respectFlipped: respectFlipped hints: hints]; } - (void) addRepresentation: (NSImageRep *)imageRep { GSRepData *repd; if (imageRep != nil) { repd = [GSRepData new]; repd->rep = RETAIN(imageRep); [_reps addObject: repd]; RELEASE(repd); } } - (void) addRepresentations: (NSArray *)imageRepArray { NSUInteger 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 { NSUInteger 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) { // Remove cached representations for this representation // instead of turning them into real ones //repd->original = nil; [_reps removeObjectAtIndex: i]; } } } - (void) lockFocus { [self lockFocusOnRepresentation: nil]; } - (void) lockFocusOnRepresentation: (NSImageRep *)imageRep { if (_cacheMode != NSImageCacheNever) { NSWindow *window; GSRepData *repd; if (NSEqualSizes(NSZeroSize, [self size])) [NSException raise: NSImageCacheException format: @"Cannot lock focus on image with size (0, 0)"]; if (imageRep == nil) imageRep = [self bestRepresentationForDevice: nil]; repd = [self _cacheForRep: imageRep]; if (repd == nil) return; imageRep = repd->rep; window = [(NSCachedImageRep *)imageRep window]; _lockedView = [window contentView]; if (_lockedView == nil) { [NSException raise: NSImageCacheException format: @"Cannot lock focus on nil rep"]; } // FIXME: This is needed to get image caching working while printing. A better solution // needs to remove the viewIsPrinting variable from NSView. [_lockedView _lockFocusInContext: [window graphicsContext] inRect: [_lockedView bounds]]; if (repd->bg == nil) { NSRect fillrect = NSMakeRect(0, 0, _size.width, _size.height); // Clear the background of the cached image, as it is not valid if ([_color alphaComponent] < 1.0) { /* 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.) */ NSRectFillUsingOperation(fillrect, NSCompositeClear); } repd->bg = [_color copy]; if ([repd->bg alphaComponent] == 1.0) { [imageRep setOpaque: YES]; } else { [imageRep setOpaque: [repd->original isOpaque]]; } // Fill with background colour and draw repesentation [self drawRepresentation: repd->original inRect: fillrect]; } } } - (void) unlockFocus { if (_lockedView != nil) { [_lockedView unlockFocus]; _lockedView = nil; } } /* Determine the number of color components in the device and filter out reps with a different number of color components. If the device lacks a color space name, all reps are treated as matching. If a rep lacks a color space name, it is assumed to match the device. WARNING: Be careful not to inadvertently mix greyscale and color representations in a TIFF. The greyscale representations will never be selected as a best rep unless you are drawing on a greyscale surface, or all reps in the TIFF are greyscale. */ - (NSMutableArray *) _bestRep: (NSArray *)reps withColorMatch: (NSDictionary*)deviceDescription { NSMutableArray *breps = [NSMutableArray array]; NSString *deviceColorSpace = [deviceDescription objectForKey: NSDeviceColorSpaceName]; if (deviceColorSpace != nil) { NSUInteger deviceColors = NSNumberOfColorComponents(deviceColorSpace); NSEnumerator *enumerator = [reps objectEnumerator]; NSImageRep *rep; while ((rep = [enumerator nextObject]) != nil) { if ([rep colorSpaceName] == nil || NSNumberOfColorComponents([rep colorSpaceName]) == deviceColors) { [breps addObject: rep]; } } } /* If there are no matches, pass all the reps */ if ([breps count] == 0) { [breps setArray: reps]; } return breps; } /** * Returns YES if x in an integer multiple of y */ static BOOL GSIsMultiple(CGFloat x, CGFloat y) { // FIXME: Test when CGFloat is float and make sure this test isn't // too strict due to floating point rounding errors. return (x/y) == floor(x/y); } /** * Returns YES if there exist integers p and q such that * (baseSize.width * p == size.width) && (baseSize.height * q == size.height) */ static BOOL GSSizeIsIntegerMultipleOfSize(NSSize size, NSSize baseSize) { return NSEqualSizes(size, baseSize) || (GSIsMultiple(size.width, baseSize.width) && GSIsMultiple(size.height, baseSize.height)); } /** * Returns {0, 0} if the image rep doesn't have a size set, * or the pixelsWide or pixelsHigh are NSImageRepMatchesDevice */ static NSSize GSResolutionOfImageRep(NSImageRep *rep) { const int pixelsWide = [rep pixelsWide]; const int pixelsHigh = [rep pixelsHigh]; const NSSize repSize = [rep size]; if (repSize.width == 0 || repSize.height == 0) { return NSMakeSize(0, 0); } else if (pixelsWide == NSImageRepMatchesDevice || pixelsHigh == NSImageRepMatchesDevice) { return NSMakeSize(0, 0); } else { return NSMakeSize(72.0 * (CGFloat)pixelsWide / repSize.width, 72.0 * (CGFloat)pixelsHigh / repSize.height); } } /* Find reps that match the resolution (DPI) of the device (including integer multiples of the device resplition if [self multipleResolutionMatching] is YES). If there are no DPI matches, use any available vector reps if [self usesEPSOnResolutionMismatch] is YES. Otherwise, use the bitmap reps that have the highest DPI. */ - (NSMutableArray *) _bestRep: (NSArray *)reps withResolutionMatch: (NSDictionary*)deviceDescription { NSMutableArray *breps = [NSMutableArray array]; NSValue *resolution = [deviceDescription objectForKey: NSDeviceResolution]; // 1. Look for exact resolution matches, or integer multiples if permitted. if (nil != resolution) { const NSSize dres = [resolution sizeValue]; if (![self matchesOnMultipleResolution]) { NSImageRep *rep; NSEnumerator *enumerator = [reps objectEnumerator]; while ((rep = [enumerator nextObject]) != nil) { if (NSEqualSizes(GSResolutionOfImageRep(rep), dres)) { [breps addObject: rep]; } } } else // [self matchesOnMultipleResolution] { NSMutableArray *integerMultiples = [NSMutableArray array]; NSSize closestRes = NSMakeSize(CGFLOAT_MAX, CGFLOAT_MAX); NSImageRep *rep; NSEnumerator *enumerator; // Iterate through the reps, keeping track of which ones // have a resolution which is an integer multiple of the device // res, and keep track of the cloest resolution enumerator = [reps objectEnumerator]; while ((rep = [enumerator nextObject]) != nil) { const NSSize repRes = GSResolutionOfImageRep(rep); if (GSSizeIsIntegerMultipleOfSize(repRes, dres)) { const NSSize repResDifference = NSMakeSize(fabs(repRes.width - dres.width), fabs(repRes.height - dres.height)); const NSSize closestResolutionDifference = NSMakeSize(fabs(closestRes.width - dres.width), fabs(closestRes.height - dres.height)); if (repResDifference.width < closestResolutionDifference.width && repResDifference.height < closestResolutionDifference.height) { closestRes = repRes; } [integerMultiples addObject: rep]; } } enumerator = [integerMultiples objectEnumerator]; while ((rep = [enumerator nextObject]) != nil) { const NSSize repRes = GSResolutionOfImageRep(rep); if (NSEqualSizes(repRes, closestRes)) { [breps addObject: rep]; } } } } // 2. If no exact matches found, use vector reps, if they are preferred if ([breps count] == 0 && [self usesEPSOnResolutionMismatch]) { NSImageRep *rep; NSEnumerator *enumerator = [reps objectEnumerator]; while ((rep = [enumerator nextObject]) != nil) { if ([rep pixelsWide] == NSImageRepMatchesDevice && [rep pixelsHigh] == NSImageRepMatchesDevice) { [breps addObject: rep]; } } } // 3. If there are still no matches, use all of the bitmaps with the highest // resolution (DPI) if ([breps count] == 0) { NSSize maxRes = NSMakeSize(0,0); NSImageRep *rep; NSEnumerator *enumerator; // Determine maxRes enumerator = [reps objectEnumerator]; while ((rep = [enumerator nextObject]) != nil) { const NSSize res = GSResolutionOfImageRep(rep); if (res.width > maxRes.width && res.height > maxRes.height) { maxRes = res; } } // Use all reps with maxRes enumerator = [reps objectEnumerator]; while ((rep = [enumerator nextObject]) != nil) { const NSSize res = GSResolutionOfImageRep(rep); if (NSEqualSizes(res, maxRes)) { [breps addObject: rep]; } } } // 4. If there are still none, use all available reps. // Note that this handles using vector reps in the case where there are // no bitmap reps, but [self usesEPSOnResolutionMismatch] is NO. if ([breps count] == 0) { [breps setArray: reps]; } return breps; } /* Find the reps that match the bitsPerSample of the device, or if none match exactly, return all that have the highest bitsPerSample. If the device lacks a bps, all reps are treated as matching. If a rep has NSImageRepMatchesDevice as its bps, it is treated as matching. */ - (NSMutableArray *) _bestRep: (NSArray *)reps withBpsMatch: (NSDictionary*)deviceDescription { NSMutableArray *breps = [NSMutableArray array]; NSNumber *bpsValue = [deviceDescription objectForKey: NSDeviceBitsPerSample]; if (bpsValue != nil) { NSInteger deviceBps = [bpsValue integerValue]; NSInteger maxBps = -1; BOOL haveDeviceBps = NO; NSImageRep *rep; NSEnumerator *enumerator; // Determine maxBps enumerator = [reps objectEnumerator]; while ((rep = [enumerator nextObject]) != nil) { if ([rep bitsPerSample] > maxBps) { maxBps = [rep bitsPerSample]; } if ([rep bitsPerSample] == deviceBps) { haveDeviceBps = YES; } } // Use all reps with deviceBps if haveDeviceBps is YES, // otherwise use all reps with maxBps enumerator = [reps objectEnumerator]; while ((rep = [enumerator nextObject]) != nil) { if ([rep bitsPerSample] == NSImageRepMatchesDevice || (!haveDeviceBps && [rep bitsPerSample] == maxBps) || (haveDeviceBps && [rep bitsPerSample] == deviceBps)) { [breps addObject: rep]; } } } /* If there are no matches, pass all the reps */ if ([breps count] == 0) { [breps setArray: reps]; } return breps; } - (NSMutableArray *) _representationsWithCachedImages: (BOOL)flag { NSUInteger 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 [NSMutableArray array]; } else { id repList[count]; NSUInteger 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 [NSMutableArray arrayWithObjects: repList count: j]; } } - (NSArray *) _bestRepresentationsForDevice: (NSDictionary*)deviceDescription { NSMutableArray *reps = [self _representationsWithCachedImages: NO]; if (deviceDescription == nil) { if ([GSCurrentContext() isDrawingToScreen] == YES) { // Take the device description from the current context. deviceDescription = [[[GSCurrentContext() attributes] objectForKey: NSGraphicsContextDestinationAttributeName] 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]; return reps; } - (NSImageRep *) bestRepresentationForDevice: (NSDictionary*)deviceDescription { NSArray *reps = [self _bestRepresentationsForDevice: deviceDescription]; /* If we have more than one match check for a representation whose size * matches the image size exactly. Otherwise, arbitrarily choose the first * representation. */ if ([reps count] > 1) { NSImageRep *rep; NSEnumerator *enumerator = [reps objectEnumerator]; while ((rep = [enumerator nextObject]) != nil) { if (NSEqualSizes(_size, [rep size]) == YES) { return rep; } } } if ([reps count] > 0) { return [reps objectAtIndex: 0]; } else { return nil; } } - (NSImageRep *) bestRepresentationForRect: (NSRect)rect context: (NSGraphicsContext *)context hints: (NSDictionary *)deviceDescription { NSArray *reps = [self _bestRepresentationsForDevice: deviceDescription]; const NSSize desiredSize = rect.size; NSImageRep *bestRep = nil; // Pick the smallest rep that is greater than or equal to the // desired size. { NSSize bestSize = NSMakeSize(CGFLOAT_MAX, CGFLOAT_MAX); NSImageRep *rep; NSEnumerator *enumerator = [reps objectEnumerator]; while ((rep = [enumerator nextObject]) != nil) { const NSSize repSize = [rep size]; if ((repSize.width >= desiredSize.width) && (repSize.height >= desiredSize.height) && (repSize.width < bestSize.width) && (repSize.height < bestSize.height)) { bestSize = repSize; bestRep = rep; } } } if (bestRep == nil) { bestRep = [reps lastObject]; } return bestRep; } - (NSArray *) representations { return [self _representationsWithCachedImages: YES]; } - (void) setDelegate: anObject { _delegate = anObject; } - (id) delegate { return _delegate; } // Producing TIFF Data for the Image - (NSData *) TIFFRepresentation { NSArray *reps; NSData *data; /* As a result of using bitmap representations, * new drawing wont show on the tiff data. */ reps = [self _representationsWithCachedImages: NO]; data = [bitmapClass TIFFRepresentationOfImageRepsInArray: reps]; if (!data) { NSBitmapImageRep *rep; NSSize size = [self size]; // If there isn't a bitmap representation to output, create one and store it. [self lockFocus]; rep = [[NSBitmapImageRep alloc] initWithFocusedViewRect: NSMakeRect(0.0, 0.0, size.width, size.height)]; [self unlockFocus]; if (nil != rep) { [self addRepresentation: rep]; data = [rep TIFFRepresentation]; RELEASE(rep); } } return data; } - (NSData *) TIFFRepresentationUsingCompression: (NSTIFFCompression)comp factor: (float)aFloat { NSArray *reps; NSData *data; /* As a result of using bitmap representations, * new drawing wont show on the tiff data. */ reps = [self _representationsWithCachedImages: NO]; data = [bitmapClass TIFFRepresentationOfImageRepsInArray: reps usingCompression: comp factor: aFloat]; if (!data) { NSBitmapImageRep *rep; NSSize size = [self size]; /* If there isn't a bitmap representation to output, * create one and store it. */ [self lockFocus]; rep = [[NSBitmapImageRep alloc] initWithFocusedViewRect: NSMakeRect(0.0, 0.0, size.width, size.height)]; [self unlockFocus]; if (nil != rep) { [self addRepresentation: rep]; data = [rep TIFFRepresentationUsingCompression: comp factor: aFloat]; RELEASE(rep); } } return data; } // NSCoding - (void) encodeWithCoder: (NSCoder*)coder { BOOL flag; if ([coder allowsKeyedCoding]) { int flags = 0; if (_flags.archiveByName == NO) { NSMutableArray *container = [NSMutableArray array]; NSMutableArray *reps = [NSMutableArray array]; NSEnumerator *en = [_reps objectEnumerator]; GSRepData *rd = nil; if ([_reps count] > 0) { [reps addObject: [NSNumber numberWithInt: 0]]; while ((rd = [en nextObject]) != nil) { [reps addObject: rd->rep]; } // add the reps to the container... [container addObject: reps]; [coder encodeObject: container forKey: @"NSReps"]; } } else { [coder encodeObject: _name forKey: @"NSName"]; } // encode the rest... if (_color != nil) { [coder encodeObject: _color forKey: @"NSColor"]; } flags |= [self scalesWhenResized] ? 0x8000000 : 0; flags |= _flags.sizeWasExplicitlySet ? 0x2000000 : 0; flags |= [self usesEPSOnResolutionMismatch] ? 0x0200000 : 0; flags |= [self prefersColorMatch] ? 0x0100000 : 0; flags |= [self matchesOnMultipleResolution] ? 0x0080000 : 0; flags |= [self isFlipped] ? 0x0008000 : 0; flags |= [self cacheMode] << 11; [coder encodeInt: flags forKey: @"NSImageFlags"]; if (_flags.sizeWasExplicitlySet) { [coder encodeSize: _size forKey: @"NSSize"]; } } else { 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]; if ([coder allowsKeyedCoding]) { if ([coder containsValueForKey: @"NSName"]) { NSImage *replacementImage; NSString *imageName; imageName = [coder decodeObjectForKey: @"NSName"]; replacementImage = [NSImage imageNamed: imageName]; if (replacementImage) { RELEASE(self); return RETAIN(replacementImage); } [self setName: imageName]; self->_flags.archiveByName = YES; } if ([coder containsValueForKey: @"NSColor"]) { [self setBackgroundColor: [coder decodeObjectForKey: @"NSColor"]]; } if ([coder containsValueForKey: @"NSImageFlags"]) { int flags = [coder decodeIntForKey: @"NSImageFlags"]; [self setScalesWhenResized: ((flags & 0x8000000) != 0)]; // _flags.sizeWasExplicitlySet = ((flags & 0x2000000) != 0); [self setUsesEPSOnResolutionMismatch: ((flags & 0x0200000) != 0)]; [self setPrefersColorMatch: ((flags & 0x0100000) != 0)]; [self setMatchesOnMultipleResolution: ((flags & 0x0080000) != 0)]; [self setFlipped: ((flags & 0x0008000) != 0)]; // ALIASED ((flags & 0x0004000) != 0) [self setCacheMode: ((flags & 0x0001800) >> 11)]; } if ([coder containsValueForKey: @"NSReps"]) { NSArray *reps; NSUInteger i; /* FIXME: NSReps is in a strange format. It is a mutable array * with one element which is an array with a first element 0 * and than the image rep. */ reps = [coder decodeObjectForKey: @"NSReps"]; reps = [reps objectAtIndex: 0]; for (i = 1; i < [reps count]; i++) { id rep = [reps objectAtIndex: i]; if ([rep isKindOfClass: [NSImageRep class]]) { [self addRepresentation: rep]; } else { if ([rep isKindOfClass: [NSURL class]]) { NSURL *tmp = (NSURL*)rep; rep = [NSImageRep imageRepWithContentsOfURL: rep]; /* If we are unable to resolved the URL, * try to get it from the resources folder. */ if (rep == nil) { NSString *fileName; NSString *path; fileName = [[tmp absoluteString] lastPathComponent]; path = [[NSBundle mainBundle] pathForImageResource: fileName]; rep = [NSImageRep imageRepWithContentsOfFile: path]; } // If the representation was found, add it... if (rep != nil) { [self addRepresentation: rep]; } } } } } if ([coder containsValueForKey: @"NSSize"]) { [self setSize: [coder decodeSizeForKey: @"NSSize"]]; } } else { [coder decodeValueOfObjCType: @encode(BOOL) at: &flag]; if (flag == YES) { NSImage *replacementImage; NSString *theName = [coder decodeObject]; replacementImage = [NSImage imageNamed: theName]; if (replacementImage) { RELEASE(self); self = RETAIN(replacementImage); } else { [self setName: theName]; self->_flags.archiveByName = YES; } } 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; } + (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 { if (nil == imageUnfilteredFileTypes) { ASSIGN(imageUnfilteredFileTypes, iterate_reps_for_types([NSImageRep registeredImageRepClasses], @selector(imageUnfilteredFileTypes))); } return imageUnfilteredFileTypes; } + (NSArray *) imageFileTypes { if (nil == imageFileTypes) { ASSIGN(imageFileTypes, iterate_reps_for_types([NSImageRep registeredImageRepClasses], @selector(imageFileTypes))); } return imageFileTypes; } + (NSArray *) imageUnfilteredPasteboardTypes { if (nil == imageUnfilteredPasteboardTypes) { ASSIGN(imageUnfilteredPasteboardTypes, iterate_reps_for_types([NSImageRep registeredImageRepClasses], @selector(imageUnfilteredPasteboardTypes))); } return imageUnfilteredPasteboardTypes; } + (NSArray *) imagePasteboardTypes { if (nil == imagePasteboardTypes) { ASSIGN(imagePasteboardTypes, iterate_reps_for_types([NSImageRep registeredImageRepClasses], @selector(imagePasteboardTypes))); } return 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) + (void) _clearFileTypeCaches: (NSNotification*)notif { DESTROY(imageUnfilteredFileTypes); DESTROY(imageFileTypes); DESTROY(imageUnfilteredPasteboardTypes); DESTROY(imagePasteboardTypes); } /** * For all NSImage instances cached in nameDict, recompute the * path using +_pathForImageNamed: and if it has changed, * reload the image contents using the new path. */ + (void) _reloadCachedImages { NSString *name; NSEnumerator *e = [nameDict keyEnumerator]; [imageLock lock]; while ((name = [e nextObject]) != nil) { NSImage *image = [nameDict objectForKey: name]; NSString *path = [[NSBundle mainBundle] pathForImageResource: name]; //NSLog(@"Loaded image %@ from %@", name, path); if (path != nil && ![path isEqual: image->_fileName]) { /* Reset the existing image to use the contents of * the specified file. */ [image _resetAndUseFromFile: path]; } } [imageLock unlock]; } + (NSString *) _resourceNameForImageNamed: (NSString *)aName type: (NSString **)aType { NSString *name = aName; NSString *ext = [aName pathExtension]; if (ext != nil && [ext length] == 0) { ext = nil; } /* Check if extension is one of the image types */ if (ext != nil && [[self imageFileTypes] indexOfObject: ext] != NSNotFound) { /* Extension is one of the image types, so remove from the name */ name = [aName stringByDeletingPathExtension]; } else { /* Otherwise extension is not an image type, so leave it alone */ ext = nil; } *aType = ext; return name; } - (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 && ([array count] > 0)) 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 && ([array count] > 0)) ? YES : NO; } - (BOOL) _useFromFile: (NSString *)fileName { NSArray *array; NSString *ext; NSFileManager *manager = [NSFileManager defaultManager]; if ([manager fileExistsAtPath: fileName] == NO) { return NO; } ext = [[fileName pathExtension] lowercaseString]; if (!ext) return NO; array = [object_getClass(self) imageFileTypes]; if ([array indexOfObject: ext] == NSNotFound) return NO; ASSIGN(_fileName, fileName); _flags.syncLoad = YES; return YES; } - (BOOL) _resetAndUseFromFile: (NSString *)fileName { [_reps removeAllObjects]; if (!_flags.sizeWasExplicitlySet) { _size = NSZeroSize; } return [self _useFromFile: fileName]; } // 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). - (NSCachedImageRep *) _doImageCache: (NSImageRep *)rep { GSRepData *repd; NSCachedImageRep *cache; repd = [self _cacheForRep: rep]; if (repd == nil) return nil; cache = (NSCachedImageRep*)(repd->rep); if ([cache isKindOfClass: cachedClass] == NO) return nil; NSDebugLLog(@"NSImage", @"Cached image rep is %p", cache); /* * 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: cache]; [self unlockFocus]; NSDebugLLog(@"NSImage", @"Rendered rep %p on background %@", cache, repd->bg); } return cache; } - (GSRepData*) _cacheForRep: (NSImageRep*)rep { if ([rep isKindOfClass: cachedClass] == YES) { return repd_for_rep(_reps, rep); } else { /* * If this is not a cached image rep - try to find the cache rep * for this image rep. If none is found create a cache to be used to * render the image rep into, and switch to the cached rep. */ NSUInteger count = [_reps count]; if (count > 0) { GSRepData *invalidCache = nil; GSRepData *partialCache = nil; GSRepData *reps[count]; NSUInteger partialCount = 0; NSUInteger 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 != nil) { if (repd->bg == nil) { NSDebugLLog(@"NSImage", @"Invalid %@ ... %@ %@", repd->bg, _color, repd->rep); invalidCache = repd; } else if (opaque == YES || [repd->bg isEqual: _color] == YES) { NSDebugLLog(@"NSImage", @"Exact %@ ... %@ %@", repd->bg, _color, repd->rep); return repd; } else { NSDebugLLog(@"NSImage", @"Partial %@ ... %@ %@", repd->bg, _color, repd->rep); partialCache = repd; partialCount++; } } } 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. */ return invalidCache; } 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 && opaque == NO) { DESTROY(partialCache->bg); } return partialCache; } } // We end here, when no representation are there or no match is found. { NSImageRep *cacheRep = nil; GSRepData *repd; NSSize imageSize = [self size]; NSSize repSize; NSInteger pixelsWide, pixelsHigh; if (rep != nil) { repSize = [rep size]; if (repSize.width <= 0 || repSize.height <= 0) repSize = imageSize; pixelsWide = [rep pixelsWide]; pixelsHigh = [rep pixelsHigh]; if (pixelsWide == NSImageRepMatchesDevice || pixelsHigh == NSImageRepMatchesDevice) { /* FIXME: Since the cached rep must be a bitmap, * we must rasterize vector reps at a particular DPI. * Here we hardcode 72, but we should choose the DPI * more intelligently. */ pixelsWide = repSize.width; pixelsHigh = repSize.height; } } else // e.g. when there are no representations at all { repSize = imageSize; /* FIXME: assumes 72 DPI. Also truncates, * not sure if that is a problem. */ pixelsWide = imageSize.width; pixelsHigh = imageSize.height; } if (repSize.width <= 0 || repSize.height <= 0 || pixelsWide <= 0 || pixelsHigh <= 0) return nil; // Create a new cached image rep without any contents. cacheRep = [[cachedClass alloc] initWithSize: repSize pixelsWide: pixelsWide pixelsHigh: pixelsHigh depth: [[NSScreen mainScreen] depth] separate: _flags.cacheSeparately alpha: [rep hasAlpha]]; if (cacheRep == nil) { return nil; } repd = [GSRepData new]; repd->rep = cacheRep; repd->original = rep; // may be nil! [_reps addObject: repd]; RELEASE(repd); /* Retained in _reps array. */ return repd; } } } @end