/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 | } |