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