Coverage Report

Created: 2025-06-16 07:00

/src/imagemagick/MagickCore/paint.c
Line
Count
Source (jump to first uncovered line)
1
/*
2
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3
%                                                                             %
4
%                                                                             %
5
%                                                                             %
6
%                      PPPP    AAA   IIIII  N   N  TTTTT                      %
7
%                      P   P  A   A    I    NN  N    T                        %
8
%                      PPPP   AAAAA    I    N N N    T                        %
9
%                      P      A   A    I    N  NN    T                        %
10
%                      P      A   A  IIIII  N   N    T                        %
11
%                                                                             %
12
%                                                                             %
13
%                        Methods to Paint on an Image                         %
14
%                                                                             %
15
%                              Software Design                                %
16
%                                   Cristy                                    %
17
%                                 July 1998                                   %
18
%                                                                             %
19
%                                                                             %
20
%  Copyright @ 1999 ImageMagick Studio LLC, a non-profit organization         %
21
%  dedicated to making software imaging solutions freely available.           %
22
%                                                                             %
23
%  You may not use this file except in compliance with the License.  You may  %
24
%  obtain a copy of the License at                                            %
25
%                                                                             %
26
%    https://imagemagick.org/script/license.php                               %
27
%                                                                             %
28
%  Unless required by applicable law or agreed to in writing, software        %
29
%  distributed under the License is distributed on an "AS IS" BASIS,          %
30
%  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   %
31
%  See the License for the specific language governing permissions and        %
32
%  limitations under the License.                                             %
33
%                                                                             %
34
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
35
%
36
%
37
*/
38

39
/*
40
 Include declarations.
41
*/
42
#include "MagickCore/studio.h"
43
#include "MagickCore/artifact.h"
44
#include "MagickCore/channel.h"
45
#include "MagickCore/color.h"
46
#include "MagickCore/color-private.h"
47
#include "MagickCore/colorspace-private.h"
48
#include "MagickCore/composite.h"
49
#include "MagickCore/composite-private.h"
50
#include "MagickCore/draw.h"
51
#include "MagickCore/draw-private.h"
52
#include "MagickCore/exception.h"
53
#include "MagickCore/exception-private.h"
54
#include "MagickCore/gem.h"
55
#include "MagickCore/gem-private.h"
56
#include "MagickCore/monitor.h"
57
#include "MagickCore/monitor-private.h"
58
#include "MagickCore/option.h"
59
#include "MagickCore/paint.h"
60
#include "MagickCore/pixel-accessor.h"
61
#include "MagickCore/resource_.h"
62
#include "MagickCore/statistic.h"
63
#include "MagickCore/string_.h"
64
#include "MagickCore/string-private.h"
65
#include "MagickCore/thread-private.h"
66

67
/*
68
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
69
%                                                                             %
70
%                                                                             %
71
%                                                                             %
72
%   F l o o d f i l l P a i n t I m a g e                                     %
73
%                                                                             %
74
%                                                                             %
75
%                                                                             %
76
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
77
%
78
%  FloodfillPaintImage() changes the color value of any pixel that matches
79
%  target and is an immediate neighbor.  If the method FillToBorderMethod is
80
%  specified, the color value is changed for any neighbor pixel that does not
81
%  match the bordercolor member of image.
82
%
83
%  By default target must match a particular pixel color exactly.  However,
84
%  in many cases two colors may differ by a small amount.  The fuzz member of
85
%  image defines how much tolerance is acceptable to consider two colors as
86
%  the same.  For example, set fuzz to 10 and the color red at intensities of
87
%  100 and 102 respectively are now interpreted as the same color for the
88
%  purposes of the floodfill.
89
%
90
%  The format of the FloodfillPaintImage method is:
91
%
92
%      MagickBooleanType FloodfillPaintImage(Image *image,
93
%        const DrawInfo *draw_info,const PixelInfo target,
94
%        const ssize_t x_offset,const ssize_t y_offset,
95
%        const MagickBooleanType invert,ExceptionInfo *exception)
96
%
97
%  A description of each parameter follows:
98
%
99
%    o image: the image.
100
%
101
%    o draw_info: the draw info.
102
%
103
%    o target: the RGB value of the target color.
104
%
105
%    o x_offset,y_offset: the starting location of the operation.
106
%
107
%    o invert: paint any pixel that does not match the target color.
108
%
109
%    o exception: return any errors or warnings in this structure.
110
%
111
*/
112
MagickExport MagickBooleanType FloodfillPaintImage(Image *image,
113
  const DrawInfo *draw_info,const PixelInfo *target,const ssize_t x_offset,
114
  const ssize_t y_offset,const MagickBooleanType invert,
115
  ExceptionInfo *exception)
116
2.59k
{
117
158k
#define MaxStacksize  524288UL
118
156k
#define PushSegmentStack(up,left,right,delta) \
119
156k
{ \
120
156k
  if (s >= (segment_stack+MaxStacksize)) \
121
156k
    { \
122
0
      segment_info=RelinquishVirtualMemory(segment_info); \
123
0
      image_view=DestroyCacheView(image_view); \
124
0
      floodplane_view=DestroyCacheView(floodplane_view); \
125
0
      floodplane_image=DestroyImage(floodplane_image); \
126
0
      ThrowBinaryException(DrawError,"SegmentStackOverflow",image->filename) \
127
0
    } \
128
156k
  else \
129
156k
    { \
130
156k
      if ((((up)+(delta)) >= 0) && (((up)+(delta)) < (ssize_t) image->rows)) \
131
156k
        { \
132
99.0k
          s->x1=(double) (left); \
133
99.0k
          s->y1=(double) (up); \
134
99.0k
          s->x2=(double) (right); \
135
99.0k
          s->y2=(double) (delta); \
136
99.0k
          s++; \
137
99.0k
        } \
138
156k
    } \
139
156k
}
140
141
2.59k
  CacheView
142
2.59k
    *floodplane_view,
143
2.59k
    *image_view;
144
145
2.59k
  Image
146
2.59k
    *floodplane_image;
147
148
2.59k
  MagickBooleanType
149
2.59k
    skip,
150
2.59k
    status;
151
152
2.59k
  MemoryInfo
153
2.59k
    *segment_info;
154
155
2.59k
  PixelInfo
156
2.59k
    pixel;
157
158
2.59k
  SegmentInfo
159
2.59k
    *s;
160
161
2.59k
  SegmentInfo
162
2.59k
    *segment_stack;
163
164
2.59k
  ssize_t
165
2.59k
    offset,
166
2.59k
    start,
167
2.59k
    x1,
168
2.59k
    x2,
169
2.59k
    y;
170
171
  /*
172
    Check boundary conditions.
173
  */
174
2.59k
  assert(image != (Image *) NULL);
175
2.59k
  assert(image->signature == MagickCoreSignature);
176
2.59k
  assert(draw_info != (DrawInfo *) NULL);
177
2.59k
  assert(draw_info->signature == MagickCoreSignature);
178
2.59k
  if (IsEventLogging() != MagickFalse) 
179
0
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
180
2.59k
  if ((x_offset < 0) || (x_offset >= (ssize_t) image->columns))
181
956
    return(MagickFalse);
182
1.64k
  if ((y_offset < 0) || (y_offset >= (ssize_t) image->rows))
183
386
    return(MagickFalse);
184
1.25k
  if (SetImageStorageClass(image,DirectClass,exception) == MagickFalse)
185
0
    return(MagickFalse);
186
1.25k
  if (IsGrayColorspace(image->colorspace) != MagickFalse)
187
0
    (void) SetImageColorspace(image,sRGBColorspace,exception);
188
1.25k
  if (((image->alpha_trait & BlendPixelTrait) == 0) &&
189
1.25k
      (draw_info->fill.alpha_trait != UndefinedPixelTrait))
190
0
    (void) SetImageAlpha(image,OpaqueAlpha,exception);
191
  /*
192
    Set floodfill state.
193
  */
194
1.25k
  floodplane_image=CloneImage(image,0,0,MagickTrue,exception);
195
1.25k
  if (floodplane_image == (Image *) NULL)
196
0
    return(MagickFalse);
197
1.25k
  floodplane_image->alpha_trait=UndefinedPixelTrait;
198
1.25k
  floodplane_image->colorspace=GRAYColorspace;
199
1.25k
  (void) QueryColorCompliance("#000",AllCompliance,
200
1.25k
    &floodplane_image->background_color,exception);
201
1.25k
  (void) SetImageBackgroundColor(floodplane_image,exception);
202
1.25k
  segment_info=AcquireVirtualMemory(MaxStacksize,sizeof(*segment_stack));
203
1.25k
  if (segment_info == (MemoryInfo *) NULL)
204
0
    {
205
0
      floodplane_image=DestroyImage(floodplane_image);
206
0
      ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
207
0
        image->filename);
208
0
    }
