1/*
2%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3%                                                                             %
4%                                                                             %
5%                                                                             %
6%                        JJJJJ  SSSSS   OOO   N   N                           %
7%                          J    SS     O   O  NN  N                           %
8%                          J     SSS   O   O  N N N                           %
9%                        J J       SS  O   O  N  NN                           %
10%                        JJJ    SSSSS   OOO   N   N                           %
11%                                                                             %
12%                                                                             %
13%                  Write Info About the Image in JSON Format.                 %
14%                                                                             %
15%                              Software Design                                %
16%                                   Cristy                                    %
17%                                January 2014                                 %
18%                                                                             %
19%                                                                             %
20%  Copyright 1999-2016 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%    http://www.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  Include declarations.
41*/
42#include "MagickCore/studio.h"
43#include "MagickCore/artifact.h"
44#include "MagickCore/attribute.h"
45#include "MagickCore/blob.h"
46#include "MagickCore/blob-private.h"
47#include "MagickCore/cache.h"
48#include "MagickCore/colorspace.h"
49#include "MagickCore/colorspace-private.h"
50#include "MagickCore/constitute.h"
51#include "MagickCore/exception.h"
52#include "MagickCore/exception-private.h"
53#include "MagickCore/feature.h"
54#include "MagickCore/image.h"
55#include "MagickCore/image-private.h"
56#include "MagickCore/list.h"
57#include "MagickCore/magick.h"
58#include "MagickCore/memory_.h"
59#include "MagickCore/monitor.h"
60#include "MagickCore/monitor-private.h"
61#include "MagickCore/option.h"
62#include "MagickCore/pixel.h"
63#include "MagickCore/pixel-accessor.h"
64#include "MagickCore/pixel-private.h"
65#include "MagickCore/prepress.h"
66#include "MagickCore/property.h"
67#include "MagickCore/quantum-private.h"
68#include "MagickCore/registry.h"
69#include "MagickCore/signature.h"
70#include "MagickCore/static.h"
71#include "MagickCore/statistic.h"
72#include "MagickCore/string_.h"
73#include "MagickCore/string-private.h"
74#include "MagickCore/utility.h"
75#include "MagickCore/version.h"
76#include "MagickCore/module.h"
77
78/*
79  Forward declarations.
80*/
81static MagickBooleanType
82  WriteJSONImage(const ImageInfo *,Image *,ExceptionInfo *);
83
84/*
85%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
86%                                                                             %
87%                                                                             %
88%                                                                             %
89%   R e g i s t e r J S O N I m a g e                                         %
90%                                                                             %
91%                                                                             %
92%                                                                             %
93%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
94%
95%  RegisterJSONImage() adds attributes for the JSON image format to
96%  the list of supported formats.  The attributes include the image format
97%  tag, a method to read and/or write the format, whether the format
98%  supports the saving of more than one frame to the same file or blob,
99%  whether the format supports native in-memory I/O, and a brief
100%  description of the format.
101%
102%  The format of the RegisterJSONImage method is:
103%
104%      size_t RegisterJSONImage(void)
105%
106*/
107ModuleExport size_t RegisterJSONImage(void)
108{
109  MagickInfo
110    *entry;
111
112  entry=AcquireMagickInfo("JSON","JSON","The image format and characteristics");
113  entry->encoder=(EncodeImageHandler *) WriteJSONImage;
114  entry->mime_type=ConstantString("application/json");
115  entry->flags^=CoderBlobSupportFlag;
116  (void) RegisterMagickInfo(entry);
117  return(MagickImageCoderSignature);
118}
119
120/*
121%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
122%                                                                             %
123%                                                                             %
124%                                                                             %
125%   U n r e g i s t e r J S O N I m a g e                                     %
126%                                                                             %
127%                                                                             %
128%                                                                             %
129%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
130%
131%  UnregisterJSONImage() removes format registrations made by the
132%  JSON module from the list of supported formats.
133%
134%  The format of the UnregisterJSONImage method is:
135%
136%      UnregisterJSONImage(void)
137%
138*/
139ModuleExport void UnregisterJSONImage(void)
140{
141  (void) UnregisterMagickInfo("JSON");
142}
143
144/*
145%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
146%                                                                             %
147%                                                                             %
148%                                                                             %
149%   W r i t e J S O N I m a g e                                               %
150%                                                                             %
151%                                                                             %
152%                                                                             %
153%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
154%
155%  WriteJSONImage writes the image attributes in the JSON format.
156%
157%  The format of the WriteJSONImage method is:
158%
159%      MagickBooleanType WriteJSONImage(const ImageInfo *image_info,
160%        Image *image,ExceptionInfo *exception)
161%
162%  A description of each parameter follows.
163%
164%    o image_info: the image info.
165%
166%    o image:  The image.
167%
168%    o exception: return any errors or warnings in this structure.
169%
170*/
171
172static void JsonFormatLocaleFile(FILE *file,const char *format,const char *value)
173{
174  char
175    *escaped_json;
176
177  register char
178    *q;
179
180  register const char
181    *p;
182
183  size_t
184    length;
185
186  assert(format != (const char *) NULL);
187  if (value == (char *) NULL || *value == '\0')
188  {
189    (void) FormatLocaleFile(file,format,"null");
190    return;
191  }
192  length=strlen(value)+2;
193  /*
194    Find all the chars that need escaping and increase the dest length counter
195  */
196  for (p=value; *p != '\0'; p++)
197    {
198      switch (*p)
199        {
200          case '"':
201          case '\b':
202          case '\f':
203          case '\n':
204          case '\r':
205          case '\t':
206          case '\\':
207            if (~length < 1)
208              return;
209            length++;
210            break;
211          default:
212            break;
213        }
214    }
215  escaped_json=(char *) NULL;
216  if (~length >= (MagickPathExtent-1))
217    escaped_json=(char *) AcquireQuantumMemory(length+MagickPathExtent,
218      sizeof(*escaped_json));
219  if (escaped_json == (char *) NULL)
220  {
221    (void) FormatLocaleFile(file,format,"null");
222    return;
223  }
224  q=escaped_json;
225  *q++='"';
226  for (p=value; *p != '\0'; p++)
227    {
228      switch (*p)
229        {
230          case '"':
231            *q++='\\';
232            *q++=(*p);
233            break;
234          case '\b':
235            *q++='\\';
236            *q++='b';
237            break;
238          case '\f':
239            *q++='\\';
240            *q++='f';
241            break;
242          case '\n':
243            *q++='\\';
244            *q++='n';
245            break;
246          case '\r':
247            *q++='\\';
248            *q++='r';
249            break;
250          case '\t':
251            *q++='\\';
252            *q++='t';
253            break;
254          case '\\':
255            *q++='\\';
256            *q++='\\';
257            break;
258          default:
259            *q++=(*p);
260            break;
261        }
262  }
263  *q++='"';
264  *q='\0';
265  (void) FormatLocaleFile(file,format,escaped_json);
266  (void) DestroyString(escaped_json);
267}
268
269static ChannelStatistics *GetLocationStatistics(const Image *image,
270  const StatisticType type,ExceptionInfo *exception)
271{
272  ChannelStatistics
273    *channel_statistics;
274
275  register ssize_t
276    i;
277
278  ssize_t
279    y;
280
281  assert(image != (Image *) NULL);
282  assert(image->signature == MagickCoreSignature);
283  if (image->debug != MagickFalse)
284    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
285  channel_statistics=(ChannelStatistics *) AcquireQuantumMemory(
286    MaxPixelChannels+1,sizeof(*channel_statistics));
287  if (channel_statistics == (ChannelStatistics *) NULL)
288    ThrowFatalException(ResourceLimitFatalError,"MemoryAllocationFailed");
289  (void) ResetMagickMemory(channel_statistics,0,(MaxPixelChannels+1)*
290    sizeof(*channel_statistics));
291  for (i=0; i <= (ssize_t) MaxPixelChannels; i++)
292  {
293    switch (type)
294    {
295      case MaximumStatistic:
296      default:
297      {
298        channel_statistics[i].maxima=(-MagickMaximumValue);
299        break;
300      }
301      case MinimumStatistic:
302      {
303        channel_statistics[i].minima=MagickMaximumValue;
304        break;
305      }
306    }
307  }
308  for (y=0; y < (ssize_t) image->rows; y++)
309  {
310    register const Quantum
311      *magick_restrict p;
312
313    register ssize_t
314      x;
315
316    p=GetVirtualPixels(image,0,y,image->columns,1,exception);
317    if (p == (const Quantum *) NULL)
318      break;
319    for (x=0; x < (ssize_t) image->columns; x++)
320    {
321      register ssize_t
322        i;
323
324      if (GetPixelReadMask(image,p) == 0)
325        {
326          p+=GetPixelChannels(image);
327          continue;
328        }
329      for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
330      {
331        PixelChannel channel=GetPixelChannelChannel(image,i);
332        PixelTrait traits=GetPixelChannelTraits(image,channel);
333        if (traits == UndefinedPixelTrait)
334          continue;
335        switch (type)
336        {
337          case MaximumStatistic:
338          default:
339          {
340            if ((double) p[i] > channel_statistics[channel].maxima)
341              channel_statistics[channel].maxima=(double) p[i];
342            break;
343          }
344          case MinimumStatistic:
345          {
346            if ((double) p[i] < channel_statistics[channel].minima)
347              channel_statistics[channel].minima=(double) p[i];
348            break;
349          }
350        }
351      }
352      p+=GetPixelChannels(image);
353    }
354  }
355  return(channel_statistics);
356}
357
358static ssize_t PrintChannelFeatures(FILE *file,const PixelChannel channel,
359  const char *name,const MagickBooleanType separator,
360  const ChannelFeatures *channel_features)
361{
362#define PrintFeature(feature) \
363  GetMagickPrecision(),(feature)[0], \
364  GetMagickPrecision(),(feature)[1], \
365  GetMagickPrecision(),(feature)[2], \
366  GetMagickPrecision(),(feature)[3], \
367  GetMagickPrecision(),((feature)[0]+(feature)[1]+(feature)[2]+(feature)[3])/4.0 \
368
369#define FeaturesFormat "      \"%s\": {\n" \
370  "        \"angularSecondMoment\": {\n" \
371  "          \"horizontal\": \"%.*g\",\n" \
372  "          \"vertical\": \"%.*g\",\n" \
373  "          \"leftDiagonal\": \"%.*g\",\n" \
374  "          \"rightDiagonal\": \"%.*g\",\n" \
375  "          \"average\": \"%.*g\"\n" \
376  "        },\n" \
377  "        \"contrast\": {\n" \
378  "          \"horizontal\": \"%.*g\",\n" \
379  "          \"vertical\": \"%.*g\",\n" \
380  "          \"leftDiagonal\": \"%.*g\",\n" \
381  "          \"rightDiagonal\": \"%.*g\",\n" \
382  "          \"average\": \"%.*g\"\n" \
383  "        },\n" \
384  "        \"correlation\": {\n" \
385  "          \"horizontal\": \"%.*g\",\n" \
386  "          \"vertical\": \"%.*g\",\n" \
387  "          \"leftDiagonal\": \"%.*g\",\n" \
388  "          \"rightDiagonal\": \"%.*g\",\n" \
389  "          \"average\": \"%.*g\"\n" \
390  "        },\n" \
391  "        \"sumOfSquaresVariance\": {\n" \
392  "          \"horizontal\": \"%.*g\",\n" \
393  "          \"vertical\": \"%.*g\",\n" \
394  "          \"leftDiagonal\": \"%.*g\",\n" \
395  "          \"rightDiagonal\": \"%.*g\",\n" \
396  "          \"average\": \"%.*g\"\n" \
397  "        },\n" \
398  "        \"inverseDifferenceMoment\": {\n" \
399  "          \"horizontal\": \"%.*g\",\n" \
400  "          \"vertical\": \"%.*g\",\n" \
401  "          \"leftDiagonal\": \"%.*g\",\n" \
402  "          \"rightDiagonal\": \"%.*g\",\n" \
403  "          \"average\": \"%.*g\"\n" \
404  "        },\n" \
405  "        \"sumAverage\": {\n" \
406  "          \"horizontal\": \"%.*g\",\n" \
407  "          \"vertical\": \"%.*g\",\n" \
408  "          \"leftDiagonal\": \"%.*g\",\n" \
409  "          \"rightDiagonal\": \"%.*g\",\n" \
410  "          \"average\": \"%.*g\"\n" \
411  "        },\n" \
412  "        \"sumVariance\": {\n" \
413  "          \"horizontal\": \"%.*g\",\n" \
414  "          \"vertical\": \"%.*g\",\n" \
415  "          \"leftDiagonal\": \"%.*g\",\n" \
416  "          \"rightDiagonal\": \"%.*g\",\n" \
417  "          \"average\": \"%.*g\"\n" \
418  "        },\n" \
419  "        \"sumEntropy\": {\n" \
420  "          \"horizontal\": \"%.*g\",\n" \
421  "          \"vertical\": \"%.*g\",\n" \
422  "          \"leftDiagonal\": \"%.*g\",\n" \
423  "          \"rightDiagonal\": \"%.*g\",\n" \
424  "          \"average\": \"%.*g\"\n" \
425  "        },\n" \
426  "        \"entropy\": {\n" \
427  "          \"horizontal\": \"%.*g\",\n" \
428  "          \"vertical\": \"%.*g\",\n" \
429  "          \"leftDiagonal\": \"%.*g\",\n" \
430  "          \"rightDiagonal\": \"%.*g\",\n" \
431  "          \"average\": \"%.*g\"\n" \
432  "        },\n" \
433  "        \"differenceVariance\": {\n" \
434  "          \"horizontal\": \"%.*g\",\n" \
435  "          \"vertical\": \"%.*g\",\n" \
436  "          \"leftDiagonal\": \"%.*g\",\n" \
437  "          \"rightDiagonal\": \"%.*g\",\n" \
438  "          \"average\": \"%.*g\"\n" \
439  "        },\n" \
440  "        \"differenceEntropy\": {\n" \
441  "          \"horizontal\": \"%.*g\",\n" \
442  "          \"vertical\": \"%.*g\",\n" \
443  "          \"leftDiagonal\": \"%.*g\",\n" \
444  "          \"rightDiagonal\": \"%.*g\",\n" \
445  "          \"average\": \"%.*g\"\n" \
446  "        },\n" \
447  "        \"informationMeasureOfCorrelation1\": {\n" \
448  "          \"horizontal\": \"%.*g\",\n" \
449  "          \"vertical\": \"%.*g\",\n" \
450  "          \"leftDiagonal\": \"%.*g\",\n" \
451  "          \"rightDiagonal\": \"%.*g\",\n" \
452  "          \"average\": \"%.*g\"\n" \
453  "        },\n" \
454  "        \"informationMeasureOfCorrelation2\": {\n" \
455  "          \"horizontal\": \"%.*g\",\n" \
456  "          \"vertical\": \"%.*g\",\n" \
457  "          \"leftDiagonal\": \"%.*g\",\n" \
458  "          \"rightDiagonal\": \"%.*g\",\n" \
459  "          \"average\": \"%.*g\"\n" \
460  "        },\n" \
461  "        \"maximumCorrelationCoefficient\": {\n" \
462  "          \"horizontal\": \"%.*g\",\n" \
463  "          \"vertical\": \"%.*g\",\n" \
464  "          \"leftDiagonal\": \"%.*g\",\n" \
465  "          \"rightDiagonal\": \"%.*g\",\n" \
466  "          \"average\": \"%.*g\"\n" \
467  "        }\n"
468
469  ssize_t
470    n;
471
472  n=FormatLocaleFile(file,FeaturesFormat,name,
473    PrintFeature(channel_features[channel].angular_second_moment),
474    PrintFeature(channel_features[channel].contrast),
475    PrintFeature(channel_features[channel].correlation),
476    PrintFeature(channel_features[channel].variance_sum_of_squares),
477    PrintFeature(channel_features[channel].inverse_difference_moment),
478    PrintFeature(channel_features[channel].sum_average),
479    PrintFeature(channel_features[channel].sum_variance),
480    PrintFeature(channel_features[channel].sum_entropy),
481    PrintFeature(channel_features[channel].entropy),
482    PrintFeature(channel_features[channel].difference_variance),
483    PrintFeature(channel_features[channel].difference_entropy),
484    PrintFeature(channel_features[channel].measure_of_correlation_1),
485    PrintFeature(channel_features[channel].measure_of_correlation_2),
486    PrintFeature(channel_features[channel].maximum_correlation_coefficient));
487  (void) FormatLocaleFile(file,"      }");
488  if (separator != MagickFalse)
489    (void) FormatLocaleFile(file,",");
490  (void) FormatLocaleFile(file,"\n");
491  return(n);
492}
493
494static ssize_t PrintChannelLocations(FILE *file,const Image *image,
495  const PixelChannel channel,const char *name,const StatisticType type,
496  const size_t max_locations,const MagickBooleanType separator,
497  const ChannelStatistics *channel_statistics)
498{
499  double
500    target;
501
502  ExceptionInfo
503    *exception;
504
505  ssize_t
506    n,
507    y;
508
509  switch (type)
510  {
511    case MaximumStatistic:
512    default:
513    {
514      target=channel_statistics[channel].maxima;
515      break;
516    }
517    case MinimumStatistic:
518    {
519      target=channel_statistics[channel].minima;
520      break;
521    }
522  }
523  (void) FormatLocaleFile(file,"      \"%s\": {\n        \"intensity\": "
524    "\"%.*g\",\n",name,GetMagickPrecision(),QuantumScale*target);
525  exception=AcquireExceptionInfo();
526  n=0;
527  for (y=0; y < (ssize_t) image->rows; y++)
528  {
529    register const Quantum
530      *p;
531
532    ssize_t
533      offset,
534      x;
535
536    p=GetVirtualPixels(image,0,y,image->columns,1,exception);
537    if (p == (const Quantum *) NULL)
538      break;
539    for (x=0; x < (ssize_t) image->columns; x++)
540    {
541      MagickBooleanType
542        match;
543
544      PixelTrait traits=GetPixelChannelTraits(image,channel);
545      if (traits == UndefinedPixelTrait)
546        continue;
547      offset=GetPixelChannelOffset(image,channel);
548      match=fabs((double) (p[offset]-target)) < 0.5 ? MagickTrue : MagickFalse;
549      if (match != MagickFalse)
550        {
551          if ((max_locations != 0) && (n >= (ssize_t) max_locations))
552            break;
553          if (n != 0)
554            (void) FormatLocaleFile(file,",\n");
555          (void) FormatLocaleFile(file,"        \"location%.20g\": {\n"
556            "          \"x\": %.20g,\n          \"y\": %.20g\n"
557            "        }",(double) n,(double) x,(double) y);
558          n++;
559        }
560      p+=GetPixelChannels(image);
561    }
562    if (x < (ssize_t) image->columns)
563      break;
564  }
565  (void) FormatLocaleFile(file,"\n      }");
566  if (separator != MagickFalse)
567    (void) FormatLocaleFile(file,",");
568  (void) FormatLocaleFile(file,"\n");
569  return(n);
570}
571
572static ssize_t PrintChannelMoments(FILE *file,const PixelChannel channel,
573  const char *name,const MagickBooleanType separator,
574  const ChannelMoments *channel_moments)
575{
576  register ssize_t
577    i;
578
579  ssize_t
580    n;
581
582  n=FormatLocaleFile(file,"      \"%s\": {\n",name);
583  n+=FormatLocaleFile(file,"        \"centroid\": {\n "
584    "          \"x\": \"%.*g\",\n"
585    "           \"y\": \"%.*g\"\n        },\n",
586    GetMagickPrecision(),channel_moments[channel].centroid.x,
587    GetMagickPrecision(),channel_moments[channel].centroid.y);
588  n+=FormatLocaleFile(file,"        \"ellipseSemiMajorMinorAxis\": {\n"
589    "          \"x\": \"%.*g\",\n"
590    "          \"y\": \"%.*g\"\n        },\n",
591    GetMagickPrecision(),channel_moments[channel].ellipse_axis.x,
592    GetMagickPrecision(),channel_moments[channel].ellipse_axis.y);
593  n+=FormatLocaleFile(file,"        \"ellipseAngle\": \"%.*g\",\n",
594    GetMagickPrecision(),channel_moments[channel].ellipse_angle);
595  n+=FormatLocaleFile(file,"        \"ellipseEccentricity\": \"%.*g\",\n",
596    GetMagickPrecision(),channel_moments[channel].ellipse_eccentricity);
597  n+=FormatLocaleFile(file,"        \"ellipseIntensity\": \"%.*g\",\n",
598    GetMagickPrecision(),channel_moments[channel].ellipse_intensity);
599  for (i=0; i < 7; i++)
600    n+=FormatLocaleFile(file,"        \"I%.20g\": \"%.*g\",\n",i+1.0,
601      GetMagickPrecision(),channel_moments[channel].invariant[i]);
602  n+=FormatLocaleFile(file,"        \"I%.20g\": \"%.*g\"\n",i+1.0,
603    GetMagickPrecision(),channel_moments[channel].invariant[i]);
604  (void) FormatLocaleFile(file,"      }");
605  if (separator != MagickFalse)
606    (void) FormatLocaleFile(file,",");
607  (void) FormatLocaleFile(file,"\n");
608  return(n);
609}
610
611static ssize_t PrintChannelPerceptualHash(FILE *file,const ChannelType channel,
612  const char *name,const MagickBooleanType separator,
613  const ChannelPerceptualHash *channel_phash)
614{
615  register ssize_t
616    i;
617
618  ssize_t
619    n;
620
621  n=FormatLocaleFile(file,"      \"%s\": {\n",name);
622  for (i=0; i < 6; i++)
623    n+=FormatLocaleFile(file,
624      "        \"PH%.20g\": [ \"%.*g\", \"%.*g\" ],\n",i+1.0,
625      GetMagickPrecision(),channel_phash[channel].srgb_hu_phash[i],
626      GetMagickPrecision(),channel_phash[channel].hclp_hu_phash[i]);
627  n+=FormatLocaleFile(file,
628    "        \"PH%.20g\": [ \"%.*g\", \"%.*g\" ]\n",i+1.0,
629    GetMagickPrecision(),channel_phash[channel].srgb_hu_phash[i],
630    GetMagickPrecision(),channel_phash[channel].hclp_hu_phash[i]);
631  (void) FormatLocaleFile(file,"      }");
632  if (separator != MagickFalse)
633    (void) FormatLocaleFile(file,",");
634  (void) FormatLocaleFile(file,"\n");
635  return(n);
636}
637
638static ssize_t PrintChannelStatistics(FILE *file,const PixelChannel channel,
639  const char *name,const double scale,const MagickBooleanType separator,
640  const ChannelStatistics *channel_statistics)
641{
642#define StatisticsFormat "      \"%s\": {\n        \"min\": \"" QuantumFormat  \
643  "\",\n        \"max\": \"" QuantumFormat "\",\n"  \
644  "        \"mean\": \"%g\",\n        \"standardDeviation\": "  \
645  "\"%g\",\n        \"kurtosis\": \"%g\",\n        \"skewness\": " \
646  "\"%g\"\n      }"
647
648  ssize_t
649    n;
650
651  n=FormatLocaleFile(file,StatisticsFormat,name,ClampToQuantum(scale*
652    channel_statistics[channel].minima),ClampToQuantum(scale*
653    channel_statistics[channel].maxima),scale*channel_statistics[channel].mean,
654    scale*channel_statistics[channel].standard_deviation,
655    channel_statistics[channel].kurtosis,channel_statistics[channel].skewness);
656  if (separator != MagickFalse)
657    (void) FormatLocaleFile(file,",");
658  (void) FormatLocaleFile(file,"\n");
659  return(n);
660}
661
662static MagickBooleanType EncodeImageAttributes(Image *image,FILE *file,
663  ExceptionInfo *exception)
664{
665  char
666    color[MagickPathExtent],
667    format[MagickPathExtent],
668    key[MagickPathExtent];
669
670  ChannelFeatures
671    *channel_features;
672
673  ChannelMoments
674    *channel_moments;
675
676  ChannelPerceptualHash
677    *channel_phash;
678
679  ChannelStatistics
680    *channel_statistics;
681
682  ColorspaceType
683    colorspace;
684
685  const char
686    *artifact,
687    *locate,
688    *name,
689    *property,
690    *registry,
691    *value;
692
693  const MagickInfo
694    *magick_info;
695
696  double
697    elapsed_time,
698    user_time;
699
700  ImageType
701    base_type,
702    type;
703
704  MagickBooleanType
705    ping;
706
707  register const Quantum
708    *p;
709
710  register ssize_t
711    i,
712    x;
713
714  size_t
715    depth,
716    distance,
717    scale;
718
719  ssize_t
720    y;
721
722  assert(image != (Image *) NULL);
723  assert(image->signature == MagickCoreSignature);
724  if (image->debug != MagickFalse)
725    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
726  *format='\0';
727  elapsed_time=GetElapsedTime(&image->timer);
728  user_time=GetUserTime(&image->timer);
729  GetTimerInfo(&image->timer);
730  p=GetVirtualPixels(image,0,0,1,1,exception);
731  ping=p == (const Quantum *) NULL ? MagickTrue : MagickFalse;
732  (void) ping;
733  (void) SignatureImage(image,exception);
734  JsonFormatLocaleFile(file,"{\n  \"image\": {\n    \"name\": %s,\n",
735    image->filename);
736  if (*image->magick_filename != '\0')
737    if (LocaleCompare(image->magick_filename,image->filename) != 0)
738      {
739        char
740          filename[MagickPathExtent];
741
742        GetPathComponent(image->magick_filename,TailPath,filename);
743        JsonFormatLocaleFile(file,"    \"baseName\": %s,\n",filename);
744      }
745  JsonFormatLocaleFile(file,"    \"format\": %s,\n",image->magick);
746  magick_info=GetMagickInfo(image->magick,exception);
747  if ((magick_info != (const MagickInfo *) NULL) &&
748      (GetMagickDescription(magick_info) != (const char *) NULL))
749    JsonFormatLocaleFile(file,"    \"formatDescription\": %s,\n",
750      image->magick);
751  if ((magick_info != (const MagickInfo *) NULL) &&
752      (GetMagickMimeType(magick_info) != (const char *) NULL))
753    JsonFormatLocaleFile(file,"    \"mimeType\": %s,\n",GetMagickMimeType(
754      magick_info));
755  JsonFormatLocaleFile(file,"    \"class\": %s,\n",CommandOptionToMnemonic(
756    MagickClassOptions,(ssize_t) image->storage_class));
757  (void) FormatLocaleFile(file,"    \"geometry\": {\n"
758    "      \"width\": %.20g,\n      \"height\": %.20g,\n"
759    "      \"x\": %.20g,\n      \"y\": %.20g\n    },\n",
760    (double) image->columns,(double) image->rows,(double) image->tile_offset.x,
761    (double) image->tile_offset.y);
762  if ((image->magick_columns != 0) || (image->magick_rows != 0))
763    if ((image->magick_columns != image->columns) ||
764        (image->magick_rows != image->rows))
765      (void) FormatLocaleFile(file,"    \"baseGeometry\": {\n"
766        "      \"width\": %.20g,\n      \"height\": %.20g\n    },\n",
767        (double) image->magick_columns,(double) image->magick_rows);
768  if ((image->resolution.x != 0.0) && (image->resolution.y != 0.0))
769    {
770      (void) FormatLocaleFile(file,"    \"resolution\": {\n"
771        "      \"x\": %.20g,\n      \"y\": %.20g\n    },\n",
772        image->resolution.x,image->resolution.y);
773      (void) FormatLocaleFile(file,"    \"printSize\": {\n"
774        "      \"x\": %.20g,\n      \"y\": %.20g\n    },\n",
775        image->columns/image->resolution.x,(double) image->rows/
776        image->resolution.y);
777    }
778  JsonFormatLocaleFile(file,"    \"units\": %s,\n",CommandOptionToMnemonic(
779    MagickResolutionOptions,(ssize_t) image->units));
780  colorspace=image->colorspace;
781  type=IdentifyImageType(image,exception);
782  if ((type == BilevelType) || (type == GrayscaleType) ||
783      (type == GrayscaleAlphaType))
784    colorspace=GRAYColorspace;
785  JsonFormatLocaleFile(file,"    \"type\": %s,\n",CommandOptionToMnemonic(
786    MagickTypeOptions,(ssize_t) type));
787  base_type=GetImageType(image);
788  if (type != base_type)
789    JsonFormatLocaleFile(file,"    \"baseType\": %s,\n",
790      CommandOptionToMnemonic(MagickTypeOptions,(ssize_t) base_type));
791  JsonFormatLocaleFile(file,"    \"endianess\": %s,\n",
792    CommandOptionToMnemonic(MagickEndianOptions,(ssize_t) image->endian));
793  locate=GetImageArtifact(image,"identify:locate");
794  if (locate == (const char *) NULL)
795    locate=GetImageArtifact(image,"json:locate");
796  if (locate != (const char *) NULL)
797    {
798      const char
799        *limit;
800
801      size_t
802        max_locations;
803
804      StatisticType
805        type;
806
807      /*
808        Display minimum, maximum, or mean pixel locations.
809      */
810      type=(StatisticType) ParseCommandOption(MagickStatisticOptions,
811        MagickFalse,locate);
812      limit=GetImageArtifact(image,"identify:limit");
813      if (limit == (const char *) NULL)
814        limit=GetImageArtifact(image,"json:limit");
815      max_locations=0;
816      if (limit != (const char *) NULL)
817        max_locations=StringToUnsignedLong(limit);
818      channel_statistics=GetLocationStatistics(image,type,exception);
819      if (channel_statistics == (ChannelStatistics *) NULL)
820        return(MagickFalse);
821      (void) FormatLocaleFile(file,"    \"channel%s\": {\n",locate);
822      if (image->alpha_trait != UndefinedPixelTrait)
823        (void) PrintChannelLocations(file,image,AlphaPixelChannel,"Alpha",
824          type,max_locations,MagickTrue,channel_statistics);
825      switch (colorspace)
826      {
827        case RGBColorspace:
828        default:
829        {
830          (void) PrintChannelLocations(file,image,RedPixelChannel,"Red",
831            type,max_locations,MagickTrue,channel_statistics);
832          (void) PrintChannelLocations(file,image,GreenPixelChannel,"Green",
833            type,max_locations,MagickTrue,channel_statistics);
834          (void) PrintChannelLocations(file,image,BluePixelChannel,"Blue",
835            type,max_locations,MagickFalse,channel_statistics);
836          break;
837        }
838        case CMYKColorspace:
839        {
840          (void) PrintChannelLocations(file,image,CyanPixelChannel,"Cyan",
841            type,max_locations,MagickTrue,channel_statistics);
842          (void) PrintChannelLocations(file,image,MagentaPixelChannel,"Magenta",
843            type,max_locations,MagickTrue,channel_statistics);
844          (void) PrintChannelLocations(file,image,YellowPixelChannel,"Yellow",
845            type,max_locations,MagickTrue,channel_statistics);
846          (void) PrintChannelLocations(file,image,BlackPixelChannel,"Black",
847            type,max_locations,MagickFalse,channel_statistics);
848          break;
849        }
850        case GRAYColorspace:
851        {
852          (void) PrintChannelLocations(file,image,GrayPixelChannel,"Gray",
853            type,max_locations,MagickFalse,channel_statistics);
854          break;
855        }
856      }
857      (void) FormatLocaleFile(file,"    },\n");
858      channel_statistics=(ChannelStatistics *) RelinquishMagickMemory(
859        channel_statistics);
860    }
861  /*
862    Detail channel depth and extrema.
863  */
864  JsonFormatLocaleFile(file,"    \"colorspace\": %s,\n",
865    CommandOptionToMnemonic(MagickColorspaceOptions,(ssize_t)
866    image->colorspace));
867  channel_statistics=(ChannelStatistics *) NULL;
868  channel_moments=(ChannelMoments *) NULL;
869  channel_phash=(ChannelPerceptualHash *) NULL;
870  channel_features=(ChannelFeatures *) NULL;
871  scale=1;
872  channel_statistics=GetImageStatistics(image,exception);
873  if (channel_statistics == (ChannelStatistics *) NULL)
874    return(MagickFalse);
875  artifact=GetImageArtifact(image,"identify:moments");
876  if (artifact == (const char *) NULL)
877    artifact=GetImageArtifact(image,"json:moments");
878  if (artifact != (const char *) NULL)
879    {
880      channel_moments=GetImageMoments(image,exception);
881      channel_phash=GetImagePerceptualHash(image,exception);
882    }
883  artifact=GetImageArtifact(image,"identify:features");
884  if (artifact == (const char *) NULL)
885    artifact=GetImageArtifact(image,"json:features");
886  if (artifact != (const char *) NULL)
887    {
888      distance=StringToUnsignedLong(artifact);
889      channel_features=GetImageFeatures(image,distance,exception);
890    }
891  depth=GetImageDepth(image,exception);
892  (void) FormatLocaleFile(file,"    \"depth\": %.20g,\n",(double) depth);
893  (void) FormatLocaleFile(file,"    \"baseDepth\": %.20g,\n",(double)
894    image->depth);
895  (void) FormatLocaleFile(file,"    \"channelDepth\": {\n");
896  if (image->alpha_trait != UndefinedPixelTrait)
897    (void) FormatLocaleFile(file,"      \"alpha\": %.20g,\n",(double)
898      channel_statistics[AlphaPixelChannel].depth);
899  switch (colorspace)
900  {
901    case RGBColorspace:
902    default:
903    {
904      (void) FormatLocaleFile(file,"      \"red\": %.20g,\n",(double)
905        channel_statistics[RedChannel].depth);
906      (void) FormatLocaleFile(file,"      \"green\": %.20g,\n",(double)
907        channel_statistics[GreenChannel].depth);
908      (void) FormatLocaleFile(file,"      \"blue\": %.20g\n",(double)
909        channel_statistics[BlueChannel].depth);
910      break;
911    }
912    case CMYKColorspace:
913    {
914      (void) FormatLocaleFile(file,"      \"cyan\": %.20g,\n",(double)
915        channel_statistics[CyanChannel].depth);
916      (void) FormatLocaleFile(file,"      \"magenta\": %.20g,\n",(double)
917        channel_statistics[MagentaChannel].depth);
918      (void) FormatLocaleFile(file,"      \"yellow\": %.20g,\n",(double)
919        channel_statistics[YellowChannel].depth);
920      (void) FormatLocaleFile(file,"      \"black\": %.20g\n",(double)
921        channel_statistics[BlackChannel].depth);
922      break;
923    }
924    case GRAYColorspace:
925    {
926      (void) FormatLocaleFile(file,"      \"gray\": %.20g\n",(double)
927        channel_statistics[GrayChannel].depth);
928      break;
929    }
930  }
931  (void) FormatLocaleFile(file,"    },\n");
932  scale=1;
933  if (image->depth <= MAGICKCORE_QUANTUM_DEPTH)
934    scale=QuantumRange/((size_t) QuantumRange >> ((size_t)
935      MAGICKCORE_QUANTUM_DEPTH-image->depth));
936  if (channel_statistics != (ChannelStatistics *) NULL)
937    {
938      (void) FormatLocaleFile(file,"    \"pixels\": %.20g,\n",
939        channel_statistics[CompositePixelChannel].area);
940      if (colorspace != GRAYColorspace)
941        {
942          (void) FormatLocaleFile(file,"    \"imageStatistics\": {\n");
943          (void) PrintChannelStatistics(file,(PixelChannel) MaxPixelChannels,
944            "Overall",1.0/scale,MagickFalse,channel_statistics);
945          (void) FormatLocaleFile(file,"    },\n");
946        }
947      (void) FormatLocaleFile(file,"    \"channelStatistics\": {\n");
948      if (image->alpha_trait != UndefinedPixelTrait)
949        (void) PrintChannelStatistics(file,AlphaPixelChannel,"Alpha",1.0/scale,
950          MagickTrue,channel_statistics);
951      switch (colorspace)
952      {
953        case RGBColorspace:
954        default:
955        {
956          (void) PrintChannelStatistics(file,RedPixelChannel,"Red",1.0/scale,
957            MagickTrue,channel_statistics);
958          (void) PrintChannelStatistics(file,GreenPixelChannel,"Green",1.0/
959            scale,MagickTrue,channel_statistics);
960          (void) PrintChannelStatistics(file,BluePixelChannel,"Blue",1.0/scale,
961            MagickFalse,channel_statistics);
962          break;
963        }
964        case CMYKColorspace:
965        {
966          (void) PrintChannelStatistics(file,CyanPixelChannel,"Cyan",1.0/scale,
967            MagickTrue,channel_statistics);
968          (void) PrintChannelStatistics(file,MagentaPixelChannel,"Magenta",1.0/
969            scale,MagickTrue,channel_statistics);
970          (void) PrintChannelStatistics(file,YellowPixelChannel,"Yellow",1.0/
971            scale,MagickTrue,channel_statistics);
972          (void) PrintChannelStatistics(file,BlackPixelChannel,"Black",1.0/
973            scale,MagickFalse,channel_statistics);
974          break;
975        }
976        case GRAYColorspace:
977        {
978          (void) PrintChannelStatistics(file,GrayPixelChannel,"Gray",1.0/scale,
979            MagickFalse,channel_statistics);
980          break;
981        }
982      }
983      (void) FormatLocaleFile(file,"    },\n");
984      channel_statistics=(ChannelStatistics *) RelinquishMagickMemory(
985        channel_statistics);
986    }
987  if (channel_moments != (ChannelMoments *) NULL)
988    {
989      (void) FormatLocaleFile(file,"    \"channelMoments\": {\n");
990      if (image->alpha_trait != UndefinedPixelTrait)
991        (void) PrintChannelMoments(file,AlphaPixelChannel,"Alpha",MagickTrue,
992          channel_moments);
993      switch (colorspace)
994      {
995        case RGBColorspace:
996        default:
997        {
998          (void) PrintChannelMoments(file,RedPixelChannel,"Red",MagickTrue,
999            channel_moments);
1000          (void) PrintChannelMoments(file,GreenPixelChannel,"Green",MagickTrue,
1001            channel_moments);
1002          (void) PrintChannelMoments(file,BluePixelChannel,"Blue",MagickFalse,
1003            channel_moments);
1004          break;
1005        }
1006        case CMYKColorspace:
1007        {
1008          (void) PrintChannelMoments(file,CyanPixelChannel,"Cyan",MagickTrue,
1009            channel_moments);
1010          (void) PrintChannelMoments(file,MagentaPixelChannel,"Magenta",
1011            MagickTrue,channel_moments);
1012          (void) PrintChannelMoments(file,YellowPixelChannel,"Yellow",
1013            MagickTrue,channel_moments);
1014          (void) PrintChannelMoments(file,BlackPixelChannel,"Black",
1015            MagickFalse,channel_moments);
1016          break;
1017        }
1018        case GRAYColorspace:
1019        {
1020          (void) PrintChannelMoments(file,GrayPixelChannel,"Gray",MagickFalse,
1021            channel_moments);
1022          break;
1023        }
1024      }
1025      (void) FormatLocaleFile(file,"    },\n");
1026      channel_moments=(ChannelMoments *) RelinquishMagickMemory(
1027        channel_moments);
1028    }
1029  if (channel_phash != (ChannelPerceptualHash *) NULL)
1030    {
1031      (void) FormatLocaleFile(file,"    \"channelPerceptualHash\": {\n");
1032      if (image->alpha_trait != UndefinedPixelTrait)
1033        (void) PrintChannelPerceptualHash(file,AlphaChannel,"alphaAlpha",
1034          MagickTrue,channel_phash);
1035      (void) PrintChannelPerceptualHash(file,RedChannel,"redHue",MagickTrue,
1036        channel_phash);
1037      (void) PrintChannelPerceptualHash(file,GreenChannel,"greenChroma",
1038        MagickTrue,channel_phash);
1039      (void) PrintChannelPerceptualHash(file,BlueChannel,"blueLuma",MagickFalse,
1040        channel_phash);
1041      (void) FormatLocaleFile(file,"    },\n");
1042      channel_phash=(ChannelPerceptualHash *) RelinquishMagickMemory(
1043        channel_phash);
1044    }
1045  if (channel_features != (ChannelFeatures *) NULL)
1046    {
1047      (void) FormatLocaleFile(file,"    \"channelFeatures\": {\n");
1048      if (image->alpha_trait != UndefinedPixelTrait)
1049        (void) PrintChannelFeatures(file,AlphaPixelChannel,"Alpha",MagickTrue,
1050          channel_features);
1051      switch (colorspace)
1052      {
1053        case RGBColorspace:
1054        default:
1055        {
1056          (void) PrintChannelFeatures(file,RedPixelChannel,"Red",MagickTrue,
1057            channel_features);
1058          (void) PrintChannelFeatures(file,GreenPixelChannel,"Green",
1059            MagickTrue,channel_features);
1060          (void) PrintChannelFeatures(file,BluePixelChannel,"Blue",MagickFalse,
1061            channel_features);
1062          break;
1063        }
1064        case CMYKColorspace:
1065        {
1066          (void) PrintChannelFeatures(file,CyanPixelChannel,"Cyan",MagickTrue,
1067            channel_features);
1068          (void) PrintChannelFeatures(file,MagentaPixelChannel,"Magenta",
1069            MagickTrue,channel_features);
1070          (void) PrintChannelFeatures(file,YellowPixelChannel,"Yellow",
1071            MagickTrue,channel_features);
1072          (void) PrintChannelFeatures(file,BlackPixelChannel,"Black",
1073            MagickFalse,channel_features);
1074          break;
1075        }
1076        case GRAYColorspace:
1077        {
1078          (void) PrintChannelFeatures(file,GrayPixelChannel,"Gray",MagickFalse,
1079            channel_features);
1080          break;
1081        }
1082      }
1083      (void) FormatLocaleFile(file,"    },\n");
1084      channel_features=(ChannelFeatures *) RelinquishMagickMemory(
1085        channel_features);
1086    }
1087    if (image->colorspace == CMYKColorspace)
1088      (void) FormatLocaleFile(file,"    \"totalInkDensity\": \"%.*g%%\",\n",
1089        GetMagickPrecision(),100.0*GetImageTotalInkDensity(image,exception)/
1090        (double) QuantumRange);
1091    x=0;
1092    if (image->alpha_trait != UndefinedPixelTrait)
1093      {
1094        register const Quantum
1095          *p;
1096
1097        p=(const Quantum *) NULL;
1098        for (y=0; y < (ssize_t) image->rows; y++)
1099        {
1100          p=GetVirtualPixels(image,0,y,image->columns,1,exception);
1101          if (p == (const Quantum *) NULL)
1102            break;
1103          for (x=0; x < (ssize_t) image->columns; x++)
1104          {
1105            if (GetPixelAlpha(image,p) == (Quantum) TransparentAlpha)
1106              break;
1107            p+=GetPixelChannels(image);
1108          }
1109          if (x < (ssize_t) image->columns)
1110            break;
1111        }
1112        if ((x < (ssize_t) image->columns) || (y < (ssize_t) image->rows))
1113          {
1114            PixelInfo
1115              pixel;
1116
1117            GetPixelInfo(image,&pixel);
1118            GetPixelInfoPixel(image,p,&pixel);
1119            GetColorTuple(&pixel,MagickTrue,color);
1120            (void) FormatLocaleFile(file,"    \"alpha\": \"%s\",\n",color);
1121          }
1122      }
1123  if (image->storage_class == PseudoClass)
1124    {
1125      register PixelInfo
1126        *magick_restrict p;
1127
1128      (void) FormatLocaleFile(file,"    \"colormapEntries\": %.20g,\n",
1129        (double) image->colors);
1130      (void) FormatLocaleFile(file,"    \"colormap\": [\n      ");
1131      p=image->colormap;
1132      for (i=0; i < (ssize_t) image->colors; i++)
1133      {
1134        GetColorTuple(p,MagickTrue,color);
1135        (void) FormatLocaleFile(file,"\"%s\"",color);
1136        if (i < (ssize_t) (image->colors-1))
1137          (void) FormatLocaleFile(file,",");
1138        if (((i+1) % 5) == 0)
1139          (void) FormatLocaleFile(file,"\n      ");
1140        p++;
1141      }
1142      (void) FormatLocaleFile(file,"\n    ],\n");
1143    }
1144  if (image->error.mean_error_per_pixel != 0.0)
1145    (void) FormatLocaleFile(file,"    \"meanErrorPerPixel\": \"%g\",\n",
1146      image->error.mean_error_per_pixel);
1147  if (image->error.normalized_mean_error != 0.0)
1148    (void) FormatLocaleFile(file,"    \"normalizedMeanError\": \"%g\",\n",
1149      image->error.normalized_mean_error);
1150  if (image->error.normalized_maximum_error != 0.0)
1151    (void) FormatLocaleFile(file,"    \"normalizedMaximumError\": \"%g\",\n",
1152      image->error.normalized_maximum_error);
1153  JsonFormatLocaleFile(file,"    \"renderingIntent\": %s,\n",
1154    CommandOptionToMnemonic(MagickIntentOptions,(ssize_t)
1155    image->rendering_intent));
1156  if (image->gamma != 0.0)
1157    (void) FormatLocaleFile(file,"    \"gamma\": %g,\n",image->gamma);
1158  if ((image->chromaticity.red_primary.x != 0.0) ||
1159      (image->chromaticity.green_primary.x != 0.0) ||
1160      (image->chromaticity.blue_primary.x != 0.0) ||
1161      (image->chromaticity.white_point.x != 0.0))
1162    {
1163      /*
1164        Display image chromaticity.
1165      */
1166      (void) FormatLocaleFile(file,"    \"chromaticity\": {\n");
1167      (void) FormatLocaleFile(file,"      \"redPrimary\": {\n"
1168        "        \"x\": %g,\n        \"y\": %g\n      },\n",
1169        image->chromaticity.red_primary.x,image->chromaticity.red_primary.y);
1170      (void) FormatLocaleFile(file,"      \"greenPrimary\": {\n"
1171        "        \"x\": %g,\n        \"y\": %g\n      },\n",
1172        image->chromaticity.green_primary.x,
1173        image->chromaticity.green_primary.y);
1174      (void) FormatLocaleFile(file,"      \"bluePrimary\": {\n"
1175        "        \"x\": %g,\n        \"y\": %g\n      },\n",
1176        image->chromaticity.blue_primary.x,image->chromaticity.blue_primary.y);
1177      (void) FormatLocaleFile(file,"      \"whitePrimary\": {\n"
1178        "        \"x\": %g,\n        \"y\": %g\n      }\n",
1179        image->chromaticity.white_point.x,image->chromaticity.white_point.y);
1180      (void) FormatLocaleFile(file,"    },\n");
1181    }
1182  if ((image->extract_info.width*image->extract_info.height) != 0)
1183    (void) FormatLocaleFile(file,"    \"tileGeometry\": {\n"
1184      "      \"width\": %.20g,\n      \"height\": %.20g,\n"
1185      "      \"x\": %.20g,\n      \"y\": %.20g\n    },\n",
1186      (double) image->extract_info.width,(double) image->extract_info.height,
1187      (double) image->extract_info.x,(double) image->extract_info.y);
1188  GetColorTuple(&image->alpha_color,MagickTrue,color);
1189  (void) FormatLocaleFile(file,"    \"alphaColor\": \"%s\",\n",color);
1190  GetColorTuple(&image->background_color,MagickTrue,color);
1191  (void) FormatLocaleFile(file,"    \"backgroundColor\": \"%s\",\n",color);
1192  GetColorTuple(&image->border_color,MagickTrue,color);
1193  (void) FormatLocaleFile(file,"    \"borderColor\": \"%s\",\n",color);
1194  GetColorTuple(&image->transparent_color,MagickTrue,color);
1195  (void) FormatLocaleFile(file,"    \"transparentColor\": \"%s\",\n",color);
1196  JsonFormatLocaleFile(file,"    \"interlace\": %s,\n",CommandOptionToMnemonic(
1197    MagickInterlaceOptions,(ssize_t) image->interlace));
1198  JsonFormatLocaleFile(file,"    \"intensity\": %s,\n",CommandOptionToMnemonic(
1199    MagickPixelIntensityOptions,(ssize_t) image->intensity));
1200  JsonFormatLocaleFile(file,"    \"compose\": %s,\n",
1201    CommandOptionToMnemonic(MagickComposeOptions,(ssize_t) image->compose));
1202  if ((image->page.width != 0) || (image->page.height != 0) ||
1203      (image->page.x != 0) || (image->page.y != 0))
1204    (void) FormatLocaleFile(file,"    \"pageGeometry\": {\n"
1205      "      \"width\": %.20g,\n      \"height\": %.20g,\n"
1206      "      \"x\": %.20g,\n      \"y\": %.20g\n    },\n",
1207      (double) image->page.width,(double) image->page.height,
1208      (double) image->page.x,(double) image->page.y);
1209  if ((image->page.x != 0) || (image->page.y != 0))
1210    (void) FormatLocaleFile(file,"    \"originGeometry\": %+.20g%+.20g\n",
1211      (double) image->page.x,(double) image->page.y);
1212  JsonFormatLocaleFile(file,"    \"dispose\": %s,\n",
1213    CommandOptionToMnemonic(MagickDisposeOptions,(ssize_t) image->dispose));
1214  if (image->delay != 0)
1215    (void) FormatLocaleFile(file,"    \"delay\": \"%.20gx%.20g\"\n",
1216      (double) image->delay,(double) image->ticks_per_second);
1217  if (image->iterations != 1)
1218    (void) FormatLocaleFile(file,"    \"iterations\": %.20g,\n",(double)
1219      image->iterations);
1220  if ((image->next != (Image *) NULL) || (image->previous != (Image *) NULL))
1221    (void) FormatLocaleFile(file,"    \"scene\": %.20g\n    \"scenes\": "
1222      "%.20g\n",(double) image->scene,(double) GetImageListLength(image));
1223  else
1224    if (image->scene != 0)
1225      (void) FormatLocaleFile(file,"    \"scene\": %.20g,\n",(double)
1226        image->scene);
1227  JsonFormatLocaleFile(file,"    \"compression\": %s,\n",
1228    CommandOptionToMnemonic(MagickCompressOptions,(ssize_t)
1229    image->compression));
1230  if (image->quality != UndefinedCompressionQuality)
1231    (void) FormatLocaleFile(file,"    \"quality\": %.20g,\n",(double)
1232      image->quality);
1233  JsonFormatLocaleFile(file,"    \"orientation\": %s,\n",
1234    CommandOptionToMnemonic(MagickOrientationOptions,(ssize_t)
1235    image->orientation));
1236  if (image->montage != (char *) NULL)
1237    JsonFormatLocaleFile(file,"    \"montage\": \"%s\",\n",image->montage);
1238  if (image->directory != (char *) NULL)
1239    {
1240      Image
1241        *tile;
1242
1243      ImageInfo
1244        *image_info;
1245
1246      register char
1247        *p,
1248        *q;
1249
1250      WarningHandler
1251        handler;
1252
1253      /*
1254        Display visual image directory.
1255      */
1256      image_info=AcquireImageInfo();
1257      (void) CloneString(&image_info->size,"64x64");
1258      (void) FormatLocaleFile(file,"    \"montageDirectory\": [");
1259      p=image->directory;
1260      while (*p != '\0')
1261      {
1262        q=p;
1263        while ((*q != '\n') && (*q != '\0'))
1264          q++;
1265        (void) CopyMagickString(image_info->filename,p,(size_t) (q-p+1));
1266        p=q+1;
1267        JsonFormatLocaleFile(file,"{\n       \"name\": %s",
1268          image_info->filename);
1269        handler=SetWarningHandler((WarningHandler) NULL);
1270        tile=ReadImage(image_info,exception);
1271        (void) SetWarningHandler(handler);
1272        if (tile == (Image *) NULL)
1273          {
1274            (void) FormatLocaleFile(file,"    }");
1275            continue;
1276          }
1277        (void) FormatLocaleFile(file,",\n       \"info\": \"%.20gx%.20g %s\"",
1278          (double) tile->magick_columns,(double) tile->magick_rows,
1279          tile->magick);
1280        (void) SignatureImage(tile,exception);
1281        ResetImagePropertyIterator(tile);
1282        property=GetNextImageProperty(tile);
1283        while (property != (const char *) NULL)
1284        {
1285          JsonFormatLocaleFile(file,",\n       %s: ",property);
1286          value=GetImageProperty(tile,property,exception);
1287          JsonFormatLocaleFile(file,"%s",value);
1288          property=GetNextImageProperty(tile);
1289        }
1290        tile=DestroyImage(tile);
1291        if (*p != '\0')
1292          (void) FormatLocaleFile(file,"\n    },");
1293        else
1294          (void) FormatLocaleFile(file,"\n    }");
1295      }
1296      (void) FormatLocaleFile(file,"],\n");
1297      image_info=DestroyImageInfo(image_info);
1298    }
1299  (void) GetImageProperty(image,"exif:*",exception);
1300  (void) GetImageProperty(image,"icc:*",exception);
1301  (void) GetImageProperty(image,"iptc:*",exception);
1302  (void) GetImageProperty(image,"xmp:*",exception);
1303  ResetImagePropertyIterator(image);
1304  property=GetNextImageProperty(image);
1305  if (property != (const char *) NULL)
1306    {
1307      size_t
1308        n;
1309
1310      /*
1311        Display image properties.
1312      */
1313      n=0;
1314      (void) FormatLocaleFile(file,"    \"properties\": {\n");
1315      while (property != (const char *) NULL)
1316      {
1317        if (n++ != 0)
1318          (void) FormatLocaleFile(file,",\n");
1319        JsonFormatLocaleFile(file,"      %s: ",property);
1320        value=GetImageProperty(image,property,exception);
1321        JsonFormatLocaleFile(file,"%s",value);
1322        property=GetNextImageProperty(image);
1323      }
1324      (void) FormatLocaleFile(file,"\n    },\n");
1325    }
1326  (void) FormatLocaleString(key,MagickPathExtent,"8BIM:1999,2998:#1");
1327  value=GetImageProperty(image,key,exception);
1328  if (value != (const char *) NULL)
1329    {
1330      /*
1331        Display clipping path.
1332      */
1333      (void) FormatLocaleFile(file,"    \"clipping path\": {\n");
1334      JsonFormatLocaleFile(file,"%s\n",value);
1335      (void) FormatLocaleFile(file,"    },\n");
1336    }
1337  ResetImageProfileIterator(image);
1338  name=GetNextImageProfile(image);
1339  if (name != (char *) NULL)
1340    {
1341      const StringInfo
1342        *profile;
1343
1344      size_t
1345        n;
1346
1347      /*
1348        Identify image profiles.
1349      */
1350      n=0;
1351      (void) FormatLocaleFile(file,"    \"profiles\": {\n");
1352      while (name != (char *) NULL)
1353      {
1354        profile=GetImageProfile(image,name);
1355        if (profile == (StringInfo *) NULL)
1356          continue;
1357        if (n++ != 0)
1358          (void) FormatLocaleFile(file,",\n");
1359        JsonFormatLocaleFile(file,"      %s: {\n",name);
1360        if (LocaleCompare(name,"iptc") == 0)
1361          {
1362            char
1363              *attribute,
1364              **attribute_list;
1365
1366            const char
1367              *tag;
1368
1369            long
1370              dataset,
1371              record,
1372              sentinel;
1373
1374            register ssize_t
1375              j;
1376
1377            size_t
1378              length,
1379              profile_length;
1380
1381            profile_length=GetStringInfoLength(profile);
1382            for (i=0; i < (ssize_t) profile_length; i+=(ssize_t) length)
1383            {
1384              length=1;
1385              sentinel=GetStringInfoDatum(profile)[i++];
1386              if (sentinel != 0x1c)
1387                continue;
1388              dataset=GetStringInfoDatum(profile)[i++];
1389              record=GetStringInfoDatum(profile)[i++];
1390              switch (record)
1391              {
1392                case 5: tag="Image Name"; break;
1393                case 7: tag="Edit Status"; break;
1394                case 10: tag="Priority"; break;
1395                case 15: tag="Category"; break;
1396                case 20: tag="Supplemental Category"; break;
1397                case 22: tag="Fixture Identifier"; break;
1398                case 25: tag="Keyword"; break;
1399                case 30: tag="Release Date"; break;
1400                case 35: tag="Release Time"; break;
1401                case 40: tag="Special Instructions"; break;
1402                case 45: tag="Reference Service"; break;
1403                case 47: tag="Reference Date"; break;
1404                case 50: tag="Reference Number"; break;
1405                case 55: tag="Created Date"; break;
1406                case 60: tag="Created Time"; break;
1407                case 65: tag="Originating Program"; break;
1408                case 70: tag="Program Version"; break;
1409                case 75: tag="Object Cycle"; break;
1410                case 80: tag="Byline"; break;
1411                case 85: tag="Byline Title"; break;
1412                case 90: tag="City"; break;
1413                case 92: tag="Sub-Location"; break;
1414                case 95: tag="Province State"; break;
1415                case 100: tag="Country Code"; break;
1416                case 101: tag="Country"; break;
1417                case 103: tag="Original Transmission Reference"; break;
1418                case 105: tag="Headline"; break;
1419                case 110: tag="Credit"; break;
1420                case 115: tag="Src"; break;
1421                case 116: tag="Copyright String"; break;
1422                case 120: tag="Caption"; break;
1423                case 121: tag="Local Caption"; break;
1424                case 122: tag="Caption Writer"; break;
1425                case 200: tag="Custom Field 1"; break;
1426                case 201: tag="Custom Field 2"; break;
1427                case 202: tag="Custom Field 3"; break;
1428                case 203: tag="Custom Field 4"; break;
1429                case 204: tag="Custom Field 5"; break;
1430                case 205: tag="Custom Field 6"; break;
1431                case 206: tag="Custom Field 7"; break;
1432                case 207: tag="Custom Field 8"; break;
1433                case 208: tag="Custom Field 9"; break;
1434                case 209: tag="Custom Field 10"; break;
1435                case 210: tag="Custom Field 11"; break;
1436                case 211: tag="Custom Field 12"; break;
1437                case 212: tag="Custom Field 13"; break;
1438                case 213: tag="Custom Field 14"; break;
1439                case 214: tag="Custom Field 15"; break;
1440                case 215: tag="Custom Field 16"; break;
1441                case 216: tag="Custom Field 17"; break;
1442                case 217: tag="Custom Field 18"; break;
1443                case 218: tag="Custom Field 19"; break;
1444                case 219: tag="Custom Field 20"; break;
1445                default: tag="unknown"; break;
1446              }
1447              (void) FormatLocaleFile(file,"      %s[%.20g,%.20g]: ",tag,
1448                (double) dataset,(double) record);
1449              length=(size_t) (GetStringInfoDatum(profile)[i++] << 8);
1450              length|=GetStringInfoDatum(profile)[i++];
1451              attribute=(char *) NULL;
1452              if (~length >= (MagickPathExtent-1))
1453                attribute=(char *) AcquireQuantumMemory(length+MagickPathExtent,
1454                  sizeof(*attribute));
1455              if (attribute != (char *) NULL)
1456                {
1457                  (void) CopyMagickString(attribute,(char *)
1458                    GetStringInfoDatum(profile)+i,length+1);
1459                  attribute_list=StringToList(attribute);
1460                  if (attribute_list != (char **) NULL)
1461                    {
1462                     (void) FormatLocaleFile(file,"[");
1463                      for (j=0; attribute_list[j] != (char *) NULL; j++)
1464                      {
1465                        if (j != 0)
1466                          (void) FormatLocaleFile(file,",");
1467                        JsonFormatLocaleFile(file,"%s",attribute_list[j]);
1468                        attribute_list[j]=(char *) RelinquishMagickMemory(
1469                          attribute_list[j]);
1470                      }
1471                      (void) FormatLocaleFile(file,"],");
1472                      attribute_list=(char **) RelinquishMagickMemory(
1473                        attribute_list);
1474                    }
1475                   else
1476                    (void) FormatLocaleFile(file,"null,");
1477                  attribute=DestroyString(attribute);
1478                }
1479              else
1480                (void) FormatLocaleFile(file,"null,");
1481            }
1482          }
1483        (void) FormatLocaleFile(file,"        \"length\": \"%.20g\"",(double)
1484          GetStringInfoLength(profile));
1485        (void) FormatLocaleFile(file,"\n      }");
1486        name=GetNextImageProfile(image);
1487      }
1488      (void) FormatLocaleFile(file,"\n    },\n");
1489    }
1490  ResetImageArtifactIterator(image);
1491  artifact=GetNextImageArtifact(image);
1492  if (artifact != (const char *) NULL)
1493    {
1494      ssize_t
1495        n;
1496
1497      /*
1498        Display image artifacts.
1499      */
1500      n=0;
1501      (void) FormatLocaleFile(file,"    \"artifacts\": {\n");
1502      while (artifact != (const char *) NULL)
1503      {
1504        if (n++ != 0)
1505          (void) FormatLocaleFile(file,",\n");
1506        JsonFormatLocaleFile(file,"      %s: ",artifact);
1507        value=GetImageArtifact(image,artifact);
1508        JsonFormatLocaleFile(file,"%s",value);
1509        artifact=GetNextImageArtifact(image);
1510      }
1511      (void) FormatLocaleFile(file,"\n    },\n");
1512    }
1513  ResetImageRegistryIterator();
1514  registry=GetNextImageRegistry();
1515  if (registry != (const char *) NULL)
1516    {
1517      ssize_t
1518        n;
1519
1520      /*
1521        Display image registry.
1522      */
1523      (void) FormatLocaleFile(file,"    \"registry\": {\n");
1524      n=0;
1525      while (registry != (const char *) NULL)
1526      {
1527        if (n++ != 0)
1528          (void) FormatLocaleFile(file,",\n");
1529        JsonFormatLocaleFile(file,"      %s: ",registry);
1530        value=(const char *) GetImageRegistry(StringRegistryType,registry,
1531          exception);
1532        JsonFormatLocaleFile(file,"%s",value);
1533        registry=GetNextImageRegistry();
1534      }
1535      (void) FormatLocaleFile(file,"    },\n");
1536    }
1537  (void) FormatLocaleFile(file,"    \"tainted\": %s,\n",
1538    image->taint != MagickFalse ? "true" : "false");
1539  (void) FormatMagickSize(GetBlobSize(image),MagickFalse,"B",MagickPathExtent,
1540    format);
1541  JsonFormatLocaleFile(file,"    \"filesize\": %s,\n",format);
1542  (void) FormatMagickSize((MagickSizeType) image->columns*image->rows,
1543    MagickFalse,"B",MagickPathExtent,format);
1544  if (strlen(format) > 1)
1545    format[strlen(format)-1]='\0';
1546  JsonFormatLocaleFile(file,"    \"numberPixels\": %s,\n",format);
1547  (void) FormatMagickSize((MagickSizeType) ((double) image->columns*image->rows/
1548    elapsed_time+0.5),MagickFalse,"B",MagickPathExtent,format);
1549  JsonFormatLocaleFile(file,"    \"pixelsPerSecond\": %s,\n",format);
1550  (void) FormatLocaleFile(file,"    \"userTime\": \"%0.3fu\",\n",user_time);
1551  (void) FormatLocaleFile(file,"    \"elapsedTime\": \"%lu:%02lu.%03lu\",\n",
1552    (unsigned long) (elapsed_time/60.0),(unsigned long) ceil(fmod(
1553    elapsed_time,60.0)),(unsigned long) (1000.0*(elapsed_time-floor(
1554    elapsed_time))));
1555  JsonFormatLocaleFile(file,"    \"version\": %s\n",
1556    GetMagickVersion((size_t *) NULL));
1557  (void) FormatLocaleFile(file,"  }\n}\n");
1558  (void) fflush(file);
1559  return(ferror(file) != 0 ? MagickFalse : MagickTrue);
1560}
1561
1562static MagickBooleanType WriteJSONImage(const ImageInfo *image_info,
1563  Image *image,ExceptionInfo *exception)
1564{
1565  FILE
1566    *file;
1567
1568  MagickBooleanType
1569    status;
1570
1571  MagickOffsetType
1572    scene;
1573
1574  /*
1575    Open output image file.
1576  */
1577  assert(image_info != (const ImageInfo *) NULL);
1578  assert(image_info->signature == MagickCoreSignature);
1579  assert(image != (Image *) NULL);
1580  assert(image->signature == MagickCoreSignature);
1581  if (image->debug != MagickFalse)
1582    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1583  status=OpenBlob(image_info,image,WriteBlobMode,exception);
1584  if (status == MagickFalse)
1585    return(status);
1586  file=GetBlobFileHandle(image);
1587  if (file == (FILE *) NULL)
1588    file=stdout;
1589  scene=0;
1590  do
1591  {
1592    WriteBlobString(image,"[");
1593    image->magick_columns=image->columns;
1594    image->magick_rows=image->rows;
1595    EncodeImageAttributes(image,file,exception);
1596    if (GetNextImageInList(image) == (Image *) NULL)
1597      {
1598        WriteBlobString(image,"]");
1599        break;
1600      }
1601    WriteBlobString(image,",\n");
1602    image=SyncNextImageInList(image);
1603    status=SetImageProgress(image,SaveImagesTag,scene++,
1604      GetImageListLength(image));
1605    if (status == MagickFalse)
1606      break;
1607  } while (image_info->adjoin != MagickFalse);
1608  (void) CloseBlob(image);
1609  return(MagickTrue);
1610}
1611