man cleanups

This commit is contained in:
John Cupitt 2007-11-02 16:37:32 +00:00
parent fc854987bd
commit 0825569263
50 changed files with 95 additions and 387 deletions

3
TODO
View File

@ -1,3 +1,6 @@
- wrap API around the double-buffered write stuff and get
jpeg/tiff/png/ppm/etc. all writing double-buffered as well
- missing libstdc++ in link? what if we configure with no openexr?
added -lstdc++ to VIPS_LIBS, but will this work on solaris etc.? maybe have

View File

@ -53,6 +53,7 @@ void *im__read_extension_block( IMAGE *im, int *size );
int im__readhist( IMAGE *image );
int im__write_extension_block( IMAGE *im, void *buf, int size );
int im__writehist( IMAGE *image );
int im__handle_eval( IMAGE *im, int w, int h );
extern int im__read_test;
extern int im__mmap_limit;

View File

@ -127,6 +127,12 @@ void im_threadgroup_trigger( im_thread_t *thr );
*/
int im_prepare_thread( im_threadgroup_t *tg, REGION *oreg, Rect *r );
/* Threaded, double-buffered eval to file.
*/
typedef int (*im_wbuffer_fn)( REGION *region, Rect *area, void *a, void *b );
int im_wbuffer( im_threadgroup_t *tg,
im_wbuffer_fn write_fn, void *a, void *b );
#ifdef __cplusplus
}
#endif /*__cplusplus*/

View File

@ -36,25 +36,6 @@
extern "C" {
#endif /*__cplusplus*/
#include <sys/types.h>
#ifdef HAVE_SYS_TIME_H
#include <sys/time.h>
#endif /*HAVE_SYS_TIME_H*/
/* Struct we keep a record of execution time in. Passed to eval callback, so
* it can assess progress.
*/
struct time_info {
IMAGE *im; /* Image we are part of */
time_t start; /* Start time, in seconds */
int run; /* Time we have been running */
int eta; /* Estimated seconds of computation left */
gint64 tpels; /* Number of pels we expect to calculate */
gint64 npels; /* Number of pels calculated so far */
int percent; /* Percent complete */
};
extern int im__handle_eval( IMAGE *im, int w, int h );
#ifdef __cplusplus
}

View File