209
1.25k
  segment_stack=(SegmentInfo *) GetVirtualMemoryBlob(segment_info);
210
  /*
211
    Push initial segment on stack.
212
  */
213
1.25k
  status=MagickTrue;
214
1.25k
  start=0;
215
1.25k
  s=segment_stack;
216
1.25k
  GetPixelInfo(image,&pixel);
217
1.25k
  image_view=AcquireVirtualCacheView(image,exception);
218
1.25k
  floodplane_view=AcquireAuthenticCacheView(floodplane_image,exception);
219
1.25k
  PushSegmentStack(y_offset,x_offset,x_offset,1);
220
1.25k
  PushSegmentStack(y_offset+1,x_offset,x_offset,-1);
221
100k
  while (s > segment_stack)
222
99.0k
  {
223
99.0k
    const Quantum
224
99.0k
      *magick_restrict p;
225
226
99.0k
    Quantum
227
99.0k
      *magick_restrict q;
228
229
99.0k
    ssize_t
230
99.0k
      x;
231
232
    /*
233
      Pop segment off stack.
234
    */
235
99.0k
    s--;
236
99.0k
    x1=(ssize_t) s->x1;
237
99.0k
    x2=(ssize_t) s->x2;
238
99.0k
    offset=(ssize_t) s->y2;
239
99.0k
    y=(ssize_t) s->y1+offset;
240
    /*
241
      Recolor neighboring pixels.
242
    */
243
99.0k
    p=GetCacheViewVirtualPixels(image_view,0,y,(size_t) (x1+1),1,exception);
244
99.0k
    q=GetCacheViewAuthenticPixels(floodplane_view,0,y,(size_t) (x1+1),1,
245
99.0k
      exception);
246
99.0k
    if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
247
0
      break;
248
99.0k
    p+=(ptrdiff_t) x1*(ssize_t) GetPixelChannels(image);
249
99.0k
    q+=(ptrdiff_t) x1*(ssize_t) GetPixelChannels(floodplane_image);
250
130k
    for (x=x1; x >= 0; x--)
251
105k
    {
252
105k
      if (GetPixelGray(floodplane_image,q) != 0)
253
69.9k
        break;
254
35.2k
      GetPixelInfoPixel(image,p,&pixel);
255
35.2k
      if (IsFuzzyEquivalencePixelInfo(&pixel,target) == invert)
256
3.86k
        break;
257
31.3k
      SetPixelGray(floodplane_image,QuantumRange,q);
258
31.3k
      p-=(ptrdiff_t)GetPixelChannels(image);
259
31.3k
      q-=GetPixelChannels(floodplane_image);
260
31.3k
    }
261
99.0k
    if (SyncCacheViewAuthenticPixels(floodplane_view,exception) == MagickFalse)
262
0
      break;
263
99.0k
    skip=x >= x1 ? MagickTrue : MagickFalse;
264
99.0k
    if (skip == MagickFalse)
265
25.6k
      {
266
25.6k
        start=x+1;
267
25.6k
        if (start < x1)
268
25.6k
          PushSegmentStack(y,start,x1-1,-offset);
269
25.6k
        x=x1+1;
270
25.6k
      }
271
99.0k
    do
272
223k
    {
273
223k
      if (skip == MagickFalse)
274
149k
        {
275
149k
          if (x < (ssize_t) image->columns)
276
149k
            {
277
149k
              p=GetCacheViewVirtualPixels(image_view,x,y,(size_t)
278
149k
                ((ssize_t) image->columns-x),1,exception);
279
149k
              q=GetCacheViewAuthenticPixels(floodplane_view,x,y,(size_t)
280
149k
                ((ssize_t) image->columns-x),1,exception);
281
149k
              if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
282
0
                break;
283
2.27M
              for ( ; x < (ssize_t) image->columns; x++)
284
2.24M
              {
285
2.24M
                if (GetPixelGray(floodplane_image,q) != 0)
286
122k
                  break;
287
2.12M
                GetPixelInfoPixel(image,p,&pixel);
288
2.12M
                if (IsFuzzyEquivalencePixelInfo(&pixel,target) == invert)
289
2.52k
                  break;
290
2.12M
                SetPixelGray(floodplane_image,QuantumRange,q);
291
2.12M
                p+=(ptrdiff_t) GetPixelChannels(image);
292
2.12M
                q+=(ptrdiff_t) GetPixelChannels(floodplane_image);
293
2.12M
              }
294
149k
              status=SyncCacheViewAuthenticPixels(floodplane_view,exception);
295
149k
              if (status == MagickFalse)
296
0
                break;
297
149k
            }
298
149k
          PushSegmentStack(y,start,x-1,offset);
299
149k
          if (x > (x2+1))
300
149k
            PushSegmentStack(y,x2+1,x-1,-offset);
301
149k
        }
302
223k
      skip=MagickFalse;
303
223k
      x++;
304
223k
      if (x <= x2)
305
125k
        {
306
125k
          p=GetCacheViewVirtualPixels(image_view,x,y,(size_t) (x2-x+1),1,
307
125k
            exception);
308
125k
          q=GetCacheViewAuthenticPixels(floodplane_view,x,y,(size_t) (x2-x+1),1,
309
125k
            exception);
310
125k
          if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
311
0
            break;
312
133k
          for ( ; x <= x2; x++)
313
132k
          {
314
132k
            if (GetPixelGray(floodplane_image,q) != 0)
315
122k
              break;
316
10.1k
            GetPixelInfoPixel(image,p,&pixel);
317
10.1k
            if (IsFuzzyEquivalencePixelInfo(&pixel,target) != invert)
318
1.71k
              break;
319
8.42k
            p+=(ptrdiff_t) GetPixelChannels(image);
320
8.42k
            q+=(ptrdiff_t) GetPixelChannels(floodplane_image);
321
8.42k
          }
322
125k
        }
323
223k
      start=x;
324
223k
    } while (x <= x2);
325
99.0k
  }
