Skip to content

Commit ce49452

Browse files
committed
draft support retrieve Exif from heif file
Signed-off-by: Benstone Zhang <benstonezhang@gmail.com>
1 parent 7f1b360 commit ce49452

File tree

4 files changed

+312
-1
lines changed

4 files changed

+312
-1
lines changed

ext/exif/exif.c

Lines changed: 165 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1290,6 +1290,18 @@ typedef struct {
12901290
mn_offset_mode_t offset_mode;
12911291
} maker_note_type;
12921292

1293+
#define FOURCC(id) (((uint32_t)(id[0])<<24) | (id[1]<<16) | (id[2]<<8) | (id[3]))
1294+
1295+
typedef struct {
1296+
uint64_t size;
1297+
uint32_t type;
1298+
} isobmff_box_type;
1299+
1300+
typedef struct {
1301+
uint32_t offset;
1302+
uint32_t size;
1303+
} isobmff_item_pos_type;
1304+
12931305
/* Some maker notes (e.g. DJI info tag) require custom parsing */
12941306
#define REQUIRES_CUSTOM_PARSING NULL
12951307

@@ -4279,11 +4291,152 @@ static bool exif_process_IFD_in_TIFF(image_info_type *ImageInfo, size_t dir_offs
42794291
return result;
42804292
}
42814293

