Compare commits

...

4 Commits

Author SHA1 Message Date
33251eaca7
Load MPF images as pages 2023-05-28 08:12:37 +02:00
63311644da
Move MPF constants and table to tiff-tables.db 2023-05-28 08:12:36 +02:00
8668e85623
Make MPF parsing a bit safer 2023-05-28 08:12:27 +02:00
902eaf5a01
Make TIFF parsing a bit safer
At least on 64-bit systems, 32-bit may still have holes.
2023-05-28 08:12:22 +02:00
4 changed files with 201 additions and 83 deletions

135
fiv-io.c
View File

@ -1143,11 +1143,94 @@ fail:
return surface;
}
// --- Multi-Picture Format ----------------------------------------------------
static uint32_t
parse_mpf_mpentry(const uint8_t *p, const struct tiffer *T)
{
uint32_t attrs = T->un->u32(p);
uint32_t offset = T->un->u32(p + 8);
enum {
TypeBaselineMPPrimaryImage = 0x030000,
TypeLargeThumbnailVGA = 0x010001,
TypeLargeThumbnailFullHD = 0x010002,
TypeMultiFrameImagePanorama = 0x020001,
TypeMultiFrameImageDisparity = 0x020002,
TypeMultiFrameImageMultiAngle = 0x020003,
TypeUndefined = 0x000000,
};
switch (attrs & 0xFFFFFF) {
case TypeLargeThumbnailVGA:
case TypeLargeThumbnailFullHD:
// Wasted cycles.
case TypeUndefined:
// Apple uses this for HDR and depth maps (same and lower resolution).
// TODO(p): It would be nice to be able to view them.
return 0;
}
// Don't report non-JPEGs, even though they're unlikely.
if (((attrs >> 24) & 0x7) != 0)
return 0;
return offset;
}
static uint32_t *
parse_mpf_index_entries(const struct tiffer *T, struct tiffer_entry *entry)
{
uint32_t count = entry->remaining_count / 16;
uint32_t *offsets = g_malloc0_n(sizeof *offsets, count + 1), *out = offsets;
for (uint32_t i = 0; i < count; i++) {
// 5.2.3.3.3. Individual Image Data Offset
uint32_t offset = parse_mpf_mpentry(entry->p + i * 16, T);
if (offset)
*out++ = offset;
}
return offsets;
}
static uint32_t *
parse_mpf_index_ifd(struct tiffer *T)
{
struct tiffer_entry entry = {};
while (tiffer_next_entry(T, &entry)) {
// 5.2.3.3. MP Entry
if (entry.tag == MPF_MPEntry && entry.type == UNDEFINED &&
!(entry.remaining_count % 16)) {
return parse_mpf_index_entries(T, &entry);
}
}
return NULL;
}
static bool
parse_mpf(
GPtrArray *individuals, const uint8_t *mpf, size_t len, const uint8_t *end)
{
struct tiffer T;
if (!tiffer_init(&T, mpf, len) || !tiffer_next_ifd(&T))
return false;
// First image: IFD0 is Index IFD, any IFD1 is Attribute IFD.
// Other images: IFD0 is Attribute IFD, there is no Index IFD.
uint32_t *offsets = parse_mpf_index_ifd(&T);
if (offsets) {
for (const uint32_t *o = offsets; *o; o++)
if (*o <= end - mpf)
g_ptr_array_add(individuals, (gpointer) mpf + *o);
free(offsets);
}
return true;
}
// --- JPEG --------------------------------------------------------------------
struct jpeg_metadata {
GByteArray *exif; ///< Exif buffer or NULL
GByteArray *icc; ///< ICC profile buffer or NULL
GPtrArray *mpf; ///< Multi-Picture Format or NULL
int width; ///< Image width
int height; ///< Image height
};
@ -1234,6 +1317,14 @@ parse_jpeg_metadata(const char *data, size_t len, struct jpeg_metadata *meta)
icc_done = payload[-1] == icc_sequence;
}
// CIPA DC-007-2021 (Multi-Picture Format) 5.2
// https://www.cipa.jp/e/std/std-sec.html
if (meta->mpf && marker == APP2 && p - payload >= 8 &&
!memcmp(payload, "MPF\0", 4) && !meta->mpf->len) {
payload += 4;
parse_mpf(meta->mpf, payload, p - payload, end);
}
// TODO(p): Extract the main XMP segment.
}
@ -1241,15 +1332,38 @@ parse_jpeg_metadata(const char *data, size_t len, struct jpeg_metadata *meta)
g_byte_array_set_size(meta->icc, 0);
}
static cairo_surface_t *open_libjpeg_turbo(
const char *data, gsize len, const FivIoOpenContext *ctx, GError **error);
static void
load_jpeg_finalize(cairo_surface_t *surface, bool cmyk,
FivIoProfile destination, const char *data, size_t len)
const FivIoOpenContext *ctx, const char *data, size_t len)
{
struct jpeg_metadata meta = {
.exif = g_byte_array_new(), .icc = g_byte_array_new()};
.exif = g_byte_array_new(),
.icc = g_byte_array_new(),
.mpf = g_ptr_array_new(),
};
parse_jpeg_metadata(data, len, &meta);
if (!ctx->first_frame_only) {
// XXX: This is ugly, as it relies on just the first individual image
// having any follow-up entries (as it should be).
cairo_surface_t *surface_tail = surface;
for (guint i = 0; i < meta.mpf->len; i++) {
const char *jpeg = meta.mpf->pdata[i];
GError *error = NULL;
if (!try_append_page(
open_libjpeg_turbo(jpeg, len - (jpeg - data), ctx, &error),
&surface, &surface_tail)) {
add_warning(ctx, "MPF image %d: %s", i + 2, error->message);
g_error_free(error);
}
}
}
g_ptr_array_free(meta.mpf, TRUE);
if (meta.exif->len)
cairo_surface_set_user_data(surface, &fiv_io_key_exif,
g_byte_array_free_to_bytes(meta.exif),
@ -1271,9 +1385,9 @@ load_jpeg_finalize(cairo_surface_t *surface, bool cmyk,
g_bytes_get_data(icc_profile, NULL), g_bytes_get_size(icc_profile));
if (cmyk)
fiv_io_profile_cmyk(surface, source, destination);
fiv_io_profile_cmyk(surface, source, ctx->screen_profile);
else
fiv_io_profile_xrgb32(surface, source, destination);
fiv_io_profile_xrgb32(surface, source, ctx->screen_profile);
if (source)
fiv_io_profile_free(source);
@ -1358,7 +1472,7 @@ open_libjpeg_turbo(
}
}
load_jpeg_finalize(surface, use_cmyk, ctx->screen_profile, data, len);
load_jpeg_finalize(surface, use_cmyk, ctx, data, len);
tjDestroy(dec);
return surface;
}
@ -1445,7 +1559,7 @@ open_libjpeg_enhanced(
surface_data, cinfo.output_width * cinfo.output_height);
(void) jpegqs_finish_decompress(&cinfo);
load_jpeg_finalize(surface, use_cmyk, ctx->screen_profile, data, len);
load_jpeg_finalize(surface, use_cmyk, ctx, data, len);
jpeg_destroy_decompress(&cinfo);
return surface;
}
@ -1844,11 +1958,10 @@ tiff_ep_find_jpeg_evaluate(const struct tiffer *T, struct tiff_ep_jpeg *out)
}
int64_t ipointer = 0, ilength = 0;
if (!tiffer_find_integer(T, tag_pointer, &ipointer) ||
!tiffer_find_integer(T, tag_length, &ilength) ||
ipointer <= 0 || ilength <= 0 ||
(uint64_t) ilength > SIZE_MAX ||
ipointer + ilength > (T->end - T->begin))
if (!tiffer_find_integer(T, tag_pointer, &ipointer) || ipointer <= 0 ||
!tiffer_find_integer(T, tag_length, &ilength) || ilength <= 0 ||
ipointer > T->end - T->begin ||
T->end - T->begin - ipointer < ilength)
return;
// Note that to get the largest JPEG,