326
1.25k
  status=MagickTrue;
327
#if defined(MAGICKCORE_OPENMP_SUPPORT)
328
  #pragma omp parallel for schedule(static) shared(status) \
329
    magick_number_threads(floodplane_image,image,image->rows,2)
330
#endif
331
29.0k
  for (y=0; y < (ssize_t) image->rows; y++)
332
27.7k
  {
333
27.7k
    const Quantum
334
27.7k
      *magick_restrict p;
335
336
27.7k
    Quantum
337
27.7k
      *magick_restrict q;
338
339
27.7k
    ssize_t
340
27.7k
      x;
341
342
    /*
343
      Tile fill color onto floodplane.
344
    */
345
27.7k
    if (status == MagickFalse)
346
0
      continue;
347
27.7k
    p=GetCacheViewVirtualPixels(floodplane_view,0,y,image->columns,1,exception);
348
27.7k
    q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
349
27.7k
    if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
350
0
      {
351
0
        status=MagickFalse;
352
0
        continue;
353
0
      }
354
3.20M
    for (x=0; x < (ssize_t) image->columns; x++)
355
3.17M
    {
356
3.17M
      if (GetPixelGray(floodplane_image,p) != 0)
357
2.15M
        {
358
2.15M
          PixelInfo
359
2.15M
            fill_color;
360
361
2.15M
          GetFillColor(draw_info,x,y,&fill_color,exception);
362
2.15M
          if ((image->channel_mask & RedChannel) != 0)
363
1.10M
            SetPixelRed(image,(Quantum) fill_color.red,q);
364
2.15M
          if ((image->channel_mask & GreenChannel) != 0)
365
1.10M
            SetPixelGreen(image,(Quantum) fill_color.green,q);
366
2.15M
          if ((image->channel_mask & BlueChannel) != 0)
367
1.10M
            SetPixelBlue(image,(Quantum) fill_color.blue,q);
368
2.15M
          if ((image->channel_mask & BlackChannel) != 0)
369
1.10M
            SetPixelBlack(image,(Quantum) fill_color.black,q);
370
2.15M
          if (((image->channel_mask & AlphaChannel) != 0) &&
371
2.15M
              ((image->alpha_trait & BlendPixelTrait) != 0))
372
2.15M
            SetPixelAlpha(image,(Quantum) fill_color.alpha,q);
373
2.15M
        }
374
3.17M
      p+=(ptrdiff_t) GetPixelChannels(floodplane_image);
375
3.17M
      q+=(ptrdiff_t) GetPixelChannels(image);
376
3.17M
    }
377
27.7k
    if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
378
0
      status=MagickFalse;
379
27.7k
  }
380
1.25k
  floodplane_view=DestroyCacheView(floodplane_view);
381
1.25k
  image_view=DestroyCacheView(image_view);
382
1.25k
  segment_info=RelinquishVirtualMemory(segment_info);
383
1.25k
  floodplane_image=DestroyImage(floodplane_image);
384
1.25k
  return(status);
385
1.25k
}
386

387
/*
388
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
389
%                                                                             %
390
%                                                                             %
391
%                                                                             %
392
+     G r a d i e n t I m a g e                                               %
393
%                                                                             %
394
%                                                                             %
395
%                                                                             %
396
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
397
%
398
%  GradientImage() applies a continuously smooth color transitions along a
399
%  vector from one color to another.
400
%
401
%  Note, the interface of this method will change in the future to support
402
%  more than one transition.
403
%
404
%  The format of the GradientImage method is:
405
%
406
%      MagickBooleanType GradientImage(Image *image,const GradientType type,
407
%        const SpreadMethod method,const PixelInfo *start_color,
408
%        const PixelInfo *stop_color,ExceptionInfo *exception)
409
%
410
%  A description of each parameter follows:
411
%
412
%    o image: the image.
413
%
414
%    o type: the gradient type: linear or radial.
415
%
416
%    o spread: the gradient spread method: pad, reflect, or repeat.
417
%
418
%    o start_color: the start color.
419
%
420
%    o stop_color: the stop color.
421
%
422
%    o exception: return any errors or warnings in this structure.
423
%
424
*/
425
MagickExport MagickBooleanType GradientImage(Image *image,
426
  const GradientType type,const SpreadMethod method,const StopInfo *stops,
427
  const size_t number_stops,ExceptionInfo *exception)
