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