improve sharpen for screen work
it was a bit granular (since it was designed for offset work), make sharpen easier to adjust for smaller effects - new 'sigma' parameter - greater parameter range - more self-tests
This commit is contained in:
parent
8911820d9e
commit
c1f1a7c1b3
@ -7,6 +7,9 @@
|
||||
- added @page param to magickload
|
||||
- matload is more specific (thanks bithive)
|
||||
- lower mem use for progressive jpg decode
|
||||
- sharpen has a new @sigma param, @radius is deprecated
|
||||
- sharpen allows a much greater range of parameters
|
||||
- better handling of deprecated args in python
|
||||
|
||||
27/1/16 started 8.2.3
|
||||
- fix a crash with SPARC byte-order labq vips images
|
||||
|
14
TODO
14
TODO
@ -1,3 +1,17 @@
|
||||
try
|
||||
|
||||
$ vips cast babe.jpg x.v ushort
|
||||
$ vips colourspace x.v x2.v labs
|
||||
$ vips avg x2.v
|
||||
27.403674
|
||||
|
||||
$ vips cast babe.jpg x.v short
|
||||
$ vips colourspace x.v x2.v labs
|
||||
$ vips avg x2.v
|
||||
10356.775608
|
||||
|
||||
how odd! cause of break in test suite
|
||||
|
||||
- could load pdf thumbnails?
|
||||
|
||||
- new vips_reduce:
|
||||
|
@ -1,12 +1,5 @@
|
||||
/* Cored sharpen of LABQ image.
|
||||
*
|
||||
* Usage:
|
||||
*
|
||||
* int im_sharpen( IMAGE *in, IMAGE *out,
|
||||
* int mask_size,
|
||||
* int x1, int x2,
|
||||
* double m1, double m2 )
|
||||
*
|
||||
* Returns 0 on success and -1 on error
|
||||
*
|
||||
* Copyright: 1995 A. Abbood
|
||||
@ -42,6 +35,9 @@
|
||||
* 13/11/13
|
||||
* - redo as a class
|
||||
* - does any type, any number of bands
|
||||
* 24/2/16
|
||||
* - swap "radius" for "sigma", allows finer control
|
||||
* - allow a much greater range of parameters
|
||||
*/
|
||||
|
||||
/*
|
||||
@ -92,29 +88,21 @@ typedef struct _VipsSharpen {
|
||||
VipsImage *in;
|
||||
VipsImage *out;
|
||||
|
||||
int radius;
|
||||
double sigma;
|
||||
double x1;
|
||||
double y2;
|
||||
double y3;
|
||||
double m1;
|
||||
double m2;
|
||||
|
||||
double x2;
|
||||
double x3;
|
||||
|
||||
/* Parameters scaled up to int.
|
||||
*
|
||||
* We need indexes in the range [-x3,x2], so add x3 to
|
||||
* indexes before starting to index table.
|
||||
*/
|
||||
int ix1;
|
||||
int ix2;
|
||||
int ix3;
|
||||
|
||||
/* The lut we build.
|
||||
*/
|
||||
int *lut;
|
||||
|
||||
/* We used to have a radius control.
|
||||
*/
|
||||
int radius;
|
||||
|
||||
} VipsSharpen;
|
||||
|
||||
typedef VipsOperationClass VipsSharpenClass;
|
||||
@ -128,8 +116,6 @@ vips_sharpen_generate( VipsRegion *or,
|
||||
VipsRegion **in = (VipsRegion **) vseq;
|
||||
VipsSharpen *sharpen = (VipsSharpen *) b;
|
||||
VipsRect *r = &or->valid;
|
||||
int ix3 = sharpen->ix3;
|
||||
int range = sharpen->ix2 + sharpen->ix3;
|
||||
int *lut = sharpen->lut;
|
||||
|
||||
int x, y;
|
||||
@ -141,43 +127,33 @@ vips_sharpen_generate( VipsRegion *or,
|
||||
VIPS_GATE_START( "vips_sharpen_generate: work" );
|
||||
|
||||
for( y = 0; y < r->height; y++ ) {
|
||||
short *p1 = (short *)
|
||||
short *p1 = (short * restrict )
|
||||
VIPS_REGION_ADDR( in[0], r->left, r->top + y );
|
||||
short *p2 = (short *)
|
||||
short *p2 = (short * restrict )
|
||||
VIPS_REGION_ADDR( in[1], r->left, r->top + y );
|
||||
short *q = (short *)
|
||||
short *q = (short * restrict )
|
||||
VIPS_REGION_ADDR( or, r->left, r->top + y );
|
||||
|
||||
for( x = 0; x < r->width; x++ ) {
|
||||
int v1 = p1[x];
|
||||
int v2 = p2[x];
|
||||
|
||||
/* v2 is the area average. If this is zero, then we
|
||||
* pass the original image through unaltered.
|
||||
/* Our LUT is -32768 - 32767. For the v1, v2
|
||||
* difference to be in this range, both must be 0 -
|
||||
* 32767.
|
||||
*/
|
||||
if( v2 == 0 )
|
||||
q[x] = v1;
|
||||
else {
|
||||
/* Find difference. Offset by x3 to get the
|
||||
* expected range of values.
|
||||
*/
|
||||
int s1 = ix3 + (v1 - v2);
|
||||
int s2;
|
||||
int d = (v1 & 0x8fff) - (v2 & 0x8fff);
|
||||
|
||||
if( s1 < 0 )
|
||||
s1 = 0;
|
||||
else if( s1 > range )
|
||||
s1 = range;
|
||||
int out;
|
||||
|
||||
s2 = v1 + lut[s1];
|
||||
out = v1 + lut[d + 32768];
|
||||
|
||||
if( s2 < 0 )
|
||||
s2 = 0;
|
||||
else if( s2 > 32767 )
|
||||
s2 = 32767;
|
||||
if( out < 0 )
|
||||
out = 0;
|
||||
if( out > 32767 )
|
||||
out = 32767;
|
||||
|
||||
q[x] = s2;
|
||||
}
|
||||
q[x] = out;
|
||||
}
|
||||
}
|
||||
|
||||
@ -195,7 +171,6 @@ vips_sharpen_build( VipsObject *object )
|
||||
VipsImage **args = (VipsImage **) vips_object_local_array( object, 2 );
|
||||
|
||||
VipsImage *in;
|
||||
int ix1, ix2, ix3;
|
||||
int i;
|
||||
|
||||
VIPS_GATE_START( "vips_sharpen_build: build" );
|
||||
@ -203,12 +178,12 @@ vips_sharpen_build( VipsObject *object )
|
||||
if( VIPS_OBJECT_CLASS( vips_sharpen_parent_class )->build( object ) )
|
||||
return( -1 );
|
||||
|
||||
/* Turn y parameters into xs.
|
||||
/* We used to have a radius control. If that's set but sigma isn't,
|
||||
* use it to set a reasonable value for sigma.
|
||||
*/
|
||||
sharpen->x2 = (sharpen->y2 -
|
||||
sharpen->x1 * (sharpen->m1 - sharpen->m2)) / sharpen->m2;
|
||||
sharpen->x3 = (sharpen->y3 -
|
||||
sharpen->x1 * (sharpen->m1 - sharpen->m2)) / sharpen->m2;
|
||||
if( !vips_object_argument_isset( object, "sigma" ) &&
|
||||
vips_object_argument_isset( object, "radius" ) )
|
||||
sharpen->sigma = 1 + sharpen->radius / 2;
|
||||
|
||||
in = sharpen->in;
|
||||
|
||||
@ -221,20 +196,10 @@ vips_sharpen_build( VipsObject *object )
|
||||
vips_check_format( class->nickname, in, VIPS_FORMAT_SHORT ) )
|
||||
return( -1 );
|
||||
|
||||
if( sharpen->x1 < 0 || sharpen->x1 > 99 ||
|
||||
sharpen->x2 < 0 || sharpen->x2 > 99 ||
|
||||
sharpen->x1 > sharpen->x2 ||
|
||||
sharpen->x3 < 0 || sharpen->x3 > 99 ||
|
||||
sharpen->x1 > sharpen->x3 ) {
|
||||
vips_error( class->nickname,
|
||||
"%s", _( "parameters out of range" ) );
|
||||
return( -1 );
|
||||
}
|
||||
|
||||
/* Stop at 20% of max ... bit mean, but means mask radius is roughly
|
||||
* right. We always sharpen a short, so no point using a float mask.
|
||||
/* Stop at 20% of max ... a bit mean. We always sharpen a short,
|
||||
* so there's no point using a float mask.
|
||||
*/
|
||||
if( vips_gaussmat( &t[1], 1 + sharpen->radius / 2, 0.2,
|
||||
if( vips_gaussmat( &t[1], sharpen->sigma, 0.2,
|
||||
"separable", TRUE,
|
||||
"precision", VIPS_PRECISION_INTEGER,
|
||||
NULL ) )
|
||||
@ -245,25 +210,51 @@ vips_sharpen_build( VipsObject *object )
|
||||
vips_matrixprint( t[1], NULL );
|
||||
#endif /*DEBUG*/
|
||||
|
||||
/* Build the int lut.
|
||||
/* Index with the signed difference between two 0 - 32767 images.
|
||||
*/
|
||||
sharpen->ix1 = ix1 = sharpen->x1 * 327.67;
|
||||
sharpen->ix2 = ix2 = sharpen->x2 * 327.67;
|
||||
sharpen->ix3 = ix3 = sharpen->x3 * 327.67;
|
||||
|
||||
if( !(sharpen->lut = VIPS_ARRAY( object, ix2 + ix3 + 1, int )) )
|
||||
if( !(sharpen->lut = VIPS_ARRAY( object, 65536, int )) )
|
||||
return( -1 );
|
||||
|
||||
for( i = 0; i < ix1; i++ ) {
|
||||
sharpen->lut[ix3 + i] = i * sharpen->m1;
|
||||
sharpen->lut[ix3 - i] = -i * sharpen->m1;
|
||||
for( i = 0; i < 65536; i++ ) {
|
||||
/* Rescale to +/- 100.
|
||||
*/
|
||||
double v = (i - 32767) / 327.67;
|
||||
double y;
|
||||
|
||||
if( v < -sharpen->x1 )
|
||||
/* Left of -x1.
|
||||
*/
|
||||
y = (v - sharpen->x1) * sharpen->m2 +
|
||||
-sharpen->x1 * sharpen->m1;
|
||||
else if( v < sharpen->x1 )
|
||||
/* Centre section.
|
||||
*/
|
||||
y = v * sharpen->m1;
|
||||
else
|
||||
/* Right of x1.
|
||||
*/
|
||||
y = (v - sharpen->x1) * sharpen->m2 +
|
||||
sharpen->x1 * sharpen->m1;
|
||||
|
||||
if( y < -sharpen->y3 )
|
||||
y = -sharpen->y3;
|
||||
if( y > sharpen->y2 )
|
||||
y = sharpen->y2;
|
||||
|
||||
sharpen->lut[i] = VIPS_RINT( y * 327.67 );
|
||||
}
|
||||
for( i = ix1; i <= ix2; i++ )
|
||||
sharpen->lut[ix3 + i] =
|
||||
ix1 * sharpen->m1 + (i - ix1) * sharpen->m2;
|
||||
for( i = ix1; i <= ix3; i++ )
|
||||
sharpen->lut[ix3 - i] =
|
||||
-(ix1 * sharpen->m1 + (i - ix1) * sharpen->m2);
|
||||
|
||||
#ifdef DEBUG
|
||||
{
|
||||
VipsImage *mat;
|
||||
|
||||
mat = vips_image_new_matrix( 65536, 1 );
|
||||
for( i = 0; i < 65536; i++ )
|
||||
*VIPS_MATRIX( mat, i, 0 ) = sharpen->lut[i];
|
||||
vips_image_write_to_file( mat, "x.v", NULL );
|
||||
g_object_unref( mat );
|
||||
}
|
||||
#endif /*DEBUG*/
|
||||
|
||||
/* Extract L and the rest, convolve L.
|
||||
*/
|
||||
@ -326,33 +317,33 @@ vips_sharpen_class_init( VipsSharpenClass *class )
|
||||
VIPS_ARGUMENT_REQUIRED_OUTPUT,
|
||||
G_STRUCT_OFFSET( VipsSharpen, out ) );
|
||||
|
||||
VIPS_ARG_INT( class, "radius", 3,
|
||||
_( "Radius" ),
|
||||
_( "Mask radius" ),
|
||||
VIPS_ARG_DOUBLE( class, "sigma", 3,
|
||||
_( "Sigma" ),
|
||||
_( "Sigma of Gaussian" ),
|
||||
VIPS_ARGUMENT_OPTIONAL_INPUT,
|
||||
G_STRUCT_OFFSET( VipsSharpen, radius ),
|
||||
1, 1000000, 3 );
|
||||
G_STRUCT_OFFSET( VipsSharpen, sigma ),
|
||||
0.000001, 10000.0, 1.0 );
|
||||
|
||||
VIPS_ARG_DOUBLE( class, "x1", 5,
|
||||
_( "x1" ),
|
||||
_( "Flat/jaggy threshold" ),
|
||||
VIPS_ARGUMENT_OPTIONAL_INPUT,
|
||||
G_STRUCT_OFFSET( VipsSharpen, x1 ),
|
||||
1, 1000000, 1.5 );
|
||||
0, 1000000, 1.5 );
|
||||
|
||||
VIPS_ARG_DOUBLE( class, "y2", 6,
|
||||
_( "y2" ),
|
||||
_( "Maximum brightening" ),
|
||||
VIPS_ARGUMENT_OPTIONAL_INPUT,
|
||||
G_STRUCT_OFFSET( VipsSharpen, y2 ),
|
||||
1, 1000000, 20 );
|
||||
0, 1000000, 20 );
|
||||
|
||||
VIPS_ARG_DOUBLE( class, "y3", 7,
|
||||
_( "y3" ),
|
||||
_( "Maximum darkening" ),
|
||||
VIPS_ARGUMENT_OPTIONAL_INPUT,
|
||||
G_STRUCT_OFFSET( VipsSharpen, y3 ),
|
||||
1, 1000000, 50 );
|
||||
0, 1000000, 50 );
|
||||
|
||||
VIPS_ARG_DOUBLE( class, "m1", 8,
|
||||
_( "m1" ),
|
||||
@ -368,12 +359,21 @@ vips_sharpen_class_init( VipsSharpenClass *class )
|
||||
G_STRUCT_OFFSET( VipsSharpen, m2 ),
|
||||
0, 1000000, 2 );
|
||||
|
||||
/* We used to have a radius control.
|
||||
*/
|
||||
VIPS_ARG_INT( class, "radius", 3,
|
||||
_( "Radius" ),
|
||||
_( "radius of Gaussian" ),
|
||||
VIPS_ARGUMENT_OPTIONAL_INPUT | VIPS_ARGUMENT_DEPRECATED,
|
||||
G_STRUCT_OFFSET( VipsSharpen, radius ),
|
||||
1, 100, 1 );
|
||||
|
||||
}
|
||||
|
||||
static void
|
||||
vips_sharpen_init( VipsSharpen *sharpen )
|
||||
{
|
||||
sharpen->radius = 3;
|
||||
sharpen->sigma = 1.0;
|
||||
sharpen->x1 = 1.5;
|
||||
sharpen->y2 = 20;
|
||||
sharpen->y3 = 50;
|
||||
@ -389,7 +389,7 @@ vips_sharpen_init( VipsSharpen *sharpen )
|
||||
*
|
||||
* Optional arguments:
|
||||
*
|
||||
* @radius: how large a mask to use
|
||||
* @sigma: sigma of gaussian
|
||||
* @x1: flat/jaggy threshold
|
||||
* @y2: maximum amount of brightening
|
||||
* @y3: maximum amount of darkening
|
||||
@ -429,7 +429,7 @@ vips_sharpen_init( VipsSharpen *sharpen )
|
||||
* For printing, we recommend the following settings (the defaults):
|
||||
*
|
||||
* |[
|
||||
* radius == 3
|
||||
* sigma == 1.0
|
||||
* x1 == 1.5
|
||||
* y2 == 20 (don't brighten by more than 20 L*)
|
||||
* y3 == 50 (can darken by up to 50 L*)
|
||||
@ -441,10 +441,10 @@ vips_sharpen_init( VipsSharpen *sharpen )
|
||||
* If you want more or less sharpening, we suggest you just change the m1
|
||||
* and m2 parameters.
|
||||
*
|
||||
* The @radius parameter changes the width of the fringe and can be
|
||||
* The @sigma parameter changes the width of the fringe and can be
|
||||
* adjusted according to the output printing resolution. As an approximate
|
||||
* guideline, use 1 for 4 pixels/mm (CRT display resolution), 2 for 8
|
||||
* pixels/mm, 3 for 12 pixels/mm and 4 for 16 pixels/mm (300 dpi == 12
|
||||
* guideline, use 0.5 for 4 pixels/mm (CRT display resolution),
|
||||
* 1.0 for 12 pixels/mm and 1.5 for 16 pixels/mm (300 dpi == 12
|
||||
* pixels/mm). These figures refer to the image raster, not the half-tone
|
||||
* resolution.
|
||||
*
|
||||
|
@ -2518,7 +2518,7 @@ im_sharpen( IMAGE *in, IMAGE *out,
|
||||
* upon this behaviour.
|
||||
*/
|
||||
if( vips_call( "sharpen", in, &t[0],
|
||||
"radius", mask_size / 2,
|
||||
"sigma", mask_size / 4.0,
|
||||
"x1", x1,
|
||||
"y2", y2,
|
||||
"y3", y3,
|
||||
|
@ -218,15 +218,28 @@ class TestConvolution(unittest.TestCase):
|
||||
def test_sharpen(self):
|
||||
for im in self.all_images:
|
||||
for fmt in noncomplex_formats:
|
||||
for radius in range(1, 7):
|
||||
# old vipses used "radius", check that that still works
|
||||
sharp = im.sharpen(radius = 5)
|
||||
|
||||
for sigma in [0.5, 1, 1.5, 2]:
|
||||
im = im.cast(fmt)
|
||||
if im.bands == 3:
|
||||
im = im.copy(interpretation = Vips.Interpretation.SRGB)
|
||||
sharp = im.sharpen(radius = radius)
|
||||
elif im.bands == 1:
|
||||
im = im.copy(interpretation = Vips.Interpretation.B_W)
|
||||
sharp = im.sharpen(sigma = sigma)
|
||||
|
||||
# hard to test much more than this
|
||||
self.assertEqual(im.width, sharp.width)
|
||||
self.assertEqual(im.height, sharp.height)
|
||||
|
||||
# if m1 and m2 are zero, sharpen should do nothing
|
||||
sharp = im.sharpen(sigma = sigma, m1 = 0, m2 = 0)
|
||||
sharp = sharp.colourspace(im.interpretation)
|
||||
#print("testing sig = %g" % sigma)
|
||||
#print("testing fmt = %s" % fmt)
|
||||
#print("max diff = %g" % (im - sharp).abs().max())
|
||||
self.assertEqual((im - sharp).abs().max(), 0)
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
Loading…
Reference in New Issue
Block a user