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