428
9.28k
{
429
9.28k
  const char
430
9.28k
    *artifact;
431
432
9.28k
  DrawInfo
433
9.28k
    *draw_info;
434
435
9.28k
  GradientInfo
436
9.28k
    *gradient;
437
438
9.28k
  MagickBooleanType
439
9.28k
    status;
440
441
  /*
442
    Set gradient start-stop end points.
443
  */
444
9.28k
  assert(image != (const Image *) NULL);
445
9.28k
  assert(image->signature == MagickCoreSignature);
446
9.28k
  assert(stops != (const StopInfo *) NULL);
447
9.28k
  assert(number_stops > 0);
448
9.28k
  if (IsEventLogging() != MagickFalse) 
449
0
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
450
9.28k
  draw_info=AcquireDrawInfo();
451
9.28k
  gradient=(&draw_info->gradient);
452
9.28k
  gradient->type=type;
453
9.28k
  gradient->bounding_box.width=image->columns;
454
9.28k
  gradient->bounding_box.height=image->rows;
455
9.28k
  artifact=GetImageArtifact(image,"gradient:bounding-box");
456
9.28k
  if (artifact != (const char *) NULL)
457
0
    (void) ParseAbsoluteGeometry(artifact,&gradient->bounding_box);
458
9.28k
  gradient->gradient_vector.x2=(double) image->columns-1;
459
9.28k
  gradient->gradient_vector.y2=(double) image->rows-1;
460
9.28k
  artifact=GetImageArtifact(image,"gradient:direction");
461
9.28k
  if (artifact != (const char *) NULL)
462
0
    {
463
0
      GravityType
464
0
        direction;
465
466
0
      direction=(GravityType) ParseCommandOption(MagickGravityOptions,
467
0
        MagickFalse,artifact);
468
0
      switch (direction)
469
0
      {
470
0
        case NorthWestGravity:
471
0
        {
472
0
          gradient->gradient_vector.x1=(double) image->columns-1;
473
0
          gradient->gradient_vector.y1=(double) image->rows-1;
474
0
          gradient->gradient_vector.x2=0.0;
475
0
          gradient->gradient_vector.y2=0.0;
476
0
          break;
477
0
        }
478
0
        case NorthGravity:
479
0
        {
480
0
          gradient->gradient_vector.x1=0.0;
481
0
          gradient->gradient_vector.y1=(double) image->rows-1;
482
0
          gradient->gradient_vector.x2=0.0;
483
0
          gradient->gradient_vector.y2=0.0;
484
0
          break;
485
0
        }
486
0
        case NorthEastGravity:
487
0
        {
488
0
          gradient->gradient_vector.x1=0.0;
489
0
          gradient->gradient_vector.y1=(double) image->rows-1;
490
0
          gradient->gradient_vector.x2=(double) image->columns-1;
491
0
          gradient->gradient_vector.y2=0.0;
492
0
          break;
493
0
        }
494
0
        case WestGravity:
495
0
        {
496
0
          gradient->gradient_vector.x1=(double) image->columns-1;
497
0
          gradient->gradient_vector.y1=0.0;
498
0
          gradient->gradient_vector.x2=0.0;
499
0
          gradient->gradient_vector.y2=0.0;
500
0
          break;
501
0
        }
502
0
        case EastGravity:
503
0
        {
504
0
          gradient->gradient_vector.x1=0.0;
505
0
          gradient->gradient_vector.y1=0.0;
506
0
          gradient->gradient_vector.x2=(double) image->columns-1;
507
0
          gradient->gradient_vector.y2=0.0;
508
0
          break;
509
0
        }
510
0
        case SouthWestGravity:
511
0
        {
512
0
          gradient->gradient_vector.x1=(double) image->columns-1;
513
0
          gradient->gradient_vector.y1=0.0;
514
0
          gradient->gradient_vector.x2=0.0;
515
0
          gradient->gradient_vector.y2=(double) image->rows-1;
516
0
          break;
517
0
        }
518
0
        case SouthGravity:
519
0
        {
520
0
          gradient->gradient_vector.x1=0.0;
521
0
          gradient->gradient_vector.y1=0.0;
522
0
          gradient->gradient_vector.x2=0.0;
523
0
          gradient->gradient_vector.y2=(double) image->rows-1;
524
0
          break;
525
0
        }
526
0
        case SouthEastGravity:
527
0
        {
528
0
          gradient->gradient_vector.x1=0.0;
529
0
          gradient->gradient_vector.y1=0.0;
530
0
          gradient->gradient_vector.x2=(double) image->columns-1;
531
0
          gradient->gradient_vector.y2=(double) image->rows-1;
532
0
          break;
533
0
        }
534
0
        default:
535
0
          break;
536
0
      }
537
0
    }
538
9.28k
  artifact=GetImageArtifact(image,"gradient:angle");
539
9.28k
  if (artifact != (const char *) NULL)
540
0
    gradient->angle=StringToDouble(artifact,(char **) NULL);
541
9.28k
  artifact=GetImageArtifact(image,"gradient:vector");
542
9.28k
  if (artifact != (const char *) NULL)
543
0
    (void) MagickSscanf(artifact,"%lf%*[ ,]%lf%*[ ,]%lf%*[ ,]%lf",
544
0
      &gradient->gradient_vector.x1,&gradient->gradient_vector.y1,
545
0
      &gradient->gradient_vector.x2,&gradient->gradient_vector.y2);
546
9.28k
  if ((GetImageArtifact(image,"gradient:angle") == (const char *) NULL) &&
547
9.28k
      (GetImageArtifact(image,"gradient:direction") == (const char *) NULL) &&
548
9.28k
      (GetImageArtifact(image,"gradient:extent") == (const char *) NULL) &&
549
9.28k
      (GetImageArtifact(image,"gradient:vector") == (const char *) NULL))
550
9.28k
    if ((type == LinearGradient) && (gradient->gradient_vector.y2 != 0.0))
551
584
      gradient->gradient_vector.x2=0.0;
552
9.28k
  gradient->center.x=(double) gradient->gradient_vector.x2/2.0;
553
9.28k
  gradient->center.y=(double) gradient->gradient_vector.y2/2.0;
554
9.28k
  artifact=GetImageArtifact(image,"gradient:center");
555
9.28k
  if (artifact != (const char *) NULL)
556
0
    (void) MagickSscanf(artifact,"%lf%*[ ,]%lf",&gradient->center.x,
557
0
      &gradient->center.y);
558
9.28k
  artifact=GetImageArtifact(image,"gradient:angle");
559
9.28k
  if ((type == LinearGradient) && (artifact != (const char *) NULL))
560
0
    {
561
0
      double
562
0
        sine,
563
0
        cosine,
564
0
        distance;
565
566
      /*
567
        Reference https://drafts.csswg.org/css-images-3/#linear-gradients.
568
      */
569
0
      sine=sin((double) DegreesToRadians(gradient->angle-90.0));
570
0
      cosine=cos((double) DegreesToRadians(gradient->angle-90.0));
571
0
      distance=fabs((double) (image->columns-1.0)*cosine)+
572
0
        fabs((double) (image->rows-1.0)*sine);
573
0
      gradient->gradient_vector.x1=0.5*((image->columns-1.0)-distance*cosine);
574
0
      gradient->gradient_vector.y1=0.5*((image->rows-1.0)-distance*sine);
575
0
      gradient->gradient_vector.x2=0.5*((image->columns-1.0)+distance*cosine);
576
0
      gradient->gradient_vector.y2=0.5*((image->rows-1.0)+distance*sine);
577
0
    }
578
9.28k
  gradient->radii.x=(double) MagickMax((image->columns-1.0),(image->rows-1.0))/
579
9.28k
    2.0;
580
9.28k
  gradient->radii.y=gradient->radii.x;
581
9.28k
  artifact=GetImageArtifact(image,"gradient:extent");
582
9.28k
  if (artifact != (const char *) NULL)
583
0
    {
584
0
      if (LocaleCompare(artifact,"Circle") == 0)
585
0
        {
586
0
          gradient->radii.x=(double) MagickMax((image->columns-1.0),
587
0
            (image->rows-1.0))/2.0;
588
0
          gradient->radii.y=gradient->radii.x;
589
0
        }
590
0
      if (LocaleCompare(artifact,"Diagonal") == 0)
591
0
        {
592
0
          gradient->radii.x=(double) (sqrt((double) (image->columns-1.0)*
593
0
            (image->columns-1.0)+(image->rows-1.0)*(image->rows-1.0)))/2.0;
594
0
          gradient->radii.y=gradient->radii.x;
595
0
        }
596
0
      if (LocaleCompare(artifact,"Ellipse") == 0)
597
0
        {
598
0
          gradient->radii.x=(double) (image->columns-1.0)/2.0;
599
0
          gradient->radii.y=(double) (image->rows-1.0)/2.0;
600
0
        }
601
0
      if (LocaleCompare(artifact,"Maximum") == 0)
602
0
        {
603
0
          gradient->radii.x=(double) MagickMax((image->columns-1.0),
604
0
            (image->rows-1.0))/2.0;
605
0
          gradient->radii.y=gradient->radii.x;
606
0
        }
607
0
      if (LocaleCompare(artifact,"Minimum") == 0)
608
0
        {
609
0
          gradient->radii.x=(double) (MagickMin((image->columns-1.0),
610
0
            (image->rows-1.0)))/2.0;
611
0
          gradient->radii.y=gradient->radii.x;
612
0
        }
613
0
    }
614
9.28k
  artifact=GetImageArtifact(image,"gradient:radii");
615
9.28k
  if (artifact != (const char *) NULL)
616
0
    (void) MagickSscanf(artifact,"%lf%*[ ,]%lf",&gradient->radii.x,
617
0
      &gradient->radii.y);
618
9.28k
  gradient->radius=MagickMax(gradient->radii.x,gradient->radii.y);
619
9.28k
  gradient->spread=method;
620
  /*
621
    Define the gradient to fill between the stops.
622
  */
623
9.28k
  gradient->number_stops=number_stops;
624
9.28k
  gradient->stops=(StopInfo *) AcquireQuantumMemory(gradient->number_stops,
625
9.28k
    sizeof(*gradient->stops));
626
9.28k
  if (gradient->stops == (StopInfo *) NULL)
627
0
    ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
628
9.28k
      image->filename);
