GNU Octave 7.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-2022 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.
48using Magick::Quantum;
49#endif
50
51OCTAVE_NAMESPACE_BEGIN
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)
84static bool
85is_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).
130static octave_idx_type
131get_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).
153static 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
179class
181{
182public:
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;
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
242private:
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
267read_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 }
282 maps(0) = cmap;
283 maps(1) = amap;
284 return maps;
285}
286
287template <typename T>
289read_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.
384template <typename T>
386read_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.
764void static
765read_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
787static 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
836DEFUN (__magick_read__, args, nargout,
837 doc: /* -*- texinfo -*-
838@deftypefn {} {[@var{img}, @var{map}, @var{alpha}] =} __magick_read__ (@var{fname}, @var{options})
839Read image with GraphicsMagick or ImageMagick.
840
841This is a private internal function not intended for direct use.
842Use @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
962template <typename T>
963static uint32NDArray
964img_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
982template <typename T>
983static 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
992static 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
1023template <typename T>
1024static void
1025encode_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
1094static void
1095encode_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
1141template <typename T>
1142static void
1143encode_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.
1422static 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}
1450static 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
1464void static
1465write_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
1489DEFUN (__magick_write__, args, ,
1490 doc: /* -*- texinfo -*-
1491@deftypefn {} {} __magick_write__ (@var{fname}, @var{fmt}, @var{img}, @var{map}, @var{options})
1492Write image with GraphicsMagick or ImageMagick.
1493
1494This is a private internal function not intended for direct use.
1495Use @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.
1678DEFUN (__magick_ping__, args, ,
1679 doc: /* -*- texinfo -*-
1680@deftypefn {} {} __magick_ping__ (@var{fname}, @var{idx})
1681Ping image information with GraphicsMagick or ImageMagick.
1682
1683This 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
1745static octave_value
1746magick_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
1789static octave_value
1790magick_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
1803static octave_value
1804magick_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
1830static octave_value
1831magick_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
1844static bool
1845is_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
1852static void
1853fill_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
1862static void
1863fill_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
1885static void
1886fill_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
1911DEFUN (__magick_finfo__, args, ,
1912 doc: /* -*- texinfo -*-
1913@deftypefn {} {} __magick_finfo__ (@var{fname})
1914Read image information with GraphicsMagick or ImageMagick.
1915
1916This is a private internal function not intended for direct use.
1917Use @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;
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
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
2362DEFUN (__magick_formats__, args, ,
2363 doc: /* -*- texinfo -*-
2364@deftypefn {} {} __magick_imformats__ (@var{formats})
2365Fill 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
2419OCTAVE_NAMESPACE_END
#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 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 std::map< std::string, octave_idx_type > init_reverse_disposal_methods()
static OCTAVE_NAMESPACE_BEGIN bool is_indexed(const Magick::Image &img)
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 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 numel(void) const
Number of elements in the array.
Definition: Array.h:411
const dim_vector & dims(void) const
Return a const-reference so that dims ()(i) works efficiently.
Definition: Array.h:487
OCTARRAY_API octave_idx_type nnz(void) const
Count nonzero elements.
Definition: Array.cc:2203
octave_idx_type rows(void) const
Definition: Array.h:449
bool isempty(void) const
Size of the specified dimension.
Definition: Array.h:607
OCTARRAY_API void resize(const dim_vector &dv, const T &rfv)
Size of the specified dimension.
Definition: Array.cc:1010
octave_idx_type columns(void) const
Definition: Array.h:458
const T * data(void) const
Size of the specified dimension.
Definition: Array.h:616
OCTARRAY_API T * fortran_vec(void)
Size of the specified dimension.
Definition: Array.cc:1744
int ndims(void) const
Size of the specified dimension.
Definition: Array.h:627
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:891
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:1229
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: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
OCTINTERP_API Array< int > xint_vector_value(const char *fmt,...) const
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
Array< std::string > cellstr_value(void) const
Definition: ov.h:1027
bool is_matrix_type(void) const
Definition: ov.h:792
octave::range< double > range_value(void) const
Definition: ov.h:1030
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
bool is_uint8_type(void) const
Definition: ov.h:763
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
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:1055
void warning_with_id(const char *id, const char *fmt,...)
Definition: error.cc:1070
void error(const char *fmt,...)
Definition: error.cc:980
void err_disabled_feature(const std::string &fcn, const std::string &feature, const std::string &pkg)
Definition: errwarn.cc:53
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
std::complex< double > w(std::complex< double > z, double relerr=0)
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:581
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:1471