@@ -54,12 +54,16 @@ typedef struct dssim_chan dssim_chan;
5454struct dssim_chan {
5555 int width , height ;
5656 dssim_px_t * img , * mu , * img_sq_blur ;
57- dssim_chan * next_half ;
5857 bool is_chroma ;
5958};
6059
60+ typedef struct dssim_image_chan {
61+ dssim_chan scales [MAX_SCALES ];
62+ int num_scales ;
63+ } dssim_image_chan ;
64+
6165struct dssim_image {
62- dssim_chan chan [MAX_CHANS ];
66+ dssim_image_chan chan [MAX_CHANS ];
6367 int num_channels ;
6468};
6569
@@ -150,16 +154,14 @@ static void dealloc_chan(dssim_chan *chan) {
150154 free (chan -> img );
151155 free (chan -> mu );
152156 free (chan -> img_sq_blur );
153- if (chan -> next_half ) {
154- dealloc_chan (chan -> next_half );
155- free (chan -> next_half );
156- }
157157}
158158
159159void dssim_dealloc_image (dssim_image * img )
160160{
161161 for (int ch = 0 ; ch < img -> num_channels ; ch ++ ) {
162- dealloc_chan (& img -> chan [ch ]);
162+ for (int s = 0 ; s < img -> chan [ch ].num_scales ; s ++ ) {
163+ dealloc_chan (& img -> chan [ch ].scales [s ]);
164+ }
163165 }
164166 free (img );
165167}
@@ -252,6 +254,11 @@ static void transpose(dssim_px_t *restrict src, dssim_px_t *restrict dst, const
252254static void regular_1d_blur (const dssim_px_t * src , dssim_px_t * restrict tmp1 , dssim_px_t * dst , const int width , const int height )
253255{
254256 const int runs = 2 ;
257+ assert (src );
258+ assert (tmp1 );
259+ assert (dst );
260+ assert (width > 4 );
261+ assert (height > 4 );
255262
256263 // tmp1 is expected to hold at least two lines
257264 dssim_px_t * restrict tmp2 = tmp1 + width ;
@@ -297,6 +304,9 @@ static void regular_1d_blur(const dssim_px_t *src, dssim_px_t *restrict tmp1, ds
297304static void blur (const dssim_px_t * restrict src , dssim_px_t * restrict tmp , dssim_px_t * restrict dst ,
298305 const int width , const int height )
299306{
307+ assert (src );
308+ assert (dst );
309+ assert (tmp );
300310#ifdef USE_COCOA
301311 vImage_Buffer srcbuf = {
302312 .width = width ,
@@ -378,8 +388,9 @@ static void subsampled_copy(dssim_chan *new_chan, const int dest_y_offset, const
378388
379389static void convert_image_subsampled (dssim_image * img , dssim_row_callback cb , void * callback_user_data )
380390{
381- const int width = img -> chan [0 ].width ;
382- const int height = img -> chan [0 ].height ;
391+ dssim_chan * chan = & img -> chan [0 ].scales [0 ];
392+ const int width = chan -> width ;
393+ const int height = chan -> height ;
383394 dssim_px_t * row_tmp0 [img -> num_channels ];
384395 dssim_px_t * row_tmp1 [img -> num_channels ];
385396
@@ -389,15 +400,15 @@ static void convert_image_subsampled(dssim_image *img, dssim_row_callback cb, vo
389400 }
390401
391402 for (int y = 0 ; y < height ; y += 2 ) {
392- row_tmp0 [0 ] = & img -> chan [ 0 ]. img [width * y ]; // Luma can be written directly (it's unscaled)
393- row_tmp1 [0 ] = & img -> chan [ 0 ]. img [width * MIN (height - 1 , y + 1 )];
403+ row_tmp0 [0 ] = & chan -> img [width * y ]; // Luma can be written directly (it's unscaled)
404+ row_tmp1 [0 ] = & chan -> img [width * MIN (height - 1 , y + 1 )];
394405
395406 cb (row_tmp0 , img -> num_channels , y , width , callback_user_data );
396407 cb (row_tmp1 , img -> num_channels , MIN (height - 1 , y + 1 ), width , callback_user_data );
397408
398409 if (y < height - 1 ) {
399410 for (int ch = 1 ; ch < img -> num_channels ; ch ++ ) { // Chroma is downsampled
400- subsampled_copy (& img -> chan [ch ], y /2 , 1 , row_tmp0 [ch ], width );
411+ subsampled_copy (& img -> chan [ch ]. scales [ 0 ] , y /2 , 1 , row_tmp0 [ch ], width );
401412 }
402413 }
403414 }
@@ -409,13 +420,14 @@ static void convert_image_subsampled(dssim_image *img, dssim_row_callback cb, vo
409420
410421static void convert_image_simple (dssim_image * img , dssim_row_callback cb , void * callback_user_data )
411422{
412- const int width = img -> chan [0 ].width ;
413- const int height = img -> chan [0 ].height ;
423+ dssim_chan * chan = & img -> chan [0 ].scales [0 ];
424+ const int width = chan -> width ;
425+ const int height = chan -> height ;
414426 dssim_px_t * row_tmp [img -> num_channels ];
415427
416428 for (int y = 0 ; y < height ; y ++ ) {
417429 for (int ch = 0 ; ch < img -> num_channels ; ch ++ ) {
418- row_tmp [ch ] = & img -> chan [ch ].img [width * y ];
430+ row_tmp [ch ] = & img -> chan [ch ].scales [ 0 ]. img [width * y ];
419431 }
420432 cb (row_tmp , img -> num_channels , y , width , callback_user_data );
421433 }
@@ -537,20 +549,7 @@ dssim_image *dssim_create_image(dssim_attr *attr, unsigned char *const *const ro
537549 return dssim_create_image_float_callback (attr , num_channels , width , height , converter , (void * )& im );
538550}
539551
540- dssim_chan * create_chan (const int width , const int height , const bool is_chroma ) {
541- assert (width > 0 && height > 0 );
542-
543- dssim_chan * const chan = malloc (sizeof (chan [0 ]));
544- * chan = (dssim_chan ){
545- .width = width ,
546- .height = height ,
547- .is_chroma = is_chroma ,
548- .img = malloc (width * height * sizeof (chan -> img [0 ])),
549- };
550- return chan ;
551- }
552-
553- static void dssim_preprocess_channel (dssim_chan * chan , dssim_px_t * tmp , int depth );
552+ static void dssim_preprocess_channel (dssim_chan * chan , dssim_px_t * tmp );
554553
555554dssim_image * dssim_create_image_float_callback (dssim_attr * attr , const int num_channels , const int width , const int height , dssim_row_callback cb , void * callback_user_data )
556555{
@@ -567,10 +566,29 @@ dssim_image *dssim_create_image_float_callback(dssim_attr *attr, const int num_c
567566
568567 for (int ch = 0 ; ch < img -> num_channels ; ch ++ ) {
569568 const bool is_chroma = ch > 0 ;
570- img -> chan [ch ] = * create_chan (
571- subsample_chroma && is_chroma ? width /2 : width ,
572- subsample_chroma && is_chroma ? height /2 : height ,
573- is_chroma );
569+ int chan_width = subsample_chroma && is_chroma ? width /2 : width ;
570+ int chan_height = subsample_chroma && is_chroma ? height /2 : height ;
571+ int s = 0 ;
572+ for (; s < attr -> num_scales ; s ++ ) {
573+ img -> chan [ch ].scales [s ] = (dssim_chan ){
574+ .width = chan_width ,
575+ .height = chan_height ,
576+ .is_chroma = is_chroma ,
577+ .img = malloc (chan_width * chan_height * sizeof (img -> chan [ch ].scales [s ].img [0 ])),
578+ };
579+ chan_width /= 2 ;
580+ chan_height /= 2 ;
581+ if (chan_width < 8 || chan_height < 8 ) {
582+ break ;
583+ }
584+ }
585+ img -> chan [ch ].num_scales = s ;
586+ }
587+
588+ for (int ch = 0 ; ch < img -> num_channels ; ch ++ ) {
589+ for (int s = 0 ; s < img -> chan [ch ].num_scales ; s ++ ) {
590+ assert (img -> chan [ch ].scales [s ].img );
591+ }
574592 }
575593
576594 if (subsample_chroma && img -> num_channels > 1 ) {
@@ -579,26 +597,39 @@ dssim_image *dssim_create_image_float_callback(dssim_attr *attr, const int num_c
579597 convert_image_simple (img , cb , callback_user_data );
580598 }
581599
600+
601+ for (int ch = 0 ; ch < img -> num_channels ; ch ++ ) {
602+ for (int s = 0 ; s < img -> chan [ch ].num_scales ; s ++ ) {
603+ assert (img -> chan [ch ].scales [s ].img );
604+ }
605+ }
606+
582607 dssim_px_t * tmp = dssim_get_tmp (attr , width * height * sizeof (tmp [0 ]));
583608 for (int ch = 0 ; ch < img -> num_channels ; ch ++ ) {
584- dssim_preprocess_channel (& img -> chan [ch ], tmp , attr -> num_scales );
609+ const dssim_chan * prev_chan = & img -> chan [ch ].scales [0 ];
610+ for (int s = 1 ; s < img -> chan [ch ].num_scales ; s ++ ) {
611+ dssim_chan * new_chan = & img -> chan [ch ].scales [s ];
612+ subsampled_copy (new_chan , 0 , new_chan -> height , prev_chan -> img , prev_chan -> width );
613+ prev_chan = new_chan ;
614+ }
615+ for (int s = 0 ; s < img -> chan [ch ].num_scales ; s ++ ) {
616+ dssim_preprocess_channel (& img -> chan [ch ].scales [s ], tmp );
617+ }
585618 }
586619
587620 return img ;
588621}
589622
590- static void dssim_preprocess_channel (dssim_chan * chan , dssim_px_t * tmp , int num_scales )
623+ static void dssim_preprocess_channel (dssim_chan * chan , dssim_px_t * tmp )
591624{
625+ assert (chan );
626+ assert (tmp );
627+ assert (chan -> img );
628+ assert (!chan -> mu );
629+ assert (!chan -> img_sq_blur );
592630 const int width = chan -> width ;
593631 const int height = chan -> height ;
594632
595- if (num_scales > 1 && chan -> width >= 8 && chan -> height >= 8 ) {
596- dssim_chan * new_chan = create_chan (chan -> width /2 , chan -> height /2 , chan -> is_chroma );
597- chan -> next_half = new_chan ;
598- subsampled_copy (new_chan , 0 , new_chan -> height , chan -> img , chan -> width );
599- dssim_preprocess_channel (new_chan , tmp , num_scales - 1 );
600- }
601-
602633 if (chan -> is_chroma ) {
603634 blur (chan -> img , tmp , chan -> img , width , height );
604635 }
@@ -621,6 +652,9 @@ static dssim_px_t *get_img1_img2_blur(const dssim_chan *restrict original, dssim
621652 dssim_px_t * restrict img1 = original -> img ;
622653 dssim_px_t * restrict img2 = modified -> img ; modified -> img = NULL ; // img2 is turned in-place into blur(img1*img2)
623654
655+ assert (img1 );
656+ assert (img2 );
657+
624658 for (int j = 0 ; j < width * height ; j ++ ) {
625659 img2 [j ] *= img1 [j ];
626660 }
@@ -653,19 +687,22 @@ double dssim_compare(dssim_attr *attr, const dssim_image *restrict original_imag
653687 const int channels = MIN (original_image -> num_channels , modified_image -> num_channels );
654688 assert (channels > 0 );
655689
656- dssim_px_t * tmp = dssim_get_tmp (attr , original_image -> chan [0 ].width * original_image -> chan [0 ].height * sizeof (tmp [0 ]));
690+ dssim_px_t * tmp = dssim_get_tmp (attr , original_image -> chan [0 ].scales [ 0 ]. width * original_image -> chan [ 0 ]. scales [0 ].height * sizeof (tmp [0 ]));
657691 assert (tmp );
658692
659693 double ssim_sum = 0 ;
660694 double weight_sum = 0 ;
661695 for (int ch = 0 ; ch < channels ; ch ++ ) {
662696
663- const dssim_chan * original = & original_image -> chan [ch ];
664- dssim_chan * modified = & modified_image -> chan [ch ];
665- assert (original );
666- assert (modified );
697+ const dssim_image_chan * original_scales = & original_image -> chan [ch ];
698+ dssim_image_chan * modified_scales = & modified_image -> chan [ch ];
699+
700+ int num_scales = MIN (original_scales -> num_scales , modified_scales -> num_scales );
701+
702+ for (int n = 0 ; n < num_scales ; n ++ ) {
703+ const dssim_chan * original = & original_scales -> scales [n ];
704+ dssim_chan * modified = & modified_scales -> scales [n ];
667705
668- for (int n = 0 ; n < attr -> num_scales ; n ++ ) {
669706 const double weight = (original -> is_chroma ? attr -> color_weight : 1.0 ) * attr -> scale_weights [n ];
670707
671708 const bool save_maps = attr -> save_maps_scales > n && attr -> save_maps_channels > ch ;
@@ -677,11 +714,6 @@ double dssim_compare(dssim_attr *attr, const dssim_image *restrict original_imag
677714 assert (modified );
678715 ssim_sum += weight * dssim_compare_channel (original , modified , tmp , & attr -> ssim_maps [ch ].scales [n ], save_maps );
679716 weight_sum += weight ;
680- original = original -> next_half ;
681- modified = modified -> next_half ;
682- if (!original || !modified ) {
683- break ;
684- }
685717 }
686718 }
687719
0 commit comments