Coverage Report

Created: 2026-04-01 07:49

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/graphicsmagick/magick/fx.c
Line
Count
Source
1
/*
2
% Copyright (C) 2003-2026 GraphicsMagick Group
3
% Copyright (C) 2002 ImageMagick Studio
4
% Copyright 1991-1999 E. I. du Pont de Nemours and Company
5
%
6
% This program is covered by multiple licenses, which are described in
7
% Copyright.txt. You should have received a copy of Copyright.txt with this
8
% package; otherwise see http://www.graphicsmagick.org/www/Copyright.html.
9
%
10
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
11
%                                                                             %
12
%                                                                             %
13
%                                                                             %
14
%                                 FFFFF  X   X                                %
15
%                                 F       X X                                 %
16
%                                 FFF      X                                  %
17
%                                 F       X X                                 %
18
%                                 F      X   X                                %
19
%                                                                             %
20
%                                                                             %
21
%                 GraphicsMagick Image Special Effects Methods                %
22
%                                                                             %
23
%                                                                             %
24
%                               Software Design                               %
25
%                                 John Cristy                                 %
26
%                                 October 1996                                %
27
%                                                                             %
28
%                                                                             %
29
%                                                                             %
30
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
31
%
32
%
33
%
34
*/
35

36
/*
37
  Include declarations.
38
*/
39
#include "magick/studio.h"
40
#include "magick/color.h"
41
#include "magick/effect.h"
42
#include "magick/enhance.h"
43
#include "magick/fx.h"
44
#include "magick/gem.h"
45
#include "magick/log.h"
46
#include "magick/pixel_cache.h"
47
#include "magick/pixel_iterator.h"
48
#include "magick/monitor.h"
49
#include "magick/resize.h"
50
#include "magick/utility.h"
51

52
/*
53
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
54
%                                                                             %
55
%                                                                             %
56
%     C h a r c o a l I m a g e                                               %
57
%                                                                             %
58
%                                                                             %
59
%                                                                             %
60
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
61
%
62
%  Method CharcoalImage creates a new image that is a copy of an existing
63
%  one with the edge highlighted.  It allocates the memory necessary for the
64
%  new Image structure and returns a pointer to the new image.
65
%
66
%  The format of the CharcoalImage method is:
67
%
68
%      Image *CharcoalImage(const Image *image,const double radius,
69
%        const double sigma,ExceptionInfo *exception)
70
%
71
%  A description of each parameter follows:
72
%
73
%    o charcoal_image: Method CharcoalImage returns a pointer to the image
74
%      after it is embossed.  A null image is returned if there is a memory
75
%      shortage.
76
%
77
%    o image: The image.
78
%
79
%    o radius: the radius of the pixel neighborhood.
80
%
81
%    o sigma: The standard deviation of the Gaussian, in pixels.
82
%
83
%    o exception: Return any errors or warnings in this structure.
84
%
85
%
86
*/
87
MagickExport Image *CharcoalImage(const Image *image,const double radius,
88
                                  const double sigma,ExceptionInfo *exception)
89
0
{
90
0
  Image
91
0
    *blur_image = (Image *) NULL,
92
0
    *charcoal_image,
93
0
    *edge_image = (Image *) NULL;
94
95
0
  MagickPassFail
96
0
    status = MagickFail;
97
98
0
  assert(image != (Image *) NULL);
99
0
  assert(image->signature == MagickSignature);
100
0
  assert(exception != (ExceptionInfo *) NULL);
101
0
  assert(exception->signature == MagickSignature);
102
103
0
  do
104
0
    {
105
0
      if ((charcoal_image=CloneImage(image,0,0,True,exception)) == (Image *) NULL)
106
0
        break;
107
0
      if (SetImageType(charcoal_image,GrayscaleType) == MagickFail)
108
0
        break;
109
0
      if ((edge_image=EdgeImage(charcoal_image,radius,exception)) == (Image *) NULL)
110
0
        break;
111
0
      DestroyImage(charcoal_image);
112
0
      charcoal_image=(Image *) NULL;
113
0
      if ((blur_image=BlurImage(edge_image,radius,sigma,exception)) == (Image *) NULL)
114
0
        break;
115
0
      DestroyImage(edge_image);
116
0
      edge_image=(Image *) NULL;
117
0
      if (NormalizeImage(blur_image) == MagickFail)
118
0
        {
119
0
          if (blur_image->exception.severity > exception->severity)
120
0
            CopyException(exception,&blur_image->exception);
121
0
          break;
122
0
        }
123
0
      if (NegateImage(blur_image,False) == MagickFail)
124
0
        {
125
0
          if (blur_image->exception.severity > exception->severity)
126
0
            CopyException(exception,&blur_image->exception);
127
0
          break;
128
0
        }
129
0
      if (SetImageType(blur_image,GrayscaleType) == MagickFail)
130
0
        {
131
0
          if (blur_image->exception.severity > exception->severity)
132
0
            CopyException(exception,&blur_image->exception);
133
0
          break;
134
0
        }
135
0
      status = MagickPass;
136
0
    } while(0);
137
138
0
  if (charcoal_image)
139
0
    DestroyImage(charcoal_image);
140
0
  if (edge_image)
141
0
    DestroyImage(edge_image);
142
0
  if (status == MagickFail)
143
0
    {
144
0
      DestroyImage(blur_image);
145
0
      blur_image=(Image *) NULL;
146
0
    }
147
148
0
  return(blur_image);
149
0
}
150

151
/*
152
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
153
%                                                                             %
154
%                                                                             %
155
%     C o l o r i z e I m a g e                                               %
156
%                                                                             %
157
%                                                                             %
158
%                                                                             %
159
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
160
%
161
%  ColorizeImage() blends the fill color with each pixel in the image.
162
%  A percentage blend is specified with opacity.  Control the application
163
%  of different color components by specifying a different percentage for
164
%  each component (e.g. 90/100/10 is 90% red, 100% green, and 10% blue).
165
%
166
%  The format of the ColorizeImage method is:
167
%
168
%      Image *ColorizeImage(const Image *image,const char *opacity,
169
%        const PixelPacket target,ExceptionInfo *exception)
170
%
171
%  A description of each parameter follows:
172
%
173
%    o image: The image.
174
%
175
%    o opacity:  A character string indicating the level of opacity as a
176
%      percentage.
177
%
178
%    o target: A color value.
179
%
180
%    o exception: Return any errors or warnings in this structure.
181
%
182
%
183
*/
184
typedef struct _ColorizeImagePixelsOptions
185
{
186
  DoublePixelPacket amount;
187
  DoublePixelPacket color;
188
} ColorizeImagePixelsOptions;
189
static MagickPassFail
190
ColorizeImagePixelsCB(void *mutable_data,                /* User provided mutable data */
191
                      const void *immutable_data,        /* User provided immutable data */
192
                      const Image * restrict source_image,         /* Source image */
193
                      const PixelPacket * restrict source_pixels,  /* Pixel row in source image */
194
                      const IndexPacket * restrict source_indexes, /* Pixel row indexes in source image */
195
                      Image * restrict new_image,                  /* New image */
196
                      PixelPacket * restrict new_pixels,           /* Pixel row in new image */
197
                      IndexPacket * restrict new_indexes,          /* Pixel row indexes in new image */
198
                      const long npixels,                /* Number of pixels in row */
199
                      ExceptionInfo *exception           /* Exception report */
200
                      )
201
0
{
202
0
  ColorizeImagePixelsOptions
203
0
    options = *((const ColorizeImagePixelsOptions *) immutable_data);
204
205
0
  register long
206
0
    i;
207
208
0
  ARG_NOT_USED(mutable_data);
209
0
  ARG_NOT_USED(source_image);
210
0
  ARG_NOT_USED(source_indexes);
211
0
  ARG_NOT_USED(new_image);
212
0
  ARG_NOT_USED(new_indexes);
213
0
  ARG_NOT_USED(exception);
214
215
0
  for (i=0; i < npixels; i++)
216
0
    {
217
0
      new_pixels[i].red=RoundDoubleToQuantum
218
0
        (((double) source_pixels[i].red*(100.0-options.amount.red)+
219
0
          options.color.red*options.amount.red)/100.0);
220
0
      new_pixels[i].green=RoundDoubleToQuantum
221
0
        (((double) source_pixels[i].green*(100.0-options.amount.green)+
222
0
          options.color.green*options.amount.green)/100.0);
223
0
      new_pixels[i].blue=RoundDoubleToQuantum
224
0
        (((double) source_pixels[i].blue*(100.0-options.amount.blue)+
225
0
          options.color.blue*options.amount.blue)/100.0);
226
0
      new_pixels[i].opacity=RoundDoubleToQuantum
227
0
        (((double) source_pixels[i].opacity*(100.0-options.amount.opacity)+
228
0
          options.color.opacity*options.amount.opacity)/100.0);
229
0
    }
230
231
0
  return MagickPass;
232
0
}
233
MagickExport Image *ColorizeImage(const Image *image,const char *opacity,
234
  const PixelPacket target,ExceptionInfo *exception)
