Add golden tests for gain maps. (#1578)

diff --git a/.github/workflows/ci-linux-golden-tests.yml b/.github/workflows/ci-linux-golden-tests.yml
index 7426f85..6722994 100644
--- a/.github/workflows/ci-linux-golden-tests.yml
+++ b/.github/workflows/ci-linux-golden-tests.yml
@@ -29,11 +29,12 @@
       uses: actions/cache@v3
       with:
         path: ext
-        key: ${{ runner.os }}-golden-tests-${{ hashFiles('ext/aom.cmd', 'ext/libyuv.cmd', 'ext/mp4box.sh') }}
+        key: ${{ runner.os }}-golden-tests-${{ hashFiles('ext/aom.cmd', 'ext/libyuv.cmd', 'ext/libxml2.cmd', 'ext/mp4box.sh') }}
     - name: Setup cmake
       uses: jwlawson/actions-setup-cmake@187efd9581ed20ee4e03c0dfb9ac2cd5896c4835
       with:
-        cmake-version: '3.13.x'
+        # CMake 3.18 or higher is required for libxml2
+        cmake-version: '3.18.x'
     - name: Print cmake version
       run: cmake --version
     - uses: ilammy/setup-nasm@v1
@@ -48,6 +49,10 @@
       if: steps.cache-ext.outputs.cache-hit != 'true'
       working-directory: ./ext
       run: bash -e libyuv.cmd
+    - name: Build libxml2
+      if: steps.cache-ext.outputs.cache-hit != 'true'
+      working-directory: ./ext
+      run: bash -e libxml2.cmd
     - name: Build mp4box
       if: steps.cache-ext.outputs.cache-hit != 'true'
       working-directory: ./ext
@@ -62,6 +67,8 @@
         -DAVIF_CODEC_AOM=ON -DAVIF_LOCAL_AOM=ON
         -DAVIF_LOCAL_LIBYUV=ON
         -DAVIF_BUILD_EXAMPLES=ON -DAVIF_BUILD_APPS=ON
+        -DAVIF_ENABLE_EXPERIMENTAL_GAIN_MAP=ON
+        -DAVIF_LOCAL_LIBXML2=ON
         -DAVIF_BUILD_TESTS=ON -DAVIF_ENABLE_GOLDEN_TESTS=ON
         -DAVIF_ENABLE_GTEST=OFF
         -DGOLDEN_TESTS_OUTPUT_DIR=${{ runner.temp	}}/golden_tests
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index 7f13d95..12f2ead 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -291,6 +291,13 @@
                  COMMAND bash ${CMAKE_CURRENT_SOURCE_DIR}/test_cmd_enc_boxes_golden.sh ${CMAKE_BINARY_DIR} ${MP4BOX_DIR}
                          ${CMAKE_CURRENT_SOURCE_DIR}/data ${GOLDEN_TESTS_OUTPUT_DIR}
         )
+
+        if(AVIF_ENABLE_EXPERIMENTAL_JPEG_GAIN_MAP_CONVERSION)
+            add_test(NAME test_cmd_enc_gainmap_boxes_golden
+                    COMMAND bash ${CMAKE_CURRENT_SOURCE_DIR}/test_cmd_enc_gainmap_boxes_golden.sh ${CMAKE_BINARY_DIR} ${MP4BOX_DIR}
+                            ${CMAKE_CURRENT_SOURCE_DIR}/data ${GOLDEN_TESTS_OUTPUT_DIR}
+            )
+        endif()
     endif()
 
     if(NOT AVIF_CODEC_AOM OR NOT AVIF_CODEC_AOM_ENCODE)
