1 /* Read a virtual microscope slide using OpenSlide.
2 *
3 * Benjamin Gilbert
4 *
5 * Copyright (c) 2011-2015 Carnegie Mellon University
6 *
7 * 26/11/11
8 * - initial version
9 * 27/11/11
10 * - fix black background in transparent areas
11 * - no need to set *stop on fill_region() error return
12 * - add OpenSlide properties to image metadata
13 * - consolidate setup into one function
14 * - support reading arbitrary layers
15 * - use VIPS_ARRAY()
16 * - add helper to copy a line of pixels
17 * - support reading associated images
18 * 7/12/11
19 * - redirect OpenSlide error logging to vips_error()
20 * 8/12/11
21 * - add more exposition to documentation
22 * 9/12/11
23 * - unpack to a tile cache
24 * 11/12/11
25 * - move argb->rgba into conversion
26 * - turn into a set of read fns ready to be called from a class
27 * 28/2/12
28 * - convert "layer" to "level" where externally visible
29 * 9/4/12
30 * - move argb2rgba back in here, we don't have a use for coded pixels
31 * - small cleanups
32 * 11/4/12
33 * - fail if both level and associated image are specified
34 * 20/9/12
35 * - update openslide_open error handling for 3.3.0 semantics
36 * - switch from deprecated _layer_ functions
37 * 11/10/12
38 * - look for tile-width and tile-height properties
39 * - use threaded tile cache
40 * 6/8/13
41 * - always output solid (not transparent) pixels
42 * 25/1/14
43 * - use openslide_detect_vendor() on >= 3.4.0
44 * 30/7/14
45 * - add autocrop toggle
46 * 9/8/14
47 * - do argb -> rgba for associated as well
48 * 27/1/15
49 * - unpremultiplication speedups for fully opaque/transparent pixels
50 * 18/1/17
51 * - reorganise to support invalidate on read error
52 * 27/1/18
53 * - option to attach associated images as metadata
54 * 22/6/20 adamu
55 * - set libvips xres/yres from openslide mpp-x/mpp-y
56 * 13/2/21 kleisauke
57 * - include GObject part from openslideload.c
58 */
59
60 /*
61
62 This file is part of VIPS.
63
64 VIPS is free software; you can redistribute it and/or modify
65 it under the terms of the GNU Lesser General Public License as published by
66 the Free Software Foundation; either version 2 of the License, or
67 (at your option) any later version.
68
69 This program is distributed in the hope that it will be useful,
70 but WITHOUT ANY WARRANTY; without even the implied warranty of
71 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
72 GNU Lesser General Public License for more details.
73
74 You should have received a copy of the GNU Lesser General Public License
75 along with this program; if not, write to the Free Software
76 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
77 02110-1301 USA
78
79 */
80
81 /*
82 #define VIPS_DEBUG
83 */
84
85 #ifdef HAVE_CONFIG_H
86 #include <config.h>
87 #endif /*HAVE_CONFIG_H*/
88 #include <vips/intl.h>
89
90 #ifdef HAVE_OPENSLIDE
91
92 #include <string.h>
93 #include <stdint.h>
94 #include <stdlib.h>
95 #include <limits.h>
96
97 #include <vips/vips.h>
98 #include <vips/debug.h>
99
100 #include "pforeign.h"
101
102 #include <openslide.h>
103
104 typedef struct {
105 /* Params.
106 */
107 char *filename;
108 VipsImage *out;
109 int32_t level;
110 gboolean autocrop;
111 char *associated;
112 gboolean attach_associated;
113
114 openslide_t *osr;
115
116 /* Crop to image bounds if @autocrop is set.
117 */
118 VipsRect bounds;
119
120 /* Only valid if associated == NULL.
121 */
122 double downsample;
123 uint32_t bg;
124
125 /* Try to get these from openslide properties.
126 */
127 int tile_width;
128 int tile_height;
129 } ReadSlide;
130
131 static int
vips__openslide_isslide(const char * filename)132 vips__openslide_isslide( const char *filename )
133 {
134 #ifdef HAVE_OPENSLIDE_3_4
135 const char *vendor;
136 int ok;
137
138 vendor = openslide_detect_vendor( filename );
139
140 /* Generic tiled tiff images can be opened by openslide as well.
141 * Only offer to load this file if it's not a generic tiff since
142 * we want vips_tiffload() to handle these.
143 */
144 ok = ( vendor &&
145 strcmp( vendor, "generic-tiff" ) != 0 );
146
147 VIPS_DEBUG_MSG( "vips__openslide_isslide: %s - %d\n", filename, ok );
148
149 return( ok );
150 #else
151 openslide_t *osr;
152 int ok;
153
154 ok = 0;
155 osr = openslide_open( filename );
156
157 if( osr ) {
158 const char *vendor;
159
160 /* Generic tiled tiff images can be opened by openslide as
161 * well. Only offer to load this file if it's not a generic
162 * tiff since we want vips_tiffload() to handle these.
163 */
164 vendor = openslide_get_property_value( osr,
165 OPENSLIDE_PROPERTY_NAME_VENDOR );
166
167 /* vendor will be NULL if osr is in error state.
168 */
169 if( vendor &&
170 strcmp( vendor, "generic-tiff" ) != 0 )
171 ok = 1;
172
173 openslide_close( osr );
174 }
175
176 VIPS_DEBUG_MSG( "vips__openslide_isslide: %s - %d\n", filename, ok );
177
178 return( ok );
179 #endif
180 }
181
182 static void
readslide_destroy_cb(VipsImage * image,ReadSlide * rslide)183 readslide_destroy_cb( VipsImage *image, ReadSlide *rslide )
184 {
185 VIPS_FREEF( openslide_close, rslide->osr );
186 VIPS_FREE( rslide->associated );
187 VIPS_FREE( rslide->filename );
188 VIPS_FREE( rslide );
189 }
190
191 static int
check_associated_image(openslide_t * osr,const char * name)192 check_associated_image( openslide_t *osr, const char *name )
193 {
194 const char * const *associated;
195
196 for( associated = openslide_get_associated_image_names( osr );
197 *associated != NULL; associated++ )
198 if( strcmp( *associated, name ) == 0 )
199 return( 0 );
200
201 vips_error( "openslide2vips",
202 "%s", _( "invalid associated image name" ) );
203
204 return( -1 );
205 }
206
207 static gboolean
get_bounds(openslide_t * osr,VipsRect * rect)208 get_bounds( openslide_t *osr, VipsRect *rect )
209 {
210 static const char *openslide_names[] = {
211 "openslide.bounds-x",
212 "openslide.bounds-y",
213 "openslide.bounds-width",
214 "openslide.bounds-height"
215 };
216 static int vips_offsets[] = {
217 G_STRUCT_OFFSET( VipsRect, left ),
218 G_STRUCT_OFFSET( VipsRect, top ),
219 G_STRUCT_OFFSET( VipsRect, width ),
220 G_STRUCT_OFFSET( VipsRect, height )
221 };
222
223 const char *value;
224 int i;
225
226 for( i = 0; i < 4; i++ ) {
227 if( !(value = openslide_get_property_value( osr,
228 openslide_names[i] )) )
229 return( FALSE );
230 G_STRUCT_MEMBER( int, rect, vips_offsets[i] ) =
231 atoi( value );
232 }
233
234 return( TRUE );
235 }
236
237 static ReadSlide *
readslide_new(const char * filename,VipsImage * out,int level,gboolean autocrop,const char * associated,gboolean attach_associated)238 readslide_new( const char *filename, VipsImage *out,
239 int level, gboolean autocrop,
240 const char *associated, gboolean attach_associated )
241 {
242 ReadSlide *rslide;
243
244 if( level &&
245 associated ) {
246 vips_error( "openslide2vips",
247 "%s", _( "specify only one of level and "
248 "associated image" ) );
249 return( NULL );
250 }
251
252 if( attach_associated &&
253 associated ) {
254 vips_error( "openslide2vips",
255 "%s", _( "specify only one of attach_assicated and "
256 "associated image" ) );
257 return( NULL );
258 }
259
260 rslide = VIPS_NEW( NULL, ReadSlide );
261 memset( rslide, 0, sizeof( *rslide ) );
262 g_signal_connect( out, "close", G_CALLBACK( readslide_destroy_cb ),
263 rslide );
264
265 rslide->filename = g_strdup( filename );
266 rslide->out = out;
267 rslide->level = level;
268 rslide->autocrop = autocrop;
269 rslide->associated = g_strdup( associated );
270 rslide->attach_associated = attach_associated;
271
272 /* Non-crazy defaults, override in _parse() if we can.
273 */
274 rslide->tile_width = 256;
275 rslide->tile_height = 256;
276
277 return( rslide );
278 }
279
280 /* Convert from ARGB to RGBA and undo premultiplication.
281 *
282 * We throw away transparency. Formats like Mirax use transparent + bg
283 * colour for areas with no useful pixels. But if we output
284 * transparent pixels and then convert to RGB for jpeg write later, we
285 * would have to pass the bg colour down the pipe somehow. The
286 * structure of dzsave makes this tricky.
287 *
288 * We could output plain RGB instead, but that would break
289 * compatibility with older vipses.
290 */
291 static void
argb2rgba(uint32_t * restrict buf,int n,uint32_t bg)292 argb2rgba( uint32_t * restrict buf, int n, uint32_t bg )
293 {
294 const uint32_t pbg = GUINT32_TO_BE( (bg << 8) | 255 );
295
296 int i;
297
298 for( i = 0; i < n; i++ ) {
299 uint32_t * restrict p = buf + i;
300 uint32_t x = *p;
301 uint8_t a = x >> 24;
302 VipsPel * restrict out = (VipsPel *) p;
303
304 if( a == 255 )
305 *p = GUINT32_TO_BE( (x << 8) | 255 );
306 else if( a == 0 )
307 /* Use background color.
308 */
309 *p = pbg;
310 else {
311 /* Undo premultiplication.
312 */
313 out[0] = 255 * ((x >> 16) & 255) / a;
314 out[1] = 255 * ((x >> 8) & 255) / a;
315 out[2] = 255 * (x & 255) / a;
316 out[3] = 255;
317 }
318 }
319 }
320
321 static int
readslide_attach_associated(ReadSlide * rslide,VipsImage * image)322 readslide_attach_associated( ReadSlide *rslide, VipsImage *image )
323 {
324 const char * const *associated_name;
325
326 for( associated_name =
327 openslide_get_associated_image_names( rslide->osr );
328 *associated_name != NULL; associated_name++ ) {
329 int64_t w, h;
330 VipsImage *associated;
331 uint32_t *p;
332 const char *error;
333 char buf[256];
334
335 associated = vips_image_new_memory();
336 openslide_get_associated_image_dimensions( rslide->osr,
337 *associated_name, &w, &h );
338 vips_image_init_fields( associated, w, h, 4, VIPS_FORMAT_UCHAR,
339 VIPS_CODING_NONE, VIPS_INTERPRETATION_sRGB, 1.0, 1.0 );
340 if( vips_image_pipelinev( associated,
341 VIPS_DEMAND_STYLE_THINSTRIP, NULL ) ||
342 vips_image_write_prepare( associated ) ) {
343 g_object_unref( associated );
344 return( -1 );
345 }
346 p = (uint32_t *) VIPS_IMAGE_ADDR( associated, 0, 0 );
347 openslide_read_associated_image( rslide->osr,
348 *associated_name, p );
349 error = openslide_get_error( rslide->osr );
350 if( error ) {
351 vips_error( "openslide2vips",
352 _( "reading associated image: %s" ), error );
353 g_object_unref( associated );
354 return( -1 );
355 }
356 argb2rgba( p, w * h, rslide->bg );
357
358 vips_snprintf( buf, 256,
359 "openslide.associated.%s", *associated_name );
360 vips_image_set_image( image, buf, associated );
361 g_object_unref( associated );
362 }
363
364 return( 0 );
365 }
366
367 /* Read out a resolution field, converting to pixels per mm.
368 */
369 static double
readslice_parse_res(ReadSlide * rslide,const char * name)370 readslice_parse_res( ReadSlide *rslide, const char *name )
371 {
372 const char *value = openslide_get_property_value( rslide->osr, name );
373 double mpp = g_ascii_strtod( value, NULL );
374
375 return( mpp == 0 ? 1.0 : 1000.0 / mpp );
376 }
377
378 static int
readslide_parse(ReadSlide * rslide,VipsImage * image)379 readslide_parse( ReadSlide *rslide, VipsImage *image )
380 {
381 int64_t w, h;
382 const char *error;
383 const char *background;
384 const char * const *properties;
385 char *associated_names;
386 double xres;
387 double yres;
388
389 rslide->osr = openslide_open( rslide->filename );
390 if( rslide->osr == NULL ) {
391 vips_error( "openslide2vips",
392 "%s", _( "unsupported slide format" ) );
393 return( -1 );
394 }
395
396 error = openslide_get_error( rslide->osr );
397 if( error ) {
398 vips_error( "openslide2vips",
399 _( "opening slide: %s" ), error );
400 return( -1 );
401 }
402
403 if( rslide->level < 0 ||
404 rslide->level >= openslide_get_level_count( rslide->osr ) ) {
405 vips_error( "openslide2vips",
406 "%s", _( "invalid slide level" ) );
407 return( -1 );
408 }
409
410 if( rslide->associated &&
411 check_associated_image( rslide->osr, rslide->associated ) )
412 return( -1 );
413
414 if( rslide->associated ) {
415 openslide_get_associated_image_dimensions( rslide->osr,
416 rslide->associated, &w, &h );
417 vips_image_set_string( image, "slide-associated-image",
418 rslide->associated );
419 if( vips_image_pipelinev( image,
420 VIPS_DEMAND_STYLE_THINSTRIP, NULL ) )
421 return( -1 );
422 }
423 else {
424 char buf[256];
425 const char *value;
426
427 openslide_get_level_dimensions( rslide->osr,
428 rslide->level, &w, &h );
429 rslide->downsample = openslide_get_level_downsample(
430 rslide->osr, rslide->level );
431 vips_image_set_int( image, "slide-level", rslide->level );
432 if( vips_image_pipelinev( image,
433 VIPS_DEMAND_STYLE_SMALLTILE, NULL ) )
434 return( -1 );
435
436 /* Try to get tile width/height. An undocumented, experimental
437 * feature.
438 */
439 vips_snprintf( buf, 256,
440 "openslide.level[%d].tile-width", rslide->level );
441 if( (value = openslide_get_property_value( rslide->osr, buf )) )
442 rslide->tile_width = atoi( value );
443 vips_snprintf( buf, 256,
444 "openslide.level[%d].tile-height", rslide->level );
445 if( (value = openslide_get_property_value( rslide->osr, buf )) )
446 rslide->tile_height = atoi( value );
447 if( value )
448 VIPS_DEBUG_MSG( "readslide_new: found tile-size\n" );
449
450 /* Some images have a bounds in the header. Crop to
451 * that if autocrop is set.
452 */
453 if( rslide->autocrop )
454 if( !get_bounds( rslide->osr, &rslide->bounds ) )
455 rslide->autocrop = FALSE;
456 if( rslide->autocrop ) {
457 VipsRect whole;
458
459 rslide->bounds.left /= rslide->downsample;
460 rslide->bounds.top /= rslide->downsample;
461 rslide->bounds.width /= rslide->downsample;
462 rslide->bounds.height /= rslide->downsample;
463
464 /* Clip against image size.
465 */
466 whole.left = 0;
467 whole.top = 0;
468 whole.width = w;
469 whole.height = h;
470 vips_rect_intersectrect( &rslide->bounds, &whole,
471 &rslide->bounds );
472
473 /* If we've clipped to nothing, ignore bounds.
474 */
475 if( vips_rect_isempty( &rslide->bounds ) )
476 rslide->autocrop = FALSE;
477 }
478 if( rslide->autocrop ) {
479 w = rslide->bounds.width;
480 h = rslide->bounds.height;
481 }
482
483 /* Attach all associated images.
484 */
485 if( rslide->attach_associated &&
486 readslide_attach_associated( rslide, image ) )
487 return( -1 );
488 }
489
490 rslide->bg = 0xffffff;
491 if( (background = openslide_get_property_value( rslide->osr,
492 OPENSLIDE_PROPERTY_NAME_BACKGROUND_COLOR )) )
493 rslide->bg = strtoul( background, NULL, 16 );
494
495 if( w <= 0 ||
496 h <= 0 ||
497 rslide->downsample < 0 ) {
498 vips_error( "openslide2vips", _( "getting dimensions: %s" ),
499 openslide_get_error( rslide->osr ) );
500 return( -1 );
501 }
502 if( w > INT_MAX ||
503 h > INT_MAX ) {
504 vips_error( "openslide2vips",
505 "%s", _( "image dimensions overflow int" ) );
506 return( -1 );
507 }
508
509 if( !rslide->autocrop ) {
510 rslide->bounds.left = 0;
511 rslide->bounds.top = 0;
512 rslide->bounds.width = w;
513 rslide->bounds.height = h;
514 }
515
516 /* Try to get resolution from openslide properties.
517 */
518 xres = 1.0;
519 yres = 1.0;
520
521 for( properties = openslide_get_property_names( rslide->osr );
522 *properties != NULL; properties++ ) {
523 const char *name = *properties;
524 const char *value =
525 openslide_get_property_value( rslide->osr, name );
526
527 /* Can be NULL for some openslides with some images.
528 */
529 if( value ) {
530 vips_image_set_string( image, name, value );
531
532 if( strcmp( *properties, "openslide.mpp-x" ) == 0 )
533 xres = readslice_parse_res( rslide, name );
534 if( strcmp( *properties, "openslide.mpp-y" ) == 0 )
535 yres = readslice_parse_res( rslide, name );
536 }
537 }
538
539 associated_names = g_strjoinv( ", ", (char **)
540 openslide_get_associated_image_names( rslide->osr ) );
541 vips_image_set_string( image,
542 "slide-associated-images", associated_names );
543 VIPS_FREE( associated_names );
544
545 vips_image_init_fields( image, w, h, 4, VIPS_FORMAT_UCHAR,
546 VIPS_CODING_NONE, VIPS_INTERPRETATION_sRGB, xres, yres );
547
548 return( 0 );
549 }
550
551 static int
vips__openslide_read_header(const char * filename,VipsImage * out,int level,gboolean autocrop,char * associated,gboolean attach_associated)552 vips__openslide_read_header( const char *filename, VipsImage *out,
553 int level, gboolean autocrop,
554 char *associated, gboolean attach_associated )
555 {
556 ReadSlide *rslide;
557
558 if( !(rslide = readslide_new( filename,
559 out, level, autocrop, associated, attach_associated )) ||
560 readslide_parse( rslide, out ) )
561 return( -1 );
562
563 return( 0 );
564 }
565
566 static int
vips__openslide_generate(VipsRegion * out,void * _seq,void * _rslide,void * unused,gboolean * stop)567 vips__openslide_generate( VipsRegion *out,
568 void *_seq, void *_rslide, void *unused, gboolean *stop )
569 {
570 ReadSlide *rslide = _rslide;
571 uint32_t bg = rslide->bg;
572 VipsRect *r = &out->valid;
573 int n = r->width * r->height;
574 uint32_t *buf = (uint32_t *) VIPS_REGION_ADDR( out, r->left, r->top );
575
576 const char *error;
577
578 VIPS_DEBUG_MSG( "vips__openslide_generate: %dx%d @ %dx%d\n",
579 r->width, r->height, r->left, r->top );
580
581 /* We're inside a cache, so requests should always be
582 * tile_width by tile_height pixels and on a tile boundary.
583 */
584 g_assert( (r->left % rslide->tile_width) == 0 );
585 g_assert( (r->top % rslide->tile_height) == 0 );
586 g_assert( r->width <= rslide->tile_width );
587 g_assert( r->height <= rslide->tile_height );
588
589 /* The memory on the region should be contiguous for our ARGB->RGBA
590 * loop below.
591 */
592 g_assert( VIPS_REGION_LSKIP( out ) == r->width * 4 );
593
594 openslide_read_region( rslide->osr,
595 buf,
596 (r->left + rslide->bounds.left) * rslide->downsample,
597 (r->top + rslide->bounds.top) * rslide->downsample,
598 rslide->level,
599 r->width, r->height );
600
601 /* openslide errors are terminal. To support
602 * @fail we'd have to close the openslide_t and reopen, perhaps
603 * somehow marking this tile as unreadable.
604 *
605 * See
606 * https://github.com/libvips/libvips/commit/bb0a6643f94e69294e36d2b253f9bdd60c8c40ed#commitcomment-19838911
607 */
608 error = openslide_get_error( rslide->osr );
609 if( error ) {
610 vips_error( "openslide2vips",
611 _( "reading region: %s" ), error );
612 return( -1 );
613 }
614
615 /* Since we are inside a cache, we know buf must be continuous.
616 */
617 argb2rgba( buf, n, bg );
618
619 return( 0 );
620 }
621
622 static int
vips__openslide_read(const char * filename,VipsImage * out,int level,gboolean autocrop,gboolean attach_associated)623 vips__openslide_read( const char *filename, VipsImage *out,
624 int level, gboolean autocrop, gboolean attach_associated )
625 {
626 ReadSlide *rslide;
627 VipsImage *raw;
628 VipsImage *t;
629
630 VIPS_DEBUG_MSG( "vips__openslide_read: %s %d\n",
631 filename, level );
632
633 if( !(rslide = readslide_new( filename, out, level, autocrop,
634 NULL, attach_associated )) )
635 return( -1 );
636
637 raw = vips_image_new();
638 vips_object_local( out, raw );
639
640 if( readslide_parse( rslide, raw ) ||
641 vips_image_generate( raw,
642 NULL, vips__openslide_generate, NULL, rslide, NULL ) )
643 return( -1 );
644
645 /* Copy to out, adding a cache. Enough tiles for two complete rows,
646 * plus 50%. We need at least two rows, or we'll constantly reload
647 * tiles if they cross a tile boundary.
648 */
649 if( vips_tilecache( raw, &t,
650 "tile_width", rslide->tile_width,
651 "tile_height", rslide->tile_height,
652 "max_tiles",
653 (int) (2.5 * (1 + raw->Xsize / rslide->tile_width)),
654 "threaded", TRUE,
655 NULL ) )
656 return( -1 );
657 if( vips_image_write( t, out ) ) {
658 g_object_unref( t );
659 return( -1 );
660 }
661 g_object_unref( t );
662
663 return( 0 );
664 }
665
666 static int
vips__openslide_read_associated(const char * filename,VipsImage * out,const char * associated)667 vips__openslide_read_associated( const char *filename, VipsImage *out,
668 const char *associated )
669 {
670 ReadSlide *rslide;
671 VipsImage *raw;
672 uint32_t *buf;
673 const char *error;
674
675 VIPS_DEBUG_MSG( "vips__openslide_read_associated: %s %s\n",
676 filename, associated );
677
678 if( !(rslide = readslide_new( filename, out, 0, FALSE,
679 associated, FALSE )) )
680 return( -1 );
681
682 /* Memory buffer. Get associated directly to this, then copy to out.
683 */
684 raw = vips_image_new_memory();
685 vips_object_local( out, raw );
686
687 if( readslide_parse( rslide, raw ) ||
688 vips_image_write_prepare( raw ) )
689 return( -1 );
690
691 buf = (uint32_t *) VIPS_IMAGE_ADDR( raw, 0, 0 );
692 openslide_read_associated_image( rslide->osr, rslide->associated, buf );
693 error = openslide_get_error( rslide->osr );
694 if( error ) {
695 vips_error( "openslide2vips",
696 _( "reading associated image: %s" ), error );
697 return( -1 );
698 }
699 argb2rgba( buf, raw->Xsize * raw->Ysize, rslide->bg );
700
701 if( vips_image_write( raw, out ) )
702 return( -1 );
703
704 return( 0 );
705 }
706
707 typedef struct _VipsForeignLoadOpenslide {
708 VipsForeignLoad parent_object;
709
710 /* Source to load from (set by subclasses).
711 */
712 VipsSource *source;
713
714 /* Filename from source.
715 */
716 const char *filename;
717
718 /* Load this level.
719 */
720 int level;
721
722 /* Crop to image bounds.
723 */
724 gboolean autocrop;
725
726 /* Load just this associated image.
727 */
728 char *associated;
729
730 /* Attach all associated images as metadata items.
731 */
732 gboolean attach_associated;
733
734 } VipsForeignLoadOpenslide;
735
736 typedef VipsForeignLoadClass VipsForeignLoadOpenslideClass;
737
738 G_DEFINE_ABSTRACT_TYPE( VipsForeignLoadOpenslide, vips_foreign_load_openslide,
739 VIPS_TYPE_FOREIGN_LOAD );
740
741 static void
vips_foreign_load_openslide_dispose(GObject * gobject)742 vips_foreign_load_openslide_dispose( GObject *gobject )
743 {
744 VipsForeignLoadOpenslide *openslide =
745 (VipsForeignLoadOpenslide *) gobject;
746
747 VIPS_UNREF( openslide->source );
748
749 G_OBJECT_CLASS( vips_foreign_load_openslide_parent_class )->
750 dispose( gobject );
751 }
752
753 static int
vips_foreign_load_openslide_build(VipsObject * object)754 vips_foreign_load_openslide_build( VipsObject *object )
755 {
756 VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( object );
757 VipsForeignLoadOpenslide *openslide =
758 (VipsForeignLoadOpenslide *) object;
759
760 /* We can only open source which have an associated filename, since
761 * the openslide library works in terms of filenames.
762 */
763 if( openslide->source ) {
764 VipsConnection *connection =
765 VIPS_CONNECTION( openslide->source );
766
767 const char *filename;
768
769 if( !vips_source_is_file( openslide->source ) ||
770 !(filename = vips_connection_filename( connection )) ) {
771 vips_error( class->nickname, "%s",
772 _( "no filename available" ) );
773 return( -1 );
774 }
775
776 openslide->filename = filename;
777 }
778
779 if( VIPS_OBJECT_CLASS( vips_foreign_load_openslide_parent_class )->
780 build( object ) )
781 return( -1 );
782
783 return( 0 );
784 }
785
786 static VipsForeignFlags
vips_foreign_load_openslide_get_flags_source(VipsSource * source)787 vips_foreign_load_openslide_get_flags_source( VipsSource *source )
788 {
789 /* We can't tell from just the source, we need to know what part of
790 * the file the user wants. But it'll usually be partial.
791 */
792 return( VIPS_FOREIGN_PARTIAL );
793 }
794
795 static VipsForeignFlags
vips_foreign_load_openslide_get_flags(VipsForeignLoad * load)796 vips_foreign_load_openslide_get_flags( VipsForeignLoad *load )
797 {
798 VipsForeignLoadOpenslide *openslide = (VipsForeignLoadOpenslide *) load;
799 VipsForeignFlags flags;
800
801 flags = 0;
802 if( !openslide->associated )
803 flags |= VIPS_FOREIGN_PARTIAL;
804
805 return( flags );
806 }
807
808 static VipsForeignFlags
vips_foreign_load_openslide_get_flags_filename(const char * filename)809 vips_foreign_load_openslide_get_flags_filename( const char *filename )
810 {
811 VipsSource *source;
812 VipsForeignFlags flags;
813
814 if( !(source = vips_source_new_from_file( filename )) )
815 return( 0 );
816 flags = vips_foreign_load_openslide_get_flags_source( source );
817 VIPS_UNREF( source );
818
819 return( flags );
820 }
821
822 static int
vips_foreign_load_openslide_header(VipsForeignLoad * load)823 vips_foreign_load_openslide_header( VipsForeignLoad *load )
824 {
825 VipsForeignLoadOpenslide *openslide = (VipsForeignLoadOpenslide *) load;
826
827 if( vips__openslide_read_header( openslide->filename, load->out,
828 openslide->level, openslide->autocrop,
829 openslide->associated, openslide->attach_associated ) )
830 return( -1 );
831
832 VIPS_SETSTR( load->out->filename, openslide->filename );
833
834 return( 0 );
835 }
836
837 static int
vips_foreign_load_openslide_load(VipsForeignLoad * load)838 vips_foreign_load_openslide_load( VipsForeignLoad *load )
839 {
840 VipsForeignLoadOpenslide *openslide = (VipsForeignLoadOpenslide *) load;
841
842 if( !openslide->associated ) {
843 if( vips__openslide_read( openslide->filename, load->real,
844 openslide->level, openslide->autocrop,
845 openslide->attach_associated ) )
846 return( -1 );
847 }
848 else {
849 if( vips__openslide_read_associated( openslide->filename,
850 load->real, openslide->associated ) )
851 return( -1 );
852 }
853
854 return( 0 );
855 }
856
857 static void
vips_foreign_load_openslide_class_init(VipsForeignLoadOpenslideClass * class)858 vips_foreign_load_openslide_class_init( VipsForeignLoadOpenslideClass *class )
859 {
860 GObjectClass *gobject_class = G_OBJECT_CLASS( class );
861 VipsObjectClass *object_class = (VipsObjectClass *) class;
862 VipsForeignClass *foreign_class = (VipsForeignClass *) class;
863 VipsForeignLoadClass *load_class = (VipsForeignLoadClass *) class;
864
865 gobject_class->dispose = vips_foreign_load_openslide_dispose;
866 gobject_class->set_property = vips_object_set_property;
867 gobject_class->get_property = vips_object_get_property;
868
869 object_class->nickname = "openslideload_base";
870 object_class->description = _( "load OpenSlide base class" );
871 object_class->build = vips_foreign_load_openslide_build;
872
873 /* We need to be ahead of the tiff sniffer since many OpenSlide
874 * formats are tiff derivatives. If we see a tiff which would be
875 * better handled by the vips tiff loader we are careful to say no.
876 *
877 * We need to be ahead of JPEG, since MRXS images are also
878 * JPEGs.
879 */
880 foreign_class->priority = 100;
881
882 load_class->get_flags_filename =
883 vips_foreign_load_openslide_get_flags_filename;
884 load_class->get_flags = vips_foreign_load_openslide_get_flags;
885 load_class->header = vips_foreign_load_openslide_header;
886 load_class->load = vips_foreign_load_openslide_load;
887
888 VIPS_ARG_INT( class, "level", 20,
889 _( "Level" ),
890 _( "Load this level from the file" ),
891 VIPS_ARGUMENT_OPTIONAL_INPUT,
892 G_STRUCT_OFFSET( VipsForeignLoadOpenslide, level ),
893 0, 100000, 0 );
894
895 VIPS_ARG_BOOL( class, "autocrop", 21,
896 _( "Autocrop" ),
897 _( "Crop to image bounds" ),
898 VIPS_ARGUMENT_OPTIONAL_INPUT,
899 G_STRUCT_OFFSET( VipsForeignLoadOpenslide, autocrop ),
900 FALSE );
901
902 VIPS_ARG_STRING( class, "associated", 22,
903 _( "Associated" ),
904 _( "Load this associated image" ),
905 VIPS_ARGUMENT_OPTIONAL_INPUT,
906 G_STRUCT_OFFSET( VipsForeignLoadOpenslide, associated ),
907 NULL );
908
909 VIPS_ARG_BOOL( class, "attach-associated", 13,
910 _( "Attach associated" ),
911 _( "Attach all associated images" ),
912 VIPS_ARGUMENT_OPTIONAL_INPUT,
913 G_STRUCT_OFFSET( VipsForeignLoadOpenslide, attach_associated ),
914 FALSE );
915
916 }
917
918 static void
vips_foreign_load_openslide_init(VipsForeignLoadOpenslide * openslide)919 vips_foreign_load_openslide_init( VipsForeignLoadOpenslide *openslide )
920 {
921 }
922
923 typedef struct _VipsForeignLoadOpenslideFile {
924 VipsForeignLoadOpenslide parent_object;
925
926 /* Filename for load.
927 */
928 char *filename;
929
930 } VipsForeignLoadOpenslideFile;
931
932 typedef VipsForeignLoadOpenslideClass VipsForeignLoadOpenslideFileClass;
933
934 G_DEFINE_TYPE( VipsForeignLoadOpenslideFile, vips_foreign_load_openslide_file,
935 vips_foreign_load_openslide_get_type() );
936
937 static int
vips_foreign_load_openslide_file_build(VipsObject * object)938 vips_foreign_load_openslide_file_build( VipsObject *object )
939 {
940 VipsForeignLoadOpenslide *openslide =
941 (VipsForeignLoadOpenslide *) object;
942 VipsForeignLoadOpenslideFile *file =
943 (VipsForeignLoadOpenslideFile *) object;
944
945 if( file->filename &&
946 !(openslide->source =
947 vips_source_new_from_file( file->filename )) )
948 return( -1 );
949
950 if( VIPS_OBJECT_CLASS( vips_foreign_load_openslide_file_parent_class )->
951 build( object ) )
952 return( -1 );
953
954 return( 0 );
955 }
956
957 static const char *vips_foreign_openslide_suffs[] = {
958 ".svs", /* Aperio */
959 ".vms", ".vmu", ".ndpi", /* Hamamatsu */
960 ".scn", /* Leica */
961 ".mrxs", /* MIRAX */
962 ".svslide", /* Sakura */
963 ".tif", /* Trestle */
964 ".bif", /* Ventana */
965 NULL
966 };
967
968 static void
vips_foreign_load_openslide_file_class_init(VipsForeignLoadOpenslideFileClass * class)969 vips_foreign_load_openslide_file_class_init(
970 VipsForeignLoadOpenslideFileClass *class )
971 {
972 GObjectClass *gobject_class = G_OBJECT_CLASS( class );
973 VipsObjectClass *object_class = (VipsObjectClass *) class;
974 VipsForeignClass *foreign_class = (VipsForeignClass *) class;
975 VipsForeignLoadClass *load_class = (VipsForeignLoadClass *) class;
976
977 gobject_class->set_property = vips_object_set_property;
978 gobject_class->get_property = vips_object_get_property;
979
980 object_class->nickname = "openslideload";
981 object_class->description = _( "load file with OpenSlide" );
982 object_class->build = vips_foreign_load_openslide_file_build;
983
984 foreign_class->suffs = vips_foreign_openslide_suffs;
985
986 load_class->is_a = vips__openslide_isslide;
987
988 VIPS_ARG_STRING( class, "filename", 1,
989 _( "Filename" ),
990 _( "Filename to load from" ),
991 VIPS_ARGUMENT_REQUIRED_INPUT,
992 G_STRUCT_OFFSET( VipsForeignLoadOpenslideFile, filename ),
993 NULL );
994
995 }
996
997 static void
vips_foreign_load_openslide_file_init(VipsForeignLoadOpenslideFile * openslide)998 vips_foreign_load_openslide_file_init( VipsForeignLoadOpenslideFile *openslide )
999 {
1000 }
1001
1002 typedef struct _VipsForeignLoadOpenslideSource {
1003 VipsForeignLoadOpenslide parent_object;
1004
1005 /* Load from a source.
1006 */
1007 VipsSource *source;
1008
1009 } VipsForeignLoadOpenslideSource;
1010
1011 typedef VipsForeignLoadOpenslideClass VipsForeignLoadOpenslideSourceClass;
1012
1013 G_DEFINE_TYPE( VipsForeignLoadOpenslideSource,
1014 vips_foreign_load_openslide_source,
1015 vips_foreign_load_openslide_get_type() );
1016
1017 static int
vips_foreign_load_openslide_source_build(VipsObject * object)1018 vips_foreign_load_openslide_source_build( VipsObject *object )
1019 {
1020 VipsForeignLoadOpenslide *openslide =
1021 (VipsForeignLoadOpenslide *) object;
1022 VipsForeignLoadOpenslideSource *source =
1023 (VipsForeignLoadOpenslideSource *) object;
1024
1025 if( source->source ) {
1026 openslide->source = source->source;
1027 g_object_ref( openslide->source );
1028 }
1029
1030 if( VIPS_OBJECT_CLASS(
1031 vips_foreign_load_openslide_source_parent_class )->
1032 build( object ) )
1033 return( -1 );
1034
1035 return( 0 );
1036 }
1037
1038 static gboolean
vips_foreign_load_openslide_source_is_a_source(VipsSource * source)1039 vips_foreign_load_openslide_source_is_a_source( VipsSource *source )
1040 {
1041 VipsConnection *connection = VIPS_CONNECTION( source );
1042
1043 const char *filename;
1044
1045 return( vips_source_is_file( source ) &&
1046 (filename = vips_connection_filename( connection )) &&
1047 vips__openslide_isslide( filename ) );
1048 }
1049
1050 static void
vips_foreign_load_openslide_source_class_init(VipsForeignLoadOpenslideSourceClass * class)1051 vips_foreign_load_openslide_source_class_init(
1052 VipsForeignLoadOpenslideSourceClass *class )
1053 {
1054 GObjectClass *gobject_class = G_OBJECT_CLASS( class );
1055 VipsObjectClass *object_class = (VipsObjectClass *) class;
1056 VipsOperationClass *operation_class = VIPS_OPERATION_CLASS( class );
1057 VipsForeignLoadClass *load_class = (VipsForeignLoadClass *) class;
1058
1059 gobject_class->set_property = vips_object_set_property;
1060 gobject_class->get_property = vips_object_get_property;
1061
1062 object_class->nickname = "openslideload_source";
1063 object_class->description = _( "load source with OpenSlide" );
1064 object_class->build = vips_foreign_load_openslide_source_build;
1065
1066 operation_class->flags = VIPS_OPERATION_NOCACHE;
1067
1068 load_class->is_a_source =
1069 vips_foreign_load_openslide_source_is_a_source;
1070
1071 VIPS_ARG_OBJECT( class, "source", 1,
1072 _( "Source" ),
1073 _( "Source to load from" ),
1074 VIPS_ARGUMENT_REQUIRED_INPUT,
1075 G_STRUCT_OFFSET( VipsForeignLoadOpenslideSource, source ),
1076 VIPS_TYPE_SOURCE );
1077
1078 }
1079
1080 static void
vips_foreign_load_openslide_source_init(VipsForeignLoadOpenslideSource * openslide)1081 vips_foreign_load_openslide_source_init(
1082 VipsForeignLoadOpenslideSource *openslide )
1083 {
1084 }
1085
1086 #endif /*HAVE_OPENSLIDE*/
1087
1088 /* The C API wrappers are defined in foreign.c.
1089 */
1090