235
0
{
236
0
#define ColorizeImageText "[%s] Colorize..."
237
238
0
  ColorizeImagePixelsOptions
239
0
    options;
240
241
0
  Image
242
0
    *colorize_image;
243
244
0
  long
245
0
    count;
246
247
0
  unsigned int
248
0
    is_grayscale;
249
250
0
  MagickPassFail
251
0
    status;
252
253
  /*
254
    Allocate colorized image.
255
  */
256
0
  assert(image != (const Image *) NULL);
257
0
  assert(image->signature == MagickSignature);
258
0
  assert(exception != (ExceptionInfo *) NULL);
259
0
  assert(exception->signature == MagickSignature);
260
0
  is_grayscale=image->is_grayscale;
261
0
  colorize_image=CloneImage(image,image->columns,image->rows,True,exception);
262
0
  if (colorize_image == (Image *) NULL)
263
0
    return((Image *) NULL);
264
0
  (void) SetImageType(colorize_image,TrueColorType);
265
0
  if (opacity == (const char *) NULL)
266
0
    return(colorize_image);
267
  /*
268
    Determine percentage RGB values of the pen color.
269
  */
270
0
  options.amount.red=100.0;
271
0
  options.amount.green=100.0;
272
0
  options.amount.blue=100.0;
273
0
  options.amount.opacity=0.0;
274
0
  count=sscanf(opacity,"%lf%*[/,]%lf%*[/,]%lf%*[/,]%lf",
275
0
    &options.amount.red,&options.amount.green,&options.amount.blue,&options.amount.opacity);
276
0
  if (count == 1)
277
0
    {
278
0
      if (options.amount.red == 0.0)
279
0
        return(colorize_image);
280
0
      options.amount.green=options.amount.red;
281
0
      options.amount.blue=options.amount.red;
282
0
      options.amount.opacity=options.amount.red;
283
0
    }
284
0
  options.color.red=target.red;
285
0
  options.color.green=target.green;
286
0
  options.color.blue=target.blue;
287
0
  options.color.opacity=target.opacity;
288
  /*
289
    Colorize DirectClass image.
290
  */
291
0
  status=PixelIterateDualNew(ColorizeImagePixelsCB,NULL,
292
0
                             ColorizeImageText,NULL,&options,
293
0
                             image->columns,image->rows,image,0,0,
294
0
                             colorize_image,0,0,exception);
295
0
  colorize_image->is_grayscale=(is_grayscale && IsGray(target));
296
0
  if (status == MagickFail)
297
0
    {
298
0
      DestroyImage(colorize_image);
299
0
      colorize_image=(Image *) NULL;
300
0
    }
301
0
  return(colorize_image);
302
0
}
303

304
/*
305
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
306
%                                                                             %
307
%                                                                             %
308
%     C o l o r M a t r i x I m a g e                                         %
309
%                                                                             %
310
%                                                                             %
311
%                                                                             %
312
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
313
%
314
%  ColorMatrixImage() applies a color matrix to the image channels.  The
315
%  user supplied matrix may be of order 1 to 5 (1x1 through 5x5) and is
316
%  used to update the default identity matrix:
317
%
318
%    1 0 0 0 0
319
%    0 1 0 0 0
320
%    0 0 1 0 0
321
%    0 0 0 1 0
322
%    0 0 0 0 1
323
%
324
%  where the first four columns represent the ratio of the color (red,
325
%  green, blue) and opacity components incorporated in the output summation.
326
%  The first four rows represent the summations for red, green, blue, and
327
%  opacity.  The last row is a dummy row and is not used.  The last column
328
%  represents a constant value (expressed as a ratio of MaxRGB) to be
329
%  added to the row summation.  The following is a summary of how the
330
%  matrix is applied:
331
%
332
%    r' = r*m[0,0] + g*m[1,0] + b*m[2,0] + o*m[3,0] + MaxRGB*m[4,0]
333
%    g' = r*m[0,1] + g*m[1,1] + b*m[2,1] + o*m[3,1] + MaxRGB*m[4,1]
334
%    b' = r*m[0,2] + g*m[1,2] + b*m[2,2] + o*m[3,2] + MaxRGB*m[4,2]
335
%    o' = r*m[0,3] + g*m[1,3] + b*m[2,3] + o*m[3,3] + MaxRGB*m[4,3]
336
%
337
%  The format of the ColorMatrixImage method is:
338
%
339
%      MagickPassFail ColorMatrixImage(Image *image,
340
%                                      const unsigned int order,
341
%                                      const double *color_matrix)
342
%
343
%  A description of each parameter follows:
344
%
345
%    o image: The image.
346
%
347
%    o order: The number of columns and rows in the filter kernel.
348
%
349
%    o matrix: An array of double representing the matrix
350
%
351
%
352
*/
353
typedef struct _ColorMatrixImageOptions_t
354
{
355
  const double
356
    *matrix[5];
357
358
} ColorMatrixImageOptions_t;
359
360
static MagickPassFail
361
ColorMatrixImagePixels(void *mutable_data,         /* User provided mutable data */
362
                       const void *immutable_data, /* User provided immutable data */
363
                       Image * restrict image,     /* Modify image */
364
                       PixelPacket * restrict pixels, /* Pixel row */
365
                       IndexPacket * restrict indexes, /* Pixel row indexes */
366
                       const long npixels,         /* Number of pixels in row */
367
                       ExceptionInfo *exception)   /* Exception report */
