1 /*
2 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3 % %
4 % %
5 % AAA N N AAA L Y Y ZZZZZ EEEEE %
6 % A A NN N A A L Y Y ZZ E %
7 % AAAAA N N N AAAAA L Y ZZZ EEE %
8 % A A N NN A A L Y ZZ E %
9 % A A N N A A LLLLL Y ZZZZZ EEEEE %
10 % %
11 % Analyze An Image %
12 % %
13 % Software Design %
14 % Bill Corbis %
15 % December 1998 %
16 % %
17 % %
18 % Copyright 1999-2021 ImageMagick Studio LLC, a non-profit organization %
19 % dedicated to making software imaging solutions freely available. %
20 % %
21 % You may not use this file except in compliance with the License. You may %
22 % obtain a copy of the License at %
23 % %
24 % https://imagemagick.org/script/license.php %
25 % %
26 % Unless required by applicable law or agreed to in writing, software %
27 % distributed under the License is distributed on an "AS IS" BASIS, %
28 % WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %
29 % See the License for the specific language governing permissions and %
30 % limitations under the License. %
31 % %
32 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
33 %
34 */
35
36 /*
37 Include declarations.
38 */
39 #include <stdio.h>
40 #include <stdlib.h>
41 #include <string.h>
42 #include <time.h>
43 #include <assert.h>
44 #include <math.h>
45 #include "MagickCore/studio.h"
46 #include "MagickCore/MagickCore.h"
47
48 /*
49 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
50 % %
51 % %
52 % %
53 % a n a l y z e I m a g e %
54 % %
55 % %
56 % %
57 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
58 %
59 % analyzeImage() computes the brightness and saturation mean, standard
60 % deviation, kurtosis and skewness and stores these values as attributes
61 % of the image.
62 %
63 % The format of the analyzeImage method is:
64 %
65 % size_t analyzeImage(Image *images,const int argc,char **argv,
66 % ExceptionInfo *exception)
67 %
68 % A description of each parameter follows:
69 %
70 % o image: the address of a structure of type Image.
71 %
72 % o argc: Specifies a pointer to an integer describing the number of
73 % elements in the argument vector.
74 %
75 % o argv: Specifies a pointer to a text array containing the command line
76 % arguments.
77 %
78 % o exception: return any errors or warnings in this structure.
79 %
80 */
81
82 typedef struct _StatisticsInfo
83 {
84 double
85 area,
86 brightness,
87 mean,
88 standard_deviation,
89 sum[5],
90 kurtosis,
91 skewness;
92 } StatisticsInfo;
93
GetMagickNumberThreads(const Image * source,const Image * destination,const size_t chunk,int multithreaded)94 static inline int GetMagickNumberThreads(const Image *source,
95 const Image *destination,const size_t chunk,int multithreaded)
96 {
97 #define MagickMax(x,y) (((x) > (y)) ? (x) : (y))
98 #define MagickMin(x,y) (((x) < (y)) ? (x) : (y))
99
100 /*
101 Number of threads bounded by the amount of work and any thread resource
102 limit. The limit is 2 if the pixel cache type is not memory or
103 memory-mapped.
104 */
105 if (multithreaded == 0)
106 return(1);
107 if (((GetImagePixelCacheType(source) != MemoryCache) &&
108 (GetImagePixelCacheType(source) != MapCache)) ||
109 ((GetImagePixelCacheType(destination) != MemoryCache) &&
110 (GetImagePixelCacheType(destination) != MapCache)))
111 return(MagickMax(MagickMin(GetMagickResourceLimit(ThreadResource),2),1));
112 return(MagickMax(MagickMin((ssize_t) GetMagickResourceLimit(ThreadResource),
113 (ssize_t) (chunk)/64),1));
114 }
115
analyzeImage(Image ** images,const int argc,const char ** argv,ExceptionInfo * exception)116 ModuleExport size_t analyzeImage(Image **images,const int argc,
117 const char **argv,ExceptionInfo *exception)
118 {
119 #define AnalyzeImageFilterTag "Filter/Analyze"
120 #define magick_number_threads(source,destination,chunk,multithreaded) \
121 num_threads(GetMagickNumberThreads(source,destination,chunk,multithreaded))
122
123 char
124 text[MagickPathExtent];
125
126 Image
127 *image;
128
129 MagickBooleanType
130 status;
131
132 MagickOffsetType
133 progress;
134
135 assert(images != (Image **) NULL);
136 assert(*images != (Image *) NULL);
137 assert((*images)->signature == MagickCoreSignature);
138 (void) argc;
139 (void) argv;
140 image=(*images);
141 status=MagickTrue;
142 progress=0;
143 for ( ; image != (Image *) NULL; image=GetNextImageInList(image))
144 {
145 CacheView
146 *image_view;
147
148 double
149 area;
150
151 ssize_t
152 y;
153
154 StatisticsInfo
155 brightness,
156 saturation;
157
158 if (status == MagickFalse)
159 continue;
160 (void) memset(&brightness,0,sizeof(brightness));
161 (void) memset(&saturation,0,sizeof(saturation));
162 status=MagickTrue;
163 image_view=AcquireVirtualCacheView(image,exception);
164 #if defined(MAGICKCORE_OPENMP_SUPPORT)
165 #pragma omp parallel for schedule(static) \
166 shared(progress,status,brightness,saturation) \
167 magick_number_threads(image,image,image->rows,1)
168 #endif
169 for (y=0; y < (ssize_t) image->rows; y++)
170 {
171 const Quantum
172 *p;
173
174 ssize_t
175 i,
176 x;
177
178 StatisticsInfo
179 local_brightness,
180 local_saturation;
181
182 if (status == MagickFalse)
183 continue;
184 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
185 if (p == (const Quantum *) NULL)
186 {
187 status=MagickFalse;
188 continue;
189 }
190 (void) memset(&local_brightness,0,sizeof(local_brightness));
191 (void) memset(&local_saturation,0,sizeof(local_saturation));
192 for (x=0; x < (ssize_t) image->columns; x++)
193 {
194 double
195 b,
196 h,
197 s;
198
199 ConvertRGBToHSL(GetPixelRed(image,p),GetPixelGreen(image,p),
200 GetPixelBlue(image,p),&h,&s,&b);
201 b*=QuantumRange;
202 for (i=1; i <= 4; i++)
203 local_brightness.sum[i]+=pow(b,(double) i);
204 s*=QuantumRange;
205 for (i=1; i <= 4; i++)
206 local_saturation.sum[i]+=pow(s,(double) i);
207 p+=GetPixelChannels(image);
208 }
209 #if defined(MAGICKCORE_OPENMP_SUPPORT)
210 #pragma omp critical (analyzeImage)
211 #endif
212 for (i=1; i <= 4; i++)
213 {
214 brightness.sum[i]+=local_brightness.sum[i];
215 saturation.sum[i]+=local_saturation.sum[i];
216 }
217 }
218 image_view=DestroyCacheView(image_view);
219 area=(double) image->columns*image->rows;
220 brightness.mean=brightness.sum[1]/area;
221 (void) FormatLocaleString(text,MagickPathExtent,"%g",brightness.mean);
222 (void) SetImageProperty(image,"filter:brightness:mean",text,exception);
223 brightness.standard_deviation=sqrt(brightness.sum[2]/area-
224 (brightness.sum[1]/area*brightness.sum[1]/area));
225 (void) FormatLocaleString(text,MagickPathExtent,"%g",
226 brightness.standard_deviation);
227 (void) SetImageProperty(image,"filter:brightness:standard-deviation",text,
228 exception);
229 if (fabs(brightness.standard_deviation) >= MagickEpsilon)
230 brightness.kurtosis=(brightness.sum[4]/area-4.0*brightness.mean*
231 brightness.sum[3]/area+6.0*brightness.mean*brightness.mean*
232 brightness.sum[2]/area-3.0*brightness.mean*brightness.mean*
233 brightness.mean*brightness.mean)/(brightness.standard_deviation*
234 brightness.standard_deviation*brightness.standard_deviation*
235 brightness.standard_deviation)-3.0;
236 (void) FormatLocaleString(text,MagickPathExtent,"%g",brightness.kurtosis);
237 (void) SetImageProperty(image,"filter:brightness:kurtosis",text,exception);
238 if (brightness.standard_deviation != 0)
239 brightness.skewness=(brightness.sum[3]/area-3.0*brightness.mean*
240 brightness.sum[2]/area+2.0*brightness.mean*brightness.mean*
241 brightness.mean)/(brightness.standard_deviation*
242 brightness.standard_deviation*brightness.standard_deviation);
243 (void) FormatLocaleString(text,MagickPathExtent,"%g",brightness.skewness);
244 (void) SetImageProperty(image,"filter:brightness:skewness",text,exception);
245 saturation.mean=saturation.sum[1]/area;
246 (void) FormatLocaleString(text,MagickPathExtent,"%g",saturation.mean);
247 (void) SetImageProperty(image,"filter:saturation:mean",text,exception);
248 saturation.standard_deviation=sqrt(saturation.sum[2]/area-
249 (saturation.sum[1]/area*saturation.sum[1]/area));
250 (void) FormatLocaleString(text,MagickPathExtent,"%g",
251 saturation.standard_deviation);
252 (void) SetImageProperty(image,"filter:saturation:standard-deviation",text,
253 exception);
254 if (fabs(saturation.standard_deviation) >= MagickEpsilon)
255 saturation.kurtosis=(saturation.sum[4]/area-4.0*saturation.mean*
256 saturation.sum[3]/area+6.0*saturation.mean*saturation.mean*
257 saturation.sum[2]/area-3.0*saturation.mean*saturation.mean*
258 saturation.mean*saturation.mean)/(saturation.standard_deviation*
259 saturation.standard_deviation*saturation.standard_deviation*
260 saturation.standard_deviation)-3.0;
261 (void) FormatLocaleString(text,MagickPathExtent,"%g",saturation.kurtosis);
262 (void) SetImageProperty(image,"filter:saturation:kurtosis",text,exception);
263 if (fabs(saturation.standard_deviation) >= MagickEpsilon)
264 saturation.skewness=(saturation.sum[3]/area-3.0*saturation.mean*
265 saturation.sum[2]/area+2.0*saturation.mean*saturation.mean*
266 saturation.mean)/(saturation.standard_deviation*
267 saturation.standard_deviation*saturation.standard_deviation);
268 (void) FormatLocaleString(text,MagickPathExtent,"%g",saturation.skewness);
269 (void) SetImageProperty(image,"filter:saturation:skewness",text,exception);
270 if (image->progress_monitor != (MagickProgressMonitor) NULL)
271 {
272 MagickBooleanType
273 proceed;
274
275 #if defined(MAGICKCORE_OPENMP_SUPPORT)
276 #pragma omp atomic
277 #endif
278 progress++;
279 proceed=SetImageProgress(image,AnalyzeImageFilterTag,progress,
280 GetImageListLength(image));
281 if (proceed == MagickFalse)
282 status=MagickFalse;
283 }
284 }
285 return(MagickImageFilterSignature);
286 }
287