Skip to content

Commit

Permalink
Add support for reading jpeg gain maps and converting them to AVIF. (#…
Browse files Browse the repository at this point in the history
…1565)

Needs libxml2 and AVIF_ENABLE_EXPERIMENTA_GAIN_MAP to be turned on.

The parsing code has three parts:
- MPF (Multi-Picture Format) metadata parsing, to find the offset(s) of the additional image(s) in the file. These offsets are relative fo the position of the MPF segment.
- JPEG metadata segment parsing, to find the offset of the MPF segment, in order to make the image offsets absolute (unfortunately jpeglib does not provide this information)
- XMP parsing (using libxml2) for the gain map metadata
  • Loading branch information
maryla-uc authored Sep 11, 2023
1 parent 8954b03 commit c90b094
Show file tree
Hide file tree
Showing 19 changed files with 901 additions and 30 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
in the future. Files created now might not decode in a future version.
This feature is off by default and must be enabled with the
AVIF_ENABLE_EXPERIMENTAL_GAIN_MAP compilation flag.
* Add experimental support for converting jpeg files with gain maps to AVIF
files with gain maps. Requires libxml2, and the AVIF_ENABLE_EXPERIMENTAL_GAIN_MAP
compilation flag.
* Add the headerFormat member of new type avifHeaderFormat to avifEncoder.
* Add experimental API for reading and writing "avir"-branded AVIF files
behind the compilation flag AVIF_ENABLE_EXPERIMENTAL_AVIR.
Expand Down
39 changes: 39 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ if(AVIF_LOCAL_ZLIBPNG)

set(ZLIB_LIBRARY zlibstatic)
endif()

option(AVIF_LOCAL_JPEG "Build jpeg by providing your own copy inside the ext subdir." OFF)
if(AVIF_LOCAL_JPEG)
add_subdirectory(ext/libjpeg)
Expand All @@ -139,6 +140,7 @@ if(AVIF_LOCAL_JPEG)
set(JPEG_LIBRARY jpeg PARENT_SCOPE)
endif()
endif()

option(AVIF_LOCAL_LIBYUV "Build libyuv by providing your own copy inside the ext subdir." OFF)
if(AVIF_LOCAL_LIBYUV)
set(LIB_FILENAME "${CMAKE_CURRENT_SOURCE_DIR}/ext/libyuv/build/${AVIF_LIBRARY_PREFIX}yuv${AVIF_LIBRARY_SUFFIX}")
Expand Down Expand Up @@ -188,6 +190,7 @@ if(libyuv_FOUND)
set(AVIF_PLATFORM_INCLUDES ${AVIF_PLATFORM_INCLUDES} ${LIBYUV_INCLUDE_DIR})
set(AVIF_PLATFORM_LIBRARIES ${AVIF_PLATFORM_LIBRARIES} ${LIBYUV_LIBRARY})
endif(libyuv_FOUND)

option(AVIF_LOCAL_LIBSHARPYUV "Build libsharpyuv by providing your own copy inside the ext subdir." OFF)
if(AVIF_LOCAL_LIBSHARPYUV)
set(LIB_FILENAME "${CMAKE_CURRENT_SOURCE_DIR}/ext/libwebp/build/libsharpyuv${AVIF_LIBRARY_SUFFIX}")
Expand All @@ -213,6 +216,28 @@ if(libsharpyuv_FOUND)
else(libsharpyuv_FOUND)
message(STATUS "libavif: libsharpyuv not found")
endif(libsharpyuv_FOUND)

