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