This is SurfImageDecoder.m in view mode; [Download] [Up]
/*
** Copyright (c) 1995 Netsurfer Inc. All Rights Reserved.
**
** Author: <bbum@friday.com>
*/
/* This object is included in the MiscKit by permission from the author
** and its use is governed by the MiscKit license, found in the file
** "LICENSE.rtf" in the MiscKit distribution. Please refer to that file
** for a list of all applicable permissions and restrictions.
*/
#import <appkit/appkit.h>
#import "SurfImageDecoder.h"
#import <objc/objc-runtime.h>
#ifdef VERBOSE
#undef VERBOSE
#undef METHOD
#undef METHODnl
#endif
#define VERBOSE if(_SIDFlags.verboseMode)
#define METHOD fprintf(stderr, "[%s %s%s] ", \
object_getClassName(self), \
(self == [self class]) ? "+" : "-", \
SELNAME(_cmd))
#define METHODnl fprintf(stderr, "[%s %s%s]\n", \
object_getClassName(self), \
(self == [self class]) ? "+" : "-", \
SELNAME(_cmd))
#define BOOL2STR(_b_) (_b_ ? "Yes": "No")
@implementation SurfImageDecoder
/*"
* A controller for image decoding. This class implements a generic image
* decoder that can be used to decde any of a number of different image
* formats depending on what decoder classes have been registered with the
* instance.
*
* Given an arbitrary path or stream, an instance of #SurfImageDecoder
* will determine the type of the image, attempt to decode it, and, upon
* success, will return an instance of #NXImage containing the decoded
* image.
*
* Instances are designed to be used in a multi-threaded context. Any
* instance can be decoding image in any thread of execution while other
* instances are simultaneously decoding images. A single instance
* %{cannot} decode multiple images from different threads; to attempt
* this guarantees catastrophic failure of the environment.
*
* #{Registrering Custom Decoders}
*
* A class can register to be a potential decoder by calling
* #{SurfImageDecoder}'s #{-addDecoderClass:} method. When instantiated,
* an instance of SurfImageDecoder automatically registers the
* #{SurfGIFDecoder} and #{SurfJPEGDecoder} classes.
*
* #{How Image Formats Are Determined}
*
* The #{-loadFromStream:...} family of methods determines the contents of
* the stream by calling {+canLoadFromStream:} within each potentially
* eligible decoder's class. The first decoder to return YES decodes the
* stream.
*
* #{-loadFromFile:...} and friends try to determine the image type by
* looking up the file's extension in the #{decoderByType} HashTable. If
* not found, the stream is mapped into memory and passed to
* #{-loadFromStream:...}.
*
* Note: the #{-lastImageCorrupt} method assumes that the last decoder used
* still exists and is still viable; ie: don't call #{-lastImageCorrupt}
* if you are using external decoders that may have been freed between the
* time an image decoding attempt was made and the time that
* #{-lastImageCorrupt} is called!.
*
"*/
+ sharedInstance
/*"
* Returns the shared instance of SurfImageDecoder. Should only be used in
* applications that require a single image decoder.
"*/
{
static id sharedInstance = nil;
if (!sharedInstance) {
sharedInstance = [self alloc];
[sharedInstance init];
}
return sharedInstance;
}
- init
/*"
* Designated intializer. Creates entries in %decoderByType for each
* decoder type, but does not allocate/initialize any decoders.
"*/
{
VERBOSE {
METHOD;
}
decoderByType = [[HashTable allocFromZone:[self zone]]
initKeyDesc:"*" valueDesc:"@"];
decoderClassByType = [[HashTable allocFromZone:[self zone]]
initKeyDesc:"*" valueDesc:"@"];
decoderList = [[List allocFromZone:[self zone]] init];
_decoderReferences = [[HashTable allocFromZone:[self zone]]
initKeyDesc:"@" valueDesc:"i"];
[self addDecoderClass: objc_lookUpClass("SurfGIFDecoder")];
[self addDecoderClass: objc_lookUpClass("SurfJPEGDecoder")];
[self addDecoderClass: objc_lookUpClass("SurfTIFFDecoder")];
[self addDecoderClass: objc_lookUpClass("SurfPNGDecoder")];
return self;
}
- free
/*"
* Frees the image decoder and all specific type decoders.
"*/
{
[[decoderByType empty] free];
[[decoderClassByType empty] free];
[[decoderList freeObjects] free];
NX_FREE((char **) _unfilteredFileTypes);
[[_decoderReferences empty] free];
return [super free];
}
- (const char *const *)imageUnfilteredFileTypes
/*"
* Returns NULL terminated array of strings containing all of the image
* types an instance of SurfImageDecoder can decode.
"*/
{
if(!_unfilteredFileTypes) {
NXHashState theState;
const char *type;
id value;
int index;
_unfilteredFileTypes = NXZoneMalloc([self zone],
sizeof(char *) *
([decoderByType count] + 1));
theState = [decoderByType initState];
index = 0;
while([decoderByType nextState:&theState
key: (void **) &type
value: (void **) &value]) {
_unfilteredFileTypes[index] = type;
index++;
}
_unfilteredFileTypes[index] = NULL;
}
return _unfilteredFileTypes;
}
- (BOOL)canLoadFromStream:(NXStream *)aStream
/*"
* Returns YES if the decoder thinks it can decode the data in aStream.
* This forwards aStream to each specific format decoding class, if any
* return YES, this method immediately returns YES.
*
* The current position in the stream is unchanged.
"*/
{
return [self decoderForStream: aStream] ? YES : NO;
}
- decoderForType:(const char *) imageType
/*"
* Returns the decoder for images of type imageType. This can be used to
* customize the particular image decoder.
"*/
{
if(!imageType)
return nil;
VERBOSE {
METHODnl;
fprintf(stderr, "{%s}\n", imageType);
}
return [decoderByType valueForKey: NXUniqueString(imageType)];
}
- (Class) decoderClassForType:(const char *) imageType
/*"
* Returns the decoder class for images of type imageType. This can be
* used to customize the particular image decoder.
"*/
{
if(!imageType)
return nil;
VERBOSE {
METHODnl;
fprintf(stderr, "{%s}\n", imageType);
}
return [decoderClassByType valueForKey: NXUniqueString(imageType)];
}
- decoderForStream:(NXStream *) aStream
/*"
* Returns the decoder object that should be used to decode aStream. If an
* appropriate decoder object is not available, returns nil.
"*/
{
/* -----------------------------------------------------------------------
* Traverse decoderList: call +canLoadFromStream: on each. If one
* returns YES, return that instance.
* -----------------------------------------------------------------------
*/
id *ida;
id *max;
ida = NX_ADDRESS(decoderList);
max = ida + [decoderList count];
for(;ida<max;ida++)
/*
* if is the implementor's responsibility to ensure that the
* stream's seek is unaltered upon return!!!
*/
if([[*ida class] canLoadFromStream: aStream])
return *ida;
return nil;
}
- (Class) decoderClassForStream:(NXStream *) aStream
/*"
* Returns the decoder Class used to decode aStream. If no class is
* available, returns nil.
"*/
{
return [[self decoderForStream: aStream] class];
}
- addDecoderClass: aClass
/*"
* Adds aClass as a possible decoder. This is accomplished by retrieving
* the types accepted by the decoder class by calling
* #{+imageUnfilteredFileTypes}. The returned types are appended to the
* list of types that can be converted by this image converter object. An
* instance of the class is allocated and an entry is created in
* %hashedExtensions for each type the decoder can decode. If a decoder
* that can decode that particular type already exists in
* %hashedExtensions, it is replaced (but not freed).
"*/
{
id newDecoder;
int refCount;
const char * const *decoderTypes;
VERBOSE {
METHOD;
}
/*
* check to see if aClass is defined and responds to some set of methods
* that define it to be a decoder. Should we create a protocol that
* defines decoder-ness?
*/
if(!aClass)
return nil;
/*
* Set the _lastDecoder to nil. This prevents anyone from doing
* something that causes the _lastDecoder to be sent a message when the
* _lastDecoder has already been freed!
*/
_lastDecoder = nil;
/*
* allocate instance of decoder
*/
newDecoder = [[aClass allocFromZone:[self zone]] init];
/*
* add to _decoderReferences hashtable
*/
refCount = 0;
/*
* ask decoder for +imageUnfilteredFileTypes; traverse array of types
*/
decoderTypes = [aClass imageUnfilteredFileTypes];
for (;*decoderTypes;decoderTypes++) {
const char *theType = *decoderTypes;
int oldRefCount;
id oldDecoder;
VERBOSE {
METHODnl;
fprintf(stderr, "Adding type: {%s}\n", theType);
}
/*
* insert decoder and decoder class in decoderByType hash; with type
* as key.
*/
oldDecoder = [decoderByType valueForKey: theType];
[decoderByType insertKey: theType value: newDecoder];
[decoderClassByType insertKey: theType value: aClass];
/*
* decremement reference count of old decoder-- if it is zero, free
* it.
*/
oldRefCount = (int) [_decoderReferences valueForKey: oldDecoder];
--oldRefCount;
if(oldRefCount == 0) {
[_decoderReferences removeKey: oldDecoder];
[oldDecoder free];
} else if (oldRefCount != -1) {
[_decoderReferences insertKey: oldDecoder
value:(void *)oldRefCount];
}
/*
* increment reference count for new decoder.
*/
refCount++;
}
// set reference count
[_decoderReferences insertKey:newDecoder value:(void *)refCount];
// add to decoderList
[decoderList addObject:newDecoder];
/*
* propagate configuration (imageDepth)
*/
[newDecoder setImageDepth:imageDepth];
[newDecoder setVerboseMode:_SIDFlags.verboseMode];
[newDecoder setErrorDelegate:self];
NX_FREE(_unfilteredFileTypes);
_unfilteredFileTypes = NULL;
[self imageUnfilteredFileTypes];
return self;
}
- decodeFromFile:(const char *) filePath
/*"
* Attempts to decode the data in filePath as an image. Uses the
* filePath's extension to determine the image type. If it fails to find a
* decoder given the filePath's extension, the method maps the filePath and
* calls {-decodeFromStream:} in hopes that one of the image decoders will
* decide it can decode the stream contents.
*
* Returns the decoded image upon success and nil upon failure. This
* methdo is a cover for #{-decodeFromFile:intoImage:withDecoder:}
"*/
{
return [self decodeFromFile: filePath
intoImage: nil
withDecoder: nil];
}
- decodeFromFile:(const char *) filePath intoImage: anImage
/*"
* Attempts to decode the contents of filePath in the same fashion as
* #{-decodeFromFile:}. Instead of allocating an instance of NXImage, this
* method will append the newly decoded image in an NXBitmapImageRep using
* the #{-useRepresentation:} method.
*
* Returns the decoded image upon success or nil upon failure. This
* methdo is a cover for #{-decodeFromFile:intoImage:withDecoder:}
"*/
{
return [self decodeFromFile: filePath
intoImage: anImage
withDecoder: nil];
}
- decodeFromFile:(const char *) filePath
intoImage: anImage
withDecoder: aDecoder
/*"
* Attempts to decode the contents of filePath using aDecoder. If anImage
* is defined, the decoded representation will be appended to anImage using
* the #{-useRepresentation:} method. If aDecoder is not defined, the
* method will try to identify the decoder to be used from filePath's
* extension. If that fails, this method will map the file and pass
* control #{-decodeFromStream:...}.
*
* Returns the decoded image upon success or nil upon failure.
"*/
{
NXStream *stream;
if (!aDecoder) {
// grab the file's suffix
// look up decoder
}
stream = NXMapFile(filePath, NX_READONLY);
if(!stream)
return nil;
VERBOSE {
METHODnl;
fprintf(stderr, "%s\n", filePath);
}
if(aDecoder) {
_lastDecoder = aDecoder;
anImage = [aDecoder decodeFromStream: stream
intoImage: anImage];
} else
anImage = [self decodeFromStream: stream
intoImage: anImage
withDecoder: nil];
NXCloseMemory(stream, NX_FREEBUFFER);
return anImage;
}
- decodeFromStream:(NXStream *) aStream
/*"
* This method is a cover for #{-decodeFromStream:intoImage:withDecoder:}.
"*/
{
return [self decodeFromStream: aStream intoImage: nil withDecoder: nil];
}
- decodeFromStream:(NXStream *) aStream intoImage:anImage
/*"
* This method is a cover for #{-decodeFromStream:intoImage:withDecoder:}.
"*/
{
return [self decodeFromStream: aStream intoImage: nil withDecoder: nil];
}
- decodeFromStream:(NXStream *) aStream
intoImage:anImage
withDecoder:aDecoder
/*"
* Decodes the contents of aStream into the image anImage using the decoder
* aDecoder. If anImage is defined, the decoded image will be appended
* using the #{-useRepresentation:} method. If aDecoder is not defined,
* this method determines the decoder using the #{-decoderForStream:}
* method.
*
* Returns the decoded image upon success, or nil upon failure.
"*/
{
if(!aStream)
return nil;
aDecoder = aDecoder ? aDecoder : [self decoderForStream: aStream];
_lastDecoder = aDecoder;
if(!aDecoder)
return nil;
return [aDecoder decodeFromStream: aStream intoImage: anImage];
}
- (void) setImageDepth:(NXWindowDepth) aDepth
/*"
* Sets the target image depth to aDepth. Some of the image decoders can
* optimize the decoding process to a specific depth. This method will
* propagate aDepth to all decoders using the #{setImageDepth} method;
* whether or not a decoder can actually decode to that depth is entirely
* up to the decoder.
*
* Generally, image decoders default to producing 24-bit deep images.
"*/
{
id *ida;
id *max;
imageDepth = aDepth;
ida = NX_ADDRESS(decoderList);
max = ida + [decoderList count];
for(;ida<max;ida++)
[*ida setImageDepth: imageDepth];
}
- (NXWindowDepth) imageDepth
/*"
* Returns %{imageDepth}.
"*/
{
return imageDepth;
}
- (BOOL) lastImageCorrupt
/*"
* Returns YES if last the image that was decoded contained some kind of
* error.
"*/
{
return _lastDecoder ? [_lastDecoder lastImageCorrupt] : YES;
}
- decoderList
/*"
* Returns the decoderList List.
"*/
{
return decoderList;
}
- decoderByType
/*"
* Returns the decoderByType HashTable.
"*/
{
return decoderByType;
}
- decoderClassByType
/*"
* Returns the decoderClassByType HashTable.
"*/
{
return decoderClassByType;
}
- (BOOL) verboseMode
/*"
* Returns YES if verbose mode is enabled.
"*/
{
return _SIDFlags.verboseMode;
}
- (void) setVerboseMode:(BOOL) aFlag
/*"
* Enabled/disables verbose mode. Verbose mode spews a HUGE quantity of
* state information to stderr.
"*/
{
id *ida;
id *max;
_SIDFlags.verboseMode = aFlag;
ida = NX_ADDRESS(decoderList);
max = ida + [decoderList count];
for(;ida<max;ida++)
[*ida setVerboseMode: _SIDFlags.verboseMode];
}
- (void) setSpewToStderr:(BOOL) aFlag
/*"
* If aFlag is YES, error messages geneerated by decoders will be printed
* via NXLogError().
"*/
{
_SIDFlags.spewStderr = aFlag;
}
- (BOOL) spewToStderr
/*"
* Returns YES if error messages will be printed to the console/stderr.
"*/
{
return _SIDFlags.spewStderr;
}
- (void) setErrorDelegate:(id <SurfErrorDelegate>) aDelegate
/*"
* Sets the error delegate to aDelegate. aDelegate's implementation of
* #{-decoder:spewMessage:withSeverity:} will be invoked whenever a decoder
* generates an error message.
"*/
{
errorDelegate = aDelegate;
}
- errorDelegate
/*"
* Returns the image decoder's error delegate.
"*/
{
return errorDelegate;
}
- (void) decoder: aDecoder
spewMessage:(const char *) errMsg
withSeverity:(SurfErrorSeverity) aSeverity
/*"
* Invoked whenever a decoder generates an error. If #errorDelegate has
* been set, it will be notified of the error message via the
* #{-decoder:spewMessage:withSeverity:} method. If #spewToStderr has been
* enabled, the message will be printed via the #{NXLogError()} function.
"*/
{
if(errorDelegate)
[errorDelegate decoder:aDecoder
spewMessage:errMsg
withSeverity:aSeverity];
if(_SIDFlags.spewStderr)
NXLogError("*** Image Decoder Msg *** {%s}", errMsg);
}
- (void)printForDebugger:(NXStream *)aStream
/*"
* Prints a summary of the decoders state to aStream.
"*/
{
}
- (void) dumpTypes
/*"
* Pretty-prints file extensions to stderr.
"*/
{
char ** typeArray = (char **) [self imageUnfilteredFileTypes];
int count;
count = 0;
while(*typeArray) {
typeArray++;
count++;
}
NXLogError("Can decode images with %d different extensions:\n", count);
typeArray = (char **) [self imageUnfilteredFileTypes];
while (*typeArray) {
NXLogError("\t'%s'", *typeArray);
typeArray++;
}
}
@end
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.