/src/imagemagick/MagickCore/effect.c
Line | Count | Source |
1 | | /* |
2 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
3 | | % % |
4 | | % % |
5 | | % % |
6 | | % EEEEE FFFFF FFFFF EEEEE CCCC TTTTT % |
7 | | % E F F E C T % |
8 | | % EEE FFF FFF EEE C T % |
9 | | % E F F E C T % |
10 | | % EEEEE F F EEEEE CCCC T % |
11 | | % % |
12 | | % % |
13 | | % MagickCore Image Effects Methods % |
14 | | % % |
15 | | % Software Design % |
16 | | % Cristy % |
17 | | % October 1996 % |
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 | | /* |
41 | | Include declarations. |
42 | | */ |
43 | | #include "MagickCore/studio.h" |
44 | | #include "MagickCore/accelerate-private.h" |
45 | | #include "MagickCore/blob.h" |
46 | | #include "MagickCore/cache-view.h" |
47 | | #include "MagickCore/color.h" |
48 | | #include "MagickCore/color-private.h" |
49 | | #include "MagickCore/colorspace.h" |
50 | | #include "MagickCore/constitute.h" |
51 | | #include "MagickCore/decorate.h" |
52 | | #include "MagickCore/distort.h" |
53 | | #include "MagickCore/draw.h" |
54 | | #include "MagickCore/enhance.h" |
55 | | #include "MagickCore/exception.h" |
56 | | #include "MagickCore/exception-private.h" |
57 | | #include "MagickCore/effect.h" |
58 | | #include "MagickCore/fx.h" |
59 | | #include "MagickCore/gem.h" |
60 | | #include "MagickCore/gem-private.h" |
61 | | #include "MagickCore/geometry.h" |
62 | | #include "MagickCore/image-private.h" |
63 | | #include "MagickCore/list.h" |
64 | | #include "MagickCore/log.h" |
65 | | #include "MagickCore/matrix.h" |
66 | | #include "MagickCore/memory_.h" |
67 | | #include "MagickCore/memory-private.h" |
68 | | #include "MagickCore/monitor.h" |
69 | | #include "MagickCore/monitor-private.h" |
70 | | #include "MagickCore/montage.h" |
71 | | #include "MagickCore/morphology.h" |
72 | | #include "MagickCore/morphology-private.h" |
73 | | #include "MagickCore/paint.h" |
74 | | #include "MagickCore/pixel-accessor.h" |
75 | | #include "MagickCore/property.h" |
76 | | #include "MagickCore/quantize.h" |
77 | | #include "MagickCore/quantum.h" |
78 | | #include "MagickCore/quantum-private.h" |
79 | | #include "MagickCore/random_.h" |
80 | | #include "MagickCore/random-private.h" |
81 | | #include "MagickCore/resample.h" |
82 | | #include "MagickCore/resample-private.h" |
83 | | #include "MagickCore/resize.h" |
84 | | #include "MagickCore/resource_.h" |
85 | | #include "MagickCore/segment.h" |
86 | | #include "MagickCore/shear.h" |
87 | | #include "MagickCore/signature-private.h" |
88 | | #include "MagickCore/statistic.h" |
89 | | #include "MagickCore/string_.h" |
90 | | #include "MagickCore/thread-private.h" |
91 | | #include "MagickCore/transform.h" |
92 | | #include "MagickCore/threshold.h" |
93 | | #include "MagickCore/utility-private.h" |
94 | | |
95 | | /* |
96 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
97 | | % % |
98 | | % % |
99 | | % % |
100 | | % A d a p t i v e B l u r I m a g e % |
101 | | % % |
102 | | % % |
103 | | % % |
104 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
105 | | % |
106 | | % AdaptiveBlurImage() adaptively blurs the image by blurring less |
107 | | % intensely near image edges and more intensely far from edges. We blur the |
108 | | % image with a Gaussian operator of the given radius and standard deviation |
109 | | % (sigma). For reasonable results, radius should be larger than sigma. Use a |
110 | | % radius of 0 and AdaptiveBlurImage() selects a suitable radius for you. |
111 | | % |
112 | | % The format of the AdaptiveBlurImage method is: |
113 | | % |
114 | | % Image *AdaptiveBlurImage(const Image *image,const double radius, |
115 | | % const double sigma,ExceptionInfo *exception) |
116 | | % |
117 | | % A description of each parameter follows: |
118 | | % |
119 | | % o image: the image. |
120 | | % |
121 | | % o radius: the radius of the Gaussian, in pixels, not counting the center |
122 | | % pixel. |
123 | | % |
124 | | % o sigma: the standard deviation of the Laplacian, in pixels. |
125 | | % |
126 | | % o exception: return any errors or warnings in this structure. |
127 | | % |
128 | | */ |
129 | | MagickExport Image *AdaptiveBlurImage(const Image *image,const double radius, |
130 | | const double sigma,ExceptionInfo *exception) |
131 | 0 | { |
132 | 0 | #define AdaptiveBlurImageTag "Convolve/Image" |
133 | 0 | #define MagickSigma (fabs(sigma) < MagickEpsilon ? MagickEpsilon : sigma) |
134 | |
|
135 | 0 | CacheView |
136 | 0 | *blur_view, |
137 | 0 | *edge_view, |
138 | 0 | *image_view; |
139 | |
|
140 | 0 | double |
141 | 0 | normalize, |
142 | 0 | **kernel; |
143 | |
|
144 | 0 | Image |
145 | 0 | *blur_image, |
146 | 0 | *edge_image, |
147 | 0 | *gaussian_image; |
148 | |
|
149 | 0 | MagickBooleanType |
150 | 0 | status; |
151 | |
|
152 | 0 | MagickOffsetType |
153 | 0 | progress; |
154 | |
|
155 | 0 | size_t |
156 | 0 | width; |
157 | |
|
158 | 0 | ssize_t |
159 | 0 | w, |
160 | 0 | y; |
161 | |
|
162 | 0 | assert(image != (const Image *) NULL); |
163 | 0 | assert(image->signature == MagickCoreSignature); |
164 | 0 | assert(exception != (ExceptionInfo *) NULL); |
165 | 0 | assert(exception->signature == MagickCoreSignature); |
166 | 0 | if (IsEventLogging() != MagickFalse) |
167 | 0 | (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename); |
168 | 0 | blur_image=CloneImage(image,0,0,MagickTrue,exception); |
169 | 0 | if (blur_image == (Image *) NULL) |
170 | 0 | return((Image *) NULL); |
171 | 0 | if (fabs(sigma) < MagickEpsilon) |
172 | 0 | return(blur_image); |
173 | 0 | if (SetImageStorageClass(blur_image,DirectClass,exception) == MagickFalse) |
174 | 0 | { |
175 | 0 | blur_image=DestroyImage(blur_image); |
176 | 0 | return((Image *) NULL); |
177 | 0 | } |
178 | | /* |
179 | | Edge detect the image brightness channel, level, blur, and level again. |
180 | | */ |
181 | 0 | edge_image=EdgeImage(image,radius,exception); |
182 | 0 | if (edge_image == (Image *) NULL) |
183 | 0 | { |
184 | 0 | blur_image=DestroyImage(blur_image); |
185 | 0 | return((Image *) NULL); |
186 | 0 | } |
187 | 0 | (void) AutoLevelImage(edge_image,exception); |
188 | 0 | gaussian_image=BlurImage(edge_image,radius,sigma,exception); |
189 | 0 | if (gaussian_image != (Image *) NULL) |
190 | 0 | { |
191 | 0 | edge_image=DestroyImage(edge_image); |
192 | 0 | edge_image=gaussian_image; |
193 | 0 | } |
194 | 0 | (void) AutoLevelImage(edge_image,exception); |
195 | | /* |
196 | | Create a set of kernels from maximum (radius,sigma) to minimum. |
197 | | */ |
198 | 0 | width=GetOptimalKernelWidth2D(radius,sigma); |
199 | 0 | kernel=(double **) MagickAssumeAligned(AcquireAlignedMemory((size_t) width, |
200 | 0 | sizeof(*kernel))); |
201 | 0 | if (kernel == (double **) NULL) |
202 | 0 | { |
203 | 0 | edge_image=DestroyImage(edge_image); |
204 | 0 | blur_image=DestroyImage(blur_image); |
205 | 0 | ThrowImageException(ResourceLimitError,"MemoryAllocationFailed"); |
206 | 0 | } |
207 | 0 | (void) memset(kernel,0,(size_t) width*sizeof(*kernel)); |
208 | 0 | for (w=0; w < (ssize_t) width; w+=2) |
209 | 0 | { |
210 | 0 | ssize_t |
211 | 0 | j, |
212 | 0 | k, |
213 | 0 | u, |
214 | 0 | v; |
215 | |
|
216 | 0 | kernel[w]=(double *) MagickAssumeAligned(AcquireAlignedMemory( |
217 | 0 | (width-(size_t) w),(width-(size_t) w)*sizeof(**kernel))); |
218 | 0 | if (kernel[w] == (double *) NULL) |
219 | 0 | break; |
220 | 0 | normalize=0.0; |
221 | 0 | j=((ssize_t) width-w-1)/2; |
222 | 0 | k=0; |
223 | 0 | for (v=(-j); v <= j; v++) |
224 | 0 | { |
225 | 0 | for (u=(-j); u <= j; u++) |
226 | 0 | { |
227 | 0 | kernel[w][k]=(double) (exp(-((double) u*u+v*v)/(2.0*MagickSigma* |
228 | 0 | MagickSigma))/(2.0*MagickPI*MagickSigma*MagickSigma)); |
229 | 0 | normalize+=kernel[w][k]; |
230 | 0 | k++; |
231 | 0 | } |
232 | 0 | } |
233 | 0 | kernel[w][(k-1)/2]+=(double) (1.0-normalize); |
234 | 0 | if (sigma < MagickEpsilon) |
235 | 0 | kernel[w][(k-1)/2]=1.0; |
236 | 0 | } |
237 | 0 | if (w < (ssize_t) width) |
238 | 0 | { |
239 | 0 | for (w-=2; w >= 0; w-=2) |
240 | 0 | kernel[w]=(double *) RelinquishAlignedMemory(kernel[w]); |
241 | 0 | kernel=(double **) RelinquishAlignedMemory(kernel); |
242 | 0 | edge_image=DestroyImage(edge_image); |
243 | 0 | blur_image=DestroyImage(blur_image); |
244 | 0 | ThrowImageException(ResourceLimitError,"MemoryAllocationFailed"); |
245 | 0 | } |
246 | | /* |
247 | | Adaptively blur image. |
248 | | */ |
249 | 0 | status=MagickTrue; |
250 | 0 | progress=0; |
251 | 0 | image_view=AcquireVirtualCacheView(image,exception); |
252 | 0 | edge_view=AcquireVirtualCacheView(edge_image,exception); |
253 | 0 | blur_view=AcquireAuthenticCacheView(blur_image,exception); |
254 | | #if defined(MAGICKCORE_OPENMP_SUPPORT) |
255 | | #pragma omp parallel for schedule(static) shared(progress,status) \ |
256 | | magick_number_threads(image,blur_image,blur_image->rows,1) |
257 | | #endif |
258 | 0 | for (y=0; y < (ssize_t) blur_image->rows; y++) |
259 | 0 | { |
260 | 0 | const Quantum |
261 | 0 | *magick_restrict r; |
262 | |
|
263 | 0 | Quantum |
264 | 0 | *magick_restrict q; |
265 | |
|
266 | 0 | ssize_t |
267 | 0 | x; |
268 | |
|
269 | 0 | if (status == MagickFalse) |
270 | 0 | continue; |
271 | 0 | r=GetCacheViewVirtualPixels(edge_view,0,y,edge_image->columns,1,exception); |
272 | 0 | q=QueueCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1, |
273 | 0 | exception); |
274 | 0 | if ((r == (const Quantum *) NULL) || (q == (Quantum *) NULL)) |
275 | 0 | { |
276 | 0 | status=MagickFalse; |
277 | 0 | continue; |
278 | 0 | } |
279 | 0 | for (x=0; x < (ssize_t) blur_image->columns; x++) |
280 | 0 | { |
281 | 0 | const Quantum |
282 | 0 | *magick_restrict p; |
283 | |
|
284 | 0 | ssize_t |
285 | 0 | i; |
286 | |
|
287 | 0 | ssize_t |
288 | 0 | center, |
289 | 0 | j; |
290 | |
|
291 | 0 | j=CastDoubleToSsizeT(ceil((double) width*(1.0-QuantumScale* |
292 | 0 | GetPixelIntensity(edge_image,r))-0.5)); |
293 | 0 | if (j < 0) |
294 | 0 | j=0; |
295 | 0 | else |
296 | 0 | if (j > (ssize_t) width) |
297 | 0 | j=(ssize_t) width; |
298 | 0 | if ((j & 0x01) != 0) |
299 | 0 | j--; |
300 | 0 | p=GetCacheViewVirtualPixels(image_view,x-((ssize_t) width-j)/2L,y- |
301 | 0 | ((ssize_t) width-j)/2L,width-(size_t) j,width-(size_t) j,exception); |
302 | 0 | if (p == (const Quantum *) NULL) |
303 | 0 | break; |
304 | 0 | center=(ssize_t) (GetPixelChannels(image)*(width-(size_t) j)* |
305 | 0 | ((width-(size_t) j)/2L)+GetPixelChannels(image)*((width-(size_t) j)/2)); |
306 | 0 | for (i=0; i < (ssize_t) GetPixelChannels(blur_image); i++) |
307 | 0 | { |
308 | 0 | const double |
309 | 0 | *magick_restrict k; |
310 | |
|
311 | 0 | const Quantum |
312 | 0 | *magick_restrict pixels; |
313 | |
|
314 | 0 | double |
315 | 0 | alpha, |
316 | 0 | gamma, |
317 | 0 | pixel; |
318 | |
|
319 | 0 | PixelChannel |
320 | 0 | channel; |
321 | |
|
322 | 0 | PixelTrait |
323 | 0 | blur_traits, |
324 | 0 | traits; |
325 | |
|
326 | 0 | ssize_t |
327 | 0 | u, |
328 | 0 | v; |
329 | |
|
330 | 0 | channel=GetPixelChannelChannel(image,i); |
331 | 0 | traits=GetPixelChannelTraits(image,channel); |
332 | 0 | blur_traits=GetPixelChannelTraits(blur_image,channel); |
333 | 0 | if ((traits == UndefinedPixelTrait) || |
334 | 0 | (blur_traits == UndefinedPixelTrait)) |
335 | 0 | continue; |
336 | 0 | if ((blur_traits & CopyPixelTrait) != 0) |
337 | 0 | { |
338 | 0 | SetPixelChannel(blur_image,channel,p[center+i],q); |
339 | 0 | continue; |
340 | 0 | } |
341 | 0 | k=kernel[j]; |
342 | 0 | pixels=p; |
343 | 0 | pixel=0.0; |
344 | 0 | gamma=0.0; |
345 | 0 | if ((blur_traits & BlendPixelTrait) == 0) |
346 | 0 | { |
347 | | /* |
348 | | No alpha blending. |
349 | | */ |
350 | 0 | for (v=0; v < ((ssize_t) width-j); v++) |
351 | 0 | { |
352 | 0 | for (u=0; u < ((ssize_t) width-j); u++) |
353 | 0 | { |
354 | 0 | pixel+=(*k)*(double) pixels[i]; |
355 | 0 | gamma+=(*k); |
356 | 0 | k++; |
357 | 0 | pixels+=(ptrdiff_t) GetPixelChannels(image); |
358 | 0 | } |
359 | 0 | } |
360 | 0 | gamma=MagickSafeReciprocal(gamma); |
361 | 0 | SetPixelChannel(blur_image,channel,ClampToQuantum(gamma*pixel),q); |
362 | 0 | continue; |
363 | 0 | } |
364 | | /* |
365 | | Alpha blending. |
366 | | */ |
367 | 0 | for (v=0; v < ((ssize_t) width-j); v++) |
368 | 0 | { |
369 | 0 | for (u=0; u < ((ssize_t) width-j); u++) |
370 | 0 | { |
371 | 0 | alpha=(double) (QuantumScale*(double) GetPixelAlpha(image,pixels)); |
372 | 0 | pixel+=(*k)*alpha*(double) pixels[i]; |
373 | 0 | gamma+=(*k)*alpha; |
374 | 0 | k++; |
375 | 0 | pixels+=(ptrdiff_t) GetPixelChannels(image); |
376 | 0 | } |
377 | 0 | } |
378 | 0 | gamma=MagickSafeReciprocal(gamma); |
379 | 0 | SetPixelChannel(blur_image,channel,ClampToQuantum(gamma*pixel),q); |
380 | 0 | } |
381 | 0 | q+=(ptrdiff_t) GetPixelChannels(blur_image); |
382 | 0 | r+=(ptrdiff_t) GetPixelChannels(edge_image); |
383 | 0 | } |
384 | 0 | if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse) |
385 | 0 | status=MagickFalse; |
386 | 0 | if (image->progress_monitor != (MagickProgressMonitor) NULL) |
387 | 0 | { |
388 | 0 | MagickBooleanType |
389 | 0 | proceed; |
390 | |
|
391 | | #if defined(MAGICKCORE_OPENMP_SUPPORT) |
392 | | #pragma omp atomic |
393 | | #endif |
394 | 0 | progress++; |
395 | 0 | proceed=SetImageProgress(image,AdaptiveBlurImageTag,progress, |
396 | 0 | image->rows); |
397 | 0 | if (proceed == MagickFalse) |
398 | 0 | status=MagickFalse; |
399 | 0 | } |
400 | 0 | } |
401 | 0 | blur_image->type=image->type; |
402 | 0 | blur_view=DestroyCacheView(blur_view); |
403 | 0 | edge_view=DestroyCacheView(edge_view); |
404 | 0 | image_view=DestroyCacheView(image_view); |
405 | 0 | edge_image=DestroyImage(edge_image); |
406 | 0 | for (w=0; w < (ssize_t) width; w+=2) |
407 | 0 | kernel[w]=(double *) RelinquishAlignedMemory(kernel[w]); |
408 | 0 | kernel=(double **) RelinquishAlignedMemory(kernel); |
409 | 0 | if (status == MagickFalse) |
410 | 0 | blur_image=DestroyImage(blur_image); |
411 | 0 | return(blur_image); |
412 | 0 | } |
413 | | |
414 | | /* |
415 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
416 | | % % |
417 | | % % |
418 | | % % |
419 | | % A d a p t i v e S h a r p e n I m a g e % |
420 | | % % |
421 | | % % |
422 | | % % |
423 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
424 | | % |
425 | | % AdaptiveSharpenImage() adaptively sharpens the image by sharpening more |
426 | | % intensely near image edges and less intensely far from edges. We sharpen the |
427 | | % image with a Gaussian operator of the given radius and standard deviation |
428 | | % (sigma). For reasonable results, radius should be larger than sigma. Use a |
429 | | % radius of 0 and AdaptiveSharpenImage() selects a suitable radius for you. |
430 | | % |
431 | | % The format of the AdaptiveSharpenImage method is: |
432 | | % |
433 | | % Image *AdaptiveSharpenImage(const Image *image,const double radius, |
434 | | % const double sigma,ExceptionInfo *exception) |
435 | | % |
436 | | % A description of each parameter follows: |
437 | | % |
438 | | % o image: the image. |
439 | | % |
440 | | % o radius: the radius of the Gaussian, in pixels, not counting the center |
441 | | % pixel. |
442 | | % |
443 | | % o sigma: the standard deviation of the Laplacian, in pixels. |
444 | | % |
445 | | % o exception: return any errors or warnings in this structure. |
446 | | % |
447 | | */ |
448 | | MagickExport Image *AdaptiveSharpenImage(const Image *image,const double radius, |
449 | | const double sigma,ExceptionInfo *exception) |
450 | 0 | { |
451 | 0 | #define AdaptiveSharpenImageTag "Convolve/Image" |
452 | 0 | #define MagickSigma (fabs(sigma) < MagickEpsilon ? MagickEpsilon : sigma) |
453 | |
|
454 | 0 | CacheView |
455 | 0 | *sharp_view, |
456 | 0 | *edge_view, |
457 | 0 | *image_view; |
458 | |
|
459 | 0 | double |
460 | 0 | normalize, |
461 | 0 | **kernel; |
462 | |
|
463 | 0 | Image |
464 | 0 | *sharp_image, |
465 | 0 | *edge_image, |
466 | 0 | *gaussian_image; |
467 | |
|
468 | 0 | MagickBooleanType |
469 | 0 | status; |
470 | |
|
471 | 0 | MagickOffsetType |
472 | 0 | progress; |
473 | |
|
474 | 0 | size_t |
475 | 0 | width; |
476 | |
|
477 | 0 | ssize_t |
478 | 0 | w, |
479 | 0 | y; |
480 | |
|
481 | 0 | assert(image != (const Image *) NULL); |
482 | 0 | assert(image->signature == MagickCoreSignature); |
483 | 0 | assert(exception != (ExceptionInfo *) NULL); |
484 | 0 | assert(exception->signature == MagickCoreSignature); |
485 | 0 | if (IsEventLogging() != MagickFalse) |
486 | 0 | (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename); |
487 | 0 | sharp_image=CloneImage(image,0,0,MagickTrue,exception); |
488 | 0 | if (sharp_image == (Image *) NULL) |
489 | 0 | return((Image *) NULL); |
490 | 0 | if (fabs(sigma) < MagickEpsilon) |
491 | 0 | return(sharp_image); |
492 | 0 | if (SetImageStorageClass(sharp_image,DirectClass,exception) == MagickFalse) |
493 | 0 | { |
494 | 0 | sharp_image=DestroyImage(sharp_image); |
495 | 0 | return((Image *) NULL); |
496 | 0 | } |
497 | | /* |
498 | | Edge detect the image brightness channel, level, sharp, and level again. |
499 | | */ |
500 | 0 | edge_image=EdgeImage(image,radius,exception); |
501 | 0 | if (edge_image == (Image *) NULL) |
502 | 0 | { |
503 | 0 | sharp_image=DestroyImage(sharp_image); |
504 | 0 | return((Image *) NULL); |
505 | 0 | } |
506 | 0 | (void) AutoLevelImage(edge_image,exception); |
507 | 0 | gaussian_image=BlurImage(edge_image,radius,sigma,exception); |
508 | 0 | if (gaussian_image != (Image *) NULL) |
509 | 0 | { |
510 | 0 | edge_image=DestroyImage(edge_image); |
511 | 0 | edge_image=gaussian_image; |
512 | 0 | } |
513 | 0 | (void) AutoLevelImage(edge_image,exception); |
514 | | /* |
515 | | Create a set of kernels from maximum (radius,sigma) to minimum. |
516 | | */ |
517 | 0 | width=GetOptimalKernelWidth2D(radius,sigma); |
518 | 0 | kernel=(double **) MagickAssumeAligned(AcquireAlignedMemory((size_t) |
519 | 0 | width,sizeof(*kernel))); |
520 | 0 | if (kernel == (double **) NULL) |
521 | 0 | { |
522 | 0 | edge_image=DestroyImage(edge_image); |
523 | 0 | sharp_image=DestroyImage(sharp_image); |
524 | 0 | ThrowImageException(ResourceLimitError,"MemoryAllocationFailed"); |
525 | 0 | } |
526 | 0 | (void) memset(kernel,0,(size_t) width*sizeof(*kernel)); |
527 | 0 | for (w=0; w < (ssize_t) width; w+=2) |
528 | 0 | { |
529 | 0 | ssize_t |
530 | 0 | j, |
531 | 0 | k, |
532 | 0 | u, |
533 | 0 | v; |
534 | |
|
535 | 0 | kernel[w]=(double *) MagickAssumeAligned(AcquireAlignedMemory((size_t) |
536 | 0 | (width-(size_t) w),(width-(size_t) w)*sizeof(**kernel))); |
537 | 0 | if (kernel[w] == (double *) NULL) |
538 | 0 | break; |
539 | 0 | normalize=0.0; |
540 | 0 | j=((ssize_t) width-w-1)/2; |
541 | 0 | k=0; |
542 | 0 | for (v=(-j); v <= j; v++) |
543 | 0 | { |
544 | 0 | for (u=(-j); u <= j; u++) |
545 | 0 | { |
546 | 0 | kernel[w][k]=(double) (-exp(-((double) u*u+v*v)/(2.0*MagickSigma* |
547 | 0 | MagickSigma))/(2.0*MagickPI*MagickSigma*MagickSigma)); |
548 | 0 | normalize+=kernel[w][k]; |
549 | 0 | k++; |
550 | 0 | } |
551 | 0 | } |
552 | 0 | kernel[w][(k-1)/2]=(double) ((-2.0)*normalize); |
553 | 0 | if (sigma < MagickEpsilon) |
554 | 0 | kernel[w][(k-1)/2]=1.0; |
555 | 0 | } |
556 | 0 | if (w < (ssize_t) width) |
557 | 0 | { |
558 | 0 | for (w-=2; w >= 0; w-=2) |
559 | 0 | kernel[w]=(double *) RelinquishAlignedMemory(kernel[w]); |
560 | 0 | kernel=(double **) RelinquishAlignedMemory(kernel); |
561 | 0 | edge_image=DestroyImage(edge_image); |
562 | 0 | sharp_image=DestroyImage(sharp_image); |
563 | 0 | ThrowImageException(ResourceLimitError,"MemoryAllocationFailed"); |
564 | 0 | } |
565 | | /* |
566 | | Adaptively sharpen image. |
567 | | */ |
568 | 0 | status=MagickTrue; |
569 | 0 | progress=0; |
570 | 0 | image_view=AcquireVirtualCacheView(image,exception); |
571 | 0 | edge_view=AcquireVirtualCacheView(edge_image,exception); |
572 | 0 | sharp_view=AcquireAuthenticCacheView(sharp_image,exception); |
573 | | #if defined(MAGICKCORE_OPENMP_SUPPORT) |
574 | | #pragma omp parallel for schedule(static) shared(progress,status) \ |
575 | | magick_number_threads(image,sharp_image,sharp_image->rows,1) |
576 | | #endif |
577 | 0 | for (y=0; y < (ssize_t) sharp_image->rows; y++) |
578 | 0 | { |
579 | 0 | const Quantum |
580 | 0 | *magick_restrict r; |
581 | |
|
582 | 0 | Quantum |
583 | 0 | *magick_restrict q; |
584 | |
|
585 | 0 | ssize_t |
586 | 0 | x; |
587 | |
|
588 | 0 | if (status == MagickFalse) |
589 | 0 | continue; |
590 | 0 | r=GetCacheViewVirtualPixels(edge_view,0,y,edge_image->columns,1,exception); |
591 | 0 | q=QueueCacheViewAuthenticPixels(sharp_view,0,y,sharp_image->columns,1, |
592 | 0 | exception); |
593 | 0 | if ((r == (const Quantum *) NULL) || (q == (Quantum *) NULL)) |
594 | 0 | { |
595 | 0 | status=MagickFalse; |
596 | 0 | continue; |
597 | 0 | } |
598 | 0 | for (x=0; x < (ssize_t) sharp_image->columns; x++) |
599 | 0 | { |
600 | 0 | const Quantum |
601 | 0 | *magick_restrict p; |
602 | |
|
603 | 0 | ssize_t |
604 | 0 | i; |
605 | |
|
606 | 0 | ssize_t |
607 | 0 | center, |
608 | 0 | j; |
609 | |
|
610 | 0 | j=CastDoubleToSsizeT(ceil((double) width*(1.0-QuantumScale* |
611 | 0 | GetPixelIntensity(edge_image,r))-0.5)); |
612 | 0 | if (j < 0) |
613 | 0 | j=0; |
614 | 0 | else |
615 | 0 | if (j > (ssize_t) width) |
616 | 0 | j=(ssize_t) width; |
617 | 0 | if ((j & 0x01) != 0) |
618 | 0 | j--; |
619 | 0 | p=GetCacheViewVirtualPixels(image_view,x-(((ssize_t) width-j)/2L),y- |
620 | 0 | (((ssize_t) width-j)/2L),width-(size_t) j,width-(size_t) j,exception); |
621 | 0 | if (p == (const Quantum *) NULL) |
622 | 0 | break; |
623 | 0 | center=(ssize_t) (GetPixelChannels(image)*(width-(size_t) j)* |
624 | 0 | ((width-(size_t) j)/2L)+GetPixelChannels(image)*((width-(size_t) j)/2)); |
625 | 0 | for (i=0; i < (ssize_t) GetPixelChannels(sharp_image); i++) |
626 | 0 | { |
627 | 0 | const double |
628 | 0 | *magick_restrict k; |
629 | |
|
630 | 0 | const Quantum |
631 | 0 | *magick_restrict pixels; |
632 | |
|
633 | 0 | double |
634 | 0 | alpha, |
635 | 0 | gamma, |
636 | 0 | pixel; |
637 | |
|
638 | 0 | PixelChannel |
639 | 0 | channel; |
640 | |
|
641 | 0 | PixelTrait |
642 | 0 | sharp_traits, |
643 | 0 | traits; |
644 | |
|
645 | 0 | ssize_t |
646 | 0 | u, |
647 | 0 | v; |
648 | |
|
649 | 0 | channel=GetPixelChannelChannel(image,i); |
650 | 0 | traits=GetPixelChannelTraits(image,channel); |
651 | 0 | sharp_traits=GetPixelChannelTraits(sharp_image,channel); |
652 | 0 | if ((traits == UndefinedPixelTrait) || |
653 | 0 | (sharp_traits == UndefinedPixelTrait)) |
654 | 0 | continue; |
655 | 0 | if ((sharp_traits & CopyPixelTrait) != 0) |
656 | 0 | { |
657 | 0 | SetPixelChannel(sharp_image,channel,p[center+i],q); |
658 | 0 | continue; |
659 | 0 | } |
660 | 0 | k=kernel[j]; |
661 | 0 | pixels=p; |
662 | 0 | pixel=0.0; |
663 | 0 | gamma=0.0; |
664 | 0 | if ((sharp_traits & BlendPixelTrait) == 0) |
665 | 0 | { |
666 | | /* |
667 | | No alpha blending. |
668 | | */ |
669 | 0 | for (v=0; v < ((ssize_t) width-j); v++) |
670 | 0 | { |
671 | 0 | for (u=0; u < ((ssize_t) width-j); u++) |
672 | 0 | { |
673 | 0 | pixel+=(*k)*(double) pixels[i]; |
674 | 0 | gamma+=(*k); |
675 | 0 | k++; |
676 | 0 | pixels+=(ptrdiff_t) GetPixelChannels(image); |
677 | 0 | } |
678 | 0 | } |
679 | 0 | gamma=MagickSafeReciprocal(gamma); |
680 | 0 | SetPixelChannel(sharp_image,channel,ClampToQuantum(gamma*pixel),q); |
681 | 0 | continue; |
682 | 0 | } |
683 | | /* |
684 | | Alpha blending. |
685 | | */ |
686 | 0 | for (v=0; v < ((ssize_t) width-j); v++) |
687 | 0 | { |
688 | 0 | for (u=0; u < ((ssize_t) width-j); u++) |
689 | 0 | { |
690 | 0 | alpha=(double) (QuantumScale*(double) GetPixelAlpha(image,pixels)); |
691 | 0 | pixel+=(*k)*alpha*(double) pixels[i]; |
692 | 0 | gamma+=(*k)*alpha; |
693 | 0 | k++; |
694 | 0 | pixels+=(ptrdiff_t) GetPixelChannels(image); |
695 | 0 | } |
696 | 0 | } |
697 | 0 | gamma=MagickSafeReciprocal(gamma); |
698 | 0 | SetPixelChannel(sharp_image,channel,ClampToQuantum(gamma*pixel),q); |
699 | 0 | } |
700 | 0 | q+=(ptrdiff_t) GetPixelChannels(sharp_image); |
701 | 0 | r+=(ptrdiff_t) GetPixelChannels(edge_image); |
702 | 0 | } |
703 | 0 | if (SyncCacheViewAuthenticPixels(sharp_view,exception) == MagickFalse) |
704 | 0 | status=MagickFalse; |
705 | 0 | if (image->progress_monitor != (MagickProgressMonitor) NULL) |
706 | 0 | { |
707 | 0 | MagickBooleanType |
708 | 0 | proceed; |
709 | |
|
710 | | #if defined(MAGICKCORE_OPENMP_SUPPORT) |
711 | | #pragma omp atomic |
712 | | #endif |
713 | 0 | progress++; |
714 | 0 | proceed=SetImageProgress(image,AdaptiveSharpenImageTag,progress, |
715 | 0 | image->rows); |
716 | 0 | if (proceed == MagickFalse) |
717 | 0 | status=MagickFalse; |
718 | 0 | } |
719 | 0 | } |
720 | 0 | sharp_image->type=image->type; |
721 | 0 | sharp_view=DestroyCacheView(sharp_view); |
722 | 0 | edge_view=DestroyCacheView(edge_view); |
723 | 0 | image_view=DestroyCacheView(image_view); |
724 | 0 | edge_image=DestroyImage(edge_image); |
725 | 0 | for (w=0; w < (ssize_t) width; w+=2) |
726 | 0 | kernel[w]=(double *) RelinquishAlignedMemory(kernel[w]); |
727 | 0 | kernel=(double **) RelinquishAlignedMemory(kernel); |
728 | 0 | if (status == MagickFalse) |
729 | 0 | sharp_image=DestroyImage(sharp_image); |
730 | 0 | return(sharp_image); |
731 | 0 | } |
732 | | |
733 | | /* |
734 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
735 | | % % |
736 | | % % |
737 | | % % |
738 | | % B l u r I m a g e % |
739 | | % % |
740 | | % % |
741 | | % % |
742 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
743 | | % |
744 | | % BlurImage() blurs an image. We convolve the image with a Gaussian operator |
745 | | % of the given radius and standard deviation (sigma). For reasonable results, |
746 | | % the radius should be larger than sigma. Use a radius of 0 and BlurImage() |
747 | | % selects a suitable radius for you. |
748 | | % |
749 | | % The format of the BlurImage method is: |
750 | | % |
751 | | % Image *BlurImage(const Image *image,const double radius, |
752 | | % const double sigma,ExceptionInfo *exception) |
753 | | % |
754 | | % A description of each parameter follows: |
755 | | % |
756 | | % o image: the image. |
757 | | % |
758 | | % o radius: the radius of the Gaussian, in pixels, not counting the center |
759 | | % pixel. |
760 | | % |
761 | | % o sigma: the standard deviation of the Gaussian, in pixels. |
762 | | % |
763 | | % o exception: return any errors or warnings in this structure. |
764 | | % |
765 | | */ |
766 | | MagickExport Image *BlurImage(const Image *image,const double radius, |
767 | | const double sigma,ExceptionInfo *exception) |
768 | 0 | { |
769 | 0 | char |
770 | 0 | geometry[MagickPathExtent]; |
771 | |
|
772 | 0 | KernelInfo |
773 | 0 | *kernel_info; |
774 | |
|
775 | 0 | Image |
776 | 0 | *blur_image; |
777 | |
|
778 | 0 | assert(image != (const Image *) NULL); |
779 | 0 | assert(image->signature == MagickCoreSignature); |
780 | 0 | assert(exception != (ExceptionInfo *) NULL); |
781 | 0 | assert(exception->signature == MagickCoreSignature); |
782 | 0 | if (IsEventLogging() != MagickFalse) |
783 | 0 | (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename); |
784 | | #if defined(MAGICKCORE_OPENCL_SUPPORT) |
785 | | blur_image=AccelerateBlurImage(image,radius,sigma,exception); |
786 | | if (blur_image != (Image *) NULL) |
787 | | return(blur_image); |
788 | | #endif |
789 | 0 | (void) FormatLocaleString(geometry,MagickPathExtent, |
790 | 0 | "blur:%.20gx%.20g;blur:%.20gx%.20g+90",radius,sigma,radius,sigma); |
791 | 0 | kernel_info=AcquireKernelInfo(geometry,exception); |
792 | 0 | if (kernel_info == (KernelInfo *) NULL) |
793 | 0 | ThrowImageException(ResourceLimitError,"MemoryAllocationFailed"); |
794 | 0 | blur_image=ConvolveImage(image,kernel_info,exception); |
795 | 0 | kernel_info=DestroyKernelInfo(kernel_info); |
796 | 0 | return(blur_image); |
797 | 0 | } |
798 | | |
799 | | /* |
800 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
801 | | % % |
802 | | % % |
803 | | % % |
804 | | % B i l a t e r a l B l u r I m a g e % |
805 | | % % |
806 | | % % |
807 | | % % |
808 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
809 | | % |
810 | | % BilateralBlurImage() is a non-linear, edge-preserving, and noise-reducing |
811 | | % smoothing filter for images. It replaces the intensity of each pixel with |
812 | | % a weighted average of intensity values from nearby pixels. This weight is |
813 | | % based on a Gaussian distribution. The weights depend not only on Euclidean |
814 | | % distance of pixels, but also on the radiometric differences (e.g., range |
815 | | % differences, such as color intensity, depth distance, etc.). This preserves |
816 | | % sharp edges. |
817 | | % |
818 | | % The format of the BilateralBlurImage method is: |
819 | | % |
820 | | % Image *BilateralBlurImage(const Image *image,const size_t width, |
821 | | % const size_t height,const double intensity_sigma, |
822 | | % const double spatial_sigma,ExceptionInfo *exception) |
823 | | % |
824 | | % A description of each parameter follows: |
825 | | % |
826 | | % o image: the image. |
827 | | % |
828 | | % o width: the width of the neighborhood in pixels. |
829 | | % |
830 | | % o height: the height of the neighborhood in pixels. |
831 | | % |
832 | | % o intensity_sigma: sigma in the intensity space. A larger value means |
833 | | % that farther colors within the pixel neighborhood (see spatial_sigma) |
834 | | % will be mixed together, resulting in larger areas of semi-equal color. |
835 | | % |
836 | | % o spatial_sigma: sigma in the coordinate space. A larger value means that |
837 | | % farther pixels influence each other as long as their colors are close |
838 | | % enough (see intensity_sigma ). When the neighborhood diameter is greater |
839 | | % than zero, it specifies the neighborhood size regardless of |
840 | | % spatial_sigma. Otherwise, the neighborhood diameter is proportional to |
841 | | % spatial_sigma. |
842 | | % |
843 | | % o exception: return any errors or warnings in this structure. |
844 | | % |
845 | | */ |
846 | | |
847 | | static inline double BlurDistance(const ssize_t x,const ssize_t y, |
848 | | const ssize_t u,const ssize_t v) |
849 | 0 | { |
850 | 0 | return(sqrt(((double) x-u)*((double) x-u)+((double) y-v)*((double) y-v))); |
851 | 0 | } |
852 | | |
853 | | static inline double BlurGaussian(const double x,const double sigma) |
854 | 0 | { |
855 | 0 | return(exp(-((double) x*x)*MagickSafeReciprocal(2.0*sigma*sigma))* |
856 | 0 | MagickSafeReciprocal(Magick2PI*sigma*sigma)); |
857 | 0 | } |
858 | | |
859 | | static double **DestroyBilateralTLS(const size_t number_threads, |
860 | | double **weights) |
861 | 0 | { |
862 | 0 | ssize_t |
863 | 0 | i; |
864 | |
|
865 | 0 | assert(weights != (double **) NULL); |
866 | 0 | for (i=0; i <= (ssize_t) number_threads; i++) |
867 | 0 | if (weights[i] != (double *) NULL) |
868 | 0 | weights[i]=(double *) RelinquishMagickMemory(weights[i]); |
869 | 0 | weights=(double **) RelinquishMagickMemory(weights); |
870 | 0 | return(weights); |
871 | 0 | } |
872 | | |
873 | | static double **AcquireBilateralTLS(const size_t number_threads, |
874 | | const size_t width,const size_t height) |
875 | 0 | { |
876 | 0 | double |
877 | 0 | **weights; |
878 | |
|
879 | 0 | ssize_t |
880 | 0 | i; |
881 | |
|
882 | 0 | weights=(double **) AcquireQuantumMemory(number_threads+1,sizeof(*weights)); |
883 | 0 | if (weights == (double **) NULL) |
884 | 0 | return((double **) NULL); |
885 | 0 | (void) memset(weights,0,number_threads*sizeof(*weights)); |
886 | 0 | for (i=0; i <= (ssize_t) number_threads; i++) |
887 | 0 | { |
888 | 0 | weights[i]=(double *) AcquireQuantumMemory(width,height*sizeof(**weights)); |
889 | 0 | if (weights[i] == (double *) NULL) |
890 | 0 | return(DestroyBilateralTLS(number_threads,weights)); |
891 | 0 | } |
892 | 0 | return(weights); |
893 | 0 | } |
894 | | |
895 | | MagickExport Image *BilateralBlurImage(const Image *image,const size_t width, |
896 | | const size_t height,const double intensity_sigma,const double spatial_sigma, |
897 | | ExceptionInfo *exception) |
898 | 0 | { |
899 | 0 | #define MaxIntensity (255) |
900 | 0 | #define BilateralBlurImageTag "Blur/Image" |
901 | |
|
902 | 0 | CacheView |
903 | 0 | *blur_view, |
904 | 0 | *image_view; |
905 | |
|
906 | 0 | double |
907 | 0 | intensity_gaussian[2*(MaxIntensity+1)], |
908 | 0 | *spatial_gaussian, |
909 | 0 | **weights; |
910 | |
|
911 | 0 | Image |
912 | 0 | *blur_image; |
913 | |
|
914 | 0 | MagickBooleanType |
915 | 0 | status; |
916 | |
|
917 | 0 | MagickOffsetType |
918 | 0 | progress; |
919 | |
|
920 | 0 | OffsetInfo |
921 | 0 | mid; |
922 | |
|
923 | 0 | size_t |
924 | 0 | number_threads; |
925 | |
|
926 | 0 | ssize_t |
927 | 0 | w, |
928 | 0 | y; |
929 | |
|
930 | 0 | assert(image != (const Image *) NULL); |
931 | 0 | assert(image->signature == MagickCoreSignature); |
932 | 0 | assert(exception != (ExceptionInfo *) NULL); |
933 | 0 | assert(exception->signature == MagickCoreSignature); |
934 | 0 | if (IsEventLogging() != MagickFalse) |
935 | 0 | (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename); |
936 | 0 | blur_image=CloneImage(image,0,0,MagickTrue,exception); |
937 | 0 | if (blur_image == (Image *) NULL) |
938 | 0 | return((Image *) NULL); |
939 | 0 | if (SetImageStorageClass(blur_image,DirectClass,exception) == MagickFalse) |
940 | 0 | { |
941 | 0 | blur_image=DestroyImage(blur_image); |
942 | 0 | return((Image *) NULL); |
943 | 0 | } |
944 | 0 | number_threads=(size_t) GetMagickResourceLimit(ThreadResource); |
945 | 0 | weights=AcquireBilateralTLS(number_threads,MagickMax(width,1), |
946 | 0 | MagickMax(height,1)); |
947 | 0 | if (weights == (double **) NULL) |
948 | 0 | { |
949 | 0 | blur_image=DestroyImage(blur_image); |
950 | 0 | ThrowImageException(ResourceLimitError,"MemoryAllocationFailed"); |
951 | 0 | } |
952 | 0 | for (w=(-MaxIntensity); w < MaxIntensity; w++) |
953 | 0 | intensity_gaussian[w+MaxIntensity]=BlurGaussian((double) w,intensity_sigma); |
954 | 0 | spatial_gaussian=weights[number_threads]; |
955 | 0 | { |
956 | 0 | ssize_t |
957 | 0 | n, |
958 | 0 | v; |
959 | |
|
960 | 0 | n=0; |
961 | 0 | mid.x=(ssize_t) (MagickMax(width,1)/2L); |
962 | 0 | mid.y=(ssize_t) (MagickMax(height,1)/2L); |
963 | 0 | for (v=0; v < (ssize_t) MagickMax(height,1); v++) |
964 | 0 | { |
965 | 0 | ssize_t |
966 | 0 | u; |
967 | |
|
968 | 0 | for (u=0; u < (ssize_t) MagickMax(width,1); u++) |
969 | 0 | spatial_gaussian[n++]=BlurGaussian(BlurDistance(0,0,u-mid.x,v-mid.y), |
970 | 0 | spatial_sigma); |
971 | 0 | } |
972 | 0 | } |
973 | | /* |
974 | | Bilateral blur image. |
975 | | */ |
976 | 0 | status=MagickTrue; |
977 | 0 | progress=0; |
978 | 0 | image_view=AcquireVirtualCacheView(image,exception); |
979 | 0 | blur_view=AcquireAuthenticCacheView(blur_image,exception); |
980 | | #if defined(MAGICKCORE_OPENMP_SUPPORT) |
981 | | #pragma omp parallel for schedule(static) shared(progress,status) \ |
982 | | magick_number_threads(image,blur_image,blur_image->rows,1) |
983 | | #endif |
984 | 0 | for (y=0; y < (ssize_t) blur_image->rows; y++) |
985 | 0 | { |
986 | 0 | const int |
987 | 0 | id = GetOpenMPThreadId(); |
988 | |
|
989 | 0 | Quantum |
990 | 0 | *magick_restrict q; |
991 | |
|
992 | 0 | ssize_t |
993 | 0 | x; |
994 | |
|
995 | 0 | if (status == MagickFalse) |
996 | 0 | continue; |
997 | 0 | q=QueueCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1, |
998 | 0 | exception); |
999 | 0 | if (q == (Quantum *) NULL) |
1000 | 0 | { |
1001 | 0 | status=MagickFalse; |
1002 | 0 | continue; |
1003 | 0 | } |
1004 | 0 | for (x=0; x < (ssize_t) blur_image->columns; x++) |
1005 | 0 | { |
1006 | 0 | const Quantum |
1007 | 0 | *magick_restrict p, |
1008 | 0 | *magick_restrict r; |
1009 | |
|
1010 | 0 | double |
1011 | 0 | gamma, |
1012 | 0 | pixel; |
1013 | |
|
1014 | 0 | ssize_t |
1015 | 0 | i, |
1016 | 0 | n, |
1017 | 0 | u, |
1018 | 0 | v; |
1019 | | |
1020 | | /* |
1021 | | Tonal weighting preserves edges while smoothing in the flat regions. |
1022 | | */ |
1023 | 0 | p=GetCacheViewVirtualPixels(image_view,x-mid.x,y-mid.y,MagickMax(width,1), |
1024 | 0 | MagickMax(height,1),exception); |
1025 | 0 | if (p == (const Quantum *) NULL) |
1026 | 0 | break; |
1027 | 0 | p+=(ptrdiff_t) (GetPixelChannels(image)*MagickMax(width,1)*(size_t) mid.y+ |
1028 | 0 | GetPixelChannels(image)*(size_t) mid.x); |
1029 | 0 | n=0; |
1030 | 0 | for (v=0; v < (ssize_t) MagickMax(height,1); v++) |
1031 | 0 | { |
1032 | 0 | for (u=0; u < (ssize_t) MagickMax(width,1); u++) |
1033 | 0 | { |
1034 | 0 | double |
1035 | 0 | intensity; |
1036 | |
|
1037 | 0 | r=p+(ssize_t) (GetPixelChannels(image)*MagickMax(width,1)* |
1038 | 0 | (size_t) (mid.y-v)+GetPixelChannels(image)*(size_t) (mid.x-u)); |
1039 | 0 | intensity=ScaleQuantumToChar((const Quantum) GetPixelIntensity(image,r))- |
1040 | 0 | (double) ScaleQuantumToChar((const Quantum) GetPixelIntensity(image,p)); |
1041 | 0 | if ((intensity >= -MaxIntensity) && (intensity <= MaxIntensity)) |
1042 | 0 | weights[id][n]=intensity_gaussian[(ssize_t) intensity+MaxIntensity]* |
1043 | 0 | spatial_gaussian[n]; |
1044 | 0 | else |
1045 | 0 | weights[id][n]=BlurGaussian(intensity,intensity_sigma)* |
1046 | 0 | BlurGaussian(BlurDistance(x,y,x+u-mid.x,y+v-mid.y),spatial_sigma); |
1047 | 0 | n++; |
1048 | 0 | } |
1049 | 0 | } |
1050 | 0 | for (i=0; i < (ssize_t) GetPixelChannels(blur_image); i++) |
1051 | 0 | { |
1052 | 0 | PixelChannel |
1053 | 0 | channel; |
1054 | |
|
1055 | 0 | PixelTrait |
1056 | 0 | blur_traits, |
1057 | 0 | traits; |
1058 | |
|
1059 | 0 | channel=GetPixelChannelChannel(image,i); |
1060 | 0 | traits=GetPixelChannelTraits(image,channel); |
1061 | 0 | blur_traits=GetPixelChannelTraits(blur_image,channel); |
1062 | 0 | if ((traits == UndefinedPixelTrait) || |
1063 | 0 | (blur_traits == UndefinedPixelTrait)) |
1064 | 0 | continue; |
1065 | 0 | if ((blur_traits & CopyPixelTrait) != 0) |
1066 | 0 | { |
1067 | 0 | SetPixelChannel(blur_image,channel,p[i],q); |
1068 | 0 | continue; |
1069 | 0 | } |
1070 | 0 | pixel=0.0; |
1071 | 0 | gamma=0.0; |
1072 | 0 | n=0; |
1073 | 0 | if ((blur_traits & BlendPixelTrait) == 0) |
1074 | 0 | { |
1075 | | /* |
1076 | | No alpha blending. |
1077 | | */ |
1078 | 0 | for (v=0; v < (ssize_t) MagickMax(height,1); v++) |
1079 | 0 | { |
1080 | 0 | for (u=0; u < (ssize_t) MagickMax(width,1); u++) |
1081 | 0 | { |
1082 | 0 | r=p+GetPixelChannels(image)*MagickMax(width,1)*(size_t) |
1083 | 0 | (mid.y-v)+GetPixelChannels(image)*(size_t) (mid.x-u); |
1084 | 0 | pixel+=weights[id][n]*(double) r[i]; |
1085 | 0 | gamma+=weights[id][n]; |
1086 | 0 | n++; |
1087 | 0 | } |
1088 | 0 | } |
1089 | 0 | SetPixelChannel(blur_image,channel,ClampToQuantum( |
1090 | 0 | MagickSafeReciprocal(gamma)*pixel),q); |
1091 | 0 | continue; |
1092 | 0 | } |
1093 | | /* |
1094 | | Alpha blending. |
1095 | | */ |
1096 | 0 | for (v=0; v < (ssize_t) MagickMax(height,1); v++) |
1097 | 0 | { |
1098 | 0 | for (u=0; u < (ssize_t) MagickMax(width,1); u++) |
1099 | 0 | { |
1100 | 0 | double |
1101 | 0 | alpha, |
1102 | 0 | beta; |
1103 | |
|
1104 | 0 | r=p+GetPixelChannels(image)*MagickMax(width,1)*(size_t) (mid.y-v)+ |
1105 | 0 | GetPixelChannels(image)*(size_t) (mid.x-u); |
1106 | 0 | alpha=(double) (QuantumScale*(double) GetPixelAlpha(image,p)); |
1107 | 0 | beta=(double) (QuantumScale*(double) GetPixelAlpha(image,r)); |
1108 | 0 | pixel+=weights[id][n]*(double) r[i]; |
1109 | 0 | gamma+=weights[id][n]*alpha*beta; |
1110 | 0 | n++; |
1111 | 0 | } |
1112 | 0 | } |
1113 | 0 | SetPixelChannel(blur_image,channel,ClampToQuantum( |
1114 | 0 | MagickSafeReciprocal(gamma)*pixel),q); |
1115 | 0 | } |
1116 | 0 | q+=(ptrdiff_t) GetPixelChannels(blur_image); |
1117 | 0 | } |
1118 | 0 | if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse) |
1119 | 0 | status=MagickFalse; |
1120 | 0 | if (image->progress_monitor != (MagickProgressMonitor) NULL) |
1121 | 0 | { |
1122 | 0 | MagickBooleanType |
1123 | 0 | proceed; |
1124 | |
|
1125 | | #if defined(MAGICKCORE_OPENMP_SUPPORT) |
1126 | | #pragma omp atomic |
1127 | | #endif |
1128 | 0 | progress++; |
1129 | 0 | proceed=SetImageProgress(image,BilateralBlurImageTag,progress, |
1130 | 0 | image->rows); |
1131 | 0 | if (proceed == MagickFalse) |
1132 | 0 | status=MagickFalse; |
1133 | 0 | } |
1134 | 0 | } |
1135 | 0 | blur_image->type=image->type; |
1136 | 0 | blur_view=DestroyCacheView(blur_view); |
1137 | 0 | image_view=DestroyCacheView(image_view); |
1138 | 0 | weights=DestroyBilateralTLS(number_threads,weights); |
1139 | 0 | if (status == MagickFalse) |
1140 | 0 | blur_image=DestroyImage(blur_image); |
1141 | 0 | return(blur_image); |
1142 | 0 | } |
1143 | | |
1144 | | /* |
1145 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
1146 | | % % |
1147 | | % % |
1148 | | % % |
1149 | | % C o n v o l v e I m a g e % |
1150 | | % % |
1151 | | % % |
1152 | | % % |
1153 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
1154 | | % |
1155 | | % ConvolveImage() applies a custom convolution kernel to the image. |
1156 | | % |
1157 | | % The format of the ConvolveImage method is: |
1158 | | % |
1159 | | % Image *ConvolveImage(const Image *image,const KernelInfo *kernel, |
1160 | | % ExceptionInfo *exception) |
1161 | | % |
1162 | | % A description of each parameter follows: |
1163 | | % |
1164 | | % o image: the image. |
1165 | | % |
1166 | | % o kernel: the filtering kernel. |
1167 | | % |
1168 | | % o exception: return any errors or warnings in this structure. |
1169 | | % |
1170 | | */ |
1171 | | MagickExport Image *ConvolveImage(const Image *image, |
1172 | | const KernelInfo *kernel_info,ExceptionInfo *exception) |
1173 | 0 | { |
1174 | 0 | Image |
1175 | 0 | *convolve_image; |
1176 | |
|
1177 | 0 | convolve_image=MorphologyImage(image,ConvolveMorphology,1,kernel_info, |
1178 | 0 | exception); |
1179 | 0 | return(convolve_image); |
1180 | 0 | } |
1181 | | |
1182 | | /* |
1183 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
1184 | | % % |
1185 | | % % |
1186 | | % % |
1187 | | % D e s p e c k l e I m a g e % |
1188 | | % % |
1189 | | % % |
1190 | | % % |
1191 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
1192 | | % |
1193 | | % DespeckleImage() reduces the speckle noise in an image while preserving the |
1194 | | % edges of the original image. A speckle removing filter uses a complementary |
1195 | | % hulling technique (raising pixels that are darker than their surrounding |
1196 | | % neighbors, then complementarily lowering pixels that are brighter than their |
1197 | | % surrounding neighbors) to reduce the speckle index of that image (reference |
1198 | | % Crimmins speckle removal). |
1199 | | % |
1200 | | % The format of the DespeckleImage method is: |
1201 | | % |
1202 | | % Image *DespeckleImage(const Image *image,ExceptionInfo *exception) |
1203 | | % |
1204 | | % A description of each parameter follows: |
1205 | | % |
1206 | | % o image: the image. |
1207 | | % |
1208 | | % o exception: return any errors or warnings in this structure. |
1209 | | % |
1210 | | */ |
1211 | | |
1212 | | static void Hull(const Image *image,const ssize_t x_offset, |
1213 | | const ssize_t y_offset,const size_t columns,const size_t rows, |
1214 | | const int polarity,Quantum *magick_restrict f,Quantum *magick_restrict g) |
1215 | 0 | { |
1216 | 0 | Quantum |
1217 | 0 | *p, |
1218 | 0 | *q, |
1219 | 0 | *r, |
1220 | 0 | *s; |
1221 | |
|
1222 | 0 | ssize_t |
1223 | 0 | y; |
1224 | |
|
1225 | 0 | assert(image != (const Image *) NULL); |
1226 | 0 | assert(image->signature == MagickCoreSignature); |
1227 | 0 | assert(f != (Quantum *) NULL); |
1228 | 0 | assert(g != (Quantum *) NULL); |
1229 | 0 | assert(columns <= (size_t) (MAGICK_SSIZE_MAX-2)); |
1230 | 0 | if (IsEventLogging() != MagickFalse) |
1231 | 0 | (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename); |
1232 | 0 | p=f+(ptrdiff_t) (columns+2); |
1233 | 0 | q=g+(ptrdiff_t) (columns+2); |
1234 | 0 | r=p+(ptrdiff_t) (y_offset*((ssize_t) columns+2)+x_offset); |
1235 | | #if defined(MAGICKCORE_OPENMP_SUPPORT) |
1236 | | #pragma omp parallel for schedule(static) \ |
1237 | | magick_number_threads(image,image,rows,2) |
1238 | | #endif |
1239 | 0 | for (y=0; y < (ssize_t) rows; y++) |
1240 | 0 | { |
1241 | 0 | MagickRealType |
1242 | 0 | v; |
1243 | |
|
1244 | 0 | ssize_t |
1245 | 0 | i, |
1246 | 0 | x; |
1247 | |
|
1248 | 0 | i=(2*y+1)+y*(ssize_t) columns; |
1249 | 0 | if (polarity > 0) |
1250 | 0 | for (x=0; x < (ssize_t) columns; x++) |
1251 | 0 | { |
1252 | 0 | v=(MagickRealType) p[i]; |
1253 | 0 | if ((MagickRealType) r[i] >= (v+(double) ScaleCharToQuantum(2))) |
1254 | 0 | v+=(double) ScaleCharToQuantum(1); |
1255 | 0 | q[i]=(Quantum) v; |
1256 | 0 | i++; |
1257 | 0 | } |
1258 | 0 | else |
1259 | 0 | for (x=0; x < (ssize_t) columns; x++) |
1260 | 0 | { |
1261 | 0 | v=(MagickRealType) p[i]; |
1262 | 0 | if ((MagickRealType) r[i] <= (v-(double) ScaleCharToQuantum(2))) |
1263 | 0 | v-=(double) ScaleCharToQuantum(1); |
1264 | 0 | q[i]=(Quantum) v; |
1265 | 0 | i++; |
1266 | 0 | } |
1267 | 0 | } |
1268 | 0 | p=f+(ptrdiff_t) (columns+2); |
1269 | 0 | q=g+(ptrdiff_t) (columns+2); |
1270 | 0 | r=q+(ptrdiff_t) (y_offset*((ssize_t) columns+2)+x_offset); |
1271 | 0 | s=q-(ptrdiff_t) (y_offset*((ssize_t) columns+2)+x_offset); |
1272 | | #if defined(MAGICKCORE_OPENMP_SUPPORT) |
1273 | | #pragma omp parallel for schedule(static) \ |
1274 | | magick_number_threads(image,image,rows,2) |
1275 | | #endif |
1276 | 0 | for (y=0; y < (ssize_t) rows; y++) |
1277 | 0 | { |
1278 | 0 | ssize_t |
1279 | 0 | i, |
1280 | 0 | x; |
1281 | |
|
1282 | 0 | MagickRealType |
1283 | 0 | v; |
1284 | |
|
1285 | 0 | i=(2*y+1)+y*(ssize_t) columns; |
1286 | 0 | if (polarity > 0) |
1287 | 0 | for (x=0; x < (ssize_t) columns; x++) |
1288 | 0 | { |
1289 | 0 | v=(MagickRealType) q[i]; |
1290 | 0 | if (((MagickRealType) s[i] >= (v+(double) ScaleCharToQuantum(2))) && |
1291 | 0 | ((MagickRealType) r[i] > v)) |
1292 | 0 | v+=(double) ScaleCharToQuantum(1); |
1293 | 0 | p[i]=(Quantum) v; |
1294 | 0 | i++; |
1295 | 0 | } |
1296 | 0 | else |
1297 | 0 | for (x=0; x < (ssize_t) columns; x++) |
1298 | 0 | { |
1299 | 0 | v=(MagickRealType) q[i]; |
1300 | 0 | if (((MagickRealType) s[i] <= (v-(double) ScaleCharToQuantum(2))) && |
1301 | 0 | ((MagickRealType) r[i] < v)) |
1302 | 0 | v-=(double) ScaleCharToQuantum(1); |
1303 | 0 | p[i]=(Quantum) v; |
1304 | 0 | i++; |
1305 | 0 | } |
1306 | 0 | } |
1307 | 0 | } |
1308 | | |
1309 | | MagickExport Image *DespeckleImage(const Image *image,ExceptionInfo *exception) |
1310 | 0 | { |
1311 | 0 | #define DespeckleImageTag "Despeckle/Image" |
1312 | |
|
1313 | 0 | CacheView |
1314 | 0 | *despeckle_view, |
1315 | 0 | *image_view; |
1316 | |
|
1317 | 0 | Image |
1318 | 0 | *despeckle_image; |
1319 | |
|
1320 | 0 | MagickBooleanType |
1321 | 0 | status; |
1322 | |
|
1323 | 0 | MemoryInfo |
1324 | 0 | *buffer_info, |
1325 | 0 | *pixel_info; |
1326 | |
|
1327 | 0 | Quantum |
1328 | 0 | *magick_restrict buffer, |
1329 | 0 | *magick_restrict pixels; |
1330 | |
|
1331 | 0 | size_t |
1332 | 0 | length; |
1333 | |
|
1334 | 0 | ssize_t |
1335 | 0 | i; |
1336 | |
|
1337 | 0 | static const ssize_t |
1338 | 0 | X[4] = {0, 1, 1,-1}, |
1339 | 0 | Y[4] = {1, 0, 1, 1}; |
1340 | | |
1341 | | /* |
1342 | | Allocate despeckled image. |
1343 | | */ |
1344 | 0 | assert(image != (const Image *) NULL); |
1345 | 0 | assert(image->signature == MagickCoreSignature); |
1346 | 0 | assert(exception != (ExceptionInfo *) NULL); |
1347 | 0 | assert(exception->signature == MagickCoreSignature); |
1348 | 0 | if (IsEventLogging() != MagickFalse) |
1349 | 0 | (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename); |
1350 | | #if defined(MAGICKCORE_OPENCL_SUPPORT) |
1351 | | despeckle_image=AccelerateDespeckleImage(image,exception); |
1352 | | if (despeckle_image != (Image *) NULL) |
1353 | | return(despeckle_image); |
1354 | | #endif |
1355 | 0 | despeckle_image=CloneImage(image,0,0,MagickTrue,exception); |
1356 | 0 | if (despeckle_image == (Image *) NULL) |
1357 | 0 | return((Image *) NULL); |
1358 | 0 | status=SetImageStorageClass(despeckle_image,DirectClass,exception); |
1359 | 0 | if (status == MagickFalse) |
1360 | 0 | { |
1361 | 0 | despeckle_image=DestroyImage(despeckle_image); |
1362 | 0 | return((Image *) NULL); |
1363 | 0 | } |
1364 | | /* |
1365 | | Allocate image buffer. |
1366 | | */ |
1367 | 0 | length=(size_t) ((image->columns+2)*(image->rows+2)); |
1368 | 0 | pixel_info=AcquireVirtualMemory(length,sizeof(*pixels)); |
1369 | 0 | buffer_info=AcquireVirtualMemory(length,sizeof(*buffer)); |
1370 | 0 | if ((pixel_info == (MemoryInfo *) NULL) || |
1371 | 0 | (buffer_info == (MemoryInfo *) NULL)) |
1372 | 0 | { |
1373 | 0 | if (buffer_info != (MemoryInfo *) NULL) |
1374 | 0 | buffer_info=RelinquishVirtualMemory(buffer_info); |
1375 | 0 | if (pixel_info != (MemoryInfo *) NULL) |
1376 | 0 | pixel_info=RelinquishVirtualMemory(pixel_info); |
1377 | 0 | despeckle_image=DestroyImage(despeckle_image); |
1378 | 0 | ThrowImageException(ResourceLimitError,"MemoryAllocationFailed"); |
1379 | 0 | } |
1380 | 0 | pixels=(Quantum *) GetVirtualMemoryBlob(pixel_info); |
1381 | 0 | buffer=(Quantum *) GetVirtualMemoryBlob(buffer_info); |
1382 | | /* |
1383 | | Reduce speckle in the image. |
1384 | | */ |
1385 | 0 | status=MagickTrue; |
1386 | 0 | image_view=AcquireVirtualCacheView(image,exception); |
1387 | 0 | despeckle_view=AcquireAuthenticCacheView(despeckle_image,exception); |
1388 | 0 | for (i=0; i < (ssize_t) GetPixelChannels(image); i++) |
1389 | 0 | { |
1390 | 0 | PixelChannel |
1391 | 0 | channel; |
1392 | |
|
1393 | 0 | PixelTrait |
1394 | 0 | despeckle_traits, |
1395 | 0 | traits; |
1396 | |
|
1397 | 0 | ssize_t |
1398 | 0 | k, |
1399 | 0 | x; |
1400 | |
|
1401 | 0 | ssize_t |
1402 | 0 | j, |
1403 | 0 | y; |
1404 | |
|
1405 | 0 | if (status == MagickFalse) |
1406 | 0 | continue; |
1407 | 0 | channel=GetPixelChannelChannel(image,i); |
1408 | 0 | traits=GetPixelChannelTraits(image,channel); |
1409 | 0 | despeckle_traits=GetPixelChannelTraits(despeckle_image,channel); |
1410 | 0 | if ((traits == UndefinedPixelTrait) || |
1411 | 0 | (despeckle_traits == UndefinedPixelTrait)) |
1412 | 0 | continue; |
1413 | 0 | if ((despeckle_traits & CopyPixelTrait) != 0) |
1414 | 0 | continue; |
1415 | 0 | (void) memset(pixels,0,length*sizeof(*pixels)); |
1416 | 0 | j=(ssize_t) image->columns+2; |
1417 | 0 | for (y=0; y < (ssize_t) image->rows; y++) |
1418 | 0 | { |
1419 | 0 | const Quantum |
1420 | 0 | *magick_restrict p; |
1421 | |
|
1422 | 0 | p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception); |
1423 | 0 | if (p == (const Quantum *) NULL) |
1424 | 0 | { |
1425 | 0 | status=MagickFalse; |
1426 | 0 | continue; |
1427 | 0 | } |
1428 | 0 | j++; |
1429 | 0 | for (x=0; x < (ssize_t) image->columns; x++) |
1430 | 0 | { |
1431 | 0 | pixels[j++]=p[i]; |
1432 | 0 | p+=(ptrdiff_t) GetPixelChannels(image); |
1433 | 0 | } |
1434 | 0 | j++; |
1435 | 0 | } |
1436 | 0 | (void) memset(buffer,0,length*sizeof(*buffer)); |
1437 | 0 | for (k=0; k < 4; k++) |
1438 | 0 | { |
1439 | 0 | Hull(image,X[k],Y[k],image->columns,image->rows,1,pixels,buffer); |
1440 | 0 | Hull(image,-X[k],-Y[k],image->columns,image->rows,1,pixels,buffer); |
1441 | 0 | Hull(image,-X[k],-Y[k],image->columns,image->rows,-1,pixels,buffer); |
1442 | 0 | Hull(image,X[k],Y[k],image->columns,image->rows,-1,pixels,buffer); |
1443 | 0 | } |
1444 | 0 | j=(ssize_t) image->columns+2; |
1445 | 0 | for (y=0; y < (ssize_t) image->rows; y++) |
1446 | 0 | { |
1447 | 0 | MagickBooleanType |
1448 | 0 | sync; |
1449 | |
|
1450 | 0 | Quantum |
1451 | 0 | *magick_restrict q; |
1452 | |
|
1453 | 0 | q=GetCacheViewAuthenticPixels(despeckle_view,0,y,despeckle_image->columns, |
1454 | 0 | 1,exception); |
1455 | 0 | if (q == (Quantum *) NULL) |
1456 | 0 | { |
1457 | 0 | status=MagickFalse; |
1458 | 0 | continue; |
1459 | 0 | } |
1460 | 0 | j++; |
1461 | 0 | for (x=0; x < (ssize_t) image->columns; x++) |
1462 | 0 | { |
1463 | 0 | SetPixelChannel(despeckle_image,channel,pixels[j++],q); |
1464 | 0 | q+=(ptrdiff_t) GetPixelChannels(despeckle_image); |
1465 | 0 | } |
1466 | 0 | sync=SyncCacheViewAuthenticPixels(despeckle_view,exception); |
1467 | 0 | if (sync == MagickFalse) |
1468 | 0 | status=MagickFalse; |
1469 | 0 | j++; |
1470 | 0 | } |
1471 | 0 | if (image->progress_monitor != (MagickProgressMonitor) NULL) |
1472 | 0 | { |
1473 | 0 | MagickBooleanType |
1474 | 0 | proceed; |
1475 | |
|
1476 | 0 | proceed=SetImageProgress(image,DespeckleImageTag,(MagickOffsetType) i, |
1477 | 0 | GetPixelChannels(image)); |
1478 | 0 | if (proceed == MagickFalse) |
1479 | 0 | status=MagickFalse; |
1480 | 0 | } |
1481 | 0 | } |
1482 | 0 | despeckle_view=DestroyCacheView(despeckle_view); |
1483 | 0 | image_view=DestroyCacheView(image_view); |
1484 | 0 | buffer_info=RelinquishVirtualMemory(buffer_info); |
1485 | 0 | pixel_info=RelinquishVirtualMemory(pixel_info); |
1486 | 0 | despeckle_image->type=image->type; |
1487 | 0 | if (status == MagickFalse) |
1488 | 0 | despeckle_image=DestroyImage(despeckle_image); |
1489 | 0 | return(despeckle_image); |
1490 | 0 | } |
1491 | | |
1492 | | /* |
1493 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
1494 | | % % |
1495 | | % % |
1496 | | % % |
1497 | | % E d g e I m a g e % |
1498 | | % % |
1499 | | % % |
1500 | | % % |
1501 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
1502 | | % |
1503 | | % EdgeImage() finds edges in an image. Radius defines the radius of the |
1504 | | % convolution filter. Use a radius of 0 and EdgeImage() selects a suitable |
1505 | | % radius for you. |
1506 | | % |
1507 | | % The format of the EdgeImage method is: |
1508 | | % |
1509 | | % Image *EdgeImage(const Image *image,const double radius, |
1510 | | % ExceptionInfo *exception) |
1511 | | % |
1512 | | % A description of each parameter follows: |
1513 | | % |
1514 | | % o image: the image. |
1515 | | % |
1516 | | % o radius: the radius of the pixel neighborhood. |
1517 | | % |
1518 | | % o exception: return any errors or warnings in this structure. |
1519 | | % |
1520 | | */ |
1521 | | MagickExport Image *EdgeImage(const Image *image,const double radius, |
1522 | | ExceptionInfo *exception) |
1523 | 0 | { |
1524 | 0 | Image |
1525 | 0 | *edge_image; |
1526 | |
|
1527 | 0 | KernelInfo |
1528 | 0 | *kernel_info; |
1529 | |
|
1530 | 0 | ssize_t |
1531 | 0 | i; |
1532 | |
|
1533 | 0 | size_t |
1534 | 0 | width; |
1535 | |
|
1536 | 0 | assert(image != (const Image *) NULL); |
1537 | 0 | assert(image->signature == MagickCoreSignature); |
1538 | 0 | assert(exception != (ExceptionInfo *) NULL); |
1539 | 0 | assert(exception->signature == MagickCoreSignature); |
1540 | 0 | if (IsEventLogging() != MagickFalse) |
1541 | 0 | (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename); |
1542 | 0 | width=GetOptimalKernelWidth1D(radius,0.5); |
1543 | 0 | kernel_info=AcquireKernelInfo((const char *) NULL,exception); |
1544 | 0 | if (kernel_info == (KernelInfo *) NULL) |
1545 | 0 | ThrowImageException(ResourceLimitError,"MemoryAllocationFailed"); |
1546 | 0 | (void) memset(kernel_info,0,sizeof(*kernel_info)); |
1547 | 0 | kernel_info->width=width; |
1548 | 0 | kernel_info->height=width; |
1549 | 0 | kernel_info->x=(ssize_t) (kernel_info->width-1)/2; |
1550 | 0 | kernel_info->y=(ssize_t) (kernel_info->height-1)/2; |
1551 | 0 | kernel_info->signature=MagickCoreSignature; |
1552 | 0 | kernel_info->values=(MagickRealType *) MagickAssumeAligned( |
1553 | 0 | AcquireAlignedMemory(kernel_info->width,kernel_info->height* |
1554 | 0 | sizeof(*kernel_info->values))); |
1555 | 0 | if (kernel_info->values == (MagickRealType *) NULL) |
1556 | 0 | { |
1557 | 0 | kernel_info=DestroyKernelInfo(kernel_info); |
1558 | 0 | ThrowImageException(ResourceLimitError,"MemoryAllocationFailed"); |
1559 | 0 | } |
1560 | 0 | for (i=0; i < (ssize_t) (kernel_info->width*kernel_info->height); i++) |
1561 | 0 | kernel_info->values[i]=(-1.0); |
1562 | 0 | kernel_info->values[i/2]=(double) kernel_info->width*kernel_info->height-1.0; |
1563 | 0 | edge_image=ConvolveImage(image,kernel_info,exception); |
1564 | 0 | kernel_info=DestroyKernelInfo(kernel_info); |
1565 | 0 | return(edge_image); |
1566 | 0 | } |
1567 | | |
1568 | | /* |
1569 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
1570 | | % % |
1571 | | % % |
1572 | | % % |
1573 | | % E m b o s s I m a g e % |
1574 | | % % |
1575 | | % % |
1576 | | % % |
1577 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
1578 | | % |
1579 | | % EmbossImage() returns a grayscale image with a three-dimensional effect. |
1580 | | % We convolve the image with a Gaussian operator of the given radius and |
1581 | | % standard deviation (sigma). For reasonable results, radius should be |
1582 | | % larger than sigma. Use a radius of 0 and Emboss() selects a suitable |
1583 | | % radius for you. |
1584 | | % |
1585 | | % The format of the EmbossImage method is: |
1586 | | % |
1587 | | % Image *EmbossImage(const Image *image,const double radius, |
1588 | | % const double sigma,ExceptionInfo *exception) |
1589 | | % |
1590 | | % A description of each parameter follows: |
1591 | | % |
1592 | | % o image: the image. |
1593 | | % |
1594 | | % o radius: the radius of the pixel neighborhood. |
1595 | | % |
1596 | | % o sigma: the standard deviation of the Gaussian, in pixels. |
1597 | | % |
1598 | | % o exception: return any errors or warnings in this structure. |
1599 | | % |
1600 | | */ |
1601 | | MagickExport Image *EmbossImage(const Image *image,const double radius, |
1602 | | const double sigma,ExceptionInfo *exception) |
1603 | 0 | { |
1604 | 0 | double |
1605 | 0 | gamma, |
1606 | 0 | normalize; |
1607 | |
|
1608 | 0 | Image |
1609 | 0 | *emboss_image; |
1610 | |
|
1611 | 0 | KernelInfo |
1612 | 0 | *kernel_info; |
1613 | |
|
1614 | 0 | ssize_t |
1615 | 0 | i; |
1616 | |
|
1617 | 0 | size_t |
1618 | 0 | width; |
1619 | |
|
1620 | 0 | ssize_t |
1621 | 0 | j, |
1622 | 0 | k, |
1623 | 0 | u, |
1624 | 0 | v; |
1625 | |
|
1626 | 0 | assert(image != (const Image *) NULL); |
1627 | 0 | assert(image->signature == MagickCoreSignature); |
1628 | 0 | assert(exception != (ExceptionInfo *) NULL); |
1629 | 0 | assert(exception->signature == MagickCoreSignature); |
1630 | 0 | if (IsEventLogging() != MagickFalse) |
1631 | 0 | (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename); |
1632 | 0 | width=GetOptimalKernelWidth1D(radius,sigma); |
1633 | 0 | kernel_info=AcquireKernelInfo((const char *) NULL,exception); |
1634 | 0 | if (kernel_info == (KernelInfo *) NULL) |
1635 | 0 | ThrowImageException(ResourceLimitError,"MemoryAllocationFailed"); |
1636 | 0 | kernel_info->width=width; |
1637 | 0 | kernel_info->height=width; |
1638 | 0 | kernel_info->x=(ssize_t) (width-1)/2; |
1639 | 0 | kernel_info->y=(ssize_t) (width-1)/2; |
1640 | 0 | kernel_info->values=(MagickRealType *) MagickAssumeAligned( |
1641 | 0 | AcquireAlignedMemory(kernel_info->width,kernel_info->width* |
1642 | 0 | sizeof(*kernel_info->values))); |
1643 | 0 | if (kernel_info->values == (MagickRealType *) NULL) |
1644 | 0 | { |
1645 | 0 | kernel_info=DestroyKernelInfo(kernel_info); |
1646 | 0 | ThrowImageException(ResourceLimitError,"MemoryAllocationFailed"); |
1647 | 0 | } |
1648 | 0 | j=(ssize_t) (kernel_info->width-1)/2; |
1649 | 0 | k=j; |
1650 | 0 | i=0; |
1651 | 0 | for (v=(-j); v <= j; v++) |
1652 | 0 | { |
1653 | 0 | for (u=(-j); u <= j; u++) |
1654 | 0 | { |
1655 | 0 | kernel_info->values[i]=(MagickRealType) (((u < 0) || (v < 0) ? -8.0 : |
1656 | 0 | 8.0)*exp(-((double) u*u+v*v)/(2.0*MagickSigma*MagickSigma))/ |
1657 | 0 | (2.0*MagickPI*MagickSigma*MagickSigma)); |
1658 | 0 | if (u != k) |
1659 | 0 | kernel_info->values[i]=0.0; |
1660 | 0 | i++; |
1661 | 0 | } |
1662 | 0 | k--; |
1663 | 0 | } |
1664 | 0 | normalize=0.0; |
1665 | 0 | for (i=0; i < (ssize_t) (kernel_info->width*kernel_info->height); i++) |
1666 | 0 | normalize+=kernel_info->values[i]; |
1667 | 0 | gamma=MagickSafeReciprocal(normalize); |
1668 | 0 | for (i=0; i < (ssize_t) (kernel_info->width*kernel_info->height); i++) |
1669 | 0 | kernel_info->values[i]*=gamma; |
1670 | 0 | emboss_image=ConvolveImage(image,kernel_info,exception); |
1671 | 0 | kernel_info=DestroyKernelInfo(kernel_info); |
1672 | 0 | if (emboss_image != (Image *) NULL) |
1673 | 0 | (void) EqualizeImage(emboss_image,exception); |
1674 | 0 | return(emboss_image); |
1675 | 0 | } |
1676 | | |
1677 | | /* |
1678 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
1679 | | % % |
1680 | | % % |
1681 | | % % |
1682 | | % G a u s s i a n B l u r I m a g e % |
1683 | | % % |
1684 | | % % |
1685 | | % % |
1686 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
1687 | | % |
1688 | | % GaussianBlurImage() blurs an image. We convolve the image with a |
1689 | | % Gaussian operator of the given radius and standard deviation (sigma). |
1690 | | % For reasonable results, the radius should be larger than sigma. Use a |
1691 | | % radius of 0 and GaussianBlurImage() selects a suitable radius for you. |
1692 | | % |
1693 | | % The format of the GaussianBlurImage method is: |
1694 | | % |
1695 | | % Image *GaussianBlurImage(const Image *image,const double radius, |
1696 | | % const double sigma,ExceptionInfo *exception) |
1697 | | % |
1698 | | % A description of each parameter follows: |
1699 | | % |
1700 | | % o image: the image. |
1701 | | % |
1702 | | % o radius: the radius of the Gaussian, in pixels, not counting the center |
1703 | | % pixel. |
1704 | | % |
1705 | | % o sigma: the standard deviation of the Gaussian, in pixels. |
1706 | | % |
1707 | | % o exception: return any errors or warnings in this structure. |
1708 | | % |
1709 | | */ |
1710 | | MagickExport Image *GaussianBlurImage(const Image *image,const double radius, |
1711 | | const double sigma,ExceptionInfo *exception) |
1712 | 0 | { |
1713 | 0 | char |
1714 | 0 | geometry[MagickPathExtent]; |
1715 | |
|
1716 | 0 | KernelInfo |
1717 | 0 | *kernel_info; |
1718 | |
|
1719 | 0 | Image |
1720 | 0 | *blur_image; |
1721 | |
|
1722 | 0 | assert(image != (const Image *) NULL); |
1723 | 0 | assert(image->signature == MagickCoreSignature); |
1724 | 0 | assert(exception != (ExceptionInfo *) NULL); |
1725 | 0 | assert(exception->signature == MagickCoreSignature); |
1726 | 0 | if (IsEventLogging() != MagickFalse) |
1727 | 0 | (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename); |
1728 | 0 | (void) FormatLocaleString(geometry,MagickPathExtent,"gaussian:%.20gx%.20g", |
1729 | 0 | radius,sigma); |
1730 | 0 | kernel_info=AcquireKernelInfo(geometry,exception); |
1731 | 0 | if (kernel_info == (KernelInfo *) NULL) |
1732 | 0 | ThrowImageException(ResourceLimitError,"MemoryAllocationFailed"); |
1733 | 0 | blur_image=ConvolveImage(image,kernel_info,exception); |
1734 | 0 | kernel_info=DestroyKernelInfo(kernel_info); |
1735 | 0 | return(blur_image); |
1736 | 0 | } |
1737 | | |
1738 | | /* |
1739 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
1740 | | % % |
1741 | | % % |
1742 | | % % |
1743 | | % K u w a h a r a I m a g e % |
1744 | | % % |
1745 | | % % |
1746 | | % % |
1747 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
1748 | | % |
1749 | | % KuwaharaImage() is an edge preserving noise reduction filter. |
1750 | | % |
1751 | | % The format of the KuwaharaImage method is: |
1752 | | % |
1753 | | % Image *KuwaharaImage(const Image *image,const double radius, |
1754 | | % const double sigma,ExceptionInfo *exception) |
1755 | | % |
1756 | | % A description of each parameter follows: |
1757 | | % |
1758 | | % o image: the image. |
1759 | | % |
1760 | | % o radius: the square window radius. |
1761 | | % |
1762 | | % o sigma: the standard deviation of the Gaussian, in pixels. |
1763 | | % |
1764 | | % o exception: return any errors or warnings in this structure. |
1765 | | % |
1766 | | */ |
1767 | | |
1768 | | static inline MagickRealType GetMeanLuma(const Image *magick_restrict image, |
1769 | | const double *magick_restrict pixel) |
1770 | 0 | { |
1771 | 0 | return(0.212656*pixel[image->channel_map[RedPixelChannel].offset]+ |
1772 | 0 | 0.715158*pixel[image->channel_map[GreenPixelChannel].offset]+ |
1773 | 0 | 0.072186*pixel[image->channel_map[BluePixelChannel].offset]); /* Rec709 */ |
1774 | 0 | } |
1775 | | |
1776 | | MagickExport Image *KuwaharaImage(const Image *image,const double radius, |
1777 | | const double sigma,ExceptionInfo *exception) |
1778 | 0 | { |
1779 | 0 | #define KuwaharaImageTag "Kuwahara/Image" |
1780 | |
|
1781 | 0 | CacheView |
1782 | 0 | *image_view, |
1783 | 0 | *kuwahara_view; |
1784 | |
|
1785 | 0 | Image |
1786 | 0 | *gaussian_image, |
1787 | 0 | *kuwahara_image; |
1788 | |
|
1789 | 0 | MagickBooleanType |
1790 | 0 | status; |
1791 | |
|
1792 | 0 | MagickOffsetType |
1793 | 0 | progress; |
1794 | |
|
1795 | 0 | size_t |
1796 | 0 | width; |
1797 | |
|
1798 | 0 | ssize_t |
1799 | 0 | y; |
1800 | | |
1801 | | /* |
1802 | | Initialize Kuwahara image attributes. |
1803 | | */ |
1804 | 0 | assert(image != (Image *) NULL); |
1805 | 0 | assert(image->signature == MagickCoreSignature); |
1806 | 0 | assert(exception != (ExceptionInfo *) NULL); |
1807 | 0 | assert(exception->signature == MagickCoreSignature); |
1808 | 0 | if (IsEventLogging() != MagickFalse) |
1809 | 0 | (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename); |
1810 | 0 | width=(size_t) radius+1; |
1811 | 0 | gaussian_image=BlurImage(image,radius,sigma,exception); |
1812 | 0 | if (gaussian_image == (Image *) NULL) |
1813 | 0 | return((Image *) NULL); |
1814 | 0 | kuwahara_image=CloneImage(image,0,0,MagickTrue,exception); |
1815 | 0 | if (kuwahara_image == (Image *) NULL) |
1816 | 0 | { |
1817 | 0 | gaussian_image=DestroyImage(gaussian_image); |
1818 | 0 | return((Image *) NULL); |
1819 | 0 | } |
1820 | 0 | if (SetImageStorageClass(kuwahara_image,DirectClass,exception) == MagickFalse) |
1821 | 0 | { |
1822 | 0 | gaussian_image=DestroyImage(gaussian_image); |
1823 | 0 | kuwahara_image=DestroyImage(kuwahara_image); |
1824 | 0 | return((Image *) NULL); |
1825 | 0 | } |
1826 | | /* |
1827 | | Edge preserving noise reduction filter. |
1828 | | */ |
1829 | 0 | status=MagickTrue; |
1830 | 0 | progress=0; |
1831 | 0 | image_view=AcquireVirtualCacheView(gaussian_image,exception); |
1832 | 0 | kuwahara_view=AcquireAuthenticCacheView(kuwahara_image,exception); |
1833 | | #if defined(MAGICKCORE_OPENMP_SUPPORT) |
1834 | | #pragma omp parallel for schedule(static) shared(progress,status) \ |
1835 | | magick_number_threads(image,kuwahara_image,gaussian_image->rows,1) |
1836 | | #endif |
1837 | 0 | for (y=0; y < (ssize_t) gaussian_image->rows; y++) |
1838 | 0 | { |
1839 | 0 | Quantum |
1840 | 0 | *magick_restrict q; |
1841 | |
|
1842 | 0 | ssize_t |
1843 | 0 | x; |
1844 | |
|
1845 | 0 | if (status == MagickFalse) |
1846 | 0 | continue; |
1847 | 0 | q=QueueCacheViewAuthenticPixels(kuwahara_view,0,y,kuwahara_image->columns,1, |
1848 | 0 | exception); |
1849 | 0 | if (q == (Quantum *) NULL) |
1850 | 0 | { |
1851 | 0 | status=MagickFalse; |
1852 | 0 | continue; |
1853 | 0 | } |
1854 | 0 | for (x=0; x < (ssize_t) gaussian_image->columns; x++) |
1855 | 0 | { |
1856 | 0 | const Quantum |
1857 | 0 | *magick_restrict p; |
1858 | |
|
1859 | 0 | double |
1860 | 0 | min_variance; |
1861 | |
|
1862 | 0 | RectangleInfo |
1863 | 0 | quadrant, |
1864 | 0 | target; |
1865 | |
|
1866 | 0 | size_t |
1867 | 0 | i; |
1868 | |
|
1869 | 0 | min_variance=MagickMaximumValue; |
1870 | 0 | SetGeometry(gaussian_image,&target); |
1871 | 0 | quadrant.width=width; |
1872 | 0 | quadrant.height=width; |
1873 | 0 | for (i=0; i < 4; i++) |
1874 | 0 | { |
1875 | 0 | const Quantum |
1876 | 0 | *magick_restrict k; |
1877 | |
|
1878 | 0 | double |
1879 | 0 | mean[MaxPixelChannels], |
1880 | 0 | variance; |
1881 | |
|
1882 | 0 | ssize_t |
1883 | 0 | n; |
1884 | |
|
1885 | 0 | ssize_t |
1886 | 0 | j; |
1887 | |
|
1888 | 0 | quadrant.x=x; |
1889 | 0 | quadrant.y=y; |
1890 | 0 | switch (i) |
1891 | 0 | { |
1892 | 0 | case 0: |
1893 | 0 | { |
1894 | 0 | quadrant.x=x-(ssize_t) (width-1); |
1895 | 0 | quadrant.y=y-(ssize_t) (width-1); |
1896 | 0 | break; |
1897 | 0 | } |
1898 | 0 | case 1: |
1899 | 0 | { |
1900 | 0 | quadrant.y=y-(ssize_t) (width-1); |
1901 | 0 | break; |
1902 | 0 | } |
1903 | 0 | case 2: |
1904 | 0 | { |
1905 | 0 | quadrant.x=x-(ssize_t) (width-1); |
1906 | 0 | break; |
1907 | 0 | } |
1908 | 0 | case 3: |
1909 | 0 | default: |
1910 | 0 | break; |
1911 | 0 | } |
1912 | 0 | p=GetCacheViewVirtualPixels(image_view,quadrant.x,quadrant.y, |
1913 | 0 | quadrant.width,quadrant.height,exception); |
1914 | 0 | if (p == (const Quantum *) NULL) |
1915 | 0 | break; |
1916 | 0 | for (j=0; j < (ssize_t) GetPixelChannels(gaussian_image); j++) |
1917 | 0 | mean[j]=0.0; |
1918 | 0 | k=p; |
1919 | 0 | for (n=0; n < (ssize_t) (width*width); n++) |
1920 | 0 | { |
1921 | 0 | for (j=0; j < (ssize_t) GetPixelChannels(gaussian_image); j++) |
1922 | 0 | mean[j]+=(double) k[j]; |
1923 | 0 | k+=(ptrdiff_t) GetPixelChannels(gaussian_image); |
1924 | 0 | } |
1925 | 0 | for (j=0; j < (ssize_t) GetPixelChannels(gaussian_image); j++) |
1926 | 0 | mean[j]/=(double) (width*width); |
1927 | 0 | k=p; |
1928 | 0 | variance=0.0; |
1929 | 0 | for (n=0; n < (ssize_t) (width*width); n++) |
1930 | 0 | { |
1931 | 0 | double |
1932 | 0 | luma; |
1933 | |
|
1934 | 0 | luma=GetPixelLuma(gaussian_image,k); |
1935 | 0 | variance+=(luma-GetMeanLuma(gaussian_image,mean))* |
1936 | 0 | (luma-GetMeanLuma(gaussian_image,mean)); |
1937 | 0 | k+=(ptrdiff_t) GetPixelChannels(gaussian_image); |
1938 | 0 | } |
1939 | 0 | if (variance < min_variance) |
1940 | 0 | { |
1941 | 0 | min_variance=variance; |
1942 | 0 | target=quadrant; |
1943 | 0 | } |
1944 | 0 | } |
1945 | 0 | if (i < 4) |
1946 | 0 | { |
1947 | 0 | status=MagickFalse; |
1948 | 0 | break; |
1949 | 0 | } |
1950 | 0 | status=InterpolatePixelChannels(gaussian_image,image_view,kuwahara_image, |
1951 | 0 | UndefinedInterpolatePixel,(double) target.x+target.width/2.0,(double) |
1952 | 0 | target.y+target.height/2.0,q,exception); |
1953 | 0 | if (status == MagickFalse) |
1954 | 0 | break; |
1955 | 0 | q+=(ptrdiff_t) GetPixelChannels(kuwahara_image); |
1956 | 0 | } |
1957 | 0 | if (SyncCacheViewAuthenticPixels(kuwahara_view,exception) == MagickFalse) |
1958 | 0 | status=MagickFalse; |
1959 | 0 | if (image->progress_monitor != (MagickProgressMonitor) NULL) |
1960 | 0 | { |
1961 | 0 | MagickBooleanType |
1962 | 0 | proceed; |
1963 | |
|
1964 | | #if defined(MAGICKCORE_OPENMP_SUPPORT) |
1965 | | #pragma omp atomic |
1966 | | #endif |
1967 | 0 | progress++; |
1968 | 0 | proceed=SetImageProgress(image,KuwaharaImageTag,progress,image->rows); |
1969 | 0 | if (proceed == MagickFalse) |
1970 | 0 | status=MagickFalse; |
1971 | 0 | } |
1972 | 0 | } |
1973 | 0 | kuwahara_view=DestroyCacheView(kuwahara_view); |
1974 | 0 | image_view=DestroyCacheView(image_view); |
1975 | 0 | gaussian_image=DestroyImage(gaussian_image); |
1976 | 0 | if (status == MagickFalse) |
1977 | 0 | kuwahara_image=DestroyImage(kuwahara_image); |
1978 | 0 | return(kuwahara_image); |
1979 | 0 | } |
1980 | | |
1981 | | /* |
1982 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
1983 | | % % |
1984 | | % % |
1985 | | % % |
1986 | | % L o c a l C o n t r a s t I m a g e % |
1987 | | % % |
1988 | | % % |
1989 | | % % |
1990 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
1991 | | % |
1992 | | % LocalContrastImage() attempts to increase the appearance of large-scale |
1993 | | % light-dark transitions. Local contrast enhancement works similarly to |
1994 | | % sharpening with an unsharp mask, however the mask is instead created using |
1995 | | % an image with a greater blur distance. |
1996 | | % |
1997 | | % The format of the LocalContrastImage method is: |
1998 | | % |
1999 | | % Image *LocalContrastImage(const Image *image, const double radius, |
2000 | | % const double strength,ExceptionInfo *exception) |
2001 | | % |
2002 | | % A description of each parameter follows: |
2003 | | % |
2004 | | % o image: the image. |
2005 | | % |
2006 | | % o radius: the radius of the Gaussian blur, in percentage with 100% |
2007 | | % resulting in a blur radius of 20% of largest dimension. |
2008 | | % |
2009 | | % o strength: the strength of the blur mask in percentage. |
2010 | | % |
2011 | | % o exception: return any errors or warnings in this structure. |
2012 | | % |
2013 | | */ |
2014 | | MagickExport Image *LocalContrastImage(const Image *image,const double radius, |
2015 | | const double strength,ExceptionInfo *exception) |
2016 | 0 | { |
2017 | 0 | #define LocalContrastImageTag "LocalContrast/Image" |
2018 | |
|
2019 | 0 | CacheView |
2020 | 0 | *image_view, |
2021 | 0 | *contrast_view; |
2022 | |
|
2023 | 0 | double |
2024 | 0 | totalWeight; |
2025 | |
|
2026 | 0 | float |
2027 | 0 | *interImage, |
2028 | 0 | *scanline; |
2029 | |
|
2030 | 0 | Image |
2031 | 0 | *contrast_image; |
2032 | |
|
2033 | 0 | MagickBooleanType |
2034 | 0 | status; |
2035 | |
|
2036 | 0 | MemoryInfo |
2037 | 0 | *scanline_info, |
2038 | 0 | *interImage_info; |
2039 | |
|
2040 | 0 | ssize_t |
2041 | 0 | scanLineSize, |
2042 | 0 | width; |
2043 | | |
2044 | | /* |
2045 | | Initialize contrast image attributes. |
2046 | | */ |
2047 | 0 | assert(image != (const Image *) NULL); |
2048 | 0 | assert(image->signature == MagickCoreSignature); |
2049 | 0 | assert(exception != (ExceptionInfo *) NULL); |
2050 | 0 | assert(exception->signature == MagickCoreSignature); |
2051 | 0 | if (IsEventLogging() != MagickFalse) |
2052 | 0 | (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename); |
2053 | | #if defined(MAGICKCORE_OPENCL_SUPPORT) |
2054 | | contrast_image=AccelerateLocalContrastImage(image,radius,strength,exception); |
2055 | | if (contrast_image != (Image *) NULL) |
2056 | | return(contrast_image); |
2057 | | #endif |
2058 | 0 | contrast_image=CloneImage(image,0,0,MagickTrue,exception); |
2059 | 0 | if (contrast_image == (Image *) NULL) |
2060 | 0 | return((Image *) NULL); |
2061 | 0 | if (SetImageStorageClass(contrast_image,DirectClass,exception) == MagickFalse) |
2062 | 0 | { |
2063 | 0 | contrast_image=DestroyImage(contrast_image); |
2064 | 0 | return((Image *) NULL); |
2065 | 0 | } |
2066 | 0 | image_view=AcquireVirtualCacheView(image,exception); |
2067 | 0 | contrast_view=AcquireAuthenticCacheView(contrast_image,exception); |
2068 | 0 | scanLineSize=(ssize_t) MagickMax(image->columns,image->rows); |
2069 | 0 | width=(ssize_t) (scanLineSize*0.002*fabs(radius)); |
2070 | 0 | scanLineSize+=(2*width); |
2071 | 0 | scanline_info=AcquireVirtualMemory(GetOpenMPMaximumThreads()* |
2072 | 0 | (size_t) scanLineSize,sizeof(*scanline)); |
2073 | 0 | if (scanline_info == (MemoryInfo *) NULL) |
2074 | 0 | { |
2075 | 0 | contrast_view=DestroyCacheView(contrast_view); |
2076 | 0 | image_view=DestroyCacheView(image_view); |
2077 | 0 | contrast_image=DestroyImage(contrast_image); |
2078 | 0 | ThrowImageException(ResourceLimitError,"MemoryAllocationFailed"); |
2079 | 0 | } |
2080 | 0 | scanline=(float *) GetVirtualMemoryBlob(scanline_info); |
2081 | | /* |
2082 | | Create intermediate buffer. |
2083 | | */ |
2084 | 0 | interImage_info=AcquireVirtualMemory(image->rows*(image->columns+(size_t) |
2085 | 0 | (2*width)),sizeof(*interImage)); |
2086 | 0 | if (interImage_info == (MemoryInfo *) NULL) |
2087 | 0 | { |
2088 | 0 | scanline_info=RelinquishVirtualMemory(scanline_info); |
2089 | 0 | contrast_view=DestroyCacheView(contrast_view); |
2090 | 0 | image_view=DestroyCacheView(image_view); |
2091 | 0 | contrast_image=DestroyImage(contrast_image); |
2092 | 0 | ThrowImageException(ResourceLimitError,"MemoryAllocationFailed"); |
2093 | 0 | } |
2094 | 0 | interImage=(float *) GetVirtualMemoryBlob(interImage_info); |
2095 | 0 | totalWeight=(float) ((width+1)*(width+1)); |
2096 | | /* |
2097 | | Vertical pass. |
2098 | | */ |
2099 | 0 | status=MagickTrue; |
2100 | 0 | { |
2101 | 0 | ssize_t |
2102 | 0 | x; |
2103 | |
|
2104 | | #if defined(MAGICKCORE_OPENMP_SUPPORT) |
2105 | | #pragma omp parallel for schedule(static) \ |
2106 | | magick_number_threads(image,image,image->columns,1) |
2107 | | #endif |
2108 | 0 | for (x=0; x < (ssize_t) image->columns; x++) |
2109 | 0 | { |
2110 | 0 | const int |
2111 | 0 | id = GetOpenMPThreadId(); |
2112 | |
|
2113 | 0 | const Quantum |
2114 | 0 | *magick_restrict p; |
2115 | |
|
2116 | 0 | float |
2117 | 0 | *out, |
2118 | 0 | *pix, |
2119 | 0 | *pixels; |
2120 | |
|
2121 | 0 | ssize_t |
2122 | 0 | y; |
2123 | |
|
2124 | 0 | ssize_t |
2125 | 0 | i; |
2126 | |
|
2127 | 0 | if (status == MagickFalse) |
2128 | 0 | continue; |
2129 | 0 | pixels=scanline; |
2130 | 0 | pixels+=id*scanLineSize; |
2131 | 0 | pix=pixels; |
2132 | 0 | p=GetCacheViewVirtualPixels(image_view,x,-(ssize_t) width,1, |
2133 | 0 | image->rows+(size_t) (2*width),exception); |
2134 | 0 | if (p == (const Quantum *) NULL) |
2135 | 0 | { |
2136 | 0 | status=MagickFalse; |
2137 | 0 | continue; |
2138 | 0 | } |
2139 | 0 | for (y=0; y < (ssize_t) image->rows+(2*width); y++) |
2140 | 0 | { |
2141 | 0 | *pix++=(float)GetPixelLuma(image,p); |
2142 | 0 | p+=(ptrdiff_t) image->number_channels; |
2143 | 0 | } |
2144 | 0 | out=interImage+x+width; |
2145 | 0 | for (y=0; y < (ssize_t) image->rows; y++) |
2146 | 0 | { |
2147 | 0 | double |
2148 | 0 | sum, |
2149 | 0 | weight; |
2150 | |
|
2151 | 0 | weight=1.0; |
2152 | 0 | sum=0; |
2153 | 0 | pix=pixels+y; |
2154 | 0 | for (i=0; i < width; i++) |
2155 | 0 | { |
2156 | 0 | sum+=weight*((double) *pix++); |
2157 | 0 | weight+=1.0; |
2158 | 0 | } |
2159 | 0 | for (i=width+1; i < (2*width); i++) |
2160 | 0 | { |
2161 | 0 | sum+=weight*((double) *pix++); |
2162 | 0 | weight-=1.0; |
2163 | 0 | } |
2164 | | /* write to output */ |
2165 | 0 | *out=(float) (sum/totalWeight); |
2166 | | /* mirror into padding */ |
2167 | 0 | if ((x <= width) && (x != 0)) |
2168 | 0 | *(out-(x*2))=*out; |
2169 | 0 | if ((x > (ssize_t) image->columns-width-2) && |
2170 | 0 | (x != (ssize_t) image->columns-1)) |
2171 | 0 | *(out+((image->columns-(size_t) x-1)*2))=*out; |
2172 | 0 | out+=image->columns+(size_t) (width*2); |
2173 | 0 | } |
2174 | 0 | } |
2175 | 0 | } |
2176 | | /* |
2177 | | Horizontal pass. |
2178 | | */ |
2179 | 0 | { |
2180 | 0 | ssize_t |
2181 | 0 | y; |
2182 | |
|
2183 | | #if defined(MAGICKCORE_OPENMP_SUPPORT) |
2184 | | #pragma omp parallel for schedule(static) \ |
2185 | | magick_number_threads(image,image,image->rows,1) |
2186 | | #endif |
2187 | 0 | for (y=0; y < (ssize_t) image->rows; y++) |
2188 | 0 | { |
2189 | 0 | const int |
2190 | 0 | id = GetOpenMPThreadId(); |
2191 | |
|
2192 | 0 | const Quantum |
2193 | 0 | *magick_restrict p; |
2194 | |
|
2195 | 0 | float |
2196 | 0 | *pix, |
2197 | 0 | *pixels; |
2198 | |
|
2199 | 0 | Quantum |
2200 | 0 | *magick_restrict q; |
2201 | |
|
2202 | 0 | ssize_t |
2203 | 0 | i, |
2204 | 0 | x; |
2205 | |
|
2206 | 0 | if (status == MagickFalse) |
2207 | 0 | continue; |
2208 | 0 | pixels=scanline; |
2209 | 0 | pixels+=id*scanLineSize; |
2210 | 0 | p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception); |
2211 | 0 | q=GetCacheViewAuthenticPixels(contrast_view,0,y,image->columns,1, |
2212 | 0 | exception); |
2213 | 0 | if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL)) |
2214 | 0 | { |
2215 | 0 | status=MagickFalse; |
2216 | 0 | continue; |
2217 | 0 | } |
2218 | 0 | memcpy(pixels,interImage+((size_t) y*(image->columns+(size_t) (2*width))), |
2219 | 0 | (image->columns+(size_t) (2*width))*sizeof(float)); |
2220 | 0 | for (x=0; x < (ssize_t) image->columns; x++) |
2221 | 0 | { |
2222 | 0 | double |
2223 | 0 | mult, |
2224 | 0 | srcVal, |
2225 | 0 | sum, |
2226 | 0 | weight; |
2227 | |
|
2228 | 0 | PixelTrait |
2229 | 0 | traits; |
2230 | |
|
2231 | 0 | weight=1.0; |
2232 | 0 | sum=0; |
2233 | 0 | pix=pixels+x; |
2234 | 0 | for (i=0; i < width; i++) |
2235 | 0 | { |
2236 | 0 | sum+=weight*((double) *pix++); |
2237 | 0 | weight+=1.0; |
2238 | 0 | } |
2239 | 0 | for (i=width+1; i < (2*width); i++) |
2240 | 0 | { |
2241 | 0 | sum+=weight*((double) *pix++); |
2242 | 0 | weight-=1.0; |
2243 | 0 | } |
2244 | | /* |
2245 | | Apply and write. |
2246 | | */ |
2247 | 0 | srcVal=(float) GetPixelLuma(image,p); |
2248 | 0 | mult=(srcVal-(sum/totalWeight))*(strength/100.0); |
2249 | 0 | mult=(srcVal+mult)/srcVal; |
2250 | 0 | traits=GetPixelChannelTraits(image,RedPixelChannel); |
2251 | 0 | if ((traits & UpdatePixelTrait) != 0) |
2252 | 0 | SetPixelRed(contrast_image,ClampToQuantum((MagickRealType) |
2253 | 0 | GetPixelRed(image,p)*mult),q); |
2254 | 0 | traits=GetPixelChannelTraits(image,GreenPixelChannel); |
2255 | 0 | if ((traits & UpdatePixelTrait) != 0) |
2256 | 0 | SetPixelGreen(contrast_image,ClampToQuantum((MagickRealType) |
2257 | 0 | GetPixelGreen(image,p)*mult),q); |
2258 | 0 | traits=GetPixelChannelTraits(image,BluePixelChannel); |
2259 | 0 | if ((traits & UpdatePixelTrait) != 0) |
2260 | 0 | SetPixelBlue(contrast_image,ClampToQuantum((MagickRealType) |
2261 | 0 | GetPixelBlue(image,p)*mult),q); |
2262 | 0 | p+=(ptrdiff_t) image->number_channels; |
2263 | 0 | q+=(ptrdiff_t) contrast_image->number_channels; |
2264 | 0 | } |
2265 | 0 | if (SyncCacheViewAuthenticPixels(contrast_view,exception) == MagickFalse) |
2266 | 0 | status=MagickFalse; |
2267 | 0 | } |
2268 | 0 | } |
2269 | 0 | scanline_info=RelinquishVirtualMemory(scanline_info); |
2270 | 0 | interImage_info=RelinquishVirtualMemory(interImage_info); |
2271 | 0 | contrast_view=DestroyCacheView(contrast_view); |
2272 | 0 | image_view=DestroyCacheView(image_view); |
2273 | 0 | if (status == MagickFalse) |
2274 | 0 | contrast_image=DestroyImage(contrast_image); |
2275 | 0 | return(contrast_image); |
2276 | 0 | } |
2277 | | |
2278 | | /* |
2279 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
2280 | | % % |
2281 | | % % |
2282 | | % % |
2283 | | % M o t i o n B l u r I m a g e % |
2284 | | % % |
2285 | | % % |
2286 | | % % |
2287 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
2288 | | % |
2289 | | % MotionBlurImage() simulates motion blur. We convolve the image with a |
2290 | | % Gaussian operator of the given radius and standard deviation (sigma). |
2291 | | % For reasonable results, radius should be larger than sigma. Use a |
2292 | | % radius of 0 and MotionBlurImage() selects a suitable radius for you. |
2293 | | % Angle gives the angle of the blurring motion. |
2294 | | % |
2295 | | % Andrew Protano contributed this effect. |
2296 | | % |
2297 | | % The format of the MotionBlurImage method is: |
2298 | | % |
2299 | | % Image *MotionBlurImage(const Image *image,const double radius, |
2300 | | % const double sigma,const double angle,ExceptionInfo *exception) |
2301 | | % |
2302 | | % A description of each parameter follows: |
2303 | | % |
2304 | | % o image: the image. |
2305 | | % |
2306 | | % o radius: the radius of the Gaussian, in pixels, not counting |
2307 | | % the center pixel. |
2308 | | % |
2309 | | % o sigma: the standard deviation of the Gaussian, in pixels. |
2310 | | % |
2311 | | % o angle: Apply the effect along this angle. |
2312 | | % |
2313 | | % o exception: return any errors or warnings in this structure. |
2314 | | % |
2315 | | */ |
2316 | | |
2317 | | static MagickRealType *GetMotionBlurKernel(const size_t width, |
2318 | | const double sigma) |
2319 | 0 | { |
2320 | 0 | MagickRealType |
2321 | 0 | *kernel, |
2322 | 0 | normalize; |
2323 | |
|
2324 | 0 | ssize_t |
2325 | 0 | i; |
2326 | | |
2327 | | /* |
2328 | | Generate a 1-D convolution kernel. |
2329 | | */ |
2330 | 0 | if (IsEventLogging() != MagickFalse) |
2331 | 0 | (void) LogMagickEvent(TraceEvent,GetMagickModule(),"..."); |
2332 | 0 | kernel=(MagickRealType *) MagickAssumeAligned(AcquireAlignedMemory((size_t) |
2333 | 0 | width,sizeof(*kernel))); |
2334 | 0 | if (kernel == (MagickRealType *) NULL) |
2335 | 0 | return(kernel); |
2336 | 0 | normalize=0.0; |
2337 | 0 | for (i=0; i < (ssize_t) width; i++) |
2338 | 0 | { |
2339 | 0 | kernel[i]=(MagickRealType) (exp((-((double) i*i)/(double) (2.0*MagickSigma* |
2340 | 0 | MagickSigma)))/(MagickSQ2PI*MagickSigma)); |
2341 | 0 | normalize+=kernel[i]; |
2342 | 0 | } |
2343 | 0 | for (i=0; i < (ssize_t) width; i++) |
2344 | 0 | kernel[i]/=normalize; |
2345 | 0 | return(kernel); |
2346 | 0 | } |
2347 | | |
2348 | | MagickExport Image *MotionBlurImage(const Image *image,const double radius, |
2349 | | const double sigma,const double angle,ExceptionInfo *exception) |
2350 | 0 | { |
2351 | 0 | #define BlurImageTag "Blur/Image" |
2352 | |
|
2353 | 0 | CacheView |
2354 | 0 | *blur_view, |
2355 | 0 | *image_view, |
2356 | 0 | *motion_view; |
2357 | |
|
2358 | 0 | Image |
2359 | 0 | *blur_image; |
2360 | |
|
2361 | 0 | MagickBooleanType |
2362 | 0 | status; |
2363 | |
|
2364 | 0 | MagickOffsetType |
2365 | 0 | progress; |
2366 | |
|
2367 | 0 | MagickRealType |
2368 | 0 | *kernel; |
2369 | |
|
2370 | 0 | OffsetInfo |
2371 | 0 | *offset; |
2372 | |
|
2373 | 0 | PointInfo |
2374 | 0 | point; |
2375 | |
|
2376 | 0 | size_t |
2377 | 0 | width; |
2378 | |
|
2379 | 0 | ssize_t |
2380 | 0 | w, |
2381 | 0 | y; |
2382 | |
|
2383 | 0 | assert(image != (Image *) NULL); |
2384 | 0 | assert(image->signature == MagickCoreSignature); |
2385 | 0 | assert(exception != (ExceptionInfo *) NULL); |
2386 | 0 | assert(exception->signature == MagickCoreSignature); |
2387 | 0 | if (IsEventLogging() != MagickFalse) |
2388 | 0 | (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename); |
2389 | 0 | width=GetOptimalKernelWidth1D(radius,sigma); |
2390 | 0 | kernel=GetMotionBlurKernel(width,sigma); |
2391 | 0 | if (kernel == (MagickRealType *) NULL) |
2392 | 0 | ThrowImageException(ResourceLimitError,"MemoryAllocationFailed"); |
2393 | 0 | offset=(OffsetInfo *) AcquireQuantumMemory(width,sizeof(*offset)); |
2394 | 0 | if (offset == (OffsetInfo *) NULL) |
2395 | 0 | { |
2396 | 0 | kernel=(MagickRealType *) RelinquishAlignedMemory(kernel); |
2397 | 0 | ThrowImageException(ResourceLimitError,"MemoryAllocationFailed"); |
2398 | 0 | } |
2399 | 0 | point.x=(double) width*sin(DegreesToRadians(angle)); |
2400 | 0 | point.y=(double) width*cos(DegreesToRadians(angle)); |
2401 | 0 | for (w=0; w < (ssize_t) width; w++) |
2402 | 0 | { |
2403 | 0 | offset[w].x=CastDoubleToSsizeT(ceil((double) (w*point.y)/ |
2404 | 0 | hypot(point.x,point.y)-0.5)); |
2405 | 0 | offset[w].y=CastDoubleToSsizeT(ceil((double) (w*point.x)/ |
2406 | 0 | hypot(point.x,point.y)-0.5)); |
2407 | 0 | } |
2408 | | /* |
2409 | | Motion blur image. |
2410 | | */ |
2411 | | #if defined(MAGICKCORE_OPENCL_SUPPORT) |
2412 | | blur_image=AccelerateMotionBlurImage(image,kernel,width,offset,exception); |
2413 | | if (blur_image != (Image *) NULL) |
2414 | | { |
2415 | | kernel=(MagickRealType *) RelinquishAlignedMemory(kernel); |
2416 | | offset=(OffsetInfo *) RelinquishMagickMemory(offset); |
2417 | | return(blur_image); |
2418 | | } |
2419 | | #endif |
2420 | 0 | blur_image=CloneImage(image,0,0,MagickTrue,exception); |
2421 | 0 | if (blur_image == (Image *) NULL) |
2422 | 0 | { |
2423 | 0 | kernel=(MagickRealType *) RelinquishAlignedMemory(kernel); |
2424 | 0 | offset=(OffsetInfo *) RelinquishMagickMemory(offset); |
2425 | 0 | return((Image *) NULL); |
2426 | 0 | } |
2427 | 0 | if (SetImageStorageClass(blur_image,DirectClass,exception) == MagickFalse) |
2428 | 0 | { |
2429 | 0 | kernel=(MagickRealType *) RelinquishAlignedMemory(kernel); |
2430 | 0 | offset=(OffsetInfo *) RelinquishMagickMemory(offset); |
2431 | 0 | blur_image=DestroyImage(blur_image); |
2432 | 0 | return((Image *) NULL); |
2433 | 0 | } |
2434 | 0 | status=MagickTrue; |
2435 | 0 | progress=0; |
2436 | 0 | image_view=AcquireVirtualCacheView(image,exception); |
2437 | 0 | motion_view=AcquireVirtualCacheView(image,exception); |
2438 | 0 | blur_view=AcquireAuthenticCacheView(blur_image,exception); |
2439 | | #if defined(MAGICKCORE_OPENMP_SUPPORT) |
2440 | | #pragma omp parallel for schedule(static) shared(progress,status) \ |
2441 | | magick_number_threads(image,blur_image,image->rows,1) |
2442 | | #endif |
2443 | 0 | for (y=0; y < (ssize_t) image->rows; y++) |
2444 | 0 | { |
2445 | 0 | const Quantum |
2446 | 0 | *magick_restrict p; |
2447 | |
|
2448 | 0 | Quantum |
2449 | 0 | *magick_restrict q; |
2450 | |
|
2451 | 0 | ssize_t |
2452 | 0 | x; |
2453 | |
|
2454 | 0 | if (status == MagickFalse) |
2455 | 0 | continue; |
2456 | 0 | p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception); |
2457 | 0 | q=QueueCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1, |
2458 | 0 | exception); |
2459 | 0 | if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL)) |
2460 | 0 | { |
2461 | 0 | status=MagickFalse; |
2462 | 0 | continue; |
2463 | 0 | } |
2464 | 0 | for (x=0; x < (ssize_t) image->columns; x++) |
2465 | 0 | { |
2466 | 0 | ssize_t |
2467 | 0 | i; |
2468 | |
|
2469 | 0 | for (i=0; i < (ssize_t) GetPixelChannels(image); i++) |
2470 | 0 | { |
2471 | 0 | double |
2472 | 0 | alpha = 0.0, |
2473 | 0 | gamma = 0.0, |
2474 | 0 | pixel; |
2475 | |
|
2476 | 0 | PixelChannel |
2477 | 0 | channel; |
2478 | |
|
2479 | 0 | PixelTrait |
2480 | 0 | blur_traits, |
2481 | 0 | traits; |
2482 | |
|
2483 | 0 | const Quantum |
2484 | 0 | *magick_restrict r; |
2485 | |
|
2486 | 0 | MagickRealType |
2487 | 0 | *magick_restrict k; |
2488 | |
|
2489 | 0 | ssize_t |
2490 | 0 | j; |
2491 | |
|
2492 | 0 | channel=GetPixelChannelChannel(image,i); |
2493 | 0 | traits=GetPixelChannelTraits(image,channel); |
2494 | 0 | blur_traits=GetPixelChannelTraits(blur_image,channel); |
2495 | 0 | if ((traits == UndefinedPixelTrait) || |
2496 | 0 | (blur_traits == UndefinedPixelTrait)) |
2497 | 0 | continue; |
2498 | 0 | if ((blur_traits & CopyPixelTrait) != 0) |
2499 | 0 | { |
2500 | 0 | SetPixelChannel(blur_image,channel,p[i],q); |
2501 | 0 | continue; |
2502 | 0 | } |
2503 | 0 | k=kernel; |
2504 | 0 | pixel=0.0; |
2505 | 0 | if ((blur_traits & BlendPixelTrait) == 0) |
2506 | 0 | { |
2507 | 0 | for (j=0; j < (ssize_t) width; j++) |
2508 | 0 | { |
2509 | 0 | r=GetCacheViewVirtualPixels(motion_view,x+offset[j].x,y+ |
2510 | 0 | offset[j].y,1,1,exception); |
2511 | 0 | if (r == (const Quantum *) NULL) |
2512 | 0 | { |
2513 | 0 | status=MagickFalse; |
2514 | 0 | continue; |
2515 | 0 | } |
2516 | 0 | pixel+=(*k)*(double) r[i]; |
2517 | 0 | k++; |
2518 | 0 | } |
2519 | 0 | SetPixelChannel(blur_image,channel,ClampToQuantum(pixel),q); |
2520 | 0 | continue; |
2521 | 0 | } |
2522 | 0 | for (j=0; j < (ssize_t) width; j++) |
2523 | 0 | { |
2524 | 0 | r=GetCacheViewVirtualPixels(motion_view,x+offset[j].x,y+offset[j].y,1, |
2525 | 0 | 1,exception); |
2526 | 0 | if (r == (const Quantum *) NULL) |
2527 | 0 | { |
2528 | 0 | status=MagickFalse; |
2529 | 0 | continue; |
2530 | 0 | } |
2531 | 0 | alpha=QuantumScale*(double) GetPixelAlpha(image,r); |
2532 | 0 | pixel+=(*k)*alpha*(double) r[i]; |
2533 | 0 | gamma+=(*k)*alpha; |
2534 | 0 | k++; |
2535 | 0 | } |
2536 | 0 | gamma=MagickSafeReciprocal(gamma); |
2537 | 0 | SetPixelChannel(blur_image,channel,ClampToQuantum(gamma*pixel),q); |
2538 | 0 | } |
2539 | 0 | p+=(ptrdiff_t) GetPixelChannels(image); |
2540 | 0 | q+=(ptrdiff_t) GetPixelChannels(blur_image); |
2541 | 0 | } |
2542 | 0 | if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse) |
2543 | 0 | status=MagickFalse; |
2544 | 0 | if (image->progress_monitor != (MagickProgressMonitor) NULL) |
2545 | 0 | { |
2546 | 0 | MagickBooleanType |
2547 | 0 | proceed; |
2548 | |
|
2549 | | #if defined(MAGICKCORE_OPENMP_SUPPORT) |
2550 | | #pragma omp atomic |
2551 | | #endif |
2552 | 0 | progress++; |
2553 | 0 | proceed=SetImageProgress(image,BlurImageTag,progress,image->rows); |
2554 | 0 | if (proceed == MagickFalse) |
2555 | 0 | status=MagickFalse; |
2556 | 0 | } |
2557 | 0 | } |
2558 | 0 | blur_view=DestroyCacheView(blur_view); |
2559 | 0 | motion_view=DestroyCacheView(motion_view); |
2560 | 0 | image_view=DestroyCacheView(image_view); |
2561 | 0 | kernel=(MagickRealType *) RelinquishAlignedMemory(kernel); |
2562 | 0 | offset=(OffsetInfo *) RelinquishMagickMemory(offset); |
2563 | 0 | if (status == MagickFalse) |
2564 | 0 | blur_image=DestroyImage(blur_image); |
2565 | 0 | return(blur_image); |
2566 | 0 | } |
2567 | | |
2568 | | /* |
2569 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
2570 | | % % |
2571 | | % % |
2572 | | % % |
2573 | | % P r e v i e w I m a g e % |
2574 | | % % |
2575 | | % % |
2576 | | % % |
2577 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
2578 | | % |
2579 | | % PreviewImage() tiles 9 thumbnails of the specified image with an image |
2580 | | % processing operation applied with varying parameters. This may be helpful |
2581 | | % pin-pointing an appropriate parameter for a particular image processing |
2582 | | % operation. |
2583 | | % |
2584 | | % The format of the PreviewImages method is: |
2585 | | % |
2586 | | % Image *PreviewImages(const Image *image,const PreviewType preview, |
2587 | | % ExceptionInfo *exception) |
2588 | | % |
2589 | | % A description of each parameter follows: |
2590 | | % |
2591 | | % o image: the image. |
2592 | | % |
2593 | | % o preview: the image processing operation. |
2594 | | % |
2595 | | % o exception: return any errors or warnings in this structure. |
2596 | | % |
2597 | | */ |
2598 | | MagickExport Image *PreviewImage(const Image *image,const PreviewType preview, |
2599 | | ExceptionInfo *exception) |
2600 | 0 | { |
2601 | 0 | #define NumberTiles 9 |
2602 | 0 | #define PreviewImageTag "Preview/Image" |
2603 | 0 | #define DefaultPreviewGeometry "204x204+10+10" |
2604 | |
|
2605 | 0 | char |
2606 | 0 | factor[MagickPathExtent], |
2607 | 0 | label[MagickPathExtent]; |
2608 | |
|
2609 | 0 | double |
2610 | 0 | degrees, |
2611 | 0 | gamma, |
2612 | 0 | percentage, |
2613 | 0 | radius, |
2614 | 0 | sigma, |
2615 | 0 | threshold; |
2616 | |
|
2617 | 0 | Image |
2618 | 0 | *images, |
2619 | 0 | *montage_image, |
2620 | 0 | *preview_image, |
2621 | 0 | *thumbnail; |
2622 | |
|
2623 | 0 | ImageInfo |
2624 | 0 | *preview_info; |
2625 | |
|
2626 | 0 | MagickBooleanType |
2627 | 0 | proceed; |
2628 | |
|
2629 | 0 | MontageInfo |
2630 | 0 | *montage_info; |
2631 | |
|
2632 | 0 | QuantizeInfo |
2633 | 0 | quantize_info; |
2634 | |
|
2635 | 0 | RectangleInfo |
2636 | 0 | geometry; |
2637 | |
|
2638 | 0 | size_t |
2639 | 0 | colors; |
2640 | |
|
2641 | 0 | ssize_t |
2642 | 0 | i, |
2643 | 0 | x = 0, |
2644 | 0 | y = 0; |
2645 | | |
2646 | | /* |
2647 | | Open output image file. |
2648 | | */ |
2649 | 0 | assert(image != (Image *) NULL); |
2650 | 0 | assert(image->signature == MagickCoreSignature); |
2651 | 0 | if (IsEventLogging() != MagickFalse) |
2652 | 0 | (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename); |
2653 | 0 | colors=2; |
2654 | 0 | degrees=0.0; |
2655 | 0 | gamma=(-0.2f); |
2656 | 0 | preview_info=AcquireImageInfo(); |
2657 | 0 | SetGeometry(image,&geometry); |
2658 | 0 | (void) ParseMetaGeometry(DefaultPreviewGeometry,&geometry.x,&geometry.y, |
2659 | 0 | &geometry.width,&geometry.height); |
2660 | 0 | images=NewImageList(); |
2661 | 0 | percentage=12.5; |
2662 | 0 | GetQuantizeInfo(&quantize_info); |
2663 | 0 | radius=0.0; |
2664 | 0 | sigma=1.0; |
2665 | 0 | threshold=0.0; |
2666 | 0 | for (i=0; i < NumberTiles; i++) |
2667 | 0 | { |
2668 | 0 | thumbnail=ThumbnailImage(image,geometry.width,geometry.height,exception); |
2669 | 0 | if (thumbnail == (Image *) NULL) |
2670 | 0 | break; |
2671 | 0 | (void) SetImageProgressMonitor(thumbnail,(MagickProgressMonitor) NULL, |
2672 | 0 | (void *) NULL); |
2673 | 0 | (void) SetImageProperty(thumbnail,"label",DefaultTileLabel,exception); |
2674 | 0 | if (i == (NumberTiles/2)) |
2675 | 0 | { |
2676 | 0 | (void) QueryColorCompliance("#dfdfdf",AllCompliance, |
2677 | 0 | &thumbnail->matte_color,exception); |
2678 | 0 | AppendImageToList(&images,thumbnail); |
2679 | 0 | continue; |
2680 | 0 | } |
2681 | 0 | switch (preview) |
2682 | 0 | { |
2683 | 0 | case RotatePreview: |
2684 | 0 | { |
2685 | 0 | degrees+=45.0; |
2686 | 0 | preview_image=RotateImage(thumbnail,degrees,exception); |
2687 | 0 | (void) FormatLocaleString(label,MagickPathExtent,"rotate %g",degrees); |
2688 | 0 | break; |
2689 | 0 | } |
2690 | 0 | case ShearPreview: |
2691 | 0 | { |
2692 | 0 | degrees+=5.0; |
2693 | 0 | preview_image=ShearImage(thumbnail,degrees,degrees,exception); |
2694 | 0 | (void) FormatLocaleString(label,MagickPathExtent,"shear %gx%g",degrees, |
2695 | 0 | 2.0*degrees); |
2696 | 0 | break; |
2697 | 0 | } |
2698 | 0 | case RollPreview: |
2699 | 0 | { |
2700 | 0 | x=((i+1)*(ssize_t) thumbnail->columns)/NumberTiles; |
2701 | 0 | y=((i+1)*(ssize_t) thumbnail->rows)/NumberTiles; |
2702 | 0 | preview_image=RollImage(thumbnail,x,y,exception); |
2703 | 0 | (void) FormatLocaleString(label,MagickPathExtent,"roll %+.20gx%+.20g", |
2704 | 0 | (double) x,(double) y); |
2705 | 0 | break; |
2706 | 0 | } |
2707 | 0 | case HuePreview: |
2708 | 0 | { |
2709 | 0 | preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception); |
2710 | 0 | if (preview_image == (Image *) NULL) |
2711 | 0 | break; |
2712 | 0 | (void) FormatLocaleString(factor,MagickPathExtent,"100,100,%g",2.0* |
2713 | 0 | percentage); |
2714 | 0 | (void) ModulateImage(preview_image,factor,exception); |
2715 | 0 | (void) FormatLocaleString(label,MagickPathExtent,"modulate %s",factor); |
2716 | 0 | break; |
2717 | 0 | } |
2718 | 0 | case SaturationPreview: |
2719 | 0 | { |
2720 | 0 | preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception); |
2721 | 0 | if (preview_image == (Image *) NULL) |
2722 | 0 | break; |
2723 | 0 | (void) FormatLocaleString(factor,MagickPathExtent,"100,%g",2.0* |
2724 | 0 | percentage); |
2725 | 0 | (void) ModulateImage(preview_image,factor,exception); |
2726 | 0 | (void) FormatLocaleString(label,MagickPathExtent,"modulate %s",factor); |
2727 | 0 | break; |
2728 | 0 | } |
2729 | 0 | case BrightnessPreview: |
2730 | 0 | { |
2731 | 0 | preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception); |
2732 | 0 | if (preview_image == (Image *) NULL) |
2733 | 0 | break; |
2734 | 0 | (void) FormatLocaleString(factor,MagickPathExtent,"%g",2.0*percentage); |
2735 | 0 | (void) ModulateImage(preview_image,factor,exception); |
2736 | 0 | (void) FormatLocaleString(label,MagickPathExtent,"modulate %s",factor); |
2737 | 0 | break; |
2738 | 0 | } |
2739 | 0 | case GammaPreview: |
2740 | 0 | default: |
2741 | 0 | { |
2742 | 0 | preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception); |
2743 | 0 | if (preview_image == (Image *) NULL) |
2744 | 0 | break; |
2745 | 0 | gamma+=0.4; |
2746 | 0 | (void) GammaImage(preview_image,gamma,exception); |
2747 | 0 | (void) FormatLocaleString(label,MagickPathExtent,"gamma %g",gamma); |
2748 | 0 | break; |
2749 | 0 | } |
2750 | 0 | case SpiffPreview: |
2751 | 0 | { |
2752 | 0 | preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception); |
2753 | 0 | if (preview_image != (Image *) NULL) |
2754 | 0 | for (x=0; x < i; x++) |
2755 | 0 | (void) ContrastImage(preview_image,MagickTrue,exception); |
2756 | 0 | (void) FormatLocaleString(label,MagickPathExtent,"contrast (%.20g)", |
2757 | 0 | (double) i+1); |
2758 | 0 | break; |
2759 | 0 | } |
2760 | 0 | case DullPreview: |
2761 | 0 | { |
2762 | 0 | preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception); |
2763 | 0 | if (preview_image == (Image *) NULL) |
2764 | 0 | break; |
2765 | 0 | for (x=0; x < i; x++) |
2766 | 0 | (void) ContrastImage(preview_image,MagickFalse,exception); |
2767 | 0 | (void) FormatLocaleString(label,MagickPathExtent,"+contrast (%.20g)", |
2768 | 0 | (double) i+1); |
2769 | 0 | break; |
2770 | 0 | } |
2771 | 0 | case GrayscalePreview: |
2772 | 0 | { |
2773 | 0 | preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception); |
2774 | 0 | if (preview_image == (Image *) NULL) |
2775 | 0 | break; |
2776 | 0 | colors<<=1; |
2777 | 0 | quantize_info.number_colors=colors; |
2778 | 0 | quantize_info.colorspace=GRAYColorspace; |
2779 | 0 | (void) QuantizeImage(&quantize_info,preview_image,exception); |
2780 | 0 | (void) FormatLocaleString(label,MagickPathExtent, |
2781 | 0 | "-colorspace gray -colors %.20g",(double) colors); |
2782 | 0 | break; |
2783 | 0 | } |
2784 | 0 | case QuantizePreview: |
2785 | 0 | { |
2786 | 0 | preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception); |
2787 | 0 | if (preview_image == (Image *) NULL) |
2788 | 0 | break; |
2789 | 0 | colors<<=1; |
2790 | 0 | quantize_info.number_colors=colors; |
2791 | 0 | (void) QuantizeImage(&quantize_info,preview_image,exception); |
2792 | 0 | (void) FormatLocaleString(label,MagickPathExtent,"colors %.20g", |
2793 | 0 | (double) colors); |
2794 | 0 | break; |
2795 | 0 | } |
2796 | 0 | case DespecklePreview: |
2797 | 0 | { |
2798 | 0 | for (x=0; x < (i-1); x++) |
2799 | 0 | { |
2800 | 0 | preview_image=DespeckleImage(thumbnail,exception); |
2801 | 0 | if (preview_image == (Image *) NULL) |
2802 | 0 | break; |
2803 | 0 | thumbnail=DestroyImage(thumbnail); |
2804 | 0 | thumbnail=preview_image; |
2805 | 0 | } |
2806 | 0 | preview_image=DespeckleImage(thumbnail,exception); |
2807 | 0 | if (preview_image == (Image *) NULL) |
2808 | 0 | break; |
2809 | 0 | (void) FormatLocaleString(label,MagickPathExtent,"despeckle (%.20g)", |
2810 | 0 | (double) i+1); |
2811 | 0 | break; |
2812 | 0 | } |
2813 | 0 | case ReduceNoisePreview: |
2814 | 0 | { |
2815 | 0 | preview_image=StatisticImage(thumbnail,NonpeakStatistic,(size_t) |
2816 | 0 | radius,(size_t) radius,exception); |
2817 | 0 | (void) FormatLocaleString(label,MagickPathExtent,"noise %g",radius); |
2818 | 0 | break; |
2819 | 0 | } |
2820 | 0 | case AddNoisePreview: |
2821 | 0 | { |
2822 | 0 | switch ((int) i) |
2823 | 0 | { |
2824 | 0 | case 0: |
2825 | 0 | { |
2826 | 0 | (void) CopyMagickString(factor,"uniform",MagickPathExtent); |
2827 | 0 | break; |
2828 | 0 | } |
2829 | 0 | case 1: |
2830 | 0 | { |
2831 | 0 | (void) CopyMagickString(factor,"gaussian",MagickPathExtent); |
2832 | 0 | break; |
2833 | 0 | } |
2834 | 0 | case 2: |
2835 | 0 | { |
2836 | 0 | (void) CopyMagickString(factor,"multiplicative",MagickPathExtent); |
2837 | 0 | break; |
2838 | 0 | } |
2839 | 0 | case 3: |
2840 | 0 | { |
2841 | 0 | (void) CopyMagickString(factor,"impulse",MagickPathExtent); |
2842 | 0 | break; |
2843 | 0 | } |
2844 | 0 | case 5: |
2845 | 0 | { |
2846 | 0 | (void) CopyMagickString(factor,"laplacian",MagickPathExtent); |
2847 | 0 | break; |
2848 | 0 | } |
2849 | 0 | case 6: |
2850 | 0 | { |
2851 | 0 | (void) CopyMagickString(factor,"Poisson",MagickPathExtent); |
2852 | 0 | break; |
2853 | 0 | } |
2854 | 0 | default: |
2855 | 0 | { |
2856 | 0 | (void) CopyMagickString(thumbnail->magick,"NULL",MagickPathExtent); |
2857 | 0 | break; |
2858 | 0 | } |
2859 | 0 | } |
2860 | 0 | preview_image=StatisticImage(thumbnail,NonpeakStatistic,(size_t) i, |
2861 | 0 | (size_t) i,exception); |
2862 | 0 | (void) FormatLocaleString(label,MagickPathExtent,"+noise %s",factor); |
2863 | 0 | break; |
2864 | 0 | } |
2865 | 0 | case SharpenPreview: |
2866 | 0 | { |
2867 | 0 | preview_image=SharpenImage(thumbnail,radius,sigma,exception); |
2868 | 0 | (void) FormatLocaleString(label,MagickPathExtent,"sharpen %gx%g", |
2869 | 0 | radius,sigma); |
2870 | 0 | break; |
2871 | 0 | } |
2872 | 0 | case BlurPreview: |
2873 | 0 | { |
2874 | 0 | preview_image=BlurImage(thumbnail,radius,sigma,exception); |
2875 | 0 | (void) FormatLocaleString(label,MagickPathExtent,"blur %gx%g",radius, |
2876 | 0 | sigma); |
2877 | 0 | break; |
2878 | 0 | } |
2879 | 0 | case ThresholdPreview: |
2880 | 0 | { |
2881 | 0 | preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception); |
2882 | 0 | if (preview_image == (Image *) NULL) |
2883 | 0 | break; |
2884 | 0 | (void) BilevelImage(thumbnail,(double) (percentage*((double) |
2885 | 0 | QuantumRange+1.0))/100.0,exception); |
2886 | 0 | (void) FormatLocaleString(label,MagickPathExtent,"threshold %g", |
2887 | 0 | (double) (percentage*((double) QuantumRange+1.0))/100.0); |
2888 | 0 | break; |
2889 | 0 | } |
2890 | 0 | case EdgeDetectPreview: |
2891 | 0 | { |
2892 | 0 | preview_image=EdgeImage(thumbnail,radius,exception); |
2893 | 0 | (void) FormatLocaleString(label,MagickPathExtent,"edge %g",radius); |
2894 | 0 | break; |
2895 | 0 | } |
2896 | 0 | case SpreadPreview: |
2897 | 0 | { |
2898 | 0 | preview_image=SpreadImage(thumbnail,image->interpolate,radius, |
2899 | 0 | exception); |
2900 | 0 | (void) FormatLocaleString(label,MagickPathExtent,"spread %g", |
2901 | 0 | radius+0.5); |
2902 | 0 | break; |
2903 | 0 | } |
2904 | 0 | case SolarizePreview: |
2905 | 0 | { |
2906 | 0 | preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception); |
2907 | 0 | if (preview_image == (Image *) NULL) |
2908 | 0 | break; |
2909 | 0 | (void) SolarizeImage(preview_image,(double) QuantumRange*percentage/ |
2910 | 0 | 100.0,exception); |
2911 | 0 | (void) FormatLocaleString(label,MagickPathExtent,"solarize %g", |
2912 | 0 | ((double) QuantumRange*percentage)/100.0); |
2913 | 0 | break; |
2914 | 0 | } |
2915 | 0 | case ShadePreview: |
2916 | 0 | { |
2917 | 0 | degrees+=10.0; |
2918 | 0 | preview_image=ShadeImage(thumbnail,MagickTrue,degrees,degrees, |
2919 | 0 | exception); |
2920 | 0 | (void) FormatLocaleString(label,MagickPathExtent,"shade %gx%g",degrees, |
2921 | 0 | degrees); |
2922 | 0 | break; |
2923 | 0 | } |
2924 | 0 | case RaisePreview: |
2925 | 0 | { |
2926 | 0 | RectangleInfo |
2927 | 0 | raise; |
2928 | |
|
2929 | 0 | preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception); |
2930 | 0 | if (preview_image == (Image *) NULL) |
2931 | 0 | break; |
2932 | 0 | raise.width=(size_t) (2*i+2); |
2933 | 0 | raise.height=(size_t) (2*i+2); |
2934 | 0 | raise.x=(i-1)/2; |
2935 | 0 | raise.y=(i-1)/2; |
2936 | 0 | (void) RaiseImage(preview_image,&raise,MagickTrue,exception); |
2937 | 0 | (void) FormatLocaleString(label,MagickPathExtent, |
2938 | 0 | "raise %.20gx%.20g%+.20g%+.20g",(double) raise.width,(double) |
2939 | 0 | raise.height,(double) raise.x,(double) raise.y); |
2940 | 0 | break; |
2941 | 0 | } |
2942 | 0 | case SegmentPreview: |
2943 | 0 | { |
2944 | 0 | preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception); |
2945 | 0 | if (preview_image == (Image *) NULL) |
2946 | 0 | break; |
2947 | 0 | threshold+=0.4; |
2948 | 0 | (void) SegmentImage(preview_image,sRGBColorspace,MagickFalse,threshold, |
2949 | 0 | threshold,exception); |
2950 | 0 | (void) FormatLocaleString(label,MagickPathExtent,"segment %gx%g", |
2951 | 0 | threshold,threshold); |
2952 | 0 | break; |
2953 | 0 | } |
2954 | 0 | case SwirlPreview: |
2955 | 0 | { |
2956 | 0 | preview_image=SwirlImage(thumbnail,degrees,image->interpolate, |
2957 | 0 | exception); |
2958 | 0 | (void) FormatLocaleString(label,MagickPathExtent,"swirl %g",degrees); |
2959 | 0 | degrees+=45.0; |
2960 | 0 | break; |
2961 | 0 | } |
2962 | 0 | case ImplodePreview: |
2963 | 0 | { |
2964 | 0 | degrees+=0.1; |
2965 | 0 | preview_image=ImplodeImage(thumbnail,degrees,image->interpolate, |
2966 | 0 | exception); |
2967 | 0 | (void) FormatLocaleString(label,MagickPathExtent,"implode %g",degrees); |
2968 | 0 | break; |
2969 | 0 | } |
2970 | 0 | case WavePreview: |
2971 | 0 | { |
2972 | 0 | degrees+=5.0; |
2973 | 0 | preview_image=WaveImage(thumbnail,0.5*degrees,2.0*degrees, |
2974 | 0 | image->interpolate,exception); |
2975 | 0 | (void) FormatLocaleString(label,MagickPathExtent,"wave %gx%g",0.5* |
2976 | 0 | degrees,2.0*degrees); |
2977 | 0 | break; |
2978 | 0 | } |
2979 | 0 | case OilPaintPreview: |
2980 | 0 | { |
2981 | 0 | preview_image=OilPaintImage(thumbnail,(double) radius,(double) sigma, |
2982 | 0 | exception); |
2983 | 0 | (void) FormatLocaleString(label,MagickPathExtent,"charcoal %gx%g", |
2984 | 0 | radius,sigma); |
2985 | 0 | break; |
2986 | 0 | } |
2987 | 0 | case CharcoalDrawingPreview: |
2988 | 0 | { |
2989 | 0 | preview_image=CharcoalImage(thumbnail,(double) radius,(double) sigma, |
2990 | 0 | exception); |
2991 | 0 | (void) FormatLocaleString(label,MagickPathExtent,"charcoal %gx%g", |
2992 | 0 | radius,sigma); |
2993 | 0 | break; |
2994 | 0 | } |
2995 | 0 | case JPEGPreview: |
2996 | 0 | { |
2997 | 0 | char |
2998 | 0 | filename[MagickPathExtent]; |
2999 | |
|
3000 | 0 | int |
3001 | 0 | file; |
3002 | |
|
3003 | 0 | MagickBooleanType |
3004 | 0 | status; |
3005 | |
|
3006 | 0 | preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception); |
3007 | 0 | if (preview_image == (Image *) NULL) |
3008 | 0 | break; |
3009 | 0 | preview_info->quality=(size_t) percentage; |
3010 | 0 | (void) FormatLocaleString(factor,MagickPathExtent,"%.20g",(double) |
3011 | 0 | preview_info->quality); |
3012 | 0 | file=AcquireUniqueFileResource(filename); |
3013 | 0 | if (file != -1) |
3014 | 0 | file=close_utf8(file)-1; |
3015 | 0 | (void) FormatLocaleString(preview_image->filename,MagickPathExtent, |
3016 | 0 | "jpeg:%s",filename); |
3017 | 0 | status=WriteImage(preview_info,preview_image,exception); |
3018 | 0 | if (status != MagickFalse) |
3019 | 0 | { |
3020 | 0 | Image |
3021 | 0 | *quality_image; |
3022 | |
|
3023 | 0 | (void) CopyMagickString(preview_info->filename, |
3024 | 0 | preview_image->filename,MagickPathExtent); |
3025 | 0 | quality_image=ReadImage(preview_info,exception); |
3026 | 0 | if (quality_image != (Image *) NULL) |
3027 | 0 | { |
3028 | 0 | preview_image=DestroyImage(preview_image); |
3029 | 0 | preview_image=quality_image; |
3030 | 0 | } |
3031 | 0 | } |
3032 | 0 | (void) RelinquishUniqueFileResource(preview_image->filename); |
3033 | 0 | if ((GetBlobSize(preview_image)/1024) >= 1024) |
3034 | 0 | (void) FormatLocaleString(label,MagickPathExtent,"quality %s\n%gmb ", |
3035 | 0 | factor,(double) ((MagickOffsetType) GetBlobSize(preview_image))/ |
3036 | 0 | 1024.0/1024.0); |
3037 | 0 | else |
3038 | 0 | if (GetBlobSize(preview_image) >= 1024) |
3039 | 0 | (void) FormatLocaleString(label,MagickPathExtent, |
3040 | 0 | "quality %s\n%gkb ",factor,(double) ((MagickOffsetType) |
3041 | 0 | GetBlobSize(preview_image))/1024.0); |
3042 | 0 | else |
3043 | 0 | (void) FormatLocaleString(label,MagickPathExtent, |
3044 | 0 | "quality %s\n%.20gb ",factor,(double) ((MagickOffsetType) |
3045 | 0 | GetBlobSize(thumbnail))); |
3046 | 0 | break; |
3047 | 0 | } |
3048 | 0 | } |
3049 | 0 | thumbnail=DestroyImage(thumbnail); |
3050 | 0 | percentage+=12.5; |
3051 | 0 | radius+=0.5; |
3052 | 0 | sigma+=0.25; |
3053 | 0 | if (preview_image == (Image *) NULL) |
3054 | 0 | break; |
3055 | 0 | preview_image->alpha_trait=UndefinedPixelTrait; |
3056 | 0 | (void) DeleteImageProperty(preview_image,"label"); |
3057 | 0 | (void) SetImageProperty(preview_image,"label",label,exception); |
3058 | 0 | AppendImageToList(&images,preview_image); |
3059 | 0 | proceed=SetImageProgress(image,PreviewImageTag,(MagickOffsetType) i, |
3060 | 0 | NumberTiles); |
3061 | 0 | if (proceed == MagickFalse) |
3062 | 0 | break; |
3063 | 0 | } |
3064 | 0 | if (images == (Image *) NULL) |
3065 | 0 | { |
3066 | 0 | preview_info=DestroyImageInfo(preview_info); |
3067 | 0 | return((Image *) NULL); |
3068 | 0 | } |
3069 | | /* |
3070 | | Create the montage. |
3071 | | */ |
3072 | 0 | montage_info=CloneMontageInfo(preview_info,(MontageInfo *) NULL); |
3073 | 0 | (void) CopyMagickString(montage_info->filename,image->filename, |
3074 | 0 | MagickPathExtent); |
3075 | 0 | montage_info->shadow=MagickTrue; |
3076 | 0 | (void) CloneString(&montage_info->tile,"3x3"); |
3077 | 0 | (void) CloneString(&montage_info->geometry,DefaultPreviewGeometry); |
3078 | 0 | (void) CloneString(&montage_info->frame,DefaultTileFrame); |
3079 | 0 | montage_image=MontageImages(images,montage_info,exception); |
3080 | 0 | montage_info=DestroyMontageInfo(montage_info); |
3081 | 0 | images=DestroyImageList(images); |
3082 | 0 | if (montage_image == (Image *) NULL) |
3083 | 0 | ThrowImageException(ResourceLimitError,"MemoryAllocationFailed"); |
3084 | 0 | if (montage_image->montage != (char *) NULL) |
3085 | 0 | { |
3086 | | /* |
3087 | | Free image directory. |
3088 | | */ |
3089 | 0 | montage_image->montage=(char *) RelinquishMagickMemory( |
3090 | 0 | montage_image->montage); |
3091 | 0 | if (image->directory != (char *) NULL) |
3092 | 0 | montage_image->directory=(char *) RelinquishMagickMemory( |
3093 | 0 | montage_image->directory); |
3094 | 0 | } |
3095 | 0 | preview_info=DestroyImageInfo(preview_info); |
3096 | 0 | return(montage_image); |
3097 | 0 | } |
3098 | | |
3099 | | /* |
3100 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
3101 | | % % |
3102 | | % % |
3103 | | % % |
3104 | | % R o t a t i o n a l B l u r I m a g e % |
3105 | | % % |
3106 | | % % |
3107 | | % % |
3108 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
3109 | | % |
3110 | | % RotationalBlurImage() applies a radial blur to the image. |
3111 | | % |
3112 | | % Andrew Protano contributed this effect. |
3113 | | % |
3114 | | % The format of the RotationalBlurImage method is: |
3115 | | % |
3116 | | % Image *RotationalBlurImage(const Image *image,const double angle, |
3117 | | % ExceptionInfo *exception) |
3118 | | % |
3119 | | % A description of each parameter follows: |
3120 | | % |
3121 | | % o image: the image. |
3122 | | % |
3123 | | % o angle: the angle of the radial blur. |
3124 | | % |
3125 | | % o blur: the blur. |
3126 | | % |
3127 | | % o exception: return any errors or warnings in this structure. |
3128 | | % |
3129 | | */ |
3130 | | MagickExport Image *RotationalBlurImage(const Image *image,const double angle, |
3131 | | ExceptionInfo *exception) |
3132 | 0 | { |
3133 | 0 | CacheView |
3134 | 0 | *blur_view, |
3135 | 0 | *image_view, |
3136 | 0 | *radial_view; |
3137 | |
|
3138 | 0 | double |
3139 | 0 | blur_radius, |
3140 | 0 | *cos_theta, |
3141 | 0 | offset, |
3142 | 0 | *sin_theta, |
3143 | 0 | theta; |
3144 | |
|
3145 | 0 | Image |
3146 | 0 | *blur_image; |
3147 | |
|
3148 | 0 | MagickBooleanType |
3149 | 0 | status; |
3150 | |
|
3151 | 0 | MagickOffsetType |
3152 | 0 | progress; |
3153 | |
|
3154 | 0 | PointInfo |
3155 | 0 | blur_center; |
3156 | |
|
3157 | 0 | size_t |
3158 | 0 | n; |
3159 | |
|
3160 | 0 | ssize_t |
3161 | 0 | w, |
3162 | 0 | y; |
3163 | | |
3164 | | /* |
3165 | | Allocate blur image. |
3166 | | */ |
3167 | 0 | assert(image != (Image *) NULL); |
3168 | 0 | assert(image->signature == MagickCoreSignature); |
3169 | 0 | assert(exception != (ExceptionInfo *) NULL); |
3170 | 0 | assert(exception->signature == MagickCoreSignature); |
3171 | 0 | if (IsEventLogging() != MagickFalse) |
3172 | 0 | (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename); |
3173 | | #if defined(MAGICKCORE_OPENCL_SUPPORT) |
3174 | | blur_image=AccelerateRotationalBlurImage(image,angle,exception); |
3175 | | if (blur_image != (Image *) NULL) |
3176 | | return(blur_image); |
3177 | | #endif |
3178 | 0 | blur_image=CloneImage(image,0,0,MagickTrue,exception); |
3179 | 0 | if (blur_image == (Image *) NULL) |
3180 | 0 | return((Image *) NULL); |
3181 | 0 | if (SetImageStorageClass(blur_image,DirectClass,exception) == MagickFalse) |
3182 | 0 | { |
3183 | 0 | blur_image=DestroyImage(blur_image); |
3184 | 0 | return((Image *) NULL); |
3185 | 0 | } |
3186 | 0 | blur_center.x=(double) (image->columns-1)/2.0; |
3187 | 0 | blur_center.y=(double) (image->rows-1)/2.0; |
3188 | 0 | blur_radius=hypot(blur_center.x,blur_center.y); |
3189 | 0 | n=(size_t) fabs(4.0*DegreesToRadians(angle)*sqrt((double) blur_radius)+2UL); |
3190 | 0 | theta=DegreesToRadians(angle)/(double) (n-1); |
3191 | 0 | cos_theta=(double *) AcquireQuantumMemory((size_t) n,sizeof(*cos_theta)); |
3192 | 0 | sin_theta=(double *) AcquireQuantumMemory((size_t) n,sizeof(*sin_theta)); |
3193 | 0 | if ((cos_theta == (double *) NULL) || (sin_theta == (double *) NULL)) |
3194 | 0 | { |
3195 | 0 | if (cos_theta != (double *) NULL) |
3196 | 0 | cos_theta=(double *) RelinquishMagickMemory(cos_theta); |
3197 | 0 | if (sin_theta != (double *) NULL) |
3198 | 0 | sin_theta=(double *) RelinquishMagickMemory(sin_theta); |
3199 | 0 | blur_image=DestroyImage(blur_image); |
3200 | 0 | ThrowImageException(ResourceLimitError,"MemoryAllocationFailed"); |
3201 | 0 | } |
3202 | 0 | offset=theta*(double) (n-1)/2.0; |
3203 | 0 | for (w=0; w < (ssize_t) n; w++) |
3204 | 0 | { |
3205 | 0 | cos_theta[w]=cos((double) (theta*w-offset)); |
3206 | 0 | sin_theta[w]=sin((double) (theta*w-offset)); |
3207 | 0 | } |
3208 | | /* |
3209 | | Radial blur image. |
3210 | | */ |
3211 | 0 | status=MagickTrue; |
3212 | 0 | progress=0; |
3213 | 0 | image_view=AcquireVirtualCacheView(image,exception); |
3214 | 0 | radial_view=AcquireVirtualCacheView(image,exception); |
3215 | 0 | blur_view=AcquireAuthenticCacheView(blur_image,exception); |
3216 | | #if defined(MAGICKCORE_OPENMP_SUPPORT) |
3217 | | #pragma omp parallel for schedule(static) shared(progress,status) \ |
3218 | | magick_number_threads(image,blur_image,image->rows,1) |
3219 | | #endif |
3220 | 0 | for (y=0; y < (ssize_t) image->rows; y++) |
3221 | 0 | { |
3222 | 0 | const Quantum |
3223 | 0 | *magick_restrict p; |
3224 | |
|
3225 | 0 | Quantum |
3226 | 0 | *magick_restrict q; |
3227 | |
|
3228 | 0 | ssize_t |
3229 | 0 | x; |
3230 | |
|
3231 | 0 | if (status == MagickFalse) |
3232 | 0 | continue; |
3233 | 0 | p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception); |
3234 | 0 | q=QueueCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1, |
3235 | 0 | exception); |
3236 | 0 | if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL)) |
3237 | 0 | { |
3238 | 0 | status=MagickFalse; |
3239 | 0 | continue; |
3240 | 0 | } |
3241 | 0 | for (x=0; x < (ssize_t) image->columns; x++) |
3242 | 0 | { |
3243 | 0 | double |
3244 | 0 | radius; |
3245 | |
|
3246 | 0 | PointInfo |
3247 | 0 | center; |
3248 | |
|
3249 | 0 | ssize_t |
3250 | 0 | i; |
3251 | |
|
3252 | 0 | size_t |
3253 | 0 | step; |
3254 | |
|
3255 | 0 | center.x=(double) x-blur_center.x; |
3256 | 0 | center.y=(double) y-blur_center.y; |
3257 | 0 | radius=hypot((double) center.x,center.y); |
3258 | 0 | if (radius == 0) |
3259 | 0 | step=1; |
3260 | 0 | else |
3261 | 0 | { |
3262 | 0 | step=(size_t) (blur_radius/radius); |
3263 | 0 | if (step == 0) |
3264 | 0 | step=1; |
3265 | 0 | else |
3266 | 0 | if (step >= n) |
3267 | 0 | step=n-1; |
3268 | 0 | } |
3269 | 0 | for (i=0; i < (ssize_t) GetPixelChannels(image); i++) |
3270 | 0 | { |
3271 | 0 | double |
3272 | 0 | gamma, |
3273 | 0 | pixel; |
3274 | |
|
3275 | 0 | PixelChannel |
3276 | 0 | channel; |
3277 | |
|
3278 | 0 | PixelTrait |
3279 | 0 | blur_traits, |
3280 | 0 | traits; |
3281 | |
|
3282 | 0 | const Quantum |
3283 | 0 | *magick_restrict r; |
3284 | |
|
3285 | 0 | ssize_t |
3286 | 0 | j; |
3287 | |
|
3288 | 0 | channel=GetPixelChannelChannel(image,i); |
3289 | 0 | traits=GetPixelChannelTraits(image,channel); |
3290 | 0 | blur_traits=GetPixelChannelTraits(blur_image,channel); |
3291 | 0 | if ((traits == UndefinedPixelTrait) || |
3292 | 0 | (blur_traits == UndefinedPixelTrait)) |
3293 | 0 | continue; |
3294 | 0 | if ((blur_traits & CopyPixelTrait) != 0) |
3295 | 0 | { |
3296 | 0 | SetPixelChannel(blur_image,channel,p[i],q); |
3297 | 0 | continue; |
3298 | 0 | } |
3299 | 0 | gamma=0.0; |
3300 | 0 | pixel=0.0; |
3301 | 0 | if ((GetPixelChannelTraits(image,AlphaPixelChannel) == UndefinedPixelTrait) || |
3302 | 0 | (channel == AlphaPixelChannel)) |
3303 | 0 | { |
3304 | 0 | for (j=0; j < (ssize_t) n; j+=(ssize_t) step) |
3305 | 0 | { |
3306 | 0 | r=GetCacheViewVirtualPixels(radial_view, (ssize_t) (blur_center.x+ |
3307 | 0 | center.x*cos_theta[j]-center.y*sin_theta[j]+0.5),(ssize_t) |
3308 | 0 | (blur_center.y+center.x*sin_theta[j]+center.y*cos_theta[j]+0.5), |
3309 | 0 | 1,1,exception); |
3310 | 0 | if (r == (const Quantum *) NULL) |
3311 | 0 | { |
3312 | 0 | status=MagickFalse; |
3313 | 0 | continue; |
3314 | 0 | } |
3315 | 0 | pixel+=(double) r[i]; |
3316 | 0 | gamma++; |
3317 | 0 | } |
3318 | 0 | gamma=MagickSafeReciprocal(gamma); |
3319 | 0 | SetPixelChannel(blur_image,channel,ClampToQuantum(gamma*pixel),q); |
3320 | 0 | continue; |
3321 | 0 | } |
3322 | 0 | for (j=0; j < (ssize_t) n; j+=(ssize_t) step) |
3323 | 0 | { |
3324 | 0 | double |
3325 | 0 | alpha; |
3326 | |
|
3327 | 0 | r=GetCacheViewVirtualPixels(radial_view, (ssize_t) (blur_center.x+ |
3328 | 0 | center.x*cos_theta[j]-center.y*sin_theta[j]+0.5),(ssize_t) |
3329 | 0 | (blur_center.y+center.x*sin_theta[j]+center.y*cos_theta[j]+0.5), |
3330 | 0 | 1,1,exception); |
3331 | 0 | if (r == (const Quantum *) NULL) |
3332 | 0 | { |
3333 | 0 | status=MagickFalse; |
3334 | 0 | continue; |
3335 | 0 | } |
3336 | 0 | alpha=QuantumScale*(double) GetPixelAlpha(image,r); |
3337 | 0 | pixel+=alpha*(double) r[i]; |
3338 | 0 | gamma+=alpha; |
3339 | 0 | } |
3340 | 0 | gamma=MagickSafeReciprocal(gamma); |
3341 | 0 | SetPixelChannel(blur_image,channel,ClampToQuantum(gamma*pixel),q); |
3342 | 0 | } |
3343 | 0 | p+=(ptrdiff_t) GetPixelChannels(image); |
3344 | 0 | q+=(ptrdiff_t) GetPixelChannels(blur_image); |
3345 | 0 | } |
3346 | 0 | if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse) |
3347 | 0 | status=MagickFalse; |
3348 | 0 | if (image->progress_monitor != (MagickProgressMonitor) NULL) |
3349 | 0 | { |
3350 | 0 | MagickBooleanType |
3351 | 0 | proceed; |
3352 | |
|
3353 | | #if defined(MAGICKCORE_OPENMP_SUPPORT) |
3354 | | #pragma omp atomic |
3355 | | #endif |
3356 | 0 | progress++; |
3357 | 0 | proceed=SetImageProgress(image,BlurImageTag,progress,image->rows); |
3358 | 0 | if (proceed == MagickFalse) |
3359 | 0 | status=MagickFalse; |
3360 | 0 | } |
3361 | 0 | } |
3362 | 0 | blur_view=DestroyCacheView(blur_view); |
3363 | 0 | radial_view=DestroyCacheView(radial_view); |
3364 | 0 | image_view=DestroyCacheView(image_view); |
3365 | 0 | cos_theta=(double *) RelinquishMagickMemory(cos_theta); |
3366 | 0 | sin_theta=(double *) RelinquishMagickMemory(sin_theta); |
3367 | 0 | if (status == MagickFalse) |
3368 | 0 | blur_image=DestroyImage(blur_image); |
3369 | 0 | return(blur_image); |
3370 | 0 | } |
3371 | | |
3372 | | /* |
3373 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
3374 | | % % |
3375 | | % % |
3376 | | % % |
3377 | | % S e l e c t i v e B l u r I m a g e % |
3378 | | % % |
3379 | | % % |
3380 | | % % |
3381 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
3382 | | % |
3383 | | % SelectiveBlurImage() selectively blur pixels within a contrast threshold. |
3384 | | % It is similar to the unsharpen mask that sharpens everything with contrast |
3385 | | % above a certain threshold. |
3386 | | % |
3387 | | % The format of the SelectiveBlurImage method is: |
3388 | | % |
3389 | | % Image *SelectiveBlurImage(const Image *image,const double radius, |
3390 | | % const double sigma,const double threshold,ExceptionInfo *exception) |
3391 | | % |
3392 | | % A description of each parameter follows: |
3393 | | % |
3394 | | % o image: the image. |
3395 | | % |
3396 | | % o radius: the radius of the Gaussian, in pixels, not counting the center |
3397 | | % pixel. |
3398 | | % |
3399 | | % o sigma: the standard deviation of the Gaussian, in pixels. |
3400 | | % |
3401 | | % o threshold: only pixels within this contrast threshold are included |
3402 | | % in the blur operation. |
3403 | | % |
3404 | | % o exception: return any errors or warnings in this structure. |
3405 | | % |
3406 | | */ |
3407 | | MagickExport Image *SelectiveBlurImage(const Image *image,const double radius, |
3408 | | const double sigma,const double threshold,ExceptionInfo *exception) |
3409 | 0 | { |
3410 | 0 | #define SelectiveBlurImageTag "SelectiveBlur/Image" |
3411 | |
|
3412 | 0 | CacheView |
3413 | 0 | *blur_view, |
3414 | 0 | *image_view, |
3415 | 0 | *luminance_view; |
3416 | |
|
3417 | 0 | Image |
3418 | 0 | *blur_image, |
3419 | 0 | *luminance_image; |
3420 | |
|
3421 | 0 | MagickBooleanType |
3422 | 0 | status; |
3423 | |
|
3424 | 0 | MagickOffsetType |
3425 | 0 | progress; |
3426 | |
|
3427 | 0 | MagickRealType |
3428 | 0 | *kernel; |
3429 | |
|
3430 | 0 | size_t |
3431 | 0 | width; |
3432 | |
|
3433 | 0 | ssize_t |
3434 | 0 | center, |
3435 | 0 | y; |
3436 | | |
3437 | | /* |
3438 | | Initialize blur image attributes. |
3439 | | */ |
3440 | 0 | assert(image != (Image *) NULL); |
3441 | 0 | assert(image->signature == MagickCoreSignature); |
3442 | 0 | assert(exception != (ExceptionInfo *) NULL); |
3443 | 0 | assert(exception->signature == MagickCoreSignature); |
3444 | 0 | if (IsEventLogging() != MagickFalse) |
3445 | 0 | (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename); |
3446 | 0 | width=GetOptimalKernelWidth1D(radius,sigma); |
3447 | 0 | kernel=(MagickRealType *) MagickAssumeAligned(AcquireAlignedMemory((size_t) |
3448 | 0 | width,width*sizeof(*kernel))); |
3449 | 0 | if (kernel == (MagickRealType *) NULL) |
3450 | 0 | ThrowImageException(ResourceLimitError,"MemoryAllocationFailed"); |
3451 | 0 | { |
3452 | 0 | ssize_t |
3453 | 0 | i, |
3454 | 0 | j, |
3455 | 0 | v; |
3456 | |
|
3457 | 0 | j=(ssize_t) (width-1)/2; |
3458 | 0 | i=0; |
3459 | 0 | for (v=(-j); v <= j; v++) |
3460 | 0 | { |
3461 | 0 | ssize_t |
3462 | 0 | u; |
3463 | |
|
3464 | 0 | for (u=(-j); u <= j; u++) |
3465 | 0 | kernel[i++]=(MagickRealType) (exp(-((double) u*u+v*v)/(2.0*MagickSigma* |
3466 | 0 | MagickSigma))/(2.0*MagickPI*MagickSigma*MagickSigma)); |
3467 | 0 | } |
3468 | 0 | } |
3469 | 0 | if (image->debug != MagickFalse) |
3470 | 0 | { |
3471 | 0 | char |
3472 | 0 | format[MagickPathExtent], |
3473 | 0 | *message; |
3474 | |
|
3475 | 0 | const MagickRealType |
3476 | 0 | *k; |
3477 | |
|
3478 | 0 | ssize_t |
3479 | 0 | u, |
3480 | 0 | v; |
3481 | |
|
3482 | 0 | (void) LogMagickEvent(TransformEvent,GetMagickModule(), |
3483 | 0 | " SelectiveBlurImage with %.20gx%.20g kernel:",(double) width,(double) |
3484 | 0 | width); |
3485 | 0 | message=AcquireString(""); |
3486 | 0 | k=kernel; |
3487 | 0 | for (v=0; v < (ssize_t) width; v++) |
3488 | 0 | { |
3489 | 0 | *message='\0'; |
3490 | 0 | (void) FormatLocaleString(format,MagickPathExtent,"%.20g: ",(double) v); |
3491 | 0 | (void) ConcatenateString(&message,format); |
3492 | 0 | for (u=0; u < (ssize_t) width; u++) |
3493 | 0 | { |
3494 | 0 | (void) FormatLocaleString(format,MagickPathExtent,"%+f ",(double) |
3495 | 0 | *k++); |
3496 | 0 | (void) ConcatenateString(&message,format); |
3497 | 0 | } |
3498 | 0 | (void) LogMagickEvent(TransformEvent,GetMagickModule(),"%s",message); |
3499 | 0 | } |
3500 | 0 | message=DestroyString(message); |
3501 | 0 | } |
3502 | 0 | blur_image=CloneImage(image,0,0,MagickTrue,exception); |
3503 | 0 | if (blur_image == (Image *) NULL) |
3504 | 0 | return((Image *) NULL); |
3505 | 0 | if (SetImageStorageClass(blur_image,DirectClass,exception) == MagickFalse) |
3506 | 0 | { |
3507 | 0 | blur_image=DestroyImage(blur_image); |
3508 | 0 | kernel=(MagickRealType *) RelinquishAlignedMemory(kernel); |
3509 | 0 | return((Image *) NULL); |
3510 | 0 | } |
3511 | 0 | luminance_image=CloneImage(image,0,0,MagickTrue,exception); |
3512 | 0 | if (luminance_image == (Image *) NULL) |
3513 | 0 | { |
3514 | 0 | blur_image=DestroyImage(blur_image); |
3515 | 0 | kernel=(MagickRealType *) RelinquishAlignedMemory(kernel); |
3516 | 0 | return((Image *) NULL); |
3517 | 0 | } |
3518 | 0 | status=TransformImageColorspace(luminance_image,GRAYColorspace,exception); |
3519 | 0 | if (status == MagickFalse) |
3520 | 0 | { |
3521 | 0 | luminance_image=DestroyImage(luminance_image); |
3522 | 0 | blur_image=DestroyImage(blur_image); |
3523 | 0 | kernel=(MagickRealType *) RelinquishAlignedMemory(kernel); |
3524 | 0 | return((Image *) NULL); |
3525 | 0 | } |
3526 | | /* |
3527 | | Threshold blur image. |
3528 | | */ |
3529 | 0 | status=MagickTrue; |
3530 | 0 | progress=0; |
3531 | 0 | center=(ssize_t) (GetPixelChannels(image)*(image->columns+width)* |
3532 | 0 | ((width-1)/2L)+GetPixelChannels(image)*((width-1)/2L)); |
3533 | 0 | image_view=AcquireVirtualCacheView(image,exception); |
3534 | 0 | luminance_view=AcquireVirtualCacheView(luminance_image,exception); |
3535 | 0 | blur_view=AcquireAuthenticCacheView(blur_image,exception); |
3536 | | #if defined(MAGICKCORE_OPENMP_SUPPORT) |
3537 | | #pragma omp parallel for schedule(static) shared(progress,status) \ |
3538 | | magick_number_threads(image,blur_image,image->rows,1) |
3539 | | #endif |
3540 | 0 | for (y=0; y < (ssize_t) image->rows; y++) |
3541 | 0 | { |
3542 | 0 | double |
3543 | 0 | contrast; |
3544 | |
|
3545 | 0 | MagickBooleanType |
3546 | 0 | sync; |
3547 | |
|
3548 | 0 | const Quantum |
3549 | 0 | *magick_restrict l, |
3550 | 0 | *magick_restrict p; |
3551 | |
|
3552 | 0 | Quantum |
3553 | 0 | *magick_restrict q; |
3554 | |
|
3555 | 0 | ssize_t |
3556 | 0 | x; |
3557 | |
|
3558 | 0 | if (status == MagickFalse) |
3559 | 0 | continue; |
3560 | 0 | p=GetCacheViewVirtualPixels(image_view,-((ssize_t) (width-1)/2L),y-(ssize_t) |
3561 | 0 | ((width-1)/2L),image->columns+width,width,exception); |
3562 | 0 | l=GetCacheViewVirtualPixels(luminance_view,-((ssize_t) (width-1)/2L),y- |
3563 | 0 | (ssize_t) ((width-1)/2L),luminance_image->columns+width,width,exception); |
3564 | 0 | q=QueueCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1, |
3565 | 0 | exception); |
3566 | 0 | if ((p == (const Quantum *) NULL) || (l == (const Quantum *) NULL) || |
3567 | 0 | (q == (Quantum *) NULL)) |
3568 | 0 | { |
3569 | 0 | status=MagickFalse; |
3570 | 0 | continue; |
3571 | 0 | } |
3572 | 0 | for (x=0; x < (ssize_t) image->columns; x++) |
3573 | 0 | { |
3574 | 0 | double |
3575 | 0 | intensity; |
3576 | |
|
3577 | 0 | ssize_t |
3578 | 0 | i; |
3579 | |
|
3580 | 0 | intensity=GetPixelIntensity(image,p+center); |
3581 | 0 | for (i=0; i < (ssize_t) GetPixelChannels(image); i++) |
3582 | 0 | { |
3583 | 0 | double |
3584 | 0 | alpha, |
3585 | 0 | gamma, |
3586 | 0 | pixel; |
3587 | |
|
3588 | 0 | PixelChannel |
3589 | 0 | channel; |
3590 | |
|
3591 | 0 | PixelTrait |
3592 | 0 | blur_traits, |
3593 | 0 | traits; |
3594 | |
|
3595 | 0 | const MagickRealType |
3596 | 0 | *magick_restrict k; |
3597 | |
|
3598 | 0 | const Quantum |
3599 | 0 | *magick_restrict luminance_pixels, |
3600 | 0 | *magick_restrict pixels; |
3601 | |
|
3602 | 0 | ssize_t |
3603 | 0 | u; |
3604 | |
|
3605 | 0 | ssize_t |
3606 | 0 | v; |
3607 | |
|
3608 | 0 | channel=GetPixelChannelChannel(image,i); |
3609 | 0 | traits=GetPixelChannelTraits(image,channel); |
3610 | 0 | blur_traits=GetPixelChannelTraits(blur_image,channel); |
3611 | 0 | if ((traits == UndefinedPixelTrait) || |
3612 | 0 | (blur_traits == UndefinedPixelTrait)) |
3613 | 0 | continue; |
3614 | 0 | if ((blur_traits & CopyPixelTrait) != 0) |
3615 | 0 | { |
3616 | 0 | SetPixelChannel(blur_image,channel,p[center+i],q); |
3617 | 0 | continue; |
3618 | 0 | } |
3619 | 0 | k=kernel; |
3620 | 0 | pixel=0.0; |
3621 | 0 | pixels=p; |
3622 | 0 | luminance_pixels=l; |
3623 | 0 | gamma=0.0; |
3624 | 0 | if ((blur_traits & BlendPixelTrait) == 0) |
3625 | 0 | { |
3626 | 0 | for (v=0; v < (ssize_t) width; v++) |
3627 | 0 | { |
3628 | 0 | for (u=0; u < (ssize_t) width; u++) |
3629 | 0 | { |
3630 | 0 | contrast=GetPixelIntensity(luminance_image,luminance_pixels)- |
3631 | 0 | intensity; |
3632 | 0 | if (fabs(contrast) < threshold) |
3633 | 0 | { |
3634 | 0 | pixel+=(*k)*(double) pixels[i]; |
3635 | 0 | gamma+=(*k); |
3636 | 0 | } |
3637 | 0 | k++; |
3638 | 0 | pixels+=(ptrdiff_t) GetPixelChannels(image); |
3639 | 0 | luminance_pixels+=(ptrdiff_t) GetPixelChannels(luminance_image); |
3640 | 0 | } |
3641 | 0 | pixels+=(ptrdiff_t) GetPixelChannels(image)*image->columns; |
3642 | 0 | luminance_pixels+=(ptrdiff_t) GetPixelChannels(luminance_image)* |
3643 | 0 | luminance_image->columns; |
3644 | 0 | } |
3645 | 0 | if (fabs((double) gamma) < MagickEpsilon) |
3646 | 0 | { |
3647 | 0 | SetPixelChannel(blur_image,channel,p[center+i],q); |
3648 | 0 | continue; |
3649 | 0 | } |
3650 | 0 | gamma=MagickSafeReciprocal(gamma); |
3651 | 0 | SetPixelChannel(blur_image,channel,ClampToQuantum(gamma*pixel),q); |
3652 | 0 | continue; |
3653 | 0 | } |
3654 | 0 | for (v=0; v < (ssize_t) width; v++) |
3655 | 0 | { |
3656 | 0 | for (u=0; u < (ssize_t) width; u++) |
3657 | 0 | { |
3658 | 0 | contrast=GetPixelIntensity(image,pixels)-intensity; |
3659 | 0 | if (fabs(contrast) < threshold) |
3660 | 0 | { |
3661 | 0 | alpha=QuantumScale*(double) GetPixelAlpha(image,pixels); |
3662 | 0 | pixel+=(*k)*alpha*(double) pixels[i]; |
3663 | 0 | gamma+=(*k)*alpha; |
3664 | 0 | } |
3665 | 0 | k++; |
3666 | 0 | pixels+=(ptrdiff_t) GetPixelChannels(image); |
3667 | 0 | luminance_pixels+=(ptrdiff_t) GetPixelChannels(luminance_image); |
3668 | 0 | } |
3669 | 0 | pixels+=(ptrdiff_t) GetPixelChannels(image)*image->columns; |
3670 | 0 | luminance_pixels+=(ptrdiff_t) GetPixelChannels(luminance_image)* |
3671 | 0 | luminance_image->columns; |
3672 | 0 | } |
3673 | 0 | if (fabs((double) gamma) < MagickEpsilon) |
3674 | 0 | { |
3675 | 0 | SetPixelChannel(blur_image,channel,p[center+i],q); |
3676 | 0 | continue; |
3677 | 0 | } |
3678 | 0 | gamma=MagickSafeReciprocal(gamma); |
3679 | 0 | SetPixelChannel(blur_image,channel,ClampToQuantum(gamma*pixel),q); |
3680 | 0 | } |
3681 | 0 | p+=(ptrdiff_t) GetPixelChannels(image); |
3682 | 0 | l+=(ptrdiff_t) GetPixelChannels(luminance_image); |
3683 | 0 | q+=(ptrdiff_t) GetPixelChannels(blur_image); |
3684 | 0 | } |
3685 | 0 | sync=SyncCacheViewAuthenticPixels(blur_view,exception); |
3686 | 0 | if (sync == MagickFalse) |
3687 | 0 | status=MagickFalse; |
3688 | 0 | if (image->progress_monitor != (MagickProgressMonitor) NULL) |
3689 | 0 | { |
3690 | 0 | MagickBooleanType |
3691 | 0 | proceed; |
3692 | |
|
3693 | | #if defined(MAGICKCORE_OPENMP_SUPPORT) |
3694 | | #pragma omp atomic |
3695 | | #endif |
3696 | 0 | progress++; |
3697 | 0 | proceed=SetImageProgress(image,SelectiveBlurImageTag,progress, |
3698 | 0 | image->rows); |
3699 | 0 | if (proceed == MagickFalse) |
3700 | 0 | status=MagickFalse; |
3701 | 0 | } |
3702 | 0 | } |
3703 | 0 | blur_image->type=image->type; |
3704 | 0 | blur_view=DestroyCacheView(blur_view); |
3705 | 0 | luminance_view=DestroyCacheView(luminance_view); |
3706 | 0 | image_view=DestroyCacheView(image_view); |
3707 | 0 | luminance_image=DestroyImage(luminance_image); |
3708 | 0 | kernel=(MagickRealType *) RelinquishAlignedMemory(kernel); |
3709 | 0 | if (status == MagickFalse) |
3710 | 0 | blur_image=DestroyImage(blur_image); |
3711 | 0 | return(blur_image); |
3712 | 0 | } |
3713 | | |
3714 | | /* |
3715 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
3716 | | % % |
3717 | | % % |
3718 | | % % |
3719 | | % S h a d e I m a g e % |
3720 | | % % |
3721 | | % % |
3722 | | % % |
3723 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
3724 | | % |
3725 | | % ShadeImage() shines a distant light on an image to create a |
3726 | | % three-dimensional effect. You control the positioning of the light with |
3727 | | % azimuth and elevation; azimuth is measured in degrees off the x axis |
3728 | | % and elevation is measured in pixels above the Z axis. |
3729 | | % |
3730 | | % The format of the ShadeImage method is: |
3731 | | % |
3732 | | % Image *ShadeImage(const Image *image,const MagickBooleanType gray, |
3733 | | % const double azimuth,const double elevation,ExceptionInfo *exception) |
3734 | | % |
3735 | | % A description of each parameter follows: |
3736 | | % |
3737 | | % o image: the image. |
3738 | | % |
3739 | | % o gray: A value other than zero shades the intensity of each pixel. |
3740 | | % |
3741 | | % o azimuth, elevation: Define the light source direction. |
3742 | | % |
3743 | | % o exception: return any errors or warnings in this structure. |
3744 | | % |
3745 | | */ |
3746 | | MagickExport Image *ShadeImage(const Image *image,const MagickBooleanType gray, |
3747 | | const double azimuth,const double elevation,ExceptionInfo *exception) |
3748 | 0 | { |
3749 | 0 | #define GetShadeIntensity(image,pixel) \ |
3750 | 0 | ClampPixel(GetPixelIntensity((image),(pixel))) |
3751 | 0 | #define ShadeImageTag "Shade/Image" |
3752 | |
|
3753 | 0 | CacheView |
3754 | 0 | *image_view, |
3755 | 0 | *shade_view; |
3756 | |
|
3757 | 0 | Image |
3758 | 0 | *linear_image, |
3759 | 0 | *shade_image; |
3760 | |
|
3761 | 0 | MagickBooleanType |
3762 | 0 | status; |
3763 | |
|
3764 | 0 | MagickOffsetType |
3765 | 0 | progress; |
3766 | |
|
3767 | 0 | PrimaryInfo |
3768 | 0 | light; |
3769 | |
|
3770 | 0 | ssize_t |
3771 | 0 | y; |
3772 | | |
3773 | | /* |
3774 | | Initialize shaded image attributes. |
3775 | | */ |
3776 | 0 | assert(image != (const Image *) NULL); |
3777 | 0 | assert(image->signature == MagickCoreSignature); |
3778 | 0 | assert(exception != (ExceptionInfo *) NULL); |
3779 | 0 | assert(exception->signature == MagickCoreSignature); |
3780 | 0 | if (IsEventLogging() != MagickFalse) |
3781 | 0 | (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename); |
3782 | 0 | linear_image=CloneImage(image,0,0,MagickTrue,exception); |
3783 | 0 | shade_image=CloneImage(image,0,0,MagickTrue,exception); |
3784 | 0 | if ((linear_image == (Image *) NULL) || (shade_image == (Image *) NULL)) |
3785 | 0 | { |
3786 | 0 | if (linear_image != (Image *) NULL) |
3787 | 0 | linear_image=DestroyImage(linear_image); |
3788 | 0 | if (shade_image != (Image *) NULL) |
3789 | 0 | shade_image=DestroyImage(shade_image); |
3790 | 0 | return((Image *) NULL); |
3791 | 0 | } |
3792 | 0 | if (SetImageStorageClass(shade_image,DirectClass,exception) == MagickFalse) |
3793 | 0 | { |
3794 | 0 | linear_image=DestroyImage(linear_image); |
3795 | 0 | shade_image=DestroyImage(shade_image); |
3796 | 0 | return((Image *) NULL); |
3797 | 0 | } |
3798 | | /* |
3799 | | Compute the light vector. |
3800 | | */ |
3801 | 0 | light.x=(double) QuantumRange*cos(DegreesToRadians(azimuth))* |
3802 | 0 | cos(DegreesToRadians(elevation)); |
3803 | 0 | light.y=(double) QuantumRange*sin(DegreesToRadians(azimuth))* |
3804 | 0 | cos(DegreesToRadians(elevation)); |
3805 | 0 | light.z=(double) QuantumRange*sin(DegreesToRadians(elevation)); |
3806 | | /* |
3807 | | Shade image. |
3808 | | */ |
3809 | 0 | status=MagickTrue; |
3810 | 0 | progress=0; |
3811 | 0 | image_view=AcquireVirtualCacheView(linear_image,exception); |
3812 | 0 | shade_view=AcquireAuthenticCacheView(shade_image,exception); |
3813 | | #if defined(MAGICKCORE_OPENMP_SUPPORT) |
3814 | | #pragma omp parallel for schedule(static) shared(progress,status) \ |
3815 | | magick_number_threads(linear_image,shade_image,linear_image->rows,1) |
3816 | | #endif |
3817 | 0 | for (y=0; y < (ssize_t) linear_image->rows; y++) |
3818 | 0 | { |
3819 | 0 | double |
3820 | 0 | distance, |
3821 | 0 | normal_distance, |
3822 | 0 | shade; |
3823 | |
|
3824 | 0 | PrimaryInfo |
3825 | 0 | normal; |
3826 | |
|
3827 | 0 | const Quantum |
3828 | 0 | *magick_restrict center, |
3829 | 0 | *magick_restrict p, |
3830 | 0 | *magick_restrict post, |
3831 | 0 | *magick_restrict pre; |
3832 | |
|
3833 | 0 | Quantum |
3834 | 0 | *magick_restrict q; |
3835 | |
|
3836 | 0 | ssize_t |
3837 | 0 | x; |
3838 | |
|
3839 | 0 | if (status == MagickFalse) |
3840 | 0 | continue; |
3841 | 0 | p=GetCacheViewVirtualPixels(image_view,-1,y-1,linear_image->columns+2,3, |
3842 | 0 | exception); |
3843 | 0 | q=QueueCacheViewAuthenticPixels(shade_view,0,y,shade_image->columns,1, |
3844 | 0 | exception); |
3845 | 0 | if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL)) |
3846 | 0 | { |
3847 | 0 | status=MagickFalse; |
3848 | 0 | continue; |
3849 | 0 | } |
3850 | | /* |
3851 | | Shade this row of pixels. |
3852 | | */ |
3853 | 0 | normal.z=2.0*(double) QuantumRange; /* constant Z of surface normal */ |
3854 | 0 | for (x=0; x < (ssize_t) linear_image->columns; x++) |
3855 | 0 | { |
3856 | 0 | ssize_t |
3857 | 0 | i; |
3858 | | |
3859 | | /* |
3860 | | Determine the surface normal and compute shading. |
3861 | | */ |
3862 | 0 | pre=p+GetPixelChannels(linear_image); |
3863 | 0 | center=pre+(linear_image->columns+2)*GetPixelChannels(linear_image); |
3864 | 0 | post=center+(linear_image->columns+2)*GetPixelChannels(linear_image); |
3865 | 0 | normal.x=(double) ( |
3866 | 0 | GetShadeIntensity(linear_image,pre-GetPixelChannels(linear_image))+ |
3867 | 0 | GetShadeIntensity(linear_image,center-GetPixelChannels(linear_image))+ |
3868 | 0 | GetShadeIntensity(linear_image,post-GetPixelChannels(linear_image))- |
3869 | 0 | GetShadeIntensity(linear_image,pre+GetPixelChannels(linear_image))- |
3870 | 0 | GetShadeIntensity(linear_image,center+GetPixelChannels(linear_image))- |
3871 | 0 | GetShadeIntensity(linear_image,post+GetPixelChannels(linear_image))); |
3872 | 0 | normal.y=(double) ( |
3873 | 0 | GetShadeIntensity(linear_image,post-GetPixelChannels(linear_image))+ |
3874 | 0 | GetShadeIntensity(linear_image,post)+ |
3875 | 0 | GetShadeIntensity(linear_image,post+GetPixelChannels(linear_image))- |
3876 | 0 | GetShadeIntensity(linear_image,pre-GetPixelChannels(linear_image))- |
3877 | 0 | GetShadeIntensity(linear_image,pre)- |
3878 | 0 | GetShadeIntensity(linear_image,pre+GetPixelChannels(linear_image))); |
3879 | 0 | if ((fabs(normal.x) <= MagickEpsilon) && |
3880 | 0 | (fabs(normal.y) <= MagickEpsilon)) |
3881 | 0 | shade=light.z; |
3882 | 0 | else |
3883 | 0 | { |
3884 | 0 | shade=0.0; |
3885 | 0 | distance=normal.x*light.x+normal.y*light.y+normal.z*light.z; |
3886 | 0 | if (distance > MagickEpsilon) |
3887 | 0 | { |
3888 | 0 | normal_distance=normal.x*normal.x+normal.y*normal.y+ |
3889 | 0 | normal.z*normal.z; |
3890 | 0 | if (normal_distance > (MagickEpsilon*MagickEpsilon)) |
3891 | 0 | shade=distance/sqrt((double) normal_distance); |
3892 | 0 | } |
3893 | 0 | } |
3894 | 0 | for (i=0; i < (ssize_t) GetPixelChannels(linear_image); i++) |
3895 | 0 | { |
3896 | 0 | PixelChannel |
3897 | 0 | channel; |
3898 | |
|
3899 | 0 | PixelTrait |
3900 | 0 | shade_traits, |
3901 | 0 | traits; |
3902 | |
|
3903 | 0 | channel=GetPixelChannelChannel(linear_image,i); |
3904 | 0 | traits=GetPixelChannelTraits(linear_image,channel); |
3905 | 0 | shade_traits=GetPixelChannelTraits(shade_image,channel); |
3906 | 0 | if ((traits == UndefinedPixelTrait) || |
3907 | 0 | (shade_traits == UndefinedPixelTrait)) |
3908 | 0 | continue; |
3909 | 0 | if ((shade_traits & CopyPixelTrait) != 0) |
3910 | 0 | { |
3911 | 0 | SetPixelChannel(shade_image,channel,center[i],q); |
3912 | 0 | continue; |
3913 | 0 | } |
3914 | 0 | if ((traits & UpdatePixelTrait) == 0) |
3915 | 0 | { |
3916 | 0 | SetPixelChannel(shade_image,channel,center[i],q); |
3917 | 0 | continue; |
3918 | 0 | } |
3919 | 0 | if (gray != MagickFalse) |
3920 | 0 | { |
3921 | 0 | SetPixelChannel(shade_image,channel,ClampToQuantum(shade),q); |
3922 | 0 | continue; |
3923 | 0 | } |
3924 | 0 | SetPixelChannel(shade_image,channel,ClampToQuantum(QuantumScale* |
3925 | 0 | shade*(double) center[i]),q); |
3926 | 0 | } |
3927 | 0 | p+=(ptrdiff_t) GetPixelChannels(linear_image); |
3928 | 0 | q+=(ptrdiff_t) GetPixelChannels(shade_image); |
3929 | 0 | } |
3930 | 0 | if (SyncCacheViewAuthenticPixels(shade_view,exception) == MagickFalse) |
3931 | 0 | status=MagickFalse; |
3932 | 0 | if (image->progress_monitor != (MagickProgressMonitor) NULL) |
3933 | 0 | { |
3934 | 0 | MagickBooleanType |
3935 | 0 | proceed; |
3936 | |
|
3937 | | #if defined(MAGICKCORE_OPENMP_SUPPORT) |
3938 | | #pragma omp atomic |
3939 | | #endif |
3940 | 0 | progress++; |
3941 | 0 | proceed=SetImageProgress(image,ShadeImageTag,progress,image->rows); |
3942 | 0 | if (proceed == MagickFalse) |
3943 | 0 | status=MagickFalse; |
3944 | 0 | } |
3945 | 0 | } |
3946 | 0 | shade_view=DestroyCacheView(shade_view); |
3947 | 0 | image_view=DestroyCacheView(image_view); |
3948 | 0 | linear_image=DestroyImage(linear_image); |
3949 | 0 | if (status == MagickFalse) |
3950 | 0 | shade_image=DestroyImage(shade_image); |
3951 | 0 | return(shade_image); |
3952 | 0 | } |
3953 | | |
3954 | | /* |
3955 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
3956 | | % % |
3957 | | % % |
3958 | | % % |
3959 | | % S h a r p e n I m a g e % |
3960 | | % % |
3961 | | % % |
3962 | | % % |
3963 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
3964 | | % |
3965 | | % SharpenImage() sharpens the image. We convolve the image with a Gaussian |
3966 | | % operator of the given radius and standard deviation (sigma). For |
3967 | | % reasonable results, radius should be larger than sigma. Use a radius of 0 |
3968 | | % and SharpenImage() selects a suitable radius for you. |
3969 | | % |
3970 | | % Using a separable kernel would be faster, but the negative weights cancel |
3971 | | % out on the corners of the kernel producing often undesirable ringing in the |
3972 | | % filtered result; this can be avoided by using a 2D gaussian shaped image |
3973 | | % sharpening kernel instead. |
3974 | | % |
3975 | | % The format of the SharpenImage method is: |
3976 | | % |
3977 | | % Image *SharpenImage(const Image *image,const double radius, |
3978 | | % const double sigma,ExceptionInfo *exception) |
3979 | | % |
3980 | | % A description of each parameter follows: |
3981 | | % |
3982 | | % o image: the image. |
3983 | | % |
3984 | | % o radius: the radius of the Gaussian, in pixels, not counting the center |
3985 | | % pixel. |
3986 | | % |
3987 | | % o sigma: the standard deviation of the Laplacian, in pixels. |
3988 | | % |
3989 | | % o exception: return any errors or warnings in this structure. |
3990 | | % |
3991 | | */ |
3992 | | MagickExport Image *SharpenImage(const Image *image,const double radius, |
3993 | | const double sigma,ExceptionInfo *exception) |
3994 | 0 | { |
3995 | 0 | double |
3996 | 0 | gamma, |
3997 | 0 | normalize; |
3998 | |
|
3999 | 0 | Image |
4000 | 0 | *sharp_image; |
4001 | |
|
4002 | 0 | KernelInfo |
4003 | 0 | *kernel_info; |
4004 | |
|
4005 | 0 | ssize_t |
4006 | 0 | i; |
4007 | |
|
4008 | 0 | size_t |
4009 | 0 | width; |
4010 | |
|
4011 | 0 | ssize_t |
4012 | 0 | j, |
4013 | 0 | u, |
4014 | 0 | v; |
4015 | |
|
4016 | 0 | assert(image != (const Image *) NULL); |
4017 | 0 | assert(image->signature == MagickCoreSignature); |
4018 | 0 | assert(exception != (ExceptionInfo *) NULL); |
4019 | 0 | assert(exception->signature == MagickCoreSignature); |
4020 | 0 | if (IsEventLogging() != MagickFalse) |
4021 | 0 | (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename); |
4022 | 0 | width=GetOptimalKernelWidth2D(radius,sigma); |
4023 | 0 | kernel_info=AcquireKernelInfo((const char *) NULL,exception); |
4024 | 0 | if (kernel_info == (KernelInfo *) NULL) |
4025 | 0 | ThrowImageException(ResourceLimitError,"MemoryAllocationFailed"); |
4026 | 0 | (void) memset(kernel_info,0,sizeof(*kernel_info)); |
4027 | 0 | kernel_info->width=width; |
4028 | 0 | kernel_info->height=width; |
4029 | 0 | kernel_info->x=(ssize_t) (width-1)/2; |
4030 | 0 | kernel_info->y=(ssize_t) (width-1)/2; |
4031 | 0 | kernel_info->signature=MagickCoreSignature; |
4032 | 0 | kernel_info->values=(MagickRealType *) MagickAssumeAligned( |
4033 | 0 | AcquireAlignedMemory(kernel_info->width,kernel_info->height* |
4034 | 0 | sizeof(*kernel_info->values))); |
4035 | 0 | if (kernel_info->values == (MagickRealType *) NULL) |
4036 | 0 | { |
4037 | 0 | kernel_info=DestroyKernelInfo(kernel_info); |
4038 | 0 | ThrowImageException(ResourceLimitError,"MemoryAllocationFailed"); |
4039 | 0 | } |
4040 | 0 | normalize=0.0; |
4041 | 0 | j=(ssize_t) (kernel_info->width-1)/2; |
4042 | 0 | i=0; |
4043 | 0 | for (v=(-j); v <= j; v++) |
4044 | 0 | { |
4045 | 0 | for (u=(-j); u <= j; u++) |
4046 | 0 | { |
4047 | 0 | kernel_info->values[i]=(MagickRealType) (-exp(-((double) u*u+v*v)/(2.0* |
4048 | 0 | MagickSigma*MagickSigma))/(2.0*MagickPI*MagickSigma*MagickSigma)); |
4049 | 0 | normalize+=kernel_info->values[i]; |
4050 | 0 | i++; |
4051 | 0 | } |
4052 | 0 | } |
4053 | 0 | kernel_info->values[i/2]=(double) ((-2.0)*normalize); |
4054 | 0 | normalize=0.0; |
4055 | 0 | for (i=0; i < (ssize_t) (kernel_info->width*kernel_info->height); i++) |
4056 | 0 | normalize+=kernel_info->values[i]; |
4057 | 0 | gamma=MagickSafeReciprocal(normalize); |
4058 | 0 | for (i=0; i < (ssize_t) (kernel_info->width*kernel_info->height); i++) |
4059 | 0 | kernel_info->values[i]*=gamma; |
4060 | 0 | sharp_image=ConvolveImage(image,kernel_info,exception); |
4061 | 0 | kernel_info=DestroyKernelInfo(kernel_info); |
4062 | 0 | return(sharp_image); |
4063 | 0 | } |
4064 | | |
4065 | | /* |
4066 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
4067 | | % % |
4068 | | % % |
4069 | | % % |
4070 | | % S p r e a d I m a g e % |
4071 | | % % |
4072 | | % % |
4073 | | % % |
4074 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
4075 | | % |
4076 | | % SpreadImage() is a special effects method that randomly displaces each |
4077 | | % pixel in a square area defined by the radius parameter. |
4078 | | % |
4079 | | % The format of the SpreadImage method is: |
4080 | | % |
4081 | | % Image *SpreadImage(const Image *image, |
4082 | | % const PixelInterpolateMethod method,const double radius, |
4083 | | % ExceptionInfo *exception) |
4084 | | % |
4085 | | % A description of each parameter follows: |
4086 | | % |
4087 | | % o image: the image. |
4088 | | % |
4089 | | % o method: interpolation method. |
4090 | | % |
4091 | | % o radius: choose a random pixel in a neighborhood of this extent. |
4092 | | % |
4093 | | % o exception: return any errors or warnings in this structure. |
4094 | | % |
4095 | | */ |
4096 | | MagickExport Image *SpreadImage(const Image *image, |
4097 | | const PixelInterpolateMethod method,const double radius, |
4098 | | ExceptionInfo *exception) |
4099 | 0 | { |
4100 | 0 | #define SpreadImageTag "Spread/Image" |
4101 | |
|
4102 | 0 | CacheView |
4103 | 0 | *image_view, |
4104 | 0 | *spread_view; |
4105 | |
|
4106 | 0 | Image |
4107 | 0 | *spread_image; |
4108 | |
|
4109 | 0 | MagickBooleanType |
4110 | 0 | status; |
4111 | |
|
4112 | 0 | MagickOffsetType |
4113 | 0 | progress; |
4114 | |
|
4115 | 0 | RandomInfo |
4116 | 0 | **magick_restrict random_info; |
4117 | |
|
4118 | 0 | size_t |
4119 | 0 | width; |
4120 | |
|
4121 | 0 | ssize_t |
4122 | 0 | y; |
4123 | |
|
4124 | | #if defined(MAGICKCORE_OPENMP_SUPPORT) |
4125 | | unsigned long |
4126 | | key; |
4127 | | #endif |
4128 | | |
4129 | | /* |
4130 | | Initialize spread image attributes. |
4131 | | */ |
4132 | 0 | assert(image != (Image *) NULL); |
4133 | 0 | assert(image->signature == MagickCoreSignature); |
4134 | 0 | assert(exception != (ExceptionInfo *) NULL); |
4135 | 0 | assert(exception->signature == MagickCoreSignature); |
4136 | 0 | if (IsEventLogging() != MagickFalse) |
4137 | 0 | (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename); |
4138 | 0 | spread_image=CloneImage(image,0,0,MagickTrue,exception); |
4139 | 0 | if (spread_image == (Image *) NULL) |
4140 | 0 | return((Image *) NULL); |
4141 | 0 | if (SetImageStorageClass(spread_image,DirectClass,exception) == MagickFalse) |
4142 | 0 | { |
4143 | 0 | spread_image=DestroyImage(spread_image); |
4144 | 0 | return((Image *) NULL); |
4145 | 0 | } |
4146 | | /* |
4147 | | Spread image. |
4148 | | */ |
4149 | 0 | status=MagickTrue; |
4150 | 0 | progress=0; |
4151 | 0 | width=GetOptimalKernelWidth1D(radius,0.5); |
4152 | 0 | random_info=AcquireRandomInfoTLS(); |
4153 | 0 | image_view=AcquireVirtualCacheView(image,exception); |
4154 | 0 | spread_view=AcquireAuthenticCacheView(spread_image,exception); |
4155 | | #if defined(MAGICKCORE_OPENMP_SUPPORT) |
4156 | | key=GetRandomSecretKey(random_info[0]); |
4157 | | #pragma omp parallel for schedule(static) shared(progress,status) \ |
4158 | | magick_number_threads(image,spread_image,image->rows,key == ~0UL) |
4159 | | #endif |
4160 | 0 | for (y=0; y < (ssize_t) image->rows; y++) |
4161 | 0 | { |
4162 | 0 | const int |
4163 | 0 | id = GetOpenMPThreadId(); |
4164 | |
|
4165 | 0 | Quantum |
4166 | 0 | *magick_restrict q; |
4167 | |
|
4168 | 0 | ssize_t |
4169 | 0 | x; |
4170 | |
|
4171 | 0 | if (status == MagickFalse) |
4172 | 0 | continue; |
4173 | 0 | q=QueueCacheViewAuthenticPixels(spread_view,0,y,spread_image->columns,1, |
4174 | 0 | exception); |
4175 | 0 | if (q == (Quantum *) NULL) |
4176 | 0 | { |
4177 | 0 | status=MagickFalse; |
4178 | 0 | continue; |
4179 | 0 | } |
4180 | 0 | for (x=0; x < (ssize_t) image->columns; x++) |
4181 | 0 | { |
4182 | 0 | PointInfo |
4183 | 0 | point; |
4184 | |
|
4185 | 0 | point.x=GetPseudoRandomValue(random_info[id]); |
4186 | 0 | point.y=GetPseudoRandomValue(random_info[id]); |
4187 | 0 | status=InterpolatePixelChannels(image,image_view,spread_image,method, |
4188 | 0 | (double) x+width*(point.x-0.5),(double) y+width*(point.y-0.5),q, |
4189 | 0 | exception); |
4190 | 0 | if (status == MagickFalse) |
4191 | 0 | break; |
4192 | 0 | q+=(ptrdiff_t) GetPixelChannels(spread_image); |
4193 | 0 | } |
4194 | 0 | if (SyncCacheViewAuthenticPixels(spread_view,exception) == MagickFalse) |
4195 | 0 | status=MagickFalse; |
4196 | 0 | if (image->progress_monitor != (MagickProgressMonitor) NULL) |
4197 | 0 | { |
4198 | 0 | MagickBooleanType |
4199 | 0 | proceed; |
4200 | |
|
4201 | | #if defined(MAGICKCORE_OPENMP_SUPPORT) |
4202 | | #pragma omp atomic |
4203 | | #endif |
4204 | 0 | progress++; |
4205 | 0 | proceed=SetImageProgress(image,SpreadImageTag,progress,image->rows); |
4206 | 0 | if (proceed == MagickFalse) |
4207 | 0 | status=MagickFalse; |
4208 | 0 | } |
4209 | 0 | } |
4210 | 0 | spread_view=DestroyCacheView(spread_view); |
4211 | 0 | image_view=DestroyCacheView(image_view); |
4212 | 0 | random_info=DestroyRandomInfoTLS(random_info); |
4213 | 0 | if (status == MagickFalse) |
4214 | 0 | spread_image=DestroyImage(spread_image); |
4215 | 0 | return(spread_image); |
4216 | 0 | } |
4217 | | |
4218 | | /* |
4219 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
4220 | | % % |
4221 | | % % |
4222 | | % % |
4223 | | % U n s h a r p M a s k I m a g e % |
4224 | | % % |
4225 | | % % |
4226 | | % % |
4227 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
4228 | | % |
4229 | | % UnsharpMaskImage() sharpens one or more image channels. We convolve the |
4230 | | % image with a Gaussian operator of the given radius and standard deviation |
4231 | | % (sigma). For reasonable results, radius should be larger than sigma. Use a |
4232 | | % radius of 0 and UnsharpMaskImage() selects a suitable radius for you. |
4233 | | % |
4234 | | % The format of the UnsharpMaskImage method is: |
4235 | | % |
4236 | | % Image *UnsharpMaskImage(const Image *image,const double radius, |
4237 | | % const double sigma,const double amount,const double threshold, |
4238 | | % ExceptionInfo *exception) |
4239 | | % |
4240 | | % A description of each parameter follows: |
4241 | | % |
4242 | | % o image: the image. |
4243 | | % |
4244 | | % o radius: the radius of the Gaussian, in pixels, not counting the center |
4245 | | % pixel. |
4246 | | % |
4247 | | % o sigma: the standard deviation of the Gaussian, in pixels. |
4248 | | % |
4249 | | % o gain: the percentage of the difference between the original and the |
4250 | | % blur image that is added back into the original. |
4251 | | % |
4252 | | % o threshold: the threshold in pixels needed to apply the difference gain. |
4253 | | % |
4254 | | % o exception: return any errors or warnings in this structure. |
4255 | | % |
4256 | | */ |
4257 | | MagickExport Image *UnsharpMaskImage(const Image *image,const double radius, |
4258 | | const double sigma,const double gain,const double threshold, |
4259 | | ExceptionInfo *exception) |
4260 | 0 | { |
4261 | 0 | #define SharpenImageTag "Sharpen/Image" |
4262 | |
|
4263 | 0 | CacheView |
4264 | 0 | *image_view, |
4265 | 0 | *unsharp_view; |
4266 | |
|
4267 | 0 | Image |
4268 | 0 | *unsharp_image; |
4269 | |
|
4270 | 0 | MagickBooleanType |
4271 | 0 | status; |
4272 | |
|
4273 | 0 | MagickOffsetType |
4274 | 0 | progress; |
4275 | |
|
4276 | 0 | double |
4277 | 0 | quantum_threshold; |
4278 | |
|
4279 | 0 | ssize_t |
4280 | 0 | y; |
4281 | |
|
4282 | 0 | assert(image != (const Image *) NULL); |
4283 | 0 | assert(image->signature == MagickCoreSignature); |
4284 | 0 | assert(exception != (ExceptionInfo *) NULL); |
4285 | 0 | assert(exception->signature == MagickCoreSignature); |
4286 | 0 | if (IsEventLogging() != MagickFalse) |
4287 | 0 | (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename); |
4288 | | /* This kernel appears to be broken. |
4289 | | #if defined(MAGICKCORE_OPENCL_SUPPORT) |
4290 | | unsharp_image=AccelerateUnsharpMaskImage(image,radius,sigma,gain,threshold, |
4291 | | exception); |
4292 | | if (unsharp_image != (Image *) NULL) |
4293 | | return(unsharp_image); |
4294 | | #endif |
4295 | | */ |
4296 | 0 | unsharp_image=BlurImage(image,radius,sigma,exception); |
4297 | 0 | if (unsharp_image == (Image *) NULL) |
4298 | 0 | return((Image *) NULL); |
4299 | 0 | quantum_threshold=(double) QuantumRange*threshold; |
4300 | | /* |
4301 | | Unsharp-mask image. |
4302 | | */ |
4303 | 0 | status=MagickTrue; |
4304 | 0 | progress=0; |
4305 | 0 | image_view=AcquireVirtualCacheView(image,exception); |
4306 | 0 | unsharp_view=AcquireAuthenticCacheView(unsharp_image,exception); |
4307 | | #if defined(MAGICKCORE_OPENMP_SUPPORT) |
4308 | | #pragma omp parallel for schedule(static) shared(progress,status) \ |
4309 | | magick_number_threads(image,unsharp_image,image->rows,1) |
4310 | | #endif |
4311 | 0 | for (y=0; y < (ssize_t) image->rows; y++) |
4312 | 0 | { |
4313 | 0 | const Quantum |
4314 | 0 | *magick_restrict p; |
4315 | |
|
4316 | 0 | Quantum |
4317 | 0 | *magick_restrict q; |
4318 | |
|
4319 | 0 | ssize_t |
4320 | 0 | x; |
4321 | |
|
4322 | 0 | if (status == MagickFalse) |
4323 | 0 | continue; |
4324 | 0 | p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception); |
4325 | 0 | q=GetCacheViewAuthenticPixels(unsharp_view,0,y,unsharp_image->columns,1, |
4326 | 0 | exception); |
4327 | 0 | if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL)) |
4328 | 0 | { |
4329 | 0 | status=MagickFalse; |
4330 | 0 | continue; |
4331 | 0 | } |
4332 | 0 | for (x=0; x < (ssize_t) image->columns; x++) |
4333 | 0 | { |
4334 | 0 | ssize_t |
4335 | 0 | i; |
4336 | |
|
4337 | 0 | for (i=0; i < (ssize_t) GetPixelChannels(image); i++) |
4338 | 0 | { |
4339 | 0 | double |
4340 | 0 | pixel; |
4341 | |
|
4342 | 0 | PixelChannel |
4343 | 0 | channel; |
4344 | |
|
4345 | 0 | PixelTrait |
4346 | 0 | traits, |
4347 | 0 | unsharp_traits; |
4348 | |
|
4349 | 0 | channel=GetPixelChannelChannel(image,i); |
4350 | 0 | traits=GetPixelChannelTraits(image,channel); |
4351 | 0 | unsharp_traits=GetPixelChannelTraits(unsharp_image,channel); |
4352 | 0 | if ((traits == UndefinedPixelTrait) || |
4353 | 0 | (unsharp_traits == UndefinedPixelTrait)) |
4354 | 0 | continue; |
4355 | 0 | if ((unsharp_traits & CopyPixelTrait) != 0) |
4356 | 0 | { |
4357 | 0 | SetPixelChannel(unsharp_image,channel,p[i],q); |
4358 | 0 | continue; |
4359 | 0 | } |
4360 | 0 | pixel=(double) p[i]-(double) GetPixelChannel(unsharp_image,channel,q); |
4361 | 0 | if (fabs(2.0*pixel) < quantum_threshold) |
4362 | 0 | pixel=(double) p[i]; |
4363 | 0 | else |
4364 | 0 | pixel=(double) p[i]+gain*pixel; |
4365 | 0 | SetPixelChannel(unsharp_image,channel,ClampToQuantum(pixel),q); |
4366 | 0 | } |
4367 | 0 | p+=(ptrdiff_t) GetPixelChannels(image); |
4368 | 0 | q+=(ptrdiff_t) GetPixelChannels(unsharp_image); |
4369 | 0 | } |
4370 | 0 | if (SyncCacheViewAuthenticPixels(unsharp_view,exception) == MagickFalse) |
4371 | 0 | status=MagickFalse; |
4372 | 0 | if (image->progress_monitor != (MagickProgressMonitor) NULL) |
4373 | 0 | { |
4374 | 0 | MagickBooleanType |
4375 | 0 | proceed; |
4376 | |
|
4377 | | #if defined(MAGICKCORE_OPENMP_SUPPORT) |
4378 | | #pragma omp atomic |
4379 | | #endif |
4380 | 0 | progress++; |
4381 | 0 | proceed=SetImageProgress(image,SharpenImageTag,progress,image->rows); |
4382 | 0 | if (proceed == MagickFalse) |
4383 | 0 | status=MagickFalse; |
4384 | 0 | } |
4385 | 0 | } |
4386 | 0 | unsharp_image->type=image->type; |
4387 | 0 | unsharp_view=DestroyCacheView(unsharp_view); |
4388 | 0 | image_view=DestroyCacheView(image_view); |
4389 | 0 | if (status == MagickFalse) |
4390 | 0 | unsharp_image=DestroyImage(unsharp_image); |
4391 | 0 | return(unsharp_image); |
4392 | 0 | } |