368
0
{
369
  /*
370
    Color matrix image pixels.
371
  */
372
0
  const ColorMatrixImageOptions_t
373
0
    *options = (const ColorMatrixImageOptions_t *) immutable_data;
374
375
0
  long
376
0
    i;
377
378
0
  double
379
0
    column[5],
380
0
    sums[4];
381
382
0
  ARG_NOT_USED(mutable_data);
383
0
  ARG_NOT_USED(image);
384
0
  ARG_NOT_USED(indexes);
385
0
  ARG_NOT_USED(exception);
386
387
0
  for (i=0; i < (long) (sizeof(sums)/sizeof(sums[0])); i++)
388
0
    sums[i] = 0.0;
389
390
0
  column[3] = MaxRGBDouble;
391
0
  column[4] = MaxRGBDouble;
392
393
0
  for (i=0; i < npixels; i++)
394
0
    {
395
0
      unsigned int
396
0
        row;
397
398
      /*
399
        Accumulate float input pixel
400
      */
401
0
      column[0]=(double) pixels[i].red;
402
0
      column[1]=(double) pixels[i].green;
403
0
      column[2]=(double) pixels[i].blue;
404
0
      if (image->matte)
405
0
        column[3]=(MaxRGBDouble-(double) pixels[i].opacity);
406
407
      /*
408
        Compute row sums.
409
      */
410
0
      for (row=0; row < 4; row++)
411
0
        {
412
0
          const double
413
0
            *m;
414
415
0
          if ((m = options->matrix[row]) != (const double *) NULL)
416
0
            sums[row]=m[0]*column[0] + m[1]*column[1] + m[2]*column[2] +
417
0
              m[3]*column[3] + m[4]*column[4];
418
0
        }
419
420
      /*
421
        Assign results.
422
      */
423
0
      for (row=0; row < 4; row++)
424
0
        {
425
0
          if (options->matrix[row] != (const double *) NULL)
426
0
            {
427
0
              switch (row)
428
0
                {
429
0
                case 0:
430
0
                  pixels[i].red = RoundDoubleToQuantum(sums[row]);
431
0
                  break;
432
0
                case 1:
433
0
                  pixels[i].green = RoundDoubleToQuantum(sums[row]);
434
0
                  break;
435
0
                case 2:
436
0
                  pixels[i].blue = RoundDoubleToQuantum(sums[row]);
437
0
                  break;
438
0
                case 3:
439
0
                  sums[row]=(MaxRGBDouble-sums[row]);
440
0
                  pixels[i].opacity = RoundDoubleToQuantum(sums[row]);
441
0
                  break;
442
0
                }
443
0
            }
444
0
        }
445
0
    }
446
447
0
  return MagickPass;
448
0
}
449
450
0
#define ColorMatrixImageText "[%s] Color matrix..."
451
MagickExport MagickPassFail
452
ColorMatrixImage(Image *image,const unsigned int order,const double *color_matrix)
453
0
{
454
0
  double
455
0
    matrix[] =
456
0
    {
457
0
      1.0, 0.0, 0.0, 0.0, 0.0,
458
0
      0.0, 1.0, 0.0, 0.0, 0.0,
459
0
      0.0, 0.0, 1.0, 0.0, 0.0,
460
0
      0.0, 0.0, 0.0, 1.0, 0.0,
461
0
      0.0, 0.0, 0.0, 0.0, 1.0
462
0
    };
463
464
0
  unsigned int
465
0
    i;
466
467
0
  int
468
0
    width = 5;
469
470
0
  ColorMatrixImageOptions_t
471
0
    options;
472
473
0
  MagickPassFail
474
0
    status = MagickPass;
475
476
0
  if ((order < 1) || (order > 5))
477
0
    ThrowBinaryException(OptionError,MatrixOrderOutOfRange,
478
0
                         MagickMsg(OptionError,UnableToColorMatrixImage));
479
480
0
  assert(color_matrix != (const double *) NULL);
481
482
0
  for (i=0; i < sizeof(options.matrix)/sizeof(options.matrix[0]); i++)
483
0
    options.matrix[i] = (double *) NULL;
484
485
0
  {
486
0
    double
487
0
      *d;
488
489
0
    const double
490
0
      *u;
491
492
0
    unsigned int
493
0
      j;
494
495
0
    u = color_matrix;
496
0
    for (i=0; i < order; i++)
497
0
      {
498
0
        d = &matrix[i*5];
499
0
        for (j=0; j < order; j++)
500
0
          {
501
0
            if (d[j] != *u)
502
0
              {
503
0
                d[j]=*u;
504
0
                options.matrix[i]=&matrix[i*5];
505
0
              }
506
0
            u++;
507
0
          }
508
0
      }
509
510
    /*
511
      Add opacity channel if we will be updating opacity.
512
    */
513
0
    if ((!image->matte) && (options.matrix[3] != (double *) NULL))
514
0
      SetImageOpacity(image,OpaqueOpacity);
515
0
  }
516
517
0
  if (LogMagickEvent(TransformEvent,GetMagickModule(),
518
0
                     "  ColorMatrix with %dx%d matrix:",width,width))
519
0
    {
520
      /*
521
        Log matrix.
522
      */
523
0
      char
524
0
        cell_text[MaxTextExtent],
525
0
        row_text[MaxTextExtent];
526
527
0
      const double
528
0
        *k;
529
530
0
      long
531
0
        u,
532
0
        v;
533
534
0
      k=matrix;
535
0
      for (v=0; v < width; v++)
536
0
        {
537
0
          *row_text='\0';
538
0
          for (u=0; u < width; u++)
539
0
            {
540
0
              MagickFormatString(cell_text,sizeof(cell_text),"%#12.4g",*k++);
541
0
              (void) strlcat(row_text,cell_text,sizeof(row_text));
542
0
              if (u%5 == 4)
543
0
                {
544
0
                  (void) LogMagickEvent(TransformEvent,GetMagickModule(),
545
0
                                        "   %.64s", row_text);
546
0
                  *row_text='\0';
547
0
                }
548
0
            }
549
0
          if (u > 5)
550
0
            (void) strlcat(row_text,"\n",sizeof(row_text));
551
0
          if (row_text[0] != '\0')
552
0
            (void) LogMagickEvent(TransformEvent,GetMagickModule(),
553
0
                                  "   %s", row_text);
554
0
        }
555
0
    }
556
557
0
  if ((options.matrix[0] != (double *) NULL) ||
558
0
      (options.matrix[1] != (double *) NULL) ||
559
0
      (options.matrix[2] != (double *) NULL) ||
560
0
      (options.matrix[3] != (double *) NULL))
561
0
    {
562
0
      image->storage_class=DirectClass;
563
      /*
564
        We don't currently handle CMYK(A) colorspaces, although
565
        manipulation in other alternate colorspaces may be useful.
566
      */
567
0
      if (image->colorspace == CMYKColorspace)
568
0
        (void) TransformColorspace(image,RGBColorspace);
569
0
      status=PixelIterateMonoModify(ColorMatrixImagePixels,
570
0
                                    NULL,
571
0
                                    ColorMatrixImageText,
572
0
                                    NULL,&options,
573
0
                                    0,0,image->columns,image->rows,
574
0
                                    image,
575
0
                                    &image->exception);
576
0
    }
577
578
0
  return status;
579
0
}
580

581
/*
582
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
583
%                                                                             %
584
%                                                                             %
585
%     I m p l o d e I m a g e                                                 %
586
%                                                                             %
587
%                                                                             %
588
%                                                                             %
589
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
590
%
591
%  Method ImplodeImage creates a new image that is a copy of an existing
592
%  one with the image pixels "implode" by the specified percentage.  It
593
%  allocates the memory necessary for the new Image structure and returns a
594
%  pointer to the new image.
595
%
596
%  The format of the ImplodeImage method is:
597
%
598
%      Image *ImplodeImage(const Image *image,const double amount,
599
%        ExceptionInfo *exception)
600
%
601
%  A description of each parameter follows:
602
%
603
%    o implode_image: Method ImplodeImage returns a pointer to the image
604
%      after it is implode.  A null image is returned if there is a memory
605
%      shortage.
606
%
607
%    o image: The image.
608
%
609
%    o amount:  Define the extent of the implosion.
610
%
611
%    o exception: Return any errors or warnings in this structure.
612
%
613
%
614
*/
615
MagickExport Image *ImplodeImage(const Image * restrict image,const double amount,
616
                                 ExceptionInfo *exception)
617
0
{
618
0
#define ImplodeImageText "[%s] Implode..."
619
620
0
  double
621
0
    radius,
622
0
    x_center,
623
0
    x_scale,
624
0
    y_center,
625
0
    y_scale;
626
627
0
  Image
628
0
    * restrict implode_image;
629
630
0
  long
631
0
    y;
632
633
0
  MagickPassFail
634
0
    status = MagickPass;
635
636
  /*
637
    Initialize implode image attributes.
638
  */
639
0
  assert(image != (Image *) NULL);
640
0
  assert(image->signature == MagickSignature);
641
0
  assert(exception != (ExceptionInfo *) NULL);
642
0
  assert(exception->signature == MagickSignature);
643
0
  implode_image=CloneImage(image,image->columns,image->rows,True,exception);
644
0
  if (implode_image == (Image *) NULL)
645
0
    return((Image *) NULL);
646
0
  (void) SetImageType(implode_image,implode_image->background_color.opacity !=
647
0
                      OpaqueOpacity ? TrueColorMatteType : TrueColorType);
648
  /*
649
    Compute scaling factor.
650
  */
651
0
  x_scale=1.0;
652
0
  y_scale=1.0;
653
0
  x_center=0.5*image->columns;
654
0
  y_center=0.5*image->rows;
655
0
  radius=x_center;
656
0
  if (image->columns > image->rows)
657
0
    y_scale=(double) image->columns/image->rows;
658
0
  else
659
0
    if (image->columns < image->rows)
660
0
      {
661
0
        x_scale=(double) image->rows/image->columns;
662
0
        radius=y_center;
663
0
      }
664
  /*
665
    Implode each row.
666
  */
667
0
  {
668
0
    unsigned long
669
0
      row_count=0;
670
671
0
    MagickBool
672
0
      monitor_active;
673
674
0
    monitor_active=MagickMonitorActive();
675
676
#if defined(HAVE_OPENMP)
677
#  if defined(TUNE_OPENMP)
678
#    pragma omp parallel for schedule(runtime) shared(row_count, status)
679
#  else
680
#    pragma omp parallel for schedule(guided) shared(row_count, status)
681
#  endif
682
#endif
683
0
    for (y=0; y < (long) image->rows; y++)
684
0
      {
685
0
        register long
686
0
          x;
687
688
0
        register PixelPacket
689
0
          * restrict q;
690
691
0
        double
692
0
          distance,
693
0
          x_distance,
694
0
          y_distance;
695
696
0
        ViewInfo
697
0
          *image_view;
698
699
0
        MagickPassFail
700
0
          thread_status;
701
702
0
        thread_status=status;
703
0
        if (thread_status == MagickFail)
704
0
          continue;
705
706
0
        image_view=AccessDefaultCacheView(image);
707
0
        q=SetImagePixelsEx(implode_image,0,y,implode_image->columns,1,
708
0
                           exception);
709
0
        if (q == (PixelPacket *) NULL)
710
0
          thread_status=MagickFail;
711
0
        if (thread_status != MagickFail)
712
0
          {
713
0
            y_distance=y_scale*(y-y_center);
714
0
            for (x=0; x < (long) image->columns; x++)
715
0
              {
716
                /*
717
                  Determine if the pixel is within an ellipse.
718
                */
719
0
                x_distance=x_scale*(x-x_center);
720
0
                distance=x_distance*x_distance+y_distance*y_distance;
721
0
                if (distance >= (radius*radius))
722
0
                  (void) AcquireOneCacheViewPixel(image_view,q,x,y,exception);
723
0
                else
724
0
                  {
725
0
                    double
726
0
                      factor;
727
728
                    /*
729
                      Implode the pixel.
730
                    */
731
0
                    factor=1.0;
732
0
                    if (distance > 0.0)
733
0
                      factor=pow(sin(MagickPI*sqrt(distance)/radius/2),-amount);
734
0
                    if (InterpolateViewColor(image_view,q,
735
0
                                             factor*x_distance/x_scale+x_center,
736
0
                                             factor*y_distance/y_scale+y_center,
737
0
                                             exception) == MagickFail)
738
0
                      {
739
0
                        thread_status=MagickFail;
740
0
                        break;
741
0
                      }
742
0
                  }
743
0
                q++;
744
0
              }
745
0
            if (thread_status != MagickFail)
746
0
              if (!SyncImagePixelsEx(implode_image,exception))
747
0
                thread_status=MagickFail;
748
0
          }
749
750
0
        if (monitor_active)
751
0
          {
752
0
            unsigned long
753
0
              thread_row_count;
754
755
#if defined(HAVE_OPENMP)
756
#  pragma omp atomic
757
#endif
758
0
            row_count++;
759
#if defined(HAVE_OPENMP)
760
#  pragma omp flush (row_count)
761
#endif
762
0
            thread_row_count=row_count;
763
0
            if (QuantumTick(thread_row_count,image->rows))
764
0
              if (!MagickMonitorFormatted(thread_row_count,image->rows,exception,
765
0
                                          ImplodeImageText,implode_image->filename))
766
0
                thread_status=MagickFail;
767
0
          }
768
769
0
        if (thread_status == MagickFail)
770
0
          {
771
0
            status=MagickFail;
772
#if defined(HAVE_OPENMP)
773
#  pragma omp flush (status)
774
#endif
775
0
          }
776
0
      }
777
0
  }
778
0
  implode_image->is_grayscale=image->is_grayscale;
779
0
  if (status == MagickFail)
780
0
    {
781
0
      DestroyImage(implode_image);
782
0
      implode_image=(Image *) NULL;
783
0
    }
784
0
  return(implode_image);
785
0
}
786

