Skip to content

Commit 80f15c3

Browse files
roticvfacebook-github-bot
authored andcommitted
Add AVIF/HEIC support in ext_gd
Summary: This diff adds support for AVIF/HEIC mainly for getimagesize and is based on D32769551 but the support is implemented via libheif. Reviewed By: ricklavoie Differential Revision: D54236983 fbshipit-source-id: 3b495413fcad7ef331eaf6c0c937dc78ecd101f0
1 parent 7e07868 commit 80f15c3

23 files changed

+290
-12
lines changed

hphp/hack/hhi/stdlib/builtins_image.hhi

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,17 @@
88
*
99
*/
1010

11+
const int IMAGETYPE_AVIF;
1112
const int IMAGETYPE_BMP;
1213
const int IMAGETYPE_COUNT;
1314
const int IMAGETYPE_GIF;
15+
const int IMAGETYPE_HEIC;
1416
const int IMAGETYPE_IFF;
1517
const int IMAGETYPE_JB2;
1618
const int IMAGETYPE_JP2;
1719
const int IMAGETYPE_JPC;
18-
const int IMAGETYPE_JPEG;
1920
const int IMAGETYPE_JPEG2000;
21+
const int IMAGETYPE_JPEG;
2022
const int IMAGETYPE_JPX;
2123
const int IMAGETYPE_PNG;
2224
const int IMAGETYPE_PSD;

hphp/runtime/ext/gd/ext_gd.cpp

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,10 @@
4242
#include <zlib.h>
4343
#include <set>
4444

45+
#include <folly/ScopeGuard.h>
4546
#include <folly/portability/Stdlib.h>
4647
#include <folly/portability/Unistd.h>
48+
#include <libheif/heif.h>
4749

4850
/* Section Filters Declarations */
4951
/* IMPORTANT NOTE FOR NEW FILTER
@@ -421,6 +423,8 @@ typedef enum {
421423
IMAGE_FILETYPE_XBM,
422424
IMAGE_FILETYPE_ICO,
423425
IMAGE_FILETYPE_WEBP,
426+
IMAGE_FILETYPE_AVIF,
427+
IMAGE_FILETYPE_HEIC,
424428

425429
IMAGE_FILETYPE_COUNT /* Must remain last */
426430
} image_filetype;
@@ -1508,6 +1512,111 @@ static struct gfxinfo *php_handle_webp(const req::ptr<File>& stream) {
15081512
return result;
15091513
}
15101514

