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