787
/*
788
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
789
%                                                                             %
790
%                                                                             %
791
%     M o r p h I m a g e s                                                   %
792
%                                                                             %
793
%                                                                             %
794
%                                                                             %
795
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
796
%
797
%  The MorphImages() method requires a minimum of two images.  The first
798
%  image is transformed into the second by a number of intervening images
799
%  as specified by frames.
800
%
801
%  The format of the MorphImage method is:
802
%
803
%      Image *MorphImages(const Image *image,const unsigned long number_frames,
804
%        ExceptionInfo *exception)
805
%
806
%  A description of each parameter follows:
807
%
808
%    o image: The image.
809
%
810
%    o number_frames:  Define the number of in-between image to generate.
811
%      The more in-between frames, the smoother the morph.
812
%
813
%    o exception: Return any errors or warnings in this structure.
814
%
815
%
816
*/
817
typedef struct _MorphImagePixelsOptions
818
{
819
  double alpha;
820
  double beta;
821
} MorphImagePixelsOptions;
822
static MagickPassFail
823
MorphImagePixelsCB(void *mutable_data,                /* User provided mutable data */
824
                   const void *immutable_data,        /* User provided immutable data */
825
                   const Image * restrict source_image,         /* Source image */
826
                   const PixelPacket * restrict source_pixels,  /* Pixel row in source image */
827
                   const IndexPacket * restrict source_indexes, /* Pixel row indexes in source image */
828
                   Image * restrict new_image,                  /* New image */
829
                   PixelPacket * restrict new_pixels,           /* Pixel row in new image */
830
                   IndexPacket * restrict new_indexes,          /* Pixel row indexes in new image */
831
                   const long npixels,                /* Number of pixels in row */
832
                   ExceptionInfo *exception           /* Exception report */
833
                   )
834
0
{
835
0
  MorphImagePixelsOptions
836
0
    options = *((const MorphImagePixelsOptions *) immutable_data);
837
838
0
  register long
839
0
    i;
840
841
0
  ARG_NOT_USED(mutable_data);
842
0
  ARG_NOT_USED(source_image);
843
0
  ARG_NOT_USED(source_indexes);
844
0
  ARG_NOT_USED(new_image);
845
0
  ARG_NOT_USED(new_indexes);
846
0
  ARG_NOT_USED(exception);
847
848
0
  for (i=0; i < npixels; i++)
849
0
    {
850
0
      new_pixels[i].red=(Quantum) (options.alpha*new_pixels[i].red+
851
0
                                   options.beta*source_pixels[i].red+0.5);
852
0
      new_pixels[i].green=(Quantum) (options.alpha*new_pixels[i].green+
853
0
                                     options.beta*source_pixels[i].green+0.5);
854
0
      new_pixels[i].blue=(Quantum) (options.alpha*new_pixels[i].blue+
855
0
                                    options.beta*source_pixels[i].blue+0.5);
856
0
      new_pixels[i].opacity=(Quantum) (options.alpha*new_pixels[i].opacity+
857
0
                                       options.beta*source_pixels[i].opacity+0.5);
858
0
    }
859
860
0
  return MagickPass;
861
0
}
862
MagickExport Image *MorphImages(const Image *image,
863
  const unsigned long number_frames,ExceptionInfo *exception)
864
0
{
865
0
#define MorphImageText "[%s] Morph sequence..."
866
867
0
  MorphImagePixelsOptions
868
0
    options;
869
870
0
  Image
871
0
    *clone_image,
872
0
    *morph_image,
873
0
    *morph_images;
874
875
0
  MonitorHandler
876
0
    handler;
877
878
0
  register const Image
879
0
    *next;
880
881
0
  register long
882
0
    i;
883
884
0
  unsigned long
885
0
    scene;
886
887
  /*
888
    Clone first frame in sequence.
889
  */
890
0
  assert(image != (Image *) NULL);
891
0
  assert(image->signature == MagickSignature);
892
0
  assert(exception != (ExceptionInfo *) NULL);
893
0
  assert(exception->signature == MagickSignature);
894
0
  morph_images=CloneImage(image,0,0,True,exception);
895
0
  if (morph_images == (Image *) NULL)
896
0
    return((Image *) NULL);
897
0
  if (image->next == (Image *) NULL)
898
0
    {
899
      /*
900
        Morph single image.
901
      */
902
0
      for (i=1; i < (long) number_frames; i++)
903
0
      {
904
0
        morph_images->next=CloneImage(image,0,0,True,exception);
905
0
        if (morph_images->next == (Image *) NULL)
906
0
          {
907
0
            DestroyImageList(morph_images);
908
0
            return((Image *) NULL);
909
0
          }
910
0
        morph_images->next->previous=morph_images;
911
0
        morph_images=morph_images->next;
912
0
        if (!MagickMonitorFormatted(i,number_frames,exception,
913
0
                                    MorphImageText,image->filename))
914
0
          break;
915
0
      }
916
0
      while (morph_images->previous != (Image *) NULL)
917
0
        morph_images=morph_images->previous;
918
0
      return(morph_images);
919
0
    }
920
  /*
921
    Morph image sequence.
922
  */
923
0
  scene=0;
924
0
  for (next=image; next->next != (Image *) NULL; next=next->next)
925
0
  {
926
0
    handler=SetMonitorHandler((MonitorHandler) NULL);
927
0
    for (i=0; i < (long) number_frames; i++)
928
0
    {
929
0
      options.beta=((double) i+1.0)/(number_frames+1.0);
930
0
      options.alpha=1.0-options.beta;
931
0
      clone_image=CloneImage(next,0,0,True,exception);
932
0
      if (clone_image == (Image *) NULL)
933
0
        break;
934
0
      morph_images->next=ZoomImage(clone_image,
935
0
        (unsigned long) (options.alpha*next->columns+options.beta*next->next->columns+0.5),
936
0
        (unsigned long) (options.alpha*next->rows+options.beta*next->next->rows+0.5),exception);
937
0
      DestroyImage(clone_image);
938
0
      if (morph_images->next == (Image *) NULL)
939
0
        break;
940
0
      morph_images->next->previous=morph_images;
941
0
      morph_images=morph_images->next;
942
0
      clone_image=CloneImage(next->next,0,0,True,exception);
943
0
      if (clone_image == (Image *) NULL)
944
0
        break;
945
0
      morph_image=ZoomImage(clone_image,morph_images->columns,
946
0
        morph_images->rows,exception);
947
0
      DestroyImage(clone_image);
948
0
      if (morph_image == (Image *) NULL)
949
0
        break;
950
0
      (void) SetImageType(morph_images,TrueColorType);
951
0
      (void) PixelIterateDualNew(MorphImagePixelsCB,NULL,
952
0
                                 MorphImageText,NULL,&options,
953
0
                                 morph_images->columns,morph_images->rows,morph_image,0,0,
954
0
                                 morph_images,0,0,exception);
955
0
      DestroyImage(morph_image);
956
0
    }
957
0
    if (i < (long) number_frames)
958
0
      break;
959
    /*
960
      Clone last frame in sequence.
961
    */
962
0
    morph_images->next=CloneImage(next->next,0,0,True,exception);
963
0
    if (morph_images->next == (Image *) NULL)
964
0
      break;
965
0
    morph_images->next->previous=morph_images;
966
0
    morph_images=morph_images->next;
967
0
    (void) SetMonitorHandler(handler);
968
0
    if (!MagickMonitorFormatted(scene,GetImageListLength(image),exception,
969
0
                                MorphImageText,image->filename))
970
0
      break;
971
0
    scene++;
972
0
  }
973
0
  while (morph_images->previous != (Image *) NULL)
974
0
    morph_images=morph_images->previous;
975
0
  if (next->next != (Image *) NULL)
976
0
    {
977
0
      DestroyImageList(morph_images);
978
0
      return((Image *) NULL);
979
0
    }
980
0
  return(morph_images);
981
0
}
982

