1
0
mirror of https://github.com/php/php-src.git synced 2026-03-24 00:02:20 +01:00

Add support for retrieving Exif from HEIF file

Signed-off-by: Benstone Zhang <benstonezhang@gmail.com>

Closes GH-13443
This commit is contained in:
benstone
2024-02-20 23:23:44 +08:00
committed by Jakub Zelenka
parent 0ee7732c01
commit 7d5c8dad3b
6 changed files with 316 additions and 1 deletions

3
NEWS
View File

@@ -6,6 +6,9 @@ PHP NEWS
. Destructing non-array values (other than NULL) using [] or list() now
emits a warning. (Girgias)
- EXIF:
. Added support to retrieve Exif from HEIF file. (Benstone Zhang)
- Opcache:
. Fixed bug GH-19486 (Incorrect opline after deoptimization). (Arnaud)
. Fixed bug GH-19601 (Wrong JIT stack setup on aarch64/clang). (Arnaud)

View File

@@ -245,6 +245,7 @@ PHP 8.5 UPGRADE NOTES
- EXIF:
. Add OffsetTime* Exif tags.
. Added support for HEIF/HEIC.
- Intl:
. Added class constants NumberFormatter::CURRENCY_ISO,

View File

@@ -1290,6 +1290,18 @@ typedef struct {
mn_offset_mode_t offset_mode;
} maker_note_type;
#define FOURCC(id) (((uint32_t)(id[0])<<24) | (id[1]<<16) | (id[2]<<8) | (id[3]))
typedef struct {
uint64_t size;
uint32_t type;
} isobmff_box_type;
typedef struct {
uint32_t offset;
uint32_t size;
} isobmff_item_pos_type;
/* Some maker notes (e.g. DJI info tag) require custom parsing */
#define REQUIRES_CUSTOM_PARSING NULL
@@ -4279,11 +4291,152 @@ static bool exif_process_IFD_in_TIFF(image_info_type *ImageInfo, size_t dir_offs
return result;
}
static int exif_isobmff_parse_box(unsigned char *buf, isobmff_box_type *box)
{
box->size = php_ifd_get32u(buf, 1);
buf += 4;
box->type = php_ifd_get32u(buf, 1);
if (box->size != 1) {
return 8;
}
buf += 4;
box->size = php_ifd_get64u(buf, 1);
return 16;
}
static void exif_isobmff_parse_meta(unsigned char *data, unsigned char *end, isobmff_item_pos_type *pos)
{
isobmff_box_type box, item;
unsigned char *box_offset, *p, *p2;
int header_size, exif_id = -1, version, item_count, i;
for (box_offset = data + 4; box_offset + 16 < end; box_offset += box.size) {
header_size = exif_isobmff_parse_box(box_offset, &box);
if (box.type == FOURCC("iinf")) {
p = box_offset + header_size;
if (p >= end) {
return;
}
version = p[0];
p += 4;
if (version < 2) {
if (p + 2 >= end) {
return;
}
item_count = php_ifd_get16u(p, 1);
p += 2;
} else {
if (p + 4 >= end) {
return;
}
item_count = php_ifd_get32u(p, 1);
p += 4;
}
for (i = 0; i < item_count && p + 20 < end; i++) {
header_size = exif_isobmff_parse_box(p, &item);
if (p + header_size + 12 >= end) {
return;
}
if (!memcmp(p + header_size + 8, "Exif", 4)) {
exif_id = php_ifd_get16u(p + header_size + 4, 1);
break;
}
p += item.size;
}
if (exif_id < 0) {
break;
}
}
else if (box.type == FOURCC("iloc")) {
p = box_offset + header_size;
if (p >= end) {
return;
}
version = p[0];
p += 6;
if (version < 2) {
if (p + 2 >= end) {
return;
}
item_count = php_ifd_get16u(p, 1);
p += 2;
} else {
if (p + 4 >= end) {
return;
}
item_count = php_ifd_get32u(p, 1);
p += 4;
}
for (i = 0, p2 = p; i < item_count && p + 16 < end; i++, p2 += 16) {
if (php_ifd_get16u(p2, 1) == exif_id) {
pos->offset = php_ifd_get32u(p2 + 8, 1);
pos->size = php_ifd_get32u(p2 + 12, 1);
break;
}
}
break;
}
}
}
static bool exif_scan_HEIF_header(image_info_type *ImageInfo, unsigned char *buf)
{
isobmff_box_type box;
isobmff_item_pos_type pos;
unsigned char *data;
off_t offset;
uint64_t limit;
int box_header_size, remain;
bool ret = false;
pos.size = 0;
for (offset = php_ifd_get32u(buf, 1); ImageInfo->FileSize > offset + 16; offset += box.size) {
if ((php_stream_seek(ImageInfo->infile, offset, SEEK_SET) < 0) ||
(exif_read_from_stream_file_looped(ImageInfo->infile, (char*)buf, 16) != 16)) {
break;
}
box_header_size = exif_isobmff_parse_box(buf, &box);
if (box.type == FOURCC("meta")) {
limit = box.size - box_header_size;
if (limit < 36) {
break;
}
data = (unsigned char *)emalloc(limit);
remain = 16 - box_header_size;
if (remain) {
memcpy(data, buf + box_header_size, remain);
}
if (exif_read_from_stream_file_looped(ImageInfo->infile, (char*)(data + remain), limit - remain) == limit - remain) {
exif_isobmff_parse_meta(data, data + limit, &pos);
}
if ((pos.size) &&
(ImageInfo->FileSize >= pos.offset + pos.size) &&
(php_stream_seek(ImageInfo->infile, pos.offset + 2, SEEK_SET) >= 0)) {
if (limit >= pos.size - 2) {
limit = pos.size - 2;
} else {
limit = pos.size - 2;
efree(data);
data = (unsigned char *)emalloc(limit);
}
if (exif_read_from_stream_file_looped(ImageInfo->infile, (char*)data, limit) == limit) {
exif_process_APP1(ImageInfo, (char*)data, limit, pos.offset + 2);
ret = true;
}
}
efree(data);
break;
}
}
return ret;
}
/* {{{ exif_scan_FILE_header
* Parse the marker stream until SOS or EOI is seen; */
static bool exif_scan_FILE_header(image_info_type *ImageInfo)
{
unsigned char file_header[8];
unsigned char file_header[16];
ImageInfo->FileType = IMAGE_FILETYPE_UNKNOWN;
@@ -4344,6 +4497,17 @@ static bool exif_scan_FILE_header(image_info_type *ImageInfo)
exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_WARNING, "Invalid TIFF file");
return false;
}
} else if ((ImageInfo->FileSize > 12) &&
(!memcmp(file_header + 4, "ftyp", 4)) &&
(exif_read_from_stream_file_looped(ImageInfo->infile, (char*)(file_header + 8), 4) == 4) &&
((!memcmp(file_header + 8, "heic", 4)) || (!memcmp(file_header + 8, "heix", 4)) || (!memcmp(file_header + 8, "mif1", 4)))) {
if (exif_scan_HEIF_header(ImageInfo, file_header)) {
ImageInfo->FileType = IMAGE_FILETYPE_HEIF;
return true;
} else {
exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_WARNING, "Invalid HEIF file");
return false;
}
} else {
exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_WARNING, "File not supported");
return false;

