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