1 /*
2   A texture synthesizing engine for bitmapped images.
3 
4   The algorithm is due to Paul Harrison and others.
5 
6   Copyright (C) 2010, 2011  Lloyd Konneker
7 
8   This program is free software; you can redistribute it and/or modify
9   it under the terms of the GNU General Public License as published by
10   the Free Software Foundation; either version 2 of the License, or
11   (at your option) any later version.
12 
13   This program is distributed in the hope that it will be useful,
14   but WITHOUT ANY WARRANTY; without even the implied warranty of
15   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16   GNU General Public License for more details.
17 
18   You should have received a copy of the GNU General Public License
19   along with this program; if not, write to the Free Software
20   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
21 */
22 
23 /*
24 Inner engine.
25 Parameters include target and corpus pixmaps.
26 Pixmaps have internal pixel format, which includes mask, color, alpha, and map.
27 (where map means an additional e.g. grayscale value for mapping corpus to target.)
28 Target and corpus pixmaps have the same pixel format.
29 (An adapter can relax the restriction that both have the same format,
30 by adding e.g. an alpha channel to one.)
31 */
32 
33 /*
34 Notes:
35 
36 The selection:
37 
38 A caller of the engine may be an application supporting "selection."
39 Then a caller should adapt the selection mask to separate masks for the target and corpus.
40 Generally, in such a case, the masks should generally be mutually exclusive,
41 especially regarding partial selection.
42 But this is not a concern of the engine.
43 
44 The alpha:
45 
46 In prior versions the alpha was treated like a color channel, and matched during synthesis.
47 Transparent pixels (which Gimp arbitrarily gives the color black in some circumstances)
48 were not distinguished.  In certain cases  with transparency, transparent pixels were synthesized
49 into the target, as good matches for black.
50 
51 Here, we don't match the alpha channel between target and corpus.
52 We don't generate any alpha in the target, instead we leave the target alpha unaltered.
53 We use the alpha to determine what pixels are in the target and corpus,
54 (similar to a selection mask.)
55 Any totally transparent pixel in the target selection IS synthesized,
56 I.E. a color is generated (but since it is totally transparent, you don't see it.)
57 Any partially transparent target pixel is also synthesized, except as stated,
58 the alpha is not matched (so colors from opaque areas of the corpus
59 could be synthesized into partially transparent areas of the target.)
60 Any totally transparent pixel in the corpus is not in the corpus, i.e. never matched.
61 Any partially transparent pixel in the corpus is a candidate for matching.
62 A color from a partially transparent pixel in the corpus could be synthesized
63 into an opaque area of the target.
64 Again, the transparency of the target is retained even as new colors are synthesized.
65 
66 Tiling: (see parameters horizontal and vertical tiling)
67 This means we synthesize a target that is *seamlessly* tileable.
68 We treat the target as a sphere, wrapping a coord outside the target around
69 to the opposite side.  See wrapOrClipTarget.
70 It doesn't make tiles in the target, it makes a target that is suitable as a tile.
71 */
72 
73 // Compiling switch #defines
74 #include "buildSwitches.h"
75 
76 #include <math.h>
77 
78 #ifdef SYNTH_USE_GLIB
79   #include "../config.h" // GNU buildtools local configuration
80   // Use glib via gimp.h
81   // #include <libgimp/gimp.h>
82   #include <glib.h>
83 #endif
84 
85 
86 #ifdef USE_GLIB_PROXY
87   #include <stddef.h>   // size_t
88   #include "glibProxy.h"  // Redefines the glib structs and routines used here
89   #include <math.h> // atan2(), log()
90   /*
91   More proxy.  Redefine GRand routines
92   On platform Linux, used Glib g_rand
93   On platform OSX (when using stdc but not Glib), proxy calls stdc rand()
94   */
95   #define g_rand_new_with_seed(s) s_rand_new_with_seed(s)
96   #define g_rand_int_range(r,u,l) s_rand_int_range(r,u,l)
97 #endif
98 
99 /* Shared with resynth-gui, engine plugin, and engine */
100 #include "imageSynthConstants.h"
101 
102 // True header files
103 #include "imageFormat.h"
104 #include "imageFormatIndicies.h"
105 
106 #include "map.h"
107 #include "engineParams.h"
108 #include "engine.h"
109 
110 /*
111 Source not compiled separately. Is separate to reduce file sizes and coupling.
112 Also, some functions are inlined.
113 */
114 
115 #include "engineTypes.h"
116 #include "mapIndex.h" // inlined, used in innermost loop
117 #include "mapOps.h"   // definitions for map.h
118 #include "matchWeighting.h"
119 #include "orderTarget.h"
120 
121 
122 #ifdef STATS
123 /* For development */
124 guint countSourceTries = 0;
125 guint countTargetTries = 0;
126 guint total_targets = 0;  /* Total target attempts over all passes, barring short circuit. */
127 
128 // remember the most recent kind of corpus point that bettered the previous best
129 guint bettermentStats[MAX_BETTERMENT_KIND];
130 #endif
131 
132 
133 /*
134 Class hasValue
135 
136 Whether a pixel in the image is ready to be matched.
137 Value more or less means a color; not an undefined color.
138 Pixels in the context: if they are not clipped, not transparent, etc.
139 Pixels in the target: if they have been synthesized.
140 
141 TODO
142 Alternate way of computing hasValue from targetMask, alpha channel, and whether synthesized (has_source.)
143 Might use less memory and have better locality.
144 But hasValue is only called in prepare_neighbors, not as costly as rest of search.
145 
146 TODO hasValue array is larger than it needs to be?
147 It could it be just the size of the target plus a band, since it is only used for neighbors.
148 Would require different wrapOrClipTarget().
149 Might affect performance of cache or memory swapping
150 */
151 
152 static inline void
setHasValue(Coordinates * coords,guchar value,Map * hasValueMap)153 setHasValue( Coordinates *coords, guchar value, Map* hasValueMap)
154 {
155   *bytemap_index(hasValueMap, *coords) = value;
156 }
157 
158 static inline gboolean
getHasValue(Coordinates coords,Map * hasValueMap)159 getHasValue(Coordinates coords, Map* hasValueMap)
160 {
161   return (* bytemap_index(hasValueMap, coords));
162 }
163 
164 static inline void
prepareHasValue(Map * targetMap,Map * hasValueMap)165 prepareHasValue(Map* targetMap, Map* hasValueMap)
166 {
167   new_bytemap(hasValueMap, targetMap->width, targetMap->height);
168 }
169 
170 
171 /*
172 Class sourceOfMap
173 
174 Whether a target pixel has a source in the corpus (from synthesis).
175 TODO Possibly we can use index into corpusPoints instead of coordinates.
176 TODO The map only needs to be the size of the target?
177 But we are calling getSourceOf(neighbor_point), which can be points outside
178 of the target (context), whose source is always themselves.
179 However, the extra memory is probably not a resource problem,
180 and probably not a performance problem because it is only used in prepare_neighbors,
181 the source are copied to a dense structure neighbor_sources for the inner search.
182 */
183 
184 
185 static inline void
setSourceOf(Coordinates target_point,Coordinates source_corpus_point,Map * sourceOfMap)186 setSourceOf (
187   Coordinates target_point,
188   Coordinates source_corpus_point,
189   Map* sourceOfMap
190   )
191 {
192   *coordmap_index(sourceOfMap, target_point) = source_corpus_point;
193 }
194 
195 
196 static inline Coordinates
getSourceOf(Coordinates target_point,Map * sourceOfMap)197 getSourceOf (
198   Coordinates target_point,
199   Map* sourceOfMap
200   )
201 {
202   return *coordmap_index(sourceOfMap, target_point);
203 }
204 
205 
206 /* Initially, no target points have source in corpus, i.e. none synthesized. */
207 static void
prepare_target_sources(Map * targetMap,Map * sourceOfMap)208 prepare_target_sources(
209   Map* targetMap,
210   Map* sourceOfMap)
211 {
212   guint x;
213   guint y;
214   Coordinates null_coords = {-1, -1};
215 
216   new_coordmap(sourceOfMap, targetMap->width, targetMap->height);
217 
218   for(y=0; y<targetMap->height; y++)
219     for(x=0; x<targetMap->width; x++)
220       {
221       Coordinates coords = {x,y};
222       setSourceOf(coords, null_coords, sourceOfMap);
223       }
224 }
225 
226 static inline gboolean
has_source(Coordinates target_point,Map * sourceOfMap)227 has_source (
228   Coordinates target_point,
229   Map* sourceOfMap
230   )
231 {
232   return (getSourceOf(target_point, sourceOfMap).x != -1) ;
233 }
234 
235 
236 
237 
238 /*
239 Is the pixel selected in the corpus?
240 !!! Note dithered, partial selection: only one value is totally unselected or selected.
241 Here, only pixels fully selected return True.
242 This is because when target/corpus are differentiated by the same selection,
243 partially selected will be in the target,
244 only fully selected (the inverse) will be the corpus.
245 !!! Note this is called from the bottleneck.
246 */
247 static inline gboolean
isSelectedCorpus(const Coordinates coords,const Map * const corpusMap)248 isSelectedCorpus (
249   const Coordinates coords,
250   const Map* const corpusMap
251   )
252 {
253   /*
254   Was:  != MASK_UNSELECTED); i.e. partially selected was included.
255   Now: if partially selected, excluded from corpus.
256   */
257   return (pixmap_index(corpusMap, coords)[MASK_PIXELEL_INDEX] == MASK_TOTALLY_SELECTED);
258   }
259 
260 
261 /*
262 Is the pixel selected in the image?
263 The selection is the mask, created by user to distinguish.
264 In the target map, distinguishes target(what is synthesized) from context.
265 In the corpus map, distinguishes undesired corpus from desired corpus.
266 */
267 static inline gboolean
isSelectedTarget(Coordinates coords,Map * imageMap)268 isSelectedTarget(
269   Coordinates coords,
270   Map * imageMap )
271 {
272   return (pixmap_index(imageMap, coords)[MASK_PIXELEL_INDEX] != MASK_UNSELECTED);
273 }
274 
275 
276 /*
277 Return whether this pixel has any opacity.
278 If it has some opacity (not totally transparent) than it contributes to the visible.
279 Our strategy is to synthesize target pixels with any opacity,
280 and use context pixels with any opacity.
281 */
282 static inline gboolean
not_transparent_image(Coordinates coords,TFormatIndices * indices,Map * targetMap)283 not_transparent_image(
284   Coordinates coords,
285   TFormatIndices* indices,
286   Map * targetMap
287   )
288 {
289   return ( indices->isAlphaTarget ? pixmap_index(targetMap, coords)[indices->alpha_bip] != ALPHA_TOTAL_TRANSPARENCY : TRUE);
290 }
291 
292 static inline gboolean
not_transparent_corpus(Coordinates coords,TFormatIndices * indices,Map * corpusMap)293 not_transparent_corpus(
294   Coordinates coords,
295   TFormatIndices* indices,
296   Map* corpusMap
297   )
298 {
299   return ( indices->isAlphaSource ? pixmap_index(corpusMap, coords)[indices->alpha_bip] != ALPHA_TOTAL_TRANSPARENCY : TRUE);
300 }
301 
302 
303 /* Included here because it depends on some routines above. */
304 // If STATS is not defined, it redefines stat function calls to nil
305 #include "stats.h"
306 
307 
308 /*
309 Array of index of most recent target point that probed this corpus point
310 (recentProberMap[corpus x,y] = target)
311 !!! Larger than necessary if the corpus has holes in it.  TODO very minor.
312 !!! Note recentProberMap is unsigned, -1 == 0xFFFFFF should not match any target index.
313 */
314 static void
prepareRecentProber(Map * corpusMap,Map * recentProberMap)315 prepareRecentProber(Map* corpusMap, Map* recentProberMap)
316 {
317   guint x;
318   guint y;
319 
320   new_intmap(recentProberMap, corpusMap->width, corpusMap->height);
321   for(y=0; y< (guint) corpusMap->height; y++)
322     for(x=0; x< (guint) corpusMap->width; x++)
323     {
324       Coordinates coords = {x,y};
325       *intmap_index(recentProberMap, coords) = -1;
326     }
327 }
328 
329 
330 /*
331 Prepare target AND initialize hasValueMap.
332 This is misnamed and is really two concerns: the target (what is synthesized)
333 and the context (the surroundings.)
334 Both come from the target image.  But the *target* is not *target image*.
335 Prepare a vector of target points.
336 Initialize hasValueMap for all target points.
337 */
338 static void
prepareTargetPoints(gboolean is_use_context,TFormatIndices * indices,Map * targetMap,Map * hasValueMap,pointVector * targetPoints)339 prepareTargetPoints(
340   gboolean is_use_context,
341   TFormatIndices* indices,
342   Map* targetMap,
343   Map* hasValueMap,
344   pointVector* targetPoints
345   )
346 {
347   guint x;
348   guint y;
349 
350   /* Count selected pixels in the image, for sizing a vector */
351   guint size = 0;
352   for(y=0; y<targetMap->height; y++)
353     for(x=0; x<targetMap->width; x++)
354       {
355       Coordinates coords = {x,y};
356       if (isSelectedTarget(coords, targetMap))
357         size++;
358       }
359 
360   *targetPoints = g_array_sized_new (FALSE, TRUE, sizeof(Coordinates), size); /* reserve */
361 
362   prepareHasValue(targetMap, hasValueMap);  /* reserve, initialize to value: unknown */
363 
364   for(y=0; y<targetMap->height; y++)
365     for(x=0; x<targetMap->width; x++)
366     {
367       Coordinates coords = {x,y};
368 
369       /*
370       Remember whether use this image point for matching target neighbors.
371       Initially, no target points have value, and some context points will have value.
372       Later, synthesized target points will have values also.
373       */
374       setHasValue(&coords,
375         (
376           is_use_context // ie use_border ie match image neighbors outside the selection (the context)
377           && ! isSelectedTarget(coords, targetMap)  // outside the target
378           /* !!! and if the point is not transparent (e.g. background layer) which is arbitrarily black !!! */
379           && not_transparent_image(coords, indices, targetMap)
380         ),
381         hasValueMap);
382 
383       /*
384       Make vector targetPoints
385       !!! Note we do NOT exclude transparent.  Will synthesize color (but not alpha)
386       for all selected pixels in target, regardless of transparency.
387       */
388       if (isSelectedTarget(coords, targetMap))
389         g_array_append_val(*targetPoints, coords);
390     }
391 }
392 
393 
394 
395 
396 /*
397 Scan corpus pixmap for selected && nottransparent pixels, create vector of coords.
398 Used to sample corpus.
399 */
400 void
prepareCorpusPoints(TFormatIndices * indices,Map * corpusMap,pointVector * corpusPoints)401 prepareCorpusPoints (
402   TFormatIndices* indices,
403   Map* corpusMap,
404   pointVector* corpusPoints
405   )
406 {
407   /* Reserve size of pixmap, but excess, includes unselected. */
408   *corpusPoints = g_array_sized_new (FALSE, TRUE, sizeof(Coordinates),
409    corpusMap->height*corpusMap->width);
410 
411   {
412   guint x;
413   guint y;
414 
415   for(y=0; y<corpusMap->height; y++)
416     for(x=0; x<corpusMap->width; x++)
417     {
418       Coordinates coords = {x, y};
419       /* In prior versions, the user's mask was inverted to establish the corpus,
420       I.E. this was "not is_selected"
421       */
422       if (isSelectedCorpus(coords, corpusMap)
423         && not_transparent_corpus(coords, indices, corpusMap) /* Exclude transparent from corpus */
424         )
425       {
426         g_array_append_val(*corpusPoints, coords);
427       }
428     }
429   }
430   // Size is checked by caller.
431 }
432 
433 
434 static inline Coordinates
randomCorpusPoint(pointVector corpusPoints,GRand * prng)435 randomCorpusPoint (
436   pointVector corpusPoints,
437   GRand * prng
438   )
439 {
440   /* Was rand()%corpusPoints_size but thats not uniform. */
441   gint index = g_rand_int_range(prng, 0, corpusPoints->len);
442   return g_array_index(corpusPoints, Coordinates, index);
443 }
444 
445 
446 
447 
448 /*
449 Vector of offsets to surrounding points of a point, i.e. to a patch around a point.
450 Used as candidates to compute neighbors vector (nearby points that are selected and with values.)
451 Which is used in two places: 1) neighbor heuristic 2) try_point.
452 
453 Sorted ascending on distance from (0,0) (see the < operator for class Coordinates).
454 
455 !!! Note that offset 0,0 included, is the first element in sorted vector.
456 That makes a point it's own neighbor, i.e. part of the patch for (surrounding) a point.
457 
458 Spans twice the min of corpus and image (target).
459 Why?  So from one corner, the offsets will reach fully across.
460 
461 TODO, for uncropping, where the target surrounds the corpus,
462 this might be vastly many more offsets than are needed for good synthesis.
463 But at worst, if not used they get paged out from virtual memory.
464 */
465 static void
prepareSortedOffsets(Map * targetMap,Map * corpusMap,pointVector * sortedOffsets)466 prepareSortedOffsets(
467   Map* targetMap,
468   Map* corpusMap,
469   pointVector* sortedOffsets
470   )
471 {
472   // Minimum().  Use smaller dimension of corpus and target.
473   gint width = (corpusMap->width < targetMap->width ? corpusMap->width : targetMap->width);
474   gint height = (corpusMap->height < targetMap->height ? corpusMap->height : targetMap->height);
475   guint allocatedSize = (2*width-1)*(2*height-1);   // eg for width==3, [-2,-1,0,1,2], size==5
476 
477   *sortedOffsets = g_array_sized_new (FALSE, TRUE, sizeof(Coordinates), allocatedSize); //Reserve
478 
479   {
480   gint x; // !!! Signed offsets
481   gint y;
482 
483   for(y=-height+1; y<height; y++)
484     for(x=-width+1; x<width; x++)
485       {
486       Coordinates coords = {x,y};
487       g_array_append_val(*sortedOffsets, coords);
488       }
489   }
490   g_assert((*sortedOffsets)->len == allocatedSize);  // Completely filled
491   g_array_sort(*sortedOffsets, (gint (*)(const void*, const void*)) lessCartesian);
492 
493   /* lkk An experiment to sort the offsets in row major order for better memory
494   locality didn't help performance.
495   Apparently the cpu cache holds many rows of the corpus.
496   */
497 }
498 
499 
500 /*
501 Return True if point is clipped or masked (not selected) in the corpus.
502 Point created by coordinate arithmetic, and can be negative or clipped.
503 !!! Note this is called in the bottleneck of try_point, crucial to speed.
504 */
505 static inline gboolean
clippedOrMaskedCorpus(const Coordinates point,const Map * const corpusMap)506 clippedOrMaskedCorpus(
507   const Coordinates point,
508   const Map * const corpusMap)
509 {
510   return (
511     point.x < 0
512     || point.y < 0
513     || point.x >= (gint) corpusMap->width
514     || point.y >= (gint) corpusMap->height /*  Clipped */
515     ||  ! isSelectedCorpus(point, corpusMap) /* Masked */
516     );
517 }
518 
519 
520 // Included source (function declarations, not just definitions.)
521 // Descending levels of the engine
522 // imageSynth()->engine()->refiner()->synthesize
523 #include "passes.h"
524 #include "progress.h"
525 #include "synthesize.h"
526 // Both files define the same function refiner()
527 #ifdef SYNTH_THREADED
528   #include "refinerThreaded.h"
529 #else
530   #include "refiner.h"
531 #endif
532 
533 /*
534 The engine.
535 Independent of platform, calling app, and graphics libraries.
536 This is mostly preparation: real work done by refiner() and synthesize().
537 */
538 
539 int
engine(TImageSynthParameters parameters,TFormatIndices * indices,Map * targetMap,Map * corpusMap,void (* progressCallback)(int,void *),void * contextInfo,int * cancelFlag)540 engine(
541   TImageSynthParameters parameters,
542   TFormatIndices* indices,
543   Map* targetMap,
544   Map* corpusMap,
545   void (*progressCallback)(int, void*),
546   void *contextInfo,
547   int *cancelFlag
548   )
549 {
550   // Engine private data. On stack (and heap), not global, so engine is reentrant.
551 
552   /*
553   A map on the corpus yielding indexes of target points.
554   For a point in the corpus, which target point (index!) most recently probed the corpus point.
555   recentProbe[coords corpus point] -> index of target_point that most recently probed corpus point
556   Heuristic#2.
557 
558   2-D array of int, addressable by Coordinates.
559   */
560   Map recentProberMap;
561 
562   /*
563   Flags for state of synthesis of image pixels.
564 
565   Does source pixel have value yet, to match (depends on selection and state of algorithm.)
566   Map over entire target image (target selection and context.)
567   */
568   Map hasValueMap;
569 
570   /*
571   Does this target pixel have a source yet: yields corpus coords.
572   (-1,-1) indicates no source.
573   */
574   Map sourceOfMap;
575 
576   /*
577   1-D array (vector) of Coordinates.
578   Subsets of image and corpus, subsetted by selection and alpha.
579   */
580   pointVector targetPoints;   // For synthesizing target in an order (ie random)
581   pointVector corpusPoints;   // For sampling corpus randomly.
582   pointVector sortedOffsets;  // offsets (signed coordinates) for finding neighbors.
583 
584   GRand *prng;  // pseudo random number generator
585 
586   // Arrays, lookup tables for quantized functions
587   TPixelelMetricFunc corpusTargetMetric;
588   TMapPixelelMetricFunc mapMetric;
589 
590   // check parameters in range
591   if ( parameters.patchSize > IMAGE_SYNTH_MAX_NEIGHBORS)
592     return IMAGE_SYNTH_ERROR_PATCH_SIZE_EXCEEDED;
593 
594   // target prep
595   prepareTargetPoints(parameters.matchContextType, indices, targetMap,
596     &hasValueMap,
597     &targetPoints);
598   #ifdef ANIMATE
599   clear_target_pixels(indices->color_end_bip);  // For debugging, blacken so new colors sparkle
600   #endif
601   /*
602   Rare user error: no target selected (mask empty.)
603   This error NOT occur in GIMP if selection does not intersect, since then we use the whole drawable.
604   */
605   if ( !targetPoints->len )
606   {
607     g_array_free(targetPoints, TRUE);
608     free_map(&hasValueMap);
609     return IMAGE_SYNTH_ERROR_EMPTY_TARGET;
610   }
611   prepare_target_sources(targetMap, &sourceOfMap);
612 
613 
614   // source prep
615   prepareCorpusPoints(indices, corpusMap, &corpusPoints);
616   /*
617   Rare user error: all corpus pixels transparent or not selected (mask empty.) Which means we can't synthesize.
618   This error NOT occur in GIMP if selection does not intersect, since then we use the whole drawable.
619   */
620   if (!corpusPoints->len )
621   {
622     g_array_free(targetPoints, TRUE);
623     free_map(&hasValueMap);
624     free_map(&sourceOfMap);
625     g_array_free(corpusPoints, TRUE);
626     return IMAGE_SYNTH_ERROR_EMPTY_CORPUS;
627   }
628 
629   // prep things not images
630   prepareSortedOffsets(targetMap, corpusMap, &sortedOffsets); // Depends on image size
631   quantizeMetricFuncs(
632     parameters.sensitivityToOutliers,
633     parameters.mapWeight,
634     corpusTargetMetric,
635     mapMetric
636     );
637 
638   // Now we need a prng, before order_targetPoints
639   /* Originally: srand(time(0));   But then testing is non-repeatable.
640   TODO the seed should be a hash of the input or a user parameter.
641   Then it would be repeatable, but changeable by the user.
642   */
643   prng = g_rand_new_with_seed(1198472);
644 
645   int error = orderTargetPoints(&parameters, targetPoints, prng);
646   // A programming error that we don't clean up.
647   if (error) return error;
648 
649   prepareRecentProber(corpusMap, &recentProberMap);  // Must follow prepare_corpus
650 
651   // Preparations done, begin actual synthesis
652   print_processor_time();
653 
654   // progress(_("Resynthesizer: synthesizing"));
655 
656   refiner(
657     parameters,
658     indices,
659     targetMap,
660     corpusMap,
661     &recentProberMap,
662     &hasValueMap,
663     &sourceOfMap,
664     targetPoints,
665     corpusPoints,
666     sortedOffsets,
667     prng,
668     corpusTargetMetric,
669     mapMetric,
670     progressCallback,
671     contextInfo,
672     cancelFlag
673     );
674 
675   // Free internal mallocs.
676   // Caller must free the IN pixmaps since the targetMap holds synthesis results
677   free_map(&recentProberMap);
678   free_map(&hasValueMap);
679   free_map(&sourceOfMap);
680 
681   g_array_free(targetPoints, TRUE);
682   g_array_free(corpusPoints, TRUE);
683   g_array_free(sortedOffsets, TRUE);
684 
685   #ifdef SYNTH_USE_GLIB
686   g_rand_free(prng);
687   #endif
688 
689   return 0; // Success, even if canceled
690 }
691 
692 
693