629
9.28k
  (void) memcpy(gradient->stops,stops,(size_t) number_stops*sizeof(*stops));
630
  /*
631
    Draw a gradient on the image.
632
  */
633
9.28k
  status=DrawGradientImage(image,draw_info,exception);
634
9.28k
  draw_info=DestroyDrawInfo(draw_info);
635
9.28k
  return(status);
636
9.28k
}
637

638
/*
639
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
640
%                                                                             %
641
%                                                                             %
642
%                                                                             %
643
%     O i l P a i n t I m a g e                                               %
644
%                                                                             %
645
%                                                                             %
646
%                                                                             %
647
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
648
%
649
%  OilPaintImage() applies a special effect filter that simulates an oil
650
%  painting.  Each pixel is replaced by the most frequent color occurring
651
%  in a circular region defined by radius.
652
%
653
%  The format of the OilPaintImage method is:
654
%
655
%      Image *OilPaintImage(const Image *image,const double radius,
656
%        const double sigma,ExceptionInfo *exception)
657
%
658
%  A description of each parameter follows:
659
%
660
%    o image: the image.
661
%
662
%    o radius: the radius of the circular neighborhood.
663
%
664
%    o sigma: the standard deviation of the Gaussian, in pixels.
665
%
666
%    o exception: return any errors or warnings in this structure.
667
%
668
*/
669
670
static size_t **DestroyHistogramTLS(size_t **histogram)
671
0
{
672
0
  ssize_t
673
0
    i;
674
675
0
  assert(histogram != (size_t **) NULL);
676
0
  for (i=0; i < (ssize_t) GetMagickResourceLimit(ThreadResource); i++)
677
0
    if (histogram[i] != (size_t *) NULL)
678
0
      histogram[i]=(size_t *) RelinquishMagickMemory(histogram[i]);
679
0
  histogram=(size_t **) RelinquishMagickMemory(histogram);
680
0
  return(histogram);
681
0
}
682
683
static size_t **AcquireHistogramTLS(const size_t count)
684
0
{
685
0
  ssize_t
686
0
    i;
687
688
0
  size_t
689
0
    **histogram,
690
0
    number_threads;
691
692
0
  number_threads=(size_t) GetMagickResourceLimit(ThreadResource);
693
0
  histogram=(size_t **) AcquireQuantumMemory(number_threads,sizeof(*histogram));
694
0
  if (histogram == (size_t **) NULL)
695
0
    return((size_t **) NULL);
696
0
  (void) memset(histogram,0,number_threads*sizeof(*histogram));
697
0
  for (i=0; i < (ssize_t) number_threads; i++)
698
0
  {
699
0
    histogram[i]=(size_t *) AcquireQuantumMemory(count,sizeof(**histogram));
700
0
    if (histogram[i] == (size_t *) NULL)
701
0
      return(DestroyHistogramTLS(histogram));
702
0
  }
703
0
  return(histogram);
704
0
}
705
706
MagickExport Image *OilPaintImage(const Image *image,const double radius,
707
  const double sigma,ExceptionInfo *exception)
