From f7381b3aaec4f2ff9e62311795c6397470380baf Mon Sep 17 00:00:00 2001 From: "Jeff Roberts (LA)" Date: Wed, 22 Oct 2025 13:30:41 -0700 Subject: [PATCH] stb_image_resize 2.16 - fixed the easy-to-use APIs to allow inverted bitmaps (negative strides), fix vertical filter kernel callback, fix threaded gather buffer priming (and assert) --- stb_image_resize2.h | 262 ++++++++++++++++++++++++-------------------- 1 file changed, 141 insertions(+), 121 deletions(-) diff --git a/stb_image_resize2.h b/stb_image_resize2.h index 2a59c95..48ff426 100644 --- a/stb_image_resize2.h +++ b/stb_image_resize2.h @@ -1,4 +1,4 @@ -/* stb_image_resize2 - v2.15 - public domain image resizing +/* stb_image_resize2 - v2.16 - public domain image resizing by Jeff Roberts (v2) and Jorge L Rodriguez http://github.com/nothings/stb @@ -329,6 +329,10 @@ Nathan Reed: warning fixes for 1.0 REVISIONS + 2.16 (2025-10-21) fixed the easy-to-use APIs to allow inverted bitmaps (negative + strides), fix vertical filter kernel callback, fix threaded + gather buffer priming (and assert). + (thanks adipose, TainZerL, and Harrison Green) 2.15 (2025-07-17) fixed an assert in debug mode when using floats with input callbacks, work around GCC warning when adding to null ptr (thanks Johannes Spohr and Pyry Kovanen). @@ -6711,7 +6715,7 @@ static void stbir__get_conservative_extents( stbir__sampler * samp, stbir__contr } } -static void stbir__get_split_info( stbir__per_split_info* split_info, int splits, int output_height, int vertical_pixel_margin, int input_full_height ) +static void stbir__get_split_info( stbir__per_split_info* split_info, int splits, int output_height, int vertical_pixel_margin, int input_full_height, int is_gather, stbir__contributors * contribs ) { int i, cur; int left = output_height; @@ -6720,9 +6724,58 @@ static void stbir__get_split_info( stbir__per_split_info* split_info, int splits for( i = 0 ; i < splits ; i++ ) { int each; + split_info[i].start_output_y = cur; each = left / ( splits - i ); split_info[i].end_output_y = cur + each; + + // ok, when we are gathering, we need to make sure we are starting on a y offset that doesn't have + // a "special" set of coefficients. Basically, with exactly the right filter at exactly the right + // resize at exactly the right phase, some of the coefficents can be zero. When they are zero, we + // don't process them at all. But this leads to a tricky thing with the thread splits, where we + // might have a set of two coeffs like this for example: (4,4) and (3,6). The 4,4 means there was + // just one single coeff because things worked out perfectly (normally, they all have 4 coeffs + // like the range 3,6. The problem is that if we start right on the (4,4) on a brand new thread, + // then when we get to (3,6), we don't have the "3" sample in memory (because we didn't load + // it on the initial (4,4) range because it didn't have a 3 (we only add new samples that are + // larger than our existing samples - it's just how the eviction works). So, our solution here + // is pretty simple, if we start right on a range that has samples that start earlier, then we + // simply bump up our previous thread split range to include it, and then start this threads + // range with the smaller sample. It just moves one scanline from one thread split to another, + // so that we end with the unusual one, instead of start with it. To do this, we check 2-4 + // sample at each thread split start and then occassionally move them. + + if ( ( is_gather ) && ( i ) ) + { + stbir__contributors * small_contribs; + int j, smallest, stop, start_n0; + stbir__contributors * split_contribs = contribs + cur; + + // scan for a max of 3x the filter width or until the next thread split + stop = vertical_pixel_margin * 3; + if ( each < stop ) + stop = each; + + // loops a few times before early out + smallest = 0; + small_contribs = split_contribs; + start_n0 = small_contribs->n0; + for( j = 1 ; j <= stop ; j++ ) + { + ++split_contribs; + if ( split_contribs->n0 > start_n0 ) + break; + if ( split_contribs->n0 < small_contribs->n0 ) + { + small_contribs = split_contribs; + smallest = j; + } + } + + split_info[i-1].end_output_y += smallest; + split_info[i].start_output_y += smallest; + } + cur += each; left -= each; @@ -7269,7 +7322,7 @@ static stbir__info * stbir__alloc_internal_mem_and_build_samplers( stbir__sample } // setup the vertical split ranges - stbir__get_split_info( info->split_info, info->splits, info->vertical.scale_info.output_sub_size, info->vertical.filter_pixel_margin, info->vertical.scale_info.input_full_size ); + stbir__get_split_info( info->split_info, info->splits, info->vertical.scale_info.output_sub_size, info->vertical.filter_pixel_margin, info->vertical.scale_info.input_full_size, info->vertical.is_gather, info->vertical.contributors ); // now we know precisely how many entries we need info->ring_buffer_num_entries = info->vertical.extent_info.widest; @@ -7833,7 +7886,7 @@ static int stbir__perform_build( STBIR_RESIZE * resize, int splits ) stbir__set_sampler(&horizontal, resize->horizontal_filter, resize->horizontal_filter_kernel, resize->horizontal_filter_support, resize->horizontal_edge, &horizontal.scale_info, 1, resize->user_data ); stbir__get_conservative_extents( &horizontal, &conservative, resize->user_data ); - stbir__set_sampler(&vertical, resize->vertical_filter, resize->horizontal_filter_kernel, resize->vertical_filter_support, resize->vertical_edge, &vertical.scale_info, 0, resize->user_data ); + stbir__set_sampler(&vertical, resize->vertical_filter, resize->vertical_filter_kernel, resize->vertical_filter_support, resize->vertical_edge, &vertical.scale_info, 0, resize->user_data ); if ( ( vertical.scale_info.output_sub_size / splits ) < STBIR_FORCE_MINIMUM_SCANLINES_FOR_SPLITS ) // each split should be a minimum of 4 scanlines (handwavey choice) { @@ -7958,138 +8011,64 @@ STBIRDEF int stbir_resize_extended_split( STBIR_RESIZE * resize, int split_start return stbir__perform_resize( resize->samplers, split_start, split_count ); } -static int stbir__check_output_stuff( void ** ret_ptr, int * ret_pitch, void * output_pixels, int type_size, int output_w, int output_h, int output_stride_in_bytes, stbir_internal_pixel_layout pixel_layout ) + +static void * stbir_quick_resize_helper( const void *input_pixels , int input_w , int input_h, int input_stride_in_bytes, + void *output_pixels, int output_w, int output_h, int output_stride_in_bytes, + stbir_pixel_layout pixel_layout, stbir_datatype data_type, stbir_edge edge, stbir_filter filter ) { - size_t size; - int pitch; - void * ptr; + STBIR_RESIZE resize; + int scanline_output_in_bytes; + int positive_output_stride_in_bytes; + void * start_ptr; + void * free_ptr; - pitch = output_w * type_size * stbir__pixel_channels[ pixel_layout ]; - if ( pitch == 0 ) + scanline_output_in_bytes = output_w * stbir__type_size[ data_type ] * stbir__pixel_channels[ pixel_layout ]; + if ( scanline_output_in_bytes == 0 ) return 0; + // if zero stride, use scanline output if ( output_stride_in_bytes == 0 ) - output_stride_in_bytes = pitch; + output_stride_in_bytes = scanline_output_in_bytes; - if ( output_stride_in_bytes < pitch ) + // abs value for inverted images (negative pitches) + positive_output_stride_in_bytes = output_stride_in_bytes; + if ( positive_output_stride_in_bytes < 0 ) + positive_output_stride_in_bytes = -positive_output_stride_in_bytes; + + // is the requested stride smaller than the scanline output? if so, just fail + if ( positive_output_stride_in_bytes < scanline_output_in_bytes ) return 0; - size = (size_t)output_stride_in_bytes * (size_t)output_h; - if ( size == 0 ) - return 0; - - *ret_ptr = 0; - *ret_pitch = output_stride_in_bytes; + start_ptr = output_pixels; + free_ptr = 0; // no free pointer, since they passed buffer to use + // did they pass a zero for the dest? if so, allocate the buffer if ( output_pixels == 0 ) { - ptr = STBIR_MALLOC( size, 0 ); + size_t size; + char * ptr; + + size = (size_t)positive_output_stride_in_bytes * (size_t)output_h; + if ( size == 0 ) + return 0; + + ptr = (char*) STBIR_MALLOC( size, 0 ); if ( ptr == 0 ) return 0; - *ret_ptr = ptr; - *ret_pitch = pitch; + free_ptr = ptr; + + // point at the last scanline, if they requested a flipped image + if ( output_stride_in_bytes < 0 ) + start_ptr = ptr + ( (size_t)positive_output_stride_in_bytes * (size_t)( output_h - 1 ) ); + else + start_ptr = ptr; } - return 1; -} - - -STBIRDEF unsigned char * stbir_resize_uint8_linear( const unsigned char *input_pixels , int input_w , int input_h, int input_stride_in_bytes, - unsigned char *output_pixels, int output_w, int output_h, int output_stride_in_bytes, - stbir_pixel_layout pixel_layout ) -{ - STBIR_RESIZE resize; - unsigned char * optr; - int opitch; - - if ( !stbir__check_output_stuff( (void**)&optr, &opitch, output_pixels, sizeof( unsigned char ), output_w, output_h, output_stride_in_bytes, stbir__pixel_layout_convert_public_to_internal[ pixel_layout ] ) ) - return 0; - + // ok, now do the resize stbir_resize_init( &resize, input_pixels, input_w, input_h, input_stride_in_bytes, - (optr) ? optr : output_pixels, output_w, output_h, opitch, - pixel_layout, STBIR_TYPE_UINT8 ); - - if ( !stbir_resize_extended( &resize ) ) - { - if ( optr ) - STBIR_FREE( optr, 0 ); - return 0; - } - - return (optr) ? optr : output_pixels; -} - -STBIRDEF unsigned char * stbir_resize_uint8_srgb( const unsigned char *input_pixels , int input_w , int input_h, int input_stride_in_bytes, - unsigned char *output_pixels, int output_w, int output_h, int output_stride_in_bytes, - stbir_pixel_layout pixel_layout ) -{ - STBIR_RESIZE resize; - unsigned char * optr; - int opitch; - - if ( !stbir__check_output_stuff( (void**)&optr, &opitch, output_pixels, sizeof( unsigned char ), output_w, output_h, output_stride_in_bytes, stbir__pixel_layout_convert_public_to_internal[ pixel_layout ] ) ) - return 0; - - stbir_resize_init( &resize, - input_pixels, input_w, input_h, input_stride_in_bytes, - (optr) ? optr : output_pixels, output_w, output_h, opitch, - pixel_layout, STBIR_TYPE_UINT8_SRGB ); - - if ( !stbir_resize_extended( &resize ) ) - { - if ( optr ) - STBIR_FREE( optr, 0 ); - return 0; - } - - return (optr) ? optr : output_pixels; -} - - -STBIRDEF float * stbir_resize_float_linear( const float *input_pixels , int input_w , int input_h, int input_stride_in_bytes, - float *output_pixels, int output_w, int output_h, int output_stride_in_bytes, - stbir_pixel_layout pixel_layout ) -{ - STBIR_RESIZE resize; - float * optr; - int opitch; - - if ( !stbir__check_output_stuff( (void**)&optr, &opitch, output_pixels, sizeof( float ), output_w, output_h, output_stride_in_bytes, stbir__pixel_layout_convert_public_to_internal[ pixel_layout ] ) ) - return 0; - - stbir_resize_init( &resize, - input_pixels, input_w, input_h, input_stride_in_bytes, - (optr) ? optr : output_pixels, output_w, output_h, opitch, - pixel_layout, STBIR_TYPE_FLOAT ); - - if ( !stbir_resize_extended( &resize ) ) - { - if ( optr ) - STBIR_FREE( optr, 0 ); - return 0; - } - - return (optr) ? optr : output_pixels; -} - - -STBIRDEF void * stbir_resize( const void *input_pixels , int input_w , int input_h, int input_stride_in_bytes, - void *output_pixels, int output_w, int output_h, int output_stride_in_bytes, - stbir_pixel_layout pixel_layout, stbir_datatype data_type, - stbir_edge edge, stbir_filter filter ) -{ - STBIR_RESIZE resize; - float * optr; - int opitch; - - if ( !stbir__check_output_stuff( (void**)&optr, &opitch, output_pixels, stbir__type_size[data_type], output_w, output_h, output_stride_in_bytes, stbir__pixel_layout_convert_public_to_internal[ pixel_layout ] ) ) - return 0; - - stbir_resize_init( &resize, - input_pixels, input_w, input_h, input_stride_in_bytes, - (optr) ? optr : output_pixels, output_w, output_h, output_stride_in_bytes, + start_ptr, output_w, output_h, output_stride_in_bytes, pixel_layout, data_type ); resize.horizontal_edge = edge; @@ -8099,12 +8078,53 @@ STBIRDEF void * stbir_resize( const void *input_pixels , int input_w , int input if ( !stbir_resize_extended( &resize ) ) { - if ( optr ) - STBIR_FREE( optr, 0 ); + if ( free_ptr ) + STBIR_FREE( free_ptr, 0 ); return 0; } - return (optr) ? optr : output_pixels; + return (free_ptr) ? free_ptr : start_ptr; +} + + + +STBIRDEF unsigned char * stbir_resize_uint8_linear( const unsigned char *input_pixels , int input_w , int input_h, int input_stride_in_bytes, + unsigned char *output_pixels, int output_w, int output_h, int output_stride_in_bytes, + stbir_pixel_layout pixel_layout ) +{ + return (unsigned char *) stbir_quick_resize_helper( input_pixels , input_w , input_h, input_stride_in_bytes, + output_pixels, output_w, output_h, output_stride_in_bytes, + pixel_layout, STBIR_TYPE_UINT8, STBIR_EDGE_CLAMP, STBIR_FILTER_DEFAULT ); +} + +STBIRDEF unsigned char * stbir_resize_uint8_srgb( const unsigned char *input_pixels , int input_w , int input_h, int input_stride_in_bytes, + unsigned char *output_pixels, int output_w, int output_h, int output_stride_in_bytes, + stbir_pixel_layout pixel_layout ) +{ + return (unsigned char *) stbir_quick_resize_helper( input_pixels , input_w , input_h, input_stride_in_bytes, + output_pixels, output_w, output_h, output_stride_in_bytes, + pixel_layout, STBIR_TYPE_UINT8_SRGB, STBIR_EDGE_CLAMP, STBIR_FILTER_DEFAULT ); +} + + +STBIRDEF float * stbir_resize_float_linear( const float *input_pixels , int input_w , int input_h, int input_stride_in_bytes, + float *output_pixels, int output_w, int output_h, int output_stride_in_bytes, + stbir_pixel_layout pixel_layout ) +{ + return (float *) stbir_quick_resize_helper( input_pixels , input_w , input_h, input_stride_in_bytes, + output_pixels, output_w, output_h, output_stride_in_bytes, + pixel_layout, STBIR_TYPE_FLOAT, STBIR_EDGE_CLAMP, STBIR_FILTER_DEFAULT ); +} + + +STBIRDEF void * stbir_resize( const void *input_pixels , int input_w , int input_h, int input_stride_in_bytes, + void *output_pixels, int output_w, int output_h, int output_stride_in_bytes, + stbir_pixel_layout pixel_layout, stbir_datatype data_type, + stbir_edge edge, stbir_filter filter ) +{ + return (void *) stbir_quick_resize_helper( input_pixels , input_w , input_h, input_stride_in_bytes, + output_pixels, output_w, output_h, output_stride_in_bytes, + pixel_layout, data_type, edge, filter ); } #ifdef STBIR_PROFILE