From e84b97038ff85bcbc4c86b777c1810030ae51299 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Thu, 4 Jun 2015 16:02:41 +0100 Subject: [PATCH] add mono image -> many band column or row ... handy for loading LUT images from CSV files --- TODO | 11 ++-- libvips/conversion/copy.c | 127 ++++++++++++++++++++++++++++++-------- test/test_conversion.py | 32 +++++++--- 3 files changed, 130 insertions(+), 40 deletions(-) diff --git a/TODO b/TODO index 2368ed26..a08b55f2 100644 --- a/TODO +++ b/TODO @@ -1,14 +1,11 @@ - try: - $ vips copy y.v x.v --width 1 --bands 3 - VipsRegion: images do not match in pixel size + $ vips csvload ~/Desktop/NDVI_VGYRM-lut.txt x.v --skip 1 + $ vips copy x.v x2.v --width 256 --height 1 --bands 4 + Segmentation fault (core dumped) - copy will make 1xN or Nx1 M-band images into MxN one-band images, but it - won't go the other way + see commented-out test in test_conversion.py - - - - does ruby need to unpack RefString as well? what about C++? diff --git a/libvips/conversion/copy.c b/libvips/conversion/copy.c index 4aff2a8a..7b946e86 100644 --- a/libvips/conversion/copy.c +++ b/libvips/conversion/copy.c @@ -49,6 +49,8 @@ * - use glib byteswap macros * 15/5/15 * - support bands -> width conversion + * 4/6/15 + * - support width -> bands conversion */ /* @@ -196,12 +198,14 @@ vips_copy_unbandize_gen( VipsRegion *or, { VipsRegion *ir = (VipsRegion *) seq; VipsImage *in = ir->im; + VipsImage *out = or->im; VipsRect *r = &or->valid; VipsCopy *copy = (VipsCopy *) b; SwapFn swap = vips_copy_swap_fn[copy->in->BandFmt]; + int sze = VIPS_IMAGE_SIZEOF_ELEMENT( in ); VipsRect need; - int y; + int x, y; /* Ask for input we need. */ @@ -220,33 +224,97 @@ vips_copy_unbandize_gen( VipsRegion *or, if( vips_region_prepare( ir, &need ) ) return( -1 ); + /* We copy 1 pixel at a time. A vertical input image won't be + * guaranteed to have continuous data. + */ for( y = 0; y < r->height; y++ ) { - VipsPel *p; - VipsPel *q; + for( x = 0; x < r->width; x++ ) { + VipsPel *p; + VipsPel *q; - if( in->Xsize == 1 ) { - p = r->left * VIPS_IMAGE_SIZEOF_ELEMENT( in ) + - VIPS_REGION_ADDR( ir, 0, r->top + y ); - } - else { - p = r->left * VIPS_IMAGE_SIZEOF_ELEMENT( in ) + - VIPS_REGION_ADDR( ir, r->top + y, 0 ); - } - q = VIPS_REGION_ADDR( or, r->left, r->top + y ); + if( in->Xsize == 1 ) + p = VIPS_REGION_ADDR( ir, 0, r->top + y ) + + (r->left + x) * sze; + else + p = VIPS_REGION_ADDR( ir, r->top + y, 0 ) + + (r->left + x) * sze; + q = VIPS_REGION_ADDR( or, r->left + x, r->top + y ); - if( copy->swap && - swap ) { - swap( p, q, r->width, copy->in ); + if( copy->swap && + swap ) + swap( p, q, 1, out ); + else + memcpy( q, p, sze ); + } + } + + return( 0 ); +} + +/* Copy, turning the x axis into bands, the inverse of the above. Useful for + * turning CSV files into RGB LUTs, for example. + * + * output has bands == input width, one of width or height 1. + */ +static int +vips_copy_bandize_gen( VipsRegion *or, + void *seq, void *a, void *b, gboolean *stop ) +{ + VipsRegion *ir = (VipsRegion *) seq; + VipsImage *in = ir->im; + VipsImage *out = or->im; + VipsRect *r = &or->valid; + VipsCopy *copy = (VipsCopy *) b; + SwapFn swap = vips_copy_swap_fn[copy->in->BandFmt]; + int sze = VIPS_IMAGE_SIZEOF_ELEMENT( in ); + + VipsRect need; + int x, y; + + /* Ask for input we need. + */ + if( out->Xsize == 1 ) { + need.left = 0; + need.top = r->top; + need.width = in->Xsize; + need.height = r->height; + } + else { + need.left = 0; + need.top = r->left; + need.width = in->Xsize; + need.height = r->width; + } + if( vips_region_prepare( ir, &need ) ) + return( -1 ); + + /* We have to copy 1 pixel at a time. Each scanline in our input + * becomes a pixel in the output. Scanlines are not guaranteed to be + * continuous after vips_region_prepare(), they may be a window on a + * larger image. + */ + + for( y = 0; y < r->height; y++ ) { + for( x = 0; x < r->width; x++ ) { + VipsPel *p; + VipsPel *q; + + if( out->Xsize == 1 ) { + p = VIPS_REGION_ADDR( ir, 0, r->top + y ); + q = VIPS_REGION_ADDR( or, 0, r->top + y ); + } + else { + p = VIPS_REGION_ADDR( ir, 0, r->left + x ); + q = VIPS_REGION_ADDR( or, 0, r->left + x ); + } + + if( copy->swap && + swap ) + swap( p, q, 1, out ); + else + memcpy( q, p, out->Bands * sze ); } - else - /* We can't use vips_region_region(), it doesn't do - * coordinate transforms. If we want to avoid the - * memcpy() we'd need to add another vips_region_ - * function. - */ - memcpy( q, p, - r->width * VIPS_IMAGE_SIZEOF_ELEMENT( in ) ); } return( 0 ); @@ -391,9 +459,18 @@ vips_copy_build( VipsObject *object ) conversion->out->Bands == 1 && conversion->out->Xsize == copy_of_fields.Bands && conversion->out->Ysize == VIPS_MAX( - copy_of_fields.Xsize, copy_of_fields.Ysize ) ) { + copy_of_fields.Xsize, copy_of_fields.Ysize ) ) copy_generate_fn = vips_copy_unbandize_gen; - } + + /* And the inverse: change a MxN one-band image into a 1xN or Nx1 + * M-band image. That is, squash M into bands. + */ + if( (conversion->out->Xsize == 1 || conversion->out->Ysize == 1) && + conversion->out->Bands == copy_of_fields.Xsize && + copy_of_fields.Bands == 1 && + copy_of_fields.Ysize == VIPS_MAX( + conversion->out->Xsize, conversion->out->Ysize ) ) + copy_generate_fn = vips_copy_bandize_gen; if( vips_image_generate( conversion->out, vips_start_one, copy_generate_fn, vips_stop_one, diff --git a/test/test_conversion.py b/test/test_conversion.py index fd0c7859..79013c4d 100755 --- a/test/test_conversion.py +++ b/test/test_conversion.py @@ -224,16 +224,32 @@ class TestConversion(unittest.TestCase): self.assertEqual(x.xoffset, 42) x = self.colour.copy(yoffset = 42) self.assertEqual(x.yoffset, 42) - x = self.colour.copy(bands = 1) - self.assertEqual(x.bands, 1) - x = self.colour.copy(format = Vips.BandFormat.USHORT, bands = 1) - self.assertEqual(x.format, Vips.BandFormat.USHORT) x = self.colour.copy(coding = Vips.Coding.NONE) self.assertEqual(x.coding, Vips.Coding.NONE) - x = self.colour.copy(width = 42) - self.assertEqual(x.width, 42) - x = self.colour.copy(height = 42) - self.assertEqual(x.height, 42) + + # test squashing width of mono image into bands + x = self.mono.copy(width = 1, bands = self.mono.width) + self.assertEqual(x.width, 1) + self.assertEqual(x.bands, self.mono.width) + + # back the other way + y = x.copy(width = self.mono.width, bands = 1) + self.assertEqual(y.width, self.mono.width) + self.assertEqual(y.bands, 1) + self.assertEqual(x.avg(), y.avg()) + + # test squashing width of mono image into bands + x = self.mono.copy(width = self.mono.height, height = 1, bands = self.mono.width) + self.assertEqual(x.width, self.mono.height) + self.assertEqual(x.height, 1) + self.assertEqual(x.bands, self.mono.width) + + # back the other way + y = x.copy(width = self.mono.width, height = self.mono.height, bands = 1) + self.assertEqual(y.width, self.mono.width) + self.assertEqual(y.height, self.mono.height) + self.assertEqual(y.bands, 1) + #self.assertEqual(x.avg(), y.avg()) def test_embed(self): for fmt in all_formats: