Skip to content

Commit cfb32af

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

11 files changed

+338
-3
lines changed

ext/exif/exif.c

Lines changed: 140 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1284,6 +1284,18 @@ typedef struct {
12841284
mn_offset_mode_t offset_mode;
12851285
} maker_note_type;
12861286

1287+
#define FOURCC(id) (((uint32_t)(id[0])<<24) | (id[1]<<16) | (id[2]<<8) | (id[3]))
1288+
1289+
typedef struct {
1290+
uint64_t size;
1291+
uint32_t type;
1292+
} isobmff_box_type;
1293+
1294+
typedef struct {
1295+
uint32_t offset;
1296+
uint32_t size;
1297+
} isobmff_item_pos_type;
1298+
12871299
/* Some maker notes (e.g. DJI info tag) require custom parsing */
12881300
#define REQUIRES_CUSTOM_PARSING NULL
12891301

@@ -4281,11 +4293,128 @@ static bool exif_process_IFD_in_TIFF(image_info_type *ImageInfo, size_t dir_offs
42814293
return result;
42824294
}
42834295

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

42914420
ImageInfo->FileType = IMAGE_FILETYPE_UNKNOWN;
@@ -4334,6 +4463,16 @@ static bool exif_scan_FILE_header(image_info_type *ImageInfo)
43344463
} else {
43354464
exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_WARNING, "Invalid TIFF file");
43364465
}
4466+
} else if ((ImageInfo->FileSize > 12) &&
4467+
(!memcmp(file_header + 4, "ftyp", 4)) &&
4468+
(exif_read_from_stream_file_looped(ImageInfo->infile, (char*)(file_header + 8), 4) == 4) &&
4469+
((!memcmp(file_header + 8, "heic", 4)) || (!memcmp(file_header + 8, "heix", 4)) || (!memcmp(file_header + 8, "mif1", 4)))) {
4470+
if (exif_scan_HEIF_header(ImageInfo, file_header)) {
4471+
ImageInfo->FileType = IMAGE_FILETYPE_HEIF;
4472+
ret = true;
4473+
} else {
4474+
exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_WARNING, "Invalid HEIF file");
4475+
}
43374476
} else {
43384477
exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_WARNING, "File not supported");
43394478
return false;

ext/exif/tests/exif028.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__.'/../../standard/tests/image/test4pix.heic'));
11+
?>
12+
--EXPECTF--
13+
array(53) {
14+
["FileName"]=>
15+
string(13) "test4pix.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+
["UndefinedTag:0x9010"]=>
74+
string(6) "+08:00"
75+
["UndefinedTag:0x9011"]=>
76+
string(6) "+08:00"
77+
["UndefinedTag:0x9012"]=>
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/standard/basic_functions.stub.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -646,6 +646,11 @@
646646
* @cvalue IMAGE_FILETYPE_AVIF
647647
*/
648648
const IMAGETYPE_AVIF = UNKNOWN;
649+
/**
650+
* @var int
651+
* @cvalue IMAGE_FILETYPE_HEIF
652+
*/
653+
const IMAGETYPE_HEIF = UNKNOWN;
649654
/**
650655
* @var int
651656
* @cvalue IMAGE_FILETYPE_UNKNOWN

ext/standard/basic_functions_arginfo.h

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

ext/standard/image.c

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,10 @@ PHPAPI const char php_sig_iff[4] = {'F','O','R','M'};
5151
PHPAPI const char php_sig_ico[4] = {(char)0x00, (char)0x00, (char)0x01, (char)0x00};
5252
PHPAPI const char php_sig_riff[4] = {'R', 'I', 'F', 'F'};
5353
PHPAPI const char php_sig_webp[4] = {'W', 'E', 'B', 'P'};
54+
PHPAPI const char php_sig_ftyp[4] = {'f', 't', 'y', 'p'};
55+
PHPAPI const char php_sig_mif1[4] = {'m', 'i', 'f', '1'};
56+
PHPAPI const char php_sig_heic[4] = {'h', 'e', 'i', 'c'};
57+
PHPAPI const char php_sig_heix[4] = {'h', 'e', 'i', 'x'};
5458

5559
/* REMEMBER TO ADD MIME-TYPE TO FUNCTION php_image_type_to_mime_type */
5660
/* PCX must check first 64bytes and byte 0=0x0a and byte2 < 0x06 */
@@ -1249,6 +1253,8 @@ PHPAPI char * php_image_type_to_mime_type(int image_type)
12491253
return "image/webp";
12501254
case IMAGE_FILETYPE_AVIF:
12511255
return "image/avif";
1256+
case IMAGE_FILETYPE_HEIF:
1257+
return "image/heif";
12521258
default:
12531259
case IMAGE_FILETYPE_UNKNOWN:
12541260
return "application/octet-stream"; /* suppose binary format */
@@ -1334,6 +1340,10 @@ PHP_FUNCTION(image_type_to_extension)
13341340
case IMAGE_FILETYPE_AVIF:
13351341
imgext = ".avif";
13361342
break;
1343+
case IMAGE_FILETYPE_HEIF:
1344+
imgext = ".heif";
1345+
break;
1346+
break;
13371347
}
13381348

13391349
if (imgext) {
@@ -1418,6 +1428,11 @@ PHPAPI int php_getimagetype(php_stream *stream, const char *input, char *filetyp
14181428
return IMAGE_FILETYPE_JP2;
14191429
}
14201430

1431+
if (twelve_bytes_read && !memcmp(filetype + 4, php_sig_ftyp, 4) &&
1432+
(!memcmp(filetype + 8, php_sig_mif1, 4) || !memcmp(filetype + 8, php_sig_heic, 4) || !memcmp(filetype + 8, php_sig_heix, 4))) {
1433+
return IMAGE_FILETYPE_HEIF;
1434+
}
1435+
14211436
if (!php_stream_rewind(stream) && php_is_image_avif(stream)) {
14221437
return IMAGE_FILETYPE_AVIF;
14231438
}
@@ -1510,6 +1525,11 @@ static void php_getimagesize_from_stream(php_stream *stream, char *input, zval *
15101525
case IMAGE_FILETYPE_AVIF:
15111526
result = php_handle_avif(stream);
15121527
break;
1528+
case IMAGE_FILETYPE_HEIF:
1529+
if (!php_stream_rewind(stream)) {
1530+
result = php_handle_avif(stream);
1531+
}
1532+
break;
15131533
default:
15141534
case IMAGE_FILETYPE_UNKNOWN:
15151535
break;

ext/standard/php_image.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ typedef enum
4444
IMAGE_FILETYPE_ICO,
4545
IMAGE_FILETYPE_WEBP,
4646
IMAGE_FILETYPE_AVIF,
47+
IMAGE_FILETYPE_HEIF,
4748
/* WHEN EXTENDING: PLEASE ALSO REGISTER IN basic_function.stub.php */
4849
IMAGE_FILETYPE_COUNT
4950
} image_filetype;

0 commit comments

Comments
 (0)