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:
3
NEWS
3
NEWS
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
|
||||
166
ext/exif/exif.c
166
ext/exif/exif.c
@@ -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
145
ext/exif/tests/exif029.phpt
Normal 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)
|
||||
}
|
||||
BIN
ext/exif/tests/image029.heic
Normal file
BIN
ext/exif/tests/image029.heic
Normal file
Binary file not shown.
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user