option(AVIF_LOCAL_LIBXML2 "Build libxml2 by providing your own copy inside the ext subdir. \
libxml2 is used when AVIF_ENABLE_EXPERIMENTAL_GAIN_MAP is ON" OFF
)
if(AVIF_LOCAL_LIBXML2)
set(LIB_FILENAME
"${CMAKE_CURRENT_SOURCE_DIR}/ext/libxml2/install.libavif/lib/${AVIF_LIBRARY_PREFIX}xml2${AVIF_LIBRARY_SUFFIX}"
)
if(NOT EXISTS "${LIB_FILENAME}")
message(FATAL_ERROR "libavif: ${LIB_FILENAME} is missing, bailing out")
endif()
if("${CMAKE_SOURCE_DIR}" STREQUAL "${CMAKE_CURRENT_SOURCE_DIR}")
set(LIBXML2_INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ext/libxml2/install.libavif/include/libxml2")
set(LIBXML2_LIBRARY ${LIB_FILENAME})
else()
set(LIBXML2_INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ext/libxml2/install.libavif/include/libxml2" PARENT_SCOPE)
set(LIBXML2_LIBRARY ${LIB_FILENAME} PARENT_SCOPE)
endif()
set(LIBXML2_FOUND TRUE)
else()
find_package(LibXml2 QUIET) # not required
endif()
# ---------------------------------------------------------------------------------------

# Enable all warnings
Expand Down Expand Up @@ -637,6 +662,17 @@ if(AVIF_BUILD_APPS OR (AVIF_BUILD_TESTS AND AVIF_ENABLE_GTEST))
avif_apps PRIVATE $<TARGET_PROPERTY:avif,INTERFACE_INCLUDE_DIRECTORIES> ${PNG_PNG_INCLUDE_DIR} ${JPEG_INCLUDE_DIR}
INTERFACE apps/shared
)

if(AVIF_ENABLE_EXPERIMENTAL_GAIN_MAP)
if(LIBXML2_FOUND)
set(AVIF_ENABLE_EXPERIMENTAL_JPEG_GAIN_MAP_CONVERSION TRUE)
add_compile_definitions(AVIF_ENABLE_EXPERIMENTAL_JPEG_GAIN_MAP_CONVERSION)
target_link_libraries(avif_apps ${LIBXML2_LIBRARY})
target_include_directories(avif_apps PRIVATE ${LIBXML2_INCLUDE_DIR})
else()
message(STATUS "libavif: libxml2 not found; avifenc will ignore any gain map in jpeg files")
endif()
endif()
endif()

if(AVIF_BUILD_APPS)
Expand Down Expand Up @@ -780,6 +816,9 @@ if(WIN32)
if(AVIF_LOCAL_JPEG)
avif_set_folder_safe(jpeg "ext/libjpeg")
endif()
if(AVIF_LOCAL_LIBXML2)
avif_set_folder_safe(xml2 "ext/libxml2")
endif()
endif()

add_subdirectory(contrib)
21 changes: 20 additions & 1 deletion apps/avifenc.c
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ typedef struct
avifBool ignoreExif;
avifBool ignoreXMP;
avifBool ignoreColorProfile;
avifBool ignoreGainMap; // only relevant when compiled with AVIF_ENABLE_EXPERIMENTAL_JPEG_GAIN_MAP_CONVERSION

// This holds the output timing for image sequences. The timescale member in this struct will
// become the timescale set on avifEncoder, and the duration member will be the default duration
Expand Down Expand Up @@ -209,6 +210,10 @@ static void syntaxLong(void)
printf(" --ignore-exif : If the input file contains embedded Exif metadata, ignore it (no-op if absent)\n");
printf(" --ignore-xmp : If the input file contains embedded XMP metadata, ignore it (no-op if absent)\n");
printf(" --ignore-profile,--ignore-icc : If the input file contains an embedded color profile, ignore it (no-op if absent)\n");
#if defined(AVIF_ENABLE_EXPERIMENTAL_JPEG_GAIN_MAP_CONVERSION)
printf(" --ignore-gain-map : If the input file contains an embedded gain map, ignore it (no-op if absent)\n");
// TODO(maryla): add quality setting for the gain map.
#endif
printf(" --pasp H,V : Add pasp property (aspect ratio). H=horizontal spacing, V=vertical spacing\n");
printf(" --crop CROPX,CROPY,CROPW,CROPH : Add clap property (clean aperture), but calculated from a crop rectangle\n");
printf(" --clap WN,WD,HN,HD,HON,HOD,VON,VOD: Add clap property (clean aperture). Width, Height, HOffset, VOffset (in num/denom pairs)\n");
Expand Down Expand Up @@ -482,6 +487,7 @@ static avifBool avifInputReadImage(avifInput * input,
avifBool ignoreExif,
avifBool ignoreXMP,
avifBool allowChangingCicp,
avifBool ignoreGainMap,
avifImage * image,
const avifInputFileSettings ** settings,
uint32_t * outDepth,
Expand Down Expand Up @@ -571,6 +577,7 @@ static avifBool avifInputReadImage(avifInput * input,
ignoreExif,
ignoreXMP,
allowChangingCicp,
ignoreGainMap,
dstImage,
dstDepth,
dstSourceTiming,
Expand Down Expand Up @@ -601,6 +608,7 @@ static avifBool avifInputReadImage(avifInput * input,
ignoreExif,
ignoreXMP,
allowChangingCicp,
ignoreGainMap,
image,
settings,
outDepth,
Expand Down Expand Up @@ -895,12 +903,14 @@ static avifBool avifEncodeRestOfImageSequence(avifEncoder * encoder,

// Ignore ICC, Exif and XMP because only the metadata of the first frame is taken into
// account by the libavif API.
// Ignore gain map as it's not supported for sequences.
if (!avifInputReadImage(input,
imageIndex,
/*ignoreColorProfile=*/AVIF_TRUE,
/*ignoreExif=*/AVIF_TRUE,
/*ignoreXMP=*/AVIF_TRUE,
/*allowChangingCicp=*/AVIF_FALSE,
/*ignoreGainMap=*/AVIF_TRUE,
nextImage,
&nextSettings,
/*outDepth=*/NULL,
Expand Down Expand Up @@ -1279,6 +1289,7 @@ int main(int argc, char * argv[])
settings.ignoreExif = AVIF_FALSE;
settings.ignoreXMP = AVIF_FALSE;
settings.ignoreColorProfile = AVIF_FALSE;
settings.ignoreGainMap = AVIF_FALSE;
settings.cicpExplicitlySet = AVIF_FALSE;

avifInputFileSettings pendingSettings;
Expand Down Expand Up @@ -1684,6 +1695,10 @@ int main(int argc, char * argv[])
settings.ignoreXMP = AVIF_TRUE;
} else if (!strcmp(arg, "--ignore-profile") || !strcmp(arg, "--ignore-icc")) {
settings.ignoreColorProfile = AVIF_TRUE;
#if defined(AVIF_ENABLE_EXPERIMENTAL_JPEG_GAIN_MAP_CONVERSION)
} else if (!strcmp(arg, "--ignore-gain-map")) {
settings.ignoreGainMap = AVIF_TRUE;
#endif
} else if (!strcmp(arg, "--pasp")) {
NEXTARG();
settings.paspCount = parseU32List(settings.paspValues, arg);
Expand Down Expand Up @@ -2031,12 +2046,16 @@ int main(int argc, char * argv[])
uint32_t sourceDepth = 0;
avifBool sourceWasRGB = AVIF_FALSE;
avifAppSourceTiming firstSourceTiming;
const avifBool isImageSequence = (settings.gridDimsCount == 0) && (input.filesCount > 1);
// Gain maps are not supported for animations or layered images.
const avifBool ignoreGainMap = settings.ignoreGainMap || isImageSequence || settings.progressive;
if (!avifInputReadImage(&input,
/*imageIndex=*/0,
settings.ignoreColorProfile,
settings.ignoreExif,
settings.ignoreXMP,
/*allowChangingCicp=*/!settings.cicpExplicitlySet,
ignoreGainMap,
image,
/*settings=*/NULL, // Must use the setting for first input
&sourceDepth,
Expand Down Expand Up @@ -2276,6 +2295,7 @@ int main(int argc, char * argv[])
/*ignoreExif=*/AVIF_TRUE,
/*ignoreXMP=*/AVIF_TRUE,
/*allowChangingCicp=*/AVIF_FALSE,
settings.ignoreGainMap,
cellImage,
/*settings=*/NULL,
/*outDepth=*/NULL,
Expand Down Expand Up @@ -2327,7 +2347,6 @@ int main(int argc, char * argv[])
printf("Encoded successfully.\n");
printf(" * Color AV1 total size: %" AVIF_FMT_ZU " bytes\n", ioStats.colorOBUSize);
printf(" * Alpha AV1 total size: %" AVIF_FMT_ZU " bytes\n", ioStats.alphaOBUSize);
const avifBool isImageSequence = (settings.gridDimsCount == 0) && (input.filesCount > 1);
if (isImageSequence) {
if (settings.repetitionCount == AVIF_REPETITION_COUNT_INFINITE) {
printf(" * Repetition Count: Infinite\n");
Expand Down
Loading

0 comments on commit c90b094

Please sign in to comment.