diff --git a/tests/data/goldens/paris_exif_xmp_gainmap_bigendian.jpg.avif.xml b/tests/data/goldens/paris_exif_xmp_gainmap_bigendian.jpg.avif.xml
new file mode 100644
index 0000000..2b6efab
--- /dev/null
+++ b/tests/data/goldens/paris_exif_xmp_gainmap_bigendian.jpg.avif.xml
@@ -0,0 +1,113 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ISOBaseMediaFileTrace>
+<!--MP4Box dump trace-->
+<IsoMediaFile xmlns="urn:mpeg:isobmff:schema:file:2016" Name="paris_exif_xmp_gainmap_bigendian.jpg.avif">
+<FileTypeBox Size="32" Type="ftyp" Specification="p12" Container="file otyp" MajorBrand="avif" MinorVersion="0">
+<BrandEntry AlternateBrand="avif"/>
+<BrandEntry AlternateBrand="mif1"/>
+<BrandEntry AlternateBrand="miaf"/>
+<BrandEntry AlternateBrand="MA1B"/>
+</FileTypeBox>
+<MetaBox Size="573" Type="meta" Version="0" Flags="0" Specification="p12" Container="file moov trak moof traf udta" >
+<HandlerBox Size="40" Type="hdlr" Version="0" Flags="0" Specification="p12" Container="mdia meta minf" hdlrType="pict" Name="libavif" reserved1="0" reserved2="data:application/octet-string,000000000000000000000000">
+</HandlerBox>
+<PrimaryItemBox Size="14" Type="pitm" Version="0" Flags="0" Specification="p12" Container="meta" item_ID="1">
+</PrimaryItemBox>
+<ItemLocationBox Size="86" Type="iloc" Version="0" Flags="0" Specification="p12" Container="meta" offset_size="4" length_size="4" base_offset_size="0" index_size="0">
+<ItemLocationEntry item_ID="1" data_reference_index="0" base_offset="0" construction_method="0">
+<ItemExtentEntry extent_offset="REDACTED" extent_length="REDACTED" extent_index="0" />
+</ItemLocationEntry>
+<ItemLocationEntry item_ID="2" data_reference_index="0" base_offset="0" construction_method="0">
+<ItemExtentEntry extent_offset="REDACTED" extent_length="REDACTED" extent_index="0" />
+</ItemLocationEntry>
+<ItemLocationEntry item_ID="3" data_reference_index="0" base_offset="0" construction_method="0">
+<ItemExtentEntry extent_offset="REDACTED" extent_length="REDACTED" extent_index="0" />
+</ItemLocationEntry>
+<ItemLocationEntry item_ID="4" data_reference_index="0" base_offset="0" construction_method="0">
+<ItemExtentEntry extent_offset="REDACTED" extent_length="REDACTED" extent_index="0" />
+</ItemLocationEntry>
+<ItemLocationEntry item_ID="5" data_reference_index="0" base_offset="0" construction_method="0">
+<ItemExtentEntry extent_offset="REDACTED" extent_length="REDACTED" extent_index="0" />
+</ItemLocationEntry>
+</ItemLocationBox>
+<ItemInfoBox Size="159" Type="iinf" Version="0" Flags="0" Specification="p12" Container="meta" >
+<ItemInfoEntryBox Size="26" Type="infe" Version="2" Flags="0" Specification="p12" Container="iinf" item_ID="1" item_protection_index="0" item_name="Color" content_type="(null)" content_encoding="(null)" item_type="av01">
+</ItemInfoEntryBox>
+<ItemInfoEntryBox Size="25" Type="infe" Version="2" Flags="0" Specification="p12" Container="iinf" item_ID="2" item_protection_index="0" item_name="GMap" content_type="(null)" content_encoding="(null)" item_type="tmap">
+</ItemInfoEntryBox>
+<ItemInfoEntryBox Size="25" Type="infe" Version="2" Flags="1" Specification="p12" Container="iinf" item_ID="3" item_protection_index="0" item_name="GMap" content_type="(null)" content_encoding="(null)" item_type="av01">
+</ItemInfoEntryBox>
+<ItemInfoEntryBox Size="25" Type="infe" Version="2" Flags="0" Specification="p12" Container="iinf" item_ID="4" item_protection_index="0" item_name="Exif" content_type="(null)" content_encoding="(null)" item_type="Exif">
+</ItemInfoEntryBox>
+<ItemInfoEntryBox Size="44" Type="infe" Version="2" Flags="0" Specification="p12" Container="iinf" item_ID="5" item_protection_index="0" item_name="XMP" content_type="application/rdf+xml" content_encoding="(null)" item_type="mime">
+</ItemInfoEntryBox>
+</ItemInfoBox>
+<ItemReferenceBox Size="56" Type="iref" Version="0" Flags="0" Specification="p12" Container="meta" >
+<ItemReferenceBox Size="16" Type="dimg" Specification="p12" Container="iref" from_item_id="2">
+<ItemReferenceBoxEntry ItemID="1"/>
+<ItemReferenceBoxEntry ItemID="3"/>
+</ItemReferenceBox>
+<ItemReferenceBox Size="14" Type="cdsc" Specification="p12" Container="iref" from_item_id="4">
+<ItemReferenceBoxEntry ItemID="1"/>
+</ItemReferenceBox>
+<ItemReferenceBox Size="14" Type="cdsc" Specification="p12" Container="iref" from_item_id="5">
+<ItemReferenceBoxEntry ItemID="1"/>
+</ItemReferenceBox>
+</ItemReferenceBox>
+<ItemPropertiesBox Size="170" Type="iprp" Specification="iff" Container="meta" >
+<ItemPropertyContainerBox Size="126" Type="ipco" Specification="iff" Container="iprp" >
+<ImageSpatialExtentsPropertyBox Size="20" Type="ispe" Version="0" Flags="0" Specification="iff" Container="ipco" image_width="403" image_height="302">
+</ImageSpatialExtentsPropertyBox>
+<PixelInformationPropertyBox Size="16" Type="pixi" Version="0" Flags="0" Specification="iff" Container="ipco" >
+<BitPerChannel bits_per_channel="8"/>
+<BitPerChannel bits_per_channel="8"/>
+<BitPerChannel bits_per_channel="8"/>
+</PixelInformationPropertyBox>
+<AV1ConfigurationBox>
+<AV1Config version="1" profile="0" level_idx0="0" tier="0" high_bitdepth="0" twelve_bit="0" monochrome="0" chroma_subsampling_x="1" chroma_subsampling_y="1" chroma_sample_position="0" initial_presentation_delay="1" OBUs_count="0">
+</AV1Config>
+</AV1ConfigurationBox>
+<ColourInformationBox Size="19" Type="colr" Specification="iff" Container="video_sample_entry ipco encv resv" colour_type="nclx" colour_primaries="1" transfer_characteristics="13" matrix_coefficients="6" full_range_flag="1">
+</ColourInformationBox>
+<ImageSpatialExtentsPropertyBox Size="20" Type="ispe" Version="0" Flags="0" Specification="iff" Container="ipco" image_width="512" image_height="384">
+</ImageSpatialExtentsPropertyBox>
+<AV1ConfigurationBox>
+<AV1Config version="1" profile="1" level_idx0="1" tier="0" high_bitdepth="0" twelve_bit="0" monochrome="0" chroma_subsampling_x="0" chroma_subsampling_y="0" chroma_sample_position="0" initial_presentation_delay="1" OBUs_count="0">
+</AV1Config>
+</AV1ConfigurationBox>
+<ColourInformationBox Size="19" Type="colr" Specification="iff" Container="video_sample_entry ipco encv resv" colour_type="nclx" colour_primaries="2" transfer_characteristics="2" matrix_coefficients="2" full_range_flag="1">
+</ColourInformationBox>
+</ItemPropertyContainerBox>
+<ItemPropertyAssociationBox Size="36" Type="ipma" Version="0" Flags="0" Specification="iff" Container="iprp" entry_count="3">
+<AssociationEntry item_ID="1" association_count="4">
+<Property index="1" essential="0"/>
+<Property index="2" essential="0"/>
+<Property index="3" essential="1"/>
+<Property index="4" essential="0"/>
+</AssociationEntry>
+<AssociationEntry item_ID="2" association_count="3">
+<Property index="1" essential="0"/>
+<Property index="2" essential="0"/>
+<Property index="4" essential="0"/>
+</AssociationEntry>
+<AssociationEntry item_ID="3" association_count="4">
+<Property index="5" essential="0"/>
+<Property index="2" essential="0"/>
+<Property index="6" essential="1"/>
+<Property index="7" essential="0"/>
+</AssociationEntry>
+</ItemPropertyAssociationBox>
+</ItemPropertiesBox>
+<GroupListBox Size="36" Type="grpl" Specification="iff" Container="meta" >
+<EntityToGroupTypeBox Size="28" Type="altr" Version="0" Flags="0" Specification="iff" Container="grpl" group_id="1">
+<EntityToGroupTypeBoxEntry EntityID="2"/>
+<EntityToGroupTypeBoxEntry EntityID="1"/>
+</EntityToGroupTypeBox>
+</GroupListBox>
+</MetaBox>
+<MediaDataBox Size="REDACTED" Type="mdat" Specification="p12" Container="file" dataSize="REDACTED">
+</MediaDataBox>
+</IsoMediaFile>
+<Tracks>
+</Tracks>
+</ISOBaseMediaFileTrace>
diff --git a/tests/data/goldens/paris_exif_xmp_gainmap_bigendian_ignore.jpg.avif.xml b/tests/data/goldens/paris_exif_xmp_gainmap_bigendian_ignore.jpg.avif.xml
new file mode 100644
index 0000000..2344130
--- /dev/null
+++ b/tests/data/goldens/paris_exif_xmp_gainmap_bigendian_ignore.jpg.avif.xml
@@ -0,0 +1,74 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ISOBaseMediaFileTrace>
+<!--MP4Box dump trace-->
+<IsoMediaFile xmlns="urn:mpeg:isobmff:schema:file:2016" Name="paris_exif_xmp_gainmap_bigendian_ignore.jpg.avif">
+<FileTypeBox Size="32" Type="ftyp" Specification="p12" Container="file otyp" MajorBrand="avif" MinorVersion="0">
+<BrandEntry AlternateBrand="avif"/>
+<BrandEntry AlternateBrand="mif1"/>
+<BrandEntry AlternateBrand="miaf"/>
+<BrandEntry AlternateBrand="MA1B"/>
+</FileTypeBox>
+<MetaBox Size="379" Type="meta" Version="0" Flags="0" Specification="p12" Container="file moov trak moof traf udta" >
+<HandlerBox Size="40" Type="hdlr" Version="0" Flags="0" Specification="p12" Container="mdia meta minf" hdlrType="pict" Name="libavif" reserved1="0" reserved2="data:application/octet-string,000000000000000000000000">
+</HandlerBox>
+<PrimaryItemBox Size="14" Type="pitm" Version="0" Flags="0" Specification="p12" Container="meta" item_ID="1">
+</PrimaryItemBox>
+<ItemLocationBox Size="58" Type="iloc" Version="0" Flags="0" Specification="p12" Container="meta" offset_size="4" length_size="4" base_offset_size="0" index_size="0">
+<ItemLocationEntry item_ID="1" data_reference_index="0" base_offset="0" construction_method="0">
+<ItemExtentEntry extent_offset="REDACTED" extent_length="REDACTED" extent_index="0" />
+</ItemLocationEntry>
+<ItemLocationEntry item_ID="2" data_reference_index="0" base_offset="0" construction_method="0">
+<ItemExtentEntry extent_offset="REDACTED" extent_length="REDACTED" extent_index="0" />
+</ItemLocationEntry>
+<ItemLocationEntry item_ID="3" data_reference_index="0" base_offset="0" construction_method="0">
+<ItemExtentEntry extent_offset="REDACTED" extent_length="REDACTED" extent_index="0" />
+</ItemLocationEntry>
+</ItemLocationBox>
+<ItemInfoBox Size="109" Type="iinf" Version="0" Flags="0" Specification="p12" Container="meta" >
+<ItemInfoEntryBox Size="26" Type="infe" Version="2" Flags="0" Specification="p12" Container="iinf" item_ID="1" item_protection_index="0" item_name="Color" content_type="(null)" content_encoding="(null)" item_type="av01">
+</ItemInfoEntryBox>
+<ItemInfoEntryBox Size="25" Type="infe" Version="2" Flags="0" Specification="p12" Container="iinf" item_ID="2" item_protection_index="0" item_name="Exif" content_type="(null)" content_encoding="(null)" item_type="Exif">
+</ItemInfoEntryBox>
+<ItemInfoEntryBox Size="44" Type="infe" Version="2" Flags="0" Specification="p12" Container="iinf" item_ID="3" item_protection_index="0" item_name="XMP" content_type="application/rdf+xml" content_encoding="(null)" item_type="mime">
+</ItemInfoEntryBox>
+</ItemInfoBox>
+<ItemReferenceBox Size="40" Type="iref" Version="0" Flags="0" Specification="p12" Container="meta" >
+<ItemReferenceBox Size="14" Type="cdsc" Specification="p12" Container="iref" from_item_id="2">
+<ItemReferenceBoxEntry ItemID="1"/>
+</ItemReferenceBox>
+<ItemReferenceBox Size="14" Type="cdsc" Specification="p12" Container="iref" from_item_id="3">
+<ItemReferenceBoxEntry ItemID="1"/>
+</ItemReferenceBox>
+</ItemReferenceBox>
+<ItemPropertiesBox Size="106" Type="iprp" Specification="iff" Container="meta" >
+<ItemPropertyContainerBox Size="75" Type="ipco" Specification="iff" Container="iprp" >
+<ImageSpatialExtentsPropertyBox Size="20" Type="ispe" Version="0" Flags="0" Specification="iff" Container="ipco" image_width="403" image_height="302">
+</ImageSpatialExtentsPropertyBox>
+<PixelInformationPropertyBox Size="16" Type="pixi" Version="0" Flags="0" Specification="iff" Container="ipco" >
+<BitPerChannel bits_per_channel="8"/>
+<BitPerChannel bits_per_channel="8"/>
+<BitPerChannel bits_per_channel="8"/>
+</PixelInformationPropertyBox>
+<AV1ConfigurationBox>
+<AV1Config version="1" profile="0" level_idx0="0" tier="0" high_bitdepth="0" twelve_bit="0" monochrome="0" chroma_subsampling_x="1" chroma_subsampling_y="1" chroma_sample_position="0" initial_presentation_delay="1" OBUs_count="0">
+</AV1Config>
+</AV1ConfigurationBox>
+<ColourInformationBox Size="19" Type="colr" Specification="iff" Container="video_sample_entry ipco encv resv" colour_type="nclx" colour_primaries="1" transfer_characteristics="13" matrix_coefficients="6" full_range_flag="1">
+</ColourInformationBox>
+</ItemPropertyContainerBox>
+<ItemPropertyAssociationBox Size="23" Type="ipma" Version="0" Flags="0" Specification="iff" Container="iprp" entry_count="1">
+<AssociationEntry item_ID="1" association_count="4">
+<Property index="1" essential="0"/>
+<Property index="2" essential="0"/>
+<Property index="3" essential="1"/>
+<Property index="4" essential="0"/>
+</AssociationEntry>
+</ItemPropertyAssociationBox>
+</ItemPropertiesBox>
+</MetaBox>
+<MediaDataBox Size="REDACTED" Type="mdat" Specification="p12" Container="file" dataSize="REDACTED">
+</MediaDataBox>
+</IsoMediaFile>
+<Tracks>
+</Tracks>
+</ISOBaseMediaFileTrace>
diff --git a/tests/data/goldens/paris_exif_xmp_gainmap_littleendian.jpg.avif.xml b/tests/data/goldens/paris_exif_xmp_gainmap_littleendian.jpg.avif.xml
new file mode 100644
index 0000000..7136636
--- /dev/null
+++ b/tests/data/goldens/paris_exif_xmp_gainmap_littleendian.jpg.avif.xml
@@ -0,0 +1,113 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ISOBaseMediaFileTrace>
+<!--MP4Box dump trace-->
+<IsoMediaFile xmlns="urn:mpeg:isobmff:schema:file:2016" Name="paris_exif_xmp_gainmap_littleendian.jpg.avif">
+<FileTypeBox Size="32" Type="ftyp" Specification="p12" Container="file otyp" MajorBrand="avif" MinorVersion="0">
+<BrandEntry AlternateBrand="avif"/>
+<BrandEntry AlternateBrand="mif1"/>
+<BrandEntry AlternateBrand="miaf"/>
+<BrandEntry AlternateBrand="MA1B"/>
+</FileTypeBox>
+<MetaBox Size="573" Type="meta" Version="0" Flags="0" Specification="p12" Container="file moov trak moof traf udta" >
+<HandlerBox Size="40" Type="hdlr" Version="0" Flags="0" Specification="p12" Container="mdia meta minf" hdlrType="pict" Name="libavif" reserved1="0" reserved2="data:application/octet-string,000000000000000000000000">
+</HandlerBox>
+<PrimaryItemBox Size="14" Type="pitm" Version="0" Flags="0" Specification="p12" Container="meta" item_ID="1">
+</PrimaryItemBox>
+<ItemLocationBox Size="86" Type="iloc" Version="0" Flags="0" Specification="p12" Container="meta" offset_size="4" length_size="4" base_offset_size="0" index_size="0">
+<ItemLocationEntry item_ID="1" data_reference_index="0" base_offset="0" construction_method="0">
+<ItemExtentEntry extent_offset="REDACTED" extent_length="REDACTED" extent_index="0" />
+</ItemLocationEntry>
+<ItemLocationEntry item_ID="2" data_reference_index="0" base_offset="0" construction_method="0">
+<ItemExtentEntry extent_offset="REDACTED" extent_length="REDACTED" extent_index="0" />
+</ItemLocationEntry>
+<ItemLocationEntry item_ID="3" data_reference_index="0" base_offset="0" construction_method="0">
+<ItemExtentEntry extent_offset="REDACTED" extent_length="REDACTED" extent_index="0" />
+</ItemLocationEntry>
+<ItemLocationEntry item_ID="4" data_reference_index="0" base_offset="0" construction_method="0">
+<ItemExtentEntry extent_offset="REDACTED" extent_length="REDACTED" extent_index="0" />
+</ItemLocationEntry>
+<ItemLocationEntry item_ID="5" data_reference_index="0" base_offset="0" construction_method="0">
+<ItemExtentEntry extent_offset="REDACTED" extent_length="REDACTED" extent_index="0" />
+</ItemLocationEntry>
+</ItemLocationBox>
+<ItemInfoBox Size="159" Type="iinf" Version="0" Flags="0" Specification="p12" Container="meta" >
+<ItemInfoEntryBox Size="26" Type="infe" Version="2" Flags="0" Specification="p12" Container="iinf" item_ID="1" item_protection_index="0" item_name="Color" content_type="(null)" content_encoding="(null)" item_type="av01">
+</ItemInfoEntryBox>
+<ItemInfoEntryBox Size="25" Type="infe" Version="2" Flags="0" Specification="p12" Container="iinf" item_ID="2" item_protection_index="0" item_name="GMap" content_type="(null)" content_encoding="(null)" item_type="tmap">
+</ItemInfoEntryBox>
+<ItemInfoEntryBox Size="25" Type="infe" Version="2" Flags="1" Specification="p12" Container="iinf" item_ID="3" item_protection_index="0" item_name="GMap" content_type="(null)" content_encoding="(null)" item_type="av01">
+</ItemInfoEntryBox>
+<ItemInfoEntryBox Size="25" Type="infe" Version="2" Flags="0" Specification="p12" Container="iinf" item_ID="4" item_protection_index="0" item_name="Exif" content_type="(null)" content_encoding="(null)" item_type="Exif">
+</ItemInfoEntryBox>
+<ItemInfoEntryBox Size="44" Type="infe" Version="2" Flags="0" Specification="p12" Container="iinf" item_ID="5" item_protection_index="0" item_name="XMP" content_type="application/rdf+xml" content_encoding="(null)" item_type="mime">
+</ItemInfoEntryBox>
+</ItemInfoBox>
+<ItemReferenceBox Size="56" Type="iref" Version="0" Flags="0" Specification="p12" Container="meta" >
+<ItemReferenceBox Size="16" Type="dimg" Specification="p12" Container="iref" from_item_id="2">
+<ItemReferenceBoxEntry ItemID="1"/>
+<ItemReferenceBoxEntry ItemID="3"/>
+</ItemReferenceBox>
+<ItemReferenceBox Size="14" Type="cdsc" Specification="p12" Container="iref" from_item_id="4">
+<ItemReferenceBoxEntry ItemID="1"/>
+</ItemReferenceBox>
+<ItemReferenceBox Size="14" Type="cdsc" Specification="p12" Container="iref" from_item_id="5">
+<ItemReferenceBoxEntry ItemID="1"/>
+</ItemReferenceBox>
+</ItemReferenceBox>
+<ItemPropertiesBox Size="170" Type="iprp" Specification="iff" Container="meta" >
+<ItemPropertyContainerBox Size="126" Type="ipco" Specification="iff" Container="iprp" >
+<ImageSpatialExtentsPropertyBox Size="20" Type="ispe" Version="0" Flags="0" Specification="iff" Container="ipco" image_width="403" image_height="302">
+</ImageSpatialExtentsPropertyBox>
+<PixelInformationPropertyBox Size="16" Type="pixi" Version="0" Flags="0" Specification="iff" Container="ipco" >
+<BitPerChannel bits_per_channel="8"/>
+<BitPerChannel bits_per_channel="8"/>
+<BitPerChannel bits_per_channel="8"/>
+</PixelInformationPropertyBox>
+<AV1ConfigurationBox>
+<AV1Config version="1" profile="0" level_idx0="0" tier="0" high_bitdepth="0" twelve_bit="0" monochrome="0" chroma_subsampling_x="1" chroma_subsampling_y="1" chroma_sample_position="0" initial_presentation_delay="1" OBUs_count="0">
+</AV1Config>
+</AV1ConfigurationBox>
+<ColourInformationBox Size="19" Type="colr" Specification="iff" Container="video_sample_entry ipco encv resv" colour_type="nclx" colour_primaries="1" transfer_characteristics="13" matrix_coefficients="6" full_range_flag="1">
+</ColourInformationBox>
+<ImageSpatialExtentsPropertyBox Size="20" Type="ispe" Version="0" Flags="0" Specification="iff" Container="ipco" image_width="512" image_height="384">
+</ImageSpatialExtentsPropertyBox>
+<AV1ConfigurationBox>
+<AV1Config version="1" profile="1" level_idx0="1" tier="0" high_bitdepth="0" twelve_bit="0" monochrome="0" chroma_subsampling_x="0" chroma_subsampling_y="0" chroma_sample_position="0" initial_presentation_delay="1" OBUs_count="0">
+</AV1Config>
+</AV1ConfigurationBox>
+<ColourInformationBox Size="19" Type="colr" Specification="iff" Container="video_sample_entry ipco encv resv" colour_type="nclx" colour_primaries="2" transfer_characteristics="2" matrix_coefficients="2" full_range_flag="1">
+</ColourInformationBox>
+</ItemPropertyContainerBox>
+<ItemPropertyAssociationBox Size="36" Type="ipma" Version="0" Flags="0" Specification="iff" Container="iprp" entry_count="3">
+<AssociationEntry item_ID="1" association_count="4">
+<Property index="1" essential="0"/>
+<Property index="2" essential="0"/>
+<Property index="3" essential="1"/>
+<Property index="4" essential="0"/>
+</AssociationEntry>
+<AssociationEntry item_ID="2" association_count="3">
+<Property index="1" essential="0"/>
+<Property index="2" essential="0"/>
+<Property index="4" essential="0"/>
+</AssociationEntry>
+<AssociationEntry item_ID="3" association_count="4">
+<Property index="5" essential="0"/>
+<Property index="2" essential="0"/>
+<Property index="6" essential="1"/>
+<Property index="7" essential="0"/>
+</AssociationEntry>
+</ItemPropertyAssociationBox>
+</ItemPropertiesBox>
+<GroupListBox Size="36" Type="grpl" Specification="iff" Container="meta" >
+<EntityToGroupTypeBox Size="28" Type="altr" Version="0" Flags="0" Specification="iff" Container="grpl" group_id="1">
+<EntityToGroupTypeBoxEntry EntityID="2"/>
+<EntityToGroupTypeBoxEntry EntityID="1"/>
+</EntityToGroupTypeBox>
+</GroupListBox>
+</MetaBox>
+<MediaDataBox Size="REDACTED" Type="mdat" Specification="p12" Container="file" dataSize="REDACTED">
+</MediaDataBox>
+</IsoMediaFile>
+<Tracks>
+</Tracks>
+</ISOBaseMediaFileTrace>
diff --git a/tests/golden_test_common.sh b/tests/golden_test_common.sh
new file mode 100755
index 0000000..920da28
--- /dev/null
+++ b/tests/golden_test_common.sh
@@ -0,0 +1,164 @@
+#!/bin/bash
+# Copyright 2023 Google LLC
+# SPDX-License-Identifier: BSD-2-Clause
+#
+# Common code for tests that compares the structure (boxes) of AVIF files
+# metadata, dumped as xml, with golden files stored in tests/data/goldens/.
+# Depends on the command line tool MP4Box.
+#
+# To use this, export a function called 'encode_test_files', then
+# 'source' this file.
+# The paths to avifenc and to the testdata dir will be passed as arguments.
+# See 'test_cmd_enc_boxes_golden.sh' for an example.
+
+set -e
+
+if [[ "$#" -ge 1 ]]; then
+  # eval so that the passed in directory can contain variables.
+  ENCODER_DIR="$(eval echo "$1")"
+else
+  # Assume "tests" is the current directory.
+  ENCODER_DIR="$(pwd)/.."
+fi
+if [[ "$#" -ge 2 ]]; then
+  # eval so that the passed in directory can contain variables.
+  MP4BOX_DIR="$(eval echo "$2")"
+else
+  # Assume "tests" is the current directory.
+  MP4BOX_DIR="$(pwd)/../ext/gpac/bin/gcc"
+fi
+if [[ "$#" -ge 3 ]]; then
+  TESTDATA_DIR="$(eval echo "$3")"
+else
+  TESTDATA_DIR="$(pwd)/data"
+fi
+if [[ "$#" -ge 4 && ! -z "$4" ]]; then
+  OUTPUT_DIR="$(eval echo "$4")/test_cmd_enc_boxes_golden"
+else
+  OUTPUT_DIR="$(mktemp -d)"
+fi
+
+GOLDEN_DIR="${TESTDATA_DIR}/goldens"
+AVIFENC="${ENCODER_DIR}/avifenc"
+MP4BOX="${MP4BOX_DIR}/MP4Box"
+
+if [[ ! -x "$AVIFENC" ]]; then
+    echo "'$AVIFENC' does not exist or is not executable"
+    exit 1
+fi
+if [[ ! -x "$MP4BOX" ]]; then
+    echo "'$MP4BOX' does not exist or is not executable, build it by running ext/mp4box.sh"
+    exit 1
+fi
+
+mkdir -p "$OUTPUT_DIR"
+echo "Outputting to $OUTPUT_DIR"
+
+has_errors=false
+
+# Cleanup
+cleanup() {
+    # Only delete temp files if the test succeeded, to allow debugging in case or error.
+    if ! $has_errors; then
+        find ${OUTPUT_DIR} -type f \( -name '*.avif' -o -name '*.xml' -o -name '*.xml.diff' \) -delete
+    fi
+}
+trap cleanup EXIT
+
+# Replaces parts of the xml that are brittle with 'REDACTED', typically because they depend
+# on the size of the encoded pixels.
+redact_xml() {
+  local f="$1"
+
+  # Remove item data offsets and size so that the xml is stable even if the encoded size changes.
+  # The first 2 regexes are for the iloc box, the next 2 are for the mdat box.
+  # Note that "-i.bak" works on both Linux and Mac https://stackoverflow.com/a/22084103
+  sed -i.bak -e 's/extent_offset="[0-9]*"/extent_offset="REDACTED"/g' \
+             -e 's/extent_length="[0-9]*"/extent_length="REDACTED"/g' \
+             -e 's/dataSize="[0-9]*"/dataSize="REDACTED"/g' \
+             -e 's/<MediaDataBox\(.*\) Size="[0-9]*"/<MediaDataBox\1 Size="REDACTED"/g' \
+             "$f"
+  # For animations.
+  sed -i.bak -e 's/CreationTime="[0-9]*"/CreationTime="REDACTED"/g' \
+             -e 's/ModificationTime="[0-9]*"/ModificationTime="REDACTED"/g' \
+             -e 's/<Sample\(.*\) size="[0-9]*"/<Sample\1 size="REDACTED"/g' \
+             -e 's/<SampleSizeEntry\(.*\) Size="[0-9]*"/<SampleSizeEntry\1 Size="REDACTED"/g' \
+             -e 's/<OBU\(.*\) size="[0-9]*"/<OBU\1 size="REDACTED"/g' \
+             -e 's/<Tile\(.*\) size="[0-9]*"/<Tile\1 size="REDACTED"/g' \
+            "$f"
+  rm "$f.bak"
+}
+
+diff_with_goldens() {
+    echo "==="
+
+    for f in $(find . -name '*.avif'); do
+        echo "Testing $f"
+        xml="$(basename "$f").xml" # Remove leading ./ for prettier paths.
+        # Dump the file structure as XML
+        "${MP4BOX}" -dxml -out "$xml"  "$f"
+        redact_xml "$xml"
+
+        # Compare with golden.
+        golden="$GOLDEN_DIR/$xml"
+        if [[ -f "$golden" ]]; then
+            golden_differs=false
+            diff -u "$golden" "$xml" > "$xml.diff" || golden_differs=true
+            if $golden_differs; then
+                has_errors=true
+                echo "FAILED: Differences found for file $xml, see details in $OUTPUT_DIR/$xml.diff" >&2
+                echo "Expected file: $golden" >&2
+                echo "Actual file: $OUTPUT_DIR/$xml" >&2
+                cp "$golden" "$xml.golden" # Copy golden to output directory for easier debugging.
+            else
+                rm "$xml.diff"  # Delete empty diff files.
+                echo "Passed"
+            fi
+        else
+            echo "FAILED:  Missing golden file $golden" >&2
+            echo "Actual file: $OUTPUT_DIR/$xml" >&2
+            has_errors=true
+        fi
+
+        echo "---"
+
+    done
+    echo "==="
+}
+
+pushd "${OUTPUT_DIR}"
+    set -x # Print the encoding command lines.
+    # Expect this function to be exported by the calling script.
+    encode_test_files "${AVIFENC}" "${TESTDATA_DIR}"
+    set +x
+
+    diff_with_goldens
+popd > /dev/null
+
+if $has_errors; then
+  cat >&2 << EOF
+Test failed.
+
+# IF RUNNING ON GITHUB
+  Check the workflow step called "How to fix failing tests" for instructions on how to debug/fix this test.
+
+# IF RUNNING LOCALLY
+  Look at the .xml.diff files in $OUTPUT_DIR
+
+  If the diffs are expected, update the golden files with:
+  cp "$OUTPUT_DIR"/*.xml "$GOLDEN_DIR"
+EOF
+
+    cat > "$OUTPUT_DIR/README.txt" << EOF
+To debug test failures, look at the .xml.diff files.
+
+If the diffs are expected, update the golden files by copying the .xml files, e.g.:
+cp *.xml $HOME/git/libavif/tests/data/goldens
+
+You can also debug the test locally by setting AVIF_ENABLE_GOLDEN_TESTS=ON in cmake.
+EOF
+
+    exit 1
+fi
+
+exit 0
diff --git a/tests/test_cmd_enc_boxes_golden.sh b/tests/test_cmd_enc_boxes_golden.sh
index 0466e1b..b09366e 100644
--- a/tests/test_cmd_enc_boxes_golden.sh
+++ b/tests/test_cmd_enc_boxes_golden.sh
@@ -12,128 +12,14 @@
 # But most of the time you will be running this test with 'make test' or
 # 'ctest -V -R test_cmd_enc_boxes_golden'
 
-set -e
-
-if [[ "$#" -ge 1 ]]; then
-  # eval so that the passed in directory can contain variables.
-  ENCODER_DIR="$(eval echo "$1")"
-else
-  # Assume "tests" is the current directory.
-  ENCODER_DIR="$(pwd)/.."
-fi
-if [[ "$#" -ge 2 ]]; then
-  # eval so that the passed in directory can contain variables.
-  MP4BOX_DIR="$(eval echo "$2")"
-else
-  # Assume "tests" is the current directory.
-  MP4BOX_DIR="$(pwd)/../ext/gpac/bin/gcc"
-fi
-if [[ "$#" -ge 3 ]]; then
-  TESTDATA_DIR="$(eval echo "$3")"
-else
-  TESTDATA_DIR="$(pwd)/data"
-fi
-if [[ "$#" -ge 4 && ! -z "$4" ]]; then
-  OUTPUT_DIR="$(eval echo "$4")/test_cmd_enc_boxes_golden"
-else
-  OUTPUT_DIR="$(mktemp -d)"
-fi
-
-GOLDEN_DIR="${TESTDATA_DIR}/goldens"
-AVIFENC="${ENCODER_DIR}/avifenc"
-MP4BOX="${MP4BOX_DIR}/MP4Box"
-
-if [[ ! -x "$AVIFENC" ]]; then
-    echo "'$AVIFENC' does not exist or is not executable"
-    exit 1
-fi
-if [[ ! -x "$MP4BOX" ]]; then
-    echo "'$MP4BOX' does not exist or is not executable, build it by running ext/mp4box.sh"
-    exit 1
-fi
-
-mkdir -p "$OUTPUT_DIR"
-echo "Outputting to $OUTPUT_DIR"
-
-has_errors=false
-
-# Cleanup
-cleanup() {
-    # Only delete temp files if the test succeeded, to allow debugging in case or error.
-    if ! $has_errors; then
-        find ${OUTPUT_DIR} -type f \( -name '*.avif' -o -name '*.xml' -o -name '*.xml.diff' \) -delete
-    fi
-}
-trap cleanup EXIT
-
-# Replaces parts of the xml that are brittle with 'REDACTED', typically because they depend
-# on the size of the encoded pixels.
-redact_xml() {
-  local f="$1"
-
-  # Remove item data offsets and size so that the xml is stable even if the encoded size changes.
-  # The first 2 regexes are for the iloc box, the next 2 are for the mdat box.
-  # Note that "-i.bak" works on both Linux and Mac https://stackoverflow.com/a/22084103
-  sed -i.bak -e 's/extent_offset="[0-9]*"/extent_offset="REDACTED"/g' \
-             -e 's/extent_length="[0-9]*"/extent_length="REDACTED"/g' \
-             -e 's/dataSize="[0-9]*"/dataSize="REDACTED"/g' \
-             -e 's/<MediaDataBox\(.*\) Size="[0-9]*"/<MediaDataBox\1 Size="REDACTED"/g' \
-             "$f"
-  # For animations.
-  sed -i.bak -e 's/CreationTime="[0-9]*"/CreationTime="REDACTED"/g' \
-             -e 's/ModificationTime="[0-9]*"/ModificationTime="REDACTED"/g' \
-             -e 's/<Sample\(.*\) size="[0-9]*"/<Sample\1 size="REDACTED"/g' \
-             -e 's/<SampleSizeEntry\(.*\) Size="[0-9]*"/<SampleSizeEntry\1 Size="REDACTED"/g' \
-             -e 's/<OBU\(.*\) size="[0-9]*"/<OBU\1 size="REDACTED"/g' \
-             -e 's/<Tile\(.*\) size="[0-9]*"/<Tile\1 size="REDACTED"/g' \
-            "$f"
-  rm "$f.bak"
-}
-
-diff_with_goldens() {
-    echo "==="
-
-    for f in $(find . -name '*.avif'); do
-        echo "Testing $f"
-        xml="$(basename "$f").xml" # Remove leading ./ for prettier paths.
-        # Dump the file structure as XML
-        "${MP4BOX}" -dxml -out "$xml"  "$f"
-        redact_xml "$xml"
-
-        # Compare with golden.
-        golden="$GOLDEN_DIR/$xml"
-        if [[ -f "$golden" ]]; then
-            golden_differs=false
-            diff -u "$golden" "$xml" > "$xml.diff" || golden_differs=true
-            if $golden_differs; then
-                has_errors=true
-                echo "FAILED: Differences found for file $xml, see details in $OUTPUT_DIR/$xml.diff" >&2
-                echo "Expected file: $golden" >&2
-                echo "Actual file: $OUTPUT_DIR/$xml" >&2
-                cp "$golden" "$xml.golden" # Copy golden to output directory for easier debugging.
-            else
-                rm "$xml.diff"  # Delete empty diff files.
-                echo "Passed"
-            fi
-        else
-            echo "FAILED:  Missing golden file $golden" >&2
-            echo "Actual file: $OUTPUT_DIR/$xml" >&2
-            has_errors=true
-        fi
-
-        echo "---"
-
-    done
-    echo "==="
-}
-
 # ===================================
 # ========== Encode files ===========
 # ===================================
 # To add new test case, just add new encode commands to this function.
 # All .avif files created here will become test cases.
 encode_test_files() {
-    set -x # Print the encoding command lines.
+    AVIFENC="$1"
+    TESTDATA_DIR="$2"
 
     # Still images.
     for f in "kodim03_yuv420_8bpc.y4m" "circle-trns-after-plte.png" "paris_icc_exif_xmp.png"; do
@@ -147,40 +33,7 @@
     # Animation.
     "${AVIFENC}" -s 9 "${TESTDATA_DIR}/kodim03_yuv420_8bpc.y4m" \
       "${TESTDATA_DIR}/kodim23_yuv420_8bpc.y4m" -o "kodim03_23_animation.avif"
-
-    set +x
 }
 
-pushd "${OUTPUT_DIR}"
-    encode_test_files
-
-    diff_with_goldens
-popd > /dev/null
-
-if $has_errors; then
-  cat >&2 << EOF
-Test failed.
-
-# IF RUNNING ON GITHUB
-  Check the workflow step called "How to fix failing tests" for instructions on how to debug/fix this test.
-
-# IF RUNNING LOCALLY
-  Look at the .xml.diff files in $OUTPUT_DIR
-
-  If the diffs are expected, update the golden files with:
-  cp "$OUTPUT_DIR"/*.xml "$GOLDEN_DIR"
-EOF
-
-    cat > "$OUTPUT_DIR/README.txt" << EOF
-To debug test failures, look at the .xml.diff files.
-
-If the diffs are expected, update the golden files by copying the .xml files, e.g.:
-cp *.xml $HOME/git/libavif/tests/data/goldens
-
-You can also debug the test locally by setting AVIF_ENABLE_GOLDEN_TESTS=ON in cmake.
-EOF
-
-    exit 1
-fi
-
-exit 0
+export -f encode_test_files
+source $(dirname "$0")/golden_test_common.sh
diff --git a/tests/test_cmd_enc_gainmap_boxes_golden.sh b/tests/test_cmd_enc_gainmap_boxes_golden.sh
new file mode 100644
index 0000000..32de9c1
--- /dev/null
+++ b/tests/test_cmd_enc_gainmap_boxes_golden.sh
@@ -0,0 +1,33 @@
+#!/bin/bash
+# Copyright 2023 Google LLC
+# SPDX-License-Identifier: BSD-2-Clause
+#
+# Same as test_cmd_enc_boxes_golden.sh but for when gain maps are enabled.
+# Compares the structure (boxes) of AVIF files metadata, dumped as xml,
+# with golden files stored in tests/data/goldens/.
+# Depends on the command line tool MP4Box.
+#
+# Usage:
+# test_cmd_enc_gainmap_boxes_golden.sh <avifenc_dir> <mp4box_dir> <testdata_dir>
+#                                      <test_output_dir>
+# But most of the time you will be running this test with 'make test' or
+# 'ctest -V -R test_cmd_enc_gainmap_boxes_golden'
+
+# ===================================
+# ========== Encode files ===========
+# ===================================
+# To add new test case, just add new encode commands to this function.
+# All .avif files created here will become test cases.
+encode_test_files() {
+    # Image with a gain map.
+    for f in "paris_exif_xmp_gainmap_bigendian.jpg" "paris_exif_xmp_gainmap_littleendian.jpg"; do
+        "${AVIFENC}" -s 9 "${TESTDATA_DIR}/$f" -o "$f.avif"
+    done
+
+    # Ignore gain map.
+    "${AVIFENC}" -s 9 --ignore-gain-map "${TESTDATA_DIR}/paris_exif_xmp_gainmap_bigendian.jpg" \
+      -o "paris_exif_xmp_gainmap_bigendian_ignore.jpg.avif"
+}
+
+export -f encode_test_files
+source $(dirname "$0")/golden_test_common.sh