Skip to content

Commit b7e94a3

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

11 files changed

+334
-3
lines changed

ext/exif/exif.c

Lines changed: 136 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

@@ -4285,11 +4297,124 @@ static bool exif_process_IFD_in_TIFF(image_info_type *ImageInfo, size_t dir_offs
42854297
return result;
42864298
}
42874299

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

42954420
ImageInfo->FileType = IMAGE_FILETYPE_UNKNOWN;
@@ -4338,6 +4463,16 @@ static bool exif_scan_FILE_header(image_info_type *ImageInfo)
43384463
} else {
43394464
exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_WARNING, "Invalid TIFF file");
43404465
}
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+
}
43414476
} else {
43424477
exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_WARNING, "File not supported");
43434478
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
@@ -666,6 +666,11 @@
666666
* @cvalue IMAGE_FILETYPE_AVIF
667667
*/
668668
const IMAGETYPE_AVIF = UNKNOWN;
669+
/**
670+
* @var int
671+
* @cvalue IMAGE_FILETYPE_HEIF
672+
*/
673+
const IMAGETYPE_HEIF = UNKNOWN;
669674
/**
670675
* @var int
671676
* @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)