Skip to content

Commit

Permalink
overwrite image size in Exif to match rotated sizes (#1190)
Browse files Browse the repository at this point in the history
  • Loading branch information
farindk committed Aug 3, 2024
1 parent e7c5649 commit 697cf59
Show file tree
Hide file tree
Showing 4 changed files with 114 additions and 15 deletions.
1 change: 1 addition & 0 deletions examples/encoder_jpeg.cc
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,7 @@ bool JpegEncoder::Encode(const struct heif_image_handle* handle,

// libheif by default normalizes the image orientation, so that we have to set the EXIF Orientation to "Horizontal (normal)"
modify_exif_orientation_tag_if_it_exists(ptr, size32, 1);
overwrite_exif_image_size_if_it_exists(ptr, size32, cinfo.image_width, cinfo.image_height);

// We have to limit the size for the memcpy, otherwise GCC warns that we exceed the maximum size.
if (size>0x1000000) {
Expand Down
1 change: 1 addition & 0 deletions examples/encoder_png.cc
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ bool PngEncoder::Encode(const struct heif_image_handle* handle,

// libheif by default normalizes the image orientation, so that we have to set the EXIF Orientation to "Horizontal (normal)"
modify_exif_orientation_tag_if_it_exists(ptr, (int)size, 1);
overwrite_exif_image_size_if_it_exists(ptr, (int)size, width, height);

png_set_eXIf_1(png_ptr, info_ptr, (png_uint_32)size, ptr);
}
Expand Down
125 changes: 110 additions & 15 deletions libheif/exif.cc
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,14 @@
#include "exif.h"

#define EXIF_TYPE_SHORT 3
#define EXIF_TYPE_LONG 4
#define DEFAULT_EXIF_ORIENTATION 1
#define EXIF_TAG_ORIENTATION 0x112
#define EXIF_TAG_IMAGE_WIDTH ((uint16_t)0x0100)
#define EXIF_TAG_IMAGE_HEIGHT ((uint16_t)0x0101)
#define EXIF_TAG_VALID_IMAGE_WIDTH ((uint16_t)0xA002)
#define EXIF_TAG_VALID_IMAGE_HEIGHT ((uint16_t)0xA003)
#define EXIF_TAG_EXIF_IFD_POINTER ((uint16_t)0x8769)

// Note: As far as I can see, it is not defined in the EXIF standard whether the offsets and counts of the IFD is signed or unsigned.
// We assume that these are all unsigned.
Expand Down Expand Up @@ -74,33 +80,55 @@ static void write16(uint8_t* data, uint32_t size, uint32_t pos, uint16_t value,
}
}

// Returns 0 if the query_tag was not found.
static uint32_t find_exif_tag(const uint8_t* exif, uint32_t size, uint16_t query_tag, bool* out_littleEndian)

static void write32(uint8_t* data, uint32_t size, uint32_t pos, uint32_t value, bool littleEndian)
{
if (size < 4) {
return 0;
assert(pos <= size - 4);

uint8_t* p = data + pos;

if (littleEndian) {
p[0] = (uint8_t) (value & 0xFF);
p[1] = (uint8_t) ((value >> 8) & 0xFF);
p[2] = (uint8_t) ((value >> 16) & 0xFF);
p[3] = (uint8_t) ((value >> 24) & 0xFF);
} else {
p[0] = (uint8_t) ((value >> 24) & 0xFF);
p[1] = (uint8_t) ((value >> 16) & 0xFF);
p[2] = (uint8_t) ((value >> 8) & 0xFF);
p[3] = (uint8_t) (value & 0xFF);
}
}

if ((exif[0] != 'I' && exif[0] != 'M') ||
(exif[1] != 'I' && exif[1] != 'M')) {

// Returns 0 if the query_tag was not found.
static uint32_t find_exif_tag_in_ifd(const uint8_t* exif, uint32_t size,
uint32_t ifd_offset,
uint16_t query_tag,
bool littleEndian,
int recursion_depth)
{
const int MAX_IFD_TABLE_RECURSION_DEPTH = 5;

if (recursion_depth > MAX_IFD_TABLE_RECURSION_DEPTH) {
return 0;
}

bool littleEndian = (exif[0] == 'I');

assert(out_littleEndian);
*out_littleEndian = littleEndian;
uint32_t offset = ifd_offset;

uint32_t offset = read32(exif, size, 4, littleEndian);
// is offset valid (i.e. can we read at least the 'size' field and the pointer to the next IFD ?)
if (offset == 0) {
return 0;
}

if (size - 2 < offset) {
if (size < 6 || size - 2 - 4 < offset) {
return 0;
}

uint16_t cnt = read16(exif, size, offset, littleEndian);

// Does the IFD table fit into our memory range? We need this check to prevent an underflow in the following statement.
uint32_t IFD_table_size = 2U + cnt * 12U;
uint32_t IFD_table_size = 2U + cnt * 12U + 4U;
if (IFD_table_size > size) {
return 0;
}
Expand All @@ -116,11 +144,78 @@ static uint32_t find_exif_tag(const uint8_t* exif, uint32_t size, uint16_t query
if (tag == query_tag) {
return offset + 2 + i * 12;
}

if (tag == EXIF_TAG_EXIF_IFD_POINTER) {
uint32_t exifIFD_offset = read32(exif, size, offset + 2 + i * 12 + 8, littleEndian);
uint32_t tag_position = find_exif_tag_in_ifd(exif, size, exifIFD_offset, query_tag, littleEndian,
recursion_depth + 1);
if (tag_position) {
return tag_position;
}
}
}

// TODO: do we have to also scan the next IFD table ?
// continue with next IFD table

return 0;
uint32_t pos = offset + 2 + cnt * 12;
uint32_t next_ifd_offset = read32(exif, size, pos, littleEndian);

return find_exif_tag_in_ifd(exif, size, next_ifd_offset, query_tag, littleEndian, recursion_depth + 1);
}


// Returns 0 if the query_tag was not found.
static uint32_t find_exif_tag(const uint8_t* exif, uint32_t size, uint16_t query_tag, bool* out_littleEndian)
{
// read TIFF header

if (size < 4) {
return 0;
}

if ((exif[0] != 'I' && exif[0] != 'M') ||
(exif[1] != 'I' && exif[1] != 'M')) {
return 0;
}

bool littleEndian = (exif[0] == 'I');

assert(out_littleEndian);
*out_littleEndian = littleEndian;


// read main IFD table

uint32_t offset;
offset = read32(exif, size, 4, littleEndian);

uint32_t tag_position = find_exif_tag_in_ifd(exif, size, offset, query_tag, littleEndian, 1);
return tag_position;
}


void overwrite_exif_image_size_if_it_exists(uint8_t* exif, uint32_t size, uint32_t width, uint32_t height)
{
bool little_endian;
uint32_t pos;

for (uint16_t tag: {EXIF_TAG_IMAGE_WIDTH, EXIF_TAG_VALID_IMAGE_WIDTH}) {
pos = find_exif_tag(exif, size, tag, &little_endian);
if (pos != 0) {
write16(exif, size, pos + 2, EXIF_TYPE_LONG, little_endian);
write32(exif, size, pos + 4, 1, little_endian);
write32(exif, size, pos + 8, width, little_endian);
}
}

for (uint16_t tag: {EXIF_TAG_IMAGE_HEIGHT, EXIF_TAG_VALID_IMAGE_HEIGHT}) {
pos = find_exif_tag(exif, size, tag, &little_endian);
if (pos != 0) {
write16(exif, size, pos + 2, EXIF_TYPE_LONG, little_endian);
write32(exif, size, pos + 4, 1, little_endian);
write32(exif, size, pos + 8, height, little_endian);
}
}
}


Expand Down
2 changes: 2 additions & 0 deletions libheif/exif.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,6 @@ int read_exif_orientation_tag(const uint8_t* exif, uint32_t size);

void modify_exif_orientation_tag_if_it_exists(uint8_t* exifData, uint32_t size, uint16_t orientation);

void overwrite_exif_image_size_if_it_exists(uint8_t* exif, uint32_t size, uint32_t width, uint32_t height);

#endif //LIBHEIF_EXIF_H

0 comments on commit 697cf59

Please sign in to comment.