1 #include "mrilib.h"
2 #include "xutil.h"
3 #include "xim.h"
4 
5 /*--------------------------------------------------------------------------*/
6 /** AFNI_XDrawLines() replacement for XDrawLines() **/
7 /*--------------------------------------------------------------------------*/
8 
9 /*======================================================================*/
10 /* Upsampling code, adapted from code in mri_dup.c [28 May 2020] */
11 
12 #ifndef SHORTIZE
13 # define SHORTIZE(xx) (  ((xx) < -32767.0f) ? (short)-32767                    \
14                        : ((xx) >  32767.0f) ? (short) 32767 : (short)rint(xx) )
15 #endif
16 
17 #define RENUP_VEC(vv,kk)  { (vv) = (float *)realloc((vv),(kk)*sizeof(float)); }
18 
19 /*-- seventh order interpolation polynomials --*/
20 
21 #define S_M3(x) (x*(x*x-1.0f)*(x*x-4.0f)*(x-3.0f)*(4.0f-x)*0.0001984126984f)
22 #define S_M2(x) (x*(x*x-1.0f)*(x-2.0f)*(x*x-9.0f)*(x-4.0f)*0.001388888889f)
23 #define S_M1(x) (x*(x-1.0f)*(x*x-4.0f)*(x*x-9.0f)*(4.0f-x)*0.004166666667f)
24 #define S_00(x) ((x*x-1.0f)*(x*x-4.0f)*(x*x-9.0f)*(x-4.0f)*0.006944444444f)
25 #define S_P1(x) (x*(x+1.0f)*(x*x-4.0f)*(x*x-9.0f)*(4.0f-x)*0.006944444444f)
26 #define S_P2(x) (x*(x*x-1.0f)*(x+2.0f)*(x*x-9.0f)*(x-4.0f)*0.004166666667f)
27 #define S_P3(x) (x*(x*x-1.0f)*(x*x-4.0f)*(x+3.0f)*(4.0f-x)*0.001388888889f)
28 #define S_P4(x) (x*(x*x-1.0f)*(x*x-4.0f)*(x*x-9.0f)*0.0001984126984f)
29 
30 /* 7 point interpolate as floats, cast to short at end */
31 
32 #define INT7(k,i)                                                \
33   ( vout = ((k)==0) ? ( far[i] )                                 \
34                     : ( fm3[k] * far[i-3] + fm2[k] * far[i-2]    \
35                       + fm1[k] * far[i-1] + f00[k] * far[i  ]    \
36                       + fp1[k] * far[i+1] + fp2[k] * far[i+2]    \
37                       + fp3[k] * far[i+3] + fp4[k] * far[i+4]  ), SHORTIZE(vout) )
38 
39 /*----------------------------------------------------------------------------
40   Up sample a short-valued array sar[0..nar-1] nup times to produce
41   sout[0..(nar-1)*nup] -- so have (nar-1)*nup+1 output points.
42   Uses 7th order polynomial interpolation (mostly).
43   NOTE: the number of output points, and the use of shorts, is where
44         this function differs from the original in mri_dup.c
45   NOTE: for use in AFNI_XDrawLines(), which uses XPoint,
46         which uses shorts as the pixel locations
47 ------------------------------------------------------------------------------*/
48 
upsample_7short(int nup,int nar,short * sar,short * sout)49 static void upsample_7short( int nup , int nar , short *sar , short *sout )
50 {
51    int kk,ii , ibot,itop ;
52    float nupi , val,vout ;
53    /* static arrays for interpolation */
54    static int nupold = -1 ;
55    static int nupmax = 0;
56    static float *fm3=NULL, *fm2=NULL, *fm1=NULL, *f00=NULL,
57                 *fp1=NULL, *fp2=NULL, *fp3=NULL, *fp4=NULL;
58    static float *far=NULL , *qar=NULL ;
59 
60    /*-- sanity checks --*/
61 
62    if( nup < 1 || nar < 2 || sar == NULL || sout == NULL ) return ;
63 
64    if( nup == 1 ){ memcpy( sout, sar, sizeof(short)*nar ); return; }
65 
66    /* temp float array for data;
67       with trickery (pointer arithmetic) to use negative subscripts,
68       because we need input [i-3]..[i+4] values to interpolate the
69       output values between input indexes i and i+1, for i=0..nar-2 */
70 
71    qar = (float *)malloc(sizeof(float)*(nar+7)) ;
72    far = qar + 3 ; /* start the indexing trickery! */
73    for( kk=0 ; kk < nar ; kk++ ) far[kk] = (float)sar[kk] ;
74    val = far[1] - far[0] ;
75    far[-1] = far[0] - 3.0f*val ; /* linear extrapolate before start */
76    far[-2] = far[0] - 2.0f*val ;
77    far[-3] = far[0] -      val ;
78    val = far[nar-1] - far[nar-2] ;
79    far[nar]   = far[nar-1] +      val ; /* and after end */
80    far[nar+1] = far[nar-1] + 2.0f*val ;
81    far[nar+2] = far[nar-1] + 3.0f*val ;
82    far[nar+3] = far[nar-1] + 4.0f*val ; /* this value not really needed */
83 
84    nupi = 1.0f / (float)nup ;
85 
86    /*-- initialize 7th order interpolation coefficients, if nup has changed --*/
87 
88    if (nupmax < nup) { /* resize coefficient arrays if bigger than of old */
89       RENUP_VEC(fm3,nup); RENUP_VEC(fm2,nup);
90       RENUP_VEC(fm1,nup); RENUP_VEC(f00,nup);
91       RENUP_VEC(fp1,nup); RENUP_VEC(fp2,nup);
92       RENUP_VEC(fp3,nup); RENUP_VEC(fp4,nup);
93       nupmax = nup ; /* keep track of largest array size ever used */
94    }
95 
96    if( nup != nupold ){ /* recalculate coefs if not the same as last time in */
97      for( kk=0 ; kk < nup ; kk++ ){
98        val = ((float)kk) * nupi ;
99        fm3[kk] = S_M3(val); fm2[kk] = S_M2(val); fm1[kk] = S_M1(val);
100        f00[kk] = S_00(val); fp1[kk] = S_P1(val); fp2[kk] = S_P2(val);
101        fp3[kk] = S_P3(val); fp4[kk] = S_P4(val);
102      }
103      nupold = nup ; /* for the historical records */
104    }
105 
106    /*-- FINALLY: interpolate --*/
107 
108    ibot = 0 ; itop = nar-2 ;  /* add points between [ii] and [ii+1] */
109 
110    switch( nup ){
111       default:       /* outer and inner loops */
112         for( ii=ibot ; ii <= itop ; ii++ )
113           for( kk=0 ; kk < nup ; kk++ ) sout[kk+ii*nup] = INT7(kk,ii) ;
114       break ;
115 
116       case 2:        /* inner loop unrolled for optimizer */
117         for( ii=ibot ; ii <= itop ; ii++ ){
118           sout[ii*nup]   = INT7(0,ii) ; sout[ii*nup+1]  = INT7(1,ii) ;
119         }
120       break ;
121 
122       case 3:        /* inner loop unrolled for optimizer */
123         for( ii=ibot ; ii <= itop ; ii++ ){
124           sout[ii*nup]   = INT7(0,ii) ; sout[ii*nup+1]  = INT7(1,ii) ;
125           sout[ii*nup+2] = INT7(2,ii) ;
126         }
127       break ;
128 
129       case 4:        /* inner loop unrolled for optimizer */
130         for( ii=ibot ; ii <= itop ; ii++ ){
131           sout[ii*nup]   = INT7(0,ii) ; sout[ii*nup+1]  = INT7(1,ii) ;
132           sout[ii*nup+2] = INT7(2,ii) ; sout[ii*nup+3]  = INT7(3,ii) ;
133         }
134       break ;
135    }
136    sout[(nar-1)*nup] = sar[nar-1] ; /* need final value */
137 
138    free(qar) ;
139    return ;
140 }
141 
142 /*--------------------------------------------------------------------------*/
143 
144 /* Hermite spline basis funcs (damn, them Frenchies was SMART) */
145 
146 #define H00(x) ( (x)*(x)*(2.0f*(x)-3.0f)+1.0f )
147 #define H01(x) ( (x)*(x)*(3.0f-2.0f*(x)) )
148 #define H10(x) ( (x)*((x)*(x)-2.0f*(x)+1.0f) )
149 #define H11(x) ( (x)*(x)*((x)-1.0f) )
150 
151 /* interpolate as floats, cast to short at end */
152 
153 #define INTM(k,i)                                             \
154   ( vout = ((k)==0) ? ( far[i] )                              \
155                     : ( h00[k] * far[i] + h01[k] * far[i+1]   \
156                       + h10[k] * mk[i]  + h11[k] * mk[i+1] ) , SHORTIZE(vout) )
157 
158 /*-------- Monotonic cubic spline interpolation:
159            https://en.wikipedia.org/wiki/Monotone_cubic_interpolation --------*/
160 
upsample_monoshort(int nup,int nar,short * sar,short * sout)161 static void upsample_monoshort( int nup , int nar , short *sar , short *sout )
162 {
163    int kk,ii , ibot,itop ;
164    float nupi , val,vout , ak,bk,tk , dmax=0.0f,fmax=0.0f ;
165    /* static arrays to avoid reallocation a milliard of times */
166    static int nupold = -1 ;
167    static int nupmax = 0;
168    static float *far , *dk , *mk ;
169    static float *h00=NULL , *h01=NULL , *h10=NULL , *h11=NULL ;
170 
171    /*-- sanity checks --*/
172 
173    if( nup < 1 || nar < 2 || sar == NULL || sout == NULL ) return ;
174 
175    if( nup == 1 ){ memcpy( sout, sar, sizeof(short)*nar ); return; }
176 
177    /* temp float arrays for data, etc */
178 
179    far = (float *)calloc(sizeof(float),nar) ;
180    dk  = (float *)calloc(sizeof(float),nar) ; /* secants */
181    mk  = (float *)calloc(sizeof(float),nar) ; /* slopes */
182 
183    for( kk=0 ; kk < nar   ; kk++ ){  /* copy input to floats */
184      far[kk] = (float)sar[kk] ;
185      val     = fabsf(far[kk]) ; if( val > fmax ) fmax = val ;
186    }
187 
188    for( kk=0 ; kk < nar-1 ; kk++ ){  /* get secants */
189      dk[kk] = far[kk+1]-far[kk] ;
190      val    = fabsf(dk[kk]) ; if( val > dmax ) dmax = val ;
191    }
192 
193    mk[0] = dk[0] ; mk[nar-1] = dk[nar-2] ; /* get slopes */
194    for( kk=1 ; kk < nar-1 ; kk++ ) mk[kk]  = 0.5f*(dk[kk]*dk[kk-1]) ;
195 
196    /* adjust slopes for monotonicity */
197 
198    vout = 0.001f*dmax + 0.0000001f*fmax ;
199    for( kk=0 ; kk < nar-1 ; kk++ ){
200      if( fabsf(dk[kk]) <= vout ){
201        mk[kk] = mk[kk+1] = 0.0f ;
202      } else {
203        ak = mk[kk] / dk[kk] ; bk = mk[kk+1] / dk[kk] ;
204        if( ak < 0.0f ){ mk[kk]   = 0.0f ; ak = 0.0f ; }
205        if( bk < 0.0f ){ mk[kk+1] = 0.0f ; bk = 0.0f ; }
206        val = ak*ak+bk*bk ;
207        if( val > 9.0f ){
208          tk = 3.0f / sqrtf(val) ;
209          mk[kk]   = tk * ak * dk[kk] ;
210          mk[kk+1] = tk * bk * dk[kk] ;
211        }
212      }
213    }
214 
215    nupi = 1.0f / (float)nup ;
216 
217    /*-- initialize Hermite interpolation coefficients, if nup has changed --*/
218 
219    if (nupmax < nup) { /* resize coefficient arrays if bigger than of old */
220       RENUP_VEC(h00,nup); RENUP_VEC(h01,nup);
221       RENUP_VEC(h10,nup); RENUP_VEC(h11,nup);
222       nupmax = nup ; /* keep track of largest every used */
223    }
224 
225    if( nup != nupold ){ /* recalculate if not the same as last time in */
226      for( kk=0 ; kk < nup ; kk++ ){
227        val = ((float)kk) * nupi ;
228        h00[kk] = H00(val) ; h01[kk] = H01(val) ;
229        h10[kk] = H10(val) ; h11[kk] = H11(val) ;
230      }
231      nupold = nup ; /* for the historical records */
232    }
233 
234    /*-- FINALLY: interpolate --*/
235 
236    ibot = 0 ; itop = nar-2 ;  /* add points between [ii] and [ii+1] */
237 
238    switch( nup ){
239       default:       /* outer and inner loops */
240         for( ii=ibot ; ii <= itop ; ii++ )
241           for( kk=0 ; kk < nup ; kk++ ) sout[kk+ii*nup] = INTM(kk,ii) ;
242       break ;
243 
244       case 2:        /* inner loop unrolled for optimizer */
245         for( ii=ibot ; ii <= itop ; ii++ ){
246           sout[ii*nup]   = INTM(0,ii) ; sout[ii*nup+1]  = INTM(1,ii) ;
247         }
248       break ;
249 
250       case 3:        /* inner loop unrolled for optimizer */
251         for( ii=ibot ; ii <= itop ; ii++ ){
252           sout[ii*nup]   = INTM(0,ii) ; sout[ii*nup+1]  = INTM(1,ii) ;
253           sout[ii*nup+2] = INTM(2,ii) ;
254         }
255       break ;
256 
257       case 4:        /* inner loop unrolled for optimizer */
258         for( ii=ibot ; ii <= itop ; ii++ ){
259           sout[ii*nup]   = INTM(0,ii) ; sout[ii*nup+1]  = INTM(1,ii) ;
260           sout[ii*nup+2] = INTM(2,ii) ; sout[ii*nup+3]  = INTM(3,ii) ;
261         }
262       break ;
263    }
264    sout[(nar-1)*nup] = sar[nar-1] ; /* need final value */
265 
266    free(mk) ; free(dk) ; free(far) ;
267    return ;
268 }
269 
270 /*--------- Upsample/interpolate both ways, and combine them;
271             Why? It looked better than either way alone, to me [RWC] ---------*/
272 
upsample_comboshort(int nup,int nar,short * sar,short * sout)273 static void upsample_comboshort( int nup , int nar , short *sar , short *sout )
274 {
275    short *st1 , *st2 ; int ii,nout=(nar-1)*nup+1 ; float val ;
276 
277    if( nup < 1 || nar < 2 || sar == NULL || sout == NULL ) return ;
278    if( nup == 1 ){ memcpy( sout, sar, sizeof(short)*nar ); return; }
279 
280    st1 = (short *)malloc(sizeof(short)*nout) ;
281    st2 = (short *)malloc(sizeof(short)*nout) ;
282 
283    upsample_7short   ( nup,nar,sar,st1 ) ;
284    upsample_monoshort( nup,nar,sar,st2 ) ;
285    for( ii=0 ; ii < nout ; ii++ ){
286      val = 0.3f*(float)st1[ii] + 0.7f*(float)st2[ii] ; /* mixing */
287      sout[ii] = SHORTIZE(val) ;
288    }
289    free(st2) ; free(st1) ; return ;
290 }
291 
292 /**---- this macro defines the function used for the actual upsampling ----**/
293 
294 #define UPSAMPLE upsample_comboshort
295 
296 /*--------------------------------------------------------------------------*/
297 /* Draw X11 lines, but upsampled to look smoother and more delicious  */
298 
AFNI_XDrawLines(Display * display,Drawable d,GC gc,XPoint * points,int npoints,int mode,int nupsam)299 void AFNI_XDrawLines( Display *display, Drawable d,
300                       GC gc, XPoint *points, int npoints, int mode , int nupsam )
301 {
302    XPoint *new_points ;
303    int     new_npoints , ii ;
304    short  *old_xy , *new_xy ;
305 
306    /* this is for the jaggedy losers */
307 
308    if( nupsam <= 1 || npoints < 3 ){
309      XDrawLines(display,d,gc,points,npoints,mode) ; return ;
310    }
311 
312    /* this is for the REAL MEN out there (insert Tarzan yell) */
313 
314    new_npoints = (npoints-1)*nupsam+1 ;
315    new_points  = (XPoint *)malloc(sizeof(XPoint)*new_npoints) ;
316 
317    old_xy = (short *)malloc(sizeof(short)*npoints) ;
318    new_xy = (short *)malloc(sizeof(short)*new_npoints) ;
319 
320    /* upsample the x coordinates */
321    for( ii=0 ; ii < npoints ; ii++ ) old_xy[ii] = points[ii].x ;
322    UPSAMPLE( nupsam , npoints , old_xy , new_xy ) ;
323    for( ii=0 ; ii < new_npoints ; ii++ ) new_points[ii].x = new_xy[ii] ;
324 
325    /* upsample the y coordinates */
326    for( ii=0 ; ii < npoints ; ii++ ) old_xy[ii] = points[ii].y ;
327    UPSAMPLE( nupsam , npoints , old_xy , new_xy ) ;
328    for( ii=0 ; ii < new_npoints ; ii++ ) new_points[ii].y = new_xy[ii] ;
329 
330    /* and draw the straight lines between them */
331    XDrawLines(display,d,gc,new_points,new_npoints,mode) ;
332 
333    free(new_xy) ; free(old_xy) ; free(new_points) ; return ;
334 }
335 
336 /*----------------------------------------------------------------------------*/
337 /* Similar code for drawing a smooted (smoothed?) filled polygon (for pmplot) */
338 /* https://en.wikipedia.org/wiki/Reed_Smoot                                   */
339 /* https://en.wikipedia.org/wiki/Oliver_R._Smoot                              */
340 /*----------------------------------------------------------------------------*/
341 
AFNI_XFillPolygon(Display * display,Drawable d,GC gc,XPoint * points,int npoints,int shape,int mode,int nupsam)342 void AFNI_XFillPolygon( Display *display, Drawable d,
343                         GC gc, XPoint *points, int npoints, int shape ,
344                         int mode , int nupsam )
345 {
346    XPoint *new_points ;
347    int     new_npoints , ii ;
348    short  *old_xy , *new_xy ;
349 
350    if( nupsam <= 1 ){
351      XFillPolygon(display,d,gc,points,npoints,shape,mode) ; return ;
352    }
353 
354    new_npoints = (npoints-1)*nupsam+1 ;
355    new_points  = (XPoint *)malloc(sizeof(XPoint)*new_npoints) ;
356 
357    old_xy = (short *)malloc(sizeof(short)*npoints) ;
358    new_xy = (short *)malloc(sizeof(short)*new_npoints) ;
359 
360    for( ii=0 ; ii < npoints ; ii++ ) old_xy[ii] = points[ii].x ;
361    UPSAMPLE( nupsam , npoints , old_xy , new_xy ) ;
362    for( ii=0 ; ii < new_npoints ; ii++ ) new_points[ii].x = new_xy[ii] ;
363 
364    for( ii=0 ; ii < npoints ; ii++ ) old_xy[ii] = points[ii].y ;
365    UPSAMPLE( nupsam , npoints , old_xy , new_xy ) ;
366    for( ii=0 ; ii < new_npoints ; ii++ ) new_points[ii].y = new_xy[ii] ;
367 
368    XFillPolygon(display,d,gc,new_points,new_npoints,shape,mode) ;
369 
370    free(new_xy) ; free(old_xy) ; free(new_points) ; return ;
371 }
372