4294+
static int exif_isobmff_parse_box(unsigned char *buf, isobmff_box_type *box)
4295+
{
4296+
box->size = php_ifd_get32u(buf, 1);
4297+
buf += 4;
4298+
box->type = php_ifd_get32u(buf, 1);
4299+
if (box->size != 1) {
4300+
return 8;
4301+
}
4302+
buf += 4;
4303+
box->size = php_ifd_get64u(buf, 1);
4304+
return 16;
4305+
}
4306+
4307+
static void exif_isobmff_parse_meta(unsigned char *data, unsigned char *end, isobmff_item_pos_type *pos)
4308+
{
4309+
isobmff_box_type box, item;
4310+
unsigned char *box_offset, *p, *p2;
4311+
int header_size, exif_id = -1, version, item_count, i;
4312+
4313+
for (box_offset = data + 4; box_offset + 16 < end; box_offset += box.size) {
4314+
header_size = exif_isobmff_parse_box(box_offset, &box);
4315+
if (box.type == FOURCC("iinf")) {
4316+
p = box_offset + header_size;
4317+
if (p >= end) {
4318+
return;
4319+
}
4320+
version = p[0];
4321+
p += 4;
4322+
if (version < 2) {
4323+
if (p + 2 >= end) {
4324+
return;
4325+
}
4326+
item_count = php_ifd_get16u(p, 1);
4327+
p += 2;
4328+
} else {
4329+
if (p + 4 >= end) {
4330+
return;
4331+
}
4332+
item_count = php_ifd_get32u(p, 1);
4333+
p += 4;
4334+
}
4335+
for (i = 0; i < item_count && p + 20 < end; i++) {
4336+
header_size = exif_isobmff_parse_box(p, &item);
4337+
if (p + header_size + 12 >= end) {
4338+
return;
4339+
}
4340+
if (!memcmp(p + header_size + 8, "Exif", 4)) {
4341+
exif_id = php_ifd_get16u(p + header_size + 4, 1);
4342+
break;
4343+
}
4344+
p += item.size;
4345+
}
4346+
if (exif_id < 0) {
4347+
break;
4348+
}
4349+
}
4350+
else if (box.type == FOURCC("iloc")) {
4351+
p = box_offset + header_size;
4352+
if (p >= end) {
4353+
return;
4354+
}
4355+
version = p[0];
4356+
p += 6;
4357+
if (version < 2) {
4358+
if (p + 2 >= end) {
4359+
return;
4360+
}
4361+
item_count = php_ifd_get16u(p, 1);
4362+
p += 2;
4363+
} else {
4364+
if (p + 4 >= end) {
4365+
return;
4366+
}
4367+
item_count = php_ifd_get32u(p, 1);
4368+
p += 4;
4369+
}
4370+
for (i = 0, p2 = p; i < item_count && p + 16 < end; i++, p2 += 16) {
4371+
if (php_ifd_get16u(p2, 1) == exif_id) {
4372+
pos->offset = php_ifd_get32u(p2 + 8, 1);
4373+
pos->size = php_ifd_get32u(p2 + 12, 1);
4374+
break;
4375+
}
4376+
}
4377+
break;
4378+
}
4379+
}
4380+
}
4381+
4382+
static bool exif_scan_HEIF_header(image_info_type *ImageInfo, unsigned char *buf)
4383+
{
4384+
isobmff_box_type box;
4385+
isobmff_item_pos_type pos;
4386+
unsigned char *data;
4387+
off_t offset;
4388+
uint64_t limit;
4389+
int box_header_size, remain;
4390+
bool ret = false;
4391+
4392+
pos.size = 0;
4393+
for (offset = php_ifd_get32u(buf, 1); ImageInfo->FileSize > offset + 16; offset += box.size) {
4394+
if ((php_stream_seek(ImageInfo->infile, offset, SEEK_SET) < 0) ||
4395+
(exif_read_from_stream_file_looped(ImageInfo->infile, (char*)buf, 16) != 16)) {
4396+
break;
4397+
}
4398+
box_header_size = exif_isobmff_parse_box(buf, &box);
4399+
if (box.type == FOURCC("meta")) {
4400+
limit = box.size - box_header_size;
4401+
if (limit < 36) {
4402+
break;
4403+
}
4404+
data = (unsigned char *)emalloc(limit);
4405+
remain = 16 - box_header_size;
4406+
if (remain) {
4407+
memcpy(data, buf + box_header_size, remain);
4408+
}
4409+
if (exif_read_from_stream_file_looped(ImageInfo->infile, (char*)(data + remain), limit - remain) == limit - remain) {
4410+
exif_isobmff_parse_meta(data, data + limit, &pos);
4411+
}
4412+
if ((pos.size) &&
4413+
(ImageInfo->FileSize >= pos.offset + pos.size) &&
4414+
(php_stream_seek(ImageInfo->infile, pos.offset + 2, SEEK_SET) >= 0)) {
4415+
if (limit >= pos.size - 2) {
4416+
limit = pos.size - 2;
4417+
} else {
4418+
limit = pos.size - 2;
4419+
efree(data);
4420+
data = (unsigned char *)emalloc(limit);
4421+
}
4422+
if (exif_read_from_stream_file_looped(ImageInfo->infile, (char*)data, limit) == limit) {
4423+
exif_process_APP1(ImageInfo, (char*)data, limit, pos.offset + 2);
4424+
ret = true;
4425+
}
4426+
}
4427+
efree(data);
4428+
break;
4429+
}
4430+
}
4431+
4432+
return ret;
4433+
}
4434+
42824435
/* {{{ exif_scan_FILE_header
42834436
* Parse the marker stream until SOS or EOI is seen; */
42844437
static bool exif_scan_FILE_header(image_info_type *ImageInfo)
42854438
{
4286-
unsigned char file_header[8];
4439+
unsigned char file_header[16];
42874440

42884441
ImageInfo->FileType = IMAGE_FILETYPE_UNKNOWN;
42894442

@@ -4344,6 +4497,17 @@ static bool exif_scan_FILE_header(image_info_type *ImageInfo)
43444497
exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_WARNING, "Invalid TIFF file");
43454498
return false;
43464499
}
4500+
} else if ((ImageInfo->FileSize > 12) &&
4501+
(!memcmp(file_header + 4, "ftyp", 4)) &&
4502+
(exif_read_from_stream_file_looped(ImageInfo->infile, (char*)(file_header + 8), 4) == 4) &&
4503+
((!memcmp(file_header + 8, "heic", 4)) || (!memcmp(file_header + 8, "heix", 4)) || (!memcmp(file_header + 8, "mif1", 4)))) {
4504+
if (exif_scan_HEIF_header(ImageInfo, file_header)) {
4505+
ImageInfo->FileType = IMAGE_FILETYPE_HEIF;
4506+
return true;
4507+
} else {
4508+
exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_WARNING, "Invalid HEIF file");
4509+
return false;
4510+
}
43474511
} else {
43484512
exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_WARNING, "File not supported");
43494513
return false;

