Coverage Report

Created: 2026-01-09 06:32

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/exiv2/src/rafimage.cpp
Line
Count
Source
1
// SPDX-License-Identifier: GPL-2.0-or-later
2
3
// included header files
4
#include "rafimage.hpp"
5
6
#include "basicio.hpp"
7
#include "config.h"
8
#include "enforce.hpp"
9
#include "error.hpp"
10
#include "futils.hpp"
11
#include "image.hpp"
12
#include "image_int.hpp"
13
#include "jpgimage.hpp"
14
#include "safe_op.hpp"
15
#include "tags.hpp"
16
#include "tiffimage.hpp"
17
18
#include <array>
19
#include <iostream>
20
21
// *****************************************************************************
22
// class member definitions
23
namespace Exiv2 {
24
RafImage::RafImage(BasicIo::UniquePtr io, bool /*create*/) :
25
168
    Image(ImageType::raf, mdExif | mdIptc | mdXmp, std::move(io)) {
26
168
}  // RafImage::RafImage
27
28
0
std::string RafImage::mimeType() const {
29
0
  return "image/x-fuji-raf";
30
0
}
31
32
9
uint32_t RafImage::pixelWidth() const {
33
9
  if (pixelWidth_ != 0)
34
0
    return pixelWidth_;
35
36
9
  auto widthIter = exifData_.findKey(Exiv2::ExifKey("Exif.Fujifilm.RawImageFullWidth"));
37
9
  if (widthIter == exifData_.end() || widthIter->count() == 0)
38
9
    return 0;
39
0
  return widthIter->toUint32();
40
9
}
41
42
9
uint32_t RafImage::pixelHeight() const {
43
9
  if (pixelHeight_ != 0)
44
0
    return pixelHeight_;
45
46
9
  auto heightIter = exifData_.findKey(Exiv2::ExifKey("Exif.Fujifilm.RawImageFullHeight"));
47
9
  if (heightIter == exifData_.end() || heightIter->count() == 0)
48
9
    return 0;
49
0
  return heightIter->toUint32();
50
9
}
51
52
0
void RafImage::setExifData(const ExifData& /*exifData*/) {
53
  // Todo: implement me!
54
0
  throw(Error(ErrorCode::kerInvalidSettingForImage, "Exif metadata", "RAF"));
55
0
}
56
57
0
void RafImage::setIptcData(const IptcData& /*iptcData*/) {
58
  // Todo: implement me!
59
0
  throw(Error(ErrorCode::kerInvalidSettingForImage, "IPTC metadata", "RAF"));
60
0
}
61
62
0
void RafImage::setComment(const std::string&) {
63
  // not supported
64
0
  throw(Error(ErrorCode::kerInvalidSettingForImage, "Image comment", "RAF"));
65
0
}
66
67
0
void RafImage::printStructure(std::ostream& out, PrintStructureOption option, size_t depth) {
68
0
  if (io_->open() != 0) {
69
0
    throw Error(ErrorCode::kerDataSourceOpenFailed, io_->path(), strError());
70
0
  }
71
  // Ensure this is the correct image type
72
0
  if (!isRafType(*io_, true)) {
73
0
    if (io_->error() || io_->eof())
74
0
      throw Error(ErrorCode::kerFailedToReadImageData);
75
0
    throw Error(ErrorCode::kerNotAnImage, "RAF");
76
0
  }
77
78
  // The following is based on https://libopenraw.freedesktop.org/formats/raf/ and
79
  // https://exiftool.org/TagNames/FujiFilm.html#RAFHeader
80
81
0
  const bool bPrint = option == kpsBasic || option == kpsRecursive;
82
0
  if (bPrint) {
83
0
    io_->seek(0, BasicIo::beg);  // rewind
84
0
    size_t address = io_->tell();
85
0
    constexpr auto format = " {:9} | {:9} | ";
86
87
0
    {
88
0
      out << Internal::indent(depth) << "STRUCTURE OF RAF FILE: " << io().path() << '\n';
89
0
      out << Internal::indent(depth) << "   Address |    Length | Payload" << '\n';
90
0
    }
91
92
0
    byte magicdata[17];
93
0
    io_->readOrThrow(magicdata, 16);
94
0
    magicdata[16] = 0;
95
0
    {
96
0
      out << Internal::indent(depth) << stringFormat(format, address, 16)  // 0
97
0
          << "       magic : " << reinterpret_cast<const char*>(magicdata) << '\n';
98
0
    }
99
100
0
    address = io_->tell();
101
0
    byte data1[5];
102
0
    io_->read(data1, 4);
103
0
    data1[4] = 0;
104
0
    {
105
0
      out << Internal::indent(depth) << stringFormat(format, address, 4)  // 16
106
0
          << "       data1 : " << reinterpret_cast<const char*>(&data1) << '\n';
107
0
    }
108
109
0
    address = io_->tell();
110
0
    byte data2[9];
111
0
    io_->read(data2, 8);
112
0
    data2[8] = 0;
113
0
    {
114
0
      out << Internal::indent(depth) << stringFormat(format, address, 8)  // 20
115
0
          << "       data2 : " << reinterpret_cast<const char*>(&data2) << '\n';
116
0
    }
117
118
0
    address = io_->tell();
119
0
    byte camdata[33];
120
0
    io_->read(camdata, 32);
121
0
    camdata[32] = 0;
122
0
    {
123
0
      out << Internal::indent(depth) << stringFormat(format, address, 32)  // 28
124
0
          << "      camera : " << reinterpret_cast<const char*>(&camdata) << '\n';
125
0
    }
126
127
0
    address = io_->tell();
128
0
    byte dir_version[5];
129
0
    io_->read(dir_version, 4);
130
0
    dir_version[4] = 0;
131
0
    {
132
0
      out << Internal::indent(depth) << stringFormat(format, address, 4)  // 60
133
0
          << "     version : " << reinterpret_cast<const char*>(&dir_version) << '\n';
134
0
    }
135
136
0
    address = io_->tell();
137
0
    DataBuf unknown(20);
138
0
    io_->readOrThrow(unknown.data(), unknown.size());
139
0
    {
140
0
      out << Internal::indent(depth) << stringFormat(format, address, 20)
141
0
          << "     unknown : " << Internal::binaryToString(makeSlice(unknown, 0, unknown.size())) << '\n';
142
0
    }
143
144
0
    address = io_->tell();
145
0
    byte jpg_img_offset[4];
146
0
    io_->read(jpg_img_offset, 4);
147
0
    byte jpg_img_length[4];
148
0
    size_t address2 = io_->tell();
149
0
    io_->read(jpg_img_length, 4);
150
151
0
    uint32_t jpg_img_off = Exiv2::getULong(jpg_img_offset, bigEndian);
152
0
    uint32_t jpg_img_len = Exiv2::getULong(jpg_img_length, bigEndian);
153
0
    {
154
0
      out << Internal::indent(depth) << stringFormat(format, address, 4) << " JPEG offset : " << jpg_img_off << '\n';
155
0
      out << Internal::indent(depth) << stringFormat(format, address2, 4) << " JPEG length : " << jpg_img_len << '\n';
156
0
    }
157
158
    // RAFs can carry the payload in one or two parts
159
0
    uint32_t meta_off[2], meta_len[2];
160
0
    uint32_t cfa_off[2], cfa_len[2], comp[2], cfa_size[2], cfa_data[2];
161
0
    for (size_t i = 0; i < 2; i++) {
162
0
      address = io_->tell();
163
0
      byte data[4];
164
0
      io_->readOrThrow(data, 4);
165
0
      meta_off[i] = Exiv2::getULong(data, bigEndian);
166
0
      address2 = io_->tell();
167
0
      io_->readOrThrow(data, 4);
168
0
      meta_len[i] = Exiv2::getULong(data, bigEndian);
169
0
      {
170
0
        out << Internal::indent(depth) << stringFormat(format, address, 4)
171
0
            << stringFormat("meta offset{} : {}\n", i + 1, meta_off[i]);
172
0
        out << Internal::indent(depth) << stringFormat(format, address2, 4)
173
0
            << stringFormat("meta length{} : {}\n", i + 1, meta_len[i]);
174
0
      }
175
176
0
      address = io_->tell();
177
0
      io_->readOrThrow(data, 4);
178
0
      cfa_off[i] = Exiv2::getULong(data, bigEndian);
179
0
      address2 = io_->tell();
180
0
      io_->readOrThrow(data, 4);
181
0
      cfa_len[i] = Exiv2::getULong(data, bigEndian);
182
0
      size_t address3 = io_->tell();
183
0
      io_->readOrThrow(data, 4);
184
0
      comp[i] = Exiv2::getULong(data, bigEndian);
185
0
      size_t address4 = io_->tell();
186
0
      io_->readOrThrow(data, 4);
187
0
      cfa_size[i] = Exiv2::getULong(data, bigEndian);
188
0
      size_t address5 = io_->tell();
189
0
      io_->readOrThrow(data, 4);
190
0
      cfa_data[i] = Exiv2::getULong(data, bigEndian);
191
0
      {
192
0
        out << Internal::indent(depth) << stringFormat(format, address, 4U)
193
0
            << stringFormat(" CFA offset{} : {}\n", i + 1, cfa_off[i]);
194
0
        out << Internal::indent(depth) << stringFormat(format, address2, 4U)
195
0
            << stringFormat(" CFA length{} : {}\n", i + 1, cfa_len[i]);
196
0
        out << Internal::indent(depth) << stringFormat(format, address3, 4U)
197
0
            << stringFormat("compression{} : {}\n", i + 1, comp[i]);
198
0
        out << Internal::indent(depth) << stringFormat(format, address4, 4U)
199
0
            << stringFormat("  CFA chunk{} : {}\n", i + 1, cfa_size[i]);
200
0
        out << Internal::indent(depth) << stringFormat(format, address5, 4U)
201
0
            << stringFormat("    unknown{} : {}\n", i + 1, cfa_data[i]);
202
0
      }
203
0
    }
204
205
0
    io_->seek(jpg_img_off, BasicIo::beg);  // rewind
206
0
    address = io_->tell();
207
0
    DataBuf payload(16);  // header is different from chunks
208
0
    io_->readOrThrow(payload.data(), payload.size());
209
0
    {
210
0
      out << Internal::indent(depth) << stringFormat(format, address, jpg_img_len)
211
0
          << "   JPEG data : " << Internal::binaryToString(makeSlice(payload, 0, payload.size())) << '\n';
212
0
    }
213
214
0
    io_->seek(meta_off[0], BasicIo::beg);  // rewind
215
0
    address = io_->tell();
216
0
    io_->readOrThrow(payload.data(), payload.size());
217
0
    {
218
0
      out << Internal::indent(depth) << stringFormat(format, address, meta_len[0])
219
0
          << "  meta data1 : " << Internal::binaryToString(makeSlice(payload, 0, payload.size())) << '\n';
220
0
    }
221
222
0
    if (meta_off[1] && meta_len[1]) {
223
0
      io_->seek(meta_off[1], BasicIo::beg);  // rewind
224
0
      address = io_->tell();
225
0
      io_->readOrThrow(payload.data(), payload.size());
226
0
      {
227
0
        out << Internal::indent(depth) << stringFormat(format, address, meta_len[1])
228
0
            << "  meta data2 : " << Internal::binaryToString(makeSlice(payload, 0, payload.size())) << '\n';
229
0
      }
230
0
    }
231
232
0
    io_->seek(cfa_off[0], BasicIo::beg);  // rewind
233
0
    address = io_->tell();
234
0
    io_->readOrThrow(payload.data(), payload.size());
235
0
    {
236
0
      out << Internal::indent(depth) << stringFormat(format, address, cfa_len[0])
237
0
          << "   CFA data1 : " << Internal::binaryToString(makeSlice(payload, 0, payload.size())) << '\n';
238
0
    }
239
240
0
    if (cfa_off[1] && cfa_len[1]) {
241
0
      io_->seek(cfa_off[1], BasicIo::beg);  // rewind
242
0
      address = io_->tell();
243
0
      io_->readOrThrow(payload.data(), payload.size());
244
0
      {
245
0
        out << Internal::indent(depth) << stringFormat(format, address, cfa_len[1])  // cfa_off
246
0
            << "   CFA data2 : " << Internal::binaryToString(makeSlice(payload, 0, payload.size())) << '\n';
247
0
      }
248
0
    }
249
0
  }
250
0
}  // RafImage::printStructure
251
252
168
void RafImage::readMetadata() {
253
#ifdef EXIV2_DEBUG_MESSAGES
254
  std::cerr << "Reading RAF file " << io_->path() << "\n";
255
#endif
256
168
  if (io_->open() != 0)
257
0
    throw Error(ErrorCode::kerDataSourceOpenFailed, io_->path(), strError());
258
168
  IoCloser closer(*io_);
259
  // Ensure that this is the correct image type
260
168
  if (!isRafType(*io_, false)) {
261
0
    if (io_->error() || io_->eof())
262
0
      throw Error(ErrorCode::kerFailedToReadImageData);
263
0
    throw Error(ErrorCode::kerNotAnImage, "RAF");
264
0
  }
265
266
168
  clearMetadata();
267
268
168
  if (io_->seek(84, BasicIo::beg) != 0)
269
19
    throw Error(ErrorCode::kerFailedToReadImageData);
270
149
  byte jpg_img_offset[4];
271
149
  if (io_->read(jpg_img_offset, 4) != 4)
272
10
    throw Error(ErrorCode::kerFailedToReadImageData);
273
139
  byte jpg_img_length[4];
274
139
  if (io_->read(jpg_img_length, 4) != 4)
275
18
    throw Error(ErrorCode::kerFailedToReadImageData);
276
121
  uint32_t jpg_img_off_u32 = Exiv2::getULong(jpg_img_offset, bigEndian);
277
121
  uint32_t jpg_img_len_u32 = Exiv2::getULong(jpg_img_length, bigEndian);
278
279
121
  Internal::enforce(Safe::add(jpg_img_off_u32, jpg_img_len_u32) <= io_->size(), ErrorCode::kerCorruptedMetadata);
280
281
121
  auto jpg_img_off = static_cast<long>(jpg_img_off_u32);
282
121
  auto jpg_img_len = static_cast<long>(jpg_img_len_u32);
283
284
121
  Internal::enforce(jpg_img_len >= 12, ErrorCode::kerCorruptedMetadata);
285
286
121
  DataBuf jpg_buf(jpg_img_len);
287
121
  if (io_->seek(jpg_img_off, BasicIo::beg) != 0)
288
0
    throw Error(ErrorCode::kerFailedToReadImageData);
289
290
121
  if (!jpg_buf.empty()) {
291
89
    io_->read(jpg_buf.data(), jpg_buf.size());
292
89
    if (io_->error() || io_->eof())
293
0
      throw Error(ErrorCode::kerFailedToReadImageData);
294
89
  }
295
296
  // Retrieve metadata from embedded JPEG preview image.
297
121
  try {
298
121
    auto jpg_io = std::make_unique<Exiv2::MemIo>(jpg_buf.data(), jpg_buf.size());
299
121
    auto jpg_img = JpegImage(std::move(jpg_io), false);
300
121
    jpg_img.readMetadata();
301
121
    setByteOrder(jpg_img.byteOrder());
302
121
    xmpData_ = jpg_img.xmpData();
303
121
    exifData_ = jpg_img.exifData();
304
121
    iptcData_ = jpg_img.iptcData();
305
121
    comment_ = jpg_img.comment();
306
121
  } catch (const Exiv2::Error&) {
307
76
  }
308
309
121
  exifData_["Exif.Image2.JPEGInterchangeFormat"] = getULong(jpg_img_offset, bigEndian);
310
89
  exifData_["Exif.Image2.JPEGInterchangeFormatLength"] = getULong(jpg_img_length, bigEndian);
311
312
  // Todo: parse the proprietary metadata structure
313
  //       at offset 92 for pixelWidth_ & pixelHeight_
314
  // See https://libopenraw.freedesktop.org/formats/raf/
315
  // and https://exiftool.org/TagNames/FujiFilm.html#RAF
316
317
  // parse the tiff
318
89
  std::array<byte, 4> readBuff;
319
89
  if (io_->seek(100, BasicIo::beg) != 0)
320
25
    throw Error(ErrorCode::kerFailedToReadImageData);
321
64
  if (io_->read(readBuff.data(), 4) != 4)
322
12
    throw Error(ErrorCode::kerFailedToReadImageData);
323
52
  uint32_t tiffOffset = Exiv2::getULong(readBuff.data(), bigEndian);
324
325
52
  if (io_->read(readBuff.data(), 4) != 4)
326
5
    throw Error(ErrorCode::kerFailedToReadImageData);
327
47
  uint32_t tiffLength = Exiv2::getULong(readBuff.data(), bigEndian);
328
329
  // sanity check.  Does tiff lie inside the file?
330
47
  Internal::enforce(Safe::add(tiffOffset, tiffLength) <= io_->size(), ErrorCode::kerCorruptedMetadata);
331
332
47
  if (io_->seek(tiffOffset, BasicIo::beg) != 0)
333
0
    throw Error(ErrorCode::kerFailedToReadImageData);
334
335
  // Check if this really is a tiff and then call the tiff parser.
336
  // Check is needed because some older models just embed a raw bitstream.
337
  // For those files we skip the parsing step.
338
47
  if (io_->read(readBuff.data(), 4) != 4) {
339
3
    throw Error(ErrorCode::kerFailedToReadImageData);
340
3
  }
341
44
  io_->seek(-4, BasicIo::cur);
342
44
  const std::array<byte, 4> Id1{0x49, 0x49, 0x2A, 0x00};
343
44
  const std::array<byte, 4> Id2{0x4D, 0x4D, 0x00, 0x2A};
344
44
  if (readBuff == Id1 || readBuff == Id2) {
345
2
    DataBuf tiff(tiffLength);
346
2
    io_->read(tiff.data(), tiff.size());
347
348
2
    if (!io_->error() && !io_->eof()) {
349
2
      TiffParser::decode(exifData_, iptcData_, xmpData_, tiff.c_data(), tiff.size());
350
2
    }
351
2
  }
352
44
}
353
354
10
void RafImage::writeMetadata() {
355
  //! Todo: implement me!
356
10
  throw(Error(ErrorCode::kerWritingImageFormatUnsupported, "RAF"));
357
10
}  // RafImage::writeMetadata
358
359
// *************************************************************************
360
// free functions
361
168
Image::UniquePtr newRafInstance(BasicIo::UniquePtr io, bool create) {
362
168
  auto image = std::make_unique<RafImage>(std::move(io), create);
363
168
  if (!image->good()) {
364
0
    return nullptr;
365
0
  }
366
168
  return image;
367
168
}
368
369
15.4k
bool isRafType(BasicIo& iIo, bool advance) {
370
15.4k
  const int32_t len = 8;
371
15.4k
  constexpr std::array<byte, len> RafId{'F', 'U', 'J', 'I', 'F', 'I', 'L', 'M'};
372
15.4k
  std::array<byte, len> buf;
373
15.4k
  iIo.read(buf.data(), len);
374
15.4k
  if (iIo.error() || iIo.eof()) {
375
0
    return false;
376
0
  }
377
15.4k
  bool rc = buf == RafId;
378
15.4k
  if (!advance || !rc) {
379
15.4k
    iIo.seek(-len, BasicIo::cur);
380
15.4k
  }
381
15.4k
  return rc;
382
15.4k
}
383
384
}  // namespace Exiv2