1/** <title>NSImage</title> 2 3 <abstract>Load, manipulate and display images</abstract> 4 5 Copyright (C) 1996-2016 Free Software Foundation, Inc. 6 7 Author: Adam Fedor <fedor@colorado.edu> 8 Date: Feb 1996 9 10 This library is free software; you can redistribute it and/or 11 modify it under the terms of the GNU Lesser General Public 12 License as published by the Free Software Foundation; either 13 version 2 of the License, or (at your option) any later version. 14 15 This library is distributed in the hope that it will be useful, 16 but WITHOUT ANY WARRANTY; without even the implied warranty of 17 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 18 Lesser General Public License for more details. 19 20 You should have received a copy of the GNU Lesser General Public 21 License along with this library; see the file COPYING.LIB. 22 If not, see <http://www.gnu.org/licenses/> or write to the 23 Free Software Foundation, 51 Franklin Street, Fifth Floor, 24 Boston, MA 02110-1301, USA. 25*/ 26 27#include "config.h" 28#include <string.h> 29#include <math.h> 30 31#import <Foundation/NSArray.h> 32#import <Foundation/NSBundle.h> 33#import <Foundation/NSDebug.h> 34#import <Foundation/NSDictionary.h> 35#import <Foundation/NSException.h> 36#import <Foundation/NSFileManager.h> 37#import <Foundation/NSKeyedArchiver.h> 38#import <Foundation/NSLock.h> 39#import <Foundation/NSNotification.h> 40#import <Foundation/NSString.h> 41#import <Foundation/NSValue.h> 42 43#import "AppKit/NSImage.h" 44 45#import "AppKit/AppKitExceptions.h" 46#import "AppKit/NSAffineTransform.h" 47#import "AppKit/NSBitmapImageRep.h" 48#import "AppKit/NSCachedImageRep.h" 49#import "AppKit/NSColor.h" 50#import "AppKit/NSPasteboard.h" 51#import "AppKit/NSPrintOperation.h" 52#import "AppKit/NSScreen.h" 53#import "AppKit/NSView.h" 54#import "AppKit/NSWindow.h" 55#import "AppKit/DPSOperators.h" 56#import "GNUstepGUI/GSDisplayServer.h" 57#import "GSThemePrivate.h" 58 59BOOL NSImageForceCaching = NO; /* use on missmatch */ 60 61static NSDictionary *nsmapping = nil; 62 63// OS_API_VERSION(MAC_OS_X_VERSION_10_5, GS_API_LATEST) 64NSString *const NSImageNameQuickLookTemplate = @"NSQuickLookTemplate"; 65NSString *const NSImageNameBluetooth = @"NSBluetoothTemplate"; 66NSString *const NSImageNameIChatTheater = @"NSIChatTheaterTemplate"; 67NSString *const NSImageNameSlideshow = @"NSSlideshowTemplate"; 68NSString *const NSImageNameAction = @"NSActionTemplate"; 69NSString *const NSImageNameSmartBadge = @"NSSmartBadgeTemplate"; 70NSString *const NSImageNameIconView = @"NSIconViewTemplate"; 71NSString *const NSImageNameListView = @"NSListViewTemplate"; 72NSString *const NSImageNameColumnView = @"NSColumnViewTemplate"; 73NSString *const NSImageNameFlowView = @"NSFlowViewTemplate"; 74NSString *const NSImageNamePath = @"NSPathTemplate"; 75NSString *const NSImageNameInvalidDataFreestanding = @"NSInvalidDataFreestandingTemplate"; 76NSString *const NSImageNameLockLocked = @"NSLockLockedTemplate"; 77NSString *const NSImageNameLockUnlocked = @"NSLockUnlockedTemplate"; 78NSString *const NSImageNameGoRight = @"NSGoRightTemplate"; 79NSString *const NSImageNameGoLeft = @"NSGoLeftTemplate"; 80NSString *const NSImageNameRightFacingTriangle = @"NSRightFacingTriangleTemplate"; 81NSString *const NSImageNameLeftFacingTriangle = @"NSLeftFacingTriangleTemplate"; 82NSString *const NSImageNameAdd = @"NSAddTemplate"; 83NSString *const NSImageNameRemove = @"NSRemoveTemplate"; 84NSString *const NSImageNameRevealFreestanding = @"NSRevealFreestandingTemplate"; 85NSString *const NSImageNameFollowLinkFreestanding = @"NSFollowLinkFreestandingTemplate"; 86NSString *const NSImageNameEnterFullScreen = @"NSEnterFullScreenTemplate"; 87NSString *const NSImageNameExitFullScreen = @"NSExitFullScreenTemplate"; 88NSString *const NSImageNameStopProgress = @"NSStopProgressTemplate"; 89NSString *const NSImageNameStopProgressFreestanding = @"NSStopProgressFreestandingTemplate"; 90NSString *const NSImageNameRefresh = @"NSRefreshTemplate"; 91NSString *const NSImageNameRefreshFreestanding = @"NSRefreshFreestandingTemplate"; 92NSString *const NSImageNameBonjour = @"NSBonjour"; 93NSString *const NSImageNameComputer = @"NSComputer"; 94NSString *const NSImageNameFolderBurnable = @"NSFolderBurnable"; 95NSString *const NSImageNameFolderSmart = @"NSFolderSmart"; 96NSString *const NSImageNameNetwork = @"NSNetwork"; 97 98@interface NSView (Private) 99- (void) _lockFocusInContext: (NSGraphicsContext *)ctxt inRect: (NSRect)rect; 100@end 101 102@implementation NSBundle (NSImageAdditions) 103 104static NSArray* 105imageTypes() 106{ 107 NSArray *types; 108 109 /* If the extension is one of the image types, 110 * remove it from the name and place it in the 111 * type argument. 112 */ 113 types = [[[GSTheme theme] imageClass] imageUnfilteredFileTypes]; 114 if (nil == types) 115 { 116 types = [NSImage imageUnfilteredFileTypes]; 117 } 118 return types; 119} 120 121static void 122fixupImageNameAndType(NSString **name, NSString **type) 123{ 124 NSString *ext = [*name pathExtension]; 125 126 if ([ext length] > 0) 127 { 128 /* If the extension is one of the image types, 129 * remove it from the name and place it in the 130 * type argument. 131 */ 132 if ([imageTypes() indexOfObject: ext] != NSNotFound) 133 { 134 *type = ext; 135 *name = [*name stringByDeletingPathExtension]; 136 } 137 } 138} 139 140- (NSString *) _pathForImageNamed: (NSString *)aName 141 ofType: (NSString *)ext 142 subdirectory: (NSString *)aDir 143 inBundle: (NSBundle *)aBundle 144{ 145 NSEnumerator *e; 146 id o; 147 148 if (ext != nil) 149 { 150 return [aBundle pathForResource: aName ofType: ext inDirectory: aDir]; 151 } 152 153 e = [imageTypes() objectEnumerator]; 154 while ((o = [e nextObject]) != nil) 155 { 156 NSString *path; 157 158 path = [aBundle pathForResource: aName ofType: o inDirectory: aDir]; 159 if ([path length] > 0) 160 { 161 return path; 162 } 163 } 164 return nil; 165} 166 167- (NSString *) _pathForLibraryImageNamed: (NSString *)aName 168 ofType: (NSString *)ext 169 inDirectory: (NSString *)aDir 170{ 171 NSEnumerator *e; 172 id o; 173 174 if (ext != nil) 175 { 176 return [NSBundle pathForLibraryResource: aName 177 ofType: ext 178 inDirectory: aDir]; 179 } 180 181 e = [imageTypes() objectEnumerator]; 182 while ((o = [e nextObject]) != nil) 183 { 184 NSString *path; 185 186 path = [NSBundle pathForLibraryResource: aName 187 ofType: o 188 inDirectory: aDir]; 189 if ([path length] > 0) 190 { 191 return path; 192 } 193 } 194 195 return nil; 196} 197 198- (NSString *) _pathForSystemImageNamed: (NSString *)realName 199 ofType: (NSString *)ext 200{ 201 NSString *path; 202 203 path = [self _pathForLibraryImageNamed: realName 204 ofType: ext 205 inDirectory: @"Images"]; 206 207 /* If not found then search in system using the reverse NSImage nsmapping */ 208 if (nil == path) 209 { 210 NSEnumerator *e; 211 NSString *aliasName; 212 213 e = [[nsmapping allKeysForObject: realName] objectEnumerator]; 214 while ((aliasName = [e nextObject]) != nil) 215 { 216 path = [self _pathForLibraryImageNamed: aliasName 217 ofType: ext 218 inDirectory: @"Images"]; 219 220 if (path != nil) 221 { 222 break; 223 } 224 } 225 } 226 227 return path; 228} 229 230/* 231 * nsmapping.strings maps alternative image naming schemes to the GSTheme 232 * standard image naming scheme. For example, NSSwitch (from OpenStep) and 233 * common_SwitchOff (from GNUstep) are mapped to GSSwitch. In nameDict that 234 * tracks image instances, the keys are image names from GSTheme such as 235 * GSSwitch or additional icon names such NSApplicationIcon or 236 * NSToolbarShowColors. In the long run, it would be cleaner to move built-in 237 * theme images into a GNUstep.theme bundle. 238 * 239 * If you pass NSSwitch to +imageNamed:, nsmapping is used to get GSSwitch as 240 * the real name, then _pathForImageNamed: will look up the image first in the 241 * theme and fall back on the Library images. For the library images, we do a 242 * reverse lookup in nsmapping (using allKeysForObject:) to get the image file 243 * name (e.g. from GSSwitch to common_SwitchOff). This reverse lookup is 244 * similar to the one supported for getting image file names from the 245 * bundle, this reverse lookup could be handled by GSTheme rather than being 246 * 247 * The type received in argument is meaningfull for searching image files 248 * using the proposed image name, but useless otherwise. 249 */ 250- (NSString *) _pathForThemeImageNamed: (NSString *)name 251 ofType: (NSString *)ext 252{ 253 GSTheme *theme; 254 NSDictionary *themeMapping; 255 NSString *mappedName; 256 NSString *path = nil; 257 258 theme = [GSTheme theme]; 259 themeMapping = [[theme infoDictionary] objectForKey: @"GSThemeImages"]; 260 mappedName = [themeMapping objectForKey: name]; 261 262 /* First search among the theme images using the GSTheme mapping */ 263 if (mappedName != nil) 264 { 265 NSString *extension = nil; 266 NSString *proposedName = mappedName; 267 268 fixupImageNameAndType(&proposedName, &extension); 269 270 /* If the image file name from the theme mapping uses an extension, 271 * this extension is used to look up the path. If the image file 272 * cannot be found, _pathForImageNamed:ofType:subdirectory:inBundle: 273 * searches an image file for the file extensions from -imageFileTypes. 274 */ 275 path = [self _pathForImageNamed: proposedName 276 ofType: extension 277 subdirectory: @"ThemeImages" 278 inBundle: [theme bundle]]; 279 } 280 281 /* If not found, search among the theme images using the reverse NSImage 282 * mapping (for GNUstep and OpenStep image names such as common_SwitchOff 283 * or NSSwitch) 284 */ 285 if (nil == path) 286 { 287 NSEnumerator *e; 288 NSString *aliasName; 289 290 e = [[nsmapping allKeysForObject: name] objectEnumerator]; 291 while (nil == path && (aliasName = [e nextObject]) != nil) 292 { 293 NSAssert([[aliasName pathExtension] length] == 0, 294 @"nsmapping.strings " 295 "must include no extensions in image file names"); 296 297 path = [self _pathForImageNamed: aliasName 298 ofType: nil 299 subdirectory: @"ThemeImages" 300 inBundle: [theme bundle]]; 301 } 302 } 303 304 /* If not found, search among the theme images using the image name directly 305 */ 306 if (path == nil) 307 { 308 path = [self _pathForImageNamed: name 309 ofType: ext 310 subdirectory: @"ThemeImages" 311 inBundle: [theme bundle]]; 312 } 313 314 return path; 315} 316 317- (NSString*) pathForImageResource: (NSString*)name 318{ 319 NSString *ext = nil; 320 NSString *path = nil; 321 NSString *ident; 322 323 fixupImageNameAndType(&name, &ext); 324 if (nil != (ident = [self bundleIdentifier])) 325 { 326 NSString *subdir; 327 328 subdir = [@"ThemeImages" stringByAppendingPathComponent: ident]; 329 path = [self _pathForImageNamed: name 330 ofType: ext 331 subdirectory: subdir 332 inBundle: [[GSTheme theme] bundle]]; 333 } 334 if (nil == path) 335 { 336 path = [self _pathForImageNamed: name 337 ofType: ext 338 subdirectory: nil 339 inBundle: self]; 340 if (nil == path) 341 { 342 path = [self _pathForThemeImageNamed: name ofType: ext]; 343 if (nil == path) 344 { 345 path = [self _pathForSystemImageNamed: name ofType: ext]; 346 } 347 } 348 } 349 return path; 350} 351 352@end 353 354@interface GSRepData : NSObject 355{ 356@public 357 NSImageRep *rep; 358 NSImageRep *original; 359 NSColor *bg; 360} 361@end 362 363@implementation GSRepData 364- (id) copyWithZone: (NSZone*)z 365{ 366 GSRepData *c = (GSRepData*)NSCopyObject(self, 0, z); 367 368 if (c->rep) 369 c->rep = [c->rep copyWithZone: z]; 370 if (c->bg) 371 c->bg = [c->bg copyWithZone: z]; 372 return c; 373} 374 375- (void) dealloc 376{ 377 TEST_RELEASE(rep); 378 TEST_RELEASE(bg); 379 [super dealloc]; 380} 381@end 382 383/* Class variables and functions for class methods */ 384static NSRecursiveLock *imageLock = nil; 385static NSMutableDictionary *nameDict = nil; 386static NSColor *clearColor = nil; 387static Class cachedClass = 0; 388static Class bitmapClass = 0; 389// Cache for the supported file types 390static NSArray *imageUnfilteredFileTypes = nil; 391static NSArray *imageFileTypes = nil; 392static NSArray *imageUnfilteredPasteboardTypes = nil; 393static NSArray *imagePasteboardTypes = nil; 394 395static NSArray *iterate_reps_for_types(NSArray *imageReps, SEL method); 396 397/* Find the GSRepData object holding a representation */ 398static GSRepData* 399repd_for_rep(NSArray *_reps, NSImageRep *rep) 400{ 401 NSEnumerator *enumerator = [_reps objectEnumerator]; 402 IMP nextImp = [enumerator methodForSelector: @selector(nextObject)]; 403 GSRepData *repd; 404 405 while ((repd = (*nextImp)(enumerator, @selector(nextObject))) != nil) 406 { 407 if (repd->rep == rep) 408 { 409 return repd; 410 } 411 } 412 [NSException raise: NSInternalInconsistencyException 413 format: @"Cannot find stored representation"]; 414 /* NOT REACHED */ 415 return nil; 416} 417 418@interface NSImage (Private) 419+ (void) _clearFileTypeCaches: (NSNotification*)notif; 420+ (void) _reloadCachedImages; 421- (BOOL) _useFromFile: (NSString *)fileName; 422- (BOOL) _loadFromData: (NSData *)data; 423- (BOOL) _loadFromFile: (NSString *)fileName; 424- (BOOL) _resetAndUseFromFile: (NSString *)fileName; 425- (GSRepData*) _cacheForRep: (NSImageRep*)rep; 426- (NSCachedImageRep*) _doImageCache: (NSImageRep *)rep; 427@end 428 429@implementation NSImage 430 431+ (void) initialize 432{ 433 if (imageLock == nil) 434 { 435 NSString *path; 436 437 imageLock = [NSRecursiveLock new]; 438 [imageLock lock]; 439 440 // Initial version 441 [self setVersion: 1]; 442 443 // initialize the class variables 444 nameDict = [[NSMutableDictionary alloc] initWithCapacity: 10]; 445 path = [NSBundle pathForLibraryResource: @"nsmapping" 446 ofType: @"strings" 447 inDirectory: @"Images"]; 448 if (path) 449 nsmapping = RETAIN([[NSString stringWithContentsOfFile: path] 450 propertyListFromStringsFileFormat]); 451 clearColor = RETAIN([NSColor clearColor]); 452 cachedClass = [NSCachedImageRep class]; 453 bitmapClass = [NSBitmapImageRep class]; 454 [[NSNotificationCenter defaultCenter] 455 addObserver: self 456 selector: @selector(_clearFileTypeCaches:) 457 name: NSImageRepRegistryChangedNotification 458 object: [NSImageRep class]]; 459 [imageLock unlock]; 460 } 461} 462 463+ (id) imageNamed: (NSString *)aName 464{ 465 NSImage *image; 466 NSString *realName; 467 468 [imageLock lock]; 469 470 realName = [nsmapping objectForKey: aName]; 471 if (realName == nil) 472 { 473 realName = aName; 474 } 475 image = (NSImage*)[nameDict objectForKey: realName]; 476 477 if (image == nil && realName != nil) 478 { 479 NSString *path = [[NSBundle mainBundle] pathForImageResource: realName]; 480 481 if ([path length] != 0) 482 { 483 image = [[[[GSTheme theme] imageClass] alloc] 484 initByReferencingFile: path]; 485 if (image != nil) 486 { 487 [image setName: realName]; 488 image->_flags.archiveByName = YES; 489 AUTORELEASE(image); 490 } 491 } 492 } 493 494 IF_NO_GC([[image retain] autorelease]); 495 [imageLock unlock]; 496 return image; 497} 498 499+ (NSImage *) _standardImageWithName: (NSString *)name 500{ 501 NSImage *image = nil; 502 503 image = [NSImage imageNamed: name]; 504 if (image == nil) 505 image = [NSImage imageNamed: [@"common_" stringByAppendingString: name]]; 506 return image; 507} 508 509- (id) init 510{ 511 return [self initWithSize: NSMakeSize(0, 0)]; 512} 513 514- (id) initWithSize: (NSSize)aSize 515{ 516 if (!(self = [super init])) 517 return nil; 518 519 //_flags.archiveByName = NO; 520 //_flags.scalable = NO; 521 //_flags.dataRetained = NO; 522 //_flags.flipDraw = NO; 523 if (aSize.width && aSize.height) 524 { 525 _size = aSize; 526 _flags.sizeWasExplicitlySet = YES; 527 } 528 //_flags.usesEPSOnResolutionMismatch = NO; 529 _flags.colorMatchPreferred = YES; 530 _flags.multipleResolutionMatching = YES; 531 //_flags.cacheSeparately = NO; 532 //_flags.unboundedCacheDepth = NO; 533 //_flags.syncLoad = NO; 534 _reps = [[NSMutableArray alloc] initWithCapacity: 2]; 535 ASSIGN(_color, clearColor); 536 _cacheMode = NSImageCacheDefault; 537 538 return self; 539} 540 541- (id) initByReferencingFile: (NSString *)fileName 542{ 543 if (!(self = [self init])) 544 return nil; 545 546 if (![self _useFromFile: fileName]) 547 { 548 RELEASE(self); 549 return nil; 550 } 551 _flags.archiveByName = YES; 552 553 return self; 554} 555 556- (id) initWithContentsOfFile: (NSString *)fileName 557{ 558 if (!(self = [self init])) 559 return nil; 560 561 _flags.dataRetained = YES; 562 if (![self _loadFromFile: fileName]) 563 { 564 RELEASE(self); 565 return nil; 566 } 567 568 return self; 569} 570 571- (id) initWithData: (NSData *)data 572{ 573 if (!(self = [self init])) 574 return nil; 575 576 _flags.dataRetained = YES; 577 if (![self _loadFromData: data]) 578 { 579 RELEASE(self); 580 return nil; 581 } 582 583 return self; 584} 585 586- (id) initWithBitmapHandle: (void *)bitmap 587{ 588 NSImageRep *rep; 589 590 if (!(self = [self init])) 591 return nil; 592 593 rep = [[NSBitmapImageRep alloc] initWithBitmapHandle: bitmap]; 594 if (rep == nil) 595 { 596 RELEASE(self); 597 return nil; 598 } 599 600 [self addRepresentation: rep]; 601 RELEASE(rep); 602 return self; 603} 604 605- (id)initWithIconHandle:(void *)icon 606{ 607 // Only needed on MS Windows 608 NSImageRep *rep; 609 610 if (!(self = [self init])) 611 return nil; 612 613 rep = [[NSBitmapImageRep alloc] initWithIconHandle: icon]; 614 if (rep == nil) 615 { 616 RELEASE(self); 617 return nil; 618 } 619 620 [self addRepresentation: rep]; 621 RELEASE(rep); 622 return self; 623} 624 625- (id) initWithContentsOfURL: (NSURL *)anURL 626{ 627 NSArray *array; 628 629 if (!(self = [self init])) 630 return nil; 631 632 array = [NSImageRep imageRepsWithContentsOfURL: anURL]; 633 if (!array) 634 { 635 RELEASE(self); 636 return nil; 637 } 638 639 _flags.dataRetained = YES; 640 [self addRepresentations: array]; 641 return self; 642} 643 644- (id) initWithPasteboard: (NSPasteboard *)pasteboard 645{ 646 NSArray *reps; 647 if (!(self = [self init])) 648 return nil; 649 650 reps = [NSImageRep imageRepsWithPasteboard: pasteboard]; 651 if (reps != nil) 652 [self addRepresentations: reps]; 653 else 654 { 655 NSArray *array = [pasteboard propertyListForType: NSFilenamesPboardType]; 656 NSString* file; 657 658 if ((array == nil) || ([array count] == 0) 659 || (file = [array objectAtIndex: 0]) == nil 660 || ![self _loadFromFile: file]) 661 { 662 RELEASE(self); 663 return nil; 664 } 665 } 666 _flags.dataRetained = YES; 667 668 return self; 669} 670 671- (void) dealloc 672{ 673 if (_name == nil) 674 { 675 RELEASE(_reps); 676 TEST_RELEASE(_fileName); 677 RELEASE(_color); 678 [super dealloc]; 679 } 680 else 681 { 682 [self retain]; 683 NSLog(@"Warning ... attempt to deallocate image with name: %@", _name); 684 } 685} 686 687- (id) copyWithZone: (NSZone *)zone 688{ 689 NSImage *copy; 690 NSArray *reps = [self representations]; 691 NSEnumerator *enumerator = [reps objectEnumerator]; 692 NSImageRep *rep; 693 694 copy = (NSImage*)NSCopyObject (self, 0, zone); 695 696 copy->_name = nil; 697 RETAIN(_fileName); 698 RETAIN(_color); 699 copy->_lockedView = nil; 700 // FIXME: maybe we should retain if _flags.dataRetained = NO 701 copy->_reps = [[NSMutableArray alloc] initWithCapacity: [_reps count]]; 702 703 // Only copy non-cached reps. 704 while ((rep = [enumerator nextObject]) != nil) 705 { 706 if (![rep isKindOfClass: cachedClass]) 707 { 708 [copy addRepresentation: rep]; 709 } 710 } 711 712 return copy; 713} 714 715- (BOOL) isEqual: (id)anObject 716{ 717 if (self == anObject) 718 return YES; 719 if (![anObject isKindOfClass: [NSImage class]]) 720 return NO; 721 722 // FIXME 723 return NO; 724} 725 726- (NSString*) description 727{ 728 return [NSString stringWithFormat: @"<%@ %p Name=%@ Size=%@ Reps=%@>", 729 [self class], 730 self, 731 [self name], 732 NSStringFromSize([self size]), 733 [self representations]]; 734} 735 736/* This methd sets the name of an image, updating the global name dictionary 737 * to point to the image (or removing an image from the dictionary if the 738 * new name is nil). 739 */ 740- (BOOL) setName: (NSString *)aName 741{ 742 [imageLock lock]; 743 744 /* The name is already set... nothing to do. 745 */ 746 if (aName == _name || [aName isEqual: _name] == YES) 747 { 748 [imageLock unlock]; 749 return YES; 750 } 751 752 /* If the new name is already in use by another image, 753 * we must do nothing. 754 */ 755 if (aName != nil && [nameDict objectForKey: aName] != nil) 756 { 757 [imageLock unlock]; 758 return NO; 759 } 760 761 /* If this image had another name, we remove it. 762 */ 763 if (_name != nil) 764 { 765 /* We retain self in case removing from the dictionary releases us */ 766 IF_NO_GC([[self retain] autorelease]); 767 [nameDict removeObjectForKey: _name]; 768 DESTROY(_name); 769 } 770 771 /* If the new name is null, there is nothing more to do. 772 */ 773 if (aName == nil) 774 { 775 [imageLock unlock]; 776 return NO; 777 } 778 779 ASSIGN(_name, aName); 780 781 [nameDict setObject: self forKey: _name]; 782 783 [imageLock unlock]; 784 return YES; 785} 786 787- (NSString *) name 788{ 789 NSString *name; 790 791 [imageLock lock]; 792 name = [[_name retain] autorelease]; 793 [imageLock unlock]; 794 return name; 795} 796 797- (void) setSize: (NSSize)aSize 798{ 799 _size = aSize; 800 _flags.sizeWasExplicitlySet = YES; 801} 802 803- (NSSize) size 804{ 805 if (_size.width == 0) 806 { 807 NSImageRep *rep = [self bestRepresentationForDevice: nil]; 808 809 if (rep) 810 _size = [rep size]; 811 else 812 _size = NSZeroSize; 813 } 814 return _size; 815} 816 817- (BOOL) isFlipped 818{ 819 return _flags.flipDraw; 820} 821 822- (void) setFlipped: (BOOL)flag 823{ 824 _flags.flipDraw = flag; 825} 826 827// Choosing Which Image Representation to Use 828- (void) setUsesEPSOnResolutionMismatch: (BOOL)flag 829{ 830 _flags.useEPSOnResolutionMismatch = flag; 831} 832 833- (BOOL) usesEPSOnResolutionMismatch 834{ 835 return _flags.useEPSOnResolutionMismatch; 836} 837 838- (void) setPrefersColorMatch: (BOOL)flag 839{ 840 _flags.colorMatchPreferred = flag; 841} 842 843- (BOOL) prefersColorMatch 844{ 845 return _flags.colorMatchPreferred; 846} 847 848- (void) setMatchesOnMultipleResolution: (BOOL)flag 849{ 850 _flags.multipleResolutionMatching = flag; 851} 852 853- (BOOL) matchesOnMultipleResolution 854{ 855 return _flags.multipleResolutionMatching; 856} 857 858// Determining How the Image is Stored 859- (void) setCachedSeparately: (BOOL)flag 860{ 861 _flags.cacheSeparately = flag; 862} 863 864- (BOOL) isCachedSeparately 865{ 866 return _flags.cacheSeparately; 867} 868 869- (void) setDataRetained: (BOOL)flag 870{ 871 _flags.dataRetained = flag; 872} 873 874- (BOOL) isDataRetained 875{ 876 return _flags.dataRetained; 877} 878 879- (void) setCacheDepthMatchesImageDepth: (BOOL)flag 880{ 881 _flags.unboundedCacheDepth = flag; 882} 883 884- (BOOL) cacheDepthMatchesImageDepth 885{ 886 return _flags.unboundedCacheDepth; 887} 888 889- (void) setCacheMode: (NSImageCacheMode)mode 890{ 891 _cacheMode = mode; 892} 893 894- (NSImageCacheMode) cacheMode 895{ 896 return _cacheMode; 897} 898 899 900// Determining How the Image is Drawn 901- (BOOL) isValid 902{ 903 BOOL valid = NO; 904 NSUInteger i, count; 905 906 if (_flags.syncLoad) 907 { 908 /* Make sure any images that were added with _useFromFile: are loaded 909 in and added to the representation list. */ 910 if (![self _loadFromFile: _fileName]) 911 return NO; 912 _flags.syncLoad = NO; 913 } 914 915 /* Go through all our representations and determine if at least one 916 is a valid cache */ 917 // FIXME: Not sure if this is correct 918 count = [_reps count]; 919 for (i = 0; i < count; i++) 920 { 921 GSRepData *repd = (GSRepData*)[_reps objectAtIndex: i]; 922 923 if (repd->bg != nil || [repd->rep isKindOfClass: cachedClass] == NO) 924 { 925 valid = YES; 926 break; 927 } 928 } 929 930 return valid; 931} 932 933- (void) recache 934{ 935 NSUInteger i; 936 937 i = [_reps count]; 938 while (i--) 939 { 940 GSRepData *repd; 941 942 repd = (GSRepData*)[_reps objectAtIndex: i]; 943 if (repd->original != nil) 944 { 945 [_reps removeObjectAtIndex: i]; 946 } 947 } 948} 949 950- (void) setScalesWhenResized: (BOOL)flag 951{ 952 // FIXME: This currently breaks NSImage. 953 // See the test case in GSTest/Image-test that uses this method. 954 955 // _flags.scalable = flag; 956} 957 958- (BOOL) scalesWhenResized 959{ 960 return _flags.scalable; 961} 962 963- (void) setBackgroundColor: (NSColor *)aColor 964{ 965 if (aColor == nil) 966 { 967 aColor = clearColor; 968 } 969 ASSIGN(_color, aColor); 970} 971 972- (NSColor *) backgroundColor 973{ 974 return _color; 975} 976 977// Using the Image 978- (void) compositeToPoint: (NSPoint)aPoint 979 operation: (NSCompositingOperation)op 980{ 981 [self compositeToPoint: aPoint 982 fromRect: NSZeroRect 983 operation: op 984 fraction: 1.0]; 985} 986 987- (void) compositeToPoint: (NSPoint)aPoint 988 fromRect: (NSRect)aRect 989 operation: (NSCompositingOperation)op 990{ 991 [self compositeToPoint: aPoint 992 fromRect: aRect 993 operation: op 994 fraction: 1.0]; 995} 996 997- (void) compositeToPoint: (NSPoint)aPoint 998 operation: (NSCompositingOperation)op 999 fraction: (CGFloat)delta 1000{ 1001 [self compositeToPoint: aPoint 1002 fromRect: NSZeroRect 1003 operation: op 1004 fraction: delta]; 1005} 1006 1007- (void) compositeToPoint: (NSPoint)aPoint 1008 fromRect: (NSRect)srcRect 1009 operation: (NSCompositingOperation)op 1010 fraction: (CGFloat)delta 1011{ 1012 NSGraphicsContext *ctxt = GSCurrentContext(); 1013 1014 // Calculate the user space scale factor of the current window 1015 NSView *focusView = [NSView focusView]; 1016 CGFloat scaleFactor = 1.0; 1017 if (focusView != nil) 1018 { 1019 scaleFactor = [[focusView window] userSpaceScaleFactor]; 1020 } 1021 1022 // Set the CTM to the identity matrix with the current translation 1023 // and the user space scale factor 1024 { 1025 NSAffineTransform *backup = [[ctxt GSCurrentCTM] retain]; 1026 NSAffineTransform *newTransform = [NSAffineTransform transform]; 1027 NSPoint translation = [backup transformPoint: aPoint]; 1028 [newTransform translateXBy: translation.x 1029 yBy: translation.y]; 1030 [newTransform scaleBy: scaleFactor]; 1031 1032 [ctxt GSSetCTM: newTransform]; 1033 1034 [self drawAtPoint: NSMakePoint(0, 0) 1035 fromRect: srcRect 1036 operation: op 1037 fraction: delta]; 1038 1039 [ctxt GSSetCTM: backup]; 1040 1041 [backup release]; 1042 } 1043} 1044 1045- (void) dissolveToPoint: (NSPoint)aPoint fraction: (CGFloat)aFloat 1046{ 1047 [self dissolveToPoint: aPoint 1048 fromRect: NSZeroRect 1049 fraction: aFloat]; 1050} 1051 1052- (void) dissolveToPoint: (NSPoint)aPoint 1053 fromRect: (NSRect)aRect 1054 fraction: (CGFloat)aFloat 1055{ 1056 [self compositeToPoint: aPoint 1057 fromRect: aRect 1058 operation: NSCompositeSourceOver 1059 fraction: aFloat]; 1060} 1061 1062- (BOOL) drawRepresentation: (NSImageRep *)imageRep inRect: (NSRect)aRect 1063{ 1064 BOOL r; 1065 NSGraphicsContext *ctxt = GSCurrentContext(); 1066 1067 DPSgsave(ctxt); 1068 1069 if (_color != nil) 1070 { 1071 NSRect fillrect = aRect; 1072 1073 [_color set]; 1074 NSRectFill(fillrect); 1075 1076 if ([GSCurrentContext() isDrawingToScreen] == NO) 1077 { 1078 /* Reset alpha for image drawing. */ 1079 [[NSColor colorWithCalibratedWhite: 1.0 alpha: 1.0] set]; 1080 } 1081 } 1082 1083 if (!_flags.scalable) 1084 r = [imageRep drawAtPoint: aRect.origin]; 1085 else 1086 r = [imageRep drawInRect: aRect]; 1087 1088 DPSgrestore(ctxt); 1089 1090 return r; 1091} 1092 1093- (void) drawAtPoint: (NSPoint)point 1094 fromRect: (NSRect)srcRect 1095 operation: (NSCompositingOperation)op 1096 fraction: (CGFloat)delta 1097{ 1098 [self drawInRect: NSMakeRect(point.x, point.y, srcRect.size.width, srcRect.size.height) 1099 fromRect: srcRect 1100 operation: op 1101 fraction: delta 1102 respectFlipped: NO 1103 hints: nil]; 1104} 1105 1106- (void) drawInRect: (NSRect)rect 1107{ 1108 [self drawInRect: rect 1109 fromRect: NSZeroRect 1110 operation: NSCompositeSourceOver 1111 fraction: 1.0]; 1112} 1113 1114- (void) drawInRect: (NSRect)dstRect 1115 fromRect: (NSRect)srcRect 1116 operation: (NSCompositingOperation)op 1117 fraction: (CGFloat)delta 1118{ 1119 [self drawInRect: dstRect 1120 fromRect: srcRect 1121 operation: op 1122 fraction: delta 1123 respectFlipped: NO 1124 hints: nil]; 1125} 1126 1127/** 1128 * Base drawing method in NSImage; all other draw methods call this one 1129 */ 1130- (void) drawInRect: (NSRect)dstRect // Negative width/height => Nothing draws. 1131 fromRect: (NSRect)srcRect 1132 operation: (NSCompositingOperation)op 1133 fraction: (CGFloat)delta 1134 respectFlipped: (BOOL)respectFlipped 1135 hints: (NSDictionary*)hints 1136{ 1137 NSImageRep *rep; 1138 NSGraphicsContext *ctxt; 1139 NSSize imgSize, repSize; 1140 NSRect repSrcRect; 1141 1142 ctxt = GSCurrentContext(); 1143 imgSize = [self size]; 1144 1145 // Handle abbreviated parameters 1146 1147 if (NSEqualRects(srcRect, NSZeroRect)) 1148 { 1149 srcRect.size = imgSize; 1150 } 1151 if (NSEqualSizes(dstRect.size, NSZeroSize)) // For -drawAtPoint:fromRect:operation:fraction: 1152 { 1153 dstRect.size = imgSize; 1154 } 1155 1156 if (imgSize.width <= 0 || imgSize.height <= 0) 1157 return; 1158 1159 // Select a rep 1160 1161 rep = [self bestRepresentationForRect: dstRect 1162 context: ctxt 1163 hints: hints]; 1164 if (rep == nil) 1165 return; 1166 1167 // Try to cache / get a cached version of the best rep 1168 1169 /** 1170 * We only use caching on backends that can efficiently draw a rect from the cache 1171 * onto the current graphics context respecting the CTM, which is currently cairo. 1172 */ 1173 if (_cacheMode != NSImageCacheNever && 1174 [ctxt supportsDrawGState]) 1175 { 1176 NSCachedImageRep *cache = [self _doImageCache: rep]; 1177 if (cache != nil) 1178 { 1179 rep = cache; 1180 } 1181 } 1182 1183 repSize = [rep size]; 1184 1185 // Convert srcRect from image coordinate space to rep coordinate space 1186 { 1187 const CGFloat imgToRepWidthScaleFactor = repSize.width / imgSize.width; 1188 const CGFloat imgToRepHeightScaleFactor = repSize.height / imgSize.height; 1189 1190 repSrcRect = NSMakeRect(srcRect.origin.x * imgToRepWidthScaleFactor, 1191 srcRect.origin.y * imgToRepHeightScaleFactor, 1192 srcRect.size.width * imgToRepWidthScaleFactor, 1193 srcRect.size.height * imgToRepHeightScaleFactor); 1194 } 1195 1196 // FIXME: Draw background? 1197 1198 [rep drawInRect: dstRect 1199 fromRect: repSrcRect 1200 operation: op 1201 fraction: delta 1202 respectFlipped: respectFlipped 1203 hints: hints]; 1204} 1205 1206- (void) addRepresentation: (NSImageRep *)imageRep 1207{ 1208 GSRepData *repd; 1209 1210 if (imageRep != nil) 1211 { 1212 repd = [GSRepData new]; 1213 repd->rep = RETAIN(imageRep); 1214 [_reps addObject: repd]; 1215 RELEASE(repd); 1216 } 1217} 1218 1219- (void) addRepresentations: (NSArray *)imageRepArray 1220{ 1221 NSUInteger i, count; 1222 GSRepData *repd; 1223 1224 count = [imageRepArray count]; 1225 for (i = 0; i < count; i++) 1226 { 1227 repd = [GSRepData new]; 1228 repd->rep = RETAIN([imageRepArray objectAtIndex: i]); 1229 [_reps addObject: repd]; 1230 RELEASE(repd); 1231 } 1232} 1233 1234- (void) removeRepresentation: (NSImageRep *)imageRep 1235{ 1236 NSUInteger i; 1237 GSRepData *repd; 1238 1239 i = [_reps count]; 1240 while (i-- > 0) 1241 { 1242 repd = (GSRepData*)[_reps objectAtIndex: i]; 1243 if (repd->rep == imageRep) 1244 { 1245 [_reps removeObjectAtIndex: i]; 1246 } 1247 else if (repd->original == imageRep) 1248 { 1249 // Remove cached representations for this representation 1250 // instead of turning them into real ones 1251 //repd->original = nil; 1252 [_reps removeObjectAtIndex: i]; 1253 } 1254 } 1255} 1256 1257- (void) lockFocus 1258{ 1259 [self lockFocusOnRepresentation: nil]; 1260} 1261 1262- (void) lockFocusOnRepresentation: (NSImageRep *)imageRep 1263{ 1264 if (_cacheMode != NSImageCacheNever) 1265 { 1266 NSWindow *window; 1267 GSRepData *repd; 1268 1269 if (NSEqualSizes(NSZeroSize, [self size])) 1270 [NSException raise: NSImageCacheException 1271 format: @"Cannot lock focus on image with size (0, 0)"]; 1272 1273 if (imageRep == nil) 1274 imageRep = [self bestRepresentationForDevice: nil]; 1275 1276 repd = [self _cacheForRep: imageRep]; 1277 if (repd == nil) 1278 return; 1279 1280 imageRep = repd->rep; 1281 1282 window = [(NSCachedImageRep *)imageRep window]; 1283 _lockedView = [window contentView]; 1284 if (_lockedView == nil) 1285 { 1286 [NSException raise: NSImageCacheException 1287 format: @"Cannot lock focus on nil rep"]; 1288 } 1289 1290 // FIXME: This is needed to get image caching working while printing. A better solution 1291 // needs to remove the viewIsPrinting variable from NSView. 1292 [_lockedView _lockFocusInContext: [window graphicsContext] inRect: [_lockedView bounds]]; 1293 if (repd->bg == nil) 1294 { 1295 NSRect fillrect = NSMakeRect(0, 0, _size.width, _size.height); 1296 1297 // Clear the background of the cached image, as it is not valid 1298 if ([_color alphaComponent] < 1.0) 1299 { 1300 /* With a Quartz-like alpha model, alpha can't be cleared 1301 with a rectfill, so we need to clear the alpha channel 1302 explictly. (A compositerect with NSCompositeCopy would 1303 be more efficient, but it doesn't seem like it's 1304 implemented correctly in all backends yet (as of 1305 2002-08-23). Also, this will work with both the Quartz- 1306 and DPS-model.) */ 1307 NSRectFillUsingOperation(fillrect, NSCompositeClear); 1308 } 1309 1310 repd->bg = [_color copy]; 1311 1312 if ([repd->bg alphaComponent] == 1.0) 1313 { 1314 [imageRep setOpaque: YES]; 1315 } 1316 else 1317 { 1318 [imageRep setOpaque: [repd->original isOpaque]]; 1319 } 1320 1321 // Fill with background colour and draw repesentation 1322 [self drawRepresentation: repd->original 1323 inRect: fillrect]; 1324 } 1325 } 1326} 1327 1328- (void) unlockFocus 1329{ 1330 if (_lockedView != nil) 1331 { 1332 [_lockedView unlockFocus]; 1333 _lockedView = nil; 1334 } 1335} 1336 1337/* Determine the number of color components in the device and 1338 filter out reps with a different number of color components. 1339 1340 If the device lacks a color space name, all reps are treated 1341 as matching. 1342 1343 If a rep lacks a color space name, it is assumed to match the 1344 device. 1345 1346 WARNING: Be careful not to inadvertently mix greyscale and color 1347 representations in a TIFF. The greyscale representations 1348 will never be selected as a best rep unless you are drawing on 1349 a greyscale surface, or all reps in the TIFF are greyscale. 1350*/ 1351- (NSMutableArray *) _bestRep: (NSArray *)reps 1352 withColorMatch: (NSDictionary*)deviceDescription 1353{ 1354 NSMutableArray *breps = [NSMutableArray array]; 1355 NSString *deviceColorSpace = [deviceDescription objectForKey: NSDeviceColorSpaceName]; 1356 1357 if (deviceColorSpace != nil) 1358 { 1359 NSUInteger deviceColors = NSNumberOfColorComponents(deviceColorSpace); 1360 NSEnumerator *enumerator = [reps objectEnumerator]; 1361 NSImageRep *rep; 1362 while ((rep = [enumerator nextObject]) != nil) 1363 { 1364 if ([rep colorSpaceName] == nil || 1365 NSNumberOfColorComponents([rep colorSpaceName]) == deviceColors) 1366 { 1367 [breps addObject: rep]; 1368 } 1369 } 1370 } 1371 1372 /* If there are no matches, pass all the reps */ 1373 if ([breps count] == 0) 1374 { 1375 [breps setArray: reps]; 1376 } 1377 1378 return breps; 1379} 1380 1381/** 1382 * Returns YES if x in an integer multiple of y 1383 */ 1384static BOOL GSIsMultiple(CGFloat x, CGFloat y) 1385{ 1386 // FIXME: Test when CGFloat is float and make sure this test isn't 1387 // too strict due to floating point rounding errors. 1388 return (x/y) == floor(x/y); 1389} 1390 1391/** 1392 * Returns YES if there exist integers p and q such that 1393 * (baseSize.width * p == size.width) && (baseSize.height * q == size.height) 1394 */ 1395static BOOL GSSizeIsIntegerMultipleOfSize(NSSize size, NSSize baseSize) 1396{ 1397 return NSEqualSizes(size, baseSize) || 1398 (GSIsMultiple(size.width, baseSize.width) && 1399 GSIsMultiple(size.height, baseSize.height)); 1400} 1401 1402/** 1403 * Returns {0, 0} if the image rep doesn't have a size set, 1404 * or the pixelsWide or pixelsHigh are NSImageRepMatchesDevice 1405 */ 1406static NSSize GSResolutionOfImageRep(NSImageRep *rep) 1407{ 1408 const int pixelsWide = [rep pixelsWide]; 1409 const int pixelsHigh = [rep pixelsHigh]; 1410 const NSSize repSize = [rep size]; 1411 1412 if (repSize.width == 0 || repSize.height == 0) 1413 { 1414 return NSMakeSize(0, 0); 1415 } 1416 else if (pixelsWide == NSImageRepMatchesDevice || 1417 pixelsHigh == NSImageRepMatchesDevice) 1418 { 1419 return NSMakeSize(0, 0); 1420 } 1421 else 1422 { 1423 return NSMakeSize(72.0 * (CGFloat)pixelsWide / repSize.width, 1424 72.0 * (CGFloat)pixelsHigh / repSize.height); 1425 } 1426} 1427 1428/* Find reps that match the resolution (DPI) of the device (including integer 1429 multiples of the device resplition if [self multipleResolutionMatching] 1430 is YES). 1431 1432 If there are no DPI matches, use any available vector reps if 1433 [self usesEPSOnResolutionMismatch] is YES. Otherwise, use the bitmap reps 1434 that have the highest DPI. 1435*/ 1436- (NSMutableArray *) _bestRep: (NSArray *)reps 1437 withResolutionMatch: (NSDictionary*)deviceDescription 1438{ 1439 NSMutableArray *breps = [NSMutableArray array]; 1440 1441 NSValue *resolution = [deviceDescription objectForKey: NSDeviceResolution]; 1442 1443 // 1. Look for exact resolution matches, or integer multiples if permitted. 1444 1445 if (nil != resolution) 1446 { 1447 const NSSize dres = [resolution sizeValue]; 1448 1449 if (![self matchesOnMultipleResolution]) 1450 { 1451 NSImageRep *rep; 1452 NSEnumerator *enumerator = [reps objectEnumerator]; 1453 1454 while ((rep = [enumerator nextObject]) != nil) 1455 { 1456 if (NSEqualSizes(GSResolutionOfImageRep(rep), dres)) 1457 { 1458 [breps addObject: rep]; 1459 } 1460 } 1461 } 1462 else // [self matchesOnMultipleResolution] 1463 { 1464 NSMutableArray *integerMultiples = [NSMutableArray array]; 1465 NSSize closestRes = NSMakeSize(CGFLOAT_MAX, CGFLOAT_MAX); 1466 NSImageRep *rep; 1467 NSEnumerator *enumerator; 1468 1469 // Iterate through the reps, keeping track of which ones 1470 // have a resolution which is an integer multiple of the device 1471 // res, and keep track of the cloest resolution 1472 1473 enumerator = [reps objectEnumerator]; 1474 while ((rep = [enumerator nextObject]) != nil) 1475 { 1476 const NSSize repRes = GSResolutionOfImageRep(rep); 1477 if (GSSizeIsIntegerMultipleOfSize(repRes, dres)) 1478 { 1479 const NSSize repResDifference 1480 = NSMakeSize(fabs(repRes.width - dres.width), 1481 fabs(repRes.height - dres.height)); 1482 const NSSize closestResolutionDifference 1483 = NSMakeSize(fabs(closestRes.width - dres.width), 1484 fabs(closestRes.height - dres.height)); 1485 if (repResDifference.width 1486 < closestResolutionDifference.width 1487 && repResDifference.height 1488 < closestResolutionDifference.height) 1489 { 1490 closestRes = repRes; 1491 } 1492 [integerMultiples addObject: rep]; 1493 } 1494 } 1495 1496 enumerator = [integerMultiples objectEnumerator]; 1497 while ((rep = [enumerator nextObject]) != nil) 1498 { 1499 const NSSize repRes = GSResolutionOfImageRep(rep); 1500 if (NSEqualSizes(repRes, closestRes)) 1501 { 1502 [breps addObject: rep]; 1503 } 1504 } 1505 } 1506 } 1507 1508 // 2. If no exact matches found, use vector reps, if they are preferred 1509 1510 if ([breps count] == 0 && [self usesEPSOnResolutionMismatch]) 1511 { 1512 NSImageRep *rep; 1513 NSEnumerator *enumerator = [reps objectEnumerator]; 1514 while ((rep = [enumerator nextObject]) != nil) 1515 { 1516 if ([rep pixelsWide] == NSImageRepMatchesDevice && 1517 [rep pixelsHigh] == NSImageRepMatchesDevice) 1518 { 1519 [breps addObject: rep]; 1520 } 1521 } 1522 } 1523 1524 // 3. If there are still no matches, use all of the bitmaps with the highest 1525 // resolution (DPI) 1526 1527 if ([breps count] == 0) 1528 { 1529 NSSize maxRes = NSMakeSize(0,0); 1530 NSImageRep *rep; 1531 NSEnumerator *enumerator; 1532 1533 // Determine maxRes 1534 1535 enumerator = [reps objectEnumerator]; 1536 while ((rep = [enumerator nextObject]) != nil) 1537 { 1538 const NSSize res = GSResolutionOfImageRep(rep); 1539 if (res.width > maxRes.width && 1540 res.height > maxRes.height) 1541 { 1542 maxRes = res; 1543 } 1544 } 1545 1546 // Use all reps with maxRes 1547 enumerator = [reps objectEnumerator]; 1548 while ((rep = [enumerator nextObject]) != nil) 1549 { 1550 const NSSize res = GSResolutionOfImageRep(rep); 1551 if (NSEqualSizes(res, maxRes)) 1552 { 1553 [breps addObject: rep]; 1554 } 1555 } 1556 } 1557 1558 // 4. If there are still none, use all available reps. 1559 // Note that this handles using vector reps in the case where there are 1560 // no bitmap reps, but [self usesEPSOnResolutionMismatch] is NO. 1561 1562 if ([breps count] == 0) 1563 { 1564 [breps setArray: reps]; 1565 } 1566 1567 return breps; 1568} 1569 1570/* Find the reps that match the bitsPerSample of the device, 1571 or if none match exactly, return all that have the highest bitsPerSample. 1572 1573 If the device lacks a bps, all reps are treated as matching. 1574 1575 If a rep has NSImageRepMatchesDevice as its bps, it is treated as matching. 1576*/ 1577- (NSMutableArray *) _bestRep: (NSArray *)reps 1578 withBpsMatch: (NSDictionary*)deviceDescription 1579{ 1580 NSMutableArray *breps = [NSMutableArray array]; 1581 NSNumber *bpsValue = [deviceDescription objectForKey: NSDeviceBitsPerSample]; 1582 1583 if (bpsValue != nil) 1584 { 1585 NSInteger deviceBps = [bpsValue integerValue]; 1586 NSInteger maxBps = -1; 1587 BOOL haveDeviceBps = NO; 1588 NSImageRep *rep; 1589 NSEnumerator *enumerator; 1590 1591 // Determine maxBps 1592 1593 enumerator = [reps objectEnumerator]; 1594 while ((rep = [enumerator nextObject]) != nil) 1595 { 1596 if ([rep bitsPerSample] > maxBps) 1597 { 1598 maxBps = [rep bitsPerSample]; 1599 } 1600 if ([rep bitsPerSample] == deviceBps) 1601 { 1602 haveDeviceBps = YES; 1603 } 1604 } 1605 1606 // Use all reps with deviceBps if haveDeviceBps is YES, 1607 // otherwise use all reps with maxBps 1608 enumerator = [reps objectEnumerator]; 1609 while ((rep = [enumerator nextObject]) != nil) 1610 { 1611 if ([rep bitsPerSample] == NSImageRepMatchesDevice || 1612 (!haveDeviceBps && [rep bitsPerSample] == maxBps) || 1613 (haveDeviceBps && [rep bitsPerSample] == deviceBps)) 1614 { 1615 [breps addObject: rep]; 1616 } 1617 } 1618 } 1619 1620 /* If there are no matches, pass all the reps */ 1621 if ([breps count] == 0) 1622 { 1623 [breps setArray: reps]; 1624 } 1625 1626 return breps; 1627} 1628 1629- (NSMutableArray *) _representationsWithCachedImages: (BOOL)flag 1630{ 1631 NSUInteger count; 1632 1633 if (_flags.syncLoad) 1634 { 1635 /* Make sure any images that were added with _useFromFile: are loaded 1636 in and added to the representation list. */ 1637 [self _loadFromFile: _fileName]; 1638 _flags.syncLoad = NO; 1639 } 1640 1641 count = [_reps count]; 1642 if (count == 0) 1643 { 1644 return [NSMutableArray array]; 1645 } 1646 else 1647 { 1648 id repList[count]; 1649 NSUInteger i, j; 1650 1651 [_reps getObjects: repList]; 1652 j = 0; 1653 for (i = 0; i < count; i++) 1654 { 1655 if (flag || ((GSRepData*)repList[i])->original == nil) 1656 { 1657 repList[j] = ((GSRepData*)repList[i])->rep; 1658 j++; 1659 } 1660 } 1661 return [NSMutableArray arrayWithObjects: repList count: j]; 1662 } 1663} 1664 1665- (NSArray *) _bestRepresentationsForDevice: (NSDictionary*)deviceDescription 1666{ 1667 NSMutableArray *reps = [self _representationsWithCachedImages: NO]; 1668 1669 if (deviceDescription == nil) 1670 { 1671 if ([GSCurrentContext() isDrawingToScreen] == YES) 1672 { 1673 // Take the device description from the current context. 1674 deviceDescription = [[[GSCurrentContext() attributes] objectForKey: 1675 NSGraphicsContextDestinationAttributeName] 1676 deviceDescription]; 1677 } 1678 else if ([NSPrintOperation currentOperation]) 1679 { 1680 /* FIXME: We could try to use the current printer, 1681 but there are many cases where might 1682 not be printing (EPS, PDF, etc) to a specific device */ 1683 } 1684 } 1685 1686 if (_flags.colorMatchPreferred == YES) 1687 { 1688 reps = [self _bestRep: reps withColorMatch: deviceDescription]; 1689 reps = [self _bestRep: reps withResolutionMatch: deviceDescription]; 1690 } 1691 else 1692 { 1693 reps = [self _bestRep: reps withResolutionMatch: deviceDescription]; 1694 reps = [self _bestRep: reps withColorMatch: deviceDescription]; 1695 } 1696 reps = [self _bestRep: reps withBpsMatch: deviceDescription]; 1697 1698 return reps; 1699} 1700 1701- (NSImageRep *) bestRepresentationForDevice: (NSDictionary*)deviceDescription 1702{ 1703 NSArray *reps = [self _bestRepresentationsForDevice: deviceDescription]; 1704 1705 /* If we have more than one match check for a representation whose size 1706 * matches the image size exactly. Otherwise, arbitrarily choose the first 1707 * representation. */ 1708 if ([reps count] > 1) 1709 { 1710 NSImageRep *rep; 1711 NSEnumerator *enumerator = [reps objectEnumerator]; 1712 1713 while ((rep = [enumerator nextObject]) != nil) 1714 { 1715 if (NSEqualSizes(_size, [rep size]) == YES) 1716 { 1717 return rep; 1718 } 1719 } 1720 } 1721 1722 1723 if ([reps count] > 0) 1724 { 1725 return [reps objectAtIndex: 0]; 1726 } 1727 else 1728 { 1729 return nil; 1730 } 1731} 1732 1733- (NSImageRep *) bestRepresentationForRect: (NSRect)rect 1734 context: (NSGraphicsContext *)context 1735 hints: (NSDictionary *)deviceDescription 1736{ 1737 NSArray *reps = [self _bestRepresentationsForDevice: deviceDescription]; 1738 const NSSize desiredSize = rect.size; 1739 NSImageRep *bestRep = nil; 1740 1741 // Pick the smallest rep that is greater than or equal to the 1742 // desired size. 1743 1744 { 1745 NSSize bestSize = NSMakeSize(CGFLOAT_MAX, CGFLOAT_MAX); 1746 NSImageRep *rep; 1747 NSEnumerator *enumerator = [reps objectEnumerator]; 1748 while ((rep = [enumerator nextObject]) != nil) 1749 { 1750 const NSSize repSize = [rep size]; 1751 if ((repSize.width >= desiredSize.width) 1752 && (repSize.height >= desiredSize.height) 1753 && (repSize.width < bestSize.width) 1754 && (repSize.height < bestSize.height)) 1755 { 1756 bestSize = repSize; 1757 bestRep = rep; 1758 } 1759 } 1760 } 1761 1762 if (bestRep == nil) 1763 { 1764 bestRep = [reps lastObject]; 1765 } 1766 1767 return bestRep; 1768} 1769 1770- (NSArray *) representations 1771{ 1772 return [self _representationsWithCachedImages: YES]; 1773} 1774 1775- (void) setDelegate: anObject 1776{ 1777 _delegate = anObject; 1778} 1779 1780- (id) delegate 1781{ 1782 return _delegate; 1783} 1784 1785// Producing TIFF Data for the Image 1786- (NSData *) TIFFRepresentation 1787{ 1788 NSArray *reps; 1789 NSData *data; 1790 1791 /* As a result of using bitmap representations, 1792 * new drawing wont show on the tiff data. 1793 */ 1794 reps = [self _representationsWithCachedImages: NO]; 1795 data = [bitmapClass TIFFRepresentationOfImageRepsInArray: reps]; 1796 1797 if (!data) 1798 { 1799 NSBitmapImageRep *rep; 1800 NSSize size = [self size]; 1801 1802 // If there isn't a bitmap representation to output, create one and store it. 1803 [self lockFocus]; 1804 rep = [[NSBitmapImageRep alloc] initWithFocusedViewRect: 1805 NSMakeRect(0.0, 0.0, size.width, size.height)]; 1806 [self unlockFocus]; 1807 if (nil != rep) 1808 { 1809 [self addRepresentation: rep]; 1810 data = [rep TIFFRepresentation]; 1811 RELEASE(rep); 1812 } 1813 } 1814 1815 return data; 1816} 1817 1818- (NSData *) TIFFRepresentationUsingCompression: (NSTIFFCompression)comp 1819 factor: (float)aFloat 1820{ 1821 NSArray *reps; 1822 NSData *data; 1823 1824 /* As a result of using bitmap representations, 1825 * new drawing wont show on the tiff data. 1826 */ 1827 reps = [self _representationsWithCachedImages: NO]; 1828 data = [bitmapClass TIFFRepresentationOfImageRepsInArray: reps 1829 usingCompression: comp 1830 factor: aFloat]; 1831 1832 if (!data) 1833 { 1834 NSBitmapImageRep *rep; 1835 NSSize size = [self size]; 1836 1837 /* If there isn't a bitmap representation to output, 1838 * create one and store it. 1839 */ 1840 [self lockFocus]; 1841 rep = [[NSBitmapImageRep alloc] initWithFocusedViewRect: 1842 NSMakeRect(0.0, 0.0, size.width, size.height)]; 1843 [self unlockFocus]; 1844 if (nil != rep) 1845 { 1846 [self addRepresentation: rep]; 1847 data = [rep TIFFRepresentationUsingCompression: comp factor: aFloat]; 1848 RELEASE(rep); 1849 } 1850 } 1851 1852 return data; 1853} 1854 1855// NSCoding 1856- (void) encodeWithCoder: (NSCoder*)coder 1857{ 1858 BOOL flag; 1859 1860 if ([coder allowsKeyedCoding]) 1861 { 1862 int flags = 0; 1863 1864 if (_flags.archiveByName == NO) 1865 { 1866 NSMutableArray *container = [NSMutableArray array]; 1867 NSMutableArray *reps = [NSMutableArray array]; 1868 NSEnumerator *en = [_reps objectEnumerator]; 1869 GSRepData *rd = nil; 1870 1871 if ([_reps count] > 0) 1872 { 1873 [reps addObject: [NSNumber numberWithInt: 0]]; 1874 while ((rd = [en nextObject]) != nil) 1875 { 1876 [reps addObject: rd->rep]; 1877 } 1878 1879 // add the reps to the container... 1880 [container addObject: reps]; 1881 [coder encodeObject: container forKey: @"NSReps"]; 1882 } 1883 } 1884 else 1885 { 1886 [coder encodeObject: _name forKey: @"NSName"]; 1887 } 1888 1889 // encode the rest... 1890 if (_color != nil) 1891 { 1892 [coder encodeObject: _color forKey: @"NSColor"]; 1893 } 1894 flags |= [self scalesWhenResized] ? 0x8000000 : 0; 1895 flags |= _flags.sizeWasExplicitlySet ? 0x2000000 : 0; 1896 flags |= [self usesEPSOnResolutionMismatch] ? 0x0200000 : 0; 1897 flags |= [self prefersColorMatch] ? 0x0100000 : 0; 1898 flags |= [self matchesOnMultipleResolution] ? 0x0080000 : 0; 1899 flags |= [self isFlipped] ? 0x0008000 : 0; 1900 flags |= [self cacheMode] << 11; 1901 [coder encodeInt: flags forKey: @"NSImageFlags"]; 1902 if (_flags.sizeWasExplicitlySet) 1903 { 1904 [coder encodeSize: _size forKey: @"NSSize"]; 1905 } 1906 } 1907 else 1908 { 1909 flag = _flags.archiveByName; 1910 [coder encodeValueOfObjCType: @encode(BOOL) at: &flag]; 1911 if (flag == YES) 1912 { 1913 /* 1914 * System image - just encode the name. 1915 */ 1916 [coder encodeValueOfObjCType: @encode(id) at: &_name]; 1917 } 1918 else 1919 { 1920 NSMutableArray *a; 1921 NSEnumerator *e; 1922 NSImageRep *r; 1923 1924 /* 1925 * Normal image - encode the ivars 1926 */ 1927 [coder encodeValueOfObjCType: @encode(NSSize) at: &_size]; 1928 [coder encodeValueOfObjCType: @encode(id) at: &_color]; 1929 flag = _flags.scalable; 1930 [coder encodeValueOfObjCType: @encode(BOOL) at: &flag]; 1931 flag = _flags.dataRetained; 1932 [coder encodeValueOfObjCType: @encode(BOOL) at: &flag]; 1933 flag = _flags.flipDraw; 1934 [coder encodeValueOfObjCType: @encode(BOOL) at: &flag]; 1935 flag = _flags.sizeWasExplicitlySet; 1936 [coder encodeValueOfObjCType: @encode(BOOL) at: &flag]; 1937 flag = _flags.useEPSOnResolutionMismatch; 1938 [coder encodeValueOfObjCType: @encode(BOOL) at: &flag]; 1939 flag = _flags.colorMatchPreferred; 1940 [coder encodeValueOfObjCType: @encode(BOOL) at: &flag]; 1941 flag = _flags.multipleResolutionMatching; 1942 [coder encodeValueOfObjCType: @encode(BOOL) at: &flag]; 1943 flag = _flags.cacheSeparately; 1944 [coder encodeValueOfObjCType: @encode(BOOL) at: &flag]; 1945 flag = _flags.unboundedCacheDepth; 1946 [coder encodeValueOfObjCType: @encode(BOOL) at: &flag]; 1947 1948 // FIXME: The documentation says to archive only the file name, 1949 // if not data retained! 1950 /* 1951 * Now encode an array of all the image reps (excluding cache) 1952 */ 1953 a = [NSMutableArray arrayWithCapacity: 2]; 1954 e = [[self representations] objectEnumerator]; 1955 while ((r = [e nextObject]) != nil) 1956 { 1957 if ([r isKindOfClass: cachedClass] == NO) 1958 { 1959 [a addObject: r]; 1960 } 1961 } 1962 [coder encodeValueOfObjCType: @encode(id) at: &a]; 1963 } 1964 } 1965} 1966 1967- (id) initWithCoder: (NSCoder*)coder 1968{ 1969 BOOL flag; 1970 1971 _reps = [[NSMutableArray alloc] initWithCapacity: 2]; 1972 if ([coder allowsKeyedCoding]) 1973 { 1974 if ([coder containsValueForKey: @"NSName"]) 1975 { 1976 NSImage *replacementImage; 1977 NSString *imageName; 1978 1979 imageName = [coder decodeObjectForKey: @"NSName"]; 1980 replacementImage = [NSImage imageNamed: imageName]; 1981 if (replacementImage) 1982 { 1983 RELEASE(self); 1984 return RETAIN(replacementImage); 1985 } 1986 1987 [self setName: imageName]; 1988 self->_flags.archiveByName = YES; 1989 } 1990 if ([coder containsValueForKey: @"NSColor"]) 1991 { 1992 [self setBackgroundColor: [coder decodeObjectForKey: @"NSColor"]]; 1993 } 1994 if ([coder containsValueForKey: @"NSImageFlags"]) 1995 { 1996 int flags = [coder decodeIntForKey: @"NSImageFlags"]; 1997 1998 [self setScalesWhenResized: ((flags & 0x8000000) != 0)]; 1999 // _flags.sizeWasExplicitlySet = ((flags & 0x2000000) != 0); 2000 [self setUsesEPSOnResolutionMismatch: ((flags & 0x0200000) != 0)]; 2001 [self setPrefersColorMatch: ((flags & 0x0100000) != 0)]; 2002 [self setMatchesOnMultipleResolution: ((flags & 0x0080000) != 0)]; 2003 [self setFlipped: ((flags & 0x0008000) != 0)]; 2004 // ALIASED ((flags & 0x0004000) != 0) 2005 [self setCacheMode: ((flags & 0x0001800) >> 11)]; 2006 } 2007 if ([coder containsValueForKey: @"NSReps"]) 2008 { 2009 NSArray *reps; 2010 NSUInteger i; 2011 2012 /* FIXME: NSReps is in a strange format. It is a mutable array 2013 * with one element which is an array with a first element 0 2014 * and than the image rep. 2015 */ 2016 reps = [coder decodeObjectForKey: @"NSReps"]; 2017 reps = [reps objectAtIndex: 0]; 2018 for (i = 1; i < [reps count]; i++) 2019 { 2020 id rep = [reps objectAtIndex: i]; 2021 if ([rep isKindOfClass: [NSImageRep class]]) 2022 { 2023 [self addRepresentation: rep]; 2024 } 2025 else 2026 { 2027 if ([rep isKindOfClass: [NSURL class]]) 2028 { 2029 NSURL *tmp = (NSURL*)rep; 2030 rep = [NSImageRep imageRepWithContentsOfURL: rep]; 2031 2032 /* If we are unable to resolved the URL, 2033 * try to get it from the resources folder. 2034 */ 2035 if (rep == nil) 2036 { 2037 NSString *fileName; 2038 NSString *path; 2039 2040 fileName = [[tmp absoluteString] lastPathComponent]; 2041 path = [[NSBundle mainBundle] 2042 pathForImageResource: fileName]; 2043 rep = [NSImageRep imageRepWithContentsOfFile: path]; 2044 } 2045 2046 // If the representation was found, add it... 2047 if (rep != nil) 2048 { 2049 [self addRepresentation: rep]; 2050 } 2051 } 2052 } 2053 } 2054 } 2055 if ([coder containsValueForKey: @"NSSize"]) 2056 { 2057 [self setSize: [coder decodeSizeForKey: @"NSSize"]]; 2058 } 2059 } 2060 else 2061 { 2062 [coder decodeValueOfObjCType: @encode(BOOL) at: &flag]; 2063 if (flag == YES) 2064 { 2065 NSImage *replacementImage; 2066 NSString *theName = [coder decodeObject]; 2067 2068 replacementImage = [NSImage imageNamed: theName]; 2069 if (replacementImage) 2070 { 2071 RELEASE(self); 2072 self = RETAIN(replacementImage); 2073 } 2074 else 2075 { 2076 [self setName: theName]; 2077 self->_flags.archiveByName = YES; 2078 } 2079 } 2080 else 2081 { 2082 NSArray *a; 2083 2084 [coder decodeValueOfObjCType: @encode(NSSize) at: &_size]; 2085 [coder decodeValueOfObjCType: @encode(id) at: &_color]; 2086 [coder decodeValueOfObjCType: @encode(BOOL) at: &flag]; 2087 _flags.scalable = flag; 2088 [coder decodeValueOfObjCType: @encode(BOOL) at: &flag]; 2089 _flags.dataRetained = flag; 2090 [coder decodeValueOfObjCType: @encode(BOOL) at: &flag]; 2091 _flags.flipDraw = flag; 2092 [coder decodeValueOfObjCType: @encode(BOOL) at: &flag]; 2093 _flags.sizeWasExplicitlySet = flag; 2094 [coder decodeValueOfObjCType: @encode(BOOL) at: &flag]; 2095 _flags.useEPSOnResolutionMismatch = flag; 2096 [coder decodeValueOfObjCType: @encode(BOOL) at: &flag]; 2097 _flags.colorMatchPreferred = flag; 2098 [coder decodeValueOfObjCType: @encode(BOOL) at: &flag]; 2099 _flags.multipleResolutionMatching = flag; 2100 [coder decodeValueOfObjCType: @encode(BOOL) at: &flag]; 2101 _flags.cacheSeparately = flag; 2102 [coder decodeValueOfObjCType: @encode(BOOL) at: &flag]; 2103 _flags.unboundedCacheDepth = flag; 2104 2105 /* 2106 * get the image reps and add them. 2107 */ 2108 a = [coder decodeObject]; 2109 [self addRepresentations: a]; 2110 } 2111 } 2112 return self; 2113} 2114 2115+ (BOOL) canInitWithPasteboard: (NSPasteboard *)pasteboard 2116{ 2117 int i, count; 2118 NSArray* array = [NSImageRep registeredImageRepClasses]; 2119 2120 count = [array count]; 2121 for (i = 0; i < count; i++) 2122 if ([[array objectAtIndex: i] canInitWithPasteboard: pasteboard]) 2123 return YES; 2124 2125 return NO; 2126} 2127 2128+ (NSArray *) imageUnfilteredFileTypes 2129{ 2130 if (nil == imageUnfilteredFileTypes) 2131 { 2132 ASSIGN(imageUnfilteredFileTypes, 2133 iterate_reps_for_types([NSImageRep registeredImageRepClasses], 2134 @selector(imageUnfilteredFileTypes))); 2135 } 2136 return imageUnfilteredFileTypes; 2137} 2138 2139+ (NSArray *) imageFileTypes 2140{ 2141 if (nil == imageFileTypes) 2142 { 2143 ASSIGN(imageFileTypes, 2144 iterate_reps_for_types([NSImageRep registeredImageRepClasses], 2145 @selector(imageFileTypes))); 2146 } 2147 return imageFileTypes; 2148} 2149 2150+ (NSArray *) imageUnfilteredPasteboardTypes 2151{ 2152 if (nil == imageUnfilteredPasteboardTypes) 2153 { 2154 ASSIGN(imageUnfilteredPasteboardTypes, 2155 iterate_reps_for_types([NSImageRep registeredImageRepClasses], 2156 @selector(imageUnfilteredPasteboardTypes))); 2157 } 2158 return imageUnfilteredPasteboardTypes; 2159} 2160 2161+ (NSArray *) imagePasteboardTypes 2162{ 2163 if (nil == imagePasteboardTypes) 2164 { 2165 ASSIGN(imagePasteboardTypes, 2166 iterate_reps_for_types([NSImageRep registeredImageRepClasses], 2167 @selector(imagePasteboardTypes))); 2168 } 2169 return imagePasteboardTypes; 2170} 2171 2172@end 2173 2174/* For every image rep, call the specified method to obtain an 2175 array of objects. Add these together, with duplicates 2176 weeded out. Used by imageUnfilteredPasteboardTypes, 2177 imageUnfilteredFileTypes, etc. */ 2178static NSArray * 2179iterate_reps_for_types(NSArray* imageReps, SEL method) 2180{ 2181 NSImageRep *rep; 2182 NSEnumerator *e; 2183 NSMutableArray *types; 2184 2185 types = [NSMutableArray arrayWithCapacity: 2]; 2186 2187 // Iterate through all the image reps 2188 e = [imageReps objectEnumerator]; 2189 rep = [e nextObject]; 2190 while (rep) 2191 { 2192 id e1; 2193 id obj; 2194 NSArray* pb_list; 2195 2196 // Have the image rep perform the operation 2197 pb_list = [rep performSelector: method]; 2198 2199 // Iterate through the returned array 2200 // and add elements to types list, duplicates weeded. 2201 e1 = [pb_list objectEnumerator]; 2202 obj = [e1 nextObject]; 2203 while (obj) 2204 { 2205 if ([types indexOfObject: obj] == NSNotFound) 2206 [types addObject: obj]; 2207 obj = [e1 nextObject]; 2208 } 2209 2210 rep = [e nextObject]; 2211 } 2212 2213 return (NSArray *)types; 2214} 2215 2216@implementation NSImage (Private) 2217 2218+ (void) _clearFileTypeCaches: (NSNotification*)notif 2219{ 2220 DESTROY(imageUnfilteredFileTypes); 2221 DESTROY(imageFileTypes); 2222 DESTROY(imageUnfilteredPasteboardTypes); 2223 DESTROY(imagePasteboardTypes); 2224} 2225 2226/** 2227 * For all NSImage instances cached in nameDict, recompute the 2228 * path using +_pathForImageNamed: and if it has changed, 2229 * reload the image contents using the new path. 2230 */ 2231+ (void) _reloadCachedImages 2232{ 2233 NSString *name; 2234 NSEnumerator *e = [nameDict keyEnumerator]; 2235 2236 [imageLock lock]; 2237 while ((name = [e nextObject]) != nil) 2238 { 2239 NSImage *image = [nameDict objectForKey: name]; 2240 NSString *path = [[NSBundle mainBundle] pathForImageResource: name]; 2241 2242 //NSLog(@"Loaded image %@ from %@", name, path); 2243 2244 if (path != nil && ![path isEqual: image->_fileName]) 2245 { 2246 /* Reset the existing image to use the contents of 2247 * the specified file. 2248 */ 2249 [image _resetAndUseFromFile: path]; 2250 } 2251 } 2252 [imageLock unlock]; 2253} 2254 2255 2256+ (NSString *) _resourceNameForImageNamed: (NSString *)aName 2257 type: (NSString **)aType 2258{ 2259 NSString *name = aName; 2260 NSString *ext = [aName pathExtension]; 2261 2262 if (ext != nil && [ext length] == 0) 2263 { 2264 ext = nil; 2265 } 2266 2267 /* Check if extension is one of the image types */ 2268 if (ext != nil && [[self imageFileTypes] indexOfObject: ext] != NSNotFound) 2269 { 2270 /* Extension is one of the image types, so remove from the name */ 2271 name = [aName stringByDeletingPathExtension]; 2272 } 2273 else 2274 { 2275 /* Otherwise extension is not an image type, so leave it alone */ 2276 ext = nil; 2277 } 2278 2279 *aType = ext; 2280 return name; 2281} 2282 2283- (BOOL) _loadFromData: (NSData *)data 2284{ 2285 BOOL ok; 2286 Class rep; 2287 2288 ok = NO; 2289 rep = [NSImageRep imageRepClassForData: data]; 2290 if (rep && [rep respondsToSelector: @selector(imageRepsWithData:)]) 2291 { 2292 NSArray* array; 2293 2294 array = [rep imageRepsWithData: data]; 2295 if (array && ([array count] > 0)) 2296 ok = YES; 2297 [self addRepresentations: array]; 2298 } 2299 else if (rep) 2300 { 2301 NSImageRep* image; 2302 2303 image = [rep imageRepWithData: data]; 2304 if (image) 2305 ok = YES; 2306 [self addRepresentation: image]; 2307 } 2308 return ok; 2309} 2310 2311- (BOOL) _loadFromFile: (NSString *)fileName 2312{ 2313 NSArray *array; 2314 2315 array = [NSImageRep imageRepsWithContentsOfFile: fileName]; 2316 if (array) 2317 [self addRepresentations: array]; 2318 2319 return (array && ([array count] > 0)) ? YES : NO; 2320} 2321 2322- (BOOL) _useFromFile: (NSString *)fileName 2323{ 2324 NSArray *array; 2325 NSString *ext; 2326 NSFileManager *manager = [NSFileManager defaultManager]; 2327 2328 if ([manager fileExistsAtPath: fileName] == NO) 2329 { 2330 return NO; 2331 } 2332 2333 ext = [[fileName pathExtension] lowercaseString]; 2334 if (!ext) 2335 return NO; 2336 array = [object_getClass(self) imageFileTypes]; 2337 if ([array indexOfObject: ext] == NSNotFound) 2338 return NO; 2339 2340 ASSIGN(_fileName, fileName); 2341 _flags.syncLoad = YES; 2342 return YES; 2343} 2344 2345- (BOOL) _resetAndUseFromFile: (NSString *)fileName 2346{ 2347 [_reps removeAllObjects]; 2348 2349 if (!_flags.sizeWasExplicitlySet) 2350 { 2351 _size = NSZeroSize; 2352 } 2353 return [self _useFromFile: fileName]; 2354} 2355 2356// Cache the bestRepresentation. If the bestRepresentation is not itself 2357// a cache and no cache exists, create one and draw the representation in it 2358// If a cache exists, but is not valid, redraw the cache from the original 2359// image (if there is one). 2360- (NSCachedImageRep *) _doImageCache: (NSImageRep *)rep 2361{ 2362 GSRepData *repd; 2363 NSCachedImageRep *cache; 2364 2365 repd = [self _cacheForRep: rep]; 2366 if (repd == nil) 2367 return nil; 2368 2369 cache = (NSCachedImageRep*)(repd->rep); 2370 if ([cache isKindOfClass: cachedClass] == NO) 2371 return nil; 2372 2373 NSDebugLLog(@"NSImage", @"Cached image rep is %p", cache); 2374 /* 2375 * if the cache is not valid, it's background color will not exist 2376 * and we must draw the background then render from the original 2377 * image rep into the cache. 2378 */ 2379 if (repd->bg == nil) 2380 { 2381 [self lockFocusOnRepresentation: cache]; 2382 [self unlockFocus]; 2383 2384 NSDebugLLog(@"NSImage", @"Rendered rep %p on background %@", 2385 cache, repd->bg); 2386 } 2387 2388 return cache; 2389} 2390 2391- (GSRepData*) _cacheForRep: (NSImageRep*)rep 2392{ 2393 if ([rep isKindOfClass: cachedClass] == YES) 2394 { 2395 return repd_for_rep(_reps, rep); 2396 } 2397 else 2398 { 2399 /* 2400 * If this is not a cached image rep - try to find the cache rep 2401 * for this image rep. If none is found create a cache to be used to 2402 * render the image rep into, and switch to the cached rep. 2403 */ 2404 NSUInteger count = [_reps count]; 2405 2406 if (count > 0) 2407 { 2408 GSRepData *invalidCache = nil; 2409 GSRepData *partialCache = nil; 2410 GSRepData *reps[count]; 2411 NSUInteger partialCount = 0; 2412 NSUInteger i; 2413 BOOL opaque = [rep isOpaque]; 2414 2415 [_reps getObjects: reps]; 2416 2417 /* 2418 * Search the cached image reps for any whose original is our 2419 * 'best' image rep. See if we can notice any invalidated 2420 * cache as we go - if we don't find a valid cache, we want to 2421 * re-use an invalidated one rather than creating a new one. 2422 * NB. If the image rep is opaque, then any cached rep is valid 2423 * irrespective of the background color it was drawn with. 2424 */ 2425 for (i = 0; i < count; i++) 2426 { 2427 GSRepData *repd = reps[i]; 2428 2429 if (repd->original == rep && repd->rep != nil) 2430 { 2431 if (repd->bg == nil) 2432 { 2433 NSDebugLLog(@"NSImage", @"Invalid %@ ... %@ %@", 2434 repd->bg, _color, repd->rep); 2435 invalidCache = repd; 2436 } 2437 else if (opaque == YES || [repd->bg isEqual: _color] == YES) 2438 { 2439 NSDebugLLog(@"NSImage", @"Exact %@ ... %@ %@", 2440 repd->bg, _color, repd->rep); 2441 return repd; 2442 } 2443 else 2444 { 2445 NSDebugLLog(@"NSImage", @"Partial %@ ... %@ %@", 2446 repd->bg, _color, repd->rep); 2447 partialCache = repd; 2448 partialCount++; 2449 } 2450 } 2451 } 2452 2453 if (invalidCache != nil) 2454 { 2455 /* 2456 * If there is an unused cache - use it rather than 2457 * re-using this one, since we might get a request 2458 * to draw with this color again. 2459 */ 2460 return invalidCache; 2461 } 2462 else if (partialCache != nil && partialCount > 2) 2463 { 2464 /* 2465 * Only re-use partially correct caches if there are already 2466 * a few partial matches - otherwise we fall default to 2467 * creating a new cache. 2468 */ 2469 if (NSImageForceCaching == NO && opaque == NO) 2470 { 2471 DESTROY(partialCache->bg); 2472 } 2473 return partialCache; 2474 } 2475 } 2476 2477 // We end here, when no representation are there or no match is found. 2478 { 2479 NSImageRep *cacheRep = nil; 2480 GSRepData *repd; 2481 NSSize imageSize = [self size]; 2482 NSSize repSize; 2483 NSInteger pixelsWide, pixelsHigh; 2484 2485 if (rep != nil) 2486 { 2487 repSize = [rep size]; 2488 2489 if (repSize.width <= 0 || repSize.height <= 0) 2490 repSize = imageSize; 2491 2492 pixelsWide = [rep pixelsWide]; 2493 pixelsHigh = [rep pixelsHigh]; 2494 2495 if (pixelsWide == NSImageRepMatchesDevice || 2496 pixelsHigh == NSImageRepMatchesDevice) 2497 { 2498 /* FIXME: Since the cached rep must be a bitmap, 2499 * we must rasterize vector reps at a particular DPI. 2500 * Here we hardcode 72, but we should choose the DPI 2501 * more intelligently. 2502 */ 2503 pixelsWide = repSize.width; 2504 pixelsHigh = repSize.height; 2505 } 2506 } 2507 else // e.g. when there are no representations at all 2508 { 2509 repSize = imageSize; 2510 /* FIXME: assumes 72 DPI. Also truncates, 2511 * not sure if that is a problem. 2512 */ 2513 pixelsWide = imageSize.width; 2514 pixelsHigh = imageSize.height; 2515 } 2516 2517 if (repSize.width <= 0 || repSize.height <= 0 || 2518 pixelsWide <= 0 || pixelsHigh <= 0) 2519 return nil; 2520 2521 // Create a new cached image rep without any contents. 2522 cacheRep = [[cachedClass alloc] 2523 initWithSize: repSize 2524 pixelsWide: pixelsWide 2525 pixelsHigh: pixelsHigh 2526 depth: [[NSScreen mainScreen] depth] 2527 separate: _flags.cacheSeparately 2528 alpha: [rep hasAlpha]]; 2529 if (cacheRep == nil) 2530 { 2531 return nil; 2532 } 2533 2534 repd = [GSRepData new]; 2535 repd->rep = cacheRep; 2536 repd->original = rep; // may be nil! 2537 [_reps addObject: repd]; 2538 RELEASE(repd); /* Retained in _reps array. */ 2539 2540 return repd; 2541 } 2542 } 2543} 2544 2545@end 2546