983
/*
984
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
985
%                                                                             %
986
%                                                                             %
987
%     O i l P a i n t I m a g e                                               %
988
%                                                                             %
989
%                                                                             %
990
%                                                                             %
991
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
992
%
993
%  OilPaintImage() applies a special effect filter that simulates an oil
994
%  painting.  Each pixel is replaced by the most frequent color occurring
995
%  in a circular region defined by radius.
996
%
997
%  The format of the OilPaintImage method is:
998
%
999
%      Image *OilPaintImage(const Image *image,const double radius,
1000
%        ExceptionInfo *exception)
1001
%
1002
%  A description of each parameter follows:
1003
%
1004
%    o image: The image.
1005
%
1006
%    o radius: The radius of the circular neighborhood.
1007
%
1008
%    o exception: Return any errors or warnings in this structure.
1009
%
1010
%
1011
*/
1012
#define PaintHistSize 256
1013
MagickExport Image *OilPaintImage(const Image *image,const double radius,
1014
                                  ExceptionInfo *exception)
1015
0
{
1016
0
#define OilPaintImageText "[%s] OilPaint..."
1017
1018
0
  Image
1019
0
    *paint_image;
1020
1021
0
  long
1022
0
    width,
1023
0
    y;
1024
1025
0
  unsigned long
1026
0
    row_count=0;
1027
1028
0
  MagickBool
1029
0
    monitor_active;
1030
1031
0
  MagickPassFail
1032
0
    status=MagickPass;
1033
1034
  /*
1035
    Initialize painted image attributes.
1036
  */
1037
0
  assert(image != (const Image *) NULL);
1038
0
  assert(image->signature == MagickSignature);
1039
0
  assert(exception != (ExceptionInfo *) NULL);
1040
0
  assert(exception->signature == MagickSignature);
1041
0
  width=GetOptimalKernelWidth(radius,0.5);
1042
0
  if (((long) image->columns < width) || ((long) image->rows < width))
1043
0
    ThrowImageException3(OptionError,UnableToPaintImage,
1044
0
                         ImageSmallerThanRadius);
1045
1046
0
  paint_image=CloneImage(image,image->columns,image->rows,True,exception);
1047
0
  if (paint_image == (Image *) NULL)
1048
0
    return((Image *) NULL);
1049
1050
0
  (void) SetImageType(paint_image,TrueColorType);
1051
1052
0
  monitor_active=MagickMonitorActive();
1053
1054
  /*
1055
    Paint each row of the image.
1056
  */
1057
#if defined(HAVE_OPENMP)
1058
#  if defined(TUNE_OPENMP)
1059
#    pragma omp parallel for schedule(runtime) shared(row_count, status)
1060
#  else
1061
#    pragma omp parallel for schedule(guided) shared(row_count, status)
1062
#  endif
1063
#endif
1064
0
  for (y=0; y < (long) image->rows; y++)
1065
0
    {
1066
0
      const PixelPacket
1067
0
        * restrict p,
1068
0
        *r;
1069
1070
0
      PixelPacket
1071
0
        * restrict q;
1072
1073
0
      long
1074
0
        x;
1075
1076
0
      const PixelPacket
1077
0
        *s;
1078
1079
0
      MagickBool
1080
0
        thread_status;
1081
1082
0
      thread_status=status;
1083
0
      if (thread_status == MagickFail)
1084
0
        continue;
1085
1086
0
      p=AcquireImagePixels(image,-width/2,y-width/2,image->columns+width,width,
1087
0
                           exception);
1088
0
      q=SetImagePixelsEx(paint_image,0,y,paint_image->columns,1,exception);
1089
0
      if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
1090
0
        thread_status=MagickFail;
1091
0
      if (thread_status != MagickFail)
1092
0
        {
1093
0
          for (x=(long) image->columns; x > 0; x--)
1094
0
            {
1095
0
              long
1096
0
                v;
1097
1098
0
              unsigned long
1099
0
                count;
1100
1101
0
              unsigned int
1102
0
                histogram[PaintHistSize];
1103
1104
              /*
1105
                Determine most frequent color.
1106
              */
1107
0
              count=0;
1108
0
              (void) memset(histogram,0,sizeof(histogram));
1109
0
              r=p++;
1110
0
              s=r;
1111
0
              for (v=width; v > 0; v--)
1112
0
                {
1113
0
                  register long
1114
0
                    u;
1115
1116
0
                  register const PixelPacket
1117
0
                    *ru;
1118
1119
0
                  ru=r;
1120
0
                  for (u=width; u > 0; u--)
1121
0
                    {
1122
0
                      register unsigned int
1123
0
                        *hp;
1124
1125
0
                      Quantum
1126
0
                        intensity;
1127
1128
0
                      if (image->is_grayscale)
1129
0
                        intensity=ru->red;
1130
0
                      else
1131
0
                        intensity=PixelIntensityToQuantum(ru);
1132
1133
0
                      hp=histogram+ScaleQuantumToChar(intensity);
1134
0
                      (*hp)++;
1135
0
                      if (*hp > count)
1136
0
                        {
1137
0
                          s=ru;
1138
0
                          count=*hp;
1139
0
                        }
1140
0
                      ru++;
1141
0
                    }
1142
0
                  r+=(size_t) image->columns+width;
1143
0
                }
1144
0
              *q++=(*s);
1145
0
            }
1146
0
          if (!SyncImagePixelsEx(paint_image,exception))
1147
0
            thread_status=MagickFail;
1148
0
        }
1149
1150
0
      if (monitor_active)
1151
0
        {
1152
0
          unsigned long
1153
0
            thread_row_count;
1154
1155
#if defined(HAVE_OPENMP)
1156
#  pragma omp atomic
1157
#endif
1158
0
          row_count++;
1159
#if defined(HAVE_OPENMP)
1160
#  pragma omp flush (row_count)
1161
#endif
1162
0
          thread_row_count=row_count;
1163
0
          if (QuantumTick(thread_row_count,image->rows))
1164
0
            if (!MagickMonitorFormatted(thread_row_count,image->rows,exception,
1165
0
                                        OilPaintImageText,image->filename))
1166
0
              thread_status=MagickFail;
1167
0
        }
1168
1169
0
      if (thread_status == MagickFail)
1170
0
        {
1171
0
          status=MagickFail;
1172
#if defined(HAVE_OPENMP)
1173
#  pragma omp flush (status)
1174
#endif
1175
0
        }
1176
0
    }
1177
1178
0
  paint_image->is_grayscale=image->is_grayscale;
1179
0
  if (status == MagickFail)
1180
0
    {
1181
0
      DestroyImage(paint_image);
1182
0
      paint_image=(Image *) NULL;
1183
0
    }
1184
0
  return(paint_image);
1185
0
}
1186

1187
/*
1188
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1189
%                                                                             %
1190
%                                                                             %
1191
%     S o l a r i z e I m a g e                                               %
1192
%                                                                             %
1193
%                                                                             %
1194
%                                                                             %
1195
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1196
%
1197
%  SolarizeImage() applies a special effect to the image, similar to the effect
1198
%  achieved in a photo darkroom by selectively exposing areas of photo
1199
%  sensitive paper to light.  Threshold ranges from 0 to MaxRGB and is a
1200
%  measure of the extent of the solarization. False is returned if an error
1201
%  is encountered.
1202
%
1203
%  The format of the SolarizeImage method is:
1204
%
1205
%      unsigned int SolarizeImage(Image *image,const double threshold)
1206
%
1207
%  A description of each parameter follows:
1208
%
1209
%    o image: The image.
1210
%
1211
%    o threshold:  Define the extent of the solarization.
1212
%
1213
%
1214
*/
1215
static MagickPassFail
1216
SolarizeImagePixelsCB(void *mutable_data,         /* User provided mutable data */
1217
                      const void *immutable_data, /* User provided immutable data */
1218
                      Image * restrict image,               /* Modify image */
1219
                      PixelPacket * restrict pixels,        /* Pixel row */
1220
                      IndexPacket * restrict indexes,       /* Pixel row indexes */
1221
                      const long npixels,         /* Number of pixels in row */
1222
                      ExceptionInfo *exception)   /* Exception report */