708
0
{
709
0
#define NumberPaintBins  256
710
0
#define OilPaintImageTag  "OilPaint/Image"
711
712
0
  CacheView
713
0
    *image_view,
714
0
    *paint_view;
715
716
0
  Image
717
0
    *linear_image,
718
0
    *paint_image;
719
720
0
  MagickBooleanType
721
0
    status;
722
723
0
  MagickOffsetType
724
0
    progress;
725
726
0
  size_t
727
0
    **histograms,
728
0
    width;
729
730
0
  ssize_t
731
0
    center,
732
0
    y;
733
734
  /*
735
    Initialize painted image attributes.
736
  */
737
0
  assert(image != (const Image *) NULL);
738
0
  assert(image->signature == MagickCoreSignature);
739
0
  assert(exception != (ExceptionInfo *) NULL);
740
0
  assert(exception->signature == MagickCoreSignature);
741
0
  if (IsEventLogging() != MagickFalse) 
742
0
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
743
0
  width=GetOptimalKernelWidth2D(radius,sigma);
744
0
  linear_image=CloneImage(image,0,0,MagickTrue,exception);
745
0
  paint_image=CloneImage(image,0,0,MagickTrue,exception);
746
0
  if ((linear_image == (Image *) NULL) || (paint_image == (Image *) NULL))
747
0
    {
748
0
      if (linear_image != (Image *) NULL)
749
0
        linear_image=DestroyImage(linear_image);
750
0
      if (paint_image != (Image *) NULL)
751
0
        linear_image=DestroyImage(paint_image);
752
0
      return((Image *) NULL);
753
0
    }
754
0
  if (SetImageStorageClass(paint_image,DirectClass,exception) == MagickFalse)
755
0
    {
756
0
      linear_image=DestroyImage(linear_image);
757
0
      paint_image=DestroyImage(paint_image);
758
0
      return((Image *) NULL);
759
0
    }
760
0
  histograms=AcquireHistogramTLS(NumberPaintBins);
761
0
  if (histograms == (size_t **) NULL)
762
0
    {
763
0
      linear_image=DestroyImage(linear_image);
764
0
      paint_image=DestroyImage(paint_image);
765
0
      ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
766
0
    }
767
  /*
768
    Oil paint image.
769
  */
770
0
  status=MagickTrue;
771
0
  progress=0;
772
0
  center=(ssize_t) (GetPixelChannels(linear_image)*(linear_image->columns+
773
0
    width)*(width/2L)+GetPixelChannels(linear_image)*(width/2L));
774
0
  image_view=AcquireVirtualCacheView(linear_image,exception);
775
0
  paint_view=AcquireAuthenticCacheView(paint_image,exception);
776
#if defined(MAGICKCORE_OPENMP_SUPPORT)
777
  #pragma omp parallel for schedule(static) shared(progress,status) \
778
    magick_number_threads(linear_image,paint_image,linear_image->rows,1)
779
#endif
780
0
  for (y=0; y < (ssize_t) linear_image->rows; y++)
781
0
  {
782
0
    const Quantum
783
0
      *magick_restrict p;
784
785
0
    Quantum
786
0
      *magick_restrict q;
787
788
0
    size_t
789
0
      *histogram;
790
791
0
    ssize_t
792
0
      x;
793
794
0
    if (status == MagickFalse)
795
0
      continue;
796
0
    p=GetCacheViewVirtualPixels(image_view,-((ssize_t) width/2L),y-(ssize_t)
797
0
      (width/2L),linear_image->columns+width,width,exception);
798
0
    q=QueueCacheViewAuthenticPixels(paint_view,0,y,paint_image->columns,1,
799
0
      exception);
800
0
    if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
801
0
      {
802
0
        status=MagickFalse;
803
0
        continue;
804
0
      }
805
0
    histogram=histograms[GetOpenMPThreadId()];
806
0
    for (x=0; x < (ssize_t) linear_image->columns; x++)
807
0
    {
808
0
      ssize_t
809
0
        i,
810
0
        u;
811
812
0
      size_t
813
0
        count;
814
815
0
      ssize_t
816
0
        j,
817
0
        k,
818
0
        n,
819
0
        v;
820
821
      /*
822
        Assign most frequent color.
823
      */
824
0
      k=0;
825
0
      j=0;
826
0
      count=0;
827
0
      (void) memset(histogram,0,NumberPaintBins* sizeof(*histogram));
828
0
      for (v=0; v < (ssize_t) width; v++)
829
0
      {
830
0
        for (u=0; u < (ssize_t) width; u++)
831
0
        {
832
0
          n=(ssize_t) ScaleQuantumToChar(ClampToQuantum(GetPixelIntensity(
833
0
            linear_image,p+(ssize_t) GetPixelChannels(linear_image)*(u+k))));
834
0
          histogram[n]++;
835
0
          if (histogram[n] > count)
836
0
            {
837
0
              j=k+u;
838
0
              count=histogram[n];
839
0
            }
840
0
        }
841
0
        k+=(ssize_t) (linear_image->columns+width);
842
0
      }
843
0
      for (i=0; i < (ssize_t) GetPixelChannels(linear_image); i++)
844
0
      {
845
0
        PixelChannel channel = GetPixelChannelChannel(linear_image,i);
846
0
        PixelTrait traits = GetPixelChannelTraits(linear_image,channel);
847
0
        PixelTrait paint_traits=GetPixelChannelTraits(paint_image,channel);
848
0
        if ((traits == UndefinedPixelTrait) ||
849
0
            (paint_traits == UndefinedPixelTrait))
850
0
          continue;
851
0
        if ((paint_traits & CopyPixelTrait) != 0)
852
0
          {
853
0
            SetPixelChannel(paint_image,channel,p[center+i],q);
854
0
            continue;
855
0
          }
856
0
        SetPixelChannel(paint_image,channel,p[j*(ssize_t)
857
0
          GetPixelChannels(linear_image)+i],q);
858
0
      }
859
0
      p+=(ptrdiff_t) GetPixelChannels(linear_image);
860
0
      q+=(ptrdiff_t) GetPixelChannels(paint_image);
861
0
    }
862
0
    if (SyncCacheViewAuthenticPixels(paint_view,exception) == MagickFalse)
863
0
      status=MagickFalse;
864
0
    if (linear_image->progress_monitor != (MagickProgressMonitor) NULL)
865
0
      {
866
0
        MagickBooleanType
867
0
          proceed;
868
869
#if defined(MAGICKCORE_OPENMP_SUPPORT)
870
        #pragma omp atomic
871
#endif
872
0
        progress++;
873
0
        proceed=SetImageProgress(linear_image,OilPaintImageTag,progress,
874
0
          linear_image->rows);
875
0
        if (proceed == MagickFalse)
876
0
          status=MagickFalse;
877
0
      }
878
0
  }
879
0
  paint_view=DestroyCacheView(paint_view);
880
0
  image_view=DestroyCacheView(image_view);
881
0
  histograms=DestroyHistogramTLS(histograms);
882
0
  linear_image=DestroyImage(linear_image);
883
0
  if (status == MagickFalse)
884
0
    paint_image=DestroyImage(paint_image);
885
0
  return(paint_image);
886
0
}
887

888
/*
889
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
890
%                                                                             %
891
%                                                                             %
892
%                                                                             %
893
%     O p a q u e P a i n t I m a g e                                         %
894
%                                                                             %
895
%                                                                             %
896
%                                                                             %
897
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
898
%
899
%  OpaquePaintImage() changes any pixel that matches color with the color
900
%  defined by fill argument.
901
%
902
%  By default color must match a particular pixel color exactly.  However, in
903
%  many cases two colors may differ by a small amount.  Fuzz defines how much
904
%  tolerance is acceptable to consider two colors as the same.  For example,
905
%  set fuzz to 10 and the color red at intensities of 100 and 102 respectively
906
%  are now interpreted as the same color.
907
%
908
%  The format of the OpaquePaintImage method is:
909
%
910
%      MagickBooleanType OpaquePaintImage(Image *image,const PixelInfo *target,
911
%        const PixelInfo *fill,const MagickBooleanType invert,
912
%        ExceptionInfo *exception)
913
%
914
%  A description of each parameter follows:
915
%
916
%    o image: the image.
917
%
918
%    o target: the RGB value of the target color.
919
%
920
%    o fill: the replacement color.
921
%
922
%    o invert: paint any pixel that does not match the target color.
923
%
924
%    o exception: return any errors or warnings in this structure.
925
%
926
*/
927
MagickExport MagickBooleanType OpaquePaintImage(Image *image,
928
  const PixelInfo *target,const PixelInfo *fill,const MagickBooleanType invert,
929
  ExceptionInfo *exception)
