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