Skip to content

Commit

Permalink
heif: add CreateCopy support (#11093)
Browse files Browse the repository at this point in the history
  • Loading branch information
bradh authored Oct 24, 2024
1 parent 62513a3 commit 58d223c
Show file tree
Hide file tree
Showing 11 changed files with 551 additions and 68 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ Supported Formats: (ro:read-only, rw:read-write, +:update, v:virtual-I/O s:subda
SIGDEM -raster- (rwv): Scaled Integer Gridded DEM .sigdem (*.sigdem)
EXR -raster- (rw+vs): Extended Dynamic Range Image File Format (*.exr)
AVIF -raster- (rwvs): AV1 Image File Format (*.avif)
HEIF -raster- (rov): ISO/IEC 23008-12:2017 High Efficiency Image File Format (*.heic)
HEIF -raster- (rwv): ISO/IEC 23008-12:2017 High Efficiency Image File Format (*.heic)
TGA -raster- (rov): TGA/TARGA Image File Format (*.tga)
OGCAPI -raster,vector- (rov): OGCAPI
STACTA -raster- (rovs): Spatio-Temporal Asset Catalog Tiled Assets (*.json)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ Supported Formats: (ro:read-only, rw:read-write, +:update, v:virtual-I/O s:subda
DAAS -raster- (ro): Airbus DS Intelligence Data As A Service driver
SIGDEM -raster- (rwv): Scaled Integer Gridded DEM .sigdem (*.sigdem)
AVIF -raster- (rwvs): AV1 Image File Format (*.avif)
HEIF -raster- (rov): ISO/IEC 23008-12:2017 High Efficiency Image File Format (*.heic)
HEIF -raster- (rwv): ISO/IEC 23008-12:2017 High Efficiency Image File Format (*.heic)
TGA -raster- (rov): TGA/TARGA Image File Format (*.tga)
OGCAPI -raster,vector- (rov): OGCAPI
STACTA -raster- (rovs): Spatio-Temporal Asset Catalog Tiled Assets (*.json)
Expand Down
1 change: 0 additions & 1 deletion autotest/gcore/misc.py
Original file line number Diff line number Diff line change
Expand Up @@ -292,7 +292,6 @@ def misc_6_internal(datatype, nBands, setDriversDone):
or datatype == gdal.GDT_UInt16
):
skip = True

if skip is False:
dirname = "tmp/tmp/tmp_%s_%d_%s" % (
drv.ShortName,
Expand Down
134 changes: 134 additions & 0 deletions autotest/gdrivers/heif.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
# SPDX-License-Identifier: MIT
###############################################################################

import array
import os
import shutil

Expand Down Expand Up @@ -47,6 +48,15 @@ def _has_uncompressed_decoding_support():
return drv and drv.GetMetadataItem("SUPPORTS_UNCOMPRESSED", "HEIF")


def _has_read_write_support_for(format):
drv = gdal.GetDriverByName("HEIF")
return (
drv
and drv.GetMetadataItem("SUPPORTS_" + format, "HEIF")
and drv.GetMetadataItem("SUPPORTS_" + format + "_WRITE", "HEIF")
)


@pytest.mark.parametrize("endianness", ["big_endian", "little_endian"])
def test_heif_exif_endian(endianness):
if not _has_hevc_decoding_support():
Expand Down Expand Up @@ -483,3 +493,127 @@ def test_identify_various(major_brand, compatible_brands, expect_success):
assert drv is None

gdal.Unlink("/vsimem/heif_header.bin")


def make_data():
ds = gdal.GetDriverByName("MEM").Create("", 300, 200, 3, gdal.GDT_Byte)

ds.GetRasterBand(1).SetRasterColorInterpretation(gdal.GCI_RedBand)
ds.GetRasterBand(2).SetRasterColorInterpretation(gdal.GCI_GreenBand)
ds.GetRasterBand(3).SetRasterColorInterpretation(gdal.GCI_BlueBand)

red_green_blue = (
([0xFF] * 100 + [0x00] * 200)
+ ([0x00] * 100 + [0xFF] * 100 + [0x00] * 100)
+ ([0x00] * 200 + [0xFF] * 100)
)
rgb_bytes = array.array("B", red_green_blue).tobytes()
for line in range(100):
ds.WriteRaster(
0, line, 300, 1, rgb_bytes, buf_type=gdal.GDT_Byte, band_list=[1, 2, 3]
)
black_white = ([0xFF] * 150 + [0x00] * 150) * 3
black_white_bytes = array.array("B", black_white).tobytes()
for line in range(100):
ds.WriteRaster(
0,
100 + line,
300,
1,
black_white_bytes,
buf_type=gdal.GDT_Byte,
band_list=[1, 2, 3],
)

assert ds.FlushCache() == gdal.CE_None
return ds


def make_data_with_alpha():
ds = gdal.GetDriverByName("MEM").Create("", 300, 200, 4, gdal.GDT_Byte)

ds.GetRasterBand(1).SetRasterColorInterpretation(gdal.GCI_RedBand)
ds.GetRasterBand(2).SetRasterColorInterpretation(gdal.GCI_GreenBand)
ds.GetRasterBand(3).SetRasterColorInterpretation(gdal.GCI_BlueBand)
ds.GetRasterBand(4).SetRasterColorInterpretation(gdal.GCI_AlphaBand)

red_green_blue_alpha = (
([0xFF] * 100 + [0x00] * 200)
+ ([0x00] * 100 + [0xFF] * 100 + [0x00] * 100)
+ ([0x00] * 200 + [0xFF] * 100)
+ ([0x7F] * 150 + [0xFF] * 150)
)
rgba_bytes = array.array("B", red_green_blue_alpha).tobytes()
for line in range(100):
ds.WriteRaster(
0, line, 300, 1, rgba_bytes, buf_type=gdal.GDT_Byte, band_list=[1, 2, 3, 4]
)
black_white = ([0xFF] * 150 + [0x00] * 150) * 4
black_white_bytes = array.array("B", black_white).tobytes()
for line in range(100):
ds.WriteRaster(
0,
100 + line,
300,
1,
black_white_bytes,
buf_type=gdal.GDT_Byte,
band_list=[1, 2, 3, 4],
)

assert ds.FlushCache() == gdal.CE_None
return ds


heif_codecs = ["AV1", "HEVC", "JPEG", "JPEG2000", "UNCOMPRESSED"]


@pytest.mark.parametrize("codec", heif_codecs)
def test_heif_create_copy(tmp_path, codec):
if not _has_read_write_support_for(codec):
pytest.skip()
tempfile = str(tmp_path / ("test_heif_create_copy_" + codec + ".hif"))
input_ds = make_data()

drv = gdal.GetDriverByName("HEIF")
result_ds = drv.CreateCopy(tempfile, input_ds, options=["CODEC=" + codec])

result_ds = None

result_ds = gdal.Open(tempfile)

assert result_ds


@pytest.mark.parametrize("codec", heif_codecs)
def test_heif_create_copy_with_alpha(tmp_path, codec):
if not _has_read_write_support_for(codec):
pytest.skip()
tempfile = str(tmp_path / ("test_heif_create_copy_" + codec + "_alpha.hif"))
input_ds = make_data_with_alpha()

drv = gdal.GetDriverByName("HEIF")
result_ds = drv.CreateCopy(tempfile, input_ds, options=["CODEC=" + codec])

result_ds = None

result_ds = gdal.Open(tempfile)

assert result_ds


def test_heif_create_copy_defaults(tmp_path):
if not _has_read_write_support_for("HEVC"):
pytest.skip()
tempfile = str(tmp_path / "test_heif_create_copy.hif")
input_ds = make_data()

drv = gdal.GetDriverByName("HEIF")

result_ds = drv.CreateCopy(tempfile, input_ds, options=[])

result_ds = None

result_ds = gdal.Open(tempfile)

assert result_ds
9 changes: 7 additions & 2 deletions doc/source/drivers/raster/heif.rst
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
.. _raster.heif:

================================================================================
HEIF / HEIC -- ISO/IEC 23008-12:2017 High Efficiency Image File Format
HEIF / HEIC -- ISO/IEC 23008-12 High Efficiency Image File Format
================================================================================

.. versionadded:: 3.2
Expand All @@ -18,11 +18,14 @@ iOS 11 can generate such files.

libheif 1.4 or later is needed to support images with more than 8-bits per channel.

Later versions of libheif may also support one or more of AVIF (AV1 in HEIF), JPEG, JPEG 2000 and
uncompressed images depending on compile-time options.

The driver can read EXIF metadata (exposed in the ``EXIF`` metadata domain)
and XMP metadata (exposed in the ``xml:XMP`` metadata domain)

The driver will expose the thumbnail as an overview (when its number of bands
matches the one of the full resolution image)
matches the number of bands in the full resolution image).

If a file contains several top-level images, they will be exposed as GDAL subdatasets.

Expand All @@ -41,6 +44,8 @@ Driver capabilities

.. supports_virtualio:: if libheif >= 1.4

.. supports_createcopy::


Built hints on Windows
----------------------
Expand Down
2 changes: 1 addition & 1 deletion frmts/heif/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
add_gdal_driver(TARGET gdal_HEIF
SOURCES heifdataset.cpp
SOURCES heifdataset.cpp heifdataset.h heifdatasetcreatecopy.cpp
CORE_SOURCES heifdrivercore.cpp
PLUGIN_CAPABLE
NO_SHARED_SYMBOL_WITH_CORE)
Expand Down
103 changes: 42 additions & 61 deletions frmts/heif/heifdataset.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,69 +9,10 @@
* SPDX-License-Identifier: MIT
****************************************************************************/

#include "gdal_pam.h"
#include "ogr_spatialref.h"

#include "include_libheif.h"

#include "heifdrivercore.h"

#include <vector>
#include "heifdataset.h"

extern "C" void CPL_DLL GDALRegister_HEIF();

// g++ -fPIC -std=c++11 frmts/heif/heifdataset.cpp -Iport -Igcore -Iogr
// -Iogr/ogrsf_frmts -I$HOME/heif/install-ubuntu-18.04/include
// -L$HOME/heif/install-ubuntu-18.04/lib -lheif -shared -o gdal_HEIF.so -L.
// -lgdal

/************************************************************************/
/* GDALHEIFDataset */
/************************************************************************/

class GDALHEIFDataset final : public GDALPamDataset
{
friend class GDALHEIFRasterBand;

heif_context *m_hCtxt = nullptr;
heif_image_handle *m_hImageHandle = nullptr;
#ifndef LIBHEIF_SUPPORTS_TILES
heif_image *m_hImage = nullptr;
#endif
bool m_bFailureDecoding = false;
std::vector<std::unique_ptr<GDALHEIFDataset>> m_apoOvrDS{};
bool m_bIsThumbnail = false;

#ifdef LIBHEIF_SUPPORTS_TILES
heif_image_tiling m_tiling;
#endif

#ifdef HAS_CUSTOM_FILE_READER
heif_reader m_oReader{};
VSILFILE *m_fpL = nullptr;
vsi_l_offset m_nSize = 0;

static int64_t GetPositionCbk(void *userdata);
static int ReadCbk(void *data, size_t size, void *userdata);
static int SeekCbk(int64_t position, void *userdata);
static enum heif_reader_grow_status WaitForFileSizeCbk(int64_t target_size,
void *userdata);
#endif

bool Init(GDALOpenInfo *poOpenInfo);
void ReadMetadata();
void OpenThumbnails();

public:
GDALHEIFDataset();
~GDALHEIFDataset();

static GDALDataset *OpenHEIF(GDALOpenInfo *poOpenInfo);
#if LIBHEIF_NUMERIC_VERSION >= BUILD_LIBHEIF_VERSION(1, 12, 0)
static GDALDataset *OpenAVIF(GDALOpenInfo *poOpenInfo);
#endif
};

/************************************************************************/
/* GDALHEIFRasterBand */
/************************************************************************/
Expand Down Expand Up @@ -590,7 +531,9 @@ static int HEIFDriverIdentify(GDALOpenInfo *poOpenInfo)
GDALDataset *GDALHEIFDataset::OpenHEIF(GDALOpenInfo *poOpenInfo)
{
if (!HEIFDriverIdentify(poOpenInfo))
{
return nullptr;
}
if (poOpenInfo->eAccess == GA_Update)
{
CPLError(CE_Failure, CPLE_NotSupported,
Expand Down Expand Up @@ -850,36 +793,70 @@ void GDALRegister_HEIF()
{
poDriver->SetMetadataItem("SUPPORTS_AVC", "YES", "HEIF");
}
if (heif_have_encoder_for_format(heif_compression_AVC))
{
poDriver->SetMetadataItem("SUPPORTS_AVC_WRITE", "YES", "HEIF");
}
// If the AVIF dedicated driver is not available, register an AVIF driver,
// called AVIF_HEIF, based on libheif, if it has AV1 decoding capabilities.
if (heif_have_decoder_for_format(heif_compression_AV1))
{
poDriver->SetMetadataItem("SUPPORTS_AVIF", "YES", "HEIF");
poDriver->SetMetadataItem("SUPPORTS_AV1", "YES", "HEIF");
}
if (heif_have_encoder_for_format(heif_compression_AV1))
{
poDriver->SetMetadataItem("SUPPORTS_AV1_WRITE", "YES", "HEIF");
}
if (heif_have_decoder_for_format(heif_compression_HEVC))
{
poDriver->SetMetadataItem("SUPPORTS_HEVC", "YES", "HEIF");
}
if (heif_have_encoder_for_format(heif_compression_HEVC))
{
poDriver->SetMetadataItem("SUPPORTS_HEVC_WRITE", "YES", "HEIF");
}
if (heif_have_decoder_for_format(heif_compression_JPEG))
{
poDriver->SetMetadataItem("SUPPORTS_JPEG", "YES", "HEIF");
}
if (heif_have_encoder_for_format(heif_compression_JPEG))
{
poDriver->SetMetadataItem("SUPPORTS_JPEG_WRITE", "YES", "HEIF");
}
if (heif_have_decoder_for_format(heif_compression_JPEG2000))
{
poDriver->SetMetadataItem("SUPPORTS_JPEG2000", "YES", "HEIF");
}
if (heif_have_encoder_for_format(heif_compression_JPEG2000))
{
poDriver->SetMetadataItem("SUPPORTS_JPEG2000_WRITE", "YES", "HEIF");
}
if (heif_have_decoder_for_format(heif_compression_HTJ2K))
{
poDriver->SetMetadataItem("SUPPORTS_JPEG2000_HT", "YES", "HEIF");
poDriver->SetMetadataItem("SUPPORTS_HTJ2K", "YES", "HEIF");
}
if (heif_have_encoder_for_format(heif_compression_HTJ2K))
{
poDriver->SetMetadataItem("SUPPORTS_HTJ2K_WRITE", "YES", "HEIF");
}
if (heif_have_decoder_for_format(heif_compression_uncompressed))
{
poDriver->SetMetadataItem("SUPPORTS_UNCOMPRESSED", "YES", "HEIF");
}
if (heif_have_encoder_for_format(heif_compression_uncompressed))
{
poDriver->SetMetadataItem("SUPPORTS_UNCOMPRESSED_WRITE", "YES",
"HEIF");
}
if (heif_have_decoder_for_format(heif_compression_VVC))
{
poDriver->SetMetadataItem("SUPPORTS_VVC", "YES", "HEIF");
}
if (heif_have_encoder_for_format(heif_compression_VVC))
{
poDriver->SetMetadataItem("SUPPORTS_VVC_WRITE", "YES", "HEIF");
}
#else
// Anything that old probably supports only HEVC
poDriver->SetMetadataItem("SUPPORTS_HEVC", "YES", "HEIF");
Expand All @@ -888,6 +865,10 @@ void GDALRegister_HEIF()
poDriver->SetMetadataItem("SUPPORTS_TILES", "YES", "HEIF");
#endif
poDriver->pfnOpen = GDALHEIFDataset::OpenHEIF;

#ifdef HAS_CUSTOM_FILE_WRITER
poDriver->pfnCreateCopy = GDALHEIFDataset::CreateCopy;
#endif
poDM->RegisterDriver(poDriver);
}

Expand Down
Loading

0 comments on commit 58d223c

Please sign in to comment.