145
ext/exif/tests/exif029.phpt Normal file
View File

@@ -0,0 +1,145 @@
--TEST--
Check for exif_read_data, HEIF with IFD0 and EXIF data in Motorola byte-order.
--EXTENSIONS--
exif
--INI--
output_handler=
zlib.output_compression=0
--FILE--
<?php
var_dump(exif_read_data(__DIR__.'/image029.heic'));
?>
--EXPECTF--
array(53) {
["FileName"]=>
string(13) "image029.heic"
["FileDateTime"]=>
int(%d)
["FileSize"]=>
int(42199)
["FileType"]=>
int(20)
["MimeType"]=>
string(10) "image/heif"
["SectionsFound"]=>
string(19) "ANY_TAG, IFD0, EXIF"
["COMPUTED"]=>
array(3) {
["IsColor"]=>
int(0)
["ByteOrderMotorola"]=>
int(1)
["ApertureFNumber"]=>
string(5) "f/1.8"
}
["Make"]=>
string(5) "Apple"
["Model"]=>
string(26) "iPhone SE (3rd generation)"
["Orientation"]=>
int(1)
["XResolution"]=>
string(4) "72/1"
["YResolution"]=>
string(4) "72/1"
["ResolutionUnit"]=>
int(2)
["Software"]=>
string(6) "17.2.1"
["DateTime"]=>
string(19) "2024:02:21 16:03:50"
["HostComputer"]=>
string(26) "iPhone SE (3rd generation)"
["TileWidth"]=>
int(512)
["TileLength"]=>
int(512)
["Exif_IFD_Pointer"]=>
int(264)
["ExposureTime"]=>
string(4) "1/60"
["FNumber"]=>
string(3) "9/5"
["ExposureProgram"]=>
int(2)
["ISOSpeedRatings"]=>
int(200)
["ExifVersion"]=>
string(4) "0232"
["DateTimeOriginal"]=>
string(19) "2024:02:21 16:03:50"
["DateTimeDigitized"]=>
string(19) "2024:02:21 16:03:50"
["OffsetTime"]=>
string(6) "+08:00"
["OffsetTimeOriginal"]=>
string(6) "+08:00"
["OffsetTimeDigitized"]=>
string(6) "+08:00"
["ShutterSpeedValue"]=>
string(12) "159921/27040"
["ApertureValue"]=>
string(11) "54823/32325"
["BrightnessValue"]=>
string(11) "29968/13467"
["ExposureBiasValue"]=>
string(3) "0/1"
["MeteringMode"]=>
int(5)
["Flash"]=>
int(16)
["FocalLength"]=>
string(7) "399/100"
["SubjectLocation"]=>
array(4) {
[0]=>
int(1995)
[1]=>
int(1507)
[2]=>
int(2217)
[3]=>
int(1332)
}
["MakerNote"]=>
string(9) "Apple iOS"
["SubSecTimeOriginal"]=>
string(3) "598"
["SubSecTimeDigitized"]=>
string(3) "598"
["ColorSpace"]=>
int(65535)
["ExifImageWidth"]=>
int(4032)
["ExifImageLength"]=>
int(3024)
["SensingMethod"]=>
int(2)
["SceneType"]=>
string(1) ""
["ExposureMode"]=>
int(0)
["WhiteBalance"]=>
int(0)
["DigitalZoomRatio"]=>
string(7) "756/151"
["FocalLengthIn35mmFilm"]=>
int(140)
["UndefinedTag:0xA432"]=>
array(4) {
[0]=>
string(15) "4183519/1048501"
[1]=>
string(15) "4183519/1048501"
[2]=>
string(3) "9/5"
[3]=>
string(3) "9/5"
}
["UndefinedTag:0xA433"]=>
string(5) "Apple"
["UndefinedTag:0xA434"]=>
string(51) "iPhone SE (3rd generation) back camera 3.99mm f/1.8"
["UndefinedTag:0xA460"]=>
int(2)
}

Binary file not shown.

View File

@@ -21,6 +21,7 @@ $image_types = array (
IMAGETYPE_IFF,
IMAGETYPE_WBMP,
IMAGETYPE_JPEG2000,
IMAGETYPE_HEIF,
IMAGETYPE_XBM,
IMAGETYPE_WEBP,
IMAGETYPE_HEIF,
@@ -50,6 +51,7 @@ string(24) "application/octet-stream"
string(9) "image/iff"
string(18) "image/vnd.wap.wbmp"
string(24) "application/octet-stream"
string(10) "image/heif"
string(9) "image/xbm"
string(10) "image/webp"
string(10) "image/heif"