@ -231,6 +231,19 @@ typedef struct {
size_t length; /* Size of window */
} im_window_t;
/* Struct we keep a record of execution time in. Passed to eval callback, so
* it can assess progress.
*/
typedef struct {
struct im__IMAGE *im; /* Image we are part of */
GTimer *start; /* Start time */
int run; /* Time we have been running */
int eta; /* Estimated seconds of computation left */
gint64 tpels; /* Number of pels we expect to calculate */
gint64 npels; /* Number of pels calculated so far */
int percent; /* Percent complete */
} im_time_t;
/* Image descriptor for subroutine i/o args
*/
typedef struct im__IMAGE {
@ -256,7 +269,7 @@ typedef struct im__IMAGE {
char *Hist; /* don't use ... call im_history_get() */
char *filename; /* pointer to copy of filename */
char *data; /* start of image data for WIO */
struct time_info *time; /* time struct for eval callback */
im_time_t *time; /* time struct for eval callback */
int kill; /* set to non-zero to block partial eval */
/* Private fields.
@ -455,6 +468,7 @@ typedef struct {
#include <vips/semaphore.h>
#include <vips/threadgroup.h>
#include <vips/meta.h>
#include <vips/util.h>
#ifdef __cplusplus
}

View File

@ -21,6 +21,8 @@
* - add </libexif/ prefix if required
* 19/1/07
* - oop, libexif confusion
* 2/11/07
* - use im_wbuffer() API for BG writes
*/
/*
@ -515,6 +517,27 @@ write_profile_meta( Write *write )
return( 0 );
}
static int
write_jpeg_block( REGION *region, Rect *area, void *a, void *b )
{
Write *write = (Write *) a;
int i;
/* We are running in a background thread. We need to catch longjmp()s
* here instead.
*/
if( setjmp( write->eman.jmp ) )
return( -1 );
for( i = 0; i < area->height; i++ )
write->row_pointer[i] = (JSAMPROW)
IM_REGION_ADDR( region, 0, area->top + i );
jpeg_write_scanlines( &write->cinfo, write->row_pointer, area->height );
return( 0 );
}
/* Write a VIPS image to a JPEG compress struct.
*/
static int
@ -522,9 +545,6 @@ write_vips( Write *write, int qfac, const char *profile )
{
IMAGE *in = write->in;
Rect area;
int y, i;
/* Should have been converted for save.
*/
assert( in->BandFmt == IM_BANDFMT_UCHAR );
@ -577,24 +597,16 @@ write_vips( Write *write, int qfac, const char *profile )
write_profile_meta( write ) )
return( -1 );
/* Write data.
/* Write data. Note that the write function grabs the longjmp()!
*/
for( y = 0; y < in->Ysize; y += write->tg->nlines ) {
area.left = 0;
area.top = y;
area.width = in->Xsize;
area.height = IM_MIN( write->tg->nlines, in->Ysize - y );
if( im_wbuffer( write->tg, write_jpeg_block, write, NULL ) )
return( -1 );
if( im_prepare_thread( write->tg, write->reg, &area ) )
return( -1 );
for( i = 0; i < area.height; i++ )
write->row_pointer[i] = (JSAMPROW)
IM_REGION_ADDR( write->reg, 0, y + i );
jpeg_write_scanlines( &write->cinfo,
write->row_pointer, area.height );
}
/* We have to reinstate the setjmp() before we jpeg_finish_compress().
* No need to destroy the write: our parent does that.
*/
if( setjmp( write->eman.jmp ) )
return( -1 );
jpeg_finish_compress( &write->cinfo );

View File

@ -42,6 +42,7 @@ libiofuncs_la_SOURCES = \
im_unmapfile.c \
im_updatehist.c \
im_guess_prefix.c \
im_wbuffer.c \
im_wrapmany.c \
im_wrapone.c \
im_writeline.c \

View File

@ -327,6 +327,28 @@ eval_to_memory( im_threadgroup_t *tg, REGION *or )
return( 0 );
}
/* A write function for VIPS images. Just write() the pixel data.
*/
static int
write_vips( REGION *region, Rect *area, void *a, void *b )
{
size_t nwritten, count;
void *buf;
count = region->bpl * area->height;
buf = IM_REGION_ADDR( region, 0, area->top );
do {
nwritten = write( region->im->fd, buf, count );
if( nwritten == (size_t) -1 )
return( errno );
buf = (void *) ((char *) buf + nwritten);
count -= nwritten;
} while( count > 0 );
return( 0 );
}
/* Attach a generate function to an image.
*/
int
@ -402,8 +424,7 @@ im_generate( IMAGE *im,
return( -1 );
}
if( im->dtype == IM_OPENOUT )
res = im_wbuffer( tg,
im_wbuffer_write_vips, NULL, NULL );
res = im_wbuffer( tg, write_vips, NULL, NULL );
else
res = eval_to_memory( tg, or );

View File

@ -75,32 +75,6 @@ typedef struct _WriteBuffer {
void *b;
} WriteBuffer;
/* A write function for VIPS images. Just write() the pixel data.
*/
int
im_wbuffer_write_vips( REGION *region, Rect *area, void *a, void *b )
{
size_t nwritten, count;
void *buf;
count = region->bpl * area->height;
buf = IM_REGION_ADDR( region, 0, area->top );
do {
nwritten = write( region->im->fd, buf, count );
/* Write failed? Note in wbuffer errno for the main
* thread to pick up.
*/
if( nwritten == (size_t) -1 )
return( errno );
buf = (void *) ((char *) buf + nwritten);
count -= nwritten;
} while( count > 0 );
return( 0 );
}
static void
wbuffer_free( WriteBuffer *wbuffer )
{
@ -135,7 +109,7 @@ wbuffer_write( WriteBuffer *wbuffer )
#ifdef DEBUG
printf( "wbuffer_write: %d bytes from wbuffer %p\n",
region->bpl * area->height, wbuffer );
wbuffer->region->bpl * wbuffer->area.height, wbuffer );
#endif /*DEBUG*/
}

View File

@ -4,8 +4,6 @@ IM_ARRAY, IM_NEW, IM_NUMBER \- memory allocation macros
.SH SYNOPSIS
#include <vips/vips.h>
.br
#include <vips/util.h>
type-name *IM_NEW( IMAGE *im, type-name )
.br
@ -41,7 +39,6 @@ Both functions return NULL on error, setting im_errorstring.
Example:
#include <vips/vips.h>
#include <vips/util.h>
/* A structure we want to carry about.
*/