930
0
{
931
0
#define OpaquePaintImageTag  "Opaque/Image"
932
933
0
  CacheView
934
0
    *image_view;
935
936
0
  MagickBooleanType
937
0
    status;
938
939
0
  MagickOffsetType
940
0
    progress;
941
942
0
  PixelInfo
943
0
    conform_fill,
944
0
    conform_target,
945
0
    zero;
946
947
0
  ssize_t
948
0
    y;
949
950
0
  assert(image != (Image *) NULL);
951
0
  assert(image->signature == MagickCoreSignature);
952
0
  assert(target != (PixelInfo *) NULL);
953
0
  assert(fill != (PixelInfo *) NULL);
954
0
  if (IsEventLogging() != MagickFalse) 
955
0
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
956
0
  if (SetImageStorageClass(image,DirectClass,exception) == MagickFalse)
957
0
    return(MagickFalse);
958
0
  ConformPixelInfo(image,fill,&conform_fill,exception);
959
0
  ConformPixelInfo(image,target,&conform_target,exception);
960
  /*
961
    Make image color opaque.
962
  */
963
0
  status=MagickTrue;
964
0
  progress=0;
965
0
  GetPixelInfo(image,&zero);
966
0
  image_view=AcquireAuthenticCacheView(image,exception);
967
#if defined(MAGICKCORE_OPENMP_SUPPORT)
968
  #pragma omp parallel for schedule(static) shared(progress,status) \
969
    magick_number_threads(image,image,image->rows,1)
970
#endif
971
0
  for (y=0; y < (ssize_t) image->rows; y++)
972
0
  {
973
0
    PixelInfo
974
0
      pixel;
975
976
0
    Quantum
977
0
      *magick_restrict q;
978
979
0
    ssize_t
980
0
      x;
981
982
0
    if (status == MagickFalse)
983
0
      continue;
984
0
    q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
985
0
    if (q == (Quantum *) NULL)
986
0
      {
987
0
        status=MagickFalse;
988
0
        continue;
989
0
      }
990
0
    pixel=zero;
991
0
    for (x=0; x < (ssize_t) image->columns; x++)
992
0
    {
993
0
      GetPixelInfoPixel(image,q,&pixel);
994
0
      if (IsFuzzyEquivalencePixelInfo(&pixel,&conform_target) != invert)
995
0
        {
996
0
          PixelTrait
997
0
            traits;
998
999
0
          traits=GetPixelChannelTraits(image,RedPixelChannel);
1000
0
          if ((traits & UpdatePixelTrait) != 0)
1001
0
            SetPixelRed(image,(Quantum) conform_fill.red,q);
1002
0
          traits=GetPixelChannelTraits(image,GreenPixelChannel);
1003
0
          if ((traits & UpdatePixelTrait) != 0)
1004
0
            SetPixelGreen(image,(Quantum) conform_fill.green,q);
1005
0
          traits=GetPixelChannelTraits(image,BluePixelChannel);
1006
0
          if ((traits & UpdatePixelTrait) != 0)
1007
0
            SetPixelBlue(image,(Quantum) conform_fill.blue,q);
1008
0
          traits=GetPixelChannelTraits(image,BlackPixelChannel);
1009
0
          if ((traits & UpdatePixelTrait) != 0)
1010
0
            SetPixelBlack(image,(Quantum) conform_fill.black,q);
1011
0
          traits=GetPixelChannelTraits(image,AlphaPixelChannel);
1012
0
          if ((traits & UpdatePixelTrait) != 0)
1013
0
            SetPixelAlpha(image,(Quantum) conform_fill.alpha,q);
1014
0
        }
1015
0
      q+=(ptrdiff_t) GetPixelChannels(image);
1016
0
    }
1017
0
    if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
1018
0
      status=MagickFalse;
1019
0
    if (image->progress_monitor != (MagickProgressMonitor) NULL)
1020
0
      {
1021
0
        MagickBooleanType
1022
0
          proceed;
1023
1024
#if defined(MAGICKCORE_OPENMP_SUPPORT)
1025
        #pragma omp atomic
1026
#endif
1027
0
        progress++;
1028
0
        proceed=SetImageProgress(image,OpaquePaintImageTag,progress,
1029
0
          image->rows);
1030
0
        if (proceed == MagickFalse)
1031
0
          status=MagickFalse;
1032
0
      }
1033
0
  }
1034
0
  image_view=DestroyCacheView(image_view);
1035
0
  return(status);
1036
0
}
1037

1038
/*
1039
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1040
%                                                                             %
1041
%                                                                             %
1042
%                                                                             %
1043
%     T r a n s p a r e n t P a i n t I m a g e                               %
1044
%                                                                             %
1045
%                                                                             %
1046
%                                                                             %
1047
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1048
%
1049
%  TransparentPaintImage() changes the opacity value associated with any pixel
1050
%  that matches color to the value defined by opacity.
1051
%
1052
%  By default color must match a particular pixel color exactly.  However, in
1053
%  many cases two colors may differ by a small amount.  Fuzz defines how much
1054
%  tolerance is acceptable to consider two colors as the same.  For example,
1055
%  set fuzz to 10 and the color red at intensities of 100 and 102 respectively
1056
%  are now interpreted as the same color.
1057
%
1058
%  The format of the TransparentPaintImage method is:
1059
%
1060
%      MagickBooleanType TransparentPaintImage(Image *image,
1061
%        const PixelInfo *target,const Quantum opacity,
1062
%        const MagickBooleanType invert,ExceptionInfo *exception)
1063
%
1064
%  A description of each parameter follows:
1065
%
1066
%    o image: the image.
1067
%
1068
%    o target: the target color.
1069
%
1070
%    o opacity: the replacement opacity value.
1071
%
1072
%    o invert: paint any pixel that does not match the target color.
1073
%
1074
%    o exception: return any errors or warnings in this structure.
1075
%
1076
*/
1077
MagickExport MagickBooleanType TransparentPaintImage(Image *image,
1078
  const PixelInfo *target,const Quantum opacity,const MagickBooleanType invert,
1079
  ExceptionInfo *exception)
1080
1.50k
{
1081
1.50k
#define TransparentPaintImageTag  "Transparent/Image"
1082
1083
1.50k
  CacheView
1084
1.50k
    *image_view;
1085
1086
1.50k
  MagickBooleanType
1087
1.50k
    status;
1088
1089
1.50k
  MagickOffsetType
1090
1.50k
    progress;
1091
1092
1.50k
  PixelInfo
1093
1.50k
    zero;
1094
1095
1.50k
  ssize_t
1096
1.50k
    y;
1097
1098
1.50k
  assert(image != (Image *) NULL);
1099
1.50k
  assert(image->signature == MagickCoreSignature);
1100
1.50k
  assert(target != (PixelInfo *) NULL);
1101
1.50k
  if (IsEventLogging() != MagickFalse) 
1102
0
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1103
1.50k
  if (SetImageStorageClass(image,DirectClass,exception) == MagickFalse)
1104
0
    return(MagickFalse);
1105
1.50k
  if ((image->alpha_trait & BlendPixelTrait) == 0)
1106
1.50k
    (void) SetImageAlphaChannel(image,OpaqueAlphaChannel,exception);
1107
  /*
1108
    Make image color transparent.
1109
  */
1110
1.50k
  status=MagickTrue;
1111
1.50k
  progress=0;
1112
1.50k
  GetPixelInfo(image,&zero);
1113
1.50k
  image_view=AcquireAuthenticCacheView(image,exception);
1114
#if defined(MAGICKCORE_OPENMP_SUPPORT)
1115
  #pragma omp parallel for schedule(static) shared(progress,status) \
1116
    magick_number_threads(image,image,image->rows,1)
1117
#endif
1118
315k
  for (y=0; y < (ssize_t) image->rows; y++)
1119
314k
  {
1120
314k
    PixelInfo
1121
314k
      pixel;
1122
1123
314k
    ssize_t
1124
314k
      x;
1125
1126
314k
    Quantum
1127
314k
      *magick_restrict q;
1128
1129
314k
    if (status == MagickFalse)
1130
0
      continue;
1131
314k
    q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
1132
314k
    if (q == (Quantum *) NULL)
1133
0
      {
1134
0
        status=MagickFalse;
1135
0
        continue;
1136
0
      }
1137
314k
    pixel=zero;
1138
182M
    for (x=0; x < (ssize_t) image->columns; x++)
1139
182M
    {
1140
182M
      GetPixelInfoPixel(image,q,&pixel);
1141
182M
      if (IsFuzzyEquivalencePixelInfo(&pixel,target) != invert)
1142
77.2M
        SetPixelAlpha(image,opacity,q);
1143
182M
      q+=(ptrdiff_t) GetPixelChannels(image);
1144
182M
    }
1145
314k
    if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
1146
0
      status=MagickFalse;
1147
314k
    if (image->progress_monitor != (MagickProgressMonitor) NULL)
1148
0
      {
1149
0
        MagickBooleanType
1150
0
          proceed;
1151
1152
#if defined(MAGICKCORE_OPENMP_SUPPORT)
1153
        #pragma omp atomic
1154
#endif
1155
0
        progress++;
1156
0
        proceed=SetImageProgress(image,TransparentPaintImageTag,progress,
1157
0
          image->rows);
1158
0
        if (proceed == MagickFalse)
1159
0
          status=MagickFalse;
1160
0
      }
1161
314k
  }
1162
1.50k
  image_view=DestroyCacheView(image_view);
1163
1.50k
  return(status);
1164
1.50k
}
1165