1223
0
{
1224
1225
0
  const unsigned int
1226
0
    threshold = *((const unsigned int *) immutable_data);
1227
1228
0
  register long
1229
0
    i;
1230
1231
0
  ARG_NOT_USED(mutable_data);
1232
0
  ARG_NOT_USED(image);
1233
0
  ARG_NOT_USED(indexes);
1234
0
  ARG_NOT_USED(exception);
1235
1236
0
  for (i=0; i < npixels; i++)
1237
0
    {
1238
0
      pixels[i].red=(pixels[i].red > threshold ?
1239
0
                     MaxRGB-pixels[i].red : pixels[i].red);
1240
0
      pixels[i].green=(pixels[i].green > threshold ?
1241
0
                       MaxRGB-pixels[i].green : pixels[i].green);
1242
0
      pixels[i].blue=(pixels[i].blue > threshold ?
1243
0
                      MaxRGB-pixels[i].blue : pixels[i].blue);
1244
0
    }
1245
1246
0
  return MagickPass;
1247
0
}
1248
MagickExport MagickPassFail SolarizeImage(Image *image,const double threshold)
1249
0
{
1250
0
#define SolarizeImageText "[%s] Solarize..."
1251
1252
0
  unsigned int
1253
0
    is_grayscale,
1254
0
    threshold_int;
1255
1256
0
  MagickPassFail
1257
0
    status=MagickPass;
1258
1259
0
  assert(image != (Image *) NULL);
1260
0
  assert(image->signature == MagickSignature);
1261
0
  is_grayscale=image->is_grayscale;
1262
0
  threshold_int=(threshold < 0.0 ? 0U :
1263
0
                 (threshold > MaxRGBDouble) ? MaxRGB :
1264
0
                 (unsigned int) (threshold + 0.5));
1265
0
  switch (image->storage_class)
1266
0
  {
1267
0
    case DirectClass:
1268
0
    default:
1269
0
    {
1270
      /*
1271
        Solarize DirectClass packets.
1272
      */
1273
0
      status=PixelIterateMonoModify(SolarizeImagePixelsCB,
1274
0
                                    NULL,
1275
0
                                    SolarizeImageText,
1276
0
                                    NULL,&threshold_int,0,0,image->columns,image->rows,
1277
0
                                    image,&image->exception);
1278
0
      break;
1279
0
    }
1280
0
    case PseudoClass:
1281
0
    {
1282
      /*
1283
        Solarize PseudoClass packets.
1284
      */
1285
0
      SolarizeImagePixelsCB(0,
1286
0
                            &threshold_int,
1287
0
                            image,
1288
0
                            image->colormap,
1289
0
                            (IndexPacket *) NULL,
1290
0
                            image->colors,
1291
0
                            &image->exception);
1292
0
      status &= SyncImage(image);
1293
0
      break;
1294
0
    }
1295
0
  }
1296
0
  image->is_grayscale=is_grayscale;
1297
0
  return (status);
1298
0
}
1299

1300
/*
1301
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1302
%                                                                             %
1303
%                                                                             %
1304
%                                                                             %
1305
%   S t e g a n o I m a g e                                                   %
1306
%                                                                             %
1307
%                                                                             %
1308
%                                                                             %
1309
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1310
%
1311
%  Use SteganoImage() to hide a digital watermark within the image.
1312
%  Recover the hidden watermark later to prove that the authenticity of
1313
%  an image.  Offset defines the start position within the image to hide
1314
%  the watermark.
1315
%
1316
%  The format of the SteganoImage method is:
1317
%
1318
%      Image *SteganoImage(const Image *image,Image *watermark,
1319
%        ExceptionInfo *exception)
1320
%
1321
%  A description of each parameter follows:
1322
%
1323
%    o image: The image.
1324
%
1325
%    o watermark: The watermark image.
1326
%
1327
%    o exception: Return any errors or warnings in this structure.
1328
%
1329
%
1330
*/
1331
#define GetBit(a,i) (((a) >> (i)) & 0x01)
1332
#define SetBit(a,i,set) \
1333
0
  a=(Quantum) ((set) ? (a) | (1UL << (i)) : (a) & ~(1UL << (i)))
1334
0
#define SteganoImageText "[%s] Stegano..."
1335
MagickExport Image *SteganoImage(const Image * restrict image,const Image * restrict watermark,
1336
  ExceptionInfo *exception)
1337
0
{
1338
1339
0
  Image
1340
0
    * restrict stegano_image;
1341
1342
0
  long
1343
0
    c,
1344
0
    i,
1345
0
    j,
1346
0
    k,
1347
0
    y;
1348
1349
0
  PixelPacket
1350
0
    pixel;
1351
1352
0
  register long
1353
0
    x;
1354
1355
0
  unsigned int
1356
0
    is_grayscale;
1357
1358
0
  register PixelPacket
1359
0
    * restrict q;
1360
1361
  /*
1362
    Initialize steganographic image attributes.
1363
  */
1364
0
  assert(image != (const Image *) NULL);
1365
0
  assert(image->signature == MagickSignature);
1366
0
  assert(watermark != (const Image *) NULL);
1367
0
  assert(watermark->signature == MagickSignature);
1368
0
  assert(exception != (ExceptionInfo *) NULL);
1369
0
  assert(exception->signature == MagickSignature);
1370
0
  is_grayscale=(image->is_grayscale && watermark->is_grayscale);
1371
0
  stegano_image=CloneImage(image,0,0,True,exception);
1372
0
  if (stegano_image == (Image *) NULL)
1373
0
    return((Image *) NULL);
1374
0
  (void) SetImageType(stegano_image,TrueColorType);
1375
0
  stegano_image->depth=QuantumDepth;
1376
  /*
1377
    Hide watermark in low-order bits of image.
1378
  */
1379
0
  c=0;
1380
0
  i=0;
1381
0
  j=0;
1382
0
  k=image->offset;
1383
0
  for (i=QuantumDepth-1; (i >= 0) && (j < QuantumDepth); i--)
1384
0
  {
1385
0
    for (y=0; (y < (long) watermark->rows) && (j < QuantumDepth); y++)
1386
0
    {
1387
0
      for (x=0; (x < (long) watermark->columns) && (j < QuantumDepth); x++)
1388
0
      {
1389
0
        (void) AcquireOnePixelByReference(watermark,&pixel,x,y,exception);
1390
0
        q=GetImagePixels(stegano_image,k % (long) stegano_image->columns,
1391
0
          k/(long) stegano_image->columns,1,1);
1392
0
        if (q == (PixelPacket *) NULL)
1393
0
          break;
1394
0
        switch ((int) c)
1395
0
        {
1396
0
          case 0:
1397
0
          {
1398
0
            SetBit(q->red,j,GetBit(PixelIntensityToQuantum(&pixel),i));
1399
0
            break;
1400
0
          }
1401
0
          case 1:
1402
0
          {
1403
0
            SetBit(q->green,j,GetBit(PixelIntensityToQuantum(&pixel),i));
1404
0
            break;
1405
0
          }
1406
0
          case 2:
1407
0
          {
1408
0
            SetBit(q->blue,j,GetBit(PixelIntensityToQuantum(&pixel),i));
1409
0
            break;
1410
0
          }
1411
0
        }
1412
0
        (void) SyncImage(stegano_image);
1413
0
        c++;
1414
0
        if (c == 3)
1415
0
          c=0;
1416
0
        k++;
1417
0
        if (k == (long) (stegano_image->columns*stegano_image->columns))
1418
0
          k=0;
1419
0
        if (k == image->offset)
1420
0
          j++;
1421
0
      }
1422
0
    }
1423
0
    if (QuantumTick(i,QuantumDepth))
1424
0
      if (!MagickMonitorFormatted(i,QuantumDepth,exception,
1425
0
                                  SteganoImageText,image->filename))
1426
0
        break;
1427
0
  }
1428
0
  if (stegano_image->storage_class == PseudoClass)
1429
0
    (void) SyncImage(stegano_image);
1430
0
  stegano_image->is_grayscale=is_grayscale;
1431
0
  return(stegano_image);
1432
0
}
1433

1434
/*
1435
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1436
%                                                                             %
1437
%                                                                             %
1438
%                                                                             %
1439
%   S t e r e o I m a g e                                                     %
1440
%                                                                             %
1441
%                                                                             %
1442
%                                                                             %
1443
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1444
%
1445
%  StereoImage() combines two images and produces a single image that is the
1446
%  composite of a left and right image of a stereo pair.  Special red-green
1447
%  stereo glasses are required to view this effect.
1448
%
1449
%  The format of the StereoImage method is:
1450
%
1451
%      Image *StereoImage(const Image *image,const Image *offset_image,
1452
%        ExceptionInfo *exception)
1453
%
1454
%  A description of each parameter follows:
1455
%
1456
%    o stereo_image: Method StereoImage returns a pointer to the stereo
1457
%      image.  A null image is returned if there is a memory shortage.
1458
%
1459
%    o image: The image.
1460
%
1461
%    o offset_image: Another image.
1462
%
1463
%    o exception: Return any errors or warnings in this structure.
1464
%
1465
%
1466
*/
1467
MagickExport Image *StereoImage(const Image * restrict image,const Image * restrict offset_image,
1468
  ExceptionInfo *exception)