View File

@ -6,8 +6,6 @@ IM_REGION_N_ELEMENTS, IM_REGION_SIZEOF_LINE \-
macros for regions
.SH SYNOPSIS
.B #include <vips/vips.h>
.br
.B #include <vips/region.h>
int IM_REGION_LSKIP( reg )
.br

View File

@ -4,8 +4,6 @@ IM_RINT, IM_MAX, IM_MIN \- misc math macros
.SH SYNOPSIS
.B #include <vips/vips.h>
.br
.B #include <vips/util.h>
int IM_RINT( float )
.br

View File

@ -303,20 +303,6 @@ man_MANS = \
im_lineset.3 \
im_lintra.3 \
im_lintra_vec.3 \
im_list_add.3 \
im_list_append.3 \
im_list_eq.3 \
im_list_fix.3 \
im_list_fold.3 \
im_list_free.3 \
im_list_index.3 \
im_list_insert.3 \
im_list_len.3 \
im_list_map.3 \
im_list_map_rev.3 \
im_list_member.3 \
im_list_pos.3 \
im_list_remove.3 \
im_litecor.3 \
im_log10tra.3 \
im_log_dmask.3 \

View File

@ -3,8 +3,6 @@
im_LabQ2Lab, im_Lab2LabQ, im_LabQ2LabS, im_LabS2LabQ, im_Lab2LabS, im_LabS2Lab \- pack and unpack LABPACK images.
.SH SYNOPSIS
#include <vips/vips.h>
.br
#include <vips/colour.h>
int im_LabQ2Lab(in, out)
.br

View File

@ -4,8 +4,6 @@ im_Lab2UCS, im_LabQ2XYZ, im_UCS2Lab, im_Lab2disp, im_disp2Lab, im_UCS2XYZ,
im_XYZ2UCS \- derived colour space conversion functions
.SH SYNOPSIS
#include <vips/vips.h>
.br
#include <vips/colour.h>
int im_Lab2UCS(in, out)
.br

View File

@ -3,8 +3,6 @@
im_LabQ2disp, im_LabQ2disp_build_table, im_LabQ2disp_table \- convert LabQ to display rgb quickly and badly
.SH SYNOPSIS
#include <vips/vips.h>
.br
#include <vips/colour.h>
int im_LabQ2disp( IMAGE *in, IMAGE *out, struct im_col_display *d );

View File

@ -6,8 +6,6 @@ im_Lab2LCh, im_LCh2Lab, im_LCh2UCS, im_UCS2LCh \- convert images between
various colour spaces
.SH SYNOPSIS
#include <vips/vips.h>
.br
#include <vips/colour.h>
int im_XYZ2disp(in, out, display)
.br

View File

@ -7,8 +7,6 @@ im_col_Chucs2h, im_col_make_tables_UCS, im_col_dECMC
\- colour space conversion
.SH SYNOPSIS
#include <vips/vips.h>
.br
#include <vips/colour.h>
int im_col_ab2Ch( a, b, C, h )

View File

@ -3,8 +3,6 @@
im_dE_fromLab, im_dECMC_fromLab, im_dE00_fromLab \- calculate colour differences
.SH SYNOPSIS
#include <vips/vips.h>
.br
#include <vips/colour.h>
int im_dE_fromLab(in1, in2, out)
.br

View File

@ -4,8 +4,6 @@ im_dE_fromdisp, im_dE_fromXYZ, im_dECMC_fromdisp \- derived colour difference
functions
.SH SYNOPSIS
#include <vips/vips.h>
.br
#include <vips/colour.h>
im_dE_fromdisp( in1, in2, out, display )
.br

View File

@ -3,8 +3,6 @@
im_demand_hint \- hint on demand style for im_generate(3)
.SH SYNOPSIS
#include <vips/vips.h>
.br
#include <vips/region.h>
int im_demand_hint( im, hint, in1, in2, ..., NULL )
.br

View File

@ -4,7 +4,6 @@ im_error_buffer, im_verror, im_error, im_error_clear, im_warn, im_diag,
error_exit \- handle error messages from VIPS
.SH SYNOPSIS
.B #include <vips/vips.h>
.B #include <vips/stdarg.h>
.B const char *im_error_buffer( void )