1166
/*
1167
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1168
%                                                                             %
1169
%                                                                             %
1170
%                                                                             %
1171
%     T r a n s p a r e n t P a i n t I m a g e C h r o m a                   %
1172
%                                                                             %
1173
%                                                                             %
1174
%                                                                             %
1175
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1176
%
1177
%  TransparentPaintImageChroma() changes the opacity value associated with any
1178
%  pixel that matches color to the value defined by opacity.
1179
%
1180
%  As there is one fuzz value for the all the channels, TransparentPaintImage()
1181
%  is not suitable for the operations like chroma, where the tolerance for
1182
%  similarity of two color component (RGB) can be different. Thus we define
1183
%  this method to take two target pixels (one low and one high) and all the
1184
%  pixels of an image which are lying between these two pixels are made
1185
%  transparent.
1186
%
1187
%  The format of the TransparentPaintImageChroma method is:
1188
%
1189
%      MagickBooleanType TransparentPaintImageChroma(Image *image,
1190
%        const PixelInfo *low,const PixelInfo *high,const Quantum opacity,
1191
%        const MagickBooleanType invert,ExceptionInfo *exception)
1192
%
1193
%  A description of each parameter follows:
1194
%
1195
%    o image: the image.
1196
%
1197
%    o low: the low target color.
1198
%
1199
%    o high: the high target color.
1200
%
1201
%    o opacity: the replacement opacity value.
1202
%
1203
%    o invert: paint any pixel that does not match the target color.
1204
%
1205
%    o exception: return any errors or warnings in this structure.
1206
%
1207
*/
1208
MagickExport MagickBooleanType TransparentPaintImageChroma(Image *image,
1209
  const PixelInfo *low,const PixelInfo *high,const Quantum opacity,
1210
  const MagickBooleanType invert,ExceptionInfo *exception)
1211
0
{
1212
0
#define TransparentPaintImageTag  "Transparent/Image"
1213
1214
0
  CacheView
1215
0
    *image_view;
1216
1217
0
  MagickBooleanType
1218
0
    status;
1219
1220
0
  MagickOffsetType
1221
0
    progress;
1222
1223
0
  ssize_t
1224
0
    y;
1225
1226
0
  assert(image != (Image *) NULL);
1227
0
  assert(image->signature == MagickCoreSignature);
1228
0
  assert(high != (PixelInfo *) NULL);
1229
0
  assert(low != (PixelInfo *) NULL);
1230
0
  if (IsEventLogging() != MagickFalse) 
1231
0
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1232
0
  if (SetImageStorageClass(image,DirectClass,exception) == MagickFalse)
1233
0
    return(MagickFalse);
1234
0
  if ((image->alpha_trait & BlendPixelTrait) == 0)
1235
0
    (void) SetImageAlphaChannel(image,OpaqueAlphaChannel,exception);
1236
  /*
1237
    Make image color transparent.
1238
  */
1239
0
  status=MagickTrue;
1240
0
  progress=0;
1241
0
  image_view=AcquireAuthenticCacheView(image,exception);
1242
#if defined(MAGICKCORE_OPENMP_SUPPORT)
1243
  #pragma omp parallel for schedule(static) shared(progress,status) \
1244
    magick_number_threads(image,image,image->rows,1)
1245
#endif
1246
0
  for (y=0; y < (ssize_t) image->rows; y++)
1247
0
  {
1248
0
    MagickBooleanType
1249
0
      match;
1250
1251
0
    PixelInfo
1252
0
      pixel;
1253
1254
0
    Quantum
1255
0
      *magick_restrict q;
1256
1257
0
    ssize_t
1258
0
      x;
1259
1260
0
    if (status == MagickFalse)
1261
0
      continue;
1262
0
    q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
1263
0
    if (q == (Quantum *) NULL)
1264
0
      {
1265
0
        status=MagickFalse;
1266
0
        continue;
1267
0
      }
1268
0
    GetPixelInfo(image,&pixel);
1269
0
    for (x=0; x < (ssize_t) image->columns; x++)
1270
0
    {
1271
0
      GetPixelInfoPixel(image,q,&pixel);
1272
0
      match=((pixel.red >= low->red) && (pixel.red <= high->red) &&
1273
0
        (pixel.green >= low->green) && (pixel.green <= high->green) &&
1274
0
        (pixel.blue  >= low->blue) && (pixel.blue <= high->blue)) ? MagickTrue :
1275
0
        MagickFalse;
1276
0
      if (match != invert)
1277
0
        SetPixelAlpha(image,opacity,q);
1278
0
      q+=(ptrdiff_t) GetPixelChannels(image);
1279
0
    }
1280
0
    if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
1281
0
      status=MagickFalse;
1282
0
    if (image->progress_monitor != (MagickProgressMonitor) NULL)
1283
0
      {
1284
0
        MagickBooleanType
1285
0
          proceed;
1286
1287
#if defined(MAGICKCORE_OPENMP_SUPPORT)
1288
        #pragma omp atomic
1289
#endif
1290
0
        progress++;
1291
0
        proceed=SetImageProgress(image,TransparentPaintImageTag,progress,
1292
0
          image->rows);
1293
0
        if (proceed == MagickFalse)
1294
0
          status=MagickFalse;
1295
0
      }
1296
0
  }
1297
0
  image_view=DestroyCacheView(image_view);
1298
0
  return(status);
1299
0
}