1469
0
{
1470
0
#define StereoImageText "[%s] Stereo..."
1471
1472
0
  Image
1473
0
    * restrict stereo_image;
1474
1475
0
  long
1476
0
    y;
1477
1478
0
  register const PixelPacket
1479
0
    * restrict p,
1480
0
    * restrict q;
1481
1482
0
  register long
1483
0
    x;
1484
1485
0
  register PixelPacket
1486
0
    * restrict r;
1487
1488
0
  assert(image != (const Image *) NULL);
1489
0
  assert(image->signature == MagickSignature);
1490
0
  assert(exception != (ExceptionInfo *) NULL);
1491
0
  assert(exception->signature == MagickSignature);
1492
0
  assert(offset_image != (const Image *) NULL);
1493
0
  if ((image->columns != offset_image->columns) ||
1494
0
      (image->rows != offset_image->rows))
1495
0
    ThrowImageException3(ImageError,UnableToCreateStereoImage,
1496
0
      LeftAndRightImageSizesDiffer);
1497
  /*
1498
    Initialize stereo image attributes.
1499
  */
1500
0
  stereo_image=CloneImage(image,image->columns,image->rows,True,exception);
1501
0
  if (stereo_image == (Image *) NULL)
1502
0
    return((Image *) NULL);
1503
0
  (void) SetImageType(stereo_image,TrueColorType);
1504
  /*
1505
    Copy left image to red channel and right image to blue channel.
1506
  */
1507
0
  for (y=0; y < (long) stereo_image->rows; y++)
1508
0
  {
1509
0
    p=AcquireImagePixels(image,0,y,image->columns,1,exception);
1510
0
    q=AcquireImagePixels(offset_image,0,y,offset_image->columns,1,exception);
1511
0
    r=SetImagePixels(stereo_image,0,y,stereo_image->columns,1);
1512
0
    if ((p == (PixelPacket *) NULL) || (q == (PixelPacket *) NULL) ||
1513
0
        (r == (PixelPacket *) NULL))
1514
0
      break;
1515
0
    for (x=0; x < (long) stereo_image->columns; x++)
1516
0
    {
1517
0
      r->red=p->red;
1518
0
      r->green=q->green;
1519
0
      r->blue=q->blue;
1520
0
      r->opacity=(Quantum) (((double) p->opacity+q->opacity)/2.0);
1521
0
      p++;
1522
0
      q++;
1523
0
      r++;
1524
0
    }
1525
0
    if (!SyncImagePixels(stereo_image))
1526
0
      break;
1527
0
    if (QuantumTick(y,stereo_image->rows))
1528
0
      if (!MagickMonitorFormatted(y,stereo_image->rows,exception,
1529
0
                                  StereoImageText,image->filename))
1530
0
        break;
1531
0
  }
1532
0
  if (y != (long) stereo_image->rows)
1533
0
    {
1534
0
      if (stereo_image->exception.severity > exception->severity)
1535
0
        CopyException(exception,&stereo_image->exception);
1536
0
      DestroyImage(stereo_image);
1537
0
      stereo_image=(Image *) NULL;
1538
0
    }
1539
0
  return(stereo_image);
1540
0
}
1541

1542
/*
1543
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1544
%                                                                             %
1545
%                                                                             %
1546
%     S w i r l I m a g e                                                     %
1547
%                                                                             %
1548
%                                                                             %
1549
%                                                                             %
1550
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1551
%
1552
%  SwirlImage() swirls the pixels about the center of the image, where
1553
%  degrees indicates the sweep of the arc through which each pixel is moved.
1554
%  You get a more dramatic effect as the degrees move from 1 to 360.
1555
%
1556
%  The format of the SwirlImage method is:
1557
%
1558
%      Image *SwirlImage(const Image *image,double degrees,
1559
%        ExceptionInfo *exception)
1560
%
1561
%  A description of each parameter follows:
1562
%
1563
%    o image: The image.
1564
%
1565
%    o degrees: Define the tightness of the swirling effect.
1566
%
1567
%    o exception: Return any errors or warnings in this structure.
1568
%
1569
%
1570
*/
1571
MagickExport Image *SwirlImage(const Image * restrict image,double degrees,
1572
                               ExceptionInfo *exception)
1573
0
{
1574
0
#define SwirlImageText "[%s] Swirl..."
1575
1576
0
  double
1577
0
    radius,
1578
0
    x_center,
1579
0
    x_scale,
1580
0
    y_center,
1581
0
    y_scale;
1582
1583
0
  long
1584
0
    y;
1585
1586
0
  Image
1587
0
    * restrict swirl_image;
1588
1589
0
  MagickPassFail
1590
0
    status = MagickPass;
1591
1592
  /*
1593
    Initialize swirl image attributes.
1594
  */
1595
0
  assert(image != (const Image *) NULL);
1596
0
  assert(image->signature == MagickSignature);
1597
0
  assert(exception != (ExceptionInfo *) NULL);
1598
0
  assert(exception->signature == MagickSignature);
1599
0
  swirl_image=CloneImage(image,image->columns,image->rows,True,exception);
1600
0
  if (swirl_image == (Image *) NULL)
1601
0
    return((Image *) NULL);
1602
0
  (void) SetImageType(swirl_image,swirl_image->background_color.opacity !=
1603
0
                      OpaqueOpacity ? TrueColorMatteType : TrueColorType);
1604
  /*
1605
    Compute scaling factor.
1606
  */
1607
0
  x_center=image->columns/2.0;
1608
0
  y_center=image->rows/2.0;
1609
0
  radius=Max(x_center,y_center);
1610
0
  x_scale=1.0;
1611
0
  y_scale=1.0;
1612
0
  if (image->columns > image->rows)
1613
0
    y_scale=(double) image->columns/image->rows;
1614
0
  else
1615
0
    if (image->columns < image->rows)
1616
0
      x_scale=(double) image->rows/image->columns;
1617
0
  degrees=DegreesToRadians(degrees);
1618
  /*
1619
    Swirl each row.
1620
  */
1621
0
  {
1622
0
    unsigned long
1623
0
      row_count=0;
1624
1625
0
    MagickBool
1626
0
      monitor_active;
1627
1628
0
    monitor_active=MagickMonitorActive();
1629
1630
#if defined(HAVE_OPENMP)
1631
#  if defined(TUNE_OPENMP)
1632
#    pragma omp parallel for schedule(runtime) shared(row_count, status)
1633
#  else
1634
#    pragma omp parallel for schedule(guided) shared(row_count, status)
1635
#  endif
1636
#endif
1637
0
    for (y=0; y < (long) image->rows; y++)
1638
0
      {
1639
0
        register PixelPacket
1640
0
          * restrict q;
1641
1642
0
        register long
1643
0
          x;
1644
1645
0
        double
1646
0
          x_distance,
1647
0
          y_distance,
1648
0
          distance;
1649
1650
0
        ViewInfo
1651
0
          *image_view;
1652
1653
0
        MagickPassFail
1654
0
          thread_status;
1655
1656
0
        thread_status=status;
1657
0
        if (thread_status == MagickFail)
1658
0
          continue;
1659
1660
0
        image_view=AccessDefaultCacheView(image);
1661
0
        q=SetImagePixelsEx(swirl_image,0,y,swirl_image->columns,1,exception);
1662
0
        if (q == (PixelPacket *) NULL)
1663
0
          thread_status=MagickFail;
1664
0
        if (thread_status != MagickFail)
1665
0
          {
1666
0
            y_distance=y_scale*(y-y_center);
1667
0
            for (x=0; x < (long) image->columns; x++)
1668
0
              {
1669
                /*
1670
                  Determine if the pixel is within an ellipse.
1671
                */
1672
0
                x_distance=x_scale*(x-x_center);
1673
0
                distance=x_distance*x_distance+y_distance*y_distance;
1674
0
                if (distance >= (radius*radius))
1675
0
                  (void) AcquireOneCacheViewPixel(image_view,q,x,y,exception);
1676
0
                else
1677
0
                  {
1678
0
                    double
1679
0
                      cosine,
1680
0
                      factor,
1681
0
                      sine;
1682
1683
                    /*
1684
                      Swirl the pixel.
1685
                    */
1686
0
                    factor=1.0-sqrt(distance)/radius;
1687
0
                    sine=sin(degrees*factor*factor);
1688
0
                    cosine=cos(degrees*factor*factor);
1689
0
                    if (InterpolateViewColor(image_view,q,
1690
0
                                             (cosine*x_distance-sine*y_distance)/x_scale+x_center,
1691
0
                                             (sine*x_distance+cosine*y_distance)/y_scale+y_center,
1692
0
                                             exception) == MagickFail)
1693
0
                      {
1694
0
                        thread_status=MagickFail;
1695
0
                        break;
1696
0
                      }
1697
0
                  }
1698
0
                q++;
1699
0
              }
1700
0
            if (thread_status != MagickFail)
1701
0
              if (!SyncImagePixelsEx(swirl_image,exception))
1702
0
                thread_status=MagickFail;
1703
0
          }
1704
1705
0
        if (monitor_active)
1706
0
          {
1707
0
            unsigned long
1708
0
              thread_row_count;
1709
1710
#if defined(HAVE_OPENMP)
1711
#  pragma omp atomic
1712
#endif
1713
0
            row_count++;
1714
#if defined(HAVE_OPENMP)
1715
#  pragma omp flush (row_count)
1716
#endif
1717
0
            thread_row_count=row_count;
1718
0
            if (QuantumTick(thread_row_count,image->rows))
1719
0
              if (!MagickMonitorFormatted(thread_row_count,image->rows,exception,
1720
0
                                          SwirlImageText,image->filename))
1721
0
                thread_status=MagickFail;
1722
0
          }
1723
1724
0
        if (thread_status == MagickFail)
1725
0
          {
1726
0
            status=MagickFail;
1727
#if defined(HAVE_OPENMP)
1728
#  pragma omp flush (status)
1729
#endif
1730
0
          }
1731
0
      }
1732
0
  }
1733
0
  swirl_image->is_grayscale=image->is_grayscale;
1734
0
  if (status == MagickFail)
1735
0
    {
1736
0
      DestroyImage(swirl_image);
1737
0
      swirl_image=(Image *) NULL;
1738
0
    }
1739
0
  return(swirl_image);
1740
0
}
1741

