Coverage Report

Created: 2026-09-14 07:37

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/graphicsmagick/magick/gradient.c
Line
Count
Source
1
/*
2
% Copyright (C) 2003-2025 GraphicsMagick Group
3
% Copyright (C) 2003 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
% GraphicsMagick Gradient Image Methods.
11
%
12
*/
13

14
/*
15
  Include declarations.
16
*/
17
#include "magick/studio.h"
18
#include "magick/alpha_composite.h"
19
#include "magick/color.h"
20
#include "magick/colormap.h"
21
#include "magick/gradient.h"
22
#include "magick/log.h"
23
#include "magick/monitor.h"
24
#include "magick/pixel_cache.h"
25

26
/*
27
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
28
%                                                                             %
29
%                                                                             %
30
+     G r a d i e n t I m a g e                                               %
31
%                                                                             %
32
%                                                                             %
33
%                                                                             %
34
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
35
%
36
%  GradientImage() applies continuously smooth color transitions along a
37
%  distance vector from one color to another.
38
%
39
%  The default is to apply a gradient from the top of the image to the bottom.
40
%  Since GraphicsMagick 1.3.35, this function responds to the image gravity
41
%  attribute as follows:
42
%
43
%    SouthGravity - Top to Bottom (Default)
44
%    NorthGravity - Bottom to Top
45
%    WestGravity  - Right to Left
46
%    EastGravity  - Left to Right
47
%    NorthWestGravity - Bottom-Right to Top-Left
48
%    NorthEastGravity - Bottom-Left to Top-Right
49
%    SouthWestGravity - Top-Right Bottom-Left
50
%    SouthEastGravity - Top-Left to Bottom-Right
51
%
52
%  Also, since GraphicsMagick 1.3.35, an effort is made to produce a
53
%  PseudoClass image representation by default.  If the gradient distance
54
%  vector produces a number of points less than or equal to the maximum
55
%  colormap size (MaxColormapSize), then a colormap is produced according
56
%  to the order indicated by the start and stop colors.  Otherwise a
57
%  DirectClass image is created (as it always was prior to 1.3.35).  The
58
%  PseudoClass representation is suitably initialized so that changing
59
%  the image storage class will lead to an immediately usable DirectClass
60
%  image.
61
%
62
%  Note, the interface of this method will change in the future to support
63
%  more than one transition.
64
%
65
%  The format of the GradientImage method is:
66
%
67
%      MagickPassFail GradientImage(Image *image,
68
%        const PixelPacket *start_color,
69
%        const PixelPacket *stop_color)
70
%
71
%  A description of each parameter follows:
72
%
73
%    o image: The image.
74
%
75
%    o start_color: The start color.
76
%
77
%    o stop_color: The stop color.
78
%
79
%
80
*/
81
82
0
#define GradientImageText "[%s] Gradient..."
83
MagickExport MagickPassFail GradientImage(Image *restrict image,
84
                                          const PixelPacket *start_color,
85
                                          const PixelPacket *stop_color)