ext/exif/tests/exif029.phpt

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
--TEST--
2+
Check for exif_read_data, HEIF with IFD0 and EXIF data in Motorola byte-order.
3+
--EXTENSIONS--
4+
exif
5+
--INI--
6+
output_handler=
7+
zlib.output_compression=0
8+
--FILE--
9+
<?php
10+
var_dump(exif_read_data(__DIR__.'/image029.heic'));
11+
?>
12+
--EXPECTF--
13+
array(53) {
14+
["FileName"]=>
15+
string(13) "image029.heic"
16+
["FileDateTime"]=>
17+
int(%d)
18+
["FileSize"]=>
19+
int(42199)
20+
["FileType"]=>
21+
int(20)
22+
["MimeType"]=>
23+
string(10) "image/heif"
24+
["SectionsFound"]=>
25+
string(19) "ANY_TAG, IFD0, EXIF"
26+
["COMPUTED"]=>
27+
array(3) {
28+
["IsColor"]=>
29+
int(0)
30+
["ByteOrderMotorola"]=>
31+
int(1)
32+
["ApertureFNumber"]=>
33+
string(5) "f/1.8"
34+
}
35+
["Make"]=>
36+
string(5) "Apple"
37+
["Model"]=>
38+
string(26) "iPhone SE (3rd generation)"
39+
["Orientation"]=>
40+
int(1)
41+
["XResolution"]=>
42+
string(4) "72/1"
43+
["YResolution"]=>
44+
string(4) "72/1"
45+
["ResolutionUnit"]=>
46+
int(2)
47+
["Software"]=>
48+
string(6) "17.2.1"
49+
["DateTime"]=>
50+
string(19) "2024:02:21 16:03:50"
51+
["HostComputer"]=>
52+
string(26) "iPhone SE (3rd generation)"
53+
["TileWidth"]=>
54+
int(512)
55+
["TileLength"]=>
56+
int(512)
57+
["Exif_IFD_Pointer"]=>
58+
int(264)
59+
["ExposureTime"]=>
60+
string(4) "1/60"
61+
["FNumber"]=>
62+
string(3) "9/5"
63+
["ExposureProgram"]=>
64+
int(2)
65+
["ISOSpeedRatings"]=>
66+
int(200)
67+
["ExifVersion"]=>
68+
string(4) "0232"
69+
["DateTimeOriginal"]=>
70+
string(19) "2024:02:21 16:03:50"
71+
["DateTimeDigitized"]=>
72+
string(19) "2024:02:21 16:03:50"
73+
["OffsetTime"]=>
74+
string(6) "+08:00"
75+
["OffsetTimeOriginal"]=>
76+
string(6) "+08:00"
77+
["OffsetTimeDigitized"]=>
78+
string(6) "+08:00"
79+
["ShutterSpeedValue"]=>
80+
string(12) "159921/27040"
81+
["ApertureValue"]=>
82+
string(11) "54823/32325"
83+
["BrightnessValue"]=>
84+
string(11) "29968/13467"
85+
["ExposureBiasValue"]=>
86+
string(3) "0/1"
87+
["MeteringMode"]=>
88+
int(5)
89+
["Flash"]=>
90+
int(16)
91+
["FocalLength"]=>
92+
string(7) "399/100"
93+
["SubjectLocation"]=>
94+
array(4) {
95+
[0]=>
96+
int(1995)
97+
[1]=>
98+
int(1507)
99+
[2]=>
100+
int(2217)
101+
[3]=>
102+
int(1332)
103+
}
104+
["MakerNote"]=>
105+
string(9) "Apple iOS"
106+
["SubSecTimeOriginal"]=>
107+
string(3) "598"
108+
["SubSecTimeDigitized"]=>
109+
string(3) "598"
110+
["ColorSpace"]=>
111+
int(65535)
112+
["ExifImageWidth"]=>
113+
int(4032)
114+
["ExifImageLength"]=>
115+
int(3024)
116+
["SensingMethod"]=>
117+
int(2)
118+
["SceneType"]=>
119+
string(1) ""
120+
["ExposureMode"]=>
121+
int(0)
122+
["WhiteBalance"]=>
123+
int(0)
124+
["DigitalZoomRatio"]=>
125+
string(7) "756/151"
126+
["FocalLengthIn35mmFilm"]=>
127+
int(140)
128+
["UndefinedTag:0xA432"]=>
129+
array(4) {
130+
[0]=>
131+
string(15) "4183519/1048501"
132+
[1]=>
133+
string(15) "4183519/1048501"
134+
[2]=>
135+
string(3) "9/5"
136+
[3]=>
137+
string(3) "9/5"
138+
}
139+
["UndefinedTag:0xA433"]=>
140+
string(5) "Apple"
141+
["UndefinedTag:0xA434"]=>
142+
string(51) "iPhone SE (3rd generation) back camera 3.99mm f/1.8"
143+
["UndefinedTag:0xA460"]=>
144+
int(2)
145+
}

ext/exif/tests/image029.heic

41.2 KB
Binary file not shown.

ext/standard/tests/image/image_type_to_mime_type_basic.phpt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ $image_types = array (
2121
IMAGETYPE_IFF,
2222
IMAGETYPE_WBMP,
2323
IMAGETYPE_JPEG2000,
24+
IMAGETYPE_HEIF,
2425
IMAGETYPE_XBM,
2526
IMAGETYPE_WEBP,
2627
IMAGETYPE_HEIF,
@@ -50,6 +51,7 @@ string(24) "application/octet-stream"
5051
string(9) "image/iff"
5152
string(18) "image/vnd.wap.wbmp"
5253
string(24) "application/octet-stream"
54+
string(10) "image/heif"
5355
string(9) "image/xbm"
5456
string(10) "image/webp"
5557
string(10) "image/heif"

0 commit comments

Comments
 (0)