1742
/*
1743
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1744
%                                                                             %
1745
%                                                                             %
1746
%     W a v e I m a g e                                                       %
1747
%                                                                             %
1748
%                                                                             %
1749
%                                                                             %
1750
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1751
%
1752
%  The WaveImage() filter creates a "ripple" effect in the image by shifting
1753
%  the pixels vertically along a sine wave whose amplitude and wavelength
1754
%  is specified by the given parameters.
1755
%
1756
%  The format of the WaveImage method is:
1757
%
1758
%      Image *WaveImage(const Image *image,const double amplitude,
1759
%        const double wave_length,ExceptionInfo *exception)
1760
%
1761
%  A description of each parameter follows:
1762
%
1763
%    o image: The image.
1764
%
1765
%    o amplitude, frequency:  Define the amplitude and wave_length of the
1766
%      sine wave.
1767
%
1768
%    o exception: Return any errors or warnings in this structure.
1769
%
1770
%
1771
*/
1772
MagickExport Image *WaveImage(const Image * restrict image,const double amplitude,
1773
                              const double wave_length,ExceptionInfo *exception)
1774
0
{
1775
0
#define WaveImageText "[%s] Wave..."
1776
1777
0
  VirtualPixelMethod
1778
0
    virtual_pixel_method;
1779
1780
0
  float
1781
0
    * restrict sine_map;
1782
1783
0
  Image
1784
0
    * restrict wave_image;
1785
1786
0
  long
1787
0
    y;
1788
1789
0
  MagickPassFail
1790
0
    status = MagickPass;
1791
1792
  /*
1793
    Initialize wave image attributes.
1794
  */
1795
0
  assert(image != (Image *) NULL);
1796
0
  assert(image->signature == MagickSignature);
1797
0
  assert(exception != (ExceptionInfo *) NULL);
1798
0
  assert(exception->signature == MagickSignature);
1799
0
  wave_image=CloneImage(image,image->columns,(long)
1800
0
                        (image->rows+2.0*FABSF(amplitude)),MagickTrue,exception);
1801
0
  if (wave_image == (Image *) NULL)
1802
0
    return((Image *) NULL);
1803
0
  wave_image->storage_class=DirectClass;
1804
1805
  /*
1806
    If background color is non-opaque, then initialize matte channel.
1807
  */
1808
0
  if ((wave_image->background_color.opacity != OpaqueOpacity) &&
1809
0
      (!wave_image->matte))
1810
0
    SetImageOpacity(wave_image,OpaqueOpacity);
1811
1812
  /*
1813
    Allocate and initialize sine map.
1814
  */
1815
0
  {
1816
0
    register long
1817
0
      x;
1818
1819
0
    sine_map=MagickAllocateArray(float *,wave_image->columns,sizeof(float));
1820
0
    if (sine_map == (float *) NULL)
1821
0
      {
1822
0
        DestroyImage(wave_image);
1823
0
        ThrowImageException(ResourceLimitError,MemoryAllocationFailed,
1824
0
                            MagickMsg(OptionError,UnableToWaveImage));
1825
0
      }
1826
1827
#if defined(HAVE_OPENMP)
1828
#  pragma omp parallel for schedule(static,256)
1829
#endif
1830
0
    for (x=0; x < (long) wave_image->columns; x++)
1831
0
      sine_map[x]=(float) (FABSF(amplitude)+amplitude*SINF((2.0*MagickPI*x)/wave_length));
1832
0
  }
1833
  /*
1834
    Set virtual pixel method.
1835
  */
1836
1837
0
  virtual_pixel_method=GetImageVirtualPixelMethod(image);
1838
0
  if (virtual_pixel_method == UndefinedVirtualPixelMethod)
1839
0
    (void) SetImageVirtualPixelMethod(image,ConstantVirtualPixelMethod);
1840
  /*
1841
    Wave image.
1842
  */
1843
0
  {
1844
0
    unsigned long
1845
0
      row_count=0;
1846
1847
0
    MagickBool
1848
0
      monitor_active;
1849
1850
0
    monitor_active=MagickMonitorActive();
1851
1852
#if defined(HAVE_OPENMP)
1853
#  if defined(TUNE_OPENMP)
1854
#    pragma omp parallel for schedule(runtime) shared(row_count, status)
1855
#  else
1856
#    pragma omp parallel for schedule(guided) shared(row_count, status)
1857
#  endif
1858
#endif
1859
0
    for (y=0; y < (long) wave_image->rows; y++)
1860
0
      {
1861
0
        register PixelPacket
1862
0
          * restrict q;
1863
1864
0
        register unsigned long
1865
0
          x;
1866
1867
0
        ViewInfo
1868
0
          *image_view;
1869
1870
0
        MagickPassFail
1871
0
          thread_status;
1872
1873
0
        thread_status=status;
1874
0
        if (thread_status == MagickFail)
1875
0
          continue;
1876
1877
0
        image_view=AccessDefaultCacheView(image);
1878
0
        q=SetImagePixelsEx(wave_image,0,y,wave_image->columns,1,exception);
1879
0
        if (q == (PixelPacket *) NULL)
1880
0
          thread_status=MagickFail;
1881
0
        if (thread_status != MagickFail)
1882
0
          {
1883
0
            for (x=0; x < wave_image->columns; x++)
1884
0
              {
1885
0
                if (InterpolateViewColor(image_view,&q[x],(double) x,
1886
0
                                         (double) y-sine_map[x],
1887
0
                                         exception) == MagickFail)
1888
0
                  {
1889
0
                    thread_status=MagickFail;
1890
0
                    break;
1891
0
                  }
1892
0
              }
1893
0
            if (thread_status != MagickFail)
1894
0
              if (!SyncImagePixelsEx(wave_image,exception))
1895
0
                thread_status=MagickFail;
1896
0
          }
1897
1898
0
        if (monitor_active)
1899
0
          {
1900
0
            unsigned long
1901
0
              thread_row_count;
1902
1903
#if defined(HAVE_OPENMP)
1904
#  pragma omp atomic
1905
#endif
1906
0
            row_count++;
1907
#if defined(HAVE_OPENMP)
1908
#  pragma omp flush (row_count)
1909
#endif
1910
0
            thread_row_count=row_count;
1911
0
            if (QuantumTick(thread_row_count,wave_image->rows))
1912
0
              if (!MagickMonitorFormatted(thread_row_count,wave_image->rows,exception,
1913
0
                                          WaveImageText,image->filename))
1914
0
                thread_status=MagickFail;
1915
0
          }
1916
1917
0
        if (thread_status == MagickFail)
1918
0
          {
1919
0
            status=MagickFail;
1920
#if defined(HAVE_OPENMP)
1921
#  pragma omp flush (status)
1922
#endif
1923
0
          }
1924
0
      }
1925
0
  }
1926
  /*
1927
    Restore virtual pixel method.
1928
  */
1929
0
  (void) SetImageVirtualPixelMethod(image,virtual_pixel_method);
1930
0
  MagickFreeMemory(sine_map);
1931
0
  wave_image->is_grayscale=(image->is_grayscale && IsGray(wave_image->background_color));
1932
0
  if (status == MagickFail)
1933
0
    {
1934
0
      DestroyImage(wave_image);
1935
      wave_image=(Image *) NULL;
1936
0
    }
1937
0
  return(wave_image);
1938
0
}