86
56.5k
{
87
56.5k
  PixelPacket
88
56.5k
    *pixel_packets;
89
90
56.5k
  double
91
56.5k
    alpha_scale,
92
56.5k
    x_origin = 0.0,
93
56.5k
    y_origin = 0.0;
94
95
56.5k
  size_t
96
56.5k
    span;
97
98
56.5k
  unsigned long
99
56.5k
    i;
100
101
56.5k
  long
102
56.5k
    y;
103
104
56.5k
  unsigned long
105
56.5k
    row_count=0;
106
107
#if defined(HAVE_OPENMP)
108
  int num_threads = omp_get_max_threads();
109
#endif
110
111
56.5k
  MagickBool
112
56.5k
    monitor_active;
113
114
56.5k
  MagickPassFail
115
56.5k
    status=MagickPass;
116
117
56.5k
  assert(image != (const Image *) NULL);
118
56.5k
  assert(image->signature == MagickSignature);
119
56.5k
  assert(start_color != (const PixelPacket *) NULL);
120
56.5k
  assert(stop_color != (const PixelPacket *) NULL);
121
122
56.5k
  monitor_active=MagickMonitorActive();
123
124
  /*
125
    -define gradient:direction={NorthWest, North, Northeast, West, East, SouthWest, South, SouthEast}
126
127
    South is the default
128
129
    image->gravity
130
  */
131
132
  /*
133
    Computed required gradient span
134
  */
135
56.5k
  switch(image->gravity)
136
56.5k
    {
137
0
    case SouthGravity:
138
0
    case NorthGravity:
139
56.5k
    default:
140
56.5k
      span = image->rows;
141
56.5k
      break;
142
0
    case WestGravity:
143
0
    case EastGravity:
144
0
      span = image->columns;
145
0
      break;
146
0
    case NorthWestGravity:
147
0
    case NorthEastGravity:
148
0
    case SouthWestGravity:
149
0
    case SouthEastGravity:
150
0
      span = (size_t) (sqrt(((double)image->columns-1)*((double)image->columns-1)+
151
0
                            ((double)image->rows-1)*((double)image->rows-1))+0.5)+1;
152
0
      break;
153
56.5k
    }
154
155
56.5k
  (void) LogMagickEvent(CoderEvent,GetMagickModule(),
156
56.5k
                        "Gradient span %" MAGICK_SIZE_T_F "u", (MAGICK_SIZE_T) span);
157
158
  /*
159
    Determine origin pixel for diagonal gradients
160
  */
161
56.5k
  switch(image->gravity)
162
56.5k
    {
163
56.5k
    default:
164
56.5k
      break;
165
56.5k
    case NorthWestGravity:
166
      /* Origin bottom-right */
167
0
      x_origin = (double)image->columns-1;
168
0
      y_origin = (double)image->rows-1;
169
0
      break;
170
0
    case NorthEastGravity:
171
      /* Origin bottom-left */
172
0
      x_origin = 0.0;
173
0
      y_origin = (double)image->rows-1;
174
0
      break;
175
0
    case SouthWestGravity:
176
      /* Origin top-right */
177
0
      x_origin = (double)image->columns-1;
178
0
      y_origin = 0.0;
179
0
      break;
180
0
    case SouthEastGravity:
181
      /* Origin top-left */
182
0
      x_origin = 0.0;
183
0
      y_origin = 0.0;
184
0
      break;
185
56.5k
    }
186
187
56.5k
  pixel_packets=MagickAllocateArray(PixelPacket *,span,sizeof(PixelPacket));
188
56.5k
  if (pixel_packets == (PixelPacket *) NULL)
189
0
    ThrowBinaryException(ResourceLimitError,MemoryAllocationFailed,
190
56.5k
                         image->filename);
191
56.5k
  if (span <= MaxColormapSize)
192
56.5k
    if (!AllocateImageColormap(image,(unsigned long) span))
193
0
      {
194
0
        MagickFreeMemory(pixel_packets);
195
0
        ThrowException3(&image->exception, ResourceLimitError,MemoryAllocationFailed,
196
0
                        UnableToConstituteImage);
197
0
        return MagickFail;
198
0
      }
199
200
  /*
201
    Generate gradient pixels using alpha blending
202
    OpenMP is not demonstrated to help here.
203
  */
204
56.5k
  alpha_scale = span > 1 ? ((MaxRGBDouble)/(span-1)) : MaxRGBDouble/2.0;
205
206
7.02M
  for (i=0; i < span; i++)
207
6.96M
    {
208
6.96M
      double alpha = (double)i*alpha_scale;
209
6.96M
      BlendCompositePixel(&pixel_packets[i],start_color,stop_color,alpha);
210
#if 0
211
      fprintf(stdout, "%lu: %g (r=%u, g=%u, b=%u)\n", i, alpha,
212
              (unsigned) pixel_packets[i].red,
213
              (unsigned) pixel_packets[i].green,
214
              (unsigned) pixel_packets[i].blue);
215
#endif
216
6.96M
    }
217
218
56.5k
  if (image->storage_class == PseudoClass)
219
56.5k
    (void) memcpy(image->colormap,pixel_packets,span*sizeof(PixelPacket));
220
221
  /*
222
    Copy gradient pixels to image rows
223
  */
224
#if defined(HAVE_OPENMP)
225
  if (num_threads > 3)
226
    num_threads = 3;
227
#  if defined(TUNE_OPENMP)
228
#    pragma omp parallel for if(num_threads > 1) num_threads(num_threads) schedule(runtime) shared(row_count, status)
229
#  else
230
#    pragma omp parallel for if(num_threads > 1) num_threads(num_threads) schedule(guided) shared(row_count, status)
231
#  endif
232
#endif
233
7.02M
  for (y=0; y < (long) image->rows; y++)
234
6.96M
    {
235
6.96M
      MagickPassFail
236
6.96M
        thread_status;
237
238
6.96M
      register unsigned long
239
6.96M
        x;
240
241
6.96M
      register PixelPacket
242
6.96M
        *q;
243
244
6.96M
      register IndexPacket
245
6.96M
        *indexes = (IndexPacket *) NULL;
246
247
6.96M
      thread_status=status;
248
6.96M
      if (thread_status == MagickFail)
249
0
        continue;
250
251
6.96M
      do
252
6.96M
        {
253
6.96M
          q=SetImagePixelsEx(image,0,y,image->columns,1,&image->exception);
254
6.96M
          if (q == (PixelPacket *) NULL)
255
0
            {
256
0
              thread_status=MagickFail;
257
0
              break;
258
0
            }
259
6.96M
          if (image->storage_class == PseudoClass)
260
6.96M
            {
261
6.96M
              indexes=AccessMutableIndexes(image);
262
6.96M
              if (indexes == (IndexPacket *) NULL)
263
0
                {
264
0
                  thread_status=MagickFail;
265
0
                  break;
266
0
                }
267
6.96M
            }
268
269
6.96M
          switch(image->gravity)
270
6.96M
            {
271
0
            case SouthGravity:
272
6.96M
            default:
273
6.96M
              {
274
710M
                for (x=0; x < image->columns; x++)
275
703M
                  q[x] = pixel_packets[y];
276
6.96M
                if (indexes)
277
710M
                  for (x=0; x < image->columns; x++)
278
703M
                    indexes[x]=(IndexPacket) y;
279
6.96M
                break;
280
0
              }
281
0
            case NorthGravity:
282
0
              {
283
0
                for (x=0; x < image->columns; x++)
284
0
                  q[x] = pixel_packets[image->rows-1-y];
285
0
                if (indexes)
286
0
                  for (x=0; x < image->columns; x++)
287
0
                    indexes[x]=(IndexPacket) image->rows-1-y;
288
0
                break;
289
0
              }
290
0
            case WestGravity:
291
0
              {
292
0
                for (x=0; x < image->columns; x++)
293
0
                  q[x] = pixel_packets[image->columns-x];
294
0
                if (indexes)
295
0
                  for (x=0; x < image->columns; x++)
296
0
                    indexes[x]=(IndexPacket) image->columns-x;
297
0
                break;
298
0
              }
299
0
            case EastGravity:
300
0
              {
301
0
                for (x=0; x < image->columns; x++)
302
0
                  q[x] = pixel_packets[x];
303
0
                if (indexes)
304
0
                  for (x=0; x < image->columns; x++)
305
0
                    indexes[x]=(IndexPacket) x;
306
0
                break;
307
0
              }
308
0
            case NorthWestGravity:
309
0
            case NorthEastGravity:
310
0
            case SouthWestGravity:
311
0
            case SouthEastGravity:
312
0
              {
313
                /*
314
                  FIXME: Diagonal gradient should be based on distance
315
                  from perpendicular line!
316
                */
317
0
                double ydf = (y_origin-(double)y)*(y_origin-(double)y);
318
0
                for (x=0; x < image->columns; x++)
319
0
                  {
320
0
                    i = (unsigned long) (sqrt((x_origin-x)*(x_origin-x)+ydf)+0.5);
321
                    /* fprintf(stderr,"NW %lux%ld: %lu\n", x, y, (unsigned long) i); */
322
0
                    q[x] = pixel_packets[i];
323
0
                    if (indexes)
324
0
                      indexes[x]=(IndexPacket) i;
325
0
                  }
326
327
0
                break;
328
0
              }
329
6.96M
            }
330
331
6.96M
          if (!SyncImagePixelsEx(image,&image->exception))
332
0
            {
333
0
              thread_status=MagickFail;
334
0
              break;
335
0
            }
336
337
6.96M
          if (monitor_active)
338
0
            {
339
0
              unsigned long
340
0
                thread_row_count;
341
342
#if defined(HAVE_OPENMP)
343
#  pragma omp atomic
344
#endif
345
0
              row_count++;
346
#if defined(HAVE_OPENMP)
347
#  pragma omp flush (row_count)
348
#endif
349
0
              thread_row_count=row_count;
350
0
              if (QuantumTick(thread_row_count,image->rows))
351
0
                if (!MagickMonitorFormatted(thread_row_count,image->rows,&image->exception,
352
0
                                            GradientImageText,image->filename))
353
0
                  {
354
0
                    thread_status=MagickFail;
355
0
                    break;
356
0
                  }
357
0
            }
358
6.96M
        } while(0);
359
360
6.96M
      if (thread_status == MagickFail)
361
0
        {
362
0
          status=MagickFail;
363
#if defined(HAVE_OPENMP)
364
#  pragma omp flush (status)
365
#endif
366
0
        }
367
6.96M
    }
368
56.5k
  if (IsGray(*start_color) && IsGray(*stop_color))
369
7.11k
    image->is_grayscale=MagickTrue;
370
56.5k
  if (IsMonochrome(*start_color) && ColorMatch(start_color,stop_color))
371
5.84k
    image->is_monochrome=MagickTrue;
372
56.5k
  MagickFreeMemory(pixel_packets);
373
56.5k
  return(status);
374
56.5k
}