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
94 #if defined(MAGICKCORE_OPENMP_SUPPORT)
GetMagickNumberThreads(const Image * source,const Image * destination,const size_t chunk,int multithreaded)95 static inline int GetMagickNumberThreads(const Image *source,
96 const Image *destination,const size_t chunk,int multithreaded)
97 {
98 #define MagickMax(x,y) (((x) > (y)) ? (x) : (y))
99 #define MagickMin(x,y) (((x) < (y)) ? (x) : (y))
100
101 /*
102 Number of threads bounded by the amount of work and any thread resource
103 limit. The limit is 2 if the pixel cache type is not memory or
104 memory-mapped.
105 */
106 if (multithreaded == 0)
107 return(1);
108 if (((GetImagePixelCacheType(source) != MemoryCache) &&
109 (GetImagePixelCacheType(source) != MapCache)) ||
110 ((GetImagePixelCacheType(destination) != MemoryCache) &&
111 (GetImagePixelCacheType(destination) != MapCache)))
112 return(MagickMax(MagickMin(GetMagickResourceLimit(ThreadResource),2),1));
113 return(MagickMax(MagickMin((int) GetMagickResourceLimit(ThreadResource),
114 (int) (chunk)/64),1));
115 }
116 #endif
117
analyzeImage(Image ** images,const int argc,const char ** argv,ExceptionInfo * exception)118 ModuleExport size_t analyzeImage(Image **images,const int argc,
119 const char **argv,ExceptionInfo *exception)
120 {
121 #define AnalyzeImageFilterTag "Filter/Analyze"
122 #define magick_number_threads(source,destination,chunk,multithreaded) \
123 num_threads(GetMagickNumberThreads(source,destination,chunk,multithreaded))
124
125 char
126 text[MagickPathExtent];
127
128 Image
129 *image;
130
131 MagickBooleanType
132 status;
133
134 MagickOffsetType
135 progress;
136
137 assert(images != (Image **) NULL);
138 assert(*images != (Image *) NULL);
139 assert((*images)->signature == MagickCoreSignature);
140 (void) argc;
141 (void) argv;
142 image=(*images);
143 status=MagickTrue;
144 progress=0;
145 for ( ; image != (Image *) NULL; image=GetNextImageInList(image))
146 {
147 CacheView
148 *image_view;
149
150 double
151 area;
152
153 ssize_t
154 y;
155
156 StatisticsInfo
157 brightness,
158 saturation;
159
160 if (status == MagickFalse)
161 continue;
162 (void) memset(&brightness,0,sizeof(brightness));
163 (void) memset(&saturation,0,sizeof(saturation));
164 status=MagickTrue;
165 image_view=AcquireVirtualCacheView(image,exception);
166 #if defined(MAGICKCORE_OPENMP_SUPPORT)
167 #pragma omp parallel for schedule(static) \
168 shared(progress,status,brightness,saturation) \
169 magick_number_threads(image,image,image->rows,1)
170 #endif
171 for (y=0; y < (ssize_t) image->rows; y++)
172 {
173 const Quantum
174 *p;
175
176 ssize_t
177 i,
178 x;
179
180 StatisticsInfo
181 local_brightness,
182 local_saturation;
183
184 if (status == MagickFalse)
185 continue;
186 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
187 if (p == (const Quantum *) NULL)
188 {
189 status=MagickFalse;
190 continue;
191 }
192 (void) memset(&local_brightness,0,sizeof(local_brightness));
193 (void) memset(&local_saturation,0,sizeof(local_saturation));
194 for (x=0; x < (ssize_t) image->columns; x++)
195 {
196 double
197 b,
198 h,
199 s;
200
201 ConvertRGBToHSL(GetPixelRed(image,p),GetPixelGreen(image,p),
202 GetPixelBlue(image,p),&h,&s,&b);
203 b*=QuantumRange;
204 for (i=1; i <= 4; i++)
205 local_brightness.sum[i]+=pow(b,(double) i);
206 s*=QuantumRange;
207 for (i=1; i <= 4; i++)
208 local_saturation.sum[i]+=pow(s,(double) i);
209 p+=GetPixelChannels(image);
210 }
211 #if defined(MAGICKCORE_OPENMP_SUPPORT)
212 #pragma omp critical (analyzeImage)
213 #endif
214 for (i=1; i <= 4; i++)
215 {
216 brightness.sum[i]+=local_brightness.sum[i];
217 saturation.sum[i]+=local_saturation.sum[i];
218 }
219 }
220 image_view=DestroyCacheView(image_view);
221 area=(double) image->columns*image->rows;
222 brightness.mean=brightness.sum[1]/area;
223 (void) FormatLocaleString(text,MagickPathExtent,"%g",brightness.mean);
224 (void) SetImageProperty(image,"filter:brightness:mean",text,exception);
225 brightness.standard_deviation=sqrt(brightness.sum[2]/area-
226 (brightness.sum[1]/area*brightness.sum[1]/area));
227 (void) FormatLocaleString(text,MagickPathExtent,"%g",
228 brightness.standard_deviation);
229 (void) SetImageProperty(image,"filter:brightness:standard-deviation",text,
230 exception);
231 if (fabs(brightness.standard_deviation) >= MagickEpsilon)
232 brightness.kurtosis=(brightness.sum[4]/area-4.0*brightness.mean*
233 brightness.sum[3]/area+6.0*brightness.mean*brightness.mean*
234 brightness.sum[2]/area-3.0*brightness.mean*brightness.mean*
235 brightness.mean*brightness.mean)/(brightness.standard_deviation*
236 brightness.standard_deviation*brightness.standard_deviation*
237 brightness.standard_deviation)-3.0;
238 (void) FormatLocaleString(text,MagickPathExtent,"%g",brightness.kurtosis);
239 (void) SetImageProperty(image,"filter:brightness:kurtosis",text,exception);
240 if (brightness.standard_deviation != 0)
241 brightness.skewness=(brightness.sum[3]/area-3.0*brightness.mean*
242 brightness.sum[2]/area+2.0*brightness.mean*brightness.mean*
243 brightness.mean)/(brightness.standard_deviation*
244 brightness.standard_deviation*brightness.standard_deviation);
245 (void) FormatLocaleString(text,MagickPathExtent,"%g",brightness.skewness);
246 (void) SetImageProperty(image,"filter:brightness:skewness",text,exception);
247 saturation.mean=saturation.sum[1]/area;
248 (void) FormatLocaleString(text,MagickPathExtent,"%g",saturation.mean);
249 (void) SetImageProperty(image,"filter:saturation:mean",text,exception);
250 saturation.standard_deviation=sqrt(saturation.sum[2]/area-
251 (saturation.sum[1]/area*saturation.sum[1]/area));
252 (void) FormatLocaleString(text,MagickPathExtent,"%g",
253 saturation.standard_deviation);
254 (void) SetImageProperty(image,"filter:saturation:standard-deviation",text,
255 exception);
256 if (fabs(saturation.standard_deviation) >= MagickEpsilon)
257 saturation.kurtosis=(saturation.sum[4]/area-4.0*saturation.mean*
258 saturation.sum[3]/area+6.0*saturation.mean*saturation.mean*
259 saturation.sum[2]/area-3.0*saturation.mean*saturation.mean*
260 saturation.mean*saturation.mean)/(saturation.standard_deviation*
261 saturation.standard_deviation*saturation.standard_deviation*
262 saturation.standard_deviation)-3.0;
263 (void) FormatLocaleString(text,MagickPathExtent,"%g",saturation.kurtosis);
264 (void) SetImageProperty(image,"filter:saturation:kurtosis",text,exception);
265 if (fabs(saturation.standard_deviation) >= MagickEpsilon)
266 saturation.skewness=(saturation.sum[3]/area-3.0*saturation.mean*
267 saturation.sum[2]/area+2.0*saturation.mean*saturation.mean*
268 saturation.mean)/(saturation.standard_deviation*
269 saturation.standard_deviation*saturation.standard_deviation);
270 (void) FormatLocaleString(text,MagickPathExtent,"%g",saturation.skewness);
271 (void) SetImageProperty(image,"filter:saturation:skewness",text,exception);
272 if (image->progress_monitor != (MagickProgressMonitor) NULL)
273 {
274 MagickBooleanType
275 proceed;
276
277 #if defined(MAGICKCORE_OPENMP_SUPPORT)
278 #pragma omp atomic
279 #endif
280 progress++;
281 proceed=SetImageProgress(image,AnalyzeImageFilterTag,progress,
282 GetImageListLength(image));
283 if (proceed == MagickFalse)
284 status=MagickFalse;
285 }
286 }
287 return(MagickImageFilterSignature);
288 }
289