1 /*
2 % Copyright (C) 2003 - 2020 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 transistion.
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 #define GradientImageText "[%s] Gradient..."
GradientImage(Image * restrict image,const PixelPacket * start_color,const PixelPacket * stop_color)83 MagickExport MagickPassFail GradientImage(Image *restrict image,
84                                           const PixelPacket *start_color,
85                                           const PixelPacket *stop_color)
86 {
87   PixelPacket
88     *pixel_packets;
89 
90   double
91     alpha_scale,
92     x_origin = 0.0,
93     y_origin = 0.0;
94 
95   size_t
96     span;
97 
98   unsigned long
99     i;
100 
101   long
102     y;
103 
104   unsigned long
105     row_count=0;
106 
107 #if defined(HAVE_OPENMP)
108   int num_threads = omp_get_max_threads();
109 #endif
110 
111   MagickBool
112     monitor_active;
113 
114   MagickPassFail
115     status=MagickPass;
116 
117   assert(image != (const Image *) NULL);
118   assert(image->signature == MagickSignature);
119   assert(start_color != (const PixelPacket *) NULL);
120   assert(stop_color != (const PixelPacket *) NULL);
121 
122   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   switch(image->gravity)
136     {
137     case SouthGravity:
138     case NorthGravity:
139     default:
140       span = image->rows;
141       break;
142     case WestGravity:
143     case EastGravity:
144       span = image->columns;
145       break;
146     case NorthWestGravity:
147     case NorthEastGravity:
148     case SouthWestGravity:
149     case SouthEastGravity:
150       span = (size_t) (sqrt(((double)image->columns-1)*((double)image->columns-1)+
151                             ((double)image->rows-1)*((double)image->rows-1))+0.5)+1;
152       break;
153     }
154 
155   (void) LogMagickEvent(CoderEvent,GetMagickModule(),
156                         "Gradient span %"MAGICK_SIZE_T_F"u", (MAGICK_SIZE_T) span);
157 
158   /*
159     Determine origin pixel for diagonal gradients
160   */
161   switch(image->gravity)
162     {
163     default:
164       break;
165     case NorthWestGravity:
166       /* Origin bottom-right */
167       x_origin = (double)image->columns-1;
168       y_origin = (double)image->rows-1;
169       break;
170     case NorthEastGravity:
171       /* Origin bottom-left */
172       x_origin = 0.0;
173       y_origin = (double)image->rows-1;
174       break;
175     case SouthWestGravity:
176       /* Origin top-right */
177       x_origin = (double)image->columns-1;
178       y_origin = 0.0;
179       break;
180     case SouthEastGravity:
181       /* Origin top-left */
182       x_origin = 0.0;
183       y_origin = 0.0;
184       break;
185     }
186 
187   pixel_packets=MagickAllocateArray(PixelPacket *,span,sizeof(PixelPacket));
188   if (pixel_packets == (PixelPacket *) NULL)
189     ThrowBinaryException(ResourceLimitError,MemoryAllocationFailed,
190                          image->filename);
191   if (span <= MaxColormapSize)
192     AllocateImageColormap(image,(unsigned long) span);
193 
194   /*
195     Generate gradient pixels using alpha blending
196     OpenMP is not demonstrated to help here.
197   */
198   alpha_scale = span > 1 ? ((MaxRGBDouble)/(span-1)) : MaxRGBDouble/2.0;
199 
200   for (i=0; i < span; i++)
201     {
202       double alpha = (double)i*alpha_scale;
203       BlendCompositePixel(&pixel_packets[i],start_color,stop_color,alpha);
204 #if 0
205       fprintf(stdout, "%lu: %g (r=%u, g=%u, b=%u)\n", i, alpha,
206               (unsigned) pixel_packets[i].red,
207               (unsigned) pixel_packets[i].green,
208               (unsigned) pixel_packets[i].blue);
209 #endif
210     }
211 
212   if (image->storage_class == PseudoClass)
213     (void) memcpy(image->colormap,pixel_packets,span*sizeof(PixelPacket));
214 
215   /*
216     Copy gradient pixels to image rows
217   */
218 #if defined(HAVE_OPENMP)
219   if (num_threads > 3)
220     num_threads = 3;
221 #  if defined(TUNE_OPENMP)
222 #    pragma omp parallel for if(num_threads > 1) num_threads(num_threads) schedule(runtime) shared(row_count, status)
223 #  else
224 #    pragma omp parallel for if(num_threads > 1) num_threads(num_threads) schedule(guided) shared(row_count, status)
225 #  endif
226 #endif
227   for (y=0; y < (long) image->rows; y++)
228     {
229       MagickPassFail
230         thread_status;
231 
232       register unsigned long
233         x;
234 
235       register PixelPacket
236         *q;
237 
238       register IndexPacket
239         *indexes = (IndexPacket *) NULL;
240 
241       thread_status=status;
242       if (thread_status == MagickFail)
243         continue;
244 
245       do
246         {
247           q=SetImagePixelsEx(image,0,y,image->columns,1,&image->exception);
248           if (q == (PixelPacket *) NULL)
249             {
250               thread_status=MagickFail;
251               break;
252             }
253           if (image->storage_class == PseudoClass)
254             {
255               indexes=AccessMutableIndexes(image);
256               if (indexes == (IndexPacket *) NULL)
257                 {
258                   thread_status=MagickFail;
259                   break;
260                 }
261             }
262 
263           switch(image->gravity)
264             {
265             case SouthGravity:
266             default:
267               {
268                 for (x=0; x < image->columns; x++)
269                   q[x] = pixel_packets[y];
270                 if (indexes)
271                   for (x=0; x < image->columns; x++)
272                     indexes[x]=(IndexPacket) y;
273                 break;
274               }
275             case NorthGravity:
276               {
277                 for (x=0; x < image->columns; x++)
278                   q[x] = pixel_packets[image->columns-y];
279                 if (indexes)
280                   for (x=0; x < image->columns; x++)
281                     indexes[x]=(IndexPacket) image->columns-y;
282                 break;
283               }
284             case WestGravity:
285               {
286                 for (x=0; x < image->columns; x++)
287                   q[x] = pixel_packets[image->columns-x];
288                 if (indexes)
289                   for (x=0; x < image->columns; x++)
290                     indexes[x]=(IndexPacket) image->columns-x;
291                 break;
292               }
293             case EastGravity:
294               {
295                 for (x=0; x < image->columns; x++)
296                   q[x] = pixel_packets[x];
297                 if (indexes)
298                   for (x=0; x < image->columns; x++)
299                     indexes[x]=(IndexPacket) x;
300                 break;
301               }
302             case NorthWestGravity:
303             case NorthEastGravity:
304             case SouthWestGravity:
305             case SouthEastGravity:
306               {
307                 /*
308                   FIXME: Diagonal gradient should be based on distance
309                   from perpendicular line!
310                 */
311                 double ydf = (y_origin-(double)y)*(y_origin-(double)y);
312                 for (x=0; x < image->columns; x++)
313                   {
314                     i = (unsigned long) (sqrt((x_origin-x)*(x_origin-x)+ydf)+0.5);
315                     /* fprintf(stderr,"NW %lux%ld: %lu\n", x, y, (unsigned long) i); */
316                     q[x] = pixel_packets[i];
317                     if (indexes)
318                       indexes[x]=(IndexPacket) i;
319                   }
320 
321                 break;
322               }
323             }
324 
325           if (!SyncImagePixelsEx(image,&image->exception))
326             {
327               thread_status=MagickFail;
328               break;
329             }
330 
331           if (monitor_active)
332             {
333               unsigned long
334                 thread_row_count;
335 
336 #if defined(HAVE_OPENMP)
337 #  pragma omp atomic
338 #endif
339               row_count++;
340 #if defined(HAVE_OPENMP)
341 #  pragma omp flush (row_count)
342 #endif
343               thread_row_count=row_count;
344               if (QuantumTick(thread_row_count,image->rows))
345                 if (!MagickMonitorFormatted(thread_row_count,image->rows,&image->exception,
346                                             GradientImageText,image->filename))
347                   {
348                     thread_status=MagickFail;
349                     break;
350                   }
351             }
352         } while(0);
353 
354       if (thread_status == MagickFail)
355         {
356           status=MagickFail;
357 #if defined(HAVE_OPENMP)
358 #  pragma omp flush (status)
359 #endif
360         }
361     }
362   if (IsGray(*start_color) && IsGray(*stop_color))
363     image->is_grayscale=MagickTrue;
364   if (IsMonochrome(*start_color) && ColorMatch(start_color,stop_color))
365     image->is_monochrome=MagickTrue;
366   MagickFreeMemory(pixel_packets);
367   return(status);
368 }
369