View File

@ -35,6 +35,9 @@
#
# Digital Negative (DNG) Specification 1.5.0.0 (2019)
# https://www.adobe.com/content/dam/acom/en/products/photoshop/pdfs/dng_spec_1.5.0.0.pdf
#
# CIPA DC-007-2021 (Multi-Picture Format)
# https://www.cipa.jp/e/std/std-sec.html
# TIFF 6.0
= TIFF
@ -444,3 +447,25 @@
# Exif 2.3 4.6.7 (Notice it starts at 1, and collides with GPS.)
= Exif Interoperability
1, InteroperabilityIndex
# CIPA DC-007-2021 5.2.3., 5.2.4. (But derive "field names" from "tag names".)
= MPF
45056, MP Format Version Number # MPFVersion
45057, Number of Images # NumberOfImages
45058, MP Entry # MPEntry
45059, Individual Image Unique ID List # ImageUIDList
45060, Total Number of Captured Frames # TotalFrames
45313, MP Individual Image Number # MPIndividualNum
45569, Panorama Scanning Orientation # PanOrientation
45570, Panorama Horizontal Overlap # PanOverlap_H
45571, Panorama Vertical Overlap # PanOverlap_V
45572, Base Viewpoint Number # BaseViewpointNum
45573, Convergence Angle # ConvergenceAngle
45574, Baseline Length # BaselineLength
45575, Divergence Angle # VerticalDivergence
45576, Horizontal Axis Distance # AxisDistance_X
45577, Vertical Axis Distance # AxisDistance_Y
45578, Collimation Axis Distance # AxisDistance_Z
45579, Yaw Angle # YawAngle
45580, Pitch Angle # PitchAngle
45581, Roll Angle # RollAngle

