GNU Octave  9.1.0
A high-level interpreted language, primarily intended for numerical computations, mostly compatible with Matlab
__magick_read__.cc
Go to the documentation of this file.
1 ////////////////////////////////////////////////////////////////////////
2 //
3 // Copyright (C) 2002-2024 The Octave Project Developers
4 //
5 // See the file COPYRIGHT.md in the top-level directory of this
6 // distribution or <https://octave.org/copyright/>.
7 //
8 // This file is part of Octave.
9 //
10 // Octave is free software: you can redistribute it and/or modify it
11 // under the terms of the GNU General Public License as published by
12 // the Free Software Foundation, either version 3 of the License, or
13 // (at your option) any later version.
14 //
15 // Octave is distributed in the hope that it will be useful, but
16 // WITHOUT ANY WARRANTY; without even the implied warranty of
17 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 // GNU General Public License for more details.
19 //
20 // You should have received a copy of the GNU General Public License
21 // along with Octave; see the file COPYING. If not, see
22 // <https://www.gnu.org/licenses/>.
23 //
24 ////////////////////////////////////////////////////////////////////////
25 
26 #if defined (HAVE_CONFIG_H)
27 # include "config.h"
28 #endif
29 
30 #include "file-stat.h"
31 #include "lo-sysdep.h"
32 #include "oct-env.h"
33 #include "oct-time.h"
34 
35 #include "defun.h"
36 #include "error.h"
37 #include "ov-struct.h"
38 
39 #include "errwarn.h"
40 
41 #if defined (HAVE_MAGICK)
42 # include <Magick++.h>
43 # include <clocale>
44 // FIXME: The following using declaration may be needed to build with
45 // ImageMagick. It doesn't appear to be needed for GraphicsMagick but
46 // it also doesn't seem to cause trouble. A configure test would be
47 // helpful.
48 using Magick::Quantum;
49 #endif
50 
52 
53 #if defined (HAVE_MAGICK)
54 
55 // In theory, it should be enough to check the class:
56 // Magick::ClassType
57 // PseudoClass:
58 // Image is composed of pixels which specify an index in a color palette.
59 // DirectClass:
60 // Image is composed of pixels which represent literal color values.
61 //
62 // GraphicsMagick does not really distinguishes between indexed and
63 // normal images. After reading a file, it decides itself the optimal
64 // way to store the image in memory, independently of the how the
65 // image was stored in the file. That's what ClassType returns. While
66 // it seems to match the original file most of the times, this is
67 // not necessarily true all the times. See
68 // https://sourceforge.net/mailarchive/message.php?msg_id=31180507
69 // In addition to the ClassType, there is also ImageType which has a
70 // type for indexed images (PaletteType and PaletteMatteType). However,
71 // they also don't represent the original image. Not only does DirectClass
72 // can have a PaletteType, but also does a PseudoClass have non Palette
73 // types.
74 //
75 // We can't do better without having format specific code which is
76 // what we are trying to avoid by using a library such as GM. We at
77 // least create workarounds for the most common problems.
78 //
79 // 1) A grayscale jpeg image can report being indexed even though the
80 // JPEG format has no support for indexed images. We can at least
81 // fix this one.
82 // 2) A PNG file is only an indexed image if color type orig is 3 (value comes
83 // from libpng)
84 static bool
85 is_indexed (const Magick::Image& img)
86 {
87  bool indexed = (img.classType () == Magick::PseudoClass);
88  // Our problem until now is non-indexed images, being represented as indexed
89  // by GM. The following attempts educated guesses to undo this optimization.
90  if (indexed)
91  {
92  const std::string fmt = img.magick ();
93  if (fmt == "JPEG")
94  // The JPEG format does not support indexed images, but GM sometimes
95  // reports grayscale JPEG as indexed. Always false for JPEG.
96  indexed = false;
97  else if (fmt == "PNG")
98  {
99  // Newer versions of GM (at least does not happens with 1.3.16) will
100  // store values from the underlying library as image attributes. In
101  // the case of PNG files, this is libpng where an indexed image will
102  // always have a value of 3 for "color-type-orig". This property
103  // always has a value in libpng so if we get nothing, we assume this
104  // GM version does not store them and we have to go with whatever
105  // GM PseudoClass says.
106  const std::string color_type
107  = const_cast<Magick::Image&> (img).attribute ("PNG:IHDR.color-type-orig");
108  if (! color_type.empty () && color_type != "3")
109  indexed = false;
110  }
111  }
112  return indexed;
113 }
114 
115 // The depth from depth() is not always correct for us but seems to be the
116 // best value we can get. For example, a grayscale png image with 1 bit
117 // per channel should return a depth of 1 but instead we get 8.
118 // We could check channelDepth() but then, which channel has the data
119 // is not straightforward. So we'd have to check all
120 // the channels and select the highest value. But then, I also
121 // have a 16bit TIFF whose depth returns 16 (correct), but all of the
122 // channels gives 8 (wrong). No idea why, maybe a bug in GM?
123 // Anyway, using depth() seems that only causes problems for binary
124 // images, and the problem with channelDepth() is not making set them
125 // all to 1. So we will guess that if all channels have depth of 1,
126 // then we must have a binary image.
127 // Note that we can't use AllChannels it doesn't work for this.
128 // We also can't check only one from RGB, one from CMYK, and grayscale
129 // and transparency, we really need to check all of the channels (bug #41584).
130 static octave_idx_type
131 get_depth (Magick::Image& img)
132 {
133  octave_idx_type depth = img.depth ();
134  if (depth == 8
135  && img.channelDepth (Magick::RedChannel) == 1
136  && img.channelDepth (Magick::GreenChannel) == 1
137  && img.channelDepth (Magick::BlueChannel) == 1
138  && img.channelDepth (Magick::CyanChannel) == 1
139  && img.channelDepth (Magick::MagentaChannel) == 1
140  && img.channelDepth (Magick::YellowChannel) == 1
141  && img.channelDepth (Magick::BlackChannel) == 1
142  && img.channelDepth (Magick::OpacityChannel) == 1
143  && img.channelDepth (Magick::GrayChannel) == 1)
144  depth = 1;
145 
146  return depth;
147 }
148 
149 // We need this in case one of the sides of the image being read has
150 // width 1. In those cases, the type will come as scalar instead of range
151 // since that's the behavior of the colon operator (1:1:1 will be a scalar,
152 // not a range).
153 static range<double>
154 get_region_range (const octave_value& region)
155 {
156  range<double> output;
157 
158  if (region.is_range ())
159  output = region.range_value ();
160  else if (region.is_scalar_type ())
161  {
162  double value = region.scalar_value ();
163  output = range<double> (value, value);
164  }
165  else if (region.is_matrix_type ())
166  {
167  NDArray array = region.array_value ();
168  double base = array(0);
169  double limit = array(array.numel () - 1);
170  double incr = array(1) - base;
171  output = range<double> (base, incr, limit);
172  }
173  else
174  error ("__magick_read__: unknown datatype for Region option");
175 
176  return output;
177 }
178 
179 class
180 image_region
181 {
182 public:
183 
184  image_region () = delete;
185 
186  image_region (const octave_scalar_map& options)
187  {
188  // FIXME: should we have better checking on the input map and values
189  // or is that expected to be done elsewhere?
190 
191  const Cell pixel_region = options.getfield ("region").cell_value ();
192 
193  // Subtract 1 to account for 0 indexing.
194 
195  const range<double> rows = get_region_range (pixel_region (0));
196  const range<double> cols = get_region_range (pixel_region (1));
197 
198  m_row_start = rows.base () - 1;
199  m_col_start = cols.base () - 1;
200  m_row_end = rows.max () - 1;
201  m_col_end = cols.max () - 1;
202 
203  m_row_cache = m_row_end - m_row_start + 1;
204  m_col_cache = m_col_end - m_col_start + 1;
205 
206  m_row_shift = m_col_cache * rows.increment ();
207  m_col_shift = m_col_cache * (m_row_cache + rows.increment () - 1) - cols.increment ();
208 
209  m_row_out = rows.numel ();
210  m_col_out = cols.numel ();
211  }
212 
213  OCTAVE_DEFAULT_COPY_MOVE_DELETE (image_region)
214 
215  octave_idx_type row_start () const { return m_row_start; }
216  octave_idx_type col_start () const { return m_col_start; }
217  octave_idx_type row_end () const { return m_row_end; }
218  octave_idx_type col_end () const { return m_col_end; }
219 
220  // Length of the area to load into the Image Pixel Cache. We use max and
221  // min to account for cases where last element of range is the range limit.
222 
223  octave_idx_type row_cache () const { return m_row_cache; }
224  octave_idx_type col_cache () const { return m_col_cache; }
225 
226  // How much we have to shift in the memory when doing the loops.
227 
228  octave_idx_type row_shift () const { return m_row_shift; }
229  octave_idx_type col_shift () const { return m_col_shift; }
230 
231  // The actual height and width of the output image
232 
233  octave_idx_type row_out () const { return m_row_out; }
234  octave_idx_type col_out () const { return m_col_out; }
235 
236 private:
237 
238  octave_idx_type m_row_start;
239  octave_idx_type m_col_start;
240  octave_idx_type m_row_end;
241  octave_idx_type m_col_end;
242 
243  // Length of the area to load into the Image Pixel Cache. We use max and
244  // min to account for cases where last element of range is the range limit.
245 
246  octave_idx_type m_row_cache;
247  octave_idx_type m_col_cache;
248 
249  // How much we have to shift in the memory when doing the loops.
250 
251  octave_idx_type m_row_shift;
252  octave_idx_type m_col_shift;
253 
254  // The actual height and width of the output image
255 
256  octave_idx_type m_row_out;
257  octave_idx_type m_col_out;
258 };
259 
260 static octave_value_list
261 read_maps (Magick::Image& img)
262 {
263  // can't call colorMapSize on const Magick::Image
264  const octave_idx_type mapsize = img.colorMapSize ();
265  Matrix cmap = Matrix (mapsize, 3); // colormap
266  ColumnVector amap = ColumnVector (mapsize); // alpha map
267  for (octave_idx_type i = 0; i < mapsize; i++)
268  {
269  const Magick::ColorRGB c = img.colorMap (i);
270  cmap(i, 0) = c.red ();
271  cmap(i, 1) = c.green ();
272  cmap(i, 2) = c.blue ();
273  amap(i) = c.alpha ();
274  }
275  octave_value_list maps;
276  maps(0) = cmap;
277  maps(1) = amap;
278  return maps;
279 }
280 
281 template <typename T>
282 static octave_value_list
283 read_indexed_images (const std::vector<Magick::Image>& imvec,
284  const Array<octave_idx_type>& frameidx,
285  const octave_idx_type& nargout,
286  const octave_scalar_map& options)
287 {
288  typedef typename T::element_type P;
289 
290  octave_value_list retval (1);
291 
292  image_region region (options);
293 
294  const octave_idx_type nFrames = frameidx.numel ();
295  const octave_idx_type nRows = region.row_out ();
296  const octave_idx_type nCols = region.col_out ();
297 
298  // imvec has all of the pages of a file, even the ones we are not
299  // interested in. We will use the first image that we will be actually
300  // reading to get information about the image.
301  const octave_idx_type def_elem = frameidx(0);
302 
303  T img = T (dim_vector (nRows, nCols, 1, nFrames));
304  P *img_fvec = img.fortran_vec ();
305 
306  const octave_idx_type row_start = region.row_start ();
307  const octave_idx_type col_start = region.col_start ();
308  const octave_idx_type row_shift = region.row_shift ();
309  const octave_idx_type col_shift = region.col_shift ();
310  const octave_idx_type row_cache = region.row_cache ();
311  const octave_idx_type col_cache = region.col_cache ();
312 
313  // When reading PixelPackets from the Image Pixel Cache, they come in
314  // row major order. So we keep moving back and forth there so we can
315  // write the image in column major order.
316  octave_idx_type idx = 0;
317  for (octave_idx_type frame = 0; frame < nFrames; frame++)
318  {
319  octave_quit ();
320 
321  imvec[frameidx(frame)].getConstPixels (col_start, row_start,
322  col_cache, row_cache);
323 
324  const Magick::IndexPacket *pix
325  = imvec[frameidx(frame)].getConstIndexes ();
326 
327  for (octave_idx_type col = 0; col < nCols; col++)
328  {
329  for (octave_idx_type row = 0; row < nRows; row++)
330  {
331  img_fvec[idx++] = static_cast<P> (*pix);
332  pix += row_shift;
333  }
334  pix -= col_shift;
335  }
336  }
337  retval(0) = octave_value (img);
338 
339  // Only bother reading the colormap if it was requested as output.
340  if (nargout >= 2)
341  {
342  // In theory, it should be possible for each frame of an image to
343  // have different colormaps but for Matlab compatibility, we only
344  // return the colormap of the first frame. To obtain the colormaps
345  // of different frames, one needs can either use imfinfo or a for
346  // loop around imread.
347  const octave_value_list maps
348  = read_maps (const_cast<Magick::Image&> (imvec[frameidx(def_elem)]));
349 
350  retval(1) = maps(0);
351 
352  // only interpret alpha channel if it was requested as output
353  if (nargout >= 3)
354  {
355  if (imvec[def_elem].matte ())
356  {
357  // Alpha channel exists.
358  const Matrix amap = maps(1).matrix_value ();
359  const double *amap_fvec = amap.data ();
360 
361  NDArray alpha (dim_vector (nRows, nCols, 1, nFrames));
362  double *alpha_fvec = alpha.fortran_vec ();
363 
364  // GraphicsMagick stores the alpha values inverted, i.e.,
365  // 1 for transparent and 0 for opaque so we fix that here.
366  const octave_idx_type nPixels = alpha.numel ();
367  for (octave_idx_type pix = 0; pix < nPixels; pix++)
368  alpha_fvec[pix] = 1 - amap_fvec[static_cast<int> (img_fvec[3])];
369 
370  retval(2) = alpha;
371  }
372  else
373  {
374  // No alpha channel. Return empty matrix.
375  retval(2) = Matrix ();
376  }
377  }
378  }
379 
380  return retval;
381 }
382 
383 // This function is highly repetitive, a bunch of for loops that are
384 // very similar to account for different image types. They are different
385 // enough that trying to reduce the copy and paste would decrease its
386 // readability too much.
387 template <typename T>
389 read_images (std::vector<Magick::Image>& imvec,
390  const Array<octave_idx_type>& frameidx,
391  const octave_idx_type& nargout,
392  const octave_scalar_map& options)
393 {
394  typedef typename T::element_type P;
395 
396  octave_value_list retval (3, Matrix ());
397 
398  image_region region (options);
399 
400  const octave_idx_type nFrames = frameidx.numel ();
401  const octave_idx_type nRows = region.row_out ();
402  const octave_idx_type nCols = region.col_out ();
403  T img;
404 
405  // imvec has all of the pages of a file, even the ones we are not
406  // interested in. We will use the first image that we will be actually
407  // reading to get information about the image.
408  const octave_idx_type def_elem = frameidx(0);
409 
410  const octave_idx_type row_start = region.row_start ();
411  const octave_idx_type col_start = region.col_start ();
412  const octave_idx_type row_shift = region.row_shift ();
413  const octave_idx_type col_shift = region.col_shift ();
414  const octave_idx_type row_cache = region.row_cache ();
415  const octave_idx_type col_cache = region.col_cache ();
416 
417  // GraphicsMagick (GM) keeps the image values in memory using whatever
418  // QuantumDepth it was built with independently of the original image
419  // bitdepth. Basically this means that if GM was built with quantum 16
420  // all values are scaled in the uint16 range. If the original image
421  // had an 8 bit depth, we need to rescale it for that range.
422  // However, if the image had a bitdepth of 32, then we will be returning
423  // a floating point image. In this case, the values need to be rescaled
424  // for the range [0 1] (this is what Matlab has documented on the page
425  // about image types but in some cases seems to be doing something else.
426  // See bug #39249).
427  // Finally, we must do the division ourselves (set a divisor) instead of
428  // using quantumOperator for the cases where we will be returning floating
429  // point and want things in the range [0 1]. This is the same reason why
430  // the divisor is of type double.
431  // uint64_t is used in expression because default 32-bit value overflows
432  // when depth() is 32.
433  // FIXME: in the next release of GraphicsMagick, MaxRGB should be replaced
434  // with QuantumRange since MaxRGB is already deprecated in ImageMagick.
435  double divisor;
436  if (imvec[def_elem].depth () == 32)
438  else
439  divisor = MaxRGB / ((uint64_t (1) << imvec[def_elem].depth ()) - 1);
440 
441  // FIXME: this workaround should probably be fixed in GM by creating a
442  // new ImageType BilevelMatteType
443  // Despite what GM documentation claims, opacity is not only on the types
444  // with Matte on the name. It is possible that an image is completely
445  // black (1 color), and have a second channel set for transparency (2nd
446  // color). Its type will be bilevel since there is no BilevelMatte. The
447  // only way to check for this seems to be by checking matte ().
448  Magick::ImageType type = imvec[def_elem].type ();
449  if (type == Magick::BilevelType && imvec[def_elem].matte ())
450  type = Magick::GrayscaleMatteType;
451 
452  // FIXME: ImageType is the type being used to represent the image in memory
453  // by GM. The real type may be different (see among others bug #36820). For
454  // example, a png file where all channels are equal may report being
455  // grayscale or even bilevel. But we must always return the real image in
456  // file. In some cases, the original image attributes are stored in the
457  // attributes but this is undocumented. This should be fixed in GM so that
458  // a method such as original_type returns an actual Magick::ImageType
459  if (imvec[0].magick () == "PNG")
460  {
461  // These values come from libpng, not GM:
462  // Grayscale = 0
463  // Palette = 2 + 1
464  // RGB = 2
465  // RGB + Alpha = 2 + 4
466  // Grayscale + Alpha = 4
467  // We won't bother with case 3 (palette) since those should be
468  // read by the function to read indexed images
469  const std::string type_str
470  = imvec[0].attribute ("PNG:IHDR.color-type-orig");
471 
472  if (type_str == "0")
473  type = Magick::GrayscaleType;
474  else if (type_str == "2")
475  type = Magick::TrueColorType;
476  else if (type_str == "6")
477  type = Magick::TrueColorMatteType;
478  else if (type_str == "4")
479  type = Magick::GrayscaleMatteType;
480  // Color types 0, 2, and 3 can also have alpha channel, conveyed
481  // via the "tRNS" chunk. For 0 and 2, it's limited to GIF-style
482  // binary transparency, while 3 can have any level of alpha per
483  // palette entry. We thus must check matte() to see if the image
484  // really doesn't have an alpha channel.
485  if (imvec[0].matte ())
486  {
487  if (type == Magick::GrayscaleType)
488  type = Magick::GrayscaleMatteType;
489  else if (type == Magick::TrueColorType)
490  type = Magick::TrueColorMatteType;
491  }
492  }
493 
494  // If the alpha channel was not requested, treat images as if
495  // it doesn't exist.
496  if (nargout < 3)
497  {
498  switch (type)
499  {
500  case Magick::GrayscaleMatteType:
501  type = Magick::GrayscaleType;
502  break;
503 
504  case Magick::PaletteMatteType:
505  type = Magick::PaletteType;
506  break;
507 
508  case Magick::TrueColorMatteType:
509  type = Magick::TrueColorType;
510  break;
511 
512  case Magick::ColorSeparationMatteType:
513  type = Magick::ColorSeparationType;
514  break;
515 
516  default:
517  // Do nothing other than silencing warnings about enumeration
518  // values not being handled in switch.
519  ;
520  }
521  }
522 
523  const octave_idx_type color_stride = nRows * nCols;
524  switch (type)
525  {
526  case Magick::BilevelType: // Monochrome bi-level image
527  case Magick::GrayscaleType: // Grayscale image
528  {
529  img = T (dim_vector (nRows, nCols, 1, nFrames));
530  P *img_fvec = img.fortran_vec ();
531 
532  octave_idx_type idx = 0;
533  for (octave_idx_type frame = 0; frame < nFrames; frame++)
534  {
535  octave_quit ();
536 
537  const Magick::PixelPacket *pix
538  = imvec[frameidx(frame)].getConstPixels (col_start, row_start,
539  col_cache, row_cache);
540 
541  for (octave_idx_type col = 0; col < nCols; col++)
542  {
543  for (octave_idx_type row = 0; row < nRows; row++)
544  {
545  img_fvec[idx++] = pix->red / divisor;
546  pix += row_shift;
547  }
548  pix -= col_shift;
549  }
550  }
551  break;
552  }
553 
554  case Magick::GrayscaleMatteType: // Grayscale image with opacity
555  {
556  img = T (dim_vector (nRows, nCols, 1, nFrames));
557  T alpha (dim_vector (nRows, nCols, 1, nFrames));
558  P *img_fvec = img.fortran_vec ();
559  P *a_fvec = alpha.fortran_vec ();
560 
561  octave_idx_type idx = 0;
562  for (octave_idx_type frame = 0; frame < nFrames; frame++)
563  {
564  octave_quit ();
565 
566  const Magick::PixelPacket *pix
567  = imvec[frameidx(frame)].getConstPixels (col_start, row_start,
568  col_cache, row_cache);
569 
570  for (octave_idx_type col = 0; col < nCols; col++)
571  {
572  for (octave_idx_type row = 0; row < nRows; row++)
573  {
574  img_fvec[idx] = pix->red / divisor;
575  a_fvec[idx] = (MaxRGB - pix->opacity) / divisor;
576  pix += row_shift;
577  idx++;
578  }
579  pix -= col_shift;
580  }
581  }
582  retval(2) = alpha;
583  break;
584  }
585 
586  case Magick::PaletteType: // Indexed color (palette) image
587  case Magick::TrueColorType: // Truecolor image
588  {
589  img = T (dim_vector (nRows, nCols, 3, nFrames));
590  P *img_fvec = img.fortran_vec ();
591 
592  const octave_idx_type frame_stride = color_stride * 3;
593  for (octave_idx_type frame = 0; frame < nFrames; frame++)
594  {
595  octave_quit ();
596 
597  const Magick::PixelPacket *pix
598  = imvec[frameidx(frame)].getConstPixels (col_start, row_start,
599  col_cache, row_cache);
600 
601  octave_idx_type idx = 0;
602  P *rbuf = img_fvec;
603  P *gbuf = img_fvec + color_stride;
604  P *bbuf = img_fvec + color_stride * 2;
605 
606  for (octave_idx_type col = 0; col < nCols; col++)
607  {
608  for (octave_idx_type row = 0; row < nRows; row++)
609  {
610  rbuf[idx] = pix->red / divisor;
611  gbuf[idx] = pix->green / divisor;
612  bbuf[idx] = pix->blue / divisor;
613  pix += row_shift;
614  idx++;
615  }
616  pix -= col_shift;
617  }
618  img_fvec += frame_stride;
619  }
620  break;
621  }
622 
623  case Magick::PaletteMatteType: // Indexed color image with opacity
624  case Magick::TrueColorMatteType: // Truecolor image with opacity
625  {
626  img = T (dim_vector (nRows, nCols, 3, nFrames));
627  T alpha (dim_vector (nRows, nCols, 1, nFrames));
628  P *img_fvec = img.fortran_vec ();
629  P *a_fvec = alpha.fortran_vec ();
630 
631  const octave_idx_type frame_stride = color_stride * 3;
632 
633  // Unlike the index for the other channels, this one won't need
634  // to be reset on each frame since it's a separate matrix.
635  octave_idx_type a_idx = 0;
636  for (octave_idx_type frame = 0; frame < nFrames; frame++)
637  {
638  octave_quit ();
639 
640  const Magick::PixelPacket *pix
641  = imvec[frameidx(frame)].getConstPixels (col_start, row_start,
642  col_cache, row_cache);
643 
644  octave_idx_type idx = 0;
645  P *rbuf = img_fvec;
646  P *gbuf = img_fvec + color_stride;
647  P *bbuf = img_fvec + color_stride * 2;
648 
649  for (octave_idx_type col = 0; col < nCols; col++)
650  {
651  for (octave_idx_type row = 0; row < nRows; row++)
652  {
653  rbuf[idx] = pix->red / divisor;
654  gbuf[idx] = pix->green / divisor;
655  bbuf[idx] = pix->blue / divisor;
656  a_fvec[a_idx++] = (MaxRGB - pix->opacity) / divisor;
657  pix += row_shift;
658  idx++;
659  }
660  pix -= col_shift;
661  }
662  img_fvec += frame_stride;
663  }
664  retval(2) = alpha;
665  break;
666  }
667 
668  case Magick::ColorSeparationType: // Cyan/Magenta/Yellow/Black (CMYK) image
669  {
670  img = T (dim_vector (nRows, nCols, 4, nFrames));
671  P *img_fvec = img.fortran_vec ();
672 
673  const octave_idx_type frame_stride = color_stride * 4;
674  for (octave_idx_type frame = 0; frame < nFrames; frame++)
675  {
676  octave_quit ();
677 
678  const Magick::PixelPacket *pix
679  = imvec[frameidx(frame)].getConstPixels (col_start, row_start,
680  col_cache, row_cache);
681 
682  octave_idx_type idx = 0;
683  P *cbuf = img_fvec;
684  P *mbuf = img_fvec + color_stride;
685  P *ybuf = img_fvec + color_stride * 2;
686  P *kbuf = img_fvec + color_stride * 3;
687 
688  for (octave_idx_type col = 0; col < nCols; col++)
689  {
690  for (octave_idx_type row = 0; row < nRows; row++)
691  {
692  cbuf[idx] = pix->red / divisor;
693  mbuf[idx] = pix->green / divisor;
694  ybuf[idx] = pix->blue / divisor;
695  kbuf[idx] = pix->opacity / divisor;
696  pix += row_shift;
697  idx++;
698  }
699  pix -= col_shift;
700  }
701  img_fvec += frame_stride;
702  }
703  break;
704  }
705 
706  // Cyan, magenta, yellow, and black with alpha (opacity) channel
707  case Magick::ColorSeparationMatteType:
708  {
709  img = T (dim_vector (nRows, nCols, 4, nFrames));
710  T alpha (dim_vector (nRows, nCols, 1, nFrames));
711  P *img_fvec = img.fortran_vec ();
712  P *a_fvec = alpha.fortran_vec ();
713 
714  const octave_idx_type frame_stride = color_stride * 4;
715 
716  // Unlike the index for the other channels, this one won't need
717  // to be reset on each frame since it's a separate matrix.
718  octave_idx_type a_idx = 0;
719  for (octave_idx_type frame = 0; frame < nFrames; frame++)
720  {
721  octave_quit ();
722 
723  const Magick::PixelPacket *pix
724  = imvec[frameidx(frame)].getConstPixels (col_start, row_start,
725  col_cache, row_cache);
726  // Note that for CMYKColorspace + matte (CMYKA), the opacity is
727  // stored in the associated IndexPacket.
728  const Magick::IndexPacket *apix
729  = imvec[frameidx(frame)].getConstIndexes ();
730 
731  octave_idx_type idx = 0;
732  P *cbuf = img_fvec;
733  P *mbuf = img_fvec + color_stride;
734  P *ybuf = img_fvec + color_stride * 2;
735  P *kbuf = img_fvec + color_stride * 3;
736 
737  for (octave_idx_type col = 0; col < nCols; col++)
738  {
739  for (octave_idx_type row = 0; row < nRows; row++)
740  {
741  cbuf[idx] = pix->red / divisor;
742  mbuf[idx] = pix->green / divisor;
743  ybuf[idx] = pix->blue / divisor;
744  kbuf[idx] = pix->opacity / divisor;
745  a_fvec[a_idx++] = (MaxRGB - *apix) / divisor;
746  pix += row_shift;
747  idx++;
748  }
749  pix -= col_shift;
750  }
751  img_fvec += frame_stride;
752  }
753  retval(2) = alpha;
754  break;
755  }
756 
757  default:
758  error ("__magick_read__: unknown Magick++ image type");
759  }
760 
761  retval(0) = img;
762 
763  return retval;
764 }
765 
766 // Read a file into vector of image objects.
767 void static
768 read_file (const std::string& filename, std::vector<Magick::Image>& imvec)
769 {
770  // FIXME: We need this on Windows because GraphicsMagick uses the ANSI API
771  // to open files on disc. In contrast, the API of ImageMagick uses UTF-8
772  // encoded strings. Should we somehow detect which is used on runtime and
773  // pass the file names accordingly? (See also bug #58493.)
774  std::string ascii_fname = sys::get_ASCII_filename (filename, true);
775 
776  try
777  {
778  Magick::readImages (&imvec, ascii_fname);
779  }
780  catch (const Magick::Warning& w)
781  {
782  warning ("Magick++ warning: %s", w.what ());
783  }
784  catch (const Magick::Exception& e)
785  {
786  error ("Magick++ exception: %s", e.what ());
787  }
788 }
789 
790 static void
791 maybe_initialize_magick ()
792 {
793  static bool initialized = false;
794 
795  if (! initialized)
796  {
797  // Save locale as GraphicsMagick might change this (fixed in
798  // GraphicsMagick since version 1.3.13 released on December 24, 2011)
799  const char *static_locale = setlocale (LC_ALL, nullptr);
800  const std::string locale = (static_locale ? static_locale : "");
801 
802  const std::string program_name
803  = sys::env::get_program_invocation_name ();
804  Magick::InitializeMagick (program_name.c_str ());
805 
806  // Restore locale from before GraphicsMagick initialisation
807  setlocale (LC_ALL, locale.c_str ());
808 
809  // Why should we give a warning?
810  // Magick does not tell us the real bitdepth of the image in file.
811  // The best we can have is the minimum between the bitdepth of the
812  // file and the quantum depth. So we never know if the file will
813  // actually be read correctly so we warn the user that it might
814  // be limited.
815  //
816  // Why we warn if < 16 instead of < 32 ?
817  // The reasons for < 32 is simply that it's the maximum quantum
818  // depth they support. However, very few people would actually
819  // need such support while being a major inconvenience to anyone
820  // else (8 bit images suddenly taking 4x more space will be
821  // critical for multi page images). It would also suggests that
822  // it covers all images which does not (it still does not support
823  // float point and signed integer images).
824  // On the other hand, 16bit images are much more common. If quantum
825  // depth is 8, there's a good chance that we will be limited. It
826  // is also the GraphicsMagick recommended setting and the default
827  // for ImageMagick.
828  if (QuantumDepth < 16)
829  warning_with_id ("Octave:GraphicsMagick-Quantum-Depth",
830  "your version of %s limits images to %d bits per pixel\n",
831  MagickPackageName, QuantumDepth);
832 
833  initialized = true;
834  }
835 }
836 
837 #endif
838 
839 DEFUN (__magick_read__, args, nargout,
840  doc: /* -*- texinfo -*-
841 @deftypefn {} {[@var{img}, @var{map}, @var{alpha}] =} __magick_read__ (@var{fname}, @var{options})
842 Read image with GraphicsMagick or ImageMagick.
843 
844 This is a private internal function not intended for direct use.
845 Use @code{imread} instead.
846 
847 @seealso{imfinfo, imformats, imread, imwrite}
848 @end deftypefn */)
849 {
850 #if defined (HAVE_MAGICK)
851 
852  if (args.length () != 2 || ! args(0).is_string ())
853  print_usage ();
854 
855  maybe_initialize_magick ();
856 
857  const octave_scalar_map options
858  = args(1).xscalar_map_value ("__magick_read__: OPTIONS must be a struct");
859 
860  octave_value_list output;
861 
862  std::vector<Magick::Image> imvec;
863  read_file (args(0).string_value (), imvec);
864 
865  // Prepare an Array with the indexes for the requested frames.
866  const octave_idx_type nFrames = imvec.size ();
867  Array<octave_idx_type> frameidx;
868  const octave_value indexes = options.getfield ("index");
869  if (indexes.is_string () && indexes.string_value () == "all")
870  {
871  frameidx.resize (dim_vector (1, nFrames));
872  for (octave_idx_type i = 0; i < nFrames; i++)
873  frameidx(i) = i;
874  }
875  else
876  {
877  frameidx = indexes.xint_vector_value ("__magick_read__: invalid value for Index/Frame");
878 
879  // Fix indexes from base 1 to base 0, and at the same time, make
880  // sure none of the indexes is outside the range of image number.
881  const octave_idx_type n = frameidx.numel ();
882  for (octave_idx_type i = 0; i < n; i++)
883  {
884  frameidx(i)--;
885  if (frameidx(i) < 0 || frameidx(i) > nFrames - 1)
886  {
887  // We do this check inside the loop because frameidx does not
888  // need to be ordered (this is a feature and even allows for
889  // some frames to be read multiple times).
890  error ("imread: index/frames specified are outside the number of images");
891  }
892  }
893  }
894 
895  // Check that all frames have the same size. We don't do this at the same
896  // time we decode the image because that's done in many different places,
897  // to cover the different types of images which would lead to a lot of
898  // copy and paste.
899  {
900  const unsigned int nRows = imvec[frameidx(0)].rows ();
901  const unsigned int nCols = imvec[frameidx(0)].columns ();
902  const octave_idx_type n = frameidx.numel ();
903  for (octave_idx_type frame = 0; frame < n; frame++)
904  {
905  if (nRows != imvec[frameidx(frame)].rows ()
906  || nCols != imvec[frameidx(frame)].columns ())
907  {
908  error ("imread: all frames must have the same size but frame "
909  "%" OCTAVE_IDX_TYPE_FORMAT " is different",
910  frameidx(frame) +1);
911  }
912  }
913  }
914 
915  const octave_idx_type depth = get_depth (imvec[frameidx(0)]);
916  if (is_indexed (imvec[frameidx(0)]))
917  {
918  if (depth <= 1)
919  output = read_indexed_images<boolNDArray> (imvec, frameidx,
920  nargout, options);
921  else if (depth <= 8)
922  output = read_indexed_images<uint8NDArray> (imvec, frameidx,
923  nargout, options);
924  else if (depth <= 16)
925  output = read_indexed_images<uint16NDArray> (imvec, frameidx,
926  nargout, options);
927  else
928  error ("imread: indexed images with depths greater than 16-bit are not supported");
929  }
930 
931  else
932  {
933  if (depth <= 1)
934  output = read_images<boolNDArray> (imvec, frameidx, nargout, options);
935  else if (depth <= 8)
936  output = read_images<uint8NDArray> (imvec, frameidx, nargout, options);
937  else if (depth <= 16)
938  output = read_images<uint16NDArray> (imvec, frameidx, nargout, options);
939  else if (depth <= 32)
940  output = read_images<FloatNDArray> (imvec, frameidx, nargout, options);
941  else
942  error ("imread: reading of images with %" OCTAVE_IDX_TYPE_FORMAT
943  "-bit depth is not supported", depth);
944  }
945 
946  return output;
947 
948 #else
949 
950  octave_unused_parameter (args);
951  octave_unused_parameter (nargout);
952 
953  err_disabled_feature ("imread", "Image IO");
954 
955 #endif
956 }
957 
958 /*
959 ## No test needed for internal helper function.
960 %!assert (1)
961 */
962 
963 #if defined (HAVE_MAGICK)
964 
965 template <typename T>
966 static uint32NDArray
967 img_float2uint (const T& img)
968 {
969  typedef typename T::element_type P;
970  uint32NDArray out (img.dims ());
971 
972  octave_uint32 *out_fvec = out.fortran_vec ();
973  const P *img_fvec = img.data ();
974 
976  const octave_idx_type numel = img.numel ();
977  for (octave_idx_type idx = 0; idx < numel; idx++)
978  out_fvec[idx] = img_fvec[idx] * max;
979 
980  return out;
981 }
982 
983 // Gets the bitdepth to be used for an Octave class, i.e, returns 8 for
984 // uint8, 16 for uint16, and 32 for uint32
985 template <typename T>
986 static octave_idx_type
987 bitdepth_from_class ()
988 {
989  typedef typename T::element_type P;
990  const octave_idx_type bitdepth
991  = sizeof (P) * std::numeric_limits<unsigned char>::digits;
992  return bitdepth;
993 }
994 
995 static Magick::Image
996 init_enconde_image (const octave_idx_type& nCols, const octave_idx_type& nRows,
997  const octave_idx_type& bitdepth,
998  const Magick::ImageType& type,
999  const Magick::ClassType& klass)
1000 {
1001  Magick::Image img (Magick::Geometry (nCols, nRows), "black");
1002  // Ensure that there are no other references to this image.
1003  img.modifyImage ();
1004 
1005  img.classType (klass);
1006  img.type (type);
1007  // FIXME: for some reason, setting bitdepth doesn't seem to work for
1008  // indexed images.
1009  img.depth (bitdepth);
1010  switch (type)
1011  {
1012  case Magick::GrayscaleMatteType:
1013  case Magick::TrueColorMatteType:
1014  case Magick::ColorSeparationMatteType:
1015  case Magick::PaletteMatteType:
1016  img.matte (true);
1017  break;
1018 
1019  default:
1020  img.matte (false);
1021  }
1022 
1023  return img;
1024 }
1025 
1026 template <typename T>
1027 static void
1028 encode_indexed_images (std::vector<Magick::Image>& imvec,
1029  const T& img,
1030  const Matrix& cmap)
1031 {
1032  typedef typename T::element_type P;
1033  const octave_idx_type nFrames = (img.ndims () < 4 ? 1 : img.dims ()(3));
1034  const octave_idx_type nRows = img.rows ();
1035  const octave_idx_type nCols = img.columns ();
1036  const octave_idx_type cmap_size = cmap.rows ();
1037  const octave_idx_type bitdepth = bitdepth_from_class<T> ();
1038 
1039  // There is no colormap object, we need to build a new one for each frame,
1040  // even if it's always the same. We can least get a vector for the Colors.
1041  std::vector<Magick::ColorRGB> colormap;
1042  {
1043  const double *cmap_fvec = cmap.data ();
1044  const octave_idx_type G_offset = cmap_size;
1045  const octave_idx_type B_offset = cmap_size * 2;
1046  for (octave_idx_type map_idx = 0; map_idx < cmap_size; map_idx++)
1047  colormap.push_back (Magick::ColorRGB (cmap_fvec[map_idx],
1048  cmap_fvec[map_idx + G_offset],
1049  cmap_fvec[map_idx + B_offset]));
1050  }
1051 
1052  for (octave_idx_type frame = 0; frame < nFrames; frame++)
1053  {
1054  octave_quit ();
1055 
1056  Magick::Image m_img = init_enconde_image (nCols, nRows, bitdepth,
1057  Magick::PaletteType,
1058  Magick::PseudoClass);
1059 
1060  // Insert colormap.
1061  m_img.colorMapSize (cmap_size);
1062  for (octave_idx_type map_idx = 0; map_idx < cmap_size; map_idx++)
1063  m_img.colorMap (map_idx, colormap[map_idx]);
1064 
1065  // Why are we also setting the pixel values instead of only the
1066  // index values? We don't know if a file format supports indexed
1067  // images. If we only set the indexes and then try to save the
1068  // image as JPEG for example, the indexed values get discarded,
1069  // there is no conversion from the indexes, it's the initial values
1070  // that get used. An alternative would be to only set the pixel
1071  // values (no indexes), then set the image as PseudoClass and GM
1072  // would create a colormap for us. However, we wouldn't have control
1073  // over the order of that colormap. And that's why we set both.
1074  Magick::PixelPacket *pix = m_img.getPixels (0, 0, nCols, nRows);
1075  Magick::IndexPacket *ind = m_img.getIndexes ();
1076  const P *img_fvec = img.data ();
1077 
1078  octave_idx_type GM_idx = 0;
1079  for (octave_idx_type column = 0; column < nCols; column++)
1080  {
1081  for (octave_idx_type row = 0; row < nRows; row++)
1082  {
1083  ind[GM_idx] = double (*img_fvec);
1084  pix[GM_idx] = m_img.colorMap (double (*img_fvec));
1085  img_fvec++;
1086  GM_idx += nCols;
1087  }
1088  GM_idx -= nCols * nRows - 1;
1089  }
1090 
1091  // Save changes to underlying image.
1092  m_img.syncPixels ();
1093  imvec.push_back (m_img);
1094  }
1095 }
1096 
1097 static void
1098 encode_bool_image (std::vector<Magick::Image>& imvec, const boolNDArray& img)
1099 {
1100  const octave_idx_type nFrames = (img.ndims () < 4 ? 1 : img.dims ()(3));
1101  const octave_idx_type nRows = img.rows ();
1102  const octave_idx_type nCols = img.columns ();
1103 
1104  // The initialized image will be black, this is for the other pixels
1105  const Magick::Color white ("white");
1106 
1107  const bool *img_fvec = img.data ();
1108  octave_idx_type img_idx = 0;
1109  for (octave_idx_type frame = 0; frame < nFrames; frame++)
1110  {
1111  octave_quit ();
1112 
1113  // For some reason, we can't set the type to Magick::BilevelType or
1114  // the output image will be black, changing to white has no effect.
1115  // However, this will still work fine and a binary image will be
1116  // saved because we are setting the bitdepth to 1.
1117  Magick::Image m_img = init_enconde_image (nCols, nRows, 1,
1118  Magick::GrayscaleType,
1119  Magick::DirectClass);
1120 
1121  Magick::PixelPacket *pix = m_img.getPixels (0, 0, nCols, nRows);
1122  octave_idx_type GM_idx = 0;
1123  for (octave_idx_type col = 0; col < nCols; col++)
1124  {
1125  for (octave_idx_type row = 0; row < nRows; row++)
1126  {
1127  if (img_fvec[img_idx])
1128  pix[GM_idx] = white;
1129 
1130  img_idx++;
1131  GM_idx += nCols;
1132  }
1133  GM_idx -= nCols * nRows - 1;
1134  }
1135  // Save changes to underlying image.
1136  m_img.syncPixels ();
1137  // While we could not set it to Bilevel at the start, we can do it
1138  // here otherwise some coders won't save it as binary.
1139  m_img.type (Magick::BilevelType);
1140  imvec.push_back (m_img);
1141  }
1142 }
1143 
1144 template <typename T>
1145 static void
1146 encode_uint_image (std::vector<Magick::Image>& imvec,
1147  const T& img, const T& alpha)
1148 {
1149  typedef typename T::element_type P;
1150  const octave_idx_type channels = (img.ndims () < 3 ? 1 : img.dims ()(2));
1151  const octave_idx_type nFrames = (img.ndims () < 4 ? 1 : img.dims ()(3));
1152  const octave_idx_type nRows = img.rows ();
1153  const octave_idx_type nCols = img.columns ();
1154  const octave_idx_type bitdepth = bitdepth_from_class<T> ();
1155 
1156  Magick::ImageType type;
1157  const bool has_alpha = ! alpha.isempty ();
1158  switch (channels)
1159  {
1160  case 1:
1161  if (has_alpha)
1162  type = Magick::GrayscaleMatteType;
1163  else
1164  type = Magick::GrayscaleType;
1165  break;
1166 
1167  case 3:
1168  if (has_alpha)
1169  type = Magick::TrueColorMatteType;
1170  else
1171  type = Magick::TrueColorType;
1172  break;
1173 
1174  case 4:
1175  if (has_alpha)
1176  type = Magick::ColorSeparationMatteType;
1177  else
1178  type = Magick::ColorSeparationType;
1179  break;
1180 
1181  default:
1182  // __imwrite should have already filtered this cases
1183  error ("__magick_write__: wrong size on 3rd dimension");
1184  }
1185 
1186  // We will be passing the values as integers with depth as specified
1187  // by QuantumDepth (maximum value specified by MaxRGB). This is independent
1188  // of the actual depth of the image. GM will then convert the values but
1189  // while in memory, it always keeps the values as specified by QuantumDepth.
1190  // From GM documentation:
1191  // Color arguments are must be scaled to fit the Quantum size according to
1192  // the range of MaxRGB
1193  const double divisor = static_cast<double> ((uint64_t (1) << bitdepth) - 1)
1194  / MaxRGB;
1195 
1196  const P *img_fvec = img.data ();
1197  const P *a_fvec = alpha.data ();
1198  switch (type)
1199  {
1200  case Magick::GrayscaleType:
1201  {
1202  for (octave_idx_type frame = 0; frame < nFrames; frame++)
1203  {
1204  octave_quit ();
1205 
1206  Magick::Image m_img = init_enconde_image (nCols, nRows, bitdepth,
1207  type,
1208  Magick::DirectClass);
1209 
1210  Magick::PixelPacket *pix = m_img.getPixels (0, 0, nCols, nRows);
1211  octave_idx_type GM_idx = 0;
1212  for (octave_idx_type col = 0; col < nCols; col++)
1213  {
1214  for (octave_idx_type row = 0; row < nRows; row++)
1215  {
1216  const double grey = math::round (double (*img_fvec) / divisor);
1217  Magick::Color c (grey, grey, grey);
1218  pix[GM_idx] = c;
1219  img_fvec++;
1220  GM_idx += nCols;
1221  }
1222  GM_idx -= nCols * nRows - 1;
1223  }
1224  // Save changes to underlying image.
1225  m_img.syncPixels ();
1226  imvec.push_back (m_img);
1227  }
1228  break;
1229  }
1230 
1231  case Magick::GrayscaleMatteType:
1232  {
1233  for (octave_idx_type frame = 0; frame < nFrames; frame++)
1234  {
1235  octave_quit ();
1236 
1237  Magick::Image m_img = init_enconde_image (nCols, nRows, bitdepth,
1238  type,
1239  Magick::DirectClass);
1240 
1241  Magick::PixelPacket *pix = m_img.getPixels (0, 0, nCols, nRows);
1242  octave_idx_type GM_idx = 0;
1243  for (octave_idx_type col = 0; col < nCols; col++)
1244  {
1245  for (octave_idx_type row = 0; row < nRows; row++)
1246  {
1247  double grey = math::round (double (*img_fvec) / divisor);
1248  Magick::Color c (grey, grey, grey,
1249  MaxRGB - math::round (double (*a_fvec) / divisor));
1250  pix[GM_idx] = c;
1251  img_fvec++;
1252  a_fvec++;
1253  GM_idx += nCols;
1254  }
1255  GM_idx -= nCols * nRows - 1;
1256  }
1257  // Save changes to underlying image.
1258  m_img.syncPixels ();
1259  imvec.push_back (m_img);
1260  }
1261  break;
1262  }
1263 
1264  case Magick::TrueColorType:
1265  {
1266  // The data offset for the green and blue channels
1267  const octave_idx_type G_offset = nCols * nRows;
1268  const octave_idx_type B_offset = nCols * nRows * 2;
1269  for (octave_idx_type frame = 0; frame < nFrames; frame++)
1270  {
1271  octave_quit ();
1272 
1273  Magick::Image m_img = init_enconde_image (nCols, nRows, bitdepth,
1274  type,
1275  Magick::DirectClass);
1276 
1277  Magick::PixelPacket *pix = m_img.getPixels (0, 0, nCols, nRows);
1278  octave_idx_type GM_idx = 0;
1279  for (octave_idx_type col = 0; col < nCols; col++)
1280  {
1281  for (octave_idx_type row = 0; row < nRows; row++)
1282  {
1283  Magick::Color c (math::round (double (*img_fvec) / divisor),
1284  math::round (double (img_fvec[G_offset]) / divisor),
1285  math::round (double (img_fvec[B_offset]) / divisor));
1286  pix[GM_idx] = c;
1287  img_fvec++;
1288  GM_idx += nCols;
1289  }
1290  GM_idx -= nCols * nRows - 1;
1291  }
1292  // Save changes to underlying image.
1293  m_img.syncPixels ();
1294  imvec.push_back (m_img);
1295  img_fvec += B_offset;
1296  }
1297  break;
1298  }
1299 
1300  case Magick::TrueColorMatteType:
1301  {
1302  // The data offset for the green and blue channels
1303  const octave_idx_type G_offset = nCols * nRows;
1304  const octave_idx_type B_offset = nCols * nRows * 2;
1305  for (octave_idx_type frame = 0; frame < nFrames; frame++)
1306  {
1307  octave_quit ();
1308 
1309  Magick::Image m_img = init_enconde_image (nCols, nRows, bitdepth,
1310  type,
1311  Magick::DirectClass);
1312 
1313  Magick::PixelPacket *pix = m_img.getPixels (0, 0, nCols, nRows);
1314  octave_idx_type GM_idx = 0;
1315  for (octave_idx_type col = 0; col < nCols; col++)
1316  {
1317  for (octave_idx_type row = 0; row < nRows; row++)
1318  {
1319  Magick::Color c (math::round (double (*img_fvec) / divisor),
1320  math::round (double (img_fvec[G_offset]) / divisor),
1321  math::round (double (img_fvec[B_offset]) / divisor),
1322  MaxRGB - math::round (double (*a_fvec) / divisor));
1323  pix[GM_idx] = c;
1324  img_fvec++;
1325  a_fvec++;
1326  GM_idx += nCols;
1327  }
1328  GM_idx -= nCols * nRows - 1;
1329  }
1330  // Save changes to underlying image.
1331  m_img.syncPixels ();
1332  imvec.push_back (m_img);
1333  img_fvec += B_offset;
1334  }
1335  break;
1336  }
1337 
1338  case Magick::ColorSeparationType:
1339  {
1340  // The data offset for the Magenta, Yellow, and blacK channels
1341  const octave_idx_type M_offset = nCols * nRows;
1342  const octave_idx_type Y_offset = nCols * nRows * 2;
1343  const octave_idx_type K_offset = nCols * nRows * 3;
1344  for (octave_idx_type frame = 0; frame < nFrames; frame++)
1345  {
1346  octave_quit ();
1347 
1348  Magick::Image m_img = init_enconde_image (nCols, nRows, bitdepth,
1349  type,
1350  Magick::DirectClass);
1351 
1352  Magick::PixelPacket *pix = m_img.getPixels (0, 0, nCols, nRows);
1353  octave_idx_type GM_idx = 0;
1354  for (octave_idx_type col = 0; col < nCols; col++)
1355  {
1356  for (octave_idx_type row = 0; row < nRows; row++)
1357  {
1358  Magick::Color c (math::round (double (*img_fvec) / divisor),
1359  math::round (double (img_fvec[M_offset]) / divisor),
1360  math::round (double (img_fvec[Y_offset]) / divisor),
1361  math::round (double (img_fvec[K_offset]) / divisor));
1362  pix[GM_idx] = c;
1363  img_fvec++;
1364  GM_idx += nCols;
1365  }
1366  GM_idx -= nCols * nRows - 1;
1367  }
1368  // Save changes to underlying image.
1369  m_img.syncPixels ();
1370  imvec.push_back (m_img);
1371  img_fvec += K_offset;
1372  }
1373  break;
1374  }
1375 
1376  case Magick::ColorSeparationMatteType:
1377  {
1378  // The data offset for the Magenta, Yellow, and blacK channels
1379  const octave_idx_type M_offset = nCols * nRows;
1380  const octave_idx_type Y_offset = nCols * nRows * 2;
1381  const octave_idx_type K_offset = nCols * nRows * 3;
1382  for (octave_idx_type frame = 0; frame < nFrames; frame++)
1383  {
1384  octave_quit ();
1385 
1386  Magick::Image m_img = init_enconde_image (nCols, nRows, bitdepth,
1387  type,
1388  Magick::DirectClass);
1389 
1390  Magick::PixelPacket *pix = m_img.getPixels (0, 0, nCols, nRows);
1391  Magick::IndexPacket *ind = m_img.getIndexes ();
1392  octave_idx_type GM_idx = 0;
1393  for (octave_idx_type col = 0; col < nCols; col++)
1394  {
1395  for (octave_idx_type row = 0; row < nRows; row++)
1396  {
1397  Magick::Color c (math::round (double (*img_fvec) / divisor),
1398  math::round (double (img_fvec[M_offset]) / divisor),
1399  math::round (double (img_fvec[Y_offset]) / divisor),
1400  math::round (double (img_fvec[K_offset]) / divisor));
1401  pix[GM_idx] = c;
1402  ind[GM_idx] = MaxRGB - math::round (double (*a_fvec) / divisor);
1403  img_fvec++;
1404  a_fvec++;
1405  GM_idx += nCols;
1406  }
1407  GM_idx -= nCols * nRows - 1;
1408  }
1409  // Save changes to underlying image.
1410  m_img.syncPixels ();
1411  imvec.push_back (m_img);
1412  img_fvec += K_offset;
1413  }
1414  break;
1415  }
1416 
1417  default:
1418  error ("__magick_write__: unrecognized Magick::ImageType");
1419  }
1420 
1421  return;
1422 }
1423 
1424 // Meant to be shared with both imfinfo and imwrite.
1425 static std::map<octave_idx_type, std::string>
1426 init_disposal_methods ()
1427 {
1428  // GIF Specifications:
1429  //
1430  // Disposal Method - Indicates the way in which the graphic is to
1431  // be treated after being displayed.
1432  //
1433  // 0 - No disposal specified. The decoder is
1434  // not required to take any action.
1435  // 1 - Do not dispose. The graphic is to be left
1436  // in place.
1437  // 2 - Restore to background color. The area used by the
1438  // graphic must be restored to the background color.
1439  // 3 - Restore to previous. The decoder is required to
1440  // restore the area overwritten by the graphic with
1441  // what was there prior to rendering the graphic.
1442  // 4-7 - To be defined.
1443  static std::map<octave_idx_type, std::string> methods;
1444  if (methods.empty ())
1445  {
1446  methods[0] = "doNotSpecify";
1447  methods[1] = "leaveInPlace";
1448  methods[2] = "restoreBG";
1449  methods[3] = "restorePrevious";
1450  }
1451  return methods;
1452 }
1453 static std::map<std::string, octave_idx_type>
1454 init_reverse_disposal_methods ()
1455 {
1456  static std::map<std::string, octave_idx_type> methods;
1457  if (methods.empty ())
1458  {
1459  methods["donotspecify"] = 0;
1460  methods["leaveinplace"] = 1;
1461  methods["restorebg"] = 2;
1462  methods["restoreprevious"] = 3;
1463  }
1464  return methods;
1465 }
1466 
1467 void static
1468 write_file (const std::string& filename,
1469  const std::string& ext,
1470  std::vector<Magick::Image>& imvec)
1471 {
1472  try
1473  {
1474  Magick::writeImages (imvec.begin (), imvec.end (), ext + ':' + filename);
1475  }
1476  catch (const Magick::Warning& w)
1477  {
1478  warning ("Magick++ warning: %s", w.what ());
1479  }
1480  catch (const Magick::ErrorCoder& e)
1481  {
1482  warning ("Magick++ coder error: %s", e.what ());
1483  }
1484  catch (const Magick::Exception& e)
1485  {
1486  error ("Magick++ exception: %s", e.what ());
1487  }
1488 }
1489 
1490 #endif
1491 
1492 DEFUN (__magick_write__, args, ,
1493  doc: /* -*- texinfo -*-
1494 @deftypefn {} {} __magick_write__ (@var{fname}, @var{fmt}, @var{img}, @var{map}, @var{options})
1495 Write image with GraphicsMagick or ImageMagick.
1496 
1497 This is a private internal function not intended for direct use.
1498 Use @code{imwrite} instead.
1499 
1500 @seealso{imfinfo, imformats, imread, imwrite}
1501 @end deftypefn */)
1502 {
1503 #if defined (HAVE_MAGICK)
1504 
1505  if (args.length () != 5 || ! args(0).is_string () || ! args(1).is_string ())
1506  print_usage ();
1507 
1508  maybe_initialize_magick ();
1509 
1510  const std::string filename = args(0).string_value ();
1511  const std::string ext = args(1).string_value ();
1512 
1513  const octave_scalar_map options
1514  = args(4).xscalar_map_value ("__magick_write__: OPTIONS must be a struct");
1515 
1516  const octave_value img = args(2);
1517  const Matrix cmap = args(3).xmatrix_value ("__magick_write__: invalid MAP");
1518 
1519  std::vector<Magick::Image> imvec;
1520 
1521  if (cmap.isempty ())
1522  {
1523  const octave_value alpha = options.getfield ("alpha");
1524  if (img.islogical ())
1525  encode_bool_image (imvec, img.bool_array_value ());
1526  else if (img.is_uint8_type ())
1527  encode_uint_image<uint8NDArray> (imvec, img.uint8_array_value (),
1528  alpha.uint8_array_value ());
1529  else if (img.is_uint16_type ())
1530  encode_uint_image<uint16NDArray> (imvec, img.uint16_array_value (),
1531  alpha.uint16_array_value ());
1532  else if (img.is_uint32_type ())
1533  encode_uint_image<uint32NDArray> (imvec, img.uint32_array_value (),
1534  alpha.uint32_array_value ());
1535  else if (img.isfloat ())
1536  {
1537  // For image formats that support floating point values, we write
1538  // the actual values. For those who don't, we only use the values
1539  // on the range [0 1] and save integer values.
1540  // But here, even for formats that would support floating point
1541  // values, GM seems unable to do that so we at least make them uint32.
1542  uint32NDArray clip_img;
1543  uint32NDArray clip_alpha;
1544  if (img.is_single_type ())
1545  {
1546  clip_img = img_float2uint<FloatNDArray>
1547  (img.float_array_value ());
1548  clip_alpha = img_float2uint<FloatNDArray>
1549  (alpha.float_array_value ());
1550  }
1551  else
1552  {
1553  clip_img = img_float2uint<NDArray> (img.array_value ());
1554  clip_alpha = img_float2uint<NDArray> (alpha.array_value ());
1555  }
1556  encode_uint_image<uint32NDArray> (imvec, clip_img, clip_alpha);
1557  }
1558  else
1559  error ("__magick_write__: image type not supported");
1560  }
1561  else
1562  {
1563  // We should not get floating point indexed images here because we
1564  // converted them in __imwrite__.m. We should probably do it here
1565  // but it would look much messier.
1566  if (img.is_uint8_type ())
1567  encode_indexed_images<uint8NDArray> (imvec, img.uint8_array_value (),
1568  cmap);
1569  else if (img.is_uint16_type ())
1570  encode_indexed_images<uint16NDArray> (imvec, img.uint16_array_value (),
1571  cmap);
1572  else
1573  error ("__magick_write__: indexed image must be uint8, uint16 or float");
1574  }
1575  static std::map<std::string, octave_idx_type> disposal_methods
1576  = init_reverse_disposal_methods ();
1577 
1578  const octave_idx_type nFrames = imvec.size ();
1579 
1580  const octave_idx_type quality = options.getfield ("quality").int_value ();
1581  const ColumnVector delaytime
1582  = options.getfield ("delaytime").column_vector_value ();
1583  const Array<std::string> disposalmethod
1584  = options.getfield ("disposalmethod").cellstr_value ();
1585  for (octave_idx_type i = 0; i < nFrames; i++)
1586  {
1587  imvec[i].quality (quality);
1588  imvec[i].animationDelay (delaytime(i));
1589  imvec[i].gifDisposeMethod (disposal_methods[disposalmethod(i)]);
1590  }
1591 
1592  // If writemode is set to append, read the image and append to it. Even
1593  // if set to append, make sure that something was read at all.
1594  const std::string writemode = options.getfield ("writemode").string_value ();
1595  if (writemode == "append" && sys::file_exists (filename))
1596  {
1597  std::vector<Magick::Image> ini_imvec;
1598  read_file (filename, ini_imvec);
1599 
1600  if (ini_imvec.size () > 0)
1601  {
1602  ini_imvec.insert (ini_imvec.end (), imvec.begin (), imvec.end ());
1603  ini_imvec.swap (imvec);
1604  }
1605  }
1606 
1607  // FIXME: LoopCount or animationIterations
1608  // How it should work:
1609  //
1610  // This value is only set for the first image in the sequence. Trying
1611  // to set this value with the append mode should have no effect, the
1612  // value used with the first image is the one that counts (that would
1613  // also be Matlab compatible). Thus, the right way to do this would be
1614  // to have an else block on the condition above, and set this only
1615  // when creating a new file. Since Matlab does not interpret a 4D
1616  // matrix as sequence of images to write, its users need to use a for
1617  // loop and set LoopCount only on the first iteration (it actually
1618  // throws warnings otherwise)
1619  //
1620  // Why is this not done the right way:
1621  //
1622  // When GM saves a single image, it discards the value if there is only
1623  // a single image and sets it to "no loop". Since our default is an
1624  // infinite loop, if the user tries to do it the Matlab way (setting
1625  // LoopCount only on the first image) that value will go nowhere.
1626  // See https://sourceforge.net/p/graphicsmagick/bugs/248/
1627  // Because of this, we document to set LoopCount on every iteration
1628  // (in Matlab will cause a lot of warnings), or pass a 4D matrix with
1629  // all frames (won't work in Matlab at all).
1630  // Note that this only needs to be set on the first frame
1631  imvec[0].animationIterations (options.getfield ("loopcount").uint_value ());
1632 
1633  const std::string compression
1634  = options.getfield ("compression").string_value ();
1635 
1636 #define COMPRESS_MAGICK_IMAGE_VECTOR(GM_TYPE) \
1637  for (std::vector<Magick::Image>::size_type i = 0; i < imvec.size (); i++) \
1638  imvec[i].compressType (GM_TYPE)
1639 
1640  if (compression == "none")
1641  COMPRESS_MAGICK_IMAGE_VECTOR (Magick::NoCompression);
1642  else if (compression == "bzip")
1643  COMPRESS_MAGICK_IMAGE_VECTOR (Magick::BZipCompression);
1644  else if (compression == "fax3")
1645  COMPRESS_MAGICK_IMAGE_VECTOR (Magick::FaxCompression);
1646  else if (compression == "fax4")
1647  COMPRESS_MAGICK_IMAGE_VECTOR (Magick::Group4Compression);
1648  else if (compression == "jpeg")
1649  COMPRESS_MAGICK_IMAGE_VECTOR (Magick::JPEGCompression);
1650  else if (compression == "lzw")
1651  COMPRESS_MAGICK_IMAGE_VECTOR (Magick::LZWCompression);
1652  else if (compression == "rle")
1653  COMPRESS_MAGICK_IMAGE_VECTOR (Magick::RLECompression);
1654  else if (compression == "deflate")
1655  COMPRESS_MAGICK_IMAGE_VECTOR (Magick::ZipCompression);
1656 
1657 #undef COMPRESS_MAGICK_IMAGE_VECTOR
1658 
1659  write_file (filename, ext, imvec);
1660 
1661  return ovl ();
1662 
1663 #else
1664 
1665  octave_unused_parameter (args);
1666 
1667  err_disabled_feature ("imwrite", "Image IO");
1668 
1669 #endif
1670 }
1671 
1672 /*
1673 ## No test needed for internal helper function.
1674 %!assert (1)
1675 */
1676 
1677 // Gets the minimum information from images such as its size and format. Much
1678 // faster than using imfinfo, which slows down a lot since. Note than without
1679 // this, we need to read the image once for imfinfo to set defaults (which is
1680 // done in Octave language), and then again for the actual reading.
1681 DEFUN (__magick_ping__, args, ,
1682  doc: /* -*- texinfo -*-
1683 @deftypefn {} {@var{fmt} =} __magick_ping__ (@var{fname}, @var{idx})
1684 Ping image information with GraphicsMagick or ImageMagick.
1685 
1686 This is a private internal function not intended for direct use.
1687 
1688 @seealso{imfinfo}
1689 @end deftypefn */)
1690 {
1691 #if defined (HAVE_MAGICK)
1692 
1693  if (args.length () < 1 || ! args(0).is_string ())
1694  print_usage ();
1695 
1696  maybe_initialize_magick ();
1697 
1698  const std::string filename = args(0).string_value ();
1699 
1700  int idx;
1701  if (args.length () > 1)
1702  idx = args(1).int_value () -1;
1703  else
1704  idx = 0;
1705 
1706  Magick::Image img;
1707  img.subImage (idx); // start ping from this image (in case of multi-page)
1708  img.subRange (1); // ping only one of them
1709 
1710  // FIXME: We need this on Windows because GraphicsMagick uses the ANSI API
1711  // to open files on disc. In contrast, the API of ImageMagick uses UTF-8
1712  // encoded strings. Should we somehow detect which is used on runtime and
1713  // pass the file names accordingly? (See also bug #58493.)
1714  std::string ascii_fname = sys::get_ASCII_filename (filename, true);
1715 
1716  try
1717  {
1718  img.ping (ascii_fname);
1719  }
1720  catch (const Magick::Warning& w)
1721  {
1722  warning ("Magick++ warning: %s", w.what ());
1723  }
1724  catch (const Magick::Exception& e)
1725  {
1726  error ("Magick++ exception: %s", e.what ());
1727  }
1728 
1729  static const char *fields[] = {"rows", "columns", "format", nullptr};
1731  ping.setfield ("rows", octave_value (img.rows ()));
1732  ping.setfield ("columns", octave_value (img.columns ()));
1733  ping.setfield ("format", octave_value (img.magick ()));
1734 
1735  return ovl (ping);
1736 
1737 #else
1738 
1739  octave_unused_parameter (args);
1740 
1741  err_disabled_feature ("imfinfo", "Image IO");
1742 
1743 #endif
1744 }
1745 
1746 #if defined (HAVE_MAGICK)
1747 
1748 static octave_value
1749 magick_to_octave_value (const Magick::CompressionType& magick)
1750 {
1751  switch (magick)
1752  {
1753  case Magick::NoCompression:
1754  return octave_value ("none");
1755  case Magick::BZipCompression:
1756  return octave_value ("bzip");
1757  case Magick::FaxCompression:
1758  return octave_value ("fax3");
1759  case Magick::Group4Compression:
1760  return octave_value ("fax4");
1761  case Magick::JPEGCompression:
1762  return octave_value ("jpeg");
1763  case Magick::LZWCompression:
1764  return octave_value ("lzw");
1765  case Magick::RLECompression:
1766  // This is named "rle" for the HDF, but the same thing is named
1767  // "ccitt" and "PackBits" for binary and non-binary images in TIFF.
1768  return octave_value ("rle");
1769  case Magick::ZipCompression:
1770  return octave_value ("deflate");
1771 
1772  // The following are present only in recent versions of GraphicsMagick.
1773  // At the moment the only use of this would be to have imfinfo report
1774  // the compression method. In the future, someone could implement
1775  // the Compression option for imwrite in which case a macro in
1776  // configure.ac will have to check for their presence of this.
1777  // See bug #39913
1778  // case Magick::LZMACompression:
1779  // return octave_value ("lzma");
1780  // case Magick::JPEG2000Compression:
1781  // return octave_value ("jpeg2000");
1782  // case Magick::JBIG1Compression:
1783  // return octave_value ("jbig1");
1784  // case Magick::JBIG2Compression:
1785  // return octave_value ("jbig2");
1786 
1787  default:
1788  return octave_value ("undefined");
1789  }
1790 }
1791 
1792 static octave_value
1793 magick_to_octave_value (const Magick::EndianType& magick)
1794 {
1795  switch (magick)
1796  {
1797  case Magick::LSBEndian:
1798  return octave_value ("little-endian");
1799  case Magick::MSBEndian:
1800  return octave_value ("big-endian");
1801  default:
1802  return octave_value ("undefined");
1803  }
1804 }
1805 
1806 static octave_value
1807 magick_to_octave_value (const Magick::OrientationType& magick)
1808 {
1809  switch (magick)
1810  {
1811  // Values come from the TIFF6 spec
1812  case Magick::TopLeftOrientation:
1813  return octave_value (1);
1814  case Magick::TopRightOrientation:
1815  return octave_value (2);
1816  case Magick::BottomRightOrientation:
1817  return octave_value (3);
1818  case Magick::BottomLeftOrientation:
1819  return octave_value (4);
1820  case Magick::LeftTopOrientation:
1821  return octave_value (5);
1822  case Magick::RightTopOrientation:
1823  return octave_value (6);
1824  case Magick::RightBottomOrientation:
1825  return octave_value (7);
1826  case Magick::LeftBottomOrientation:
1827  return octave_value (8);
1828  default:
1829  return octave_value (1);
1830  }
1831 }
1832 
1833 static octave_value
1834 magick_to_octave_value (const Magick::ResolutionType& magick)
1835 {
1836  switch (magick)
1837  {
1838  case Magick::PixelsPerInchResolution:
1839  return octave_value ("Inch");
1840  case Magick::PixelsPerCentimeterResolution:
1841  return octave_value ("Centimeter");
1842  default:
1843  return octave_value ("undefined");
1844  }
1845 }
1846 
1847 static bool
1848 is_valid_exif (const std::string& val)
1849 {
1850  // Sometimes GM will return the string "unknown" instead of empty
1851  // for an empty value.
1852  return (! val.empty () && val != "unknown");
1853 }
1854 
1855 static void
1856 fill_exif (octave_scalar_map& map, Magick::Image& img,
1857  const std::string& key)
1858 {
1859  const std::string attr = img.attribute ("EXIF:" + key);
1860  if (is_valid_exif (attr))
1861  map.setfield (key, octave_value (attr));
1862  return;
1863 }
1864 
1865 static void
1866 fill_exif_ints (octave_scalar_map& map, Magick::Image& img,
1867  const std::string& key)
1868 {
1869  const std::string attr = img.attribute ("EXIF:" + key);
1870  if (is_valid_exif (attr))
1871  {
1872  // string of the type "float,float,float....."
1873  float number;
1874  ColumnVector values (std::count (attr.begin (), attr.end (), ',') +1);
1875  std::string sub;
1876  std::istringstream sstream (attr);
1877  octave_idx_type n = 0;
1878  while (std::getline (sstream, sub, char (',')))
1879  {
1880  sscanf (sub.c_str (), "%f", &number);
1881  values(n++) = number;
1882  }
1883  map.setfield (key, octave_value (values));
1884  }
1885  return;
1886 }
1887 
1888 static void
1889 fill_exif_floats (octave_scalar_map& map, Magick::Image& img,
1890  const std::string& key)
1891 {
1892  const std::string attr = img.attribute ("EXIF:" + key);
1893  if (is_valid_exif (attr))
1894  {
1895  // string of the type "int/int,int/int,int/int....."
1896  int numerator;
1897  int denominator;
1898  ColumnVector values (std::count (attr.begin (), attr.end (), ',') +1);
1899  std::string sub;
1900  std::istringstream sstream (attr);
1901  octave_idx_type n = 0;
1902  while (std::getline (sstream, sub, ','))
1903  {
1904  sscanf (sub.c_str (), "%i/%i", &numerator, &denominator);
1905  values(n++) = double (numerator) / double (denominator);
1906  }
1907  map.setfield (key, octave_value (values));
1908  }
1909  return;
1910 }
1911 
1912 #endif
1913 
1914 DEFUN (__magick_finfo__, args, ,
1915  doc: /* -*- texinfo -*-
1916 @deftypefn {} {@var{infostruct} =} __magick_finfo__ (@var{fname})
1917 Read image information with GraphicsMagick or ImageMagick.
1918 
1919 This is a private internal function not intended for direct use.
1920 Use @code{imfinfo} instead.
1921 
1922 @seealso{imfinfo, imformats, imread, imwrite}
1923 @end deftypefn */)
1924 {
1925 #if defined (HAVE_MAGICK)
1926 
1927  if (args.length () < 1 || ! args(0).is_string ())
1928  print_usage ();
1929 
1930  maybe_initialize_magick ();
1931 
1932  const std::string filename = args(0).string_value ();
1933 
1934  std::vector<Magick::Image> imvec;
1935  read_file (filename, imvec);
1936 
1937  const octave_idx_type nFrames = imvec.size ();
1938  const std::string format = imvec[0].magick ();
1939 
1940  // Here's how this function works. We need to return a struct array, one
1941  // struct for each image in the file (remember, there are image
1942  // that allow for multiple images in the same file). Now, Matlab seems
1943  // to have format specific code so the fields on the struct are different
1944  // for each format. It only has a small subset that is common to all
1945  // of them, the others are undocumented. Because we try to abstract from
1946  // the formats we always return the same list of fields (note that with
1947  // GM we support more than 88 formats. That's way more than Matlab, and
1948  // I don't want to write specific code for each of them).
1949  //
1950  // So what we do is we create an octave_scalar_map, fill it with the
1951  // information for that image, and then insert it into an octave_map.
1952  // Because in the same file, different images may have values for
1953  // different fields, we can't create a field only if there's a value.
1954  // Bad things happen if we merge octave_scalar_maps with different
1955  // fields from the others (suppose for example a TIFF file with 4 images,
1956  // where only the third image has a colormap.
1957 
1958  static const char *fields[] =
1959  {
1960  // These are fields that must always appear for Matlab.
1961  "Filename",
1962  "FileModDate",
1963  "FileSize",
1964  "Format",
1965  "FormatVersion",
1966  "Width",
1967  "Height",
1968  "BitDepth",
1969  "ColorType",
1970 
1971  // These are format specific or not existent in Matlab. The most
1972  // annoying thing is that Matlab may have different names for the
1973  // same thing in different formats.
1974  "DelayTime",
1975  "DisposalMethod",
1976  "LoopCount",
1977  "ByteOrder",
1978  "Gamma",
1979  "Chromaticities",
1980  "Comment",
1981  "Quality",
1982  "Compression", // same as CompressionType
1983  "Colormap", // same as ColorTable (in PNG)
1984  "Orientation",
1985  "ResolutionUnit",
1986  "XResolution",
1987  "YResolution",
1988  "Software", // sometimes is an Exif tag
1989  "Make", // actually an Exif tag
1990  "Model", // actually an Exif tag
1991  "DateTime", // actually an Exif tag
1992  "ImageDescription", // actually an Exif tag
1993  "Artist", // actually an Exif tag
1994  "Copyright", // actually an Exif tag
1995  "DigitalCamera",
1996  "GPSInfo",
1997  // Notes for the future: GM allows one to get many attributes, and even has
1998  // attribute() to obtain arbitrary ones, that may exist in only some
1999  // cases. The following is a list of some methods and into what possible
2000  // Matlab compatible values they may be converted.
2001  //
2002  // colorSpace() -> PhotometricInterpretation
2003  // backgroundColor() -> BackgroundColor
2004  // interlaceType() -> Interlaced, InterlaceType, and PlanarConfiguration
2005  // label() -> Title
2006  nullptr
2007  };
2008 
2009  // The one we will return at the end
2010  octave_map info (dim_vector (nFrames, 1), string_vector (fields));
2011 
2012  // Some of the fields in the struct are about file information and will be
2013  // the same for all images in the file. So we create a template, fill in
2014  // those values, and make a copy of the template for each image.
2015  octave_scalar_map template_info = (string_vector (fields));
2016 
2017  template_info.setfield ("Format", octave_value (format));
2018  // We can't actually get FormatVersion but even Matlab sometimes can't.
2019  template_info.setfield ("FormatVersion", octave_value (""));
2020 
2021  const sys::file_stat fs (filename);
2022  if (! fs)
2023  error ("imfinfo: error reading '%s': %s", filename.c_str (),
2024  fs.error ().c_str ());
2025 
2026  const sys::localtime mtime (fs.mtime ());
2027  const std::string filetime = mtime.strftime ("%e-%b-%Y %H:%M:%S");
2028  template_info.setfield ("Filename", octave_value (filename));
2029  template_info.setfield ("FileModDate", octave_value (filetime));
2030  template_info.setfield ("FileSize", octave_value (fs.size ()));
2031 
2032  for (octave_idx_type frame = 0; frame < nFrames; frame++)
2033  {
2034  octave_quit ();
2035 
2036  octave_scalar_map info_frame (template_info);
2037  const Magick::Image img = imvec[frame];
2038 
2039  info_frame.setfield ("Width", octave_value (img.columns ()));
2040  info_frame.setfield ("Height", octave_value (img.rows ()));
2041  info_frame.setfield ("BitDepth",
2042  octave_value (get_depth (const_cast<Magick::Image&> (img))));
2043 
2044  // Stuff related to colormap, image class and type
2045  // Because GM is too smart for us... Read the comments in is_indexed()
2046  {
2047  std::string color_type;
2048  Matrix cmap;
2049  if (is_indexed (img))
2050  {
2051  color_type = "indexed";
2052  cmap = read_maps (const_cast<Magick::Image&> (img))(0).matrix_value ();
2053  }
2054  else
2055  {
2056  switch (img.type ())
2057  {
2058  case Magick::BilevelType:
2059  case Magick::GrayscaleType:
2060  case Magick::GrayscaleMatteType:
2061  color_type = "grayscale";
2062  break;
2063 
2064  case Magick::TrueColorType:
2065  case Magick::TrueColorMatteType:
2066  color_type = "truecolor";
2067  break;
2068 
2069  case Magick::PaletteType:
2070  case Magick::PaletteMatteType:
2071  // we should never get here or is_indexed needs to be fixed
2072  color_type = "indexed";
2073  break;
2074 
2075  case Magick::ColorSeparationType:
2076  case Magick::ColorSeparationMatteType:
2077  color_type = "CMYK";
2078  break;
2079 
2080  default:
2081  color_type = "undefined";
2082  }
2083  }
2084  info_frame.setfield ("ColorType", octave_value (color_type));
2085  info_frame.setfield ("Colormap", octave_value (cmap));
2086  }
2087 
2088  {
2089  // Not all images have chroma values. In such cases, they'll
2090  // be all zeros. So rather than send a matrix of zeros, we will
2091  // check for that, and send an empty vector instead.
2092  RowVector chromaticities (8);
2093  double *chroma_fvec = chromaticities.fortran_vec ();
2094  img.chromaWhitePoint (&chroma_fvec[0], &chroma_fvec[1]);
2095  img.chromaRedPrimary (&chroma_fvec[2], &chroma_fvec[3]);
2096  img.chromaGreenPrimary (&chroma_fvec[4], &chroma_fvec[5]);
2097  img.chromaBluePrimary (&chroma_fvec[6], &chroma_fvec[7]);
2098  if (chromaticities.nnz () == 0)
2099  chromaticities = RowVector (0);
2100  info_frame.setfield ("Chromaticities", octave_value (chromaticities));
2101  }
2102 
2103  info_frame.setfield ("Gamma", octave_value (img.gamma ()));
2104  info_frame.setfield ("XResolution", octave_value (img.xResolution ()));
2105  info_frame.setfield ("YResolution", octave_value (img.yResolution ()));
2106  info_frame.setfield ("DelayTime", octave_value (img.animationDelay ()));
2107  info_frame.setfield ("LoopCount",
2108  octave_value (img.animationIterations ()));
2109  info_frame.setfield ("Quality", octave_value (img.quality ()));
2110  info_frame.setfield ("Comment", octave_value (img.comment ()));
2111 
2112  info_frame.setfield ("Compression",
2113  magick_to_octave_value (img.compressType ()));
2114  info_frame.setfield ("Orientation",
2115  magick_to_octave_value (img.orientation ()));
2116  info_frame.setfield ("ResolutionUnit",
2117  magick_to_octave_value (img.resolutionUnits ()));
2118  info_frame.setfield ("ByteOrder",
2119  magick_to_octave_value (img.endian ()));
2120 
2121  // It is not possible to know if there's an Exif field so we just
2122  // check for the Exif Version value. If it does exists, then we
2123  // bother about looking for specific fields.
2124  {
2125  Magick::Image& cimg = const_cast<Magick::Image&> (img);
2126 
2127  // These will be in Exif tags but must appear as fields in the
2128  // base struct array, not as another struct in one of its fields.
2129  // This is likely because they belong to the Baseline TIFF specs
2130  // and may appear out of the Exif tag. So first we check if it
2131  // exists outside the Exif tag.
2132  // See Section 4.6.4, table 4, page 28 of Exif specs version 2.3
2133  // (CIPA DC- 008-Translation- 2010)
2134  static const char *base_exif_str_fields[] =
2135  {
2136  "DateTime",
2137  "ImageDescription",
2138  "Make",
2139  "Model",
2140  "Software",
2141  "Artist",
2142  "Copyright",
2143  nullptr,
2144  };
2145  static const string_vector base_exif_str (base_exif_str_fields);
2146  static const octave_idx_type n_base_exif_str = base_exif_str.numel ();
2147  for (octave_idx_type field = 0; field < n_base_exif_str; field++)
2148  {
2149  info_frame.setfield (base_exif_str[field],
2150  octave_value (cimg.attribute (base_exif_str[field])));
2151  fill_exif (info_frame, cimg, base_exif_str[field]);
2152  }
2153 
2154  octave_scalar_map camera;
2155  octave_scalar_map gps;
2156  if (! cimg.attribute ("EXIF:ExifVersion").empty ())
2157  {
2158  // See Section 4.6.5, table 7 and 8, over pages page 42 to 43
2159  // of Exif specs version 2.3 (CIPA DC- 008-Translation- 2010)
2160 
2161  // Listed on the Exif specs as being of type ASCII.
2162  static const char *exif_str_fields[] =
2163  {
2164  "RelatedSoundFile",
2165  "DateTimeOriginal",
2166  "DateTimeDigitized",
2167  "SubSecTime",
2168  "DateTimeOriginal",
2169  "SubSecTimeOriginal",
2170  "SubSecTimeDigitized",
2171  "ImageUniqueID",
2172  "CameraOwnerName",
2173  "BodySerialNumber",
2174  "LensMake",
2175  "LensModel",
2176  "LensSerialNumber",
2177  "SpectralSensitivity",
2178  // These last two are of type undefined but most likely will
2179  // be strings. Even if they're not GM returns a string anyway.
2180  "UserComment",
2181  "MakerComment",
2182  nullptr
2183  };
2184  static const string_vector exif_str (exif_str_fields);
2185  static const octave_idx_type n_exif_str = exif_str.numel ();
2186  for (octave_idx_type field = 0; field < n_exif_str; field++)
2187  fill_exif (camera, cimg, exif_str[field]);
2188 
2189  // Listed on the Exif specs as being of type SHORT or LONG.
2190  static const char *exif_int_fields[] =
2191  {
2192  "ColorSpace",
2193  "ExifImageWidth", // PixelXDimension (CPixelXDimension in Matlab)
2194  "ExifImageHeight", // PixelYDimension (CPixelYDimension in Matlab)
2195  "PhotographicSensitivity",
2196  "StandardOutputSensitivity",
2197  "RecommendedExposureIndex",
2198  "ISOSpeed",
2199  "ISOSpeedLatitudeyyy",
2200  "ISOSpeedLatitudezzz",
2201  "FocalPlaneResolutionUnit",
2202  "FocalLengthIn35mmFilm",
2203  // Listed as SHORT or LONG but with more than 1 count.
2204  "SubjectArea",
2205  "SubjectLocation",
2206  // While the following are an integer, their value have a meaning
2207  // that must be represented as a string for Matlab compatibility.
2208  // For example, a 3 on ExposureProgram, would return
2209  // "Aperture priority" as defined on the Exif specs.
2210  "ExposureProgram",
2211  "SensitivityType",
2212  "MeteringMode",
2213  "LightSource",
2214  "Flash",
2215  "SensingMethod",
2216  "FileSource",
2217  "CustomRendered",
2218  "ExposureMode",
2219  "WhiteBalance",
2220  "SceneCaptureType",
2221  "GainControl",
2222  "Contrast",
2223  "Saturation",
2224  "Sharpness",
2225  "SubjectDistanceRange",
2226  nullptr
2227  };
2228  static const string_vector exif_int (exif_int_fields);
2229  static const octave_idx_type n_exif_int = exif_int.numel ();
2230  for (octave_idx_type field = 0; field < n_exif_int; field++)
2231  fill_exif_ints (camera, cimg, exif_int[field]);
2232 
2233  // Listed as RATIONAL or SRATIONAL
2234  static const char *exif_float_fields[] =
2235  {
2236  "Gamma",
2237  "CompressedBitsPerPixel",
2238  "ExposureTime",
2239  "FNumber",
2240  "ShutterSpeedValue", // SRATIONAL
2241  "ApertureValue",
2242  "BrightnessValue", // SRATIONAL
2243  "ExposureBiasValue", // SRATIONAL
2244  "MaxApertureValue",
2245  "SubjectDistance",
2246  "FocalLength",
2247  "FlashEnergy",
2248  "FocalPlaneXResolution",
2249  "FocalPlaneYResolution",
2250  "ExposureIndex",
2251  "DigitalZoomRatio",
2252  // Listed as RATIONAL or SRATIONAL with more than 1 count.
2253  "LensSpecification",
2254  nullptr
2255  };
2256  static const string_vector exif_float (exif_float_fields);
2257  static const octave_idx_type n_exif_float = exif_float.numel ();
2258  for (octave_idx_type field = 0; field < n_exif_float; field++)
2259  fill_exif_floats (camera, cimg, exif_float[field]);
2260 
2261  // Inside a Exif field, it is possible that there is also a
2262  // GPS field. This is not the same as ExifVersion but seems
2263  // to be how we have to check for it.
2264  if (cimg.attribute ("EXIF:GPSInfo") != "unknown")
2265  {
2266  // The story here is the same as with Exif.
2267  // See Section 4.6.6, table 15 on page 68 of Exif specs
2268  // version 2.3 (CIPA DC- 008-Translation- 2010)
2269 
2270  static const char *gps_str_fields[] =
2271  {
2272  "GPSLatitudeRef",
2273  "GPSLongitudeRef",
2274  "GPSAltitudeRef",
2275  "GPSSatellites",
2276  "GPSStatus",
2277  "GPSMeasureMode",
2278  "GPSSpeedRef",
2279  "GPSTrackRef",
2280  "GPSImgDirectionRef",
2281  "GPSMapDatum",
2282  "GPSDestLatitudeRef",
2283  "GPSDestLongitudeRef",
2284  "GPSDestBearingRef",
2285  "GPSDestDistanceRef",
2286  "GPSDateStamp",
2287  nullptr
2288  };
2289  static const string_vector gps_str (gps_str_fields);
2290  static const octave_idx_type n_gps_str = gps_str.numel ();
2291  for (octave_idx_type field = 0; field < n_gps_str; field++)
2292  fill_exif (gps, cimg, gps_str[field]);
2293 
2294  static const char *gps_int_fields[] =
2295  {
2296  "GPSDifferential",
2297  nullptr
2298  };
2299  static const string_vector gps_int (gps_int_fields);
2300  static const octave_idx_type n_gps_int = gps_int.numel ();
2301  for (octave_idx_type field = 0; field < n_gps_int; field++)
2302  fill_exif_ints (gps, cimg, gps_int[field]);
2303 
2304  static const char *gps_float_fields[] =
2305  {
2306  "GPSAltitude",
2307  "GPSDOP",
2308  "GPSSpeed",
2309  "GPSTrack",
2310  "GPSImgDirection",
2311  "GPSDestBearing",
2312  "GPSDestDistance",
2313  "GPSHPositioningError",
2314  // Listed as RATIONAL or SRATIONAL with more than 1 count.
2315  "GPSLatitude",
2316  "GPSLongitude",
2317  "GPSTimeStamp",
2318  "GPSDestLatitude",
2319  "GPSDestLongitude",
2320  nullptr
2321  };
2322  static const string_vector gps_float (gps_float_fields);
2323  static const octave_idx_type n_gps_float = gps_float.numel ();
2324  for (octave_idx_type field = 0; field < n_gps_float; field++)
2325  fill_exif_floats (gps, cimg, gps_float[field]);
2326 
2327  }
2328  }
2329  info_frame.setfield ("DigitalCamera", octave_value (camera));
2330  info_frame.setfield ("GPSInfo", octave_value (gps));
2331  }
2332 
2333  info.fast_elem_insert (frame, info_frame);
2334  }
2335 
2336  if (format == "GIF")
2337  {
2338  static std::map<octave_idx_type, std::string> disposal_methods
2339  = init_disposal_methods ();
2340  string_vector methods (nFrames);
2341  for (octave_idx_type frame = 0; frame < nFrames; frame++)
2342  methods[frame] = disposal_methods[imvec[frame].gifDisposeMethod ()];
2343  info.setfield ("DisposalMethod", Cell (methods));
2344  }
2345  else
2346  info.setfield ("DisposalMethod",
2347  Cell (dim_vector (nFrames, 1), octave_value ("")));
2348 
2349  return ovl (info);
2350 
2351 #else
2352 
2353  octave_unused_parameter (args);
2354 
2355  err_disabled_feature ("imfinfo", "Image IO");
2356 
2357 #endif
2358 }
2359 
2360 /*
2361 ## No test needed for internal helper function.
2362 %!assert (1)
2363 */
2364 
2365 DEFUN (__magick_formats__, args, ,
2366  doc: /* -*- texinfo -*-
2367 @deftypefn {} {@var{fmt_struct} =} __magick_imformats__ (@var{formats})
2368 Fill formats info with GraphicsMagick CoderInfo.
2369 
2370 @seealso{imfinfo, imformats, imread, imwrite}
2371 @end deftypefn */)
2372 {
2373  if (args.length () != 1 || ! args(0).isstruct ())
2374  print_usage ();
2375 
2376  octave_map formats = args(0).map_value ();
2377 
2378 #if defined (HAVE_MAGICK)
2379 
2380  maybe_initialize_magick ();
2381 
2382  for (octave_idx_type idx = 0; idx < formats.numel (); idx++)
2383  {
2384  try
2385  {
2386  octave_scalar_map fmt = formats.checkelem (idx);
2387  Magick::CoderInfo coder (fmt.getfield ("coder").string_value ());
2388 
2389  fmt.setfield ("description", octave_value (coder.description ()));
2390  fmt.setfield ("multipage", coder.isMultiFrame () ? true : false);
2391  // default for read and write is a function handle. If we can't
2392  // read or write them, them set it to an empty value
2393  if (! coder.isReadable ())
2394  fmt.setfield ("read", Matrix ());
2395  if (! coder.isWritable ())
2396  fmt.setfield ("write", Matrix ());
2397  formats.fast_elem_insert (idx, fmt);
2398  }
2399  catch (const Magick::Exception&)
2400  {
2401  // Exception here are missing formats. So we remove the format
2402  // from the structure and reduce idx.
2403  formats.delete_elements (idx);
2404  idx--;
2405  }
2406  }
2407 
2408 #else
2409 
2410  formats = octave_map (dim_vector (1, 0), formats.fieldnames ());
2411 
2412 #endif
2413 
2414  return ovl (formats);
2415 }
2416 
2417 /*
2418 ## No test needed for internal helper function.
2419 %!assert (1)
2420 */
2421 
2422 OCTAVE_END_NAMESPACE(octave)
#define COMPRESS_MAGICK_IMAGE_VECTOR(GM_TYPE)
charNDArray max(char d, const charNDArray &m)
Definition: chNDArray.cc:230
T * fortran_vec()
Size of the specified dimension.
Definition: Array-base.cc:1764
int ndims() const
Size of the specified dimension.
Definition: Array.h:671
octave_idx_type nnz() const
Count nonzero elements.
Definition: Array-base.cc:2223
octave_idx_type rows() const
Definition: Array.h:459
void resize(const dim_vector &dv, const T &rfv)
Size of the specified dimension.
Definition: Array-base.cc:1023
const T * data() const
Size of the specified dimension.
Definition: Array.h:663
octave_idx_type columns() const
Definition: Array.h:471
bool isempty() const
Size of the specified dimension.
Definition: Array.h:651
const dim_vector & dims() const
Return a const-reference so that dims ()(i) works efficiently.
Definition: Array.h:503
octave_idx_type numel() const
Number of elements in the array.
Definition: Array.h:414
Definition: Cell.h:43
Definition: dMatrix.h:42
Vector representing the dimensions (size) of an Array.
Definition: dim-vector.h:94
static octave_int< T > max()
Definition: oct-inttypes.h:899
octave_scalar_map checkelem(octave_idx_type n) const
Definition: oct-map.h:378
string_vector fieldnames() const
Definition: oct-map.h:332
void delete_elements(const octave::idx_vector &i)
Definition: oct-map.cc:1230
void setfield(const std::string &key, const Cell &val)
Definition: oct-map.cc:282
octave_idx_type numel() const
Definition: oct-map.h:368
bool fast_elem_insert(octave_idx_type n, const octave_scalar_map &rhs)
Definition: oct-map.cc:413
void setfield(const std::string &key, const octave_value &val)
Definition: oct-map.cc:190
octave_value getfield(const std::string &key) const
Definition: oct-map.cc:183
bool isfloat() const
Definition: ov.h:701
bool is_uint32_type() const
Definition: ov.h:724
boolNDArray bool_array_value(bool warn=false) const
Definition: ov.h:891
uint16NDArray uint16_array_value() const
Definition: ov.h:965
Cell cell_value() const
int int_value(bool req_int=false, bool frc_str_conv=false) const
Definition: ov.h:812
bool is_scalar_type() const
Definition: ov.h:744
octave::range< double > range_value() const
Definition: ov.h:985
bool is_string() const
Definition: ov.h:637
Array< int > xint_vector_value(const char *fmt,...) const
bool is_range() const
Definition: ov.h:646
bool is_single_type() const
Definition: ov.h:698
bool is_uint8_type() const
Definition: ov.h:718
bool is_uint16_type() const
Definition: ov.h:721
double scalar_value(bool frc_str_conv=false) const
Definition: ov.h:847
std::string string_value(bool force=false) const
Definition: ov.h:974
bool is_matrix_type() const
Definition: ov.h:747
NDArray array_value(bool frc_str_conv=false) const
Definition: ov.h:859
uint8NDArray uint8_array_value() const
Definition: ov.h:962
ColumnVector column_vector_value(bool frc_str_conv=false, bool frc_vec_conv=false) const
FloatNDArray float_array_value(bool frc_str_conv=false) const
Definition: ov.h:862
uint32NDArray uint32_array_value() const
Definition: ov.h:968
unsigned int uint_value(bool req_int=false, bool frc_str_conv=false) const
Definition: ov.h:816
bool islogical() const
Definition: ov.h:735
Array< std::string > cellstr_value() const
Definition: ov.h:982
octave_idx_type numel() const
Definition: str-vec.h:100
OCTAVE_BEGIN_NAMESPACE(octave) static octave_value daspk_fcn
void print_usage(void)
Definition: defun-int.h:72
#define DEFUN(name, args_name, nargout_name, doc)
Macro to define a builtin function.
Definition: defun.h:56
void warning(const char *fmt,...)
Definition: error.cc:1063
void warning_with_id(const char *id, const char *fmt,...)
Definition: error.cc:1078
void() error(const char *fmt,...)
Definition: error.cc:988
void err_disabled_feature(const std::string &fcn, const std::string &feature, const std::string &pkg)
Definition: errwarn.cc:53
double round(double x)
Definition: lo-mappers.h:136
std::string get_ASCII_filename(const std::string &orig_file_name, const bool allow_locale)
Definition: lo-sysdep.cc:814
bool file_exists(const std::string &filename, bool is_dir)
Definition: lo-sysdep.cc:341
octave_idx_type n
Definition: mx-inlines.cc:761
std::complex< double > w(std::complex< double > z, double relerr=0)
octave_value_list read_images(std::vector< Magick::Image > &imvec, const Array< octave_idx_type > &frameidx, const octave_idx_type &nargout, const octave_scalar_map &options)
T::size_type numel(const T &str)
Definition: oct-string.cc:74
return octave_value(v1.char_array_value() . concat(v2.char_array_value(), ra_idx),((a1.is_sq_string()||a2.is_sq_string()) ? '\'' :'"'))
octave_value_list ovl(const OV_Args &... args)
Construct an octave_value_list with less typing.
Definition: ovl.h:219
std::size_t format(std::ostream &os, const char *fmt,...)