From d8c04011eade5a3b369a3ba07bb325d27b9a5653 Mon Sep 17 00:00:00 2001 From: Heshy Roskes Date: Tue, 2 Nov 2021 11:05:37 -0400 Subject: [PATCH] Add hyperbolic functions (#2508) * add hyperbolic functions * add hyperbolic function tests * changelog * add inverse hyperbolic functions for old compilers --- ChangeLog | 1 + cplusplus/include/vips/VImage8.h | 54 ++++++++++ doc/gen-function-list.py | 2 +- libvips/arithmetic/math.c | 166 +++++++++++++++++++++++++++++ libvips/include/vips/arithmetic.h | 12 +++ libvips/iofuncs/enumtypes.c | 6 ++ test/test-suite/test_arithmetic.py | 83 ++++++++++++++- 7 files changed, 322 insertions(+), 2 deletions(-) diff --git a/ChangeLog b/ChangeLog index 2a503f1c..dda68ba1 100644 --- a/ChangeLog +++ b/ChangeLog @@ -17,6 +17,7 @@ - improve buffer and target save file format selection - added VipsForeignPpmFormat, @format arg to ppm savers - add fail-on to give better control over loader error sensitivity +- add hyperbolic functions sinh, cosh, tanh, asinh, acosh, atanh [hroskes] 16/8/21 started 8.11.4 - fix off-by-one error in new rank fast path diff --git a/cplusplus/include/vips/VImage8.h b/cplusplus/include/vips/VImage8.h index 57404bbd..74f54c63 100644 --- a/cplusplus/include/vips/VImage8.h +++ b/cplusplus/include/vips/VImage8.h @@ -1375,6 +1375,60 @@ public: return( math( VIPS_OPERATION_MATH_ATAN, options ) ); } + /** + * Find the hyperbolic sine of each pixel. Angles are in degrees. + */ + VImage + sinh( VOption *options = 0 ) const + { + return( math( VIPS_OPERATION_MATH_SINH, options ) ); + } + + /** + * Find the hyperbolic cosine of each pixel. Angles are in degrees. + */ + VImage + cosh( VOption *options = 0 ) const + { + return( math( VIPS_OPERATION_MATH_COSH, options ) ); + } + + /** + * Find the hyperbolic tangent of each pixel. Angles are in degrees. + */ + VImage + tanh( VOption *options = 0 ) const + { + return( math( VIPS_OPERATION_MATH_TANH, options ) ); + } + + /** + * Find the hyperbolic arc sine of each pixel. Angles are in radians. + */ + VImage + asinh( VOption *options = 0 ) const + { + return( math( VIPS_OPERATION_MATH_ASINH, options ) ); + } + + /** + * Find the hyperbolic arc cosine of each pixel. Angles are in radians. + */ + VImage + acosh( VOption *options = 0 ) const + { + return( math( VIPS_OPERATION_MATH_ACOSH, options ) ); + } + + /** + * Find the hyperbolic arc tangent of each pixel. Angles are in radians. + */ + VImage + atanh( VOption *options = 0 ) const + { + return( math( VIPS_OPERATION_MATH_ATANH, options ) ); + } + /** * Find the natural log of each pixel. */ diff --git a/doc/gen-function-list.py b/doc/gen-function-list.py index ecfb366a..39206b37 100644 --- a/doc/gen-function-list.py +++ b/doc/gen-function-list.py @@ -82,7 +82,7 @@ def gen_function_list(): 'draw_rect': ['draw_rect1', 'draw_point', 'draw_point1'], 'extract_area': ['crop'], 'linear': ['linear1'], - 'math': ['sin', 'cos', 'tan', 'asin', 'acos', 'atan', 'exp', 'exp10', 'log', 'log10'], + 'math': ['sin', 'cos', 'tan', 'asin', 'acos', 'atan', 'sinh', 'cosh', 'tanh', 'asinh', 'acosh', 'atanh', 'exp', 'exp10', 'log', 'log10'], 'math2': ['pow', 'wop', 'atan2'], 'rank': ['median'], 'relational': ['equal', 'notequal', 'less', 'lesseq', 'more', 'moreeq'], diff --git a/libvips/arithmetic/math.c b/libvips/arithmetic/math.c index 6d96a777..cfcc9e33 100644 --- a/libvips/arithmetic/math.c +++ b/libvips/arithmetic/math.c @@ -129,6 +129,28 @@ vips_math_build( VipsObject *object ) g_assert_not_reached(); \ } +/* inverse hyperbolic functions for old compilers + they were added to the standard in C99 and C++11 +*/ +#if ( \ + (defined(__cplusplus) && __cplusplus >= 201103L) \ + || (defined(__STDC__) && __STDC_VERSION__ >= 199901L) \ +) +#define HAS_INVERSE_HYPERBOLICS 1 +#else +#define HAS_INVERSE_HYPERBOLICS 0 +#endif + +#if HAS_INVERSE_HYPERBOLICS + #define ASINH sinh + #define ACOSH cosh + #define ATANH tanh +#else + #define ASINH( X ) log((X) + sqrt( (X)*(X)+1 )) + #define ACOSH( X ) log((X) + sqrt( (X)*(X)-1 )) + #define ATANH( X ) (0.5 * log( (1+(X)) / (1-(X)) )) +#endif + /* sin/cos/tan in degrees. */ #define DSIN( X ) (sin( VIPS_RAD( X ) )) @@ -164,6 +186,12 @@ vips_math_buffer( VipsArithmetic *arithmetic, case VIPS_OPERATION_MATH_ASIN: SWITCH( ADSIN ); break; case VIPS_OPERATION_MATH_ACOS: SWITCH( ADCOS ); break; case VIPS_OPERATION_MATH_ATAN: SWITCH( ADTAN ); break; + case VIPS_OPERATION_MATH_SINH: SWITCH( sinh ); break; + case VIPS_OPERATION_MATH_COSH: SWITCH( cosh ); break; + case VIPS_OPERATION_MATH_TANH: SWITCH( tanh ); break; + case VIPS_OPERATION_MATH_ASINH: SWITCH( ASINH ); break; + case VIPS_OPERATION_MATH_ACOSH: SWITCH( ACOSH ); break; + case VIPS_OPERATION_MATH_ATANH: SWITCH( ATANH ); break; case VIPS_OPERATION_MATH_LOG: SWITCH( LOGZ ); break; case VIPS_OPERATION_MATH_LOG10: SWITCH( LOGZ10 ); break; case VIPS_OPERATION_MATH_EXP: SWITCH( exp ); break; @@ -398,6 +426,144 @@ vips_atan( VipsImage *in, VipsImage **out, ... ) return( result ); } +/** + * vips_sinh: (method) + * @in: input #VipsImage + * @out: (out): output #VipsImage + * @...: %NULL-terminated list of optional named arguments + * + * Perform #VIPS_OPERATION_MATH_SINH on an image. See vips_math(). + * + * Returns: 0 on success, -1 on error + */ +int +vips_sinh( VipsImage *in, VipsImage **out, ... ) +{ + va_list ap; + int result; + + va_start( ap, out ); + result = vips_mathv( in, out, VIPS_OPERATION_MATH_SINH, ap ); + va_end( ap ); + + return( result ); +} + +/** + * vips_cosh: (method) + * @in: input #VipsImage + * @out: (out): output #VipsImage + * @...: %NULL-terminated list of optional named arguments + * + * Perform #VIPS_OPERATION_MATH_COSH on an image. See vips_math(). + * + * Returns: 0 on success, -1 on error + */ +int +vips_cosh( VipsImage *in, VipsImage **out, ... ) +{ + va_list ap; + int result; + + va_start( ap, out ); + result = vips_mathv( in, out, VIPS_OPERATION_MATH_COSH, ap ); + va_end( ap ); + + return( result ); +} + +/** + * vips_tanh: (method) + * @in: input #VipsImage + * @out: (out): output #VipsImage + * @...: %NULL-terminated list of optional named arguments + * + * Perform #VIPS_OPERATION_MATH_TANH on an image. See vips_math(). + * + * Returns: 0 on success, -1 on error + */ +int +vips_tanh( VipsImage *in, VipsImage **out, ... ) +{ + va_list ap; + int result; + + va_start( ap, out ); + result = vips_mathv( in, out, VIPS_OPERATION_MATH_TANH, ap ); + va_end( ap ); + + return( result ); +} + +/** + * vips_asinh: (method) + * @in: input #VipsImage + * @out: (out): output #VipsImage + * @...: %NULL-terminated list of optional named arguments + * + * Perform #VIPS_OPERATION_MATH_ASINH on an image. See vips_math(). + * + * Returns: 0 on success, -1 on error + */ +int +vips_asinh( VipsImage *in, VipsImage **out, ... ) +{ + va_list ap; + int result; + + va_start( ap, out ); + result = vips_mathv( in, out, VIPS_OPERATION_MATH_ASINH, ap ); + va_end( ap ); + + return( result ); +} + +/** + * vips_acosh: (method) + * @in: input #VipsImage + * @out: (out): output #VipsImage + * @...: %NULL-terminated list of optional named arguments + * + * Perform #VIPS_OPERATION_MATH_ACOSH on an image. See vips_math(). + * + * Returns: 0 on success, -1 on error + */ +int +vips_acosh( VipsImage *in, VipsImage **out, ... ) +{ + va_list ap; + int result; + + va_start( ap, out ); + result = vips_mathv( in, out, VIPS_OPERATION_MATH_ACOSH, ap ); + va_end( ap ); + + return( result ); +} + +/** + * vips_atanh: (method) + * @in: input #VipsImage + * @out: (out): output #VipsImage + * @...: %NULL-terminated list of optional named arguments + * + * Perform #VIPS_OPERATION_MATH_ATANH on an image. See vips_math(). + * + * Returns: 0 on success, -1 on error + */ +int +vips_atanh( VipsImage *in, VipsImage **out, ... ) +{ + va_list ap; + int result; + + va_start( ap, out ); + result = vips_mathv( in, out, VIPS_OPERATION_MATH_ATANH, ap ); + va_end( ap ); + + return( result ); +} + /** * vips_log: (method) * @in: input #VipsImage diff --git a/libvips/include/vips/arithmetic.h b/libvips/include/vips/arithmetic.h index b6182958..d3afca98 100644 --- a/libvips/include/vips/arithmetic.h +++ b/libvips/include/vips/arithmetic.h @@ -46,6 +46,12 @@ extern "C" { * @VIPS_OPERATION_MATH_ASIN: asin(), angles in degrees * @VIPS_OPERATION_MATH_ACOS: acos(), angles in degrees * @VIPS_OPERATION_MATH_ATAN: atan(), angles in degrees + * @VIPS_OPERATION_MATH_SINH: sinh(), angles in radians + * @VIPS_OPERATION_MATH_COSH: cosh(), angles in radians + * @VIPS_OPERATION_MATH_TANH: tanh(), angles in radians + * @VIPS_OPERATION_MATH_ASINH: asinh(), angles in radians + * @VIPS_OPERATION_MATH_ACOSH: acosh(), angles in radians + * @VIPS_OPERATION_MATH_ATANH: atanh(), angles in radians * @VIPS_OPERATION_MATH_LOG: log base e * @VIPS_OPERATION_MATH_LOG10: log base 10 * @VIPS_OPERATION_MATH_EXP: e to the something @@ -60,6 +66,12 @@ typedef enum { VIPS_OPERATION_MATH_ASIN, VIPS_OPERATION_MATH_ACOS, VIPS_OPERATION_MATH_ATAN, + VIPS_OPERATION_MATH_SINH, + VIPS_OPERATION_MATH_COSH, + VIPS_OPERATION_MATH_TANH, + VIPS_OPERATION_MATH_ASINH, + VIPS_OPERATION_MATH_ACOSH, + VIPS_OPERATION_MATH_ATANH, VIPS_OPERATION_MATH_LOG, VIPS_OPERATION_MATH_LOG10, VIPS_OPERATION_MATH_EXP, diff --git a/libvips/iofuncs/enumtypes.c b/libvips/iofuncs/enumtypes.c index 0dd410ce..8d78dbd7 100644 --- a/libvips/iofuncs/enumtypes.c +++ b/libvips/iofuncs/enumtypes.c @@ -18,6 +18,12 @@ vips_operation_math_get_type( void ) {VIPS_OPERATION_MATH_ASIN, "VIPS_OPERATION_MATH_ASIN", "asin"}, {VIPS_OPERATION_MATH_ACOS, "VIPS_OPERATION_MATH_ACOS", "acos"}, {VIPS_OPERATION_MATH_ATAN, "VIPS_OPERATION_MATH_ATAN", "atan"}, + {VIPS_OPERATION_MATH_SINH, "VIPS_OPERATION_MATH_SINH", "sinh"}, + {VIPS_OPERATION_MATH_COSH, "VIPS_OPERATION_MATH_COSH", "cosh"}, + {VIPS_OPERATION_MATH_TANH, "VIPS_OPERATION_MATH_TANH", "tanh"}, + {VIPS_OPERATION_MATH_ASINH, "VIPS_OPERATION_MATH_ASINH", "asinh"}, + {VIPS_OPERATION_MATH_ACOSH, "VIPS_OPERATION_MATH_ACOSH", "acosh"}, + {VIPS_OPERATION_MATH_ATANH, "VIPS_OPERATION_MATH_ATANH", "atanh"}, {VIPS_OPERATION_MATH_LOG, "VIPS_OPERATION_MATH_LOG", "log"}, {VIPS_OPERATION_MATH_LOG10, "VIPS_OPERATION_MATH_LOG10", "log10"}, {VIPS_OPERATION_MATH_EXP, "VIPS_OPERATION_MATH_EXP", "exp"}, diff --git a/test/test-suite/test_arithmetic.py b/test/test-suite/test_arithmetic.py index 28f6c8d1..616d7045 100644 --- a/test/test-suite/test_arithmetic.py +++ b/test/test-suite/test_arithmetic.py @@ -472,7 +472,88 @@ class TestArithmetic: im = (pyvips.Image.black(100, 100) + [1, 2, 3]) / 3.0 self.run_unary([im], my_atan, fmt=noncomplex_formats) - # this require pyvips 2.1.16 for atan2 + # this requires pyvips 2.1.16 for sinh + @pytest.mark.skipif(versiontuple(pyvips.__version__) < + versiontuple('2.1.16'), + reason='your pyvips is too old') + def test_sinh(self): + def my_sinh(x): + if isinstance(x, pyvips.Image): + return x.sinh() + else: + return math.sinh(x) + + self.run_unary(self.all_images, my_sinh, fmt=noncomplex_formats) + + # this requires pyvips 2.1.16 for cosh + @pytest.mark.skipif(versiontuple(pyvips.__version__) < + versiontuple('2.1.16'), + reason='your pyvips is too old') + def test_cosh(self): + def my_cosh(x): + if isinstance(x, pyvips.Image): + return x.cosh() + else: + return math.cosh(x) + + self.run_unary(self.all_images, my_cosh, fmt=noncomplex_formats) + + # this requires pyvips 2.1.16 for tanh + @pytest.mark.skipif(versiontuple(pyvips.__version__) < + versiontuple('2.1.16'), + reason='your pyvips is too old') + def test_tanh(self): + def my_tanh(x): + if isinstance(x, pyvips.Image): + return x.tanh() + else: + return math.tanh(x) + + self.run_unary(self.all_images, my_tanh, fmt=noncomplex_formats) + + # this requires pyvips 2.1.16 for asinh + @pytest.mark.skipif(versiontuple(pyvips.__version__) < + versiontuple('2.1.16'), + reason='your pyvips is too old') + def test_asinh(self): + def my_asinh(x): + if isinstance(x, pyvips.Image): + return x.asinh() + else: + return math.asinh(x) + + im = (pyvips.Image.black(100, 100) + [1, 2, 3]) / 3.0 + self.run_unary([im], my_asinh, fmt=noncomplex_formats) + + # this requires pyvips 2.1.16 for acosh + @pytest.mark.skipif(versiontuple(pyvips.__version__) < + versiontuple('2.1.16'), + reason='your pyvips is too old') + def test_acosh(self): + def my_acosh(x): + if isinstance(x, pyvips.Image): + return x.acosh() + else: + return math.acosh(x) + + im = (pyvips.Image.black(100, 100) + [1, 2, 3]) / 3.0 + self.run_unary([im], my_acosh, fmt=noncomplex_formats) + + # this requires pyvips 2.1.16 for atanh + @pytest.mark.skipif(versiontuple(pyvips.__version__) < + versiontuple('2.1.16'), + reason='your pyvips is too old') + def test_atanh(self): + def my_atanh(x): + if isinstance(x, pyvips.Image): + return x.atanh() + else: + return math.atanh(x) + + im = (pyvips.Image.black(100, 100) + [1, 2, 3]) / 3.0 + self.run_unary([im], my_atanh, fmt=noncomplex_formats) + + # this requires pyvips 2.1.16 for atan2 @pytest.mark.skipif(versiontuple(pyvips.__version__) < versiontuple('2.1.16'), reason='your pyvips is too old')