View File

@ -15,9 +15,10 @@
// CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
//
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <string.h>
#include <stdbool.h>
// --- Utilities ---------------------------------------------------------------
@ -85,7 +86,7 @@ struct tiffer {
static bool
tiffer_u32(struct tiffer *self, uint32_t *u)
{
if (self->p < self->begin || self->p + 4 > self->end)
if (self->end - self->p < 4)
return false;
*u = self->un->u32(self->p);
@ -96,7 +97,7 @@ tiffer_u32(struct tiffer *self, uint32_t *u)
static bool
tiffer_u16(struct tiffer *self, uint16_t *u)
{
if (self->p < self->begin || self->p + 2 > self->end)
if (self->end - self->p < 2)
return false;
*u = self->un->u16(self->p);
@ -160,6 +161,9 @@ static bool
tiffer_subifd(
const struct tiffer *self, uint32_t offset, struct tiffer *subreader)
{
if (self->end - self->begin < offset)
return false;
*subreader = *self;
subreader->p = subreader->begin + offset;
return tiffer_u16(subreader, &subreader->remaining_fields);
@ -323,14 +327,15 @@ tiffer_next_entry(struct tiffer *self, struct tiffer_entry *entry)
if (values_size <= sizeof offset) {
entry->p = self->p;
self->p += sizeof offset;
} else if (tiffer_u32(self, &offset)) {
} else if (tiffer_u32(self, &offset) && self->end - self->begin >= offset) {
entry->p = self->begin + offset;
} else {
return false;
}
// All entries are pre-checked not to overflow.
if (entry->p + values_size > self->end)
if (values_size > PTRDIFF_MAX ||
self->end - entry->p < (ptrdiff_t) values_size)
return false;
// Setting it at the end may provide an indication while debugging.

View File

@ -92,8 +92,8 @@ static jv parse_jpeg(jv o, const uint8_t *p, size_t len);
static jv parse_exif_ifd(struct tiffer *T, const struct tiff_entry *info);
static bool
parse_exif_subifds_entry(struct tiffer *T, const struct tiffer_entry *entry,
struct tiffer *subT)
parse_exif_subifds_entry(const struct tiffer *T,
const struct tiffer_entry *entry, struct tiffer *subT)
{
int64_t offset = 0;
return tiffer_integer(T, entry, &offset) &&
@ -101,7 +101,7 @@ parse_exif_subifds_entry(struct tiffer *T, const struct tiffer_entry *entry,
}
static jv
parse_exif_subifds(struct tiffer *T, struct tiffer_entry *entry,
parse_exif_subifds(const struct tiffer *T, struct tiffer_entry *entry,
struct tiff_entry *info)
{
struct tiffer subT = {};
@ -173,7 +173,7 @@ parse_exif_extract_sole_array_element(jv a)
}
static jv
parse_exif_entry(jv o, struct tiffer *T, struct tiffer_entry *entry,
parse_exif_entry(jv o, const struct tiffer *T, struct tiffer_entry *entry,
const struct tiff_entry *info)
{
static struct tiff_entry empty[] = {{}};
@ -631,55 +631,8 @@ parse_icc(jv o, const uint8_t *profile, size_t profile_len)
// --- Multi-Picture Format ----------------------------------------------------
enum {
MPF_MPFVersion = 45056,
MPF_NumberOfImages = 45057,
MPF_MPEntry = 45058,
MPF_ImageUIDList = 45059,
MPF_TotalFrames = 45060,
MPF_MPIndividualNum = 45313,
MPF_PanOrientation = 45569,
MPF_PanOverlap_H = 45570,
MPF_PanOverlap_V = 45571,
MPF_BaseViewpointNum = 45572,
MPF_ConvergenceAngle = 45573,
MPF_BaselineLength = 45574,
MPF_VerticalDivergence = 45575,
MPF_AxisDistance_X = 45576,
MPF_AxisDistance_Y = 45577,
MPF_AxisDistance_Z = 45578,
MPF_YawAngle = 45579,
MPF_PitchAngle = 45580,
MPF_RollAngle = 45581
};
static struct tiff_entry mpf_entries[] = {
{"MP Format Version Number", MPF_MPFVersion, NULL},
{"Number of Images", MPF_NumberOfImages, NULL},
{"MP Entry", MPF_MPEntry, NULL},
{"Individual Image Unique ID List", MPF_ImageUIDList, NULL},
{"Total Number of Captured Frames", MPF_TotalFrames, NULL},
{"MP Individual Image Number", MPF_MPIndividualNum, NULL},
{"Panorama Scanning Orientation", MPF_PanOrientation, NULL},
{"Panorama Horizontal Overlap", MPF_PanOverlap_H, NULL},
{"Panorama Vertical Overlap", MPF_PanOverlap_V, NULL},
{"Base Viewpoint Number", MPF_BaseViewpointNum, NULL},
{"Convergence Angle", MPF_ConvergenceAngle, NULL},
{"Baseline Length", MPF_BaselineLength, NULL},
{"Divergence Angle", MPF_VerticalDivergence, NULL},
{"Horizontal Axis Distance", MPF_AxisDistance_X, NULL},
{"Vertical Axis Distance", MPF_AxisDistance_Y, NULL},
{"Collimation Axis Distance", MPF_AxisDistance_Z, NULL},
{"Yaw Angle", MPF_YawAngle, NULL},
{"Pitch Angle", MPF_PitchAngle, NULL},
{"Roll Angle", MPF_RollAngle, NULL},
{}
};
static uint32_t
parse_mpf_mpentry(jv *a, const uint8_t *p, struct tiffer *T)
parse_mpf_mpentry(jv *a, const uint8_t *p, const struct tiffer *T)
{
uint32_t attrs = T->un->u32(p);
uint32_t offset = T->un->u32(p + 8);
@ -725,7 +678,7 @@ parse_mpf_mpentry(jv *a, const uint8_t *p, struct tiffer *T)
}
static jv
parse_mpf_index_entry(jv o, const uint8_t ***offsets, struct tiffer *T,
parse_mpf_index_entry(jv o, uint32_t **offsets, const struct tiffer *T,
struct tiffer_entry *entry)
{
// 5.2.3.3. MP Entry
@ -736,17 +689,18 @@ parse_mpf_index_entry(jv o, const uint8_t ***offsets, struct tiffer *T,
uint32_t count = entry->remaining_count / 16;
jv a = jv_array_sized(count);
const uint8_t **out = *offsets = calloc(sizeof *out, count + 1);
uint32_t *out = *offsets = calloc(sizeof *out, count + 1);
for (uint32_t i = 0; i < count; i++) {
// 5.2.3.3.3. Individual Image Data Offset
uint32_t offset = parse_mpf_mpentry(&a, entry->p + i * 16, T);
if (offset)
*out++ = T->begin + offset;
*out++ = offset;
}
return jv_set(o, jv_string("MP Entry"), a);
}
static jv
parse_mpf_index_ifd(const uint8_t ***offsets, struct tiffer *T)
parse_mpf_index_ifd(uint32_t **offsets, struct tiffer *T)
{
jv ifd = jv_object();
struct tiffer_entry entry = {};
@ -756,7 +710,8 @@ parse_mpf_index_ifd(const uint8_t ***offsets, struct tiffer *T)
}
static jv
parse_mpf(jv o, const uint8_t ***offsets, const uint8_t *p, size_t len)
parse_mpf(jv o, const uint8_t ***individuals, const uint8_t *p, size_t len,
const uint8_t *end)
{
struct tiffer T;
if (!tiffer_init(&T, p, len) || !tiffer_next_ifd(&T))
@ -764,14 +719,34 @@ parse_mpf(jv o, const uint8_t ***offsets, const uint8_t *p, size_t len)
// First image: IFD0 is Index IFD, any IFD1 is Attribute IFD.
// Other images: IFD0 is Attribute IFD, there is no Index IFD.
if (!*offsets) {
o = add_to_subarray(o, "MPF", parse_mpf_index_ifd(offsets, &T));
uint32_t *offsets = NULL;
if (!*individuals) {
o = add_to_subarray(o, "MPF", parse_mpf_index_ifd(&offsets, &T));
if (!tiffer_next_ifd(&T))
return o;
goto out;
}
// This isn't optimal, but it will do.
return add_to_subarray(o, "MPF", parse_exif_ifd(&T, mpf_entries));
o = add_to_subarray(o, "MPF", parse_exif_ifd(&T, mpf_entries));
out:
if (offsets) {
size_t count = 0;
for (const uint32_t *i = offsets; *i; i++)
count++;
free(*individuals);
const uint8_t **out = *individuals = calloc(sizeof *out, count + 1);
for (const uint32_t *i = offsets; *i; i++) {
if (*i > end - p)
o = add_warning(o, "MPF offset points past available data");
else
*out++ = p + *i;
}
free(offsets);
}
return o;
}
// --- JPEG --------------------------------------------------------------------
@ -904,7 +879,7 @@ struct data {
uint8_t *exif, *icc, *psir;
size_t exif_len, icc_len, psir_len;
int icc_sequence, icc_done;
const uint8_t **mpf_offsets, **mpf_next;
const uint8_t **mpf_individuals, **mpf_next;
};
static void
@ -924,7 +899,7 @@ parse_marker(uint8_t marker, const uint8_t *p, const uint8_t *end,
// Found: Random metadata! Multi-Picture Format!
if ((data->ended = marker == EOI)) {
// TODO(p): Handle Exifs independently--flush the last one.
if ((data->mpf_next || (data->mpf_next = data->mpf_offsets)) &&
if ((data->mpf_next || (data->mpf_next = data->mpf_individuals)) &&
*data->mpf_next)
return *data->mpf_next++;
if (p != end)
@ -1027,11 +1002,11 @@ parse_marker(uint8_t marker, const uint8_t *p, const uint8_t *end,
unprintable ? jv_null() : jv_string((const char *) payload));
}
// CIPA DC-007 (Multi-Picture Format) 5.2
// http://fileformats.archiveteam.org/wiki/Multi-Picture_Format
// CIPA DC-007-2021 (Multi-Picture Format) 5.2
// https://www.cipa.jp/e/std/std-sec.html
if (marker == APP2 && p - payload >= 8 && !memcmp(payload, "MPF\0", 4)) {
payload += 4;
*o = parse_mpf(*o, &data->mpf_offsets, payload, p - payload);
*o = parse_mpf(*o, &data->mpf_individuals, payload, p - payload, end);
}
// CIPA DC-006 (Stereo Still Image Format for Digital Cameras)
@ -1154,6 +1129,6 @@ parse_jpeg(jv o, const uint8_t *p, size_t len)
free(data.psir);
}
free(data.mpf_offsets);
free(data.mpf_individuals);
return jv_set(o, jv_string("markers"), markers);
}