Merge pull request #10 from gunyarakun/fix-invalid-return
[cocotron.git] / AppKit / NSImage.m
blobc9017045a9bd3a12fd046d603180b54ecc27adf6
1 /* Copyright (c) 2006-2007 Christopher J. W. Lloyd <cjwl@objc.net>
3 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
5 The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
7 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
9 #import <AppKit/NSImage.h>
10 #import <AppKit/NSImageRep.h>
11 #import <AppKit/NSBitmapImageRep.h>
12 #import <AppKit/NSCachedImageRep.h>
13 #import <AppKit/NSPDFImageRep.h>
14 #import <AppKit/NSEPSImageRep.h>
15 #import <AppKit/NSCustomImageRep.h>
16 #import <AppKit/NSPasteboard.h>
17 #import <AppKit/NSColor.h>
18 #import <AppKit/NSGraphicsContextFunctions.h>
19 #import <Foundation/NSKeyedArchiver.h>
20 #import <AppKit/NSRaise.h>
22 // Private class used so the context knows the flipped status of a locked image
23 // 10.4 does something like that - probably for more than just getting the flippiness - 10.6 uses some special NSSnapshotBitmapGraphicsContext
24 @interface NSImageCacheView : NSView {
25         BOOL _flipped;
27 - (id)initWithFlipped:(BOOL)flipped;
28 @end
29 @implementation NSImageCacheView
30 - (id)initWithFlipped:(BOOL)flipped
32         if ((self = [super init])) {
33                 _flipped = flipped;
34         }
35         return self;
37 -(BOOL)isFlipped
39         return _flipped;
41 @end
43 @implementation NSImage
45 +(NSArray *)imageFileTypes {
46    return [self imageUnfilteredFileTypes];
49 +(NSArray *)imageUnfilteredFileTypes {
50    NSMutableArray *result=[NSMutableArray array];
51    NSArray        *allClasses=[NSImageRep registeredImageRepClasses];
52    int             i,count=[allClasses count];
53    
54    for(i=0;i<count;i++)
55     [result addObjectsFromArray:[[allClasses objectAtIndex:i] imageUnfilteredFileTypes]];
57    return result;
60 +(NSArray *)imagePasteboardTypes {
61    return [self imageUnfilteredPasteboardTypes];
64 +(NSArray *)imageUnfilteredPasteboardTypes{
65    NSMutableArray *result=[NSMutableArray array];
66    NSArray        *allClasses=[NSImageRep registeredImageRepClasses];
67    int             i,count=[allClasses count];
68    
69    for(i=0;i<count;i++)
70     [result addObjectsFromArray:[[allClasses objectAtIndex:i] imageUnfilteredPasteboardTypes]];
72    return result;
75 +(BOOL)canInitWithPasteboard:(NSPasteboard *)pasteboard {
76    NSString *available=[pasteboard availableTypeFromArray:[self imageUnfilteredPasteboardTypes]];
77    
78    return (available!=nil)?YES:NO;
81 +(NSArray *)_checkBundles {
82    return [NSArray arrayWithObjects:
83            [NSBundle mainBundle], // Check the main bundle first according to the doc
84            [NSBundle bundleForClass:self],
85     nil];
88 +(NSMutableDictionary *)allImages {
89    NSMutableDictionary *result=[[[NSThread currentThread] threadDictionary] objectForKey:@"__allImages"];
91    if(result==nil){
92     result=[NSMutableDictionary dictionary];
93     [[[NSThread currentThread] threadDictionary] setObject:result forKey:@"__allImages"];
94    }
96    return result;
99 +imageNamed:(NSString *)name {
100    NSImage *image=[[self allImages] objectForKey:name];
101    if(image==nil){
102     NSArray *bundles=[self _checkBundles];
103     int      i,count=[bundles count];
105     for(i=0;i<count;i++){
106      NSBundle *bundle=[bundles objectAtIndex:i];
107      NSString *path=[bundle pathForImageResource:name];
109      if(path!=nil){
110          image=[[[NSImage alloc] initWithContentsOfFile:path] autorelease];
111          [image setName:name];
112          if (image) {
113              break;
114          }
115      }
116     }
117    }
119   // Cocoa AppKit always returns the same shared cached image
120    return image;
123 -(void)encodeWithCoder:(NSCoder *)coder {
124    NSUnimplementedMethod();
127 -initWithCoder:(NSCoder *)coder {
128    if([coder allowsKeyedCoding]){
129     NSKeyedUnarchiver *keyed=(NSKeyedUnarchiver *)coder;
130     NSUInteger            length;
131     const unsigned char  *tiff=[keyed decodeBytesForKey:@"NSTIFFRepresentation" returnedLength:&length];
132     NSBitmapImageRep     *rep;
133     
134     if(tiff==NULL){
135         [self release];
136      return nil;
137     }
138    
139     rep=[NSBitmapImageRep imageRepWithData:[NSData dataWithBytes:tiff length:length]];
140     if(rep==nil){
141         [self release];
142      return nil;
143     }
144     
145     _name=nil;
146     _size=NSMakeSize(0,0);
147     _representations=[NSMutableArray new];
149     [_representations addObject:rep];
150    }
151    else {
152     [NSException raise:NSInvalidArgumentException format:@"-[%@ %s] is not implemented for coder %@",isa,sel_getName(_cmd),coder];
153    }
154    return nil;
157 -initWithSize:(NSSize)size {
158    _name=nil;
159    _size=size;
160    _representations=[NSMutableArray new];
161    return self;
164 -init {
165    return [self initWithSize:NSMakeSize(0,0)];
168 -initWithData:(NSData *)data {
169         Class repClass = [NSImageRep imageRepClassForData:data];
170     NSArray *reps=nil;
171     
172     if([repClass respondsToSelector:@selector(imageRepsWithData:)])
173      reps=[repClass performSelector:@selector(imageRepsWithData:) withObject:data];
174     else if([repClass respondsToSelector:@selector(imageRepWithData:)]){
175      NSImageRep *rep=[repClass performSelector:@selector(imageRepWithData:) withObject:data];
176      
177      if(rep!=nil)
178       reps=[NSArray arrayWithObject:rep];
179     }
181         if([reps count]==0){
182        [self release];
183                 return nil;
184         }
185         
186         _name=nil;
187     _size=NSMakeSize(0,0);
188         _representations=[NSMutableArray new];
189         
190         [_representations addObjectsFromArray:reps];
191         
192         return self;
195 -initWithContentsOfFile:(NSString *)path {
196    NSArray *reps=[NSImageRep imageRepsWithContentsOfFile:path];
197     
198    if([reps count]==0){
199        [self release];
200     return nil;
201    }
203    _name=nil;
204    _size=NSMakeSize(0,0);
205    _representations=[NSMutableArray new];
207    [_representations addObjectsFromArray:reps];
209    return self;
212 -initWithContentsOfURL:(NSURL *)url {
213    NSData *data=[NSData dataWithContentsOfURL:url];
214    
215    if(data==nil){
216     [self release];
217     return nil;
220    return [self initWithData:data];
223 -initWithCGImage:(CGImageRef)cgImage size:(NSSize)size;
225     if (self = [self initWithSize:size]) {
226         NSBitmapImageRep *rep = [[[NSBitmapImageRep alloc] initWithCGImage:cgImage] autorelease];
227         [_representations addObject:rep];
228     }
229     return self;
232 -initWithPasteboard:(NSPasteboard *)pasteboard {
234         NSString *available=[pasteboard availableTypeFromArray:[[self class] imageUnfilteredPasteboardTypes]];
235         NSData *data = [pasteboard dataForType:available];
236         if (data == nil) {
237                 [self release];
238                 return nil;
239         }
240         return [self initWithData:data];
243 -initByReferencingFile:(NSString *)path {
244    return [self initWithContentsOfFile:path];
247 -initByReferencingURL:(NSURL *)url {
248         // Better than nothing
249         return [self initWithContentsOfURL:url];
252 -(void)dealloc {
253    [_name release];
254    [_backgroundColor release];
255    [_representations release];
256    [super dealloc];
259 -copyWithZone:(NSZone *)zone {
260    NSImage *result=NSCopyObject(self,0,zone);
261    
262    result->_name=[_name copy];
263    result->_backgroundColor=[_backgroundColor copy];
264    result->_representations=[_representations mutableCopy];
265    
266    return result;
269 -(NSString *)name {
270    return _name;
273 -(NSSize)size {
274    if(_size.width==0.0 && _size.height==0.0){
275     int i,count=[_representations count];
276     NSSize  largestSize=NSMakeSize(0,0);
277     
278     for(i=0;i<count;i++){
279      NSImageRep *check=[_representations objectAtIndex:i];
280      NSSize      checkSize=[check size];
281     
282      if(checkSize.width*checkSize.height>largestSize.width*largestSize.height)
283       largestSize=checkSize;
284     }
286     return largestSize;
287    }
288    
289    return _size;
292 -(NSColor *)backgroundColor {
293    return _backgroundColor;
296 -(BOOL)isFlipped {
297    return _isFlipped;
300 -(BOOL)isTemplate {
301    return _isTemplate;
304 -(BOOL)scalesWhenResized {
305    return _scalesWhenResized;
308 -(BOOL)matchesOnMultipleResolution {
309    return _matchesOnMultipleResolution;
312 -(BOOL)usesEPSOnResolutionMismatch {
313    return _usesEPSOnResolutionMismatch;
316 -(BOOL)prefersColorMatch {
317    return _prefersColorMatch;
320 -(NSImageCacheMode)cacheMode {
321    return _cacheMode;
324 -(BOOL)isCachedSeparately {
325    return _isCachedSeparately;
328 -(BOOL)cacheDepthMatchesImageDepth {
329    return _cacheDepthMatchesImageDepth;
332 -(BOOL)isDataRetained {
333    return _isDataRetained;
336 -delegate {
337    return _delegate;
340 -(BOOL)setName:(NSString *)name {
341    if(_name!=nil && [[NSImage allImages] objectForKey:_name]==self)
342     [[NSImage allImages] removeObjectForKey:_name];
344    name=[name copy];
345    [_name release];
346    _name=name;
348    if([[NSImage allImages] objectForKey:_name]!=nil)
349     return NO;
351    [[NSImage allImages] setObject:self forKey:_name];
352    return YES;
355 -(void)setSize:(NSSize)size {
356    _size=size;
357    [self recache];
360 -(void)setBackgroundColor:(NSColor *)value {
361    value=[value copy];
362    [_backgroundColor release];
363    _backgroundColor=value;
366 -(void)setFlipped:(BOOL)value {
367    _isFlipped=value;
370 -(void)setTemplate:(BOOL)value {
371    _isTemplate=value;
374 -(void)setScalesWhenResized:(BOOL)value {
375    _scalesWhenResized=value;
378 -(void)setMatchesOnMultipleResolution:(BOOL)value {
379    _matchesOnMultipleResolution=value;
382 -(void)setUsesEPSOnResolutionMismatch:(BOOL)value {
383    _usesEPSOnResolutionMismatch=value;
386 -(void)setPrefersColorMatch:(BOOL)value {
387    _prefersColorMatch=value;
390 -(void)setCacheMode:(NSImageCacheMode)value {
391    _cacheMode=value;
394 -(void)setCachedSeparately:(BOOL)value {
395    _isCachedSeparately=value;
398 -(void)setCacheDepthMatchesImageDepth:(BOOL)value {
399    _cacheDepthMatchesImageDepth=value;
402 -(void)setDataRetained:(BOOL)value {
403    _isDataRetained=value;
406 -(void)setDelegate:delegate {
407    _delegate=delegate;
410 -(BOOL)isValid {
411    NSUnimplementedMethod();
412    return 0;
415 -(NSArray *)representations {
416    return _representations;
419 -(void)addRepresentation:(NSImageRep *)representation {
420    if(representation!=nil)
421     [_representations addObject:representation];
424 -(void)addRepresentations:(NSArray *)array {
425    int i,count=[array count];
426    
427    for(i=0;i<count;i++)
428     [self addRepresentation:[array objectAtIndex:i]];
431 -(void)removeRepresentation:(NSImageRep *)representation {
432    [_representations removeObjectIdenticalTo:representation];
435 -(NSCachedImageRep *)_cachedImageRepCreateIfNeeded {
436    int count=[_representations count];
438    while(--count>=0){
439     NSCachedImageRep *check=[_representations objectAtIndex:count];
441     if([check isKindOfClass:[NSCachedImageRep class]]){
442     
443      if(_cacheIsValid)
444      return check;
446      [_representations removeObjectAtIndex:count];
447    }
448    }
449    
450    NSCachedImageRep *cached=[[NSCachedImageRep alloc] initWithSize:[self size] depth:0 separate:_isCachedSeparately alpha:YES];
451    [self addRepresentation:cached];
452    [cached release];
453    return cached;
456 -(NSImageRep *)_bestUncachedRepresentationForDevice:(NSDictionary *)device {
457    int i,count=[_representations count];
459    for(i=0;i<count;i++){
460     NSImageRep *check=[_representations objectAtIndex:i];
461       
462     if(![check isKindOfClass:[NSCachedImageRep class]]){
463      return check;
464     }
465    }
466    
467    return nil;
468    
471 -(NSImageRep *)_bestUncachedFallbackCachedRepresentationForDevice:(NSDictionary *)device size:(NSSize)size {
472    int i,count=[_representations count];
473    NSImageRep *best=nil;
475    size.width=ABS(size.width);
476    size.height=ABS(size.height);
477       
478    for(i=0;i<count;i++){
479     NSImageRep *check=[_representations objectAtIndex:i];
480       
481     if(![check isKindOfClass:[NSCachedImageRep class]]){
482      if(best==nil)
483       best=check;
484      else {
485       NSSize checkSize=[check size];
486       NSSize bestSize=[best size];
487       float  checkArea=checkSize.width*checkSize.height;
488       float  bestArea=bestSize.width*bestSize.height;
489       float  desiredArea=size.width*size.height;
490       
491       // downsampling is better than upsampling
492       if(bestArea<desiredArea && checkArea>=desiredArea)
493        best=check;
494       // downsampling a closer image is better
495       if(checkArea<bestArea && checkArea>=desiredArea)
496        best=check;
497       // if we have to upsample, biggest is better
498       if(checkArea>bestArea && bestArea<desiredArea)
499        best=check;
500     }
501    }
502    }
503    
504    if(best!=nil)
505     return best;
506     
507    for(i=0;i<count;i++){
508     NSImageRep *check=[_representations objectAtIndex:i];
509       
510     if([check isKindOfClass:[NSCachedImageRep class]]){
511      return check;
512     }
513    }
514    
515    return nil;   
518 -(NSImageRep *)bestRepresentationForDevice:(NSDictionary *)device {
519    if(device==nil)    
520     device=[[NSGraphicsContext currentContext] deviceDescription];
521    
522    if([device objectForKey:NSDeviceIsPrinter]!=nil){
523     int i,count=[_representations count];
524      
525     for(i=0;i<count;i++){
526      NSImageRep *check=[_representations objectAtIndex:i];
527       
528      if(![check isKindOfClass:[NSCachedImageRep class]])
529       return check;
530     }
531    }
532    
533    if([device objectForKey:NSDeviceIsScreen]!=nil){
534     NSImageRep      *uncached=[self _bestUncachedRepresentationForDevice:device];
535     NSImageCacheMode caching=_cacheMode;
536     
537     if(caching==NSImageCacheDefault){
538      if([uncached isKindOfClass:[NSBitmapImageRep class]])
539       caching=NSImageCacheBySize;
540      else if([uncached isKindOfClass:[NSPDFImageRep class]])
541       caching=NSImageCacheAlways;
542      else if([uncached isKindOfClass:[NSEPSImageRep class]])
543       caching=NSImageCacheAlways;
544      else if([uncached isKindOfClass:[NSCustomImageRep class]])
545       caching=NSImageCacheAlways;
546     }
547      
548     switch(caching){
549     
550      case NSImageCacheDefault:
551      case NSImageCacheAlways:
552       break;
553      
554      case NSImageCacheBySize:
555       if([[uncached colorSpaceName] isEqual:[device objectForKey:NSDeviceColorSpaceName]]){
556        NSSize size=[self size];
557        
558        if((size.width==[uncached pixelsWide]) && (size.height==[uncached pixelsHigh])){
559         int deviceBPS=[[device objectForKey:NSDeviceBitsPerSample] intValue];
560        
561         if(deviceBPS==[uncached bitsPerSample])
562          return uncached;
563        }
564       }
565       break;
566       
567      case NSImageCacheNever:
568       return uncached;
569     }
570         
571     NSCachedImageRep *cached=[self _cachedImageRepCreateIfNeeded];
572     
573     if(!_cacheIsValid){
574      [self lockFocusOnRepresentation:cached];
575      NSRect rect;
576      rect.origin.x=0;
577      rect.origin.y=0;
578      rect.size=[self size];
579      
580      if([self scalesWhenResized]){
581       [self drawRepresentation:uncached inRect:rect];
582      }
583      else
584       [uncached drawAtPoint:rect.origin];
585       
586      [self unlockFocus];
587      _cacheIsValid=YES;
588     }
589     
590     return cached;
591    }
592    
593    return [_representations lastObject];
596 -(void)recache {
597 // This doesn't actually remove the cache, it just marks it as invalid
598 // This is important because you can change the size of a drawn image
599 // and it doesn't destroy the cache. It is recached next time it is drawn.
600    _cacheIsValid=NO;
603 -(void)cancelIncrementalLoad {
604    NSUnimplementedMethod();
607 -(NSData *)TIFFRepresentation {
608    return [self TIFFRepresentationUsingCompression:NSTIFFCompressionNone factor:0.0];
611 -(NSData *)TIFFRepresentationUsingCompression:(NSTIFFCompression)compression factor:(float)factor {
612    NSMutableArray *bitmaps=[NSMutableArray array];
613    
614         for(NSImageRep *check in _representations){
615                 if([check isKindOfClass:[NSBitmapImageRep class]]) {
616                         [bitmaps addObject:check];
617                 } else if([check isKindOfClass:[NSCachedImageRep class]]) {
618                         // We don't use the general case else we get flipped results for flipped images since lockFocusOnRepresentation is flipping and the Cache content
619                         // is already flipped
620                         NSRect r = { .origin = NSZeroPoint, .size = check.size };
621                         [self lockFocus];
622                         NSBitmapImageRep *image = [[NSBitmapImageRep alloc] initWithFocusedViewRect: r];
623                         [self unlockFocus];
625                         [bitmaps addObject:image];
626                         [image release];
627                 } else {
628                         NSSize size=[check size];
629                         NSBitmapImageRep *image=[[NSBitmapImageRep alloc] initWithBitmapDataPlanes:NULL pixelsWide:size.width pixelsHigh:size.height bitsPerSample:8 samplesPerPixel:4 hasAlpha:YES isPlanar:NO colorSpaceName:NSDeviceRGBColorSpace bytesPerRow:0 bitsPerPixel:32];
630                         
631                         [self lockFocusOnRepresentation:image];
632      // we should probably use -draw here but not all reps implement it, or not?
633                         [check draw];
634                         [self unlockFocus];
635                         
636                         [bitmaps addObject:image];
637                         [image release];
638     }
639     
640    }
641    
642    return [NSBitmapImageRep TIFFRepresentationOfImageRepsInArray:bitmaps usingCompression:compression factor:factor];
645 -(void)lockFocus {
646    [self lockFocusOnRepresentation:nil];
649 -(void)lockFocusOnRepresentation:(NSImageRep *)representation {
650    NSGraphicsContext *context=nil;
651    CGContextRef       graphicsPort;
653    if(representation==nil){
654 // FIXME: Cocoa doesn't add the cached rep until the unlockFocus, it just creates the drawing context 
655 // then snaps the image during unlock and adds it
656     representation=[self _cachedImageRepCreateIfNeeded];
657       
658       [self lockFocusOnRepresentation:representation];
659       NSRect rect;
660       id uncached=[self _bestUncachedRepresentationForDevice:nil];
661       rect.origin.x=0;
662       rect.origin.y=0;
663       rect.size=[self size];
664       
665   //    if([self scalesWhenResized])
666          [uncached drawInRect:rect];
667          // drawAtPoint: is not working with NSPDFImageRep
668          // Should probably ditch all the caching stuff anyway as it is deprecated
669     //   else
670       //   [uncached drawAtPoint:rect.origin];
671          
672       [self unlockFocus];
673       _cacheIsValid=YES;
674    }
675    
676    if([representation isKindOfClass:[NSCachedImageRep class]])
677     context=[NSGraphicsContext graphicsContextWithWindow:[(NSCachedImageRep *)representation window]];
678    else if([representation isKindOfClass:[NSBitmapImageRep class]])
679     context=[NSGraphicsContext graphicsContextWithBitmapImageRep:(NSBitmapImageRep *)representation];
680    
681    if(context==nil){
682     [NSException raise:NSInvalidArgumentException format:@"NSImageRep %@ can not be lockFocus'd"]; 
683     return;
684    }
685    
686    [NSGraphicsContext saveGraphicsState];
687    [NSGraphicsContext setCurrentContext:context];
689    graphicsPort=NSCurrentGraphicsPort();
690    CGContextSaveGState(graphicsPort);
691    CGContextClipToRect(graphicsPort,NSMakeRect(0,0,[representation size].width,[representation size].height));
692    
693         // Some fake view, just so the context knows if it's flipped or not
694         NSView *view = [[[NSImageCacheView alloc] initWithFlipped:[self isFlipped]] autorelease];
695     [[context focusStack] addObject:self];
697         if([self isFlipped]){
698     CGAffineTransform flip={1,0,0,-1,0,[self size].height};
699     CGContextConcatCTM(graphicsPort,flip);
700    }
704 -(void)unlockFocus {
705         // Remove the pushed view
706         [[[NSGraphicsContext currentContext] focusStack] removeLastObject];
708         CGContextRef graphicsPort=NSCurrentGraphicsPort();
710    CGContextRestoreGState(graphicsPort);
712    [NSGraphicsContext restoreGraphicsState];
715 -(BOOL)drawRepresentation:(NSImageRep *)representation inRect:(NSRect)rect {
716    NSColor *bg=[self backgroundColor];
717    
718    if(bg!=nil){
719     [bg setFill];
720    NSRectFill(rect);
721    }
722    
723    return [representation drawInRect:rect];
726 -(void)compositeToPoint:(NSPoint)point fromRect:(NSRect)rect operation:(NSCompositingOperation)operation {
727    [self compositeToPoint:point fromRect:rect operation:operation fraction:1.0];
730 -(void)compositeToPoint:(NSPoint)point fromRect:(NSRect)source operation:(NSCompositingOperation)operation fraction:(float)fraction {
731    /* Compositing is a blitting operation. We simulate it using the draw operation.
732    
733       Compositing does not honor all aspects of the CTM, e.g. it will keep an image upright regardless of the orientation of CTM.
734       To deal with that we use a negative height in a flipped coordinate system.
735       There are probably other cases which are not right here.
736     */
737     
738    NSSize size=[self size];
739    NSRect rect=NSMakeRect(point.x,point.y,size.width,size.height);
741    NSGraphicsContext *graphicsContext=[NSGraphicsContext currentContext];
742    CGContextRef context=[graphicsContext graphicsPort];
743    
744    CGContextSaveGState(context);   
745    if([[NSGraphicsContext currentContext] isFlipped]){
746     rect.size.height=-rect.size.height;
747    }
748    
749    [self drawInRect:rect fromRect:source operation:operation fraction:fraction];
750    CGContextRestoreGState(context);   
753 -(void)compositeToPoint:(NSPoint)point operation:(NSCompositingOperation)operation {
754    [self compositeToPoint:point operation:operation fraction:1.0];
757 -(void)compositeToPoint:(NSPoint)point operation:(NSCompositingOperation)operation fraction:(float)fraction {   
758    [self compositeToPoint:point fromRect:NSZeroRect operation:operation fraction:1.0];
761 -(void)dissolveToPoint:(NSPoint)point fraction:(float)fraction {
762    NSUnimplementedMethod();
765 -(void)dissolveToPoint:(NSPoint)point fromRect:(NSRect)rect fraction:(float)fraction {
766    NSUnimplementedMethod();
769 -(void)drawAtPoint:(NSPoint)point fromRect:(NSRect)source operation:(NSCompositingOperation)operation fraction:(float)fraction {
770    NSSize size=[self size];
771    
772    [self drawInRect:NSMakeRect(point.x,point.y,size.width,size.height) fromRect:source operation:operation fraction:fraction];
775 -(void)drawInRect:(NSRect)rect fromRect:(NSRect)source operation:(NSCompositingOperation)operation fraction:(float)fraction {
777         // Keep a lid on any intermediate allocations while producing caches
778         NSAutoreleasePool *pool=[NSAutoreleasePool new];
779    NSImageRep        *any=[[[self _bestUncachedFallbackCachedRepresentationForDevice:nil size:rect.size] retain] autorelease];
780    NSImageRep        *cachedRep=nil;
781    CGContextRef       context;
782         NSRect fullRect = { .origin = NSZeroPoint, .size = self.size };
783         BOOL drawFullImage = (NSIsEmptyRect(source) || NSEqualRects(source, fullRect));
784         BOOL canCache = drawFullImage && !_isFlipped;
785         
786         if (canCache) {
787                 // If we're drawing the full image unflipped then we can just draw from a cached rep or a bitmap rep (assuming we have one)
788                 if([any isKindOfClass:[NSCachedImageRep class]] ||
789                    [any isKindOfClass:[NSBitmapImageRep class]]) {
790                         cachedRep=any;
791                 }
792         }
793     
794         if(cachedRep==nil) {
795                 // Looks like we need to create a cached rep for this image
796                 NSImageRep       *uncached=any;
797                 NSSize            uncachedSize=[uncached size];
798                 BOOL              useSourceRect=NSIsEmptyRect(source)?NO:YES;
799                 NSSize            cachedSize=useSourceRect?source.size:uncachedSize;
800         
801                 // Create a cached image rep to hold our image
802                 NSCachedImageRep *cached=[[[NSCachedImageRep alloc] initWithSize:cachedSize depth:0 separate:YES alpha:YES] autorelease]; // remember that pool we created earlier
804                 // a non-nil object passed here means we need to manually add the rep
805                 [self lockFocusOnRepresentation: cached];
806    
807                 context=NSCurrentGraphicsPort();
808                 if(useSourceRect) {
809                         // move to the origin of the source rect - remember we've locked focus so we've got a fresh CTM to work with
810                         CGContextTranslateCTM(context,-source.origin.x,-source.origin.y);
811                 }
812                 if (_isFlipped) {
813                         // Flip the CTM so the image is drawn the right way up in the cache
814                         CGContextTranslateCTM(context, 0, uncachedSize.height);
815                         CGContextScaleCTM(context, 1, -1);
816                 }
817                 // Draw into the new cache rep
818                 [self drawRepresentation:uncached inRect:NSMakeRect(0,0,uncachedSize.width,uncachedSize.height)];
820                 [self unlockFocus];
821     
822                 // And keep it if it makes sense
823                 if (canCache) {
824                         [self addRepresentation: cached];
825                 }
826                 
827                 cachedRep=cached;
828         }
829    
830         // OK now we've got a rep we can draw
831         
832         context=NSCurrentGraphicsPort();
834         CGContextSaveGState(context);
835    
836         if (CGContextSupportsGlobalAlpha(context) == NO) {
837                 // That should really be done by setting the context alpha - and the compositing done in the context implementation
838                 if(fraction!=1.0){
839                         // fraction is accomplished with a 1x1 alpha mask
840                         // FIXME: could use a float format image to completely preserve fraction
841                         uint8_t           bytes[1]={ MIN(MAX(0,fraction*255),255) };
842                         CGDataProviderRef provider=CGDataProviderCreateWithData(NULL,bytes,1,NULL);
843                         CGImageRef        mask=CGImageMaskCreate(1,1,8,8,1,provider,NULL,NO);
844                         
845                         CGContextClipToMask(context,rect,mask);
846                         CGImageRelease(mask);
847                         CGDataProviderRelease(provider);
848                 }
849         } else {
850                 CGContextSetAlpha(context, fraction);
851         }       
852         [[NSGraphicsContext currentContext] setCompositingOperation:operation];
853     
854         [self drawRepresentation: cachedRep inRect:rect];
855    
856         CGContextRestoreGState(context);
858         [pool release];
861 -(NSString *)description {
862    NSSize size=[self size];
863    
864    return [NSString stringWithFormat:@"<%@[0x%lx] name: %@ size: { %f, %f } representations: %@>", [self class], self, _name, size.width, size.height, _representations];
867 @end
869 @implementation NSBundle(NSImage)
871 -(NSString *)pathForImageResource:(NSString *)name {
872     NSString *extension = [name pathExtension];
873     if (extension && extension.length) {
874         NSString *baseName=[name stringByDeletingPathExtension];
875         return [self pathForResource:baseName ofType:extension];
876     }
877    NSArray *types=[NSImage imageFileTypes];
878    int      i,count=[types count];
880    for(i=0;i<count;i++){
881     NSString *type=[types objectAtIndex:i];
882     NSString *path=[self pathForResource:name ofType:type];
884     if(path!=nil)
885      return path;
886    }
888    return [self pathForResource:[name stringByDeletingPathExtension] ofType:[name pathExtension]];
891 @end