1 // Copyright 2008-present Contributors to the OpenImageIO project.
2 // SPDX-License-Identifier: BSD-3-Clause
3 // https://github.com/OpenImageIO/oiio/blob/master/LICENSE.md
4 
5 #include <OpenImageIO/filesystem.h>
6 #include <OpenImageIO/imageio.h>
7 #include <OpenImageIO/tiffutils.h>
8 
9 #include <libheif/heif_cxx.h>
10 
11 
12 // This plugin utilises libheif:
13 //   https://github.com/strukturag/libheif
14 //
15 // General information about HEIF/HEIC/AVIF:
16 //
17 // Sources of sample images:
18 //     https://github.com/nokiatech/heif/tree/gh-pages/content
19 
20 
21 OIIO_PLUGIN_NAMESPACE_BEGIN
22 
23 class HeifInput final : public ImageInput {
24 public:
HeifInput()25     HeifInput() {}
~HeifInput()26     virtual ~HeifInput() { close(); }
format_name(void) const27     virtual const char* format_name(void) const override { return "heif"; }
supports(string_view feature) const28     virtual int supports(string_view feature) const override
29     {
30         return feature == "exif";
31     }
32 #if LIBHEIF_HAVE_VERSION(1, 4, 0)
33     virtual bool valid_file(const std::string& filename) const override;
34 #endif
35     virtual bool open(const std::string& name, ImageSpec& newspec) override;
36     virtual bool open(const std::string& name, ImageSpec& newspec,
37                       const ImageSpec& config) override;
38     virtual bool close() override;
39     virtual bool seek_subimage(int subimage, int miplevel) override;
40     virtual bool read_native_scanline(int subimage, int miplevel, int y, int z,
41                                       void* data) override;
42 
43 private:
44     std::string m_filename;
45     int m_subimage      = -1;
46     int m_num_subimages = 0;
47     int m_has_alpha     = false;
48     std::unique_ptr<heif::Context> m_ctx;
49     heif_item_id m_primary_id;             // id of primary image
50     std::vector<heif_item_id> m_item_ids;  // ids of all other images
51     heif::ImageHandle m_ihandle;
52     heif::Image m_himage;
53 };
54 
55 
56 
57 // Export version number and create function symbols
58 OIIO_PLUGIN_EXPORTS_BEGIN
59 
60 OIIO_EXPORT int heif_imageio_version = OIIO_PLUGIN_VERSION;
61 
62 OIIO_EXPORT const char*
heif_imageio_library_version()63 heif_imageio_library_version()
64 {
65     return "libheif " LIBHEIF_VERSION;
66 }
67 
68 OIIO_EXPORT ImageInput*
heif_input_imageio_create()69 heif_input_imageio_create()
70 {
71     return new HeifInput;
72 }
73 
74 OIIO_EXPORT const char* heif_input_extensions[] = { "heic", "heif", "heics",
75 #if LIBHEIF_HAVE_VERSION(1, 7, 0)
76                                                     "avif",
77 #endif
78                                                     nullptr };
79 
80 OIIO_PLUGIN_EXPORTS_END
81 
82 
83 #if LIBHEIF_HAVE_VERSION(1, 4, 0)
84 bool
valid_file(const std::string & filename) const85 HeifInput::valid_file(const std::string& filename) const
86 {
87     uint8_t magic[12];
88     if (Filesystem::read_bytes(filename, magic, sizeof(magic)) != sizeof(magic))
89         return false;
90     heif_filetype_result filetype_check = heif_check_filetype(magic,
91                                                               sizeof(magic));
92     return filetype_check != heif_filetype_no
93            && filetype_check != heif_filetype_yes_unsupported;
94 }
95 #endif
96 
97 
98 
99 bool
open(const std::string & name,ImageSpec & newspec)100 HeifInput::open(const std::string& name, ImageSpec& newspec)
101 {
102     // If user doesn't want to provide any config, just use an empty spec.
103     ImageSpec config;
104     return open(name, newspec, config);
105 }
106 
107 
108 
109 bool
open(const std::string & name,ImageSpec & newspec,const ImageSpec &)110 HeifInput::open(const std::string& name, ImageSpec& newspec,
111                 const ImageSpec& /*config*/)
112 {
113     m_filename = name;
114     m_subimage = -1;
115 
116     m_ctx.reset(new heif::Context);
117     m_himage  = heif::Image();
118     m_ihandle = heif::ImageHandle();
119 
120     try {
121         m_ctx->read_from_file(name);
122         // FIXME: should someday be read_from_reader to give full flexibility
123 
124         m_item_ids   = m_ctx->get_list_of_top_level_image_IDs();
125         m_primary_id = m_ctx->get_primary_image_ID();
126         for (size_t i = 0; i < m_item_ids.size(); ++i)
127             if (m_item_ids[i] == m_primary_id) {
128                 m_item_ids.erase(m_item_ids.begin() + i);
129                 break;
130             }
131         // std::cout << " primary id: " << m_primary_id << "\n";
132         // std::cout << " item ids: " << Strutil::join(m_item_ids, ", ") << "\n";
133         m_num_subimages = 1 + int(m_item_ids.size());
134 
135     } catch (const heif::Error& err) {
136         std::string e = err.get_message();
137         errorf("%s", e.empty() ? "unknown exception" : e.c_str());
138         return false;
139     } catch (const std::exception& err) {
140         std::string e = err.what();
141         errorf("%s", e.empty() ? "unknown exception" : e.c_str());
142         return false;
143     }
144 
145     bool ok = seek_subimage(0, 0);
146     newspec = spec();
147     return ok;
148 }
149 
150 
151 
152 bool
close()153 HeifInput::close()
154 {
155     m_himage  = heif::Image();
156     m_ihandle = heif::ImageHandle();
157     m_ctx.reset();
158     m_subimage      = -1;
159     m_num_subimages = 0;
160     return true;
161 }
162 
163 
164 
165 bool
seek_subimage(int subimage,int miplevel)166 HeifInput::seek_subimage(int subimage, int miplevel)
167 {
168     if (miplevel != 0)
169         return false;
170 
171     if (subimage == m_subimage) {
172         return true;  // already there
173     }
174 
175     if (subimage >= m_num_subimages) {
176         return false;
177     }
178 
179     try {
180         auto id     = (subimage == 0) ? m_primary_id : m_item_ids[subimage - 1];
181         m_ihandle   = m_ctx->get_image_handle(id);
182         m_has_alpha = m_ihandle.has_alpha_channel();
183         auto chroma = m_has_alpha ? heif_chroma_interleaved_RGBA
184                                   : heif_chroma_interleaved_RGB;
185         m_himage    = m_ihandle.decode_image(heif_colorspace_RGB, chroma);
186 
187     } catch (const heif::Error& err) {
188         std::string e = err.get_message();
189         errorf("%s", e.empty() ? "unknown exception" : e.c_str());
190         return false;
191     } catch (const std::exception& err) {
192         std::string e = err.what();
193         errorf("%s", e.empty() ? "unknown exception" : e.c_str());
194         return false;
195     }
196 
197     int bits = m_himage.get_bits_per_pixel(heif_channel_interleaved);
198     m_spec = ImageSpec(m_ihandle.get_width(), m_ihandle.get_height(), bits / 8,
199                        TypeUInt8);
200 
201     m_spec.attribute("oiio:ColorSpace", "sRGB");
202 
203     auto meta_ids = m_ihandle.get_list_of_metadata_block_IDs();
204     // std::cout << "nmeta? " << meta_ids.size() << "\n";
205     for (auto m : meta_ids) {
206         std::vector<uint8_t> metacontents;
207         try {
208             metacontents = m_ihandle.get_metadata(m);
209         } catch (const heif::Error& err) {
210             if (err.get_code() == heif_error_Usage_error
211                 && err.get_subcode() == heif_suberror_Null_pointer_argument) {
212                 // a bug in heif_cxx.h means a 0 byte metadata causes a null
213                 // ptr error code, which we ignore
214                 continue;
215             }
216         }
217         if (Strutil::iequals(m_ihandle.get_metadata_type(m), "Exif")
218             && metacontents.size() >= 10) {
219             cspan<uint8_t> s(&metacontents[10], metacontents.size() - 10);
220             decode_exif(s, m_spec);
221         } else if (0  // For now, skip this, I haven't seen anything useful
222                    && Strutil::iequals(m_ihandle.get_metadata_type(m), "mime")
223                    && Strutil::iequals(m_ihandle.get_metadata_content_type(m),
224                                        "application/rdf+xml")) {
225             decode_xmp(metacontents, m_spec);
226         } else {
227 #ifdef DEBUG
228             std::cout << "Don't know how to decode meta " << m
229                       << " type=" << m_ihandle.get_metadata_type(m)
230                       << " contenttype='"
231                       << m_ihandle.get_metadata_content_type(m) << "'\n";
232             std::cout << "---\n"
233                       << string_view((const char*)&metacontents[0],
234                                      metacontents.size())
235                       << "\n---\n";
236 #endif
237         }
238     }
239 
240     // Erase the orientation metadata becaue libheif appears to be doing
241     // the rotation-to-canonical-direction for us.
242     m_spec.erase_attribute("Orientation");
243 
244     m_subimage = subimage;
245     return true;
246 }
247 
248 
249 
250 bool
read_native_scanline(int subimage,int miplevel,int y,int,void * data)251 HeifInput::read_native_scanline(int subimage, int miplevel, int y, int /*z*/,
252                                 void* data)
253 {
254     lock_guard lock(m_mutex);
255     if (!seek_subimage(subimage, miplevel))
256         return false;
257     if (y < 0 || y >= m_spec.height)  // out of range scanline
258         return false;
259 
260     int ystride          = 0;
261     const uint8_t* hdata = m_himage.get_plane(heif_channel_interleaved,
262                                               &ystride);
263     if (!hdata) {
264         errorf("Unknown read error");
265         return false;
266     }
267     hdata += (y - m_spec.y) * ystride;
268     memcpy(data, hdata, m_spec.width * m_spec.pixel_bytes());
269     return true;
270 }
271 
272 
273 OIIO_PLUGIN_NAMESPACE_END
274