/* read and write masks */ /* Copyright: 1990, N. Dessipris. * * Author: Nicos Dessipris * Written on: 29/04/1991 * Modified on: 10/8/1992, J.Cupitt * - Mask reading routines no longer fail if scale and offset are missing. * Instead, they set default values of scale=1, offset=0. * - Code tidied up, better error recovery. * - Bugs fixed in im_dup_*mask. No longer coredump. * - Bugs fixed in im_write_*mask. Now work for non-square matricies. * - im_copy_*mask_matrix, im_copy_matrix_*mask added: copy VIPS mask * structures into Numerical Recipies in C style matricies and vice * versa. Both structures should have been built before copy attempted. * See im_create_*mask, im_*mat_alloc. The matrix should be indexed by 0 * to size-1. * 9/7/93 JC * - some ANSIfication and tidies * - im_free_*mask() now return zero, so they can be used as close * callbacks. * 7/10/94 JC * - new IM_NEW(), IM_ARRAY() macros added * 27/4/95 JC * - oops! forgot to init IM_ARRAY() memory to zero * 7/8/96 JC * - im_scale_dmask() rewritten * 7/5/98 JC * - im_read_*mask() rewritten, now more robust * - im_write_*mask() rewritten * - new functions im_write_*mask_name() * 28/7/99 JC * - im_create_imaskv(), im_create_dmaskv() make masks and init from * varargs * - tabs allowed as column separators * 9/2/05 * - "," allowed as column separator ... helps CSV read * 31/5/06 * - use g_ascii_strtod() and friends * 2006-09-08 tcv * - add im_norm_dmask() * 1/9/09 * - move im_print_*mask() here * 12/11/09 * - reading a float mask with im_read_imask() produced an incorrect * error messagge * 21/10/10 * - gtk-doc * - you can use commas to separate header fields * - small cleanups * 30/11/10 * - im_scale_dmask() normalises to 20, not 100 ... we hit the fast * conv path more often * 24/1/10 * - oops, missing braces in write dmask removed spaces between items */ /* This file is part of VIPS. VIPS is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ /* These files are distributed with VIPS - http://www.vips.ecs.soton.ac.uk */ #ifdef HAVE_CONFIG_H #include #endif /*HAVE_CONFIG_H*/ #include #include #include #include #include #include #include #include /** * INTMASK: * @xsize: mask width * @ysize: mask height * @scale: mask scale factor * @offset: mask offset * @coeff: array of mask elements * @filename: the file this mask was read from, or should be written to * * An integer mask. * * @scale lets the mask represent fractional values: for * example, in integer convolution (see im_conv()) the result of the * convolution is divided by @scale and then added to @offset before being * written to the output image. * * @scale and @offset default to 1 and 0. Various functions, such as * im_conv(), will fail if @scale is zero. * * You can read and write the matrix elements in @coeff. */ /** * DOUBLEMASK: * @xsize: mask width * @ysize: mask height * @scale: mask scale factor * @offset: mask offset * @coeff: array of mask elements * @filename: the file this mask was read from, or should be written to * * A floating-point mask. * * As with #INTMASK, in convolution (see im_convf()) the result of the * convolution is divided by @scale and then added to @offset before being * written to the output image. * * @scale and @offset default to 1.0 and 0.0. Various functions, such as * im_conv(), will fail if @scale is zero. * * You can read and write the matrix elements in @coeff. */ /* Size of line buffer for reading. */ #define MAX_LINE (32768) /** * im_free_imask: * @in: mask to free * * Free mask structure and any attached arrays. Return zero, so we can use * these functions as close callbacks. * * See also: im_free_dmask(). * * Returns: zero. */ int im_free_imask( INTMASK *in ) { if( !in ) return( 0 ); IM_FREE( in->coeff ); IM_FREE( in->filename ); IM_FREE( in ); return( 0 ); } /** * im_free_dmask: * @in: mask to free * * Free mask structure and any attached arrays. Return zero, so we can use * these functions as close callbacks. * * See also: im_free_dmask(). * * Returns: zero. */ int im_free_dmask( DOUBLEMASK *in ) { if( !in ) return( 0 ); IM_FREE( in->coeff ); IM_FREE( in->filename ); IM_FREE( in ); return( 0 ); } /** * im_create_imask: * @filename: set mask filename to this * @xsize: mask width * @ysize: mask height * * Create an empty imask. You need to loop over @coeff to set the values. * * See also: im_create_imaskv(). * * Returns: The newly-allocated mask. */ INTMASK * im_create_imask( const char *filename, int xsize, int ysize ) { INTMASK *out; int size = xsize * ysize; /* Check args. */ if( xsize <= 0 || ysize <= 0 || filename == NULL ) { im_error( "im_create_imask", "%s", _( "bad arguments" ) ); return( NULL ); } /* Allocate and initialise structure. */ if( !(out = IM_NEW( NULL, INTMASK )) ) return( NULL ); out->coeff = NULL; out->filename = NULL; out->scale = 1; out->offset = 0; out->xsize = 0; out->ysize = 0; if( !(out->coeff = IM_ARRAY( NULL, size, int )) ) { im_free_imask( out ); return( NULL ); } (void) memset( (char *) out->coeff, 0, size * sizeof( int ) ); if( !(out->filename = im_strdup( NULL, filename )) ) { im_free_imask( out ); return( NULL ); } out->xsize = xsize; out->ysize = ysize; return( out ); } /** * im_create_imaskv: * @filename: set mask filename to this * @xsize: mask width * @ysize: mask height * @...: values to set for the mask * * Create an imask and initialise it from the function parameter list. * * See also: im_create_imask(). * * Returns: The newly-allocated mask. */ INTMASK * im_create_imaskv( const char *filename, int xsize, int ysize, ... ) { va_list ap; INTMASK *out; int i; if( !(out = im_create_imask( filename, xsize, ysize )) ) return( NULL ); va_start( ap, ysize ); for( i = 0; i < xsize * ysize; i++ ) out->coeff[i] = va_arg( ap, int ); va_end( ap ); return( out ); } /** * im_create_dmask: * @filename: set mask filename to this * @xsize: mask width * @ysize: mask height * * Create an empty dmask. You need to loop over @coeff to set the values. * * See also: im_create_dmaskv(), im_vips2mask(). * * Returns: The newly-allocated mask. */ DOUBLEMASK * im_create_dmask( const char *filename, int xsize, int ysize ) { DOUBLEMASK *out; int size = xsize * ysize; /* Check args. */ if( xsize <= 0 || ysize <= 0 || filename == NULL ) { im_error( "im_create_dmask", "%s", _( "bad arguments" ) ); return( NULL ); } /* Allocate and initialise structure. */ if( !(out = IM_NEW( NULL, DOUBLEMASK )) ) return( NULL ); out->coeff = NULL; out->filename = NULL; out->scale = 1.0; out->offset = 0.0; out->xsize = 0; out->ysize = 0; if( !(out->coeff = IM_ARRAY( NULL, size, double )) ) { im_free_dmask( out ); return( NULL ); } (void) memset( (char *) out->coeff, 0, size * sizeof( double ) ); if( !(out->filename = im_strdup( NULL, filename )) ) { im_free_dmask( out ); return( NULL ); } out->xsize = xsize; out->ysize = ysize; return( out ); } /** * im_create_dmaskv: * @filename: set mask filename to this * @xsize: mask width * @ysize: mask height * @...: values to set for the mask * * Create a dmask and initialise it from the function parameter list. * * See also: im_create_dmask(). * * Returns: The newly-allocated mask. */ DOUBLEMASK * im_create_dmaskv( const char *filename, int xsize, int ysize, ... ) { va_list ap; DOUBLEMASK *out; int i; if( !(out = im_create_dmask( filename, xsize, ysize )) ) return( NULL ); va_start( ap, ysize ); for( i = 0; i < xsize * ysize; i++ ) out->coeff[i] = va_arg( ap, double ); va_end( ap ); return( out ); } /* Read a line from a file! */ static int get_line( FILE *fp, char *buf ) { if( !fgets( buf, MAX_LINE, fp ) ) { im_error( "read_mask", "%s", _( "unexpected EOF" ) ); return( -1 ); } return( 0 ); } /* width, height, optional scale, optional offset. */ static int read_header( FILE *fp, int *xs, int *ys, double *scale, double *offset ) { char buf[MAX_LINE]; char *p, *q; double v[4]; int i; /* Read the first line: should contain size and optional * scale + offset. */ if( get_line( fp, buf ) ) return( -1 ); /* Read as space separated doubles. \n is in the break list because * our line will (usually) have a trailing \n which we want to count * as whitespace. */ p = buf; for( i = 0, p = buf; i < 4 && (q = im_break_token( p, " \";,\t\n" )); i++, p = q ) v[i] = g_ascii_strtod( p, NULL ); if( (i != 2 && i != 4) || ceil( v[0] ) != v[0] || ceil( v[1] ) != v[1] || v[0] <= 0 || v[1] <= 0 ) { im_error( "read_header", "%s", _( "error reading matrix header" ) ); return( -1 ); } if( i == 4 && v[2] == 0 ) { im_error( "read_header", "%s", _( "scale should be non-zero" ) ); return( -1 ); } *xs = v[0]; *ys = v[1]; if( i == 2 ) { *scale = 1.0; *offset = 0.0; } else { *scale = v[2]; *offset = v[3]; } return( 0 ); } /** * im_read_dmask: * @filename: read matrix from this file * * Reads a matrix from a file. * * Matrix files have a simple format that's supposed to be easy to create with * a text editor or a spreadsheet. * * The first line has four numbers for width, height, scale and * offset (scale and offset may be omitted, in which case they default to 1.0 * and 0.0). Scale must be non-zero. Width and height must be positive * integers. The numbers are separated by any mixture of spaces, commas, * tabs and quotation marks ("). The scale and offset fields may be * floating-point, and must use '.' * as a decimal separator. * * Subsequent lines each hold one line of matrix data, with numbers again * separated by any mixture of spaces, commas, * tabs and quotation marks ("). The numbers may be floating-point, and must * use '.' * as a decimal separator. * * Extra characters at the ends of lines or at the end of the file are * ignored. * * See also: im_read_imask(), im_gauss_dmask(). * * Returns: the loaded mask on success, or NULL on error. */ DOUBLEMASK * im_read_dmask( const char *filename ) { FILE *fp; double sc, off; int xs, ys; DOUBLEMASK *out; int x, y, i; char buf[MAX_LINE]; if( !(fp = im__file_open_read( filename, NULL, TRUE )) ) return( NULL ); if( read_header( fp, &xs, &ys, &sc, &off ) ) { fclose( fp ); return( NULL ); } if( !(out = im_create_dmask( filename, xs, ys )) ) { fclose( fp ); return( NULL ); } out->scale = sc; out->offset = off; for( i = 0, y = 0; y < ys; y++ ) { char *p; if( get_line( fp, buf ) ) { im_free_dmask( out ); fclose( fp ); return( NULL ); } for( p = buf, x = 0; p && x < xs; x++, i++, p = im_break_token( p, " \t,\";" ) ) out->coeff[i] = g_ascii_strtod( p, NULL ); } fclose( fp ); return( out ); } /** * im_read_imask: * @filename: read matrix from this file * * Reads an integer matrix from a file. * * This function works exactly as im_read_dmask(), but the loaded matrix is * checked for 'int-ness'. All coefficients must be integers, and scale and * offset must be integers. * * See also: im_read_dmask(). * * Returns: the loaded mask on success, or NULL on error. */ INTMASK * im_read_imask( const char *filename ) { DOUBLEMASK *dmask; INTMASK *imask; int i; if( !(dmask = im_read_dmask( filename )) ) return( NULL ); if( ceil( dmask->scale ) != dmask->scale || ceil( dmask->offset ) != dmask->offset ) { im_error( "im_read_imask", "%s", _( "scale and offset should be int" ) ); im_free_dmask( dmask ); return( NULL ); } for( i = 0; i < dmask->xsize * dmask->ysize; i++ ) if( ceil( dmask->coeff[i] ) != dmask->coeff[i] ) { im_error( "im_read_imask", _( "ceofficient at " "position (%d, %d) is not int" ), i % dmask->xsize, i / dmask->xsize ); im_free_dmask( dmask ); return( NULL ); } if( !(imask = im_create_imask( filename, dmask->xsize, dmask->ysize )) ) { im_free_dmask( dmask ); return( NULL ); } imask->scale = dmask->scale; imask->offset = dmask->offset; for( i = 0; i < dmask->xsize * dmask->ysize; i++ ) imask->coeff[i] = dmask->coeff[i]; im_free_dmask( dmask ); return( imask ); } /** * im_scale_dmask: * @in: mask to scale * @filename: filename for returned mask * * Scale the dmask to make an imask with a maximum value of 20. * * See also: im_norm_dmask(). * * Returns: the converted mask, or NULL on error. */ INTMASK * im_scale_dmask( DOUBLEMASK *in, const char *filename ) { const int size = in->xsize * in->ysize; INTMASK *out; double maxval, dsum; int i; int isum; if( im_check_dmask( "im_scale_dmask", in ) || !(out = im_create_imask( filename, in->xsize, in->ysize )) ) return( NULL ); /* Find mask max. */ maxval = in->coeff[0]; for( i = 0; i < size; i++ ) if( in->coeff[i] > maxval ) maxval = in->coeff[i]; /* Copy and scale, setting max to 20. */ for( i = 0; i < size; i++ ) out->coeff[i] = IM_RINT( in->coeff[i] * 20.0 / maxval ); out->offset = in->offset; /* Set the scale to match the adjustment to max. */ isum = 0; dsum = 0.0; for( i = 0; i < size; i++ ) { isum += out->coeff[i]; dsum += in->coeff[i]; } if( dsum == in->scale ) out->scale = isum; else if( dsum == 0.0 ) out->scale = 1.0; else out->scale = IM_RINT( in->scale * isum / dsum ); return( out ); } /** * im_dmask2imask: * @in: mask to convert * @filename: filename for returned mask * * Make an imask from the dmask, rounding to nearest. * * See also: im_scale_dmask(). * * Returns: the converted mask, or NULL on error. */ INTMASK * im_dmask2imask( DOUBLEMASK *in, const char *filename ) { const int size = in->xsize * in->ysize; INTMASK *out; int i; if( im_check_dmask( "im_dmask2imask", in ) || !(out = im_create_imask( filename, in->xsize, in->ysize )) ) return( NULL ); for( i = 0; i < size; i++ ) out->coeff[i] = IM_RINT( in->coeff[i] ); out->offset = IM_RINT( in->offset ); out->scale = IM_RINT( in->scale ); return( out ); } /** * im_imask2dmask: * @in: mask to convert * @filename: filename for returned mask * * Make a dmask from the imask. * * See also: im_dmask2imask(). * * Returns: the converted mask, or NULL on error. */ DOUBLEMASK * im_imask2dmask( INTMASK *in, const char *filename ) { const int size = in->xsize * in->ysize; DOUBLEMASK *out; int i; if( im_check_imask( "im_imask2dmask", in ) || !(out = im_create_dmask( filename, in->xsize, in->ysize )) ) return( NULL ); for( i = 0; i < size; i++ ) out->coeff[i] = in->coeff[i]; out->offset = in->offset; out->scale = in->scale; return( out ); } /** * im_norm_dmask: * @mask: mask to scale * * Normalise the dmask. Apply the scale and offset to each element to make * a mask with scale 1, offset zero. * * See also: im_scale_dmask(). * * Returns: 0 on success, or -1 on error. */ void im_norm_dmask( DOUBLEMASK *mask ) { const int n = mask->xsize * mask->ysize; const double scale = (mask->scale == 0) ? 0 : (1.0 / mask->scale); int i; if( im_check_dmask( "im_norm_dmask", mask ) || (1.0 == scale && 0.0 == mask->offset) ) return; for( i = 0; i < n; i++ ) mask->coeff[i] = mask->coeff[i] * scale + mask->offset; mask->scale = 1.0; mask->offset = 0.0; } /** * im_dup_imask: * @in: mask to duplicate * @filename: filename to set for the new mask * * Duplicate an imask. * * See also: im_dup_dmask(). * * Returns: the mask copy, or NULL on error. */ INTMASK * im_dup_imask( INTMASK *in, const char *filename ) { INTMASK *out; int i; if( im_check_imask( "im_dup_imask", in ) || !(out = im_create_imask( filename, in->xsize, in->ysize )) ) return( NULL ); out->offset = in->offset; out->scale = in->scale; for( i = 0; i < in->xsize * in->ysize; i++ ) out->coeff[i] = in->coeff[i]; return( out ); } /** * im_dup_dmask: * @in: mask to duplicate * @filename: filename to set for the new mask * * Duplicate a dmask. * * See also: im_dup_imask(). * * Returns: the mask copy, or NULL on error. */ DOUBLEMASK * im_dup_dmask( DOUBLEMASK *in, const char *filename ) { DOUBLEMASK *out; int i; if( im_check_dmask( "im_dup_dmask", in ) || !(out = im_create_dmask( filename, in->xsize, in->ysize )) ) return( NULL ); out->offset = in->offset; out->scale = in->scale; for( i = 0; i < in->xsize * in->ysize; i++ ) out->coeff[i] = in->coeff[i]; return( out ); } /* Write to file. */ static int write_line( FILE *fp, const char *fmt, ... ) { va_list ap; va_start( ap, fmt ); if( !vfprintf( fp, fmt, ap ) ) { im_error( "write_mask", "%s", _( "write error, disc full?" ) ); return( -1 ); } va_end( ap ); return( 0 ); } static int write_double( FILE *fp, double d ) { char buf[G_ASCII_DTOSTR_BUF_SIZE]; fprintf( fp, "%s", g_ascii_dtostr( buf, sizeof( buf ), d ) ); return( 0 ); } /** * im_write_imask_name: * @in: mask to write * @filename: filename to write to * * Write an imask to a file. See im_read_dmask() for a description of the mask * file format. * * See also: im_write_imask(). * * Returns: 0 on success, or -1 on error. */ int im_write_imask_name( INTMASK *in, const char *filename ) { FILE *fp; int x, y, i; if( im_check_imask( "im_write_imask_name", in ) || !(fp = im__file_open_write( filename, TRUE )) ) return( -1 ); if( write_line( fp, "%d %d", in->xsize, in->ysize ) ) { fclose( fp ); return( -1 ); } if( in->scale != 1 || in->offset != 0 ) write_line( fp, " %d %d", in->scale, in->offset ); write_line( fp, "\n" ); for( i = 0, y = 0; y < in->ysize; y++ ) { for( x = 0; x < in->xsize; x++, i++ ) write_line( fp, "%d ", in->coeff[i] ); if( write_line( fp, "\n" ) ) { fclose( fp ); return( -1 ); } } fclose( fp ); return( 0 ); } /** * im_write_imask: * @in: mask to write * * Write an imask to a file. * * See also: im_write_imask_name(). * * Returns: 0 on success, or -1 on error. */ int im_write_imask( INTMASK *in ) { if( !in->filename ) { im_error( "im_write_imask", "%s", _( "filename not set" ) ); return( -1 ); } return( im_write_imask_name( in, in->filename ) ); } /** * im_write_dmask_name: * @in: mask to write * @filename: filename to write to * * Write a dmask to a file. See im_read_dmask() for a description of the mask * file format. * * See also: im_write_dmask(). * * Returns: 0 on success, or -1 on error. */ int im_write_dmask_name( DOUBLEMASK *in, const char *filename ) { FILE *fp; int x, y, i; if( im_check_dmask( "im_write_dmask_name", in ) || !(fp = im__file_open_write( filename, TRUE )) ) return( -1 ); if( write_line( fp, "%d %d", in->xsize, in->ysize ) ) { fclose( fp ); return( -1 ); } if( in->scale != 1.0 || in->offset != 0.0 ) { write_line( fp, " " ); write_double( fp, in->scale ); write_line( fp, " " ); write_double( fp, in->offset ); } write_line( fp, "\n" ); for( i = 0, y = 0; y < in->ysize; y++ ) { for( x = 0; x < in->xsize; x++, i++ ) { write_double( fp, in->coeff[i] ); write_line( fp, " " ); } if( write_line( fp, "\n" ) ) { fclose( fp ); return( -1 ); } } fclose( fp ); return( 0 ); } /** * im_write_dmask: * @in: mask to write * * Write a dmask to a file. See im_read_dmask() for a description of the mask * file format. * * See also: im_write_dmask_name(). * * Returns: 0 on success, or -1 on error. */ int im_write_dmask( DOUBLEMASK *in ) { if( !in->filename ) { im_error( "im_write_dmask", "%s", _( "filename not set" ) ); return( -1 ); } return( im_write_dmask_name( in, in->filename ) ); } /* Copy an imask into a matrix. Only used internally by matrix package for * invert. */ void im_copy_imask_matrix( INTMASK *mask, int **matrix ) { int x, y; int *p = mask->coeff; for( y = 0; y < mask->ysize; y++ ) for( x = 0; x < mask->xsize; x++ ) matrix[x][y] = *p++; } /* Copy a matrix into an imask. */ void im_copy_matrix_imask( int **matrix, INTMASK *mask ) { int x, y; int *p = mask->coeff; for( y = 0; y < mask->ysize; y++ ) for( x = 0; x < mask->xsize; x++ ) *p++ = matrix[x][y]; } /* Copy a dmask into a matrix. */ void im_copy_dmask_matrix( DOUBLEMASK *mask, double **matrix ) { int x, y; double *p = mask->coeff; for( y = 0; y < mask->ysize; y++ ) for( x = 0; x < mask->xsize; x++ ) matrix[x][y] = *p++; } /* Copy a matrix to a dmask. */ void im_copy_matrix_dmask( double **matrix, DOUBLEMASK *mask ) { int x, y; double *p = mask->coeff; for( y = 0; y < mask->ysize; y++ ) for( x = 0; x < mask->xsize; x++ ) *p++ = matrix[x][y]; } /** * im_print_imask: * @in: mask to print * * Print an imask to stdout. * * See also: im_print_dmask(). */ void im_print_imask( INTMASK *in ) { int i, j, k; printf( "%s: %d %d %d %d\n", in->filename, in->xsize, in->ysize, in->scale, in->offset ); for( k = 0, j = 0; j < in->ysize; j++ ) { for( i = 0; i < in->xsize; i++, k++ ) printf( "%d\t", in->coeff[k] ); printf( "\n" ); } } /** * im_print_dmask: * @in: mask to print * * Print a dmask to stdout. * * See also: im_print_imask(). */ void im_print_dmask( DOUBLEMASK *in ) { int i, j, k; printf( "%s: %d %d %f %f\n", in->filename, in->xsize, in->ysize, in->scale, in->offset ); for( k = 0, j = 0; j < in->ysize; j++ ) { for( i = 0; i < in->xsize; i++, k++ ) printf( "%f\t", in->coeff[k] ); printf( "\n" ); } } /** * im_local_dmask: * @out: image to make the mask local to * @mask: mask to local-ize * * @out takes ownership of @mask: when @out is closed, @mask will be closed * for you. If im_local_dmask() itself fails, the mask is also freed. * * See also: im_local_imask(). * * Returns: the mask, or NULL on error. */ DOUBLEMASK * im_local_dmask( VipsImage *out, DOUBLEMASK *mask ) { if( im_check_dmask( "im_local_dmask", mask ) ) return( NULL ); if( im_add_close_callback( out, (im_callback_fn) im_free_dmask, mask, NULL ) ) { im_free_dmask( mask ); return( NULL ); } return( mask ); } /** * im_local_imask: * @out: image to make the mask local to * @mask: mask to local-ize * * @out takes ownership of @mask: when @out is closed, @mask will be closed * for you. If im_local_imask() itself fails, the mask is also freed. * * See also: im_local_dmask(). * * Returns: the mask, or NULL on error. */ INTMASK * im_local_imask( VipsImage *out, INTMASK *mask ) { if( im_check_imask( "im_local_dmask", mask ) ) return( NULL ); if( im_add_close_callback( out, (im_callback_fn) im_free_imask, mask, NULL ) ) { im_free_imask( mask ); return( NULL ); } return( mask ); }