View File

@ -3,7 +3,6 @@
im_flood, im_flood_blob \- flood a area
.SH SYNOPSIS
.B #include <vips/vips.h>
.B #include <vips/rect.h>
int im_flood( im, x, y, ink, dout )
.br

View File

@ -4,8 +4,6 @@ im_generate, im_start_one, im_stop_one, im_allocate_input_array,
im_start_many, im_stop_many \- generate image pixels
.SH SYNOPSIS
.B #include <vips/vips.h>
.br
.B #include <vips/region.h>
void *im_start_one( out, in )
.br

View File

@ -5,8 +5,6 @@ im_icc_export,
im_icc_export_depth \- transform images using ICC profiles
.SH SYNOPSIS
#include <vips/vips.h>
.br
#include <vips/colour.h>
#define VIPS_INTENT_PERCEPTUAL (0)
.br

View File

@ -3,8 +3,6 @@
im_iterate \- PIO input from image
.SH SYNOPSIS
.B #include <vips/vips.h>
.br
.B #include <vips/region.h>
int im_iterate( im, start_fn, scan_fn, stop_fn, a, b )
.br

View File

@ -1,229 +0,0 @@
.TH IM_LIST_ADD 3 "2 May 1991"
.SH NAME
im_list_add, im_list_len, im_list_pos, im_list_member, im_list_append,
im_list_remove,
im_list_eq, im_list_map, im_list_map_rev, im_list_fold, im_list_fix,
im_list_free, im_list_insert \- linked list functions
.SH SYNOPSIS
#include <vips/vips.h>
.br
#include <vips/list.h>
typedef struct list_type {
.br
struct list_type *next;
.br
void *this;
.br
} List;
#define hd(L) ((L)->this)
.br
#define tl(L) ((L)->next)
typedef void *(*im_list_map_fn)( void *, void *, void * );
.br
typedef void (*im_list_free_fn)( void *, void *, void * );
.br
typedef void *(*im_list_fold_fn)( void *, void *,
.br
void *, void * );
int im_list_len( List *l );
.br
int im_list_pos( List *l, void *t );
.br
int im_list_member( List *l, void *t );
.br
void *im_list_index( List *l, int n );
.br
int im_list_add( List **base, void *new );
.br
int im_list_insert( List **base, void *new, void *old );
.br
int im_list_append( List **base, void *new );
.br
int im_list_remove( List **base, void *t );
void *im_list_eq( void *a, void *b );
.br
void *im_list_map( List *l,
.br
im_list_map_fn fn, void *a, void *b );
.br
void *im_list_map_rev( List *l,
.br
im_list_map_fn fn, void *a, void *b );
.br
void *im_list_fold( List *l,
.br
void *start, im_list_fold_fn fn, void *a, void *b );
.br
void im_list_fix( List **base,
.br
im_list_map_fn fn, void *a, void *b );
.br
void im_list_free( List **base,
.br
im_list_free_fn fn, void *a, void *b );
.SH DESCRIPTION
Manipulate linked lists in various ways. These functions are heavily used by
the VIPS IO system; use them yourself if you like. VIPS lists store lists of
void * pointers - use casts if you want to store some other type. Note that
if sizeof( your object ) != sizeof( void * ), you will be in trouble!
All are based on the List type (see above). An empty list is a NULL pointer, a
one element list is a pointer to a List struct, whose this field contains a
pointer to the object in the list and whose next field is NULL. Macros hd(3)
and tl(3) (head and tail) return this and next respectively.
im_list_len(3) returns the number of elements in list l. im_list_pos(3) searches
list l for stored object t, returning an index. The first list element has
index zero. im_list_pos(3) returns -1 for not present. im_list_index(3) returns
the item at position n in the list, or NULL for index out of range.
im_list_member(3) returns non-zero if the list contains the element.
im_list_map(3) applies a void * valued function to every element in a list,
running from beginning to end. If the function returns NULL, im_list_map
continues with the next element. If the function returns non-NULL,
im_list_map(3) abandons the map and returns immediately, returning the value
the user function returned. If the list is empty, im_list_map(3) returns NULL.
The two extra arguments a and b are carried around for you by VIPS and fed
into each call of the function. They are useful for communicating context
information.
You can use im_list_map to implement many kinds of list search/apply operation.
VIPS supplies the function im_list_eq(3) which tests two void * pointers for
equality, returning the pointer if they match, and returning NULL otherwise.
Example: search a list for an object
im_list_map( list,
.br
(im_list_map_fn) im_list_eq, object, NULL );
This could also be written as
List *p;
for( p = list; p; p = tl( p ) )
.br
if( object == hd( p ) )
.br
break;
I prefer the first.
im_list_map_rev(3) behaves exactly as im_list_map(3), but applies the function
running from te end to the beginning of the list. It is much slower than
im_list_map(3) and should be used only in emergencies.
im_list_fold(3) folds up a list with a dyadic function. If a list contains
[1,2], return fn( 2, fn( 1, start, a, b ), a, b ). If the list is empty,
return start.
The two extra arguments a and b are carried around for you by VIPS and fed
into each call of the function. They are useful for communicating context
information.
Example: find a pointer to the largest element in a list of ints (assume
sizeof(int) <= sizeof(void *))
max_pair( int *new, int *old )
.br
{
.br
if( !old || *new > *old )
.br
return( new );
.br
else
.br
return( old );
.br
}
largest = im_list_fold( list,
.br
NULL, (im_list_map_fn) max_pair, NULL, NULL );
im_list_add(3) adds a new element to the head of a list. Since the head of the
list will move, you must pass in a *pointer* to your pointer to your old head.
Example: make a list of the numbers 9-0 (assume sizeof(int) <= sizeof(void *))
int i;
.br
List *nlist = NULL;
for( i = 0; i < 10; i++ )
.br
im_list_add( &nlist, (void *) i );
im_list_insert(3) adds a new element to a list, placing it just before the
indicated old element. If the old element is not found, im_list_insert(3)
returns an error.
im_list_append(3) appends a new element to the end of a list. This is much
slower than im_list_add(3), and should be avoided if possible.
im_list_remove(3) removes the specified element from the list. Since the head
of the list may move, you must pass in a *pointer* to your pointer to your
old head.
im_list_fix(3) finds the fixed-point of a list-altering function. It repeatedly
maps a function over the list until the function returns NULL. Note that,
since the list may be changing, you must pass in a *pointer* to the pointer
you store the list in.
The two extra arguments a and b are carried around for you by VIPS and fed
into each call of the function. They are useful for communicating context
information.
Example: remove all elements less than x from a list of numbers (assume
sizeof(int) <= sizeof(void *))
int *
.br
test_ele( int *n, List **base, int x )
.br
{
.br
if( *n < x ) {
.br
im_list_remove( base, n );
.br
return( base );
.br
}
.br
else
.br
return( NULL );
.br
}
im_list_fix( &nlist,
.br
(im_list_map_fn) test_ele, &nlist, x );
im_list_free(3) frees the list, applying a user free function to every element
as it is freed. You may pass NULL instead of a pointer to a function, in which
case im_list_free(3) will just free the memory used by the list nodes.
The two extra arguments a and b are carried around for you by VIPS and fed
into each call of the function. They are useful for communicating context
information.
.SH RETURN VALUE
The functions returns a 0 or a pointer on sucess, and non-zero or NULL on
failure.
.SH SEE\ ALSO
im_rect_intersectrect(3), etc.
.SH COPYRIGHT
.br
National Gallery, 1992
.SH AUTHOR
J. Cupitt

View File

@ -1 +0,0 @@
.so man3/im_list_add.3

View File

@ -1 +0,0 @@
.so man3/im_list_add.3

View File

@ -1 +0,0 @@
.so man3/im_list_add.3

View File

@ -1 +0,0 @@
.so man3/im_list_add.3

View File

@ -1 +0,0 @@
.so man3/im_list_add.3

View File

@ -1 +0,0 @@
.so man3/im_list_add.3

View File

@ -1 +0,0 @@
.so man3/im_list_add.3

View File

@ -1 +0,0 @@
.so man3/im_list_add.3

View File

@ -1 +0,0 @@
.so man3/im_list_add.3

View File

@ -1 +0,0 @@
.so man3/im_list_add.3

View File

@ -1 +0,0 @@
.so man3/im_list_add.3

View File

@ -1 +0,0 @@
.so man3/im_list_add.3

View File

@ -1 +0,0 @@
.so man3/im_list_add.3

View File

@ -32,8 +32,6 @@ char *im_malloc( IMAGE *im, int size );
int im_free( void *s );
.B #include <vips/vips.h>
.br
.B #include <vips/time.h>
int im_add_eval_callback( im, fn, a, b )
.br
@ -107,19 +105,19 @@ the image is written to. This works for both PIO and WIO images,
although it is rather more successful with PIO image output.
When the callback is triggered, the time field of the descriptor will point to
a time_info structure, as defined in <vips/time.h>
a
.B im_time_t
structure, see vips.h
#include <sys/timeb.h>
struct time_info {
IMAGE *im; /* Image we are part of */
time_t start; /* Start time, in seconds */
int run; /* Time we have been running */
int eta; /* Seconds of computation left */
int ttiles; /* Tiles we expect to calculate */
int ntiles; /* Tiles calculated so far */
int percent; /* Percent complete */
};
typedef struct {
IMAGE *im; /* Image we are part of */
GTimer *start; /* Start time */
int run; /* Time we have been running (secs) */
int eta; /* Estimated seconds of computation left */
gint64 tpels; /* Number of pels we expect to calculate */
gint64 npels; /* Number of pels calculated so far */
int percent; /* Percent complete */
} im_time_t;
These fields are not exact! They should only be used to give approximate
feedback to the user. It is possible to have

View File

@ -78,7 +78,6 @@ calls to
escape points. Example: find the total of an array of images.
#include <vips/vips.h>
#include <vips/region.h>
int
total( IMAGE **in, int nin, IMAGE *out )
@ -111,7 +110,8 @@ This function will create many intermediate images, but does not need to close
them. Any which are created will be closed automatically when out is closed by
our caller.
im_open_local(3) returns NULL on error, or if its first parameter is NULL.
.B im_open_local(3)
returns NULL on error, or if its first parameter is NULL.
.B im_open_local_array(3)
will open an array of images, failing if any of the opens fail. It's handy if

View File

@ -4,8 +4,6 @@ im_paintrect, im_plotmask, im_readpoint, im_plotpoint, im_fastline,
im_fastlineuser \- suck pels up, and paint them down somewhere else
.SH SYNOPSIS
.B #include <vips/vips.h>
.br
.B #include <vips/rect.h>
int im_readpoint( im, x, y, ink )
.br

View File

@ -3,8 +3,6 @@
im_prepare, im_prepare_to \- fill region with data
.SH SYNOPSIS
.B #include <vips/vips.h>
.br
.B #include <vips/region.h>
int im_prepare( reg, r )
.br

View File

@ -6,8 +6,6 @@ im_Coding2char, im_char2Coding, im_Compression2char, im_char2Compression
.SH SYNOPSIS
.B #include <vips/vips.h>
.br
.B #include <vips/util.h>
.B void im_printdesc(image)
.br

View File

@ -7,8 +7,6 @@ IM_RECT_RIGHT, IM_RECT_BOTTOM, IM_RECT_HCENTRE, IM_RECT_VCENTRE
\- rectangle algebra functions
.SH SYNOPSIS
#include <vips/vips.h>
.br
#include <vips/rect.h>
typedef struct {
.br

View File

@ -5,8 +5,6 @@ im_region_equalsregion \-
attach data to region
.SH SYNOPSIS
.B #include <vips/vips.h>
.br
.B #include <vips/region.h>
int im_region_buffer( reg, r )
.br

View File

@ -3,8 +3,6 @@
im_region_create, im_region_free \- region creation and destruction
.SH SYNOPSIS
.B #include <vips/vips.h>
.br
.B #include <vips/region.h>
REGION *im_region_create( im )
.br

View File

@ -3,8 +3,6 @@
im_smudge, im_smear \- filter and smudge in place
.SH SYNOPSIS
.B #include <vips/vips.h>
.br
.B #include <vips/rect.h>
int
.br

View File

@ -3,8 +3,6 @@
im_wrapone, im_wrapmany \- easy interface to partial image IO system
.SH SYNOPSIS
.B #include <vips/vips.h>
.br
.B #include <vips/region.h>
int im_wrapone( IMAGE *in, IMAGE *out,
im_wrapone_fn fn, void *a, void *b )