1515+
static int64_t heif_get_position(void* userdata) {
1516+
auto stream = static_cast<File*>(userdata);
1517+
return stream->tell();
1518+
}
1519+
1520+
static int heif_read(void* data, size_t size, void* userdata) {
1521+
auto stream = static_cast<File*>(userdata);
1522+
1523+
// Special handling as read doesn't like 0 passed in for size
1524+
if (size == 0) {
1525+
return heif_error_Ok;
1526+
}
1527+
// An attempt was made to use readImpl instead to avoid memcpy, but
1528+
// unfortunately, tell() returns wrong position if readImpl is used.
1529+
String result = stream->read(size);
1530+
if (result.length() != size) {
1531+
return 1; // Failure
1532+
}
1533+
memcpy(data, result.data(), size);
1534+
return heif_error_Ok;
1535+
}
1536+
1537+
static int heif_seek(int64_t position, void* userdata) {
1538+
auto stream = static_cast<File*>(userdata);
1539+
if (!stream->seekable()) {
1540+
return 1; // Failure
1541+
}
1542+
if (stream->seek(position, SEEK_SET)) {
1543+
return heif_error_Ok;
1544+
}
1545+
return 1; // Failure
1546+
}
1547+
1548+
static heif_reader_grow_status heif_wait_for_file_size(
1549+
int64_t /*targetSize*/,
1550+
void* /*userdata*/) {
1551+
return heif_reader_grow_status_size_reached;
1552+
}
1553+
1554+
static heif_reader create_heif_reader() {
1555+
return heif_reader{
1556+
.reader_api_version = 1,
1557+
.get_position = &heif_get_position,
1558+
.read = &heif_read,
1559+
.seek = &heif_seek,
1560+
.wait_for_file_size = &heif_wait_for_file_size};
1561+
}
1562+
1563+
static struct gfxinfo *php_handle_heif(const req::ptr<File>& stream) {
1564+
struct gfxinfo *result = nullptr;
1565+
1566+
if (!stream->rewind()) {
1567+
return nullptr;
1568+
}
1569+
1570+
heif_context* ctx = heif_context_alloc();
1571+
SCOPE_EXIT {
1572+
heif_context_free(ctx);
1573+
};
1574+
1575+
heif_reader reader = create_heif_reader();
1576+
auto err = heif_context_read_from_reader(ctx, &reader, stream.get(), nullptr);
1577+
if (err.code != 0) {
1578+
raise_notice("Heif context read failed: %s", err.message);
1579+
return nullptr;
1580+
}
1581+
1582+
heif_image_handle* handle;
1583+
err = heif_context_get_primary_image_handle(ctx, &handle);
1584+
if (err.code != 0) {
1585+
// Unable to get image handle
1586+
raise_notice("Failed to get primary image handle: %s", err.message);
1587+
return nullptr;
1588+
}
1589+
1590+
SCOPE_EXIT {
1591+
heif_image_handle_release(handle);
1592+
};
1593+
1594+
result = (struct gfxinfo *)IM_CALLOC(1, sizeof(struct gfxinfo));
1595+
CHECK_ALLOC_R(result, (sizeof(struct gfxinfo)), nullptr);
1596+
1597+
result->width = heif_image_handle_get_width(handle);
1598+
result->height = heif_image_handle_get_height(handle);
1599+
// It is easier to not set bits/channels because it will require actual
1600+
// decoding of the image bitstream to get the information using libheif.
1601+
result->bits = 0;
1602+
result->channels = 0;
1603+
return result;
1604+
}
1605+
1606+
static heif_brand2 php_get_heif(const req::ptr<File>& stream) {
1607+
if (!stream->rewind()) {
1608+
return heif_unknown_brand;
1609+
}
1610+
1611+
String fileType = stream->read(12);
1612+
if (fileType.length() != 12) {
1613+
return heif_unknown_brand;
1614+
}
1615+
1616+
return heif_read_main_brand(reinterpret_cast<const uint8_t*>(
1617+
fileType.c_str()), 12);
1618+
}
1619+
15111620
/* Convert internal image_type to mime type */
15121621
static char *php_image_type_to_mime_type(int image_type) {
15131622
switch( image_type) {
@@ -1541,6 +1650,10 @@ static char *php_image_type_to_mime_type(int image_type) {
15411650
return "image/vnd.microsoft.icon";
15421651
case IMAGE_FILETYPE_WEBP:
15431652
return "image/webp";
1653+
case IMAGE_FILETYPE_AVIF:
1654+
return "image/avif";
1655+
case IMAGE_FILETYPE_HEIC:
1656+
return "image/heic";
15441657
default:
15451658
case IMAGE_FILETYPE_UNKNOWN:
15461659
return "application/octet-stream"; /* suppose binary format */
@@ -1627,6 +1740,12 @@ static int php_getimagetype(const req::ptr<File>& file) {
16271740
}
16281741

16291742
/* AFTER ALL ABOVE FAILED */
1743+
auto heifBrand = php_get_heif(file);
1744+
if (heifBrand == heif_brand2_heic || heifBrand == heif_brand2_heix) {
1745+
return IMAGE_FILETYPE_HEIC;
1746+
} else if (heifBrand == heif_brand2_avif) {
1747+
return IMAGE_FILETYPE_AVIF;
1748+
}
16301749
if (php_get_wbmp(file, nullptr, 1)) {
16311750
return IMAGE_FILETYPE_WBMP;
16321751
}
@@ -1668,6 +1787,10 @@ String HHVM_FUNCTION(image_type_to_mime_type, int64_t imagetype) {
16681787
return "image/vnd.microsoft.icon";
16691788
case IMAGE_FILETYPE_WEBP:
16701789
return "image/webp";
1790+
case IMAGE_FILETYPE_AVIF:
1791+
return "image/avif";
1792+
case IMAGE_FILETYPE_HEIC:
1793+
return "image/heic";
16711794
default:
16721795
case IMAGE_FILETYPE_UNKNOWN:
16731796
return "application/octet-stream"; /* suppose binary format */
@@ -1710,6 +1833,10 @@ Variant HHVM_FUNCTION(image_type_to_extension,
17101833
return include_dot ? String(".ico") : String("ico");
17111834
case IMAGE_FILETYPE_WEBP:
17121835
return include_dot ? String(".webp") : String("webp");
1836+
case IMAGE_FILETYPE_AVIF:
1837+
return include_dot ? String(".avif") : String("avif");
1838+
case IMAGE_FILETYPE_HEIC:
1839+
return include_dot ? String(".heic") : String("heic");
17131840
default:
17141841
return false;
17151842
}
@@ -1786,6 +1913,12 @@ Variant getImageSize(const req::ptr<File>& stream, Array& imageinfo) {
17861913
case IMAGE_FILETYPE_WEBP:
17871914
result = php_handle_webp(stream);
17881915
break;
1916+
case IMAGE_FILETYPE_AVIF:
1917+
result = php_handle_heif(stream);
1918+
break;
1919+
case IMAGE_FILETYPE_HEIC:
1920+
result = php_handle_heif(stream);
1921+
break;
17891922
default:
17901923
case IMAGE_FILETYPE_UNKNOWN:
17911924
break;
@@ -8247,6 +8380,8 @@ struct GdExtension final : Extension {
82478380
HHVM_RC_INT(IMAGETYPE_XBM, IMAGE_FILETYPE_XBM);
82488381
HHVM_RC_INT(IMAGETYPE_ICO, IMAGE_FILETYPE_ICO);
82498382
HHVM_RC_INT(IMAGETYPE_WEBP, IMAGE_FILETYPE_WEBP);
8383+
HHVM_RC_INT(IMAGETYPE_AVIF, IMAGE_FILETYPE_AVIF);
8384+
HHVM_RC_INT(IMAGETYPE_HEIC, IMAGE_FILETYPE_HEIC);
82508385
HHVM_RC_INT(IMAGETYPE_UNKNOWN, IMAGE_FILETYPE_UNKNOWN);
82518386
HHVM_RC_INT(IMAGETYPE_COUNT, IMAGE_FILETYPE_COUNT);
82528387
HHVM_RC_INT(IMAGETYPE_SWC, IMAGE_FILETYPE_SWC);

hphp/test/slow/ext_image/1791.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ function main_1791() :mixed{
2121
var_dump(image_type_to_mime_type(IMAGETYPE_WEBP));
2222
var_dump(image_type_to_mime_type(IMAGETYPE_XBM));
2323
var_dump(image_type_to_mime_type(IMAGETYPE_ICO));
24+
var_dump(image_type_to_mime_type(IMAGETYPE_AVIF));
25+
var_dump(image_type_to_mime_type(IMAGETYPE_HEIC));
2426
var_dump(image_type_to_mime_type(IMAGETYPE_UNKNOWN));
2527
var_dump(image_type_to_mime_type(IMAGETYPE_COUNT));
2628
}

hphp/test/slow/ext_image/1791.php.expect

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,5 +16,7 @@ string(18) "image/vnd.wap.wbmp"
1616
string(10) "image/webp"
1717
string(9) "image/xbm"
1818
string(24) "image/vnd.microsoft.icon"
19+
string(10) "image/avif"
20+
string(10) "image/heic"
1921
string(24) "application/octet-stream"
2022
string(24) "application/octet-stream"
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.

hphp/test/zend/good/ext/standard/tests/image/getimagesize_246x247.php.expect

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,30 @@
1-
dict(1) {
1+
dict(3) {
2+
["246x247.avif"]=>
3+
dict(5) {
4+
[0]=>
5+
int(246)
6+
[1]=>
7+
int(247)
8+
[2]=>
9+
int(19)
10+
[3]=>
11+
string(24) "width="246" height="247""
12+
["mime"]=>
13+
string(10) "image/avif"
14+
}
15+
["246x247.heic"]=>
16+
dict(5) {
17+
[0]=>
18+
int(246)
19+
[1]=>
20+
int(247)
21+
[2]=>
22+
int(20)
23+
[3]=>
24+
string(24) "width="246" height="247""
25+
["mime"]=>
26+
string(10) "image/heic"
27+
}
228
["246x247.png"]=>
329
dict(6) {
430
[0]=>

hphp/test/zend/good/ext/standard/tests/image/getimagesize_384x385.php.expect

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,30 @@
1-
dict(1) {
1+
dict(3) {
2+
["384x385.avif"]=>
3+
dict(5) {
4+
[0]=>
5+
int(384)
6+
[1]=>
7+
int(385)
8+
[2]=>
9+
int(19)
10+
[3]=>
11+
string(24) "width="384" height="385""
12+
["mime"]=>
13+
string(10) "image/avif"
14+
}
15+
["384x385.heic"]=>
16+
dict(5) {
17+
[0]=>
18+
int(384)
19+
[1]=>
20+
int(385)
21+
[2]=>
22+
int(20)
23+
[3]=>
24+
string(24) "width="384" height="385""
25+
["mime"]=>
26+
string(10) "image/heic"
27+
}
228
["384x385.png"]=>
329
dict(6) {
430
[0]=>

hphp/test/zend/good/ext/standard/tests/image/getimagesize_basic.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,10 @@
3333
"IFF image file" => "test4pix.iff",
3434

3535
"WEBP image file" => "200x100.webp",
36+
37+
"AVIF image file" => "200x100.avif",
38+
39+
"HEIC image file" => "200x100.heic",
3640
];
3741

3842
echo "*** Testing getimagesize() : basic functionality ***\n";
Binary file not shown.
Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,22 @@
11
<?hh
22
<<__EntryPoint>> function main(): void {
3+
$imagetype_filenames = dict[
4+
"GIF image file" => 'test.gif',
5+
"AVIF image file" => "test.avif",
6+
"HEIC image file" => "test.heic",
7+
];
38
$img = __DIR__ . '/test.gif';
49
$info = null;
5-
$i1 = getimagesize($img, inout $info);
610

7-
$data = file_get_contents($img);
11+
foreach ($imagetype_filenames as $key => $filename) {
12+
echo "\n-- $key ($filename) --\n";
13+
$img = __DIR__ . "/$filename";
14+
$i1 = getimagesize($img, inout $info);
815

9-
$i2 = getimagesizefromstring($data, inout $info);
10-
11-
var_dump($i1);
12-
var_dump($i2);
16+
$data = file_get_contents($img);
17+
$i2 = getimagesizefromstring($data, inout $info);
18+
var_dump($i1);
19+
var_dump($i2);
20+
};
21+
echo "===DONE===\n";
1322
}

0 commit comments

Comments
 (0)