Skip to content

Commit eacf55d

Browse files
committed
draft support retrieve Exif from heif file
Signed-off-by: Benstone Zhang <[email protected]>
1 parent 39cf276 commit eacf55d

File tree

4 files changed

+288
-1
lines changed

4 files changed

+288
-1
lines changed

ext/exif/exif.c

Lines changed: 141 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,128 @@ 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 < 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+
version = p[0];
4318+
p += 4;
4319+
if (version < 2) {
4320+
item_count = php_ifd_get16u(p, 1);
4321+
p += 2;
4322+
} else {
4323+
item_count = php_ifd_get32u(p, 1);
4324+
p += 4;
4325+
}
4326+
for (i=0; i < item_count; i++) {
4327+
header_size = exif_isobmff_parse_box(p, &item);
4328+
if (!memcmp(p + header_size + 8, "Exif", 4)) {
4329+
exif_id = php_ifd_get16u(p + header_size + 4, 1);
4330+
break;
4331+
}
4332+
p += item.size;
4333+
}
4334+
if (exif_id < 0) {
4335+
break;
4336+
}
4337+
}
4338+
else if (box.type == FOURCC("iloc")) {
4339+
p = box_offset + header_size;
4340+
version = p[0];
4341+
p += 6;
4342+
if (version < 2) {
4343+
item_count = php_ifd_get16u(p, 1);
4344+
p += 2;
4345+
} else {
4346+
item_count = php_ifd_get32u(p, 1);
4347+
p += 4;
4348+
}
4349+
for (i = 0, p2 = p; i<item_count; i++, p2 += 16) {
4350+
if (php_ifd_get16u(p2, 1) == exif_id) {
4351+
pos->offset = php_ifd_get32u(p2 + 8, 1);
4352+
pos->size = php_ifd_get32u(p2 + 12, 1);
4353+
break;
4354+
}
4355+
}
4356+
break;
4357+
}
4358+
}
4359+
}
4360+
4361+
static bool exif_scan_HEIF_header(image_info_type *ImageInfo, unsigned char *buf)
4362+
{
4363+
isobmff_box_type box;
4364+
isobmff_item_pos_type pos;
4365+
unsigned char *data;
4366+
off_t offset;
4367+
uint64_t limit;
4368+
int box_header_size, remain;
4369+
bool ret = false;
4370+
4371+
pos.size = 0;
4372+
for (offset = php_ifd_get32u(buf, 1); ImageInfo->FileSize > offset + 16; offset += box.size) {
4373+
if ((php_stream_seek(ImageInfo->infile, offset, SEEK_SET) < 0) ||
4374+
(exif_read_from_stream_file_looped(ImageInfo->infile, (char*)buf, 16) != 16)) {
4375+
break;
4376+
}
4377+
box_header_size = exif_isobmff_parse_box(buf, &box);
4378+
if (box.type == FOURCC("meta")) {
4379+
limit = box.size - box_header_size;
4380+
data = (unsigned char *)emalloc(limit);
4381+
remain = 16 - box_header_size;
4382+
if (remain) {
4383+
memcpy(data, buf + box_header_size, remain);
4384+
}
4385+
if (exif_read_from_stream_file_looped(ImageInfo->infile, (char*)(data + remain), limit - remain) == limit - remain) {
4386+
exif_isobmff_parse_meta(data, data + limit, &pos);
4387+
}
4388+
if ((pos.size) &&
4389+
(ImageInfo->FileSize >= pos.offset + pos.size) &&
4390+
(php_stream_seek(ImageInfo->infile, pos.offset + 2, SEEK_SET) >= 0)) {
4391+
if (limit >= pos.size - 2) {
4392+
limit = pos.size - 2;
4393+
} else {
4394+
limit = pos.size - 2;
4395+
efree(data);
4396+
data = (unsigned char *)emalloc(limit);
4397+
}
4398+
if (exif_read_from_stream_file_looped(ImageInfo->infile, (char*)data, limit) == limit) {
4399+
exif_process_APP1(ImageInfo, (char*)data, limit, pos.offset + 2);
4400+
ret = true;
4401+
}
4402+
}
4403+
efree(data);
4404+
break;
4405+
}
4406+
}
4407+
4408+
return ret;
4409+
}
4410+
42824411
/* {{{ exif_scan_FILE_header
42834412
* Parse the marker stream until SOS or EOI is seen; */
42844413
static bool exif_scan_FILE_header(image_info_type *ImageInfo)
42854414
{
4286-
unsigned char file_header[8];
4415+
unsigned char file_header[16];
42874416

42884417
ImageInfo->FileType = IMAGE_FILETYPE_UNKNOWN;
42894418

@@ -4344,6 +4473,17 @@ static bool exif_scan_FILE_header(image_info_type *ImageInfo)
43444473
exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_WARNING, "Invalid TIFF file");
43454474
return false;
43464475
}
4476+
} else if ((ImageInfo->FileSize > 12) &&
4477+
(!memcmp(file_header + 4, "ftyp", 4)) &&
4478+
(exif_read_from_stream_file_looped(ImageInfo->infile, (char*)(file_header + 8), 4) == 4) &&
4479+
((!memcmp(file_header + 8, "heic", 4)) || (!memcmp(file_header + 8, "heix", 4)) || (!memcmp(file_header + 8, "mif1", 4)))) {
4480+
if (exif_scan_HEIF_header(ImageInfo, file_header)) {
4481+
ImageInfo->FileType = IMAGE_FILETYPE_HEIF;
4482+
return true;
4483+
} else {
4484+
exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_WARNING, "Invalid HEIF file");
4485+
return false;
4486+
}
43474487
} else {
43484488
exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_WARNING, "File not supported");
43494489
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
);
@@ -49,6 +50,7 @@ string(24) "application/octet-stream"
4950
string(9) "image/iff"
5051
string(18) "image/vnd.wap.wbmp"
5152
string(24) "application/octet-stream"
53+
string(10) "image/heif"
5254
string(9) "image/xbm"
5355
string(10) "image/webp"
5456

0 commit comments

Comments
 (0)