#!/usr/bin/env pytest
# -*- coding: utf-8 -*-
###############################################################################
# $Id$
#
# Project:  GDAL/OGR Test Suite
# Purpose:  Test read/write functionality for GeoTIFF format.
# Author:   Frank Warmerdam <warmerdam@pobox.com>
#
###############################################################################
# Copyright (c) 2003, Frank Warmerdam <warmerdam@pobox.com>
# Copyright (c) 2008-2013, Even Rouault <even dot rouault at spatialys.com>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Library General Public
# License as published by the Free Software Foundation; either
# version 2 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# Library General Public License for more details.
#
# You should have received a copy of the GNU Library General Public
# License along with this library; if not, write to the
# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
# Boston, MA 02111-1307, USA.
###############################################################################

import array
import copy
import math
import os
import shutil
import struct
import sys

import gdaltest
import pytest
from test_py_scripts import samples_path

from osgeo import gdal, osr


###############################################################################
@pytest.fixture(autouse=True, scope="module")
def module_disable_exceptions():
    with gdaltest.disable_exceptions():
        yield


@pytest.fixture(autouse=True, scope="module")
def setup_and_cleanup():
    gdaltest.tiff_drv = gdal.GetDriverByName("GTiff")

    yield

    del gdaltest.tiff_drv


###############################################################################


def _check_cog(filename, check_tiled=True, full_check=False):

    path = samples_path
    if path not in sys.path:
        sys.path.append(path)
    import validate_cloud_optimized_geotiff

    try:
        _, errors, _ = validate_cloud_optimized_geotiff.validate(
            filename, check_tiled=check_tiled, full_check=full_check
        )
        assert not errors, "validate_cloud_optimized_geotiff failed"
    except OSError:
        pytest.fail("validate_cloud_optimized_geotiff failed")


###############################################################################
# Get the GeoTIFF driver, and verify a few things about it.


def test_tiff_write_1():

    assert gdaltest.tiff_drv is not None, "GTiff driver not found!"

    drv_md = gdaltest.tiff_drv.GetMetadata()
    assert drv_md["DMD_MIMETYPE"] == "image/tiff", "mime type is wrong"


###############################################################################
# Create a simple file by copying from an existing one.


def test_tiff_write_2():

    src_ds = gdal.Open("data/cfloat64.tif")

    new_ds = gdaltest.tiff_drv.CreateCopy("tmp/test_2.tif", src_ds)
    assert new_ds.FlushCache() == gdal.CE_None

    bnd = new_ds.GetRasterBand(1)
    assert bnd.Checksum() == 5028, "Didn't get expected checksum on still-open file"

    bnd = None
    new_ds = None

    # hopefully it's closed now!

    new_ds = gdal.Open("tmp/test_2.tif")
    bnd = new_ds.GetRasterBand(1)
    assert bnd.Checksum() == 5028, "Didn't get expected checksum on reopened file"

    assert bnd.ComputeRasterMinMax() == (
        74.0,
        255.0,
    ), "ComputeRasterMinMax() returned wrong value"

    bnd = None
    new_ds = None

    gdaltest.tiff_drv.Delete("tmp/test_2.tif")


###############################################################################
# Create a simple file by copying from an existing one.


def test_tiff_write_3():

    src_ds = gdal.Open("data/utmsmall.tif")

    options = ["TILED=YES", "BLOCKXSIZE=32", "BLOCKYSIZE=32"]

    new_ds = gdaltest.tiff_drv.CreateCopy("tmp/test_3.tif", src_ds, options=options)

    bnd = new_ds.GetRasterBand(1)
    assert bnd.Checksum() == 50054, "Didn't get expected checksum on still-open file"

    bnd = None
    new_ds = None

    gdaltest.tiff_drv.Delete("tmp/test_3.tif")


###############################################################################
# Create a tiled file.


def test_tiff_write_4():

    np = pytest.importorskip("numpy")

    options = ["TILED=YES", "BLOCKXSIZE=32", "BLOCKYSIZE=32"]

    new_ds = gdaltest.tiff_drv.Create(
        "tmp/test_4.tif", 40, 50, 3, gdal.GDT_Byte, options
    )

    data_red = np.zeros((50, 40), dtype=np.uint8)
    data_green = np.zeros((50, 40), dtype=np.uint8)
    data_blue = np.zeros((50, 40), dtype=np.uint8)

    for y in range(50):
        for x in range(40):
            data_red[y][x] = x
            data_green[y][x] = y
            data_blue[y][x] = x + y

    new_ds.GetRasterBand(1).WriteArray(data_red)
    new_ds.GetRasterBand(2).WriteArray(data_green)
    new_ds.GetRasterBand(3).WriteArray(data_blue)

    gt = (0.0, 1.0, 0.0, 50.0, 0.0, -1.0)
    new_ds.SetGeoTransform(gt)

    assert (
        new_ds.GetRasterBand(1).Checksum() == 21577
        and new_ds.GetRasterBand(2).Checksum() == 20950
        and new_ds.GetRasterBand(3).Checksum() == 23730
    ), "Wrong checksum."

    assert gt == new_ds.GetGeoTransform(), "Wrong geotransform."

    new_ds.SetMetadata({"TEST_KEY": "TestValue <>"})

    new_ds = None

    new_ds = gdal.Open("tmp/test_4.tif")

    assert (
        new_ds.GetRasterBand(1).Checksum() == 21577
        and new_ds.GetRasterBand(2).Checksum() == 20950
        and new_ds.GetRasterBand(3).Checksum() == 23730
    ), "Wrong checksum (2)."

    assert gt == new_ds.GetGeoTransform(), "Wrong geotransform(2)."

    nd = new_ds.GetRasterBand(1).GetNoDataValue()
    assert nd is None, "Got unexpected nodata value."

    md_dict = new_ds.GetMetadata()
    assert md_dict["TEST_KEY"] == "TestValue <>", "Missing metadata"

    new_ds = None

    gdaltest.tiff_drv.Delete("tmp/test_4.tif")


###############################################################################
# Write a file with GCPs.


def test_tiff_write_5():

    src_ds = gdal.Open("data/gcps.vrt")

    new_ds = gdaltest.tiff_drv.CreateCopy("tmp/test_5.tif", src_ds)

    assert (
        new_ds.GetGCPProjection().find('AUTHORITY["EPSG","26711"]') != -1
    ), "GCP Projection not set properly."

    gcps = new_ds.GetGCPs()
    assert len(gcps) == 4, "GCP count wrong."

    new_ds = None

    gdaltest.tiff_drv.Delete("tmp/test_5.tif")

    # Test SetGCPs on a new GTiff
    new_ds = gdaltest.tiff_drv.Create("tmp/test_5.tif", 10, 10, 1)
    new_ds.SetGCPs(gcps, src_ds.GetGCPProjection())
    new_ds = None

    new_ds = gdal.Open("tmp/test_5.tif")
    gcps = new_ds.GetGCPs()
    assert len(gcps) == 4, "GCP count wrong."
    new_ds = None

    gdaltest.tiff_drv.Delete("tmp/test_5.tif")


###############################################################################
# Test a mixture of reading and writing on a DEFLATE compressed file.


def test_tiff_write_6():

    options = [
        "TILED=YES",
        "BLOCKXSIZE=32",
        "BLOCKYSIZE=32",
        "COMPRESS=DEFLATE",
        "PREDICTOR=2",
    ]
    ds = gdaltest.tiff_drv.Create("tmp/test_6.tif", 200, 200, 1, gdal.GDT_Byte, options)

    # make a 32x32 byte buffer
    buf = b"".join(struct.pack("B", v) for v in range(32)) * 32

    ds.WriteRaster(0, 0, 32, 32, buf, buf_type=gdal.GDT_Byte)
    ds.FlushCache()
    ds.WriteRaster(32, 0, 32, 32, buf, buf_type=gdal.GDT_Byte)
    ds.FlushCache()
    buf_read = ds.ReadRaster(0, 0, 32, 32, buf_type=gdal.GDT_Byte)

    if buf_read != buf:
        gdaltest.tiff_write_6_failed = True
        pytest.fail("did not get back expected data.")

    ds = None

    ds = gdal.Open("tmp/test_6.tif")
    assert ds.GetMetadataItem("COMPRESSION", "IMAGE_STRUCTURE") == "DEFLATE"
    assert ds.GetMetadataItem("PREDICTOR", "IMAGE_STRUCTURE") == "2"
    ds = None

    gdaltest.tiff_write_6_failed = False
    gdaltest.tiff_drv.Delete("tmp/test_6.tif")


###############################################################################
# Test a mixture of reading and writing on a LZW compressed file.


def test_tiff_write_7():

    options = ["TILED=YES", "COMPRESS=LZW", "PREDICTOR=2"]
    ds = gdaltest.tiff_drv.Create("tmp/test_7.tif", 200, 200, 1, gdal.GDT_Byte, options)

    # make a 32x32 byte buffer
    buf = b"".join(struct.pack("B", v) for v in range(32)) * 32

    ds.WriteRaster(0, 0, 32, 32, buf, buf_type=gdal.GDT_Byte)
    ds.FlushCache()
    ds.WriteRaster(32, 0, 32, 32, buf, buf_type=gdal.GDT_Byte)
    ds.FlushCache()
    buf_read = ds.ReadRaster(0, 0, 32, 32, buf_type=gdal.GDT_Byte)

    assert buf_read == buf, "did not get back expected data."

    ds = None

    gdaltest.tiff_drv.Delete("tmp/test_7.tif")


###############################################################################
# Test a mixture of reading and writing on a PACKBITS compressed file.


def test_tiff_write_8():

    options = ["TILED=YES", "BLOCKXSIZE=32", "BLOCKYSIZE=32", "COMPRESS=PACKBITS"]
    ds = gdaltest.tiff_drv.Create("tmp/test_8.tif", 200, 200, 1, gdal.GDT_Byte, options)

    # make a 32x32 byte buffer
    buf = b"".join(struct.pack("B", v) for v in range(32)) * 32

    ds.WriteRaster(0, 0, 32, 32, buf, buf_type=gdal.GDT_Byte)
    ds.FlushCache()
    ds.WriteRaster(32, 0, 32, 32, buf, buf_type=gdal.GDT_Byte)
    ds.FlushCache()

    buf_read = ds.ReadRaster(0, 0, 32, 32, buf_type=gdal.GDT_Byte)

    assert buf_read == buf, "did not get back expected data."

    ds = None

    gdaltest.tiff_drv.Delete("tmp/test_8.tif")


###############################################################################
# Create a simple file by copying from an existing one.


def test_tiff_write_9():

    src_ds = gdal.Open("data/byte.tif")
    new_ds = gdaltest.tiff_drv.CreateCopy("tmp/test_9.tif", src_ds, options=["NBITS=5"])
    with gdal.quiet_errors():
        new_ds = None

    new_ds = gdal.Open("tmp/test_9.tif")
    bnd = new_ds.GetRasterBand(1)
    assert bnd.Checksum() == 5287, "Didn't get expected checksum on reopened file"

    bnd = None
    new_ds = None

    gdaltest.tiff_drv.Delete("tmp/test_9.tif")


###############################################################################
# 1bit file but with band interleaving, and odd size (not multiple of 8) #1957


def test_tiff_write_10():

    ut = gdaltest.GDALTest(
        "GTiff", "oddsize_1bit2b.tif", 2, 5918, options=["NBITS=1", "INTERLEAVE=BAND"]
    )
    ut.testCreate(out_bands=2)


###############################################################################
# Simple 1 bit file, treated through the GTiffBitmapBand class.


def test_tiff_write_11():

    ut = gdaltest.GDALTest(
        "GTiff", "oddsize1bit.tif", 1, 5918, options=["NBITS=1", "COMPRESS=CCITTFAX4"]
    )
    ut.testCreateCopy()


###############################################################################
# Read JPEG Compressed YCbCr subsampled image.


@pytest.mark.require_creation_option("GTiff", "JPEG")
def test_tiff_write_12():

    ds = gdal.Open("data/sasha.tif")
    cs = ds.GetRasterBand(3).Checksum()
    assert cs == 31952 or cs == 30145


###############################################################################
# Write JPEG Compressed YCbCr subsampled image.


@pytest.mark.require_creation_option("GTiff", "JPEG")
def test_tiff_write_13():

    src_ds = gdal.Open("data/sasha.tif")
    ds = gdaltest.tiff_drv.CreateCopy(
        "tmp/sasha.tif",
        src_ds,
        options=[
            "PROFILE=BASELINE",
            "TILED=YES",
            "COMPRESS=JPEG",
            "PHOTOMETRIC=YCBCR",
            "JPEG_QUALITY=31",
        ],
    )
    ds = None

    ds = gdal.Open("tmp/sasha.tif")
    cs = ds.GetRasterBand(3).Checksum()
    ds = None

    gdaltest.tiff_drv.Delete("tmp/sasha.tif")
    assert cs in (16612,)


###############################################################################
# Test creating an in memory copy.


def test_tiff_write_14():

    tst = gdaltest.GDALTest("GTiff", "byte.tif", 1, 4672)

    tst.testCreateCopy(vsimem=1)


###############################################################################
# Test that we can restrict metadata and georeferencing in the output
# file using the PROFILE creation option with CreateCopy()


def test_tiff_write_15():

    ds_in = gdal.Open("data/byte.vrt")

    ds = gdaltest.tiff_drv.CreateCopy(
        "tmp/tw_15.tif", ds_in, options=["PROFILE=BASELINE"]
    )

    ds_in = None
    ds = None

    ds = gdal.Open("tmp/tw_15.tif")

    md = ds.GetMetadata()
    assert "test" in md, "Metadata absent from .aux.xml file."

    md = ds.GetRasterBand(1).GetMetadata()
    assert "testBand" in md, "Metadata absent from .aux.xml file."

    ds = None

    gdal.Unlink("tmp/tw_15.tif.aux.xml")

    ds = gdal.Open("tmp/tw_15.tif")

    assert ds.GetGeoTransform() == (
        0.0,
        1.0,
        0.0,
        0.0,
        0.0,
        1.0,
    ), "Got wrong geotransform, profile ignored?"

    md = ds.GetMetadata()
    assert "test" not in md, "Metadata written to BASELINE file."

    md = ds.GetRasterBand(1).GetMetadata()
    assert "testBand" not in md, "Metadata written to BASELINE file."

    ds = None

    gdaltest.tiff_drv.Delete("tmp/tw_15.tif")


###############################################################################
# Test that we can restrict metadata and georeferencing in the output
# file using the PROFILE creation option with Create()


def test_tiff_write_16():

    ds_in = gdal.Open("data/byte.vrt")

    ds = gdaltest.tiff_drv.Create(
        "tmp/tw_16.tif", 20, 20, gdal.GDT_Byte, options=["PROFILE=BASELINE"]
    )

    ds.SetMetadata({"test": "testvalue"})
    ds.GetRasterBand(1).SetMetadata({"testBand": "testvalueBand"})

    srs = osr.SpatialReference()
    srs.ImportFromEPSG(4326)
    ds.SetSpatialRef(srs)
    ds.SetGeoTransform((10, 5, 0, 30, 0, -5))

    data = ds_in.ReadRaster(0, 0, 20, 20)
    ds.WriteRaster(0, 0, 20, 20, data)

    ds_in = None
    ds = None

    # Check first from PAM
    assert gdal.VSIStatL("tmp/tw_16.tif.aux.xml") is not None
    ds = gdal.Open("tmp/tw_16.tif")
    assert ds.GetGeoTransform() == (10, 5, 0, 30, 0, -5)
    assert ds.GetSpatialRef() is not None
    assert ds.GetSpatialRef().GetAuthorityCode(None) == "4326"

    md = ds.GetMetadata()
    assert "test" in md, "Metadata absent from .aux.xml file."

    md = ds.GetRasterBand(1).GetMetadata()
    assert "testBand" in md, "Metadata absent from .aux.xml file."

    ds = None
    gdal.Unlink("tmp/tw_16.tif.aux.xml")

    ds = gdal.Open("tmp/tw_16.tif")
    assert ds.GetGeoTransform() == (
        0.0,
        1.0,
        0.0,
        0.0,
        0.0,
        1.0,
    ), "Got wrong geotransform, profile ignored?"
    assert ds.GetSpatialRef() is None

    md = ds.GetMetadata()
    assert "test" not in md, "Metadata written to BASELINE file."

    md = ds.GetRasterBand(1).GetMetadata()
    assert "testBand" not in md, "Metadata written to BASELINE file."

    ds = None

    gdaltest.tiff_drv.Delete("tmp/tw_16.tif")


###############################################################################
# Test writing a TIFF with an RPC tag.


def test_tiff_write_17():

    # Translate RPC controlled data to GeoTIFF.

    ds_in = gdal.Open("data/rpc.vrt")
    rpc_md = ds_in.GetMetadata("RPC")

    tmpfilename = "/vsimem/tiff_write_17.tif"
    ds = gdaltest.tiff_drv.CreateCopy(tmpfilename, ds_in)

    ds_in = None
    ds = None

    # Ensure there is no .aux.xml file which might hold the RPC.
    assert not gdal.VSIStatL(
        tmpfilename + ".aux.xml"
    ), "unexpectedly found.aux.xml file"

    # confirm there is no .rpb file created by default.
    assert not gdal.VSIStatL(tmpfilename + ".RPB"), "unexpectedly found .RPB file"

    # confirm there is no _rpc.txt file created by default.
    assert not gdal.VSIStatL(
        tmpfilename + "_RPC.TXT"
    ), "unexpectedly found _RPC.TXT file"

    # Open the dataset, and confirm the RPC data is still intact.
    ds = gdal.Open(tmpfilename)
    gdaltest.check_rpcs_equal(ds.GetMetadata("RPC"), rpc_md)
    ds = None

    # Modify the RPC
    modified_rpc = copy.copy(rpc_md)
    modified_rpc["LINE_OFF"] = "123456"

    ds = gdal.Open(tmpfilename, gdal.GA_Update)
    ds.SetMetadata(modified_rpc, "RPC")
    ds = None

    ds = gdal.Open(tmpfilename)
    gdaltest.check_rpcs_equal(ds.GetMetadata("RPC"), modified_rpc)
    ds = None

    # Unset the RPC
    ds = gdal.Open(tmpfilename, gdal.GA_Update)
    ds.SetMetadata(None, "RPC")
    ds = None

    ds = gdal.Open(tmpfilename)
    assert not ds.GetMetadata("RPC"), "got RPC, but was not expected"
    ds = None

    gdaltest.tiff_drv.Delete(tmpfilename)


###############################################################################
# Test that above test still work with the optimization in the GDAL_DISABLE_READDIR_ON_OPEN
# case (#3996)


def test_tiff_write_17_disable_readdir():
    with gdal.config_option("GDAL_DISABLE_READDIR_ON_OPEN", "TRUE"):
        test_tiff_write_17()


###############################################################################
# Test writing a TIFF with an RPB file and IMD file.


def test_tiff_write_18():

    # Translate RPC controlled data to GeoTIFF.

    ds_in = gdal.Open("data/rpc.vrt")
    rpc_md = ds_in.GetMetadata("RPC")

    gdaltest.tiff_drv.CreateCopy("tmp/tw_18.tif", ds_in, options=["PROFILE=BASELINE"])

    # Ensure there is no .aux.xml file which might hold the RPC.
    assert not gdal.VSIStatL(
        "tmp/tm_18.tif.aux.xml"
    ), "unexpectedly found tm_18.tif.aux.xml file"

    # confirm there is an .rpb and .imd file.
    assert gdal.VSIStatL("tmp/tw_18.RPB") is not None, "missing .RPB file."
    assert gdal.VSIStatL("tmp/tw_18.IMD") is not None, "missing .IMD file."

    # confirm there is no _rpc.txt file created by default.
    assert not gdal.VSIStatL("tmp/tw_18_RPC.TXT"), "unexpectedly found _RPC.TXT file"

    # Open the dataset, and confirm the RPC/IMD data is still intact.
    ds = gdal.Open("tmp/tw_18.tif")

    gdaltest.check_rpcs_equal(ds.GetMetadata("RPC"), rpc_md)

    imd_md = ds.GetMetadata("IMD")
    assert (
        imd_md["version"] == '"R"'
        and imd_md["numColumns"] == "30324"
        and imd_md["IMAGE_1.sunEl"] == "39.7"
    ), "IMD contents wrong?"

    ds = None

    # Test deferred loading with GetMetadataItem()
    ds = gdal.Open("tmp/tw_18.tif")
    assert (
        ds.GetMetadataItem("LINE_OFF", "RPC") == "16201"
    ), "wrong value for GetMetadataItem('LINE_OFF', 'RPC')"
    assert (
        ds.GetMetadataItem("version", "IMD") == '"R"'
    ), "wrong value for GetMetadataItem('version', 'IMD')"
    ds = None

    gdaltest.tiff_drv.Delete("tmp/tw_18.tif")

    # Confirm IMD and RPC files are cleaned up.  If not likely the
    # file list functionality is not working properly.
    assert not gdal.VSIStatL("tmp/tw_18.RPB"), "RPB did not get cleaned up."

    assert not gdal.VSIStatL("tmp/tw_18.IMD"), "IMD did not get cleaned up."

    # Remove the RPC
    gdaltest.tiff_drv.CreateCopy("tmp/tw_18.tif", ds_in, options=["PROFILE=BASELINE"])
    ds = gdal.Open("tmp/tw_18.tif", gdal.GA_Update)
    ds.SetMetadata(None, "RPC")
    ds = None
    assert not os.path.exists("tmp/tw_18.RPB"), "RPB did not get removed"

    gdaltest.tiff_drv.Delete("tmp/tw_18.tif")


###############################################################################
# Test writing a IMD files with space in values


def test_tiff_write_imd_with_space_in_values():

    ds = gdal.GetDriverByName("GTiff").Create("/vsimem/out.tif", 1, 1)
    ds.SetMetadataItem("foo.key", "value with space", "IMD")
    ds.SetMetadataItem("foo.key2", 'value with " double quote', "IMD")
    ds.SetMetadataItem("foo.key3", "value with ' single quote", "IMD")
    ds.SetMetadataItem("foo.key4", """value with " double and ' single quote""", "IMD")
    ds.SetMetadataItem("foo.key5", "value_with_;", "IMD")
    ds.SetMetadataItem("foo.key6", "regular_value", "IMD")
    ds = None

    f = gdal.VSIFOpenL("/vsimem/out.IMD", "rb")
    assert f
    data = gdal.VSIFReadL(1, 1000, f)
    gdal.VSIFCloseL(f)

    gdal.GetDriverByName("GTiff").Delete("/vsimem/out.tif")

    assert (
        data
        == b'BEGIN_GROUP = foo\n\tkey = "value with space";\n\tkey2 = \'value with " double quote\';\n\tkey3 = "value with \' single quote";\n\tkey4 = "value with \'\' double and \' single quote";\n\tkey5 = "value_with_;";\n\tkey6 = regular_value;\nEND_GROUP = foo\nEND;\n'
    )


###############################################################################
# Test that above test still work with the optimization in the GDAL_DISABLE_READDIR_ON_OPEN
# case (#3996)


def test_tiff_write_18_disable_readdir():
    with gdal.config_option("GDAL_DISABLE_READDIR_ON_OPEN", "TRUE"):
        test_tiff_write_18()


###############################################################################
# Test writing a TIFF with an _RPC.TXT


def test_tiff_write_rpc_txt():

    # Translate RPC controlled data to GeoTIFF.

    ds_in = gdal.Open("data/rpc.vrt")

    # Remove IMD before creating the TIFF to avoid creating an .IMD
    # since .IMD + _RPC.TXT is an odd combination
    # If the .IMD is found, we don't try reading _RPC.TXT
    ds_in_without_imd = gdal.GetDriverByName("VRT").CreateCopy("", ds_in)
    ds_in_without_imd.SetMetadata(None, "IMD")

    rpc_md = ds_in.GetMetadata("RPC")

    ds = gdaltest.tiff_drv.CreateCopy(
        "tmp/tiff_write_rpc_txt.tif",
        ds_in_without_imd,
        options=["PROFILE=BASELINE", "RPCTXT=YES"],
    )
    assert gdal.GetLastErrorMsg() == ""

    ds_in = None
    ds = None

    # Ensure there is no .aux.xml file which might hold the RPC.
    try:
        os.remove("tmp/tiff_write_rpc_txt.tif.aux.xml")
    except OSError:
        pass

    # confirm there is no .RPB file created by default.
    assert not os.path.exists("tmp/tiff_write_rpc_txt.RPB")
    assert os.path.exists("tmp/tiff_write_rpc_txt_RPC.TXT")

    # Open the dataset, and confirm the RPC data is still intact.
    ds = gdal.Open("tmp/tiff_write_rpc_txt.tif")

    gdaltest.check_rpcs_equal(ds.GetMetadata("RPC"), rpc_md)

    ds = None

    gdaltest.tiff_drv.Delete("tmp/tiff_write_rpc_txt.tif")

    # Confirm _RPC.TXT file is cleaned up.  If not likely the
    # file list functionality is not working properly.
    assert not os.path.exists("tmp/tiff_write_rpc_txt_RPC.TXT")


###############################################################################
# Test writing a TIFF with an RPC in .aux.xml


def test_tiff_write_rpc_in_pam():

    ds_in = gdal.Open("data/rpc.vrt")
    rpc_md = ds_in.GetMetadata("RPC")

    ds = gdaltest.tiff_drv.CreateCopy(
        "tmp/tiff_write_rpc_in_pam.tif", ds_in, options=["PROFILE=BASELINE", "RPB=NO"]
    )

    ds_in = None
    ds = None

    # Ensure there is a .aux.xml file which might hold the RPC.
    try:
        os.stat("tmp/tiff_write_rpc_in_pam.tif.aux.xml")
    except OSError:
        pytest.fail("missing .aux.xml file.")

    # confirm there is no .RPB file created.
    assert not os.path.exists("tmp/tiff_write_rpc_txt.RPB")

    # Open the dataset, and confirm the RPC data is still intact.
    ds = gdal.Open("tmp/tiff_write_rpc_in_pam.tif")

    gdaltest.check_rpcs_equal(ds.GetMetadata("RPC"), rpc_md)

    ds = None

    gdaltest.tiff_drv.Delete("tmp/tiff_write_rpc_in_pam.tif")


###############################################################################
# Test the write of a pixel-interleaved image with NBITS = 7


def test_tiff_write_19():

    src_ds = gdal.Open("data/contig_strip.tif")

    new_ds = gdaltest.tiff_drv.CreateCopy(
        "tmp/contig_strip_7.tif", src_ds, options=["NBITS=7", "INTERLEAVE=PIXEL"]
    )

    new_ds = None

    # hopefully it's closed now!

    new_ds = gdal.Open("tmp/contig_strip_7.tif")
    assert (
        new_ds.GetRasterBand(1).Checksum() == src_ds.GetRasterBand(1).Checksum()
        and new_ds.GetRasterBand(2).Checksum() == src_ds.GetRasterBand(2).Checksum()
        and new_ds.GetRasterBand(3).Checksum() == src_ds.GetRasterBand(3).Checksum()
    ), "Didn't get expected checksum on reopened file"

    new_ds = None
    src_ds = None

    gdaltest.tiff_drv.Delete("tmp/contig_strip_7.tif")


###############################################################################
# Test write and read of some TIFF tags
# Also test unsetting those tags (#5619)


def test_tiff_write_20():

    new_ds = gdaltest.tiff_drv.Create("tmp/tags.tif", 1, 1, 1)

    values = [
        ("TIFFTAG_DOCUMENTNAME", "document_name"),
        ("TIFFTAG_IMAGEDESCRIPTION", "image_description"),
        ("TIFFTAG_SOFTWARE", "software"),
        ("TIFFTAG_DATETIME", "2009/01/01 13:01:08"),
        # TODO: artitst?
        ("TIFFTAG_ARTIST", "artitst"),
        ("TIFFTAG_HOSTCOMPUTER", "host_computer"),
        ("TIFFTAG_COPYRIGHT", "copyright"),
        ("TIFFTAG_XRESOLUTION", "100"),
        ("TIFFTAG_YRESOLUTION", "101"),
        ("TIFFTAG_RESOLUTIONUNIT", "2 (pixels/inch)"),
        ("TIFFTAG_MINSAMPLEVALUE", "1"),
        ("TIFFTAG_MAXSAMPLEVALUE", "2"),
    ]

    new_ds.SetMetadata(dict(values))

    new_ds = None

    # hopefully it's closed now!

    assert not os.path.exists("tmp/tags.tif.aux.xml")

    new_ds = gdal.Open("tmp/tags.tif")
    md = new_ds.GetMetadata()
    for item in values:
        assert item[0] in md, "Could not find tag %s" % (item[0])

        assert md[item[0]] == item[1], "For tag %s, got %s, expected %s" % (
            item[0],
            md[item[0]],
            item[1],
        )

    new_ds = None

    # Test just unsetting once, but leaving other unchanged
    ds = gdal.Open("tmp/tags.tif", gdal.GA_Update)
    ds.SetMetadataItem("TIFFTAG_SOFTWARE", None)
    ds = None

    assert not os.path.exists("tmp/tags.tif.aux.xml")

    ds = gdal.Open("tmp/tags.tif")
    assert (
        ds.GetMetadataItem("TIFFTAG_SOFTWARE") is None
    ), "expected unset TIFFTAG_SOFTWARE but got %s" % ds.GetMetadataItem(
        "TIFFTAG_SOFTWARE"
    )
    assert (
        ds.GetMetadataItem("TIFFTAG_DOCUMENTNAME") is not None
    ), "expected set TIFFTAG_DOCUMENTNAME but got None"
    ds = None

    # Test unsetting all the remaining items
    ds = gdal.Open("tmp/tags.tif", gdal.GA_Update)
    ds.SetMetadata({})
    ds = None

    ds = gdal.Open("tmp/tags.tif")
    got_md = ds.GetMetadata()
    ds = None

    assert got_md == {}, "expected empty metadata list, but got some"

    gdaltest.tiff_drv.Delete("tmp/tags.tif")


###############################################################################
# Test RGBA images with TIFFTAG_EXTRASAMPLES=EXTRASAMPLE_ASSOCALPHA


def test_tiff_write_21():

    src_ds = gdal.Open("data/stefan_full_rgba.tif")

    new_ds = gdaltest.tiff_drv.CreateCopy("tmp/stefan_full_rgba.tif", src_ds)

    new_ds = None

    new_ds = gdal.Open("tmp/stefan_full_rgba.tif")
    assert new_ds.RasterCount == 4
    for i in range(4):
        assert (
            new_ds.GetRasterBand(i + 1).GetRasterColorInterpretation()
            == src_ds.GetRasterBand(i + 1).GetRasterColorInterpretation()
        )
        assert (
            new_ds.GetRasterBand(i + 1).Checksum()
            == src_ds.GetRasterBand(i + 1).Checksum()
        )

    new_ds = None
    src_ds = None

    gdaltest.tiff_drv.Delete("tmp/stefan_full_rgba.tif")


###############################################################################
# Test RGBA images with TIFFTAG_EXTRASAMPLES=EXTRASAMPLE_UNSPECIFIED


def test_tiff_write_22():

    src_ds = gdal.Open("data/stefan_full_rgba_photometric_rgb.tif")

    new_ds = gdaltest.tiff_drv.CreateCopy(
        "tmp/stefan_full_rgba_photometric_rgb.tif", src_ds, options=["PHOTOMETRIC=RGB"]
    )

    new_ds = None

    new_ds = gdal.Open("tmp/stefan_full_rgba_photometric_rgb.tif")
    assert new_ds.RasterCount == 4
    for i in range(4):
        assert (
            new_ds.GetRasterBand(i + 1).GetRasterColorInterpretation()
            == src_ds.GetRasterBand(i + 1).GetRasterColorInterpretation()
        )
        assert (
            new_ds.GetRasterBand(i + 1).Checksum()
            == src_ds.GetRasterBand(i + 1).Checksum()
        )

    new_ds = None
    src_ds = None

    gdaltest.tiff_drv.Delete("tmp/stefan_full_rgba_photometric_rgb.tif")


###############################################################################
# Test grey+alpha images with ALPHA=YES


def test_tiff_write_23():

    src_ds = gdal.Open("data/stefan_full_greyalpha.tif")

    new_ds = gdaltest.tiff_drv.CreateCopy(
        "tmp/stefan_full_greyalpha.tif", src_ds, options=["ALPHA=YES"]
    )

    new_ds = None

    new_ds = gdal.Open("tmp/stefan_full_greyalpha.tif")
    assert new_ds.RasterCount == 2
    for i in range(2):
        assert (
            new_ds.GetRasterBand(i + 1).GetRasterColorInterpretation()
            == src_ds.GetRasterBand(i + 1).GetRasterColorInterpretation()
        )
        assert (
            new_ds.GetRasterBand(i + 1).Checksum()
            == src_ds.GetRasterBand(i + 1).Checksum()
        )

    new_ds = None
    src_ds = None

    gdaltest.tiff_drv.Delete("tmp/stefan_full_greyalpha.tif")


###############################################################################
# Test grey+alpha images without ALPHA=YES


def test_tiff_write_24():

    src_ds = gdal.Open("data/stefan_full_greyalpha.tif")

    new_ds = gdaltest.tiff_drv.CreateCopy("tmp/stefan_full_greyunspecified.tif", src_ds)

    new_ds = None

    new_ds = gdal.Open("tmp/stefan_full_greyunspecified.tif")
    assert new_ds.RasterCount == 2
    for i in range(2):
        assert (
            new_ds.GetRasterBand(i + 1).GetRasterColorInterpretation()
            == src_ds.GetRasterBand(i + 1).GetRasterColorInterpretation()
        )
        assert (
            new_ds.GetRasterBand(i + 1).Checksum()
            == src_ds.GetRasterBand(i + 1).Checksum()
        )

    new_ds = None
    src_ds = None

    gdaltest.tiff_drv.Delete("tmp/stefan_full_greyunspecified.tif")


###############################################################################
# Read a CIELAB image to test the RGBA image TIFF interface


def test_tiff_write_25():

    src_ds = gdal.Open("data/cielab.tif")
    assert src_ds.RasterCount == 4
    assert src_ds.GetRasterBand(1).Checksum() == 6
    assert src_ds.GetRasterBand(2).Checksum() == 3
    assert src_ds.GetRasterBand(3).Checksum() == 0
    assert src_ds.GetRasterBand(4).Checksum() == 3
    assert src_ds.GetRasterBand(1).GetRasterColorInterpretation() == gdal.GCI_RedBand
    assert src_ds.GetRasterBand(2).GetRasterColorInterpretation() == gdal.GCI_GreenBand
    assert src_ds.GetRasterBand(3).GetRasterColorInterpretation() == gdal.GCI_BlueBand
    assert src_ds.GetRasterBand(4).GetRasterColorInterpretation() == gdal.GCI_AlphaBand
    src_ds = None


###############################################################################
# Test color table in a 8 bit image


def test_tiff_write_26():

    ds = gdaltest.tiff_drv.Create("tmp/ct8.tif", 1, 1, 1, gdal.GDT_Byte)

    ct = gdal.ColorTable()
    ct.SetColorEntry(0, (255, 255, 255, 255))
    ct.SetColorEntry(1, (255, 255, 0, 255))
    ct.SetColorEntry(2, (255, 0, 255, 255))
    ct.SetColorEntry(3, (0, 255, 255, 255))

    ds.GetRasterBand(1).SetRasterColorTable(ct)

    ct = None
    ds = None

    ds = gdal.Open("tmp/ct8.tif")

    ct = ds.GetRasterBand(1).GetRasterColorTable()
    assert (
        ct.GetCount() == 256
        and ct.GetColorEntry(0) == (255, 255, 255, 255)
        and ct.GetColorEntry(1) == (255, 255, 0, 255)
        and ct.GetColorEntry(2) == (255, 0, 255, 255)
        and ct.GetColorEntry(3) == (0, 255, 255, 255)
    ), "Wrong color table entry."

    ct = None
    ds = None

    gdaltest.tiff_drv.Delete("tmp/ct8.tif")


###############################################################################
# Test color table in a 16 bit image


def test_tiff_write_27():

    ds = gdaltest.tiff_drv.Create("tmp/ct16.tif", 1, 1, 1, gdal.GDT_UInt16)

    ct = gdal.ColorTable()
    ct.SetColorEntry(0, (255, 255, 255, 255))
    ct.SetColorEntry(1, (255, 255, 0, 255))
    ct.SetColorEntry(2, (255, 0, 255, 255))
    ct.SetColorEntry(3, (0, 255, 255, 255))

    ds.GetRasterBand(1).SetRasterColorTable(ct)

    ct = None
    ds = None

    ds = gdal.Open("tmp/ct16.tif")
    new_ds = gdaltest.tiff_drv.CreateCopy("tmp/ct16_copy.tif", ds)
    del new_ds
    ds = None

    ds = gdal.Open("tmp/ct16_copy.tif")

    ct = ds.GetRasterBand(1).GetRasterColorTable()
    assert (
        ct.GetCount() == 65536
        and ct.GetColorEntry(0) == (255, 255, 255, 255)
        and ct.GetColorEntry(1) == (255, 255, 0, 255)
        and ct.GetColorEntry(2) == (255, 0, 255, 255)
        and ct.GetColorEntry(3) == (0, 255, 255, 255)
    ), "Wrong color table entry."

    ct = None
    ds = None

    gdaltest.tiff_drv.Delete("tmp/ct16.tif")
    gdaltest.tiff_drv.Delete("tmp/ct16_copy.tif")


###############################################################################
# Test SetRasterColorInterpretation on a 2 channel image


def test_tiff_write_28():

    ds = gdaltest.tiff_drv.Create("tmp/greyalpha.tif", 1, 1, 2)

    assert ds.GetRasterBand(2).GetRasterColorInterpretation() == gdal.GCI_Undefined

    ds.GetRasterBand(2).SetRasterColorInterpretation(gdal.GCI_AlphaBand)

    assert ds.GetRasterBand(2).GetRasterColorInterpretation() == gdal.GCI_AlphaBand

    ds = None

    ds = gdal.Open("tmp/greyalpha.tif")

    assert ds.GetRasterBand(2).GetRasterColorInterpretation() == gdal.GCI_AlphaBand
    ds = None

    gdaltest.tiff_drv.Delete("tmp/greyalpha.tif")


###############################################################################
# Test SetRasterColorInterpretation on a 4 channel image


def test_tiff_write_29():

    # When creating a 4 channel image with PHOTOMETRIC=RGB,
    # TIFFTAG_EXTRASAMPLES=EXTRASAMPLE_UNSPECIFIED
    ds = gdaltest.tiff_drv.Create(
        "/vsimem/rgba.tif", 1, 1, 4, options=["PHOTOMETRIC=RGB"]
    )
    assert ds.GetMetadataItem("TIFFTAG_EXTRASAMPLES", "_DEBUG_") == "0"
    assert ds.GetRasterBand(4).GetRasterColorInterpretation() == gdal.GCI_Undefined

    # Now turn on alpha
    ds.GetRasterBand(4).SetRasterColorInterpretation(gdal.GCI_AlphaBand)

    assert ds.GetRasterBand(4).GetRasterColorInterpretation() == gdal.GCI_AlphaBand
    assert ds.GetMetadataItem("TIFFTAG_EXTRASAMPLES", "_DEBUG_") == "2"
    ds = None

    assert gdal.VSIStatL("/vsimem/rgba.tif.aux.xml") is None

    ds = gdal.Open("/vsimem/rgba.tif")
    assert ds.GetMetadataItem("TIFFTAG_EXTRASAMPLES", "_DEBUG_") == "2"
    assert ds.GetRasterBand(4).GetRasterColorInterpretation() == gdal.GCI_AlphaBand

    # Test cancelling alpha
    gdaltest.tiff_drv.CreateCopy("/vsimem/rgb_no_alpha.tif", ds, options=["ALPHA=NO"])
    ds = None

    assert gdal.VSIStatL("/vsimem/rgb_no_alpha.tif.aux.xml") is None

    ds = gdal.Open("/vsimem/rgb_no_alpha.tif")
    assert ds.GetMetadataItem("TIFFTAG_EXTRASAMPLES", "_DEBUG_") == "0"
    assert ds.GetRasterBand(4).GetRasterColorInterpretation() == gdal.GCI_Undefined

    # Test re-adding alpha
    gdaltest.tiff_drv.CreateCopy(
        "/vsimem/rgb_added_alpha.tif", ds, options=["ALPHA=YES"]
    )
    ds = None

    assert gdal.VSIStatL("/vsimem/rgb_added_alpha.tif.aux.xml") is None

    ds = gdal.Open("/vsimem/rgb_added_alpha.tif")
    assert ds.GetMetadataItem("TIFFTAG_EXTRASAMPLES", "_DEBUG_") == "2"
    assert ds.GetRasterBand(4).GetRasterColorInterpretation() == gdal.GCI_AlphaBand
    ds = None

    gdaltest.tiff_drv.Delete("/vsimem/rgba.tif")
    gdaltest.tiff_drv.Delete("/vsimem/rgb_no_alpha.tif")
    gdaltest.tiff_drv.Delete("/vsimem/rgb_added_alpha.tif")


###############################################################################
# Create a BigTIFF image with BigTIFF=YES


def test_tiff_write_30():

    ds = gdaltest.tiff_drv.Create("tmp/bigtiff.tif", 1, 1, 1, options=["BigTIFF=YES"])
    ds = None

    ds = gdal.Open("tmp/bigtiff.tif")
    assert ds is not None
    ds = None

    fileobj = open("tmp/bigtiff.tif", mode="rb")
    binvalues = struct.unpack("B" * 4, fileobj.read(4))
    fileobj.close()

    gdaltest.tiff_drv.Delete("tmp/bigtiff.tif")

    # Check BigTIFF signature
    assert not (
        (binvalues[2] != 0x2B or binvalues[3] != 0)
        and (binvalues[3] != 0x2B or binvalues[2] != 0)
    )


###############################################################################
# Create a BigTIFF image implicitly (more than 4Gb).


def test_tiff_write_31():

    ds = gdaltest.tiff_drv.Create(
        "tmp/bigtiff.tif", 100000, 100000, 1, options=["SPARSE_OK=TRUE"]
    )
    ds = None

    ds = gdal.Open("tmp/bigtiff.tif")
    assert ds is not None
    ds = None

    fileobj = open("tmp/bigtiff.tif", mode="rb")
    binvalues = struct.unpack("B" * 4, fileobj.read(4))
    fileobj.close()

    gdaltest.tiff_drv.Delete("tmp/bigtiff.tif")

    # Check BigTIFF signature
    assert not (
        (binvalues[2] != 0x2B or binvalues[3] != 0)
        and (binvalues[3] != 0x2B or binvalues[2] != 0)
    )


###############################################################################
# Create a rotated image


def test_tiff_write_32():

    ds_in = gdal.Open("data/byte.vrt")

    # Test creation
    ds = gdaltest.tiff_drv.Create("tmp/byte_rotated.tif", 20, 20, gdal.GDT_Byte)

    gt = (10, 3.53553390593, 3.53553390593, 30, 3.53553390593, -3.53553390593)
    ds.SetGeoTransform(gt)

    data = ds_in.ReadRaster(0, 0, 20, 20)
    ds.WriteRaster(0, 0, 20, 20, data)

    ds_in = None

    # Test copy
    new_ds = gdaltest.tiff_drv.CreateCopy("tmp/byte_rotated_copy.tif", ds)
    del new_ds

    # Check copy
    ds = gdal.Open("tmp/byte_rotated_copy.tif")
    new_gt = ds.GetGeoTransform()
    for i in range(6):
        if new_gt[i] != pytest.approx(gt[i], abs=1e-5):
            print("")
            print(("old = ", gt))
            print(("new = ", new_gt))
            pytest.fail("Geotransform differs.")

    ds = None

    gdaltest.tiff_drv.Delete("tmp/byte_rotated.tif")
    gdaltest.tiff_drv.Delete("tmp/byte_rotated_copy.tif")


###############################################################################
# Test that metadata is written in .aux.xml file in GeoTIFF profile with CreateCopy
# (BASELINE is tested by tiff_write_15)


def test_tiff_write_33():

    ds_in = gdal.Open("data/byte.vrt")

    ds = gdaltest.tiff_drv.CreateCopy(
        "tmp/tw_33.tif", ds_in, options=["PROFILE=GeoTIFF"]
    )

    ds_in = None

    ds = None

    ds = gdal.Open("tmp/tw_33.tif")

    md = ds.GetMetadata()
    assert "test" in md, "Metadata absent from .aux.xml file."

    md = ds.GetRasterBand(1).GetMetadata()
    assert "testBand" in md, "Metadata absent from .aux.xml file."

    ds = None

    try:
        os.remove("tmp/tw_33.tif.aux.xml")
    except OSError:
        try:
            os.stat("tmp/tw_33.tif.aux.xml")
        except OSError:
            pytest.fail("No .aux.xml file.")

    ds = gdal.Open("tmp/tw_33.tif")

    md = ds.GetMetadata()
    assert "test" not in md, "Metadata written to GeoTIFF file."

    md = ds.GetRasterBand(1).GetMetadata()
    assert "testBand" not in md, "Metadata written to GeoTIFF file."

    ds = None

    gdaltest.tiff_drv.Delete("tmp/tw_33.tif")


###############################################################################
# Test that metadata is written in .aux.xml file in GeoTIFF profile with Create
# (BASELINE is tested by tiff_write_16)


def test_tiff_write_34():

    ds = gdaltest.tiff_drv.Create(
        "tmp/tw_34.tif", 1, 1, gdal.GDT_Byte, options=["PROFILE=GeoTIFF"]
    )
    ds.SetMetadata({"test": "testvalue"})
    ds.GetRasterBand(1).SetMetadata({"testBand": "testvalueBand"})

    ds = None

    ds = gdal.Open("tmp/tw_34.tif")

    md = ds.GetMetadata()
    assert "test" in md, "Metadata absent from .aux.xml file."

    md = ds.GetRasterBand(1).GetMetadata()
    assert "testBand" in md, "Metadata absent from .aux.xml file."

    ds = None

    try:
        os.remove("tmp/tw_34.tif.aux.xml")
    except OSError:
        try:
            os.stat("tmp/tw_34.tif.aux.xml")
        except OSError:
            pytest.fail("No .aux.xml file.")

    ds = gdal.Open("tmp/tw_34.tif")

    md = ds.GetMetadata()
    assert "test" not in md, "Metadata written to GeoTIFF file."

    md = ds.GetRasterBand(1).GetMetadata()
    assert "testBand" not in md, "Metadata written to GeoTIFF file."

    ds = None

    gdaltest.tiff_drv.Delete("tmp/tw_34.tif")


###############################################################################
# Test big metadata (that was used to consider too big to fit into the GDALGeotiff tag
# before GDAL 3.4.2)


def test_tiff_write_35():

    big_string = "a" * 12345678
    ds = gdaltest.tiff_drv.Create("tmp/tw_35.tif", 1, 1, gdal.GDT_Byte)

    md = {}
    md["test"] = big_string
    ds.SetMetadata(md)
    ds = None

    assert not os.path.exists("tmp/tw_35.tif.aux.xml")

    ds = gdal.Open("tmp/tw_35.tif")
    assert ds.GetMetadataItem("test") == big_string
    ds = None

    gdaltest.tiff_drv.Delete("tmp/tw_35.tif")


###############################################################################
# Generic functions for the 8 following tests


def tiff_write_big_odd_bits(vrtfilename, tmpfilename, nbits, interleaving):
    ds_in = gdal.Open(vrtfilename)

    ds = gdaltest.tiff_drv.CreateCopy(
        tmpfilename,
        ds_in,
        options=["NBITS=" + str(nbits), "INTERLEAVE=" + interleaving],
    )

    ds_in = None

    ds = None

    ds = gdal.Open(tmpfilename)
    bnd = ds.GetRasterBand(1)
    cs = bnd.Checksum()
    assert cs == 4672, "Didn't get expected checksum on band 1"
    md = bnd.GetMetadata("IMAGE_STRUCTURE")
    assert md["NBITS"] == str(nbits), "Didn't get expected NBITS value"

    bnd = ds.GetRasterBand(2)
    assert bnd.Checksum() == 4672, "Didn't get expected checksum on band 2"
    bnd = ds.GetRasterBand(3)
    assert bnd.Checksum() == 4672, "Didn't get expected checksum on band 3"
    bnd = None

    md = ds.GetMetadata("IMAGE_STRUCTURE")
    assert md["INTERLEAVE"] == interleaving, "Didn't get expected interleaving"

    ds = None

    gdaltest.tiff_drv.Delete(tmpfilename)


###############################################################################
# Test copy with NBITS=9, INTERLEAVE=PIXEL


def test_tiff_write_36():
    return tiff_write_big_odd_bits("data/uint16_3band.vrt", "tmp/tw_36.tif", 9, "PIXEL")


###############################################################################
# Test copy with NBITS=9, INTERLEAVE=BAND


def test_tiff_write_37():
    return tiff_write_big_odd_bits("data/uint16_3band.vrt", "tmp/tw_37.tif", 9, "BAND")


###############################################################################
# Test copy with NBITS=12, INTERLEAVE=PIXEL


def test_tiff_write_38():
    return tiff_write_big_odd_bits(
        "data/uint16_3band.vrt", "tmp/tw_38.tif", 12, "PIXEL"
    )


###############################################################################
# Test copy with NBITS=12, INTERLEAVE=BAND


def test_tiff_write_39():
    return tiff_write_big_odd_bits("data/uint16_3band.vrt", "tmp/tw_39.tif", 12, "BAND")


###############################################################################
# Test copy with NBITS=17, INTERLEAVE=PIXEL


def test_tiff_write_40():
    return tiff_write_big_odd_bits("data/uint32_3band.vrt", "tmp/tw_40tif", 17, "PIXEL")


###############################################################################
# Test copy with NBITS=17, INTERLEAVE=BAND


def test_tiff_write_41():
    return tiff_write_big_odd_bits("data/uint32_3band.vrt", "tmp/tw_41.tif", 17, "BAND")


###############################################################################
# Test copy with NBITS=24, INTERLEAVE=PIXEL


def test_tiff_write_42():
    return tiff_write_big_odd_bits(
        "data/uint32_3band.vrt", "tmp/tw_42.tif", 24, "PIXEL"
    )


###############################################################################
# Test copy with NBITS=24, INTERLEAVE=BAND


def test_tiff_write_43():
    return tiff_write_big_odd_bits("data/uint32_3band.vrt", "tmp/tw_43.tif", 24, "BAND")


###############################################################################
# Test create with NBITS=9 and preservation through CreateCopy of NBITS


def test_tiff_write_44():

    ds = gdaltest.tiff_drv.Create(
        "tmp/tw_44.tif", 1, 1, 1, gdal.GDT_UInt16, options=["NBITS=9"]
    )
    ds = None
    ds = gdal.Open("tmp/tw_44.tif")
    bnd = ds.GetRasterBand(1)
    md = bnd.GetMetadata("IMAGE_STRUCTURE")
    bnd = None
    assert md["NBITS"] == "9", "Didn't get expected NBITS value"

    ds2 = gdaltest.tiff_drv.CreateCopy("tmp/tw_44_copy.tif", ds)
    ds2 = None

    ds2 = gdal.Open("tmp/tw_44_copy.tif")
    bnd = ds2.GetRasterBand(1)
    md = bnd.GetMetadata("IMAGE_STRUCTURE")
    bnd = None
    assert md["NBITS"] == "9", "Didn't get expected NBITS value"

    ds = None
    ds2 = None

    gdaltest.tiff_drv.Delete("tmp/tw_44.tif")
    gdaltest.tiff_drv.Delete("tmp/tw_44_copy.tif")


###############################################################################
# Test create with NBITS=17 and preservation through CreateCopy of NBITS


def test_tiff_write_45():

    ds = gdaltest.tiff_drv.Create(
        "tmp/tw_45.tif", 1, 1, 1, gdal.GDT_UInt32, options=["NBITS=17"]
    )
    ds = None
    ds = gdal.Open("tmp/tw_45.tif")
    bnd = ds.GetRasterBand(1)
    md = bnd.GetMetadata("IMAGE_STRUCTURE")
    bnd = None
    assert md["NBITS"] == "17", "Didn't get expected NBITS value"

    ds2 = gdaltest.tiff_drv.CreateCopy("tmp/tw_45_copy.tif", ds)
    ds2 = None

    ds2 = gdal.Open("tmp/tw_45_copy.tif")
    bnd = ds2.GetRasterBand(1)
    md = bnd.GetMetadata("IMAGE_STRUCTURE")
    bnd = None
    assert md["NBITS"] == "17", "Didn't get expected NBITS value"

    ds = None
    ds2 = None

    gdaltest.tiff_drv.Delete("tmp/tw_45.tif")
    gdaltest.tiff_drv.Delete("tmp/tw_45_copy.tif")


###############################################################################
# Test correct round-tripping of ReadBlock/WriteBlock


def test_tiff_write_46():

    with gdaltest.SetCacheMax(0):

        ds = gdaltest.tiff_drv.Create(
            "tmp/tiff_write_46_1.tif", 10, 10, 1, options=["NBITS=1"]
        )
        ds.GetRasterBand(1).Fill(0)

        ds2 = gdaltest.tiff_drv.Create(
            "tmp/tiff_write_46_2.tif", 10, 10, 1, options=["NBITS=1"]
        )
        ds2.GetRasterBand(1).Fill(1)
        ones = ds2.ReadRaster(0, 0, 10, 1)

        # Load the working block
        data = ds.ReadRaster(0, 0, 10, 1)

        # Write the working bloc
        ds.WriteRaster(0, 0, 10, 1, ones)

        # This will discard the cached block for ds
        ds3 = gdaltest.tiff_drv.Create("tmp/tiff_write_46_3.tif", 10, 10, 1)
        ds3.GetRasterBand(1).Fill(1)

        # Load the working block again
        data = ds.ReadRaster(0, 0, 10, 1)

        # We expect (1, 1, 1, 1, 1, 1, 1, 1, 1, 1)
        got = struct.unpack("B" * 10, data)
        for g in got:
            assert g == 1, got

    ds = None
    ds2 = None
    ds3 = None
    gdaltest.tiff_drv.Delete("tmp/tiff_write_46_1.tif")
    gdaltest.tiff_drv.Delete("tmp/tiff_write_46_2.tif")
    gdaltest.tiff_drv.Delete("tmp/tiff_write_46_3.tif")


###############################################################################
# Test #2457


def test_tiff_write_47():

    with gdaltest.SetCacheMax(0):
        test_tiff_write_3()


###############################################################################
# Test #2457 with nYOff of RasterIO not aligned on the block height


def test_tiff_write_48():

    with gdaltest.SetCacheMax(0):

        src_ds = gdal.Open("data/utmsmall.tif")
        new_ds = gdal.GetDriverByName("GTiff").Create(
            "tmp/tiff_write_48.tif",
            100,
            100,
            1,
            options=["TILED=YES", "BLOCKXSIZE=96", "BLOCKYSIZE=96"],
        )
        data = src_ds.ReadRaster(0, 0, 100, 1)
        data2 = src_ds.ReadRaster(0, 1, 100, 99)
        new_ds.WriteRaster(0, 1, 100, 99, data2)
        new_ds.WriteRaster(0, 0, 100, 1, data)
        new_ds = None

    new_ds = None
    new_ds = gdal.Open("tmp/tiff_write_48.tif")
    assert new_ds.GetRasterBand(1).Checksum() == 50054, "Didn't get expected checksum "

    new_ds = None

    gdaltest.tiff_drv.Delete("tmp/tiff_write_48.tif")


###############################################################################
# Test copying a CMYK TIFF into another CMYK TIFF


def test_tiff_write_49():

    # We open the source as RAW to get the CMYK bands
    src_ds = gdal.Open("GTIFF_RAW:data/rgbsmall_cmyk.tif")

    new_ds = gdal.GetDriverByName("GTiff").CreateCopy(
        "tmp/tiff_write_49.tif", src_ds, options=["PHOTOMETRIC=CMYK"]
    )

    # At this point, for the purpose of the copy, the dataset will have been opened as RAW
    assert (
        new_ds.GetRasterBand(1).GetRasterColorInterpretation() == gdal.GCI_CyanBand
    ), "Wrong color interpretation."

    new_ds = None

    new_ds = gdal.Open("GTIFF_RAW:tmp/tiff_write_49.tif")

    for i in range(4):
        assert (
            new_ds.GetRasterBand(i + 1).Checksum()
            == src_ds.GetRasterBand(i + 1).Checksum()
        ), "Didn't get expected checksum "

    src_ds = None
    new_ds = None

    gdaltest.tiff_drv.Delete("tmp/tiff_write_49.tif")


###############################################################################
# Test creating a CMYK TIFF from another CMYK TIFF


def test_tiff_write_50():

    # We open the source as RAW to get the CMYK bands
    src_ds = gdal.Open("GTIFF_RAW:data/rgbsmall_cmyk.tif")

    new_ds = gdal.GetDriverByName("GTiff").Create(
        "tmp/tiff_write_50.tif",
        src_ds.RasterXSize,
        src_ds.RasterYSize,
        4,
        options=["PHOTOMETRIC=CMYK"],
    )
    for i in range(4):
        data = src_ds.GetRasterBand(i + 1).ReadRaster(
            0, 0, src_ds.RasterXSize, src_ds.RasterYSize
        )
        new_ds.GetRasterBand(i + 1).WriteRaster(
            0, 0, src_ds.RasterXSize, src_ds.RasterYSize, data
        )

    assert (
        new_ds.GetRasterBand(1).GetRasterColorInterpretation() == gdal.GCI_CyanBand
    ), "Wrong color interpretation."

    new_ds = None

    new_ds = gdal.Open("GTIFF_RAW:tmp/tiff_write_50.tif")

    for i in range(4):
        assert (
            new_ds.GetRasterBand(i + 1).Checksum()
            == src_ds.GetRasterBand(i + 1).Checksum()
        ), "Didn't get expected checksum "

    src_ds = None
    new_ds = None

    gdaltest.tiff_drv.Delete("tmp/tiff_write_50.tif")


###############################################################################
# Test proper clearing of existing GeoTIFF tags when updating the projection.
# http://trac.osgeo.org/gdal/ticket/2546


def test_tiff_write_51():
    shutil.copyfile("data/utmsmall.tif", "tmp/tiff_write_51.tif")

    ds = gdal.Open("tmp/tiff_write_51.tif", gdal.GA_Update)

    srs = osr.SpatialReference()
    srs.SetFromUserInput("EPSG:32601")
    ds.SetProjection(srs.ExportToWkt())
    ds = None

    ds = gdal.Open("tmp/tiff_write_51.tif")
    wkt = ds.GetProjection()
    ds = None

    # Create a new GeoTIFF file with same projection
    ds = gdaltest.tiff_drv.Create("tmp/tiff_write_51_ref.tif", 1, 1, 1)
    ds.SetProjection(srs.ExportToWkt())
    ds = None

    # Read it back as the reference WKT
    ds = gdal.Open("tmp/tiff_write_51_ref.tif")
    expected_wkt = ds.GetProjection()
    ds = None

    assert (
        wkt.find("NAD") == -1 and wkt.find("North Am") == -1
    ), "It appears the NAD27 datum was not properly cleared."

    assert (
        wkt == expected_wkt and wkt.find("WGS 84 / UTM zone 1N") != -1
    ), "coordinate system does not exactly match."

    ds = None

    gdaltest.tiff_drv.Delete("tmp/tiff_write_51.tif")
    gdaltest.tiff_drv.Delete("tmp/tiff_write_51_ref.tif")


###############################################################################
# Test the ability to update a paletted TIFF files color table.


def test_tiff_write_52():
    shutil.copyfile("data/test_average_palette.tif", "tmp/tiff_write_52.tif")

    test_ct_data = [(255, 0, 0), (0, 255, 0), (0, 0, 255), (255, 255, 255, 0)]

    test_ct = gdal.ColorTable()
    for i, data in enumerate(test_ct_data):
        test_ct.SetColorEntry(i, data)

    ds = gdal.Open("tmp/tiff_write_52.tif", gdal.GA_Update)
    ds.GetRasterBand(1).SetRasterColorTable(test_ct)
    ds = None

    ds = gdal.Open("tmp/tiff_write_52.tif")
    ct = ds.GetRasterBand(1).GetRasterColorTable()

    assert ct.GetColorEntry(0) == (255, 0, 0, 255), "Did not get expected color 0."

    ct = None
    ds = None

    gdaltest.tiff_drv.Delete("tmp/tiff_write_52.tif")


###############################################################################
# Test the ability to create a paletted image and then update later.


def test_tiff_write_53():
    test_ct_data = [(255, 0, 0), (0, 255, 0), (0, 0, 255), (255, 255, 255, 0)]

    test_ct = gdal.ColorTable()
    for i, data in enumerate(test_ct_data):
        test_ct.SetColorEntry(i, data)

    ds = gdaltest.tiff_drv.Create(
        "tmp/tiff_write_53.tif", 30, 50, 1, options=["PHOTOMETRIC=PALETTE"]
    )
    ds.GetRasterBand(1).Fill(10)
    ds = None

    ds = gdal.Open("tmp/tiff_write_53.tif", gdal.GA_Update)
    ds.GetRasterBand(1).SetRasterColorTable(test_ct)
    ds = None

    ds = gdal.Open("tmp/tiff_write_53.tif")
    ct = ds.GetRasterBand(1).GetRasterColorTable()

    assert ct.GetColorEntry(0) == (255, 0, 0, 255), "Did not get expected color 0."

    ct = None
    ds = None

    gdaltest.tiff_drv.Delete("tmp/tiff_write_53.tif")


###############################################################################
# Same as before except we create an overview before reopening the file and
# adding the color table


def test_tiff_write_53_bis():
    test_ct_data = [(255, 0, 0), (0, 255, 0), (0, 0, 255), (255, 255, 255, 0)]

    test_ct = gdal.ColorTable()
    for i, data in enumerate(test_ct_data):
        test_ct.SetColorEntry(i, data)

    ds = gdaltest.tiff_drv.Create(
        "tmp/tiff_write_53_bis.tif", 30, 50, 1, options=["PHOTOMETRIC=PALETTE"]
    )
    ds.GetRasterBand(1).Fill(10)
    ds.BuildOverviews("NONE", overviewlist=[2])
    ds = None

    ds = gdal.Open("tmp/tiff_write_53_bis.tif", gdal.GA_Update)
    ds.GetRasterBand(1).SetRasterColorTable(test_ct)
    ds = None

    ds = gdal.Open("tmp/tiff_write_53_bis.tif")
    ct = ds.GetRasterBand(1).GetRasterColorTable()

    assert ct.GetColorEntry(0) == (255, 0, 0, 255), "Did not get expected color 0."

    ct = None
    ds = None

    gdaltest.tiff_drv.Delete("tmp/tiff_write_53_bis.tif")


###############################################################################
# Test the ability to create a JPEG compressed TIFF, with PHOTOMETRIC=YCBCR
# and write data into it without closing it and re-opening it (#2645)


@pytest.mark.require_creation_option("GTiff", "JPEG")
def test_tiff_write_54():

    ds = gdaltest.tiff_drv.Create(
        "tmp/tiff_write_54.tif",
        256,
        256,
        3,
        options=["TILED=YES", "COMPRESS=JPEG", "PHOTOMETRIC=YCBCR"],
    )
    ds.GetRasterBand(1).Fill(255)
    ds.FlushCache()
    ds = None

    ds = gdal.Open("tmp/tiff_write_54.tif")
    cs = ds.GetRasterBand(1).Checksum()
    ds = None

    gdaltest.tiff_drv.Delete("tmp/tiff_write_54.tif")

    assert cs != 0, "did not get expected checksum"


###############################################################################
# Test creating and reading an equirectangular file with all parameters (#2706)


def test_tiff_write_55():

    ds = gdaltest.tiff_drv.Create("tmp/tiff_write_55.tif", 256, 256, 1)
    srs_expected = 'PROJCS["Equirectangular Mars",GEOGCS["GCS_Mars",DATUM["unknown",SPHEROID["unnamed",3394813.85797594,0]],PRIMEM["Greenwich",0],UNIT["degree",0.0174532925199433,AUTHORITY["EPSG","9122"]]],PROJECTION["Equirectangular"],PARAMETER["latitude_of_origin",-2],PARAMETER["central_meridian",184.412994384766],PARAMETER["standard_parallel_1",-15],PARAMETER["false_easting",0],PARAMETER["false_northing",0],UNIT["metre",1,AUTHORITY["EPSG","9001"]],AXIS["Easting",EAST],AXIS["Northing",NORTH]]'

    ds.SetProjection(srs_expected)

    ds.SetGeoTransform((100, 1, 0, 200, 0, -1))
    ds = None

    ds = gdal.Open("tmp/tiff_write_55.tif")
    srs = ds.GetProjectionRef()
    ds = None

    assert (
        srs == srs_expected
    ), "failed to preserve Equirectangular projection as expected, old libgeotiff?"

    gdaltest.tiff_drv.Delete("tmp/tiff_write_55.tif")


###############################################################################
# Test clearing the colormap from an existing paletted TIFF file.


def test_tiff_write_56():

    test_ct_data = [(255, 0, 0), (0, 255, 0), (0, 0, 255), (255, 255, 255, 0)]

    test_ct = gdal.ColorTable()
    for i, data in enumerate(test_ct_data):
        test_ct.SetColorEntry(i, data)

    ds = gdaltest.tiff_drv.Create(
        "tmp/tiff_write_56.tif", 30, 50, 1, options=["PHOTOMETRIC=PALETTE"]
    )
    ds.GetRasterBand(1).Fill(10)
    ds = None

    test_ct = gdal.ColorTable()

    ds = gdal.Open("tmp/tiff_write_56.tif", gdal.GA_Update)
    ds.GetRasterBand(1).SetRasterColorTable(test_ct)
    ds = None

    ds = gdal.Open("tmp/tiff_write_56.tif")
    ct = ds.GetRasterBand(1).GetRasterColorTable()

    assert ct is None, "color table seemingly not cleared."

    ct = None
    ds = None

    gdaltest.tiff_drv.Delete("tmp/tiff_write_56.tif")


###############################################################################
# Test replacing normal norm up georef with rotated georef (#2625)


def test_tiff_write_57():

    # copy a file to tmp dir to modify.
    open("tmp/tiff57.tif", "wb").write(open("data/byte.tif", "rb").read())

    # open and set a non-northup geotransform.

    ds = gdal.Open("tmp/tiff57.tif", gdal.GA_Update)
    ds.SetGeoTransform([100, 1, 3, 200, 3, 1])
    ds = None

    ds = gdal.Open("tmp/tiff57.tif")
    gt = ds.GetGeoTransform()
    ds = None

    assert gt == (
        100,
        1,
        3,
        200,
        3,
        1,
    ), "did not get expected geotransform, perhaps unset is not working?"

    gdaltest.tiff_drv.Delete("tmp/tiff57.tif")


###############################################################################
# Test writing partial end strips (#2748)


def test_tiff_write_58():

    md = gdaltest.tiff_drv.GetMetadata()

    for compression in ("NONE", "JPEG", "LZW", "DEFLATE", "PACKBITS"):

        if md["DMD_CREATIONOPTIONLIST"].find(compression) != -1:
            ds = gdaltest.tiff_drv.Create(
                "tmp/tiff_write_58.tif", 4, 4000, 1, options=["COMPRESS=" + compression]
            )
            ds.GetRasterBand(1).Fill(255)
            ds = None

            ds = gdal.Open("tmp/tiff_write_58.tif")
            assert ds.GetRasterBand(1).Checksum() == 65241, "wrong checksum"
            ds = None

            gdaltest.tiff_drv.Delete("tmp/tiff_write_58.tif")
        else:
            print(("Skipping compression method %s" % compression))


###############################################################################
# Test fix for #2759


def test_tiff_write_59():

    for nbands in (1, 2):
        for nbits in (1, 8, 9, 12, 16, 17, 24, 32):

            if nbits <= 8:
                gdal_type = gdal.GDT_Byte
                ctype = "B"
            elif nbits <= 16:
                gdal_type = gdal.GDT_UInt16
                ctype = "h"
            else:
                gdal_type = gdal.GDT_UInt32
                ctype = "i"

            ds = gdaltest.tiff_drv.Create(
                "tmp/tiff_write_59.tif",
                10,
                10,
                nbands,
                gdal_type,
                options=["NBITS=%d" % nbits],
            )
            ds.GetRasterBand(1).Fill(1)

            ds = None
            ds = gdal.Open("tmp/tiff_write_59.tif", gdal.GA_Update)

            data = struct.pack(ctype * 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
            ds.GetRasterBand(1).WriteRaster(0, 0, 10, 1, data)

            ds = None
            ds = gdal.Open("tmp/tiff_write_59.tif")

            data = ds.GetRasterBand(1).ReadRaster(0, 0, 10, 1)

            # We expect zeros
            got = struct.unpack(ctype * 10, data)
            for g in got:
                assert g == 0, (nbands, nbits)
            ds = None
            gdaltest.tiff_drv.Delete("tmp/tiff_write_59.tif")


###############################################################################
# Test fix for #2760


def test_tiff_write_60():

    tuples = [
        ("TFW=YES", "tmp/tiff_write_60.tfw"),
        ("WORLDFILE=YES", "tmp/tiff_write_60.wld"),
    ]

    for options_tuple in tuples:
        # Create case
        with gdal.quiet_errors():
            ds = gdaltest.tiff_drv.Create(
                "tmp/tiff_write_60.tif",
                10,
                10,
                options=[options_tuple[0], "PROFILE=BASELINE"],
            )
        gt = (0.0, 1.0, 0.0, 50.0, 0.0, -1.0)
        ds.SetGeoTransform(gt)
        ds = None

        with gdal.quiet_errors():
            ds = gdal.Open("tmp/tiff_write_60.tif")
        assert ds.GetGeoTransform() == gt, "case1: %s != %s" % (
            ds.GetGeoTransform(),
            gt,
        )

        ds = None
        gdaltest.tiff_drv.Delete("tmp/tiff_write_60.tif")

        assert not os.path.exists(options_tuple[1])

        # CreateCopy case
        src_ds = gdal.Open("data/byte.tif")
        with gdal.quiet_errors():
            ds = gdaltest.tiff_drv.CreateCopy(
                "tmp/tiff_write_60.tif",
                src_ds,
                options=[options_tuple[0], "PROFILE=BASELINE"],
            )
        gt = (0.0, 1.0, 0.0, 50.0, 0.0, -1.0)
        ds.SetGeoTransform(gt)
        ds = None
        gdal.Unlink("tmp/tiff_write_60.tif.aux.xml")

        ds = gdal.Open("tmp/tiff_write_60.tif")
        assert ds.GetGeoTransform() == gt, "case2: %s != %s" % (
            ds.GetGeoTransform(),
            gt,
        )

        ds = None
        gdaltest.tiff_drv.Delete("tmp/tiff_write_60.tif")

        assert not os.path.exists(options_tuple[1])


###############################################################################
# Test BigTIFF=IF_NEEDED creation option


def test_tiff_write_61():

    ds = gdaltest.tiff_drv.Create(
        "tmp/bigtiff.tif",
        50000,
        50000,
        1,
        options=["BIGTIFF=IF_NEEDED", "SPARSE_OK=TRUE"],
    )
    ds = None

    ds = gdal.Open("tmp/bigtiff.tif")
    assert ds is not None
    ds = None

    fileobj = open("tmp/bigtiff.tif", mode="rb")
    binvalues = struct.unpack("B" * 4, fileobj.read(4))
    fileobj.close()

    gdaltest.tiff_drv.Delete("tmp/bigtiff.tif")

    # Check classical TIFF signature
    assert not (
        (binvalues[2] != 0x2A or binvalues[3] != 0)
        and (binvalues[3] != 0x2A or binvalues[2] != 0)
    )


###############################################################################
# Test BigTIFF=IF_SAFER creation option


def test_tiff_write_62():

    ds = gdaltest.tiff_drv.Create(
        "tmp/bigtiff.tif",
        50000,
        50000,
        1,
        options=["BIGTIFF=IF_SAFER", "SPARSE_OK=TRUE"],
    )
    ds = None

    ds = gdal.Open("tmp/bigtiff.tif")
    assert ds is not None
    ds = None

    fileobj = open("tmp/bigtiff.tif", mode="rb")
    binvalues = struct.unpack("B" * 4, fileobj.read(4))
    fileobj.close()

    gdaltest.tiff_drv.Delete("tmp/bigtiff.tif")

    # Check BigTIFF signature
    assert not (
        (binvalues[2] != 0x2B or binvalues[3] != 0)
        and (binvalues[3] != 0x2B or binvalues[2] != 0)
    )


###############################################################################
# Test BigTIFF=NO creation option when creating a BigTIFF file would be required


def test_tiff_write_63():

    with gdal.quiet_errors():
        ds = gdaltest.tiff_drv.Create(
            "tmp/bigtiff.tif", 150000, 150000, 1, options=["BIGTIFF=NO"]
        )

    if ds is None:
        return

    pytest.fail()


###############################################################################
# Test returned projection in WKT format for a WGS84 GeoTIFF (#2787)


def test_tiff_write_64():

    ds = gdaltest.tiff_drv.Create("tmp/tiff_write_64.tif", 1, 1, 1)
    srs = osr.SpatialReference()
    srs.SetFromUserInput("WGS84")
    srs.SetAxisMappingStrategy(osr.OAMS_TRADITIONAL_GIS_ORDER)
    ds.SetSpatialRef(srs)
    ds = None

    ds = gdal.Open("tmp/tiff_write_64.tif")
    got_srs = ds.GetSpatialRef()
    assert got_srs.IsSame(srs)
    ds = None

    gdaltest.tiff_drv.Delete("tmp/tiff_write_64.tif")


###############################################################################
# Verify that we can write XML metadata.


def test_tiff_write_65():

    ds = gdaltest.tiff_drv.Create("tmp/tiff_write_65.tif", 10, 10)

    doc = '<doc><test xml:attr="abc"/></doc>'
    ds.SetMetadata([doc], "xml:test")

    ds = None

    ds = gdal.Open("tmp/tiff_write_65.tif")
    md = ds.GetMetadata("xml:test")
    ds = None

    assert len(md) == 1 and md[0] == doc, "did not get xml back clean"

    gdaltest.tiff_drv.Delete("tmp/tiff_write_65.tif")


###############################################################################
# Verify that we can write and read a band-interleaved GeoTIFF with 65535 bands (#2838)


def test_tiff_write_66():

    if gdal.GetConfigOption("SKIP_MEM_INTENSIVE_TEST") is not None:
        pytest.skip()

    ds = gdaltest.tiff_drv.Create(
        "tmp/tiff_write_66.tif", 1, 1, 65535, options=["INTERLEAVE=BAND"]
    )
    ds = None

    ds = gdal.Open("tmp/tiff_write_66.tif")
    assert ds.RasterCount == 65535

    assert ds.GetRasterBand(1).Checksum() == 0

    assert ds.GetRasterBand(65535).Checksum() == 0

    ds = None

    gdaltest.tiff_drv.Delete("tmp/tiff_write_66.tif")


###############################################################################
# Verify that we can write and read a pixel-interleaved GeoTIFF with 65535 bands (#2838)


def test_tiff_write_67():

    if gdal.GetConfigOption("SKIP_MEM_INTENSIVE_TEST") is not None:
        pytest.skip()

    ds = gdaltest.tiff_drv.Create(
        "tmp/tiff_write_67.tif", 1, 1, 65535, options=["INTERLEAVE=PIXEL"]
    )
    ds = None

    ds = gdal.Open("tmp/tiff_write_67.tif")
    assert ds.RasterCount == 65535

    assert ds.GetRasterBand(1).Checksum() == 0

    assert ds.GetRasterBand(65535).Checksum() == 0

    ds = None

    gdaltest.tiff_drv.Delete("tmp/tiff_write_67.tif")


###############################################################################
# Verify that we can set the color table after a Create() (scenario hit by map.tif in #2820)


def test_tiff_write_68():

    ds = gdaltest.tiff_drv.Create(
        "tmp/tiff_write_68.tif", 151, 161, options=["COMPRESS=LZW"]
    )
    ct = gdal.ColorTable()
    ct.SetColorEntry(0, (255, 255, 255, 255))
    ct.SetColorEntry(1, (255, 255, 0, 255))
    ct.SetColorEntry(2, (255, 0, 255, 255))
    ct.SetColorEntry(3, (0, 255, 255, 255))
    ds.GetRasterBand(1).SetRasterColorTable(ct)
    ds.GetRasterBand(1).Fill(255)
    ds = None

    ds = gdal.Open("tmp/tiff_write_68.tif")
    assert ds.GetRasterBand(1).Checksum() != 0
    ds = None

    gdaltest.tiff_drv.Delete("tmp/tiff_write_68.tif")


###############################################################################
# Verify GTiffRasterBand::NullBlock() when reading empty block without any nodata value set


def test_tiff_write_69():

    ds = gdaltest.tiff_drv.Create(
        "tmp/tiff_write_69.tif", 32, 32, 1, gdal.GDT_Int16, options=["SPARSE_OK=YES"]
    )
    ds = None

    ds = gdal.Open("tmp/tiff_write_69.tif")
    assert ds.GetRasterBand(1).Checksum() == 0
    ds = None

    gdaltest.tiff_drv.Delete("tmp/tiff_write_69.tif")


###############################################################################
# Verify GTiffRasterBand::NullBlock() when reading empty block with nodata value set


def test_tiff_write_70():

    ref_ds = gdaltest.tiff_drv.Create(
        "tmp/tiff_write_70_ref.tif", 32, 32, 1, gdal.GDT_Int16
    )
    ref_ds.GetRasterBand(1).Fill(-32768)
    ref_ds = None

    ref_ds = gdal.Open("tmp/tiff_write_70_ref.tif")
    expected_cs = ref_ds.GetRasterBand(1).Checksum()
    ref_ds = None

    ds = gdaltest.tiff_drv.Create(
        "tmp/tiff_write_70.tif", 32, 32, 1, gdal.GDT_Int16, options=["SPARSE_OK=YES"]
    )
    ds.GetRasterBand(1).SetNoDataValue(0)
    assert (
        os.stat("tmp/tiff_write_70.tif").st_size <= 8
    ), "directory should not be crystallized"
    ds = None

    ds = gdal.Open("tmp/tiff_write_70.tif", gdal.GA_Update)
    ds.GetRasterBand(1).SetNoDataValue(-32768)
    ds = None

    ds = gdal.Open("tmp/tiff_write_70.tif")
    assert ds.GetRasterBand(1).Checksum() == expected_cs, "wrong checksum"
    ds = None

    ds = gdal.Open("tmp/tiff_write_70.tif", gdal.GA_Update)
    assert ds.GetRasterBand(1).DeleteNoDataValue() == 0
    assert ds.GetRasterBand(1).GetNoDataValue() is None
    ds = None

    with pytest.raises(OSError):
        os.stat("tmp/tiff_write_70.tif.aux.xml")

    ds = gdal.Open("tmp/tiff_write_70.tif")
    assert ds.GetRasterBand(1).GetNoDataValue() is None
    ds = None

    gdaltest.tiff_drv.Delete("tmp/tiff_write_70.tif")
    gdaltest.tiff_drv.Delete("tmp/tiff_write_70_ref.tif")


###############################################################################
# Test reading in a real BigTIFF file (on filesystems supporting sparse files)


def test_tiff_write_71():

    # Determine if the filesystem supports sparse files (we don't want to create a real 10 GB
    # file !
    if not gdaltest.filesystem_supports_sparse_files("tmp"):
        pytest.skip()

    header = open("data/bigtiff_header_extract.tif", "rb").read()

    f = open("tmp/tiff_write_71.tif", "wb")
    f.write(header)

    # Write StripByteCounts tag
    # 100,000 in little endian
    for _ in range(100000):
        f.write(b"\xa0\x86\x01\x00\x00\x00\x00\x00")

    # Write StripOffsets tag
    offset = 1600252
    for _ in range(100000):
        f.write(struct.pack("<Q", offset))
        offset = offset + 100000

    # Write 0x78 as value of pixel (99999, 99999)
    f.seek(10001600252 - 1, 0)
    f.write(b"\x78")
    f.close()

    ds = gdal.Open("tmp/tiff_write_71.tif")
    data = ds.GetRasterBand(1).ReadRaster(99999, 99999, 1, 1)
    assert struct.unpack("b", data)[0] == 0x78
    ds = None

    gdaltest.tiff_drv.Delete("tmp/tiff_write_71.tif")


###############################################################################
# With CreateCopy(), check that TIFF directory is in the first bytes of the file
# and has not been rewritten later (#3021)


def test_tiff_write_72():

    shutil.copyfile("data/byte.tif", "tmp/byte.tif")
    ds = gdal.Open("tmp/byte.tif", gdal.GA_Update)
    ds.SetMetadata({"TEST_KEY": "TestValue"})
    ds = None

    for profile in ("GDALGeotiff", "GEOTIFF", "BASELINE"):
        src_ds = gdal.Open("tmp/byte.tif")
        out_ds = gdaltest.tiff_drv.CreateCopy(
            "tmp/tiff_write_72.tif",
            src_ds,
            options=["ENDIANNESS=LITTLE", "PROFILE=" + profile],
        )
        del out_ds
        src_ds = None

        fileobj = open("tmp/tiff_write_72.tif", mode="rb")
        fileobj.seek(4)
        binvalues = struct.unpack("B" * 4, fileobj.read(4))
        fileobj.close()

        # Directory should be at offset 8 of the file
        assert (
            binvalues[0] == 0x08
            and binvalues[1] == 0x00
            and binvalues[2] == 0x00
            and binvalues[3] == 0x00
        ), ("Failed with profile %s" % profile)

    gdaltest.tiff_drv.Delete("tmp/byte.tif")
    gdaltest.tiff_drv.Delete("tmp/tiff_write_72.tif")


###############################################################################
# With Create(), check that TIFF directory is in the first bytes of the file
# and has not been rewritten later (#3021)


def test_tiff_write_73():

    out_ds = gdaltest.tiff_drv.Create(
        "tmp/tiff_write_73.tif", 10, 10, options=["ENDIANNESS=LITTLE"]
    )
    out_ds.SetGeoTransform([1, 0.01, 0, 1, 0, -0.01])
    srs = osr.SpatialReference()
    srs.SetFromUserInput("EPSG:32601")
    out_ds.SetProjection(srs.ExportToWkt())
    out_ds.SetMetadata({"TEST_KEY": "TestValue"})
    out_ds.BuildOverviews("NONE", [2])
    out_ds.GetRasterBand(1).Fill(255)
    out_ds = None

    fileobj = open("tmp/tiff_write_73.tif", mode="rb")
    fileobj.seek(4)
    binvalues = struct.unpack("B" * 4, fileobj.read(4))
    fileobj.close()

    # Directory should be at offset 8 of the file
    assert (
        binvalues[0] == 0x08
        and binvalues[1] == 0x00
        and binvalues[2] == 0x00
        and binvalues[3] == 0x00
    )

    # Re-open the file and modify the pixel content
    out_ds = gdal.Open("tmp/tiff_write_73.tif", gdal.GA_Update)
    out_ds.GetRasterBand(1).Fill(0)
    out_ds = None

    fileobj = open("tmp/tiff_write_73.tif", mode="rb")
    fileobj.seek(4)
    binvalues = struct.unpack("B" * 4, fileobj.read(4))
    fileobj.close()

    # Directory should be at offset 8 of the file
    assert (
        binvalues[0] == 0x08
        and binvalues[1] == 0x00
        and binvalues[2] == 0x00
        and binvalues[3] == 0x00
    )

    gdaltest.tiff_drv.Delete("tmp/tiff_write_73.tif")


###############################################################################
# Verify we can write 12bit jpeg encoded tiff.


@pytest.mark.skipif(
    "SKIP_TIFF_JPEG12" in os.environ, reason="Crashes on build-windows-msys2-mingw"
)
@pytest.mark.require_creation_option("GTiff", "JPEG")
def test_tiff_write_74():

    with gdal.config_option("CPL_ACCUM_ERROR_MSG", "ON"):
        gdal.ErrorReset()
        with gdal.quiet_errors():

            try:
                ds = gdal.Open("data/mandrilmini_12bitjpeg.tif")
                ds.GetRasterBand(1).ReadRaster(0, 0, 1, 1)
            except Exception:
                ds = None

    if gdal.GetLastErrorMsg().find("Unsupported JPEG data precision 12") != -1:
        pytest.skip("12bit jpeg not available")

    for photometric in ("YCBCR", "RGB"):

        drv = gdal.GetDriverByName("GTiff")
        dst_ds = drv.CreateCopy(
            "tmp/test_74.tif",
            ds,
            options=[
                "COMPRESS=JPEG",
                "NBITS=12",
                "JPEG_QUALITY=95",
                "PHOTOMETRIC=" + photometric,
            ],
        )
        dst_ds = None

        dst_ds = gdal.Open("tmp/test_74.tif")
        stats = dst_ds.GetRasterBand(1).GetStatistics(0, 1)

        if stats[2] < 2150 or stats[2] > 2180:
            print(photometric)
            pytest.fail("did not get expected mean for band1.")

        try:
            compression = dst_ds.GetMetadataItem("COMPRESSION", "IMAGE_STRUCTURE")
        except Exception:
            md = dst_ds.GetMetadata("IMAGE_STRUCTURE")
            compression = md["COMPRESSION"]

        if (photometric == "YCBCR" and compression != "YCbCr JPEG") or (
            photometric == "RGB" and compression != "JPEG"
        ):
            print(('COMPRESSION="%s"' % compression))
            pytest.fail("did not get expected COMPRESSION value")

        try:
            nbits = dst_ds.GetRasterBand(3).GetMetadataItem("NBITS", "IMAGE_STRUCTURE")
        except Exception:
            md = dst_ds.GetRasterBand(3).GetMetadata("IMAGE_STRUCTURE")
            nbits = md["NBITS"]

        if nbits != "12":
            print(photometric)
            pytest.fail("did not get expected NBITS value")

        dst_ds = None

        gdaltest.tiff_drv.Delete("tmp/test_74.tif")


###############################################################################
# Verify that FlushCache() alone doesn't cause crash (#3067 )


def test_tiff_write_75():

    ds = gdaltest.tiff_drv.Create("tmp/tiff_write_75.tif", 1, 1, 1)
    ds.FlushCache()
    ds = None

    gdaltest.tiff_drv.Delete("tmp/tiff_write_75.tif")


###############################################################################
# Test generating a G4 band to use the TIFFWriteScanline()


def test_tiff_write_76():

    src_ds = gdal.Open("data/slim_g4.tif")
    compression = src_ds.GetMetadata("IMAGE_STRUCTURE")["COMPRESSION"]
    new_ds = gdaltest.tiff_drv.CreateCopy(
        "tmp/tiff_write_76.tif",
        src_ds,
        options=["BLOCKYSIZE=%d" % src_ds.RasterYSize, "COMPRESS=" + compression],
    )
    new_ds = None
    new_ds = gdal.Open("tmp/tiff_write_76.tif")

    cs = new_ds.GetRasterBand(1).Checksum()
    assert cs == 3322, "Got wrong checksum"

    src_ds = None
    new_ds = None

    gdaltest.tiff_drv.Delete("tmp/tiff_write_76.tif")


###############################################################################
# Test generating & reading a 8bit all-in-one-strip multiband TIFF (#3904)


def test_tiff_write_77():

    src_ds = gdaltest.tiff_drv.Create("tmp/tiff_write_77_src.tif", 1, 5000, 3)
    src_ds.GetRasterBand(2).Fill(255)

    for interleaving in ("PIXEL", "BAND"):
        new_ds = gdaltest.tiff_drv.CreateCopy(
            "tmp/tiff_write_77.tif",
            src_ds,
            options=[
                "BLOCKYSIZE=%d" % src_ds.RasterYSize,
                "COMPRESS=LZW",
                "INTERLEAVE=" + interleaving,
            ],
        )

        for attempt in range(2):

            # Test reading a few samples to check that random reading works
            band_lines = [
                (1, 0),
                (1, 5),
                (1, 3),
                (2, 10),
                (1, 100),
                (2, 1000),
                (2, 500),
                (1, 500),
                (2, 500),
                (2, 4999),
                (2, 4999),
                (3, 4999),
                (1, 4999),
            ]
            for band_line in band_lines:
                cs = new_ds.GetRasterBand(band_line[0]).Checksum(0, band_line[1], 1, 1)
                if band_line[0] == 2:
                    expected_cs = 255 % 7
                else:
                    expected_cs = 0 % 7
                assert cs == expected_cs, "Got wrong checksum"

            # Test whole bands
            for i in range(3):
                cs = new_ds.GetRasterBand(i + 1).Checksum()
                expected_cs = src_ds.GetRasterBand(i + 1).Checksum()
                assert cs == expected_cs, "Got wrong checksum"

            if attempt == 0:
                new_ds = None
                new_ds = gdal.Open("tmp/tiff_write_77.tif")

        new_ds = None

        gdaltest.tiff_drv.Delete("tmp/tiff_write_77.tif")

    src_ds = None
    gdaltest.tiff_drv.Delete("tmp/tiff_write_77_src.tif")


###############################################################################
# Test generating & reading a YCbCr JPEG all-in-one-strip multiband TIFF (#3259)


@pytest.mark.require_creation_option("GTiff", "JPEG")
def test_tiff_write_78():

    src_ds = gdaltest.tiff_drv.Create("tmp/tiff_write_78_src.tif", 16, 2048, 3)
    src_ds.GetRasterBand(2).Fill(255)

    new_ds = gdaltest.tiff_drv.CreateCopy(
        "tmp/tiff_write_78.tif",
        src_ds,
        options=[
            "BLOCKYSIZE=%d" % src_ds.RasterYSize,
            "COMPRESS=JPEG",
            "PHOTOMETRIC=YCBCR",
        ],
    )

    # Make sure the file is flushed so that we re-read from it rather from cached blocks
    new_ds.FlushCache()
    # new_ds = None
    # new_ds = gdal.Open('tmp/tiff_write_78.tif')

    if "GetBlockSize" in dir(gdal.Band):
        (_, blocky) = new_ds.GetRasterBand(1).GetBlockSize()
        if blocky != 1:
            print("")
            print(
                "using regular band (libtiff <= 3.9.2 or <= 4.0.0beta5, or SplitBand disabled by config option)"
            )

    # Test reading a few samples to check that random reading works
    band_lines = [
        (1, 0),
        (1, 5),
        (1, 3),
        (2, 10),
        (1, 100),
        (2, 1000),
        (2, 500),
        (1, 500),
        (2, 500),
        (2, 2047),
        (2, 2047),
        (3, 2047),
        (1, 2047),
    ]
    for band_line in band_lines:
        cs = new_ds.GetRasterBand(band_line[0]).Checksum(0, band_line[1], 1, 1)
        if band_line[0] == 1:
            expected_cs = 0 % 7
        elif band_line[0] == 2:
            expected_cs = 255 % 7
        else:
            # We should expect 0, but due to JPEG YCbCr compression & decompression,
            # this ends up being 1
            expected_cs = 1 % 7
        if cs != expected_cs:
            print(band_line)
            pytest.fail("Got wrong checksum")

    # Test whole bands
    for i in range(3):
        cs = new_ds.GetRasterBand(i + 1).Checksum()
        expected_cs = src_ds.GetRasterBand(i + 1).Checksum()
        if i == 2:
            # We should expect 0, but due to JPEG YCbCr compression & decompression,
            # this ends up being 32768
            expected_cs = 32768
        assert cs == expected_cs, "Got wrong checksum"

    new_ds = None
    gdaltest.tiff_drv.Delete("tmp/tiff_write_78.tif")

    src_ds = None
    gdaltest.tiff_drv.Delete("tmp/tiff_write_78_src.tif")


###############################################################################
# Test reading & updating GDALMD_AREA_OR_POINT (#3522)


def test_tiff_write_79():

    ds = gdaltest.tiff_drv.Create("tmp/tiff_write_79.tif", 1, 1)
    srs = osr.SpatialReference()
    srs.SetFromUserInput("EPSG:32601")
    ds.SetProjection(srs.ExportToWkt())
    ds = None

    for do_projection_ref in [False, True]:
        for check_just_after in [False, True]:

            ds = gdal.Open("tmp/tiff_write_79.tif")
            if do_projection_ref:
                ds.GetProjectionRef()
            mdi = ds.GetMetadataItem("AREA_OR_POINT")
            assert mdi == "Area", (
                "(1) did not get expected value. do_projection_ref = %d, check_just_after = %d"
                % (do_projection_ref, check_just_after)
            )
            ds = None

            # Now update to 'Point'
            ds = gdal.Open("tmp/tiff_write_79.tif", gdal.GA_Update)
            if do_projection_ref:
                ds.GetProjectionRef()
            ds.SetMetadataItem("AREA_OR_POINT", "Point")
            if check_just_after:
                mdi = ds.GetMetadataItem("AREA_OR_POINT")
                assert mdi == "Point", (
                    "(3) did not get expected value. do_projection_ref = %d, check_just_after = %d"
                    % (do_projection_ref, check_just_after)
                )
            ds = None
            assert not os.path.exists("tmp/tiff_write_79.tif.aux.xml")

            # Now should get 'Point'
            ds = gdal.Open("tmp/tiff_write_79.tif")
            if do_projection_ref:
                ds.GetProjectionRef()
            mdi = ds.GetMetadataItem("AREA_OR_POINT")
            assert mdi == "Point", (
                "(4) did not get expected value. do_projection_ref = %d, check_just_after = %d"
                % (do_projection_ref, check_just_after)
            )
            ds = None

            # Now update back to 'Area' through SetMetadata()
            ds = gdal.Open("tmp/tiff_write_79.tif", gdal.GA_Update)
            if do_projection_ref:
                ds.GetProjectionRef()
            md = {}
            md["AREA_OR_POINT"] = "Area"
            ds.SetMetadata(md)
            if check_just_after:
                mdi = ds.GetMetadataItem("AREA_OR_POINT")
                assert mdi == "Area", (
                    "(5) did not get expected value. do_projection_ref = %d, check_just_after = %d"
                    % (do_projection_ref, check_just_after)
                )
            ds = None

            # Now should get 'Area'
            ds = gdal.Open("tmp/tiff_write_79.tif")
            if do_projection_ref:
                ds.GetProjectionRef()
            mdi = ds.GetMetadataItem("AREA_OR_POINT")
            assert mdi == "Area", "(6) did not get expected value"
            ds = None

    gdaltest.tiff_drv.Delete("tmp/tiff_write_79.tif")


###############################################################################
# Test SetOffset() & SetScale()


def test_tiff_write_80():

    # First part : test storing and retrieving scale & offsets from internal metadata
    ds = gdaltest.tiff_drv.Create("tmp/tiff_write_80.tif", 1, 1)
    ds.GetRasterBand(1).SetScale(100)
    ds.GetRasterBand(1).SetOffset(1000)
    ds = None

    assert not os.path.exists("tmp/tiff_write_80.tif.aux.xml")

    ds = gdal.Open("tmp/tiff_write_80.tif")
    scale = ds.GetRasterBand(1).GetScale()
    offset = ds.GetRasterBand(1).GetOffset()
    assert (
        scale == 100 and offset == 1000
    ), "did not get expected values in internal case (1)"
    ds = None

    # Test CreateCopy()
    src_ds = gdal.Open("tmp/tiff_write_80.tif")
    ds = gdaltest.tiff_drv.CreateCopy("tmp/tiff_write_80_copy.tif", src_ds)
    src_ds = None
    ds = None
    ds = gdal.Open("tmp/tiff_write_80_copy.tif")
    scale = ds.GetRasterBand(1).GetScale()
    offset = ds.GetRasterBand(1).GetOffset()
    assert scale == 100 and offset == 1000, "did not get expected values in copy"
    ds = None
    gdaltest.tiff_drv.Delete("tmp/tiff_write_80_copy.tif")

    # Second part : test unsetting scale & offsets from internal metadata
    ds = gdal.Open("tmp/tiff_write_80.tif", gdal.GA_Update)
    ds.GetRasterBand(1).SetScale(1)
    ds.GetRasterBand(1).SetOffset(0)
    ds = None

    ds = gdal.Open("tmp/tiff_write_80.tif")
    scale = ds.GetRasterBand(1).GetScale()
    offset = ds.GetRasterBand(1).GetOffset()
    assert not scale
    assert not offset
    ds = None

    gdaltest.tiff_drv.Delete("tmp/tiff_write_80.tif")

    # Third part : test storing and retrieving scale & offsets from PAM metadata
    ds = gdaltest.tiff_drv.Create("tmp/tiff_write_80_bis.tif", 1, 1)
    assert (
        ds.GetRasterBand(1).GetScale() is None
        and ds.GetRasterBand(1).GetOffset() is None
    ), "expected None values"
    ds = None

    ds = gdal.Open("tmp/tiff_write_80_bis.tif")
    ds.GetRasterBand(1).SetScale(-100)
    ds.GetRasterBand(1).SetOffset(-1000)
    ds = None

    try:
        # check that it *goes* to PAM
        os.stat("tmp/tiff_write_80_bis.tif.aux.xml")
    except OSError:
        pytest.fail("did not go to PAM as expected")

    ds = gdal.Open("tmp/tiff_write_80_bis.tif")
    scale = ds.GetRasterBand(1).GetScale()
    offset = ds.GetRasterBand(1).GetOffset()
    assert (
        scale == -100 and offset == -1000
    ), "did not get expected values in PAM case (1)"
    ds = None

    # Fourth part : test unsetting scale & offsets from PAM metadata
    ds = gdal.Open("tmp/tiff_write_80_bis.tif")
    ds.GetRasterBand(1).SetScale(1)
    ds.GetRasterBand(1).SetOffset(0)
    ds = None

    assert not os.path.exists("tmp/tiff_write_80_bis.tif.aux.xml")

    ds = gdal.Open("tmp/tiff_write_80_bis.tif")
    scale = ds.GetRasterBand(1).GetScale()
    offset = ds.GetRasterBand(1).GetOffset()
    assert not scale
    assert not offset
    ds = None

    gdaltest.tiff_drv.Delete("tmp/tiff_write_80_bis.tif")


###############################################################################
# Test retrieving GCP from PAM


def test_tiff_write_81():

    shutil.copyfile("data/byte.tif", "tmp/tiff_write_81.tif")
    f = open("tmp/tiff_write_81.tif.aux.xml", "wt")
    f.write(
        """
<PAMDataset>
  <GCPList Projection="PROJCS[&quot;NAD27 / UTM zone 11N&quot;,GEOGCS[&quot;NAD27&quot;,DATUM[&quot;North_American_Datum_1927&quot;,SPHEROID[&quot;Clarke 1866&quot;,6378206.4,294.9786982139006,AUTHORITY[&quot;EPSG&quot;,&quot;7008&quot;]],AUTHORITY[&quot;EPSG&quot;,&quot;6267&quot;]],PRIMEM[&quot;Greenwich&quot;,0],UNIT[&quot;degree&quot;,0.0174532925199433],AUTHORITY[&quot;EPSG&quot;,&quot;4267&quot;]],PROJECTION[&quot;Transverse_Mercator&quot;],PARAMETER[&quot;latitude_of_origin&quot;,0],PARAMETER[&quot;central_meridian&quot;,-117],PARAMETER[&quot;scale_factor&quot;,0.9996],PARAMETER[&quot;false_easting&quot;,500000],PARAMETER[&quot;false_northing&quot;,0],UNIT[&quot;metre&quot;,1,AUTHORITY[&quot;EPSG&quot;,&quot;9001&quot;]],AUTHORITY[&quot;EPSG&quot;,&quot;26711&quot;]]">
    <GCP Id="" Pixel="0.0000" Line="0.0000" X="4.407200000000E+05" Y="3.751320000000E+06"/>
    <GCP Id="" Pixel="100.0000" Line="0.0000" X="4.467200000000E+05" Y="3.751320000000E+06"/>
    <GCP Id="" Pixel="0.0000" Line="100.0000" X="4.407200000000E+05" Y="3.745320000000E+06"/>
    <GCP Id="" Pixel="100.0000" Line="100.0000" X="4.467200000000E+05" Y="3.745320000000E+06"/>
  </GCPList>
</PAMDataset>"""
    )
    f.close()

    ds = gdal.Open("tmp/tiff_write_81.tif")

    assert (
        ds.GetGCPProjection().find('AUTHORITY["EPSG","26711"]') != -1
    ), "GCP Projection not set properly."

    gcps = ds.GetGCPs()
    assert len(gcps) == 4, "GCP count wrong."

    ds = None

    gdaltest.tiff_drv.Delete("tmp/tiff_write_81.tif")


###############################################################################
# Test writing & reading a signedbyte 8 bit geotiff


def test_tiff_write_82():

    src_ds = gdal.Open("data/byte.tif")
    with gdal.quiet_errors():
        ds = gdaltest.tiff_drv.CreateCopy(
            "tmp/tiff_write_82.tif", src_ds, options=["PIXELTYPE=SIGNEDBYTE"]
        )
    src_ds = None
    ds = None

    ds = gdal.Open("tmp/tiff_write_82.tif")
    assert ds.GetRasterBand(1).DataType == gdal.GDT_Int8
    assert ds.GetRasterBand(1).ComputeRasterMinMax() == (-124, 123)
    ds = None

    gdaltest.tiff_drv.Delete("tmp/tiff_write_82.tif")


###############################################################################
# Test writing & reading an indexed GeoTIFF with an extra transparency band (#3547)


def test_tiff_write_83():

    # Test Create() method
    ds = gdaltest.tiff_drv.Create("tmp/tiff_write_83.tif", 1, 1, 2)
    ct = gdal.ColorTable()
    ct.SetColorEntry(127, (255, 255, 255, 255))
    ds.GetRasterBand(1).SetRasterColorTable(ct)
    ds.GetRasterBand(1).Fill(127)
    ds.GetRasterBand(2).Fill(255)
    ds = None

    # Test CreateCopy() method
    src_ds = gdal.Open("tmp/tiff_write_83.tif")
    ds = gdaltest.tiff_drv.CreateCopy("tmp/tiff_write_83_2.tif", src_ds)
    src_ds = None
    ds = None

    ds = gdal.Open("tmp/tiff_write_83_2.tif")
    ct2 = ds.GetRasterBand(1).GetRasterColorTable()
    assert ct2.GetColorEntry(127) == (
        255,
        255,
        255,
        255,
    ), "did not get expected color table"
    ct2 = None
    cs1 = ds.GetRasterBand(1).Checksum()
    assert cs1 == 127 % 7, "did not get expected checksum for band 1"
    cs2 = ds.GetRasterBand(2).Checksum()
    assert cs2 == 255 % 7, "did not get expected checksum for band 2"
    ds = None

    gdaltest.tiff_drv.Delete("tmp/tiff_write_83.tif")
    gdaltest.tiff_drv.Delete("tmp/tiff_write_83_2.tif")


###############################################################################
# Test propagation of non-standard JPEG quality when the current directory
# changes in the midst of encoding of tiles (#3539)


@pytest.mark.require_creation_option("GTiff", "JPEG")
def test_tiff_write_84():

    with gdaltest.SetCacheMax(0):
        ds = gdal.GetDriverByName("GTiff").Create("tmp/tiff_write_84.tif", 128, 128, 3)
        ds = None

        try:
            os.remove("tmp/tiff_write_84.tif.ovr")
        except OSError:
            pass

        ds = gdal.Open("tmp/tiff_write_84.tif")
        with gdal.config_options(
            {"COMPRESS_OVERVIEW": "JPEG", "JPEG_QUALITY_OVERVIEW": "90"}
        ):
            ds.BuildOverviews("NEAREST", overviewlist=[2])
            cs = ds.GetRasterBand(2).GetOverview(0).Checksum()
            ds = None

    gdaltest.tiff_drv.Delete("tmp/tiff_write_84.tif")

    assert cs == 0, "did not get expected checksum"


###############################################################################
# Test SetUnitType()


def test_tiff_write_85():

    # First part : test storing and retrieving unittype from internal metadata
    ds = gdaltest.tiff_drv.Create("tmp/tiff_write_85.tif", 1, 1)
    ds.GetRasterBand(1).SetUnitType("ft")
    ds = None

    assert not os.path.exists("tmp/tiff_write_85.tif.aux.xml")

    ds = gdal.Open("tmp/tiff_write_85.tif")
    unittype = ds.GetRasterBand(1).GetUnitType()
    assert unittype == "ft", "did not get expected values in internal case (1)"
    ds = None

    # Test CreateCopy()
    src_ds = gdal.Open("tmp/tiff_write_85.tif")
    ds = gdaltest.tiff_drv.CreateCopy("tmp/tiff_write_85_copy.tif", src_ds)
    src_ds = None
    ds = None
    ds = gdal.Open("tmp/tiff_write_85_copy.tif")
    unittype = ds.GetRasterBand(1).GetUnitType()
    assert unittype == "ft", "did not get expected values in copy"
    ds = None
    gdaltest.tiff_drv.Delete("tmp/tiff_write_85_copy.tif")

    # Second part : test unsetting unittype from internal metadata
    ds = gdal.Open("tmp/tiff_write_85.tif", gdal.GA_Update)
    ds.GetRasterBand(1).SetUnitType(None)
    ds = None

    ds = gdal.Open("tmp/tiff_write_85.tif")
    unittype = ds.GetRasterBand(1).GetUnitType()
    assert unittype == "", "did not get expected values in internal case (2)"
    ds = None

    gdaltest.tiff_drv.Delete("tmp/tiff_write_85.tif")

    # Third part : test storing and retrieving unittype from PAM metadata
    ds = gdaltest.tiff_drv.Create("tmp/tiff_write_85_bis.tif", 1, 1)
    assert not ds.GetRasterBand(1).GetUnitType(), "expected None values"
    ds = None

    ds = gdal.Open("tmp/tiff_write_85_bis.tif")
    ds.GetRasterBand(1).SetUnitType("ft")
    ds = None

    try:
        # check that it *goes* to PAM
        os.stat("tmp/tiff_write_85_bis.tif.aux.xml")
    except OSError:
        pytest.fail("did not go to PAM as expected")

    ds = gdal.Open("tmp/tiff_write_85_bis.tif")
    unittype = ds.GetRasterBand(1).GetUnitType()
    assert unittype == "ft", "did not get expected values in PAM case (1)"
    ds = None

    # Fourth part : test unsetting unittype from PAM metadata
    ds = gdal.Open("tmp/tiff_write_85_bis.tif")
    ds.GetRasterBand(1).SetUnitType(None)
    ds = None

    assert not os.path.exists("tmp/tiff_write_85_bis.tif.aux.xml")

    ds = gdal.Open("tmp/tiff_write_85_bis.tif")
    unittype = ds.GetRasterBand(1).GetUnitType()
    assert unittype == "", "did not get expected values in PAM case (2)"
    ds = None

    gdaltest.tiff_drv.Delete("tmp/tiff_write_85_bis.tif")


###############################################################################
# Test special handling of xml:ESRI domain.  When the ESRI_XML_PAM config
# option is set we want to write this to PAM, not into the geotiff itself.
# This is a special option so that ArcGIS 10 written geotiffs will still work
# properly with earlier versions of ArcGIS, requested by ESRI.


def test_tiff_write_86():

    with gdal.config_option("ESRI_XML_PAM", "YES"):

        ds = gdaltest.tiff_drv.Create(
            "tmp/tiff_write_86.tif", 100, 100, 1, gdal.GDT_Byte
        )
        ds.SetMetadata(["<abc></abc>"], "xml:ESRI")
        ds.SetMetadataItem("BaseTest", "Value")
        ds = None

        # Is the xml:ESRI data available?
        ds = gdal.Open("tmp/tiff_write_86.tif")
        assert ds.GetMetadata("xml:ESRI") == [
            "<abc />\n"
        ], "did not get expected xml:ESRI metadata."

        if ds.GetMetadataItem("BaseTest") != "Value":
            gdaltest.post_value("missing metadata(1)")
            pytest.fail()
        ds = None

        # After removing the pam file is it gone, but the conventional
        # metadata still available?

        os.rename(
            "tmp/tiff_write_86.tif.aux.xml", "tmp/tiff_write_86.tif.aux.xml.hidden"
        )

        ds = gdal.Open("tmp/tiff_write_86.tif")
        assert ds.GetMetadata("xml:ESRI") is None, "unexpectedly got xml:ESRI metadata"

        if ds.GetMetadataItem("BaseTest") != "Value":
            gdaltest.post_value("missing metadata(2)")
            pytest.fail()

        ds = None

        # now confirm that CreateCopy also preserves things similarly.

        os.rename(
            "tmp/tiff_write_86.tif.aux.xml.hidden", "tmp/tiff_write_86.tif.aux.xml"
        )

        ds_src = gdal.Open("tmp/tiff_write_86.tif")
        ds = gdaltest.tiff_drv.CreateCopy("tmp/tiff_write_86_cc.tif", ds_src)
        ds_src = None
        ds = None

        # Is the xml:ESRI data available?
        ds = gdal.Open("tmp/tiff_write_86_cc.tif")
        assert ds.GetMetadata("xml:ESRI") == [
            "<abc />\n"
        ], "did not get expected xml:ESRI metadata (cc)."

        if ds.GetMetadataItem("BaseTest") != "Value":
            gdaltest.post_value("missing metadata(1cc)")
            pytest.fail()
        ds = None

        # After removing the pam file is it gone, but the conventional
        # metadata still available?

        os.remove("tmp/tiff_write_86_cc.tif.aux.xml")

        ds = gdal.Open("tmp/tiff_write_86_cc.tif")
        assert (
            ds.GetMetadata("xml:ESRI") is None
        ), "unexpectedly got xml:ESRI metadata(2)"

        if ds.GetMetadataItem("BaseTest") != "Value":
            gdaltest.post_value("missing metadata(2cc)")
            pytest.fail()

        ds = None

    # Cleanup

    gdaltest.tiff_drv.Delete("tmp/tiff_write_86.tif")
    gdaltest.tiff_drv.Delete("tmp/tiff_write_86_cc.tif")


###############################################################################
# Test COPY_SRC_OVERVIEWS creation option


def test_tiff_write_87():

    gdal.Translate(
        "tmp/tiff_write_87_src.tif", "data/utmsmall.tif", options="-a_nodata 0"
    )

    src_ds = gdal.Open("tmp/tiff_write_87_src.tif", gdal.GA_Update)
    src_ds.BuildOverviews("NEAR", overviewlist=[2, 4])
    expected_cs1 = src_ds.GetRasterBand(1).GetOverview(0).Checksum()
    expected_cs2 = src_ds.GetRasterBand(1).GetOverview(1).Checksum()
    ds = gdaltest.tiff_drv.CreateCopy(
        "tmp/tiff_write_87_dst.tif",
        src_ds,
        options=["COPY_SRC_OVERVIEWS=YES", "ENDIANNESS=LITTLE"],
    )
    ds = None
    src_ds = None

    ds = gdal.Open("tmp/tiff_write_87_dst.tif")
    cs1 = ds.GetRasterBand(1).GetOverview(0).Checksum()
    cs2 = ds.GetRasterBand(1).GetOverview(1).Checksum()
    nodata_ovr_0 = ds.GetRasterBand(1).GetOverview(0).GetNoDataValue()
    nodata_ovr_1 = ds.GetRasterBand(1).GetOverview(1).GetNoDataValue()
    ifd_main = int(ds.GetRasterBand(1).GetMetadataItem("IFD_OFFSET", "TIFF"))
    ifd_ovr_0 = int(
        ds.GetRasterBand(1).GetOverview(0).GetMetadataItem("IFD_OFFSET", "TIFF")
    )
    ifd_ovr_1 = int(
        ds.GetRasterBand(1).GetOverview(1).GetMetadataItem("IFD_OFFSET", "TIFF")
    )
    data_ovr_1 = int(
        ds.GetRasterBand(1).GetOverview(1).GetMetadataItem("BLOCK_OFFSET_0_0", "TIFF")
    )
    data_ovr_0 = int(
        ds.GetRasterBand(1).GetOverview(0).GetMetadataItem("BLOCK_OFFSET_0_0", "TIFF")
    )
    data_main = int(ds.GetRasterBand(1).GetMetadataItem("BLOCK_OFFSET_0_0", "TIFF"))
    size_main = int(ds.GetRasterBand(1).GetMetadataItem("BLOCK_SIZE_0_0", "TIFF"))
    with open("tmp/tiff_write_87_dst.tif", "rb") as f:
        f.seek(data_main - 4)
        size_from_header = struct.unpack("<I", f.read(4))[0]
        assert size_main == size_from_header
        f.seek(data_main + size_main - 4)
        last_bytes = f.read(4)
        last_bytes_repeated = f.read(4)
        assert last_bytes == last_bytes_repeated

    ds = None

    _check_cog("tmp/tiff_write_87_dst.tif", check_tiled=False, full_check=True)

    gdaltest.tiff_drv.Delete("tmp/tiff_write_87_src.tif")
    gdaltest.tiff_drv.Delete("tmp/tiff_write_87_dst.tif")

    # Check checksums
    assert cs1 == expected_cs1 and cs2 == expected_cs2, "did not get expected checksums"

    assert nodata_ovr_0 == 0 and nodata_ovr_1 == 0, "did not get expected nodata values"

    assert ifd_main == 8 or (
        ifd_main < ifd_ovr_0
        and ifd_ovr_0 < ifd_ovr_1
        and ifd_ovr_1 < data_ovr_1
        and data_ovr_1 < data_ovr_0
        and data_ovr_0 < data_main
    )


###############################################################################
# Test that COPY_SRC_OVERVIEWS creation option has an influence
# on BIGTIFF creation


def test_tiff_write_88():

    # The file would be > 4.2 GB without SPARSE_OK
    src_ds = gdaltest.tiff_drv.Create(
        "tmp/tiff_write_88_src.tif",
        60000,
        60000,
        1,
        options=["TILED=YES", "SPARSE_OK=YES"],
    )
    src_ds.BuildOverviews("NONE", overviewlist=[2, 4])
    # Just write one data block so that we can truncate it
    data = src_ds.GetRasterBand(1).GetOverview(1).ReadRaster(0, 0, 128, 128)
    src_ds.GetRasterBand(1).GetOverview(1).WriteRaster(0, 0, 128, 128, data)
    src_ds = None

    # Truncate the file to cause an I/O error on reading
    # so that the CreateCopy() aborts quickly
    f = open("tmp/tiff_write_88_src.tif", "rb")
    f.seek(0, 2)
    length = f.tell()
    f.seek(0, 0)
    data = f.read(length - 1)
    f.close()
    f = open("tmp/tiff_write_88_src.tif", "wb")
    f.write(data)
    f.close()

    src_ds = gdal.Open("tmp/tiff_write_88_src.tif")
    # for testing only. We need to keep the file to check it was a bigtiff
    # we don't want free space to be an issue here
    with gdal.config_options(
        {"GTIFF_DELETE_ON_ERROR": "NO", "CHECK_DISK_FREE_SPACE": "NO"}
    ), gdaltest.error_handler():
        ds = gdaltest.tiff_drv.CreateCopy(
            "tmp/tiff_write_88_dst.tif",
            src_ds,
            options=["TILED=YES", "COPY_SRC_OVERVIEWS=YES", "ENDIANNESS=LITTLE"],
        )
    del ds
    src_ds = None

    f = open("tmp/tiff_write_88_dst.tif", "rb")
    data = f.read(8)
    f.close()

    os.remove("tmp/tiff_write_88_src.tif")
    os.remove("tmp/tiff_write_88_dst.tif")

    ar = struct.unpack("B" * 8, data)
    assert ar[2] == 43, "not a BIGTIFF file"
    assert (
        ar[4] == 8 and ar[5] == 0 and ar[6] == 0 and ar[7] == 0
    ), "first IFD is not at offset 8"


###############################################################################
# Test JPEG_QUALITY propagation while creating a (default compressed) mask band


@pytest.mark.require_creation_option("GTiff", "JPEG")
def test_tiff_write_89():

    last_size = 0
    for quality in [90, 75, 30]:
        src_ds = gdal.Open("../gdrivers/data/utm.tif")

        ds = gdal.GetDriverByName("GTiff").Create(
            "tmp/tiff_write_89.tif",
            1024,
            1024,
            3,
            options=["COMPRESS=JPEG", "PHOTOMETRIC=YCBCR", "JPEG_QUALITY=%d" % quality],
        )

        ds.CreateMaskBand(gdal.GMF_PER_DATASET)

        data = src_ds.GetRasterBand(1).ReadRaster(0, 0, 512, 512, 1024, 1024)
        ds.GetRasterBand(1).WriteRaster(0, 0, 1024, 1024, data)
        ds.GetRasterBand(2).WriteRaster(0, 0, 1024, 1024, data)
        ds.GetRasterBand(3).WriteRaster(0, 0, 1024, 1024, data)
        ds.GetRasterBand(1).GetMaskBand().Fill(255)

        src_ds = None
        ds = None

        # older versions of python don't have SEEK_END, add if missing.
        try:
            os.SEEK_END
        except AttributeError:
            os.SEEK_END = 2

        f = open("tmp/tiff_write_89.tif", "rb")
        f.seek(0, os.SEEK_END)
        size = f.tell()
        f.close()

        # print('quality = %d, size = %d' % (quality, size))

        if quality != 90:
            assert size < last_size, "did not get decreasing file sizes"

        last_size = size

    gdaltest.tiff_drv.Delete("tmp/tiff_write_89.tif")


###############################################################################
# Test JPEG_QUALITY propagation/override while creating (internal) overviews


@pytest.mark.require_creation_option("GTiff", "JPEG")
def test_tiff_write_90():

    checksums = {}
    qualities = [90, 75, 75]
    for i, quality in enumerate(qualities):
        src_ds = gdal.Open("../gdrivers/data/utm.tif")
        fname = "tmp/tiff_write_90_%d" % i

        ds = gdal.GetDriverByName("GTiff").Create(
            fname,
            1024,
            1024,
            3,
            options=["COMPRESS=JPEG", "PHOTOMETRIC=YCBCR", "JPEG_QUALITY=%d" % quality],
        )

        data = src_ds.GetRasterBand(1).ReadRaster(0, 0, 512, 512, 1024, 1024)
        ds.GetRasterBand(1).WriteRaster(0, 0, 1024, 1024, data)
        ds.GetRasterBand(2).WriteRaster(0, 0, 1024, 1024, data)
        ds.GetRasterBand(3).WriteRaster(0, 0, 1024, 1024, data)
        if i == 2:
            quality = 30
        with gdaltest.config_option("JPEG_QUALITY_OVERVIEW", "%d" % quality):
            ds.BuildOverviews("AVERAGE", overviewlist=[2, 4])

        src_ds = None
        ds = None

        ds = gdal.Open(fname)
        checksums[i] = [
            ds.GetRasterBand(1).Checksum(),
            ds.GetRasterBand(1).GetOverview(0).Checksum(),
            ds.GetRasterBand(1).GetOverview(1).Checksum(),
        ]
        ds = None
        gdaltest.tiff_drv.Delete(fname)

    assert checksums[0][0] != checksums[1][0]
    assert checksums[0][1] != checksums[1][1]
    assert checksums[0][2] != checksums[1][2]

    assert checksums[0][0] != checksums[2][0]
    assert checksums[0][1] != checksums[2][1]
    assert checksums[0][2] != checksums[2][2]

    assert checksums[1][0] == checksums[2][0]
    assert checksums[1][1] != checksums[2][1]
    assert checksums[1][2] != checksums[2][2]


###############################################################################
# Test WEBP_LEVEL propagation and overriding while creating overviews
# on a newly created dataset


@pytest.mark.parametrize("external_ovr", [True, False])
@pytest.mark.require_creation_option("GTiff", "WEBP")
def test_tiff_write_90_webp(external_ovr):

    checksums = {}
    qualities = [90, 75, 75]
    for i, quality in enumerate(qualities):
        src_ds = gdal.Open("../gdrivers/data/utm.tif")
        fname = "tmp/tiff_write_90_webp_%d" % i

        ds = gdal.GetDriverByName("GTiff").Create(
            fname, 512, 512, 3, options=["COMPRESS=WEBP", "WEBP_LEVEL=%d" % quality]
        )

        data = src_ds.GetRasterBand(1).ReadRaster()
        ds.GetRasterBand(1).WriteRaster(0, 0, 512, 512, data)
        ds.GetRasterBand(2).WriteRaster(0, 0, 512, 512, data)
        ds.GetRasterBand(3).WriteRaster(0, 0, 512, 512, data)
        if i == 2:
            quality = 30
        options = {}
        if external_ovr:
            ds = None
            ds = gdal.Open(fname)
            options["COMPRESS_OVERVIEW"] = "WEBP"
        options["WEBP_LEVEL_OVERVIEW"] = "%d" % quality
        with gdaltest.config_options(options):
            ds.BuildOverviews("AVERAGE", overviewlist=[2, 4])

        src_ds = None
        ds = None

        ds = gdal.Open(fname)
        checksums[i] = [
            ds.GetRasterBand(1).Checksum(),
            ds.GetRasterBand(1).GetOverview(0).Checksum(),
            ds.GetRasterBand(1).GetOverview(1).Checksum(),
        ]
        ds = None
        gdaltest.tiff_drv.Delete(fname)

    assert checksums[0][0] != checksums[1][0]
    assert checksums[0][1] != checksums[1][1]
    assert checksums[0][2] != checksums[1][2]

    assert checksums[0][0] != checksums[2][0]
    assert checksums[0][1] != checksums[2][1]
    assert checksums[0][2] != checksums[2][2]

    assert checksums[1][0] == checksums[2][0]
    assert checksums[1][1] != checksums[2][1]
    assert checksums[1][2] != checksums[2][2]


###############################################################################
# Test WEBP_LOSSLESS propagation and overriding while creating overviews
# on a newly created dataset


@pytest.mark.parametrize("external_ovr", [True, False])
@pytest.mark.require_creation_option("GTiff", "WEBP")
def test_tiff_write_90_webp_lossless(external_ovr):

    checksums = {}
    for i in range(2):
        src_ds = gdal.Open("../gdrivers/data/utm.tif")
        fname = "tmp/tiff_write_90_webp_lossless_%d" % i

        ds = gdaltest.tiff_drv.Create(fname, 512, 512, 3, options=["COMPRESS=WEBP"])

        data = src_ds.GetRasterBand(1).ReadRaster()
        ds.GetRasterBand(1).WriteRaster(0, 0, 512, 512, data)
        ds.GetRasterBand(2).WriteRaster(0, 0, 512, 512, data)
        ds.GetRasterBand(3).WriteRaster(0, 0, 512, 512, data)

        options = {}
        if external_ovr:
            ds = None
            ds = gdal.Open(fname)
            options["COMPRESS_OVERVIEW"] = "WEBP"
        if i == 1:
            options["WEBP_LOSSLESS_OVERVIEW"] = "YES"
        with gdaltest.config_options(options):
            ds.BuildOverviews("AVERAGE", overviewlist=[2])

        src_ds = None
        ds = None

        ds = gdal.Open(fname)
        checksums[i] = [
            ds.GetRasterBand(1).Checksum(),
            ds.GetRasterBand(1).GetOverview(0).Checksum(),
        ]
        ds = None
        gdaltest.tiff_drv.Delete(fname)

    assert checksums[0][0] == checksums[1][0]
    assert checksums[0][1] != checksums[1][1]


###############################################################################
# Test JPEG_QUALITY propagation while creating (internal) overviews after re-opening


@pytest.mark.require_creation_option("GTiff", "JPEG")
def test_tiff_write_91():

    checksums = {}
    for quality in [90, 75, 30]:
        src_ds = gdal.Open("../gdrivers/data/utm.tif")

        ds = gdal.GetDriverByName("GTiff").Create(
            "tmp/tiff_write_91.tif",
            1024,
            1024,
            3,
            options=["COMPRESS=JPEG", "PHOTOMETRIC=YCBCR", "JPEG_QUALITY=%d" % quality],
        )

        data = src_ds.GetRasterBand(1).ReadRaster(0, 0, 512, 512, 1024, 1024)
        ds.GetRasterBand(1).WriteRaster(0, 0, 1024, 1024, data)
        ds.GetRasterBand(2).WriteRaster(0, 0, 1024, 1024, data)
        ds.GetRasterBand(3).WriteRaster(0, 0, 1024, 1024, data)
        ds = None

        ds = gdal.Open("tmp/tiff_write_91.tif", gdal.GA_Update)
        with gdal.config_option("JPEG_QUALITY_OVERVIEW", "%d" % quality):
            ds.BuildOverviews("NEAR", overviewlist=[2, 4])

        src_ds = None
        ds = None

        ds = gdal.Open("tmp/tiff_write_91.tif")
        checksums[quality] = [
            ds.GetRasterBand(1).Checksum(),
            ds.GetRasterBand(1).GetOverview(0).Checksum(),
            ds.GetRasterBand(1).GetOverview(1).Checksum(),
        ]
        ds = None

    gdaltest.tiff_drv.Delete("tmp/tiff_write_91.tif")

    assert checksums[75][0] != checksums[90][0]
    assert checksums[75][1] != checksums[90][1]
    assert checksums[75][2] != checksums[90][2]

    assert checksums[75][0] != checksums[30][0]
    assert checksums[75][1] != checksums[30][1]
    assert checksums[75][2] != checksums[30][2]

    assert checksums[90][0] != checksums[30][0]
    assert checksums[90][1] != checksums[30][1]
    assert checksums[90][2] != checksums[30][2]


###############################################################################
# Test WEBP_LEVEL_OVERVIEW while creating (internal) overviews after re-opening


@pytest.mark.require_creation_option("GTiff", "WEBP")
def test_tiff_write_91_webp():

    checksums = {}
    for quality in [90, 75, 30]:
        src_ds = gdal.Open("../gdrivers/data/utm.tif")
        fname = "tmp/tiff_write_91_webp_%d" % quality

        ds = gdal.GetDriverByName("GTiff").Create(
            fname, 1024, 1024, 3, options=["COMPRESS=WEBP"]
        )

        data = src_ds.GetRasterBand(1).ReadRaster(0, 0, 512, 512, 1024, 1024)
        ds.GetRasterBand(1).WriteRaster(0, 0, 1024, 1024, data)
        ds.GetRasterBand(2).WriteRaster(0, 0, 1024, 1024, data)
        ds.GetRasterBand(3).WriteRaster(0, 0, 1024, 1024, data)
        ds = None
        src_ds = None

        ds = gdal.Open(fname, gdal.GA_Update)
        with gdaltest.config_option("WEBP_LEVEL_OVERVIEW", "%d" % quality):
            ds.BuildOverviews("AVERAGE", overviewlist=[2, 4])

        ds = None

        ds = gdal.Open(fname)
        checksums[quality] = [
            ds.GetRasterBand(1).Checksum(),
            ds.GetRasterBand(1).GetOverview(0).Checksum(),
            ds.GetRasterBand(1).GetOverview(1).Checksum(),
        ]

        ds = None
        gdaltest.tiff_drv.Delete(fname)

    assert checksums[75][0] == checksums[90][0]
    assert checksums[75][1] != checksums[90][1]
    assert checksums[75][2] != checksums[90][2]

    assert checksums[75][0] == checksums[30][0]
    assert checksums[75][1] != checksums[30][1]
    assert checksums[75][2] != checksums[30][2]

    assert checksums[90][0] == checksums[30][0]
    assert checksums[90][1] != checksums[30][1]
    assert checksums[90][2] != checksums[30][2]


###############################################################################
# Test the effect of JPEG_QUALITY_OVERVIEW while creating (internal) overviews after re-opening
# This will test that we correctly guess the quality of the main dataset


@pytest.mark.require_creation_option("GTiff", "JPEG")
def test_tiff_write_92():

    last_size = 0
    quality = 30
    for jpeg_quality_overview in [False, 30, 40]:
        src_ds = gdal.Open("../gdrivers/data/utm.tif")

        ds = gdal.GetDriverByName("GTiff").Create(
            "tmp/tiff_write_92.tif",
            1024,
            1024,
            3,
            options=["COMPRESS=JPEG", "PHOTOMETRIC=YCBCR", "JPEG_QUALITY=%d" % quality],
        )

        data = src_ds.GetRasterBand(1).ReadRaster(0, 0, 512, 512, 1024, 1024)
        ds.GetRasterBand(1).WriteRaster(0, 0, 1024, 1024, data)
        ds.GetRasterBand(2).WriteRaster(0, 0, 1024, 1024, data)
        ds.GetRasterBand(3).WriteRaster(0, 0, 1024, 1024, data)
        ds = None

        ds = gdal.Open("tmp/tiff_write_92.tif", gdal.GA_Update)
        assert ds.GetMetadataItem("JPEG_QUALITY", "IMAGE_STRUCTURE") == str(quality)
        if jpeg_quality_overview is not False:
            gdal.SetConfigOption("JPEG_QUALITY_OVERVIEW", "%d" % jpeg_quality_overview)
        ds.BuildOverviews("NEAR", overviewlist=[2, 4])
        gdal.SetConfigOption("JPEG_QUALITY_OVERVIEW", None)

        src_ds = None
        ds = None

        f = open("tmp/tiff_write_92.tif", "rb")
        f.seek(0, os.SEEK_END)
        size = f.tell()
        f.close()

        # print('quality = %d, size = %d' % (quality, size))

        if jpeg_quality_overview == 30:
            assert size == last_size, "did not get equal file sizes"
        elif jpeg_quality_overview == 40:
            assert size > last_size, "did not get growing file sizes"

        last_size = size

    gdaltest.tiff_drv.Delete("tmp/tiff_write_92.tif")


###############################################################################
# Test JPEG_QUALITY_OVERVIEW propagation while creating external overviews


@pytest.mark.require_creation_option("GTiff", "JPEG")
def test_tiff_write_93():

    src_ds = gdal.Open("../gdrivers/data/utm.tif")
    ds = gdal.GetDriverByName("GTiff").Create(
        "tmp/tiff_write_93.tif",
        1024,
        1024,
        3,
        options=["COMPRESS=JPEG", "PHOTOMETRIC=YCBCR"],
    )

    data = src_ds.GetRasterBand(1).ReadRaster(0, 0, 512, 512, 1024, 1024)
    ds.GetRasterBand(1).WriteRaster(0, 0, 1024, 1024, data)
    ds.GetRasterBand(2).WriteRaster(0, 0, 1024, 1024, data)
    ds.GetRasterBand(3).WriteRaster(0, 0, 1024, 1024, data)
    ds = None

    src_ds = None

    last_size = 0
    for quality in [90, 75, 30]:

        try:
            os.remove("tmp/tiff_write_93.tif.ovr")
        except OSError:
            pass

        ds = gdal.Open("tmp/tiff_write_93.tif")
        with gdal.config_options(
            {
                "COMPRESS_OVERVIEW": "JPEG",
                "JPEG_QUALITY_OVERVIEW": "%d" % quality,
                "PHOTOMETRIC_OVERVIEW": "YCBCR",
            }
        ):
            ds.BuildOverviews("NEAR", overviewlist=[2, 4])
        ds = None

        f = open("tmp/tiff_write_93.tif.ovr", "rb")
        f.seek(0, os.SEEK_END)
        size = f.tell()
        f.close()

        # print('quality = %d, size = %d' % (quality, size))

        if quality != 90:
            assert size < last_size, "did not get decreasing file sizes"

            assert not (
                quality == 30 and size >= 83000
            ), "file larger than expected. should be about 69100. perhaps jpeg quality is not well propagated"

        last_size = size

    gdaltest.tiff_drv.Delete("tmp/tiff_write_93.tif")


###############################################################################
# Test CreateCopy() of a dataset with a mask into a JPEG compressed dataset
# and check JPEG_QUALITY propagation without warning


@pytest.mark.require_creation_option("GTiff", "JPEG")
def test_tiff_write_94():

    src_ds = gdal.GetDriverByName("GTiff").Create(
        "tmp/tiff_write_94_src.tif", 1024, 1024, 3
    )
    src_ds.CreateMaskBand(gdal.GMF_PER_DATASET)
    src_ds.GetRasterBand(1).GetMaskBand().WriteRaster(0, 0, 1, 1, "\xff", 1, 1)

    ds = gdal.GetDriverByName("GTiff").CreateCopy(
        "tmp/tiff_write_94_dst.tif",
        src_ds,
        options=["COMPRESS=JPEG", "PHOTOMETRIC=YCBCR", "JPEG_QUALITY=30"],
    )

    src_ds = None
    ds = None

    ds = gdal.Open("tmp/tiff_write_94_dst.tif")
    cs = ds.GetRasterBand(1).GetMaskBand().Checksum()
    ds = None

    gdaltest.tiff_drv.Delete("tmp/tiff_write_94_src.tif")
    gdaltest.tiff_drv.Delete("tmp/tiff_write_94_dst.tif")

    assert cs == 3, "wrong checksum"


###############################################################################
# Test that COPY_SRC_OVERVIEWS deal well with rounding issues when computing
# overview levels from the overview size


def test_tiff_write_95():

    src_ds = gdaltest.tiff_drv.Create(
        "tmp/tiff_write_95_src.tif", 7171, 6083, options=["SPARSE_OK=YES"]
    )
    src_ds.BuildOverviews("NONE", overviewlist=[2, 4, 8, 16, 32, 64])
    with gdal.config_option("GTIFF_DONT_WRITE_BLOCKS", "YES"):
        ds = gdaltest.tiff_drv.CreateCopy(
            "tmp/tiff_write_95_dst.tif", src_ds, options=["COPY_SRC_OVERVIEWS=YES"]
        )
    ok = ds is not None
    ds = None
    src_ds = None

    gdaltest.tiff_drv.Delete("tmp/tiff_write_95_src.tif")
    gdaltest.tiff_drv.Delete("tmp/tiff_write_95_dst.tif")

    assert ok


###############################################################################
# Test that COPY_SRC_OVERVIEWS combined with internal masks work well


def test_tiff_write_96(other_options=[], nbands=1, nbits=8):

    src_ds = gdaltest.tiff_drv.Create(
        "tmp/tiff_write_96_src.tif",
        100,
        100,
        nbands,
        options=["NBITS=" + str(nbits)],
    )
    src_ds.GetRasterBand(1).Fill(255 if nbits == 8 else 127)
    src_ds.CreateMaskBand(gdal.GMF_PER_DATASET)
    src_ds.GetRasterBand(1).GetMaskBand().WriteRaster(25, 25, 50, 50, b"\xff", 1, 1)
    src_ds.BuildOverviews("NEAR", overviewlist=[2, 4])
    expected_cs = src_ds.GetRasterBand(1).Checksum()
    expected_cs_mask = src_ds.GetRasterBand(1).GetMaskBand().Checksum()
    expected_cs_ovr_1 = src_ds.GetRasterBand(1).GetOverview(0).Checksum()
    expected_cs_ovr_mask_1 = (
        src_ds.GetRasterBand(1).GetOverview(0).GetMaskBand().Checksum()
    )
    expected_cs_ovr_2 = src_ds.GetRasterBand(1).GetOverview(1).Checksum()
    expected_cs_ovr_mask_2 = (
        src_ds.GetRasterBand(1).GetOverview(1).GetMaskBand().Checksum()
    )

    ds = gdaltest.tiff_drv.CreateCopy(
        "tmp/tiff_write_96_dst.tif",
        src_ds,
        options=["COPY_SRC_OVERVIEWS=YES"] + other_options + ["NBITS=" + str(nbits)],
    )
    ds = None
    src_ds = None

    ds = gdal.Open("tmp/tiff_write_96_dst.tif")
    cs = ds.GetRasterBand(1).Checksum()
    cs_mask = ds.GetRasterBand(1).GetMaskBand().Checksum()
    cs_ovr_1 = ds.GetRasterBand(1).GetOverview(0).Checksum()
    cs_ovr_mask_1 = ds.GetRasterBand(1).GetOverview(0).GetMaskBand().Checksum()
    cs_ovr_2 = ds.GetRasterBand(1).GetOverview(1).Checksum()
    cs_ovr_mask_2 = ds.GetRasterBand(1).GetOverview(1).GetMaskBand().Checksum()
    assert ds.GetMetadataItem("HAS_USED_READ_ENCODED_API", "_DEBUG_") == "1"
    ds = None

    assert [
        expected_cs,
        expected_cs_mask,
        expected_cs_ovr_1,
        expected_cs_ovr_mask_1,
        expected_cs_ovr_2,
        expected_cs_ovr_mask_2,
    ] == [
        cs,
        cs_mask,
        cs_ovr_1,
        cs_ovr_mask_1,
        cs_ovr_2,
        cs_ovr_mask_2,
    ], "did not get expected checksums"

    if check_libtiff_internal_or_at_least(4, 0, 11):
        with gdaltest.config_option("GTIFF_HAS_OPTIMIZED_READ_MULTI_RANGE", "YES"):
            ds = gdal.Open("tmp/tiff_write_96_dst.tif")
            cs = ds.GetRasterBand(1).Checksum()
            cs_mask = ds.GetRasterBand(1).GetMaskBand().Checksum()
            cs_ovr_1 = ds.GetRasterBand(1).GetOverview(0).Checksum()
            cs_ovr_mask_1 = ds.GetRasterBand(1).GetOverview(0).GetMaskBand().Checksum()
            cs_ovr_2 = ds.GetRasterBand(1).GetOverview(1).Checksum()
            cs_ovr_mask_2 = ds.GetRasterBand(1).GetOverview(1).GetMaskBand().Checksum()

        assert [
            expected_cs,
            expected_cs_mask,
            expected_cs_ovr_1,
            expected_cs_ovr_mask_1,
            expected_cs_ovr_2,
            expected_cs_ovr_mask_2,
        ] == [
            cs,
            cs_mask,
            cs_ovr_1,
            cs_ovr_mask_1,
            cs_ovr_2,
            cs_ovr_mask_2,
        ], "did not get expected checksums"
        assert ds.GetMetadataItem("HAS_USED_READ_ENCODED_API", "_DEBUG_") == "0"
        ds = None

    _check_cog("tmp/tiff_write_96_dst.tif", check_tiled=False, full_check=True)

    gdaltest.tiff_drv.Delete("tmp/tiff_write_96_src.tif")
    gdaltest.tiff_drv.Delete("tmp/tiff_write_96_dst.tif")


def test_tiff_write_96_tiled_threads_nbits7_nbands1():
    return test_tiff_write_96(
        ["TILED=YES", "BLOCKXSIZE=16", "BLOCKYSIZE=32", "NUM_THREADS=ALL_CPUS"],
        nbands=1,
        nbits=7,
    )


def test_tiff_write_96_tiled_threads_nbits7_nbands2():
    return test_tiff_write_96(
        [
            "BIGTIFF=YES",
            "TILED=YES",
            "BLOCKXSIZE=16",
            "BLOCKYSIZE=32",
            "NUM_THREADS=ALL_CPUS",
        ],
        nbands=2,
        nbits=7,
    )


###############################################################################
# Test that strile arrays are written after the IFD


def test_tiff_write_ifd_offsets():

    if not check_libtiff_internal_or_at_least(4, 0, 11):
        pytest.skip()

    src_ds = gdal.GetDriverByName("MEM").Create("", 100, 100)
    src_ds.CreateMaskBand(gdal.GMF_PER_DATASET)
    src_ds.BuildOverviews("NEAR", overviewlist=[2, 4])

    filename = "/vsimem/test_tiff_write_ifd_offsets.tif"
    ds = gdal.GetDriverByName("GTiff").CreateCopy(
        filename,
        src_ds,
        options=["COPY_SRC_OVERVIEWS=YES", "TILED=YES", "COMPRESS=LZW"],
    )
    val0_ref = int(ds.GetRasterBand(1).GetMetadataItem("IFD_OFFSET", "TIFF"))
    val1_ref = int(
        ds.GetRasterBand(1).GetMaskBand().GetMetadataItem("IFD_OFFSET", "TIFF")
    )
    val2_ref = int(
        ds.GetRasterBand(1).GetOverview(0).GetMetadataItem("IFD_OFFSET", "TIFF")
    )
    val3_ref = int(
        ds.GetRasterBand(1).GetOverview(1).GetMetadataItem("IFD_OFFSET", "TIFF")
    )
    val4_ref = int(
        ds.GetRasterBand(1)
        .GetOverview(0)
        .GetMaskBand()
        .GetMetadataItem("IFD_OFFSET", "TIFF")
    )
    val5_ref = int(
        ds.GetRasterBand(1)
        .GetOverview(1)
        .GetMaskBand()
        .GetMetadataItem("IFD_OFFSET", "TIFF")
    )
    ds = None

    assert val0_ref < val1_ref
    assert val1_ref < val2_ref
    assert val2_ref < val3_ref
    assert val3_ref < val4_ref
    assert val4_ref < val5_ref
    assert val5_ref < 1100

    # Retry with larger file
    src_ds = gdal.GetDriverByName("MEM").Create("", 4096, 4096)
    src_ds.CreateMaskBand(gdal.GMF_PER_DATASET)
    src_ds.BuildOverviews("NEAR", overviewlist=[2, 4])

    ds = gdal.GetDriverByName("GTiff").CreateCopy(
        filename,
        src_ds,
        options=["COPY_SRC_OVERVIEWS=YES", "TILED=YES", "COMPRESS=LZW"],
    )
    val0 = int(ds.GetRasterBand(1).GetMetadataItem("IFD_OFFSET", "TIFF"))
    val1 = int(ds.GetRasterBand(1).GetMaskBand().GetMetadataItem("IFD_OFFSET", "TIFF"))
    val2 = int(ds.GetRasterBand(1).GetOverview(0).GetMetadataItem("IFD_OFFSET", "TIFF"))
    val3 = int(ds.GetRasterBand(1).GetOverview(1).GetMetadataItem("IFD_OFFSET", "TIFF"))
    val4 = int(
        ds.GetRasterBand(1)
        .GetOverview(0)
        .GetMaskBand()
        .GetMetadataItem("IFD_OFFSET", "TIFF")
    )
    val5 = int(
        ds.GetRasterBand(1)
        .GetOverview(1)
        .GetMaskBand()
        .GetMetadataItem("IFD_OFFSET", "TIFF")
    )
    ds = None

    # Test rewriting but without changing strile size
    ds = gdal.OpenEx(
        filename, gdal.GA_Update, open_options=["IGNORE_COG_LAYOUT_BREAK=YES"]
    )
    ds.GetRasterBand(1).Fill(0)
    ds = None
    assert gdal.GetLastErrorMsg() == ""
    f = gdal.VSIFOpenL(filename, "rb")
    data = gdal.VSIFReadL(1, 1000, f).decode("LATIN1")
    gdal.VSIFCloseL(f)
    assert "KNOWN_INCOMPATIBLE_EDITION=NO\n " in data

    # Test rewriting with changing strile size
    ds = gdal.OpenEx(
        filename, gdal.GA_Update, open_options=["IGNORE_COG_LAYOUT_BREAK=YES"]
    )
    ds.GetRasterBand(1).WriteRaster(0, 0, 1, 1, "x")
    ds = None
    assert gdal.GetLastErrorMsg() != ""
    f = gdal.VSIFOpenL(filename, "rb")
    data = gdal.VSIFReadL(1, 1000, f).decode("LATIN1")
    gdal.VSIFCloseL(f)
    assert "KNOWN_INCOMPATIBLE_EDITION=YES\n" in data

    gdal.GetDriverByName("GTiff").Delete(filename)

    assert (val0_ref, val1_ref, val2_ref, val3_ref, val4_ref, val5_ref) == (
        val0,
        val1,
        val2,
        val3,
        val4,
        val5,
    )


###############################################################################
# Create a simple file by copying from an existing one - PixelIsPoint


def test_tiff_write_97():

    with gdal.config_option("GTIFF_POINT_GEO_IGNORE", "FALSE"):

        src_ds = gdal.Open("data/byte_point.tif")

        new_ds = gdaltest.tiff_drv.CreateCopy("tmp/test_97.tif", src_ds)

        gt = new_ds.GetGeoTransform()
        md = new_ds.GetMetadataItem("AREA_OR_POINT")
        new_ds = None

        gt_expected = (440690.0, 60.0, 0.0, 3751350.0, 0.0, -60.0)

        assert gt == gt_expected, "did not get expected geotransform"

        assert md == "Point", "did not get expected AREA_OR_POINT value"

        gdaltest.tiff_drv.Delete("tmp/test_97.tif")

    # Again, but ignoring PixelIsPoint

    with gdal.config_option("GTIFF_POINT_GEO_IGNORE", "TRUE"):

        new_ds = gdaltest.tiff_drv.CreateCopy("tmp/test_97_2.tif", src_ds)

        gt = new_ds.GetGeoTransform()
        md = new_ds.GetMetadataItem("AREA_OR_POINT")
        new_ds = None
        src_ds = None

        gt_expected = (440690.0, 60.0, 0.0, 3751350.0, 0.0, -60.0)

        assert (
            gt == gt_expected
        ), "did not get expected geotransform when ignoring PixelIsPoint"

        assert md == "Point", "did not get expected AREA_OR_POINT value"

    # read back this file with pixelispoint behavior enabled.

    new_ds = gdal.Open("tmp/test_97_2.tif")

    gt = new_ds.GetGeoTransform()
    md = new_ds.GetMetadataItem("AREA_OR_POINT")
    new_ds = None

    gt_expected = (440660.0, 60.0, 0.0, 3751380.0, 0.0, -60.0)

    assert (
        gt == gt_expected
    ), "did not get expected geotransform when ignoring PixelIsPoint (2)"

    assert md == "Point", "did not get expected AREA_OR_POINT value"

    gdaltest.tiff_drv.Delete("tmp/test_97_2.tif")


###############################################################################
# Create a rotated geotiff file (uses a geomatrix) with - PixelIsPoint


def test_tiff_write_98():

    with gdaltest.config_option("GTIFF_POINT_GEO_IGNORE", "FALSE"):
        src_ds = gdal.Open("data/geomatrix.tif")

    with gdaltest.config_option("GTIFF_POINT_GEO_IGNORE", "TRUE"):
        new_ds = gdaltest.tiff_drv.CreateCopy("tmp/test_98.tif", src_ds)

        gt = new_ds.GetGeoTransform()
        md = new_ds.GetMetadataItem("AREA_OR_POINT")
        new_ds = None
        src_ds = None

        gt_expected = (1841001.75, 1.5, -5.0, 1144003.25, -5.0, -1.5)

    assert gt == gt_expected, "did not get expected geotransform"

    assert md == "Point", "did not get expected AREA_OR_POINT value"

    with gdaltest.config_option("GTIFF_POINT_GEO_IGNORE", "FALSE"):

        new_ds = gdal.Open("tmp/test_98.tif")

        gt = new_ds.GetGeoTransform()
        md = new_ds.GetMetadataItem("AREA_OR_POINT")
        new_ds = None
        src_ds = None

    gt_expected = (1841003.5, 1.5, -5.0, 1144006.5, -5.0, -1.5)

    assert gt == gt_expected, "did not get expected geotransform (2)"

    assert md == "Point", "did not get expected AREA_OR_POINT value"

    gdaltest.tiff_drv.Delete("tmp/test_98.tif")


###############################################################################
# Create a rotated geotiff file (uses a geomatrix) with - PixelIsPoint


def test_tiff_write_tiepoints_pixelispoint():

    tmpfilename = "/vsimem/test_tiff_write_tiepoints_pixelispoint.tif"

    gdal.Translate(tmpfilename, "data/byte_gcp_pixelispoint.tif")
    ds = gdal.Open(tmpfilename)
    assert ds.GetMetadataItem("AREA_OR_POINT") == "Point"
    assert ds.GetGCPCount() == 4
    gcp = ds.GetGCPs()[0]
    assert (
        gcp.GCPPixel == pytest.approx(0.5, abs=1e-5)
        and gcp.GCPLine == pytest.approx(0.5, abs=1e-5)
        and gcp.GCPX == pytest.approx(-180, abs=1e-5)
        and gcp.GCPY == pytest.approx(90, abs=1e-5)
        and gcp.GCPZ == pytest.approx(0, abs=1e-5)
    )

    with gdaltest.config_option("GTIFF_POINT_GEO_IGNORE", "YES"):
        gdal.Translate(tmpfilename, "data/byte_gcp_pixelispoint.tif")
        ds = gdal.Open(tmpfilename)
        assert ds.GetMetadataItem("AREA_OR_POINT") == "Point"
        assert ds.GetGCPCount() == 4
        gcp = ds.GetGCPs()[0]
        assert (
            gcp.GCPPixel == pytest.approx(0, abs=1e-5)
            and gcp.GCPLine == pytest.approx(0, abs=1e-5)
            and gcp.GCPX == pytest.approx(-180, abs=1e-5)
            and gcp.GCPY == pytest.approx(90, abs=1e-5)
            and gcp.GCPZ == pytest.approx(0, abs=1e-5)
        )

    gdal.Unlink(tmpfilename)


###############################################################################
# Create copy into a RGB JPEG-IN-TIFF (#3887)


@pytest.mark.require_creation_option("GTiff", "JPEG")
def test_tiff_write_99():

    src_ds = gdal.Open("data/rgbsmall.tif")
    new_ds = gdaltest.tiff_drv.CreateCopy(
        "tmp/test_99.tif", src_ds, options=["COMPRESS=JPEG"]
    )
    del new_ds
    src_ds = None

    ds = gdal.Open("tmp/test_99.tif")
    cs1 = ds.GetRasterBand(1).Checksum()
    cs2 = ds.GetRasterBand(2).Checksum()
    cs3 = ds.GetRasterBand(3).Checksum()
    ds = None

    gdaltest.tiff_drv.Delete("tmp/test_99.tif")

    assert (cs1, cs2, cs3) == (21629, 21651, 21371), "%d,%d,%d" % (cs1, cs2, cs3)


###############################################################################
# Create copy into a 2 band JPEG-IN-TIFF (#3887)


@pytest.mark.require_creation_option("GTiff", "JPEG")
def test_tiff_write_100():

    src_ds = gdaltest.tiff_drv.Create("/vsimem/test_100_src.tif", 16, 16, 2)
    src_ds.GetRasterBand(1).Fill(255)
    new_ds = gdaltest.tiff_drv.CreateCopy(
        "/vsimem/test_100_dst.tif", src_ds, options=["COMPRESS=JPEG"]
    )
    del new_ds
    src_ds = None

    ds = gdal.Open("/vsimem/test_100_dst.tif")
    cs1 = ds.GetRasterBand(1).Checksum()
    cs2 = ds.GetRasterBand(2).Checksum()
    ds = None

    gdaltest.tiff_drv.Delete("/vsimem/test_100_src.tif")
    gdaltest.tiff_drv.Delete("/vsimem/test_100_dst.tif")

    assert (cs1, cs2) == (3118, 0), "%d,%d" % (cs1, cs2)


###############################################################################
# Test CHUNKY_STRIP_READ_SUPPORT (#3894)
# We use random data so the compressed files are big enough to need partial
# reloading. tiff_write_78 doesn't produce enough big data to trigger this...


@pytest.mark.slow()
def test_tiff_write_101():

    md = gdaltest.tiff_drv.GetMetadata()

    if sys.platform.startswith("linux"):
        # Much faster to use /dev/urandom than python random generator !
        f = open("/dev/urandom", "rb")
        rand_array = f.read(10 * 1024 * 1024)
        f.close()
    else:
        import random

        rand_array = b"".join(
            struct.pack("B", random.randint(0, 255)) for _ in range(10 * 1024 * 1024)
        )

    f = open("tmp/tiff_write_101.bin", "wb")
    f.write(rand_array)
    f.close()

    f = open("tmp/tiff_write_101.hdr", "wb")
    f.write(
        """ENVI
samples = 2500
lines   = 4000
bands   = 1
header offset = 0
file type = ENVI Standard
data type = 1
interleave = bsq
byte order = 0
map info = {UTM, 1, 1, 440720.000000, 3751320.000000, 60.000000, 60.000000, 11, North}
band names = {
Band 1}""".encode(
            "ascii"
        )
    )
    f.close()

    src_ds = gdal.Open("tmp/tiff_write_101.bin")
    expected_cs = src_ds.GetRasterBand(1).Checksum()

    for compression_method in ["DEFLATE", "LZW", "JPEG", "PACKBITS", "LZMA"]:
        if md["DMD_CREATIONOPTIONLIST"].find(compression_method) == -1:
            continue

        ds = gdaltest.tiff_drv.CreateCopy(
            "tmp/tiff_write_101.tif",
            src_ds,
            options=[
                "COMPRESS=" + compression_method,
                "BLOCKXSIZE=2500",
                "BLOCKYSIZE=4000",
            ],
        )
        ds = None

        ds = gdal.Open("tmp/tiff_write_101.tif")
        gdal.ErrorReset()
        cs = ds.GetRasterBand(1).Checksum()
        error_msg = gdal.GetLastErrorMsg()
        ds = None

        gdaltest.tiff_drv.Delete("tmp/tiff_write_101.tif")

        if error_msg != "":
            src_ds = None
            gdaltest.tiff_drv.Delete("tmp/tiff_write_101.bin")
            pytest.fail()

        if compression_method != "JPEG" and cs != expected_cs:
            src_ds = None
            gdaltest.tiff_drv.Delete("tmp/tiff_write_101.bin")
            pytest.fail(
                "for compression method %s, got %d instead of %d"
                % (compression_method, cs, expected_cs)
            )

    src_ds = None
    gdaltest.tiff_drv.Delete("tmp/tiff_write_101.bin")


###############################################################################
# Test writing and reading back COMPD_CS


def test_tiff_write_102():

    if int(gdal.GetDriverByName("GTiff").GetMetadataItem("LIBGEOTIFF")) < 1600:
        pytest.skip("requires libgeotiff >= 1.6")

    ds = gdaltest.tiff_drv.Create("/vsimem/tiff_write_102.tif", 1, 1)
    sr = osr.SpatialReference()
    sr.ImportFromEPSG(7401)
    name = sr.GetAttrValue("COMPD_CS")
    wkt = sr.ExportToWkt()
    ds.SetProjection(wkt)
    ds = None
    ds = gdal.Open("/vsimem/tiff_write_102.tif")
    wkt1 = ds.GetProjectionRef()
    ds = None

    with gdaltest.config_option("GTIFF_REPORT_COMPD_CS", "NO"):
        ds = gdal.Open("/vsimem/tiff_write_102.tif")
        wkt2 = ds.GetProjectionRef()
    ds = None

    gdaltest.tiff_drv.Delete("/vsimem/tiff_write_102.tif")

    assert wkt1.startswith("COMPD_CS"), "expected COMPD_CS, but got something else"

    assert not wkt2.startswith("COMPD_CS"), "got COMPD_CS, but did not expected it"

    sr2 = osr.SpatialReference()
    sr2.SetFromUserInput(wkt1)
    got_name = sr2.GetAttrValue("COMPD_CS")
    assert got_name == name, wkt2


###############################################################################
# Test -co COPY_SRC_OVERVIEWS=YES on a multiband source with external overviews (#3938)


def test_tiff_write_103():
    import test_cli_utilities

    if test_cli_utilities.get_gdaladdo_path() is None:
        pytest.skip()

    gdal.Translate(
        "tmp/tiff_write_103_src.tif", "data/rgbsmall.tif", options="-outsize 260 260"
    )
    gdaltest.runexternal(
        test_cli_utilities.get_gdaladdo_path() + " -ro tmp/tiff_write_103_src.tif 2"
    )
    gdal.Translate(
        "tmp/tiff_write_103_dst.tif",
        "tmp/tiff_write_103_src.tif",
        options="-co COPY_SRC_OVERVIEWS=YES",
    )

    src_ds = gdal.Open("tmp/tiff_write_103_src.tif")
    dst_ds = gdal.Open("tmp/tiff_write_103_dst.tif")
    src_cs = src_ds.GetRasterBand(1).GetOverview(0).Checksum()
    dst_cs = dst_ds.GetRasterBand(1).GetOverview(0).Checksum()
    src_ds = None
    dst_ds = None

    gdaltest.tiff_drv.Delete("tmp/tiff_write_103_src.tif")
    gdaltest.tiff_drv.Delete("tmp/tiff_write_103_dst.tif")

    assert src_cs == dst_cs, "did not get expected checksum"


###############################################################################
# Confirm as best we can that we can write geotiff files with detailed
# projection parameters with the correct linear units set.  (#3901)


def test_tiff_write_104():

    src_ds = gdal.Open("data/spaf27_correct.tif")
    dst_ds = gdaltest.tiff_drv.CreateCopy("tmp/test_104.tif", src_ds)

    src_ds = None
    del dst_ds

    ds = gdal.Open("tmp/test_104.tif")
    wkt = ds.GetProjectionRef()
    ds = None

    srs = osr.SpatialReference(wkt)
    fe = srs.GetProjParm(osr.SRS_PP_FALSE_EASTING)
    assert fe == pytest.approx(
        2000000.0, abs=0.001
    ), "did not get expected false easting"

    gdaltest.tiff_drv.Delete("tmp/test_104.tif")


###############################################################################
# Confirm as best we can that we can write geotiff files with detailed
# projection parameters with the correct linear units set.  (#3901)


def test_tiff_write_105():

    shutil.copyfile("data/bug4468.tif", "tmp/bug4468.tif")

    # Update a pixel and close again.
    ds = gdal.Open("tmp/bug4468.tif", gdal.GA_Update)
    data = ds.ReadRaster(0, 0, 1, 1)
    ds.WriteRaster(0, 0, 1, 1, data)
    ds = None

    # Now check if the image is still intact.
    ds = gdal.Open("tmp/bug4468.tif")
    cs = ds.GetRasterBand(1).Checksum()

    assert cs == 2923, "Did not get expected checksum, got %d." % cs

    ds = None

    gdaltest.tiff_drv.Delete("tmp/bug4468.tif")


###############################################################################
# Test the direct copy mechanism of JPEG source


@pytest.mark.require_creation_option("GTiff", "JPEG")
@pytest.mark.require_driver("JPEG")
@pytest.mark.parametrize(
    "filename,options,check_cs",
    [
        ("../gdrivers/data/jpeg/byte_with_xmp.jpg", None, True),
        (
            "../gdrivers/data/jpeg/byte_with_xmp.jpg",
            ["COMPRESS=JPEG", "BLOCKYSIZE=8"],
            True,
        ),
        (
            "../gdrivers/data/jpeg/byte_with_xmp.jpg",
            ["COMPRESS=JPEG", "BLOCKYSIZE=20"],
            True,
        ),
        (
            "../gdrivers/data/jpeg/byte_with_xmp.jpg",
            ["COMPRESS=JPEG", "TILED=YES", "BLOCKYSIZE=16", "BLOCKXSIZE=16"],
            True,
        ),
        # Strip organization of YCbCr does *NOT* give exact pixels w.r.t. original image
        ("../gdrivers/data/jpeg/albania.jpg", None, False),
        # Whole copy of YCbCr *DOES* give exact pixels w.r.t. original image
        (
            "../gdrivers/data/jpeg/albania.jpg",
            ["COMPRESS=JPEG", "BLOCKYSIZE=260"],
            False,
        ),
        (
            "../gdrivers/data/jpeg/albania.jpg",
            ["COMPRESS=JPEG", "BLOCKYSIZE=260", "INTERLEAVE=PIXEL"],
            True,
        ),
        (
            "../gdrivers/data/jpeg/albania.jpg",
            ["COMPRESS=JPEG", "BLOCKYSIZE=260", "INTERLEAVE=BAND"],
            False,
        ),
        # Tiled organization of YCbCr does *NOT* give exact pixels w.r.t. original image
        ("../gdrivers/data/jpeg/albania.jpg", ["COMPRESS=JPEG", "TILED=YES"], False),
        # The source is a JPEG in RGB colorspace (usually it is YCbCr).
        (
            "../gdrivers/data/jpeg/rgbsmall_rgb.jpg",
            ["COMPRESS=JPEG", "BLOCKYSIZE=8"],
            True,
        ),
    ],
)
def test_tiff_write_direct_copy_jpeg(filename, options, check_cs):

    if options is None:
        options = ["COMPRESS=JPEG"]

    src_ds = gdal.Open(filename)
    nbands = src_ds.RasterCount
    src_cs = []
    for i in range(nbands):
        src_cs.append(src_ds.GetRasterBand(i + 1).Checksum())

    out_ds = gdaltest.tiff_drv.CreateCopy(
        "/vsimem/tiff_write_106.tif", src_ds, options=options
    )
    out_ds = None

    out_ds = gdal.Open("/vsimem/tiff_write_106.tif")
    cs = []
    for i in range(nbands):
        cs.append(out_ds.GetRasterBand(i + 1).Checksum())
    out_ds = None

    gdal.Unlink("/vsimem/tiff_write_106.tif")

    if check_cs:
        for i in range(nbands):
            assert cs[i] == src_cs[i], "did not get expected checksum"
    else:
        for i in range(nbands):
            assert cs[i] != 0, "did not get expected checksum"


###############################################################################
# Test CreateCopy() interruption


def test_tiff_write_114():

    tst = gdaltest.GDALTest("GTiff", "byte.tif", 1, 4672)

    tst.testCreateCopy(vsimem=1, interrupt_during_copy=True)


###############################################################################
# Test writing a pixel interleaved RGBA JPEG-compressed TIFF


@pytest.mark.require_creation_option("GTiff", "JPEG")
def test_tiff_write_115():

    tmpfilename = "/vsimem/tiff_write_115.tif"

    src_ds = gdal.Open("data/stefan_full_rgba.tif")
    ds = gdaltest.tiff_drv.CreateCopy(tmpfilename, src_ds, options=["COMPRESS=JPEG"])
    assert ds is not None
    ds = None
    src_ds = None

    f = gdal.VSIFOpenL(tmpfilename + ".aux.xml", "rb")
    if f is not None:
        gdal.VSIFCloseL(f)
        gdal.Unlink(tmpfilename)
        pytest.fail()

    ds = gdal.Open(tmpfilename)
    md = ds.GetMetadata("IMAGE_STRUCTURE")
    if md["INTERLEAVE"] != "PIXEL":
        ds = None
        gdal.Unlink(tmpfilename)
        pytest.fail()

    expected_cs = [16404, 62700, 37913, 14174]
    for i in range(4):
        cs = ds.GetRasterBand(i + 1).Checksum()
        if cs != expected_cs[i]:
            ds = None
            gdal.Unlink(tmpfilename)
            pytest.fail()

        if (
            ds.GetRasterBand(i + 1).GetRasterColorInterpretation()
            != gdal.GCI_RedBand + i
        ):
            ds = None
            gdal.Unlink(tmpfilename)
            pytest.fail()

    ds = None
    gdal.Unlink(tmpfilename)


###############################################################################
# Test writing a band interleaved RGBA JPEG-compressed TIFF


@pytest.mark.require_creation_option("GTiff", "JPEG")
def test_tiff_write_116():

    tmpfilename = "/vsimem/tiff_write_116.tif"

    src_ds = gdal.Open("data/stefan_full_rgba.tif")
    ds = gdaltest.tiff_drv.CreateCopy(
        tmpfilename, src_ds, options=["COMPRESS=JPEG", "INTERLEAVE=BAND"]
    )
    assert ds is not None
    ds = None
    src_ds = None

    f = gdal.VSIFOpenL(tmpfilename + ".aux.xml", "rb")
    if f is not None:
        gdal.VSIFCloseL(f)
        gdal.Unlink(tmpfilename)
        pytest.fail()

    ds = gdal.Open(tmpfilename)
    md = ds.GetMetadata("IMAGE_STRUCTURE")
    if md["INTERLEAVE"] != "BAND":
        ds = None
        gdal.Unlink(tmpfilename)
        pytest.fail()

    expected_cs = [16404, 62700, 37913, 14174]
    for i in range(4):
        cs = ds.GetRasterBand(i + 1).Checksum()
        if cs != expected_cs[i]:
            ds = None
            gdal.Unlink(tmpfilename)
            pytest.fail()

        if (
            ds.GetRasterBand(i + 1).GetRasterColorInterpretation()
            != gdal.GCI_RedBand + i
        ):
            ds = None
            gdal.Unlink(tmpfilename)
            pytest.fail()

    ds = None
    gdal.Unlink(tmpfilename)


###############################################################################
# Test bugfix for ticket #4771 (rewriting of a deflate compressed tile, libtiff bug)


def test_tiff_write_117():
    # This fail with a libtiff 4.x older than 2012-08-13
    md = gdaltest.tiff_drv.GetMetadata()
    if md["LIBTIFF"] != "INTERNAL":
        pytest.skip()

    import random

    # so that we have always the same random :-)
    random.seed(0)

    ds = gdal.GetDriverByName("GTiff").Create(
        "/vsimem/tiff_write_117.tif",
        512,
        256,
        2,
        options=["COMPRESS=DEFLATE", "TILED=YES"],
    )

    # Write first tile so that its byte count of that tile is 2048 (a multiple of 1024)
    adjust = 1254
    data = "0" * (65536 - adjust) + "".join(
        [("%c" % random.randint(0, 255)) for _ in range(adjust)]
    )
    ds.GetRasterBand(1).WriteRaster(0, 0, 256, 256, data)

    # Second tile will be implicitly written at closing, or we could write
    # any content

    ds = None

    ds = gdal.Open("/vsimem/tiff_write_117.tif", gdal.GA_Update)

    # Will adjust tif_rawdatasize to TIFFroundup_64((uint64)size, 1024) = TIFFroundup_64(2048, 1024) = 2048
    ds.GetRasterBand(1).ReadRaster(0, 0, 256, 256)

    # The new bytecount will be greater than 2048
    data = "".join([("%c" % random.randint(0, 255)) for _ in range(256 * 256)])
    ds.GetRasterBand(1).WriteRaster(0, 0, 256, 256, data)

    # Make sure that data is written now
    ds.FlushCache()

    # Oops, without fix, the second tile will have been overwritten and an error will be emitted
    data = ds.GetRasterBand(1).ReadRaster(256, 0, 256, 256)

    ds = None

    gdal.Unlink("/vsimem/tiff_write_117.tif")

    assert (
        data is not None
    ), "if GDAL is configured with external libtiff 4.x, it can fail if it is older than 4.0.3. With internal libtiff, should not fail"


###############################################################################
# Test bugfix for ticket gh #4538 (rewriting of a deflate compressed tile, libtiff bug)


def test_tiff_write_rewrite_in_place_issue_gh_4538():
    # This fail with libtiff <= 4.3.0
    md = gdaltest.tiff_drv.GetMetadata()
    if md["LIBTIFF"] != "INTERNAL":
        pytest.skip()

    # Defeats the logic that fixed test_tiff_write_117

    import array

    filename = "/vsimem/tmp.tif"
    ds = gdal.GetDriverByName("GTiff").Create(
        filename,
        144 * 2,
        128,
        1,
        options=["TILED=YES", "COMPRESS=PACKBITS", "BLOCKXSIZE=144", "BLOCKYSIZE=128"],
    )
    x = ((144 * 128) // 2) - 645
    ds.GetRasterBand(1).WriteRaster(
        0,
        0,
        144,
        128,
        b"\x00" * x + array.array("B", [i % 255 for i in range(144 * 128 - x)]),
    )
    block1_data = b"\x00" * (x + 8) + array.array(
        "B", [i % 255 for i in range(144 * 128 - (x + 8))]
    )
    ds.GetRasterBand(1).WriteRaster(144, 0, 144, 128, block1_data)
    ds = None

    ds = gdal.Open(filename, gdal.GA_Update)
    ds.GetRasterBand(1).ReadRaster(144, 0, 144, 128)
    block0_data = array.array("B", [i % 255 for i in range(144 * 128)])
    ds.GetRasterBand(1).WriteRaster(0, 0, 144, 128, block0_data)
    ds = None

    ds = gdal.Open(filename)
    assert ds.GetRasterBand(1).ReadRaster(0, 0, 144, 128) == block0_data
    assert ds.GetRasterBand(1).ReadRaster(144, 0, 144, 128) == block1_data
    ds = None

    gdal.Unlink(filename)


###############################################################################
# Test bugfix for ticket #4816


def test_tiff_write_118():

    ds = gdal.GetDriverByName("GTiff").Create("/vsimem/tiff_write_118.tif", 1, 1)
    # Should be rejected in a non-XML domain
    ds.SetMetadata("bla", "foo")
    ds = None

    ds = gdal.Open("/vsimem/tiff_write_118.tif")
    md = ds.GetMetadata("foo")
    ds = None

    gdal.Unlink("/vsimem/tiff_write_118.tif")

    assert not md


###############################################################################
# Test bugfix for ticket #4816


def test_tiff_write_119():

    ds = gdal.GetDriverByName("GTiff").Create("/vsimem/tiff_write_119.tif", 1, 1)
    ds.SetMetadata("foo=bar", "foo")
    ds = None

    ds = gdal.Open("/vsimem/tiff_write_119.tif")
    md = ds.GetMetadata("foo")
    ds = None

    gdal.Unlink("/vsimem/tiff_write_119.tif")

    assert md["foo"] == "bar"


###############################################################################
# Test bugfix for ticket #4816


def test_tiff_write_120():

    ds = gdal.GetDriverByName("GTiff").Create("/vsimem/tiff_write_120.tif", 1, 1)
    ds.SetMetadata("<foo/>", "xml:foo")
    ds = None

    ds = gdal.Open("/vsimem/tiff_write_120.tif")
    md = ds.GetMetadata("xml:foo")
    ds = None

    gdal.Unlink("/vsimem/tiff_write_120.tif")

    assert len(md) == 1
    assert md[0] == "<foo/>"


###############################################################################
# Test error cases of COPY_SRC_OVERVIEWS creation option


def test_tiff_write_121():

    # Test when the overview band is NULL
    src_ds = gdal.Open(
        """<VRTDataset rasterXSize="20" rasterYSize="20">
  <VRTRasterBand dataType="Byte" band="1">
    <SimpleSource>
      <SourceFilename relativeToVRT="1">data/byte.tif</SourceFilename>
      <SourceBand>1</SourceBand>
    </SimpleSource>
    <Overview>
      <SourceFilename relativeToVRT="0">non_existing</SourceFilename>
      <SourceBand>1</SourceBand>
    </Overview>
  </VRTRasterBand>
</VRTDataset>"""
    )
    with gdal.quiet_errors():
        ds = gdaltest.tiff_drv.CreateCopy(
            "/vsimem/tiff_write_121.tif", src_ds, options=["COPY_SRC_OVERVIEWS=YES"]
        )
    assert ds is None
    src_ds = None

    # Test when the overview count isn't the same on all base bands
    src_ds = gdal.Open(
        """<VRTDataset rasterXSize="20" rasterYSize="20">
  <VRTRasterBand dataType="Byte" band="1">
    <SimpleSource>
      <SourceFilename relativeToVRT="1">data/byte.tif</SourceFilename>
      <SourceBand>1</SourceBand>
    </SimpleSource>
    <Overview>
      <SourceFilename relativeToVRT="1">data/byte.tif</SourceFilename>
      <SourceBand>1</SourceBand>
    </Overview>
  </VRTRasterBand>
  <VRTRasterBand dataType="Byte" band="2">
    <SimpleSource>
      <SourceFilename relativeToVRT="1">data/byte.tif</SourceFilename>
      <SourceBand>1</SourceBand>
    </SimpleSource>
  </VRTRasterBand>
</VRTDataset>"""
    )
    with gdal.quiet_errors():
        ds = gdaltest.tiff_drv.CreateCopy(
            "/vsimem/tiff_write_121.tif", src_ds, options=["COPY_SRC_OVERVIEWS=YES"]
        )
    assert ds is None
    src_ds = None

    # Test when the overview bands of same level have not the same dimensions
    src_ds = gdal.Open(
        """<VRTDataset rasterXSize="20" rasterYSize="20">
  <VRTRasterBand dataType="Byte" band="1">
    <SimpleSource>
      <SourceFilename relativeToVRT="1">data/byte.tif</SourceFilename>
      <SourceBand>1</SourceBand>
    </SimpleSource>
    <Overview>
      <SourceFilename relativeToVRT="1">data/byte.tif</SourceFilename>
      <SourceBand>1</SourceBand>
    </Overview>
  </VRTRasterBand>
  <VRTRasterBand dataType="Byte" band="2">
    <SimpleSource>
      <SourceFilename relativeToVRT="1">data/byte.tif</SourceFilename>
      <SourceBand>1</SourceBand>
    </SimpleSource>
    <Overview>
      <SourceFilename relativeToVRT="0">data/rgbsmall.tif</SourceFilename>
      <SourceBand>1</SourceBand>
    </Overview>
  </VRTRasterBand>
</VRTDataset>"""
    )
    with gdal.quiet_errors():
        ds = gdaltest.tiff_drv.CreateCopy(
            "/vsimem/tiff_write_121.tif", src_ds, options=["COPY_SRC_OVERVIEWS=YES"]
        )
    assert ds is None
    src_ds = None


###############################################################################
# Test write and read of some TIFFTAG_RESOLUTIONUNIT tags where '*'/'' is
# specified (gdalwarp conflicts)
# Expected to fail (properly) with older libtiff versions (<=3.8.2 for sure)


def test_tiff_write_122():
    new_ds = gdaltest.tiff_drv.Create("tmp/tags122.tif", 1, 1, 1)

    new_ds.SetMetadata(
        {
            "TIFFTAG_RESOLUTIONUNIT": "*",
        }
    )

    new_ds = None
    # hopefully it's closed now!

    new_ds = gdal.Open("tmp/tags122.tif")
    md = new_ds.GetMetadata()

    if "TIFFTAG_RESOLUTIONUNIT" not in md:
        pytest.fail("Couldn't find tag TIFFTAG_RESOLUTIONUNIT")

    elif md["TIFFTAG_RESOLUTIONUNIT"] != "1 (unitless)":
        pytest.fail(
            "Got unexpected tag TIFFTAG_RESOLUTIONUNIT='%s' (expected ='1 (unitless)')"
            % md["TIFFTAG_RESOLUTIONUNIT"]
        )

    new_ds = None

    gdaltest.tiff_drv.Delete("tmp/tags122.tif")


###############################################################################
# Test implicit photometric interpretation


def test_tiff_write_123():

    src_ds = gdaltest.tiff_drv.Create(
        "/vsimem/tiff_write_123_src.tif", 1, 1, 5, gdal.GDT_Int16
    )
    src_ds.GetRasterBand(2).SetColorInterpretation(gdal.GCI_GreenBand)
    src_ds.GetRasterBand(5).SetColorInterpretation(gdal.GCI_AlphaBand)
    src_ds.GetRasterBand(3).SetColorInterpretation(gdal.GCI_BlueBand)
    src_ds.GetRasterBand(1).SetColorInterpretation(gdal.GCI_RedBand)
    src_ds = None
    statBuf = gdal.VSIStatL(
        "/vsimem/tiff_write_123_src.tif.aux.xml",
        gdal.VSI_STAT_EXISTS_FLAG | gdal.VSI_STAT_NATURE_FLAG | gdal.VSI_STAT_SIZE_FLAG,
    )
    assert statBuf is None, "did not expect PAM file"
    src_ds = gdal.Open("/vsimem/tiff_write_123_src.tif")
    assert (
        src_ds.GetMetadataItem("TIFFTAG_GDAL_METADATA", "_DEBUG_") is None
    ), "did not expect a TIFFTAG_GDAL_METADATA tag"
    assert src_ds.GetMetadataItem("TIFFTAG_PHOTOMETRIC", "_DEBUG_") == "2"
    assert src_ds.GetRasterBand(1).GetColorInterpretation() == gdal.GCI_RedBand
    assert src_ds.GetRasterBand(4).GetColorInterpretation() == gdal.GCI_Undefined
    assert src_ds.GetRasterBand(5).GetColorInterpretation() == gdal.GCI_AlphaBand
    assert src_ds.GetMetadataItem("TIFFTAG_EXTRASAMPLES", "_DEBUG_") == "0,2"

    new_ds = gdaltest.tiff_drv.CreateCopy("/vsimem/tiff_write_123.tif", src_ds)
    del new_ds
    statBuf = gdal.VSIStatL(
        "/vsimem/tiff_write_123.tif.aux.xml",
        gdal.VSI_STAT_EXISTS_FLAG | gdal.VSI_STAT_NATURE_FLAG | gdal.VSI_STAT_SIZE_FLAG,
    )
    assert statBuf is None, "did not expect PAM file"
    ds = gdal.Open("/vsimem/tiff_write_123.tif")
    assert (
        ds.GetMetadataItem("TIFFTAG_GDAL_METADATA", "_DEBUG_") is None
    ), "did not expect a TIFFTAG_GDAL_METADATA tag"
    assert ds.GetRasterBand(1).GetColorInterpretation() == gdal.GCI_RedBand
    assert src_ds.GetRasterBand(4).GetColorInterpretation() == gdal.GCI_Undefined
    assert src_ds.GetRasterBand(5).GetColorInterpretation() == gdal.GCI_AlphaBand
    assert ds.GetMetadataItem("TIFFTAG_EXTRASAMPLES", "_DEBUG_") == "0,2"
    ds = None

    gdaltest.tiff_drv.Delete("/vsimem/tiff_write_123_src.tif")
    gdaltest.tiff_drv.Delete("/vsimem/tiff_write_123.tif")

    # From implicit RGB to BGR (with Photometric = MinIsBlack)
    ds = gdaltest.tiff_drv.Create(
        "/vsimem/tiff_write_123_bgr.tif", 1, 1, 3, gdal.GDT_Byte
    )
    assert ds.GetMetadataItem("TIFFTAG_PHOTOMETRIC", "_DEBUG_") == "2"
    assert ds.GetMetadataItem("TIFFTAG_EXTRASAMPLES", "_DEBUG_") is None
    ds.GetRasterBand(1).SetColorInterpretation(gdal.GCI_BlueBand)
    assert ds.GetRasterBand(1).GetColorInterpretation() == gdal.GCI_BlueBand
    ds.GetRasterBand(2).SetColorInterpretation(gdal.GCI_GreenBand)
    ds.GetRasterBand(3).SetColorInterpretation(gdal.GCI_RedBand)
    ds = None
    statBuf = gdal.VSIStatL(
        "/vsimem/tiff_write_123_bgr.tif.aux.xml",
        gdal.VSI_STAT_EXISTS_FLAG | gdal.VSI_STAT_NATURE_FLAG | gdal.VSI_STAT_SIZE_FLAG,
    )
    assert statBuf is None, "did not expect a PAM file"
    ds = gdal.Open("/vsimem/tiff_write_123_bgr.tif")
    assert ds.GetMetadataItem("TIFFTAG_PHOTOMETRIC", "_DEBUG_") == "1"
    assert ds.GetMetadataItem("TIFFTAG_EXTRASAMPLES", "_DEBUG_") == "0,0"
    assert (
        ds.GetMetadataItem("TIFFTAG_GDAL_METADATA", "_DEBUG_") is not None
    ), "expected a TIFFTAG_GDAL_METADATA tag"
    assert ds.GetRasterBand(1).GetColorInterpretation() == gdal.GCI_BlueBand
    assert ds.GetRasterBand(2).GetColorInterpretation() == gdal.GCI_GreenBand
    assert ds.GetRasterBand(3).GetColorInterpretation() == gdal.GCI_RedBand
    ds = None

    # Test overriding internal color interpretation with PAM one (read-only mode)
    ds = gdal.Open("/vsimem/tiff_write_123_bgr.tif")
    ds.GetRasterBand(1).SetColorInterpretation(gdal.GCI_RedBand)
    ds = None
    statBuf = gdal.VSIStatL(
        "/vsimem/tiff_write_123_bgr.tif.aux.xml",
        gdal.VSI_STAT_EXISTS_FLAG | gdal.VSI_STAT_NATURE_FLAG | gdal.VSI_STAT_SIZE_FLAG,
    )
    assert statBuf is not None, "expected a PAM file"
    ds = gdal.Open("/vsimem/tiff_write_123_bgr.tif")
    assert ds.GetRasterBand(1).GetColorInterpretation() == gdal.GCI_RedBand
    assert ds.GetRasterBand(2).GetColorInterpretation() == gdal.GCI_GreenBand
    assert ds.GetRasterBand(3).GetColorInterpretation() == gdal.GCI_RedBand
    ds = None
    gdaltest.tiff_drv.Delete("/vsimem/tiff_write_123_bgr.tif")

    # Create a BGR with PROFILE=BASELINE --> no TIFFTAG_GDAL_METADATA tag, but .aux.xml instead
    ds = gdaltest.tiff_drv.Create(
        "/vsimem/tiff_write_123_bgr.tif", 1, 1, 3, options=["PROFILE=BASELINE"]
    )
    ds.GetRasterBand(1).SetColorInterpretation(gdal.GCI_BlueBand)
    ds.GetRasterBand(2).SetColorInterpretation(gdal.GCI_GreenBand)
    ds.GetRasterBand(3).SetColorInterpretation(gdal.GCI_RedBand)
    ds = None
    statBuf = gdal.VSIStatL(
        "/vsimem/tiff_write_123_bgr.tif.aux.xml",
        gdal.VSI_STAT_EXISTS_FLAG | gdal.VSI_STAT_NATURE_FLAG | gdal.VSI_STAT_SIZE_FLAG,
    )
    assert statBuf is not None, "expected a PAM file"
    ds = gdal.Open("/vsimem/tiff_write_123_bgr.tif")
    assert (
        ds.GetMetadataItem("TIFFTAG_GDAL_METADATA", "_DEBUG_") is None
    ), "did not expect a TIFFTAG_GDAL_METADATA tag"
    assert ds.GetRasterBand(1).GetColorInterpretation() == gdal.GCI_BlueBand
    assert ds.GetRasterBand(2).GetColorInterpretation() == gdal.GCI_GreenBand
    assert ds.GetRasterBand(3).GetColorInterpretation() == gdal.GCI_RedBand
    ds = None
    gdaltest.tiff_drv.Delete("/vsimem/tiff_write_123_bgr.tif")

    # From implicit RGBA to MINISBLACK
    ds = gdaltest.tiff_drv.Create(
        "/vsimem/tiff_write_123_rgba.tif", 1, 1, 4, gdal.GDT_Byte
    )
    assert ds.GetMetadataItem("TIFFTAG_PHOTOMETRIC", "_DEBUG_") == "2"
    assert ds.GetRasterBand(1).GetColorInterpretation() == gdal.GCI_RedBand
    assert ds.GetRasterBand(4).GetColorInterpretation() == gdal.GCI_AlphaBand
    assert ds.GetMetadataItem("TIFFTAG_EXTRASAMPLES", "_DEBUG_") == "2"

    ds.GetRasterBand(1).SetColorInterpretation(gdal.GCI_Undefined)
    assert ds.GetRasterBand(1).GetColorInterpretation() == gdal.GCI_Undefined
    assert ds.GetMetadataItem("TIFFTAG_PHOTOMETRIC", "_DEBUG_") == "1"
    assert ds.GetMetadataItem("TIFFTAG_EXTRASAMPLES", "_DEBUG_") == "0,0,2"
    ds = None

    gdaltest.tiff_drv.Delete("/vsimem/tiff_write_123_rgba.tif")

    # From that implicit RGBA to Gray,Undefined,Undefined,Alpha doesn't
    # produce PAM file
    ds = gdaltest.tiff_drv.Create(
        "/vsimem/tiff_write_123_guua.tif", 1, 1, 4, gdal.GDT_Byte
    )
    ds.GetRasterBand(1).SetColorInterpretation(gdal.GCI_GrayIndex)
    ds.GetRasterBand(2).SetColorInterpretation(gdal.GCI_Undefined)
    ds.GetRasterBand(3).SetColorInterpretation(gdal.GCI_Undefined)
    ds.GetRasterBand(4).SetColorInterpretation(gdal.GCI_AlphaBand)
    ds = None
    statBuf = gdal.VSIStatL(
        "/vsimem/tiff_write_123_guua.tif.aux.xml",
        gdal.VSI_STAT_EXISTS_FLAG | gdal.VSI_STAT_NATURE_FLAG | gdal.VSI_STAT_SIZE_FLAG,
    )
    assert statBuf is None, "did not expect PAM file"
    ds = gdal.Open("/vsimem/tiff_write_123_guua.tif")
    assert (
        ds.GetMetadataItem("TIFFTAG_GDAL_METADATA", "_DEBUG_") is None
    ), "did not expect TIFFTAG_GDAL_METADATA tag"
    assert ds.GetRasterBand(1).GetColorInterpretation() == gdal.GCI_GrayIndex
    assert ds.GetRasterBand(4).GetColorInterpretation() == gdal.GCI_AlphaBand
    ds = None
    gdaltest.tiff_drv.Delete("/vsimem/tiff_write_123_guua.tif")

    # Test that CreateCopy() from a RGB UInt16 doesn't generate ExtraSamples
    src_ds = gdaltest.tiff_drv.Create(
        "/vsimem/tiff_write_123_rgb_src.tif",
        1,
        1,
        3,
        gdal.GDT_UInt16,
        options=["PHOTOMETRIC=RGB"],
    )
    ds = gdaltest.tiff_drv.CreateCopy("/vsimem/tiff_write_123_rgb.tif", src_ds)
    src_ds = None
    assert ds.GetMetadataItem("TIFFTAG_PHOTOMETRIC", "_DEBUG_") == "2"
    assert ds.GetMetadataItem("TIFFTAG_EXTRASAMPLES", "_DEBUG_") is None
    ds = None
    gdaltest.tiff_drv.Delete("/vsimem/tiff_write_123_rgb_src.tif")
    gdaltest.tiff_drv.Delete("/vsimem/tiff_write_123_rgb.tif")

    # Test that PHOTOMETRIC=RGB overrides the source color interpretation of the
    # first 3 bands
    src_ds = gdal.GetDriverByName("MEM").Create("", 1, 1, 3)
    gdaltest.tiff_drv.CreateCopy(
        "/vsimem/tiff_write_123_rgb.tif", src_ds, options=["PHOTOMETRIC=RGB"]
    )
    ds = gdal.Open("/vsimem/tiff_write_123_rgb.tif")
    assert ds.GetRasterBand(1).GetColorInterpretation() == gdal.GCI_RedBand
    ds = None
    gdaltest.tiff_drv.Delete("/vsimem/tiff_write_123_rgb.tif")

    src_ds = gdal.GetDriverByName("MEM").Create("", 1, 1, 5)
    src_ds.GetRasterBand(5).SetColorInterpretation(gdal.GCI_AlphaBand)
    gdaltest.tiff_drv.CreateCopy(
        "/vsimem/tiff_write_123_rgbua.tif", src_ds, options=["PHOTOMETRIC=RGB"]
    )
    ds = gdal.Open("/vsimem/tiff_write_123_rgbua.tif")
    assert ds.GetRasterBand(1).GetColorInterpretation() == gdal.GCI_RedBand
    assert ds.GetRasterBand(4).GetColorInterpretation() == gdal.GCI_Undefined
    assert ds.GetRasterBand(5).GetColorInterpretation() == gdal.GCI_AlphaBand
    ds = None
    gdaltest.tiff_drv.Delete("/vsimem/tiff_write_123_rgbua.tif")

    # Test updating alpha to undefined
    gdaltest.tiff_drv.Create(
        "/vsimem/tiff_write_123_rgba_to_undefined.tif",
        1,
        1,
        4,
        options=["PHOTOMETRIC=RGB", "ALPHA=YES"],
    )
    ds = gdal.Open("/vsimem/tiff_write_123_rgba_to_undefined.tif", gdal.GA_Update)
    ds.GetRasterBand(4).SetColorInterpretation(gdal.GCI_Undefined)
    ds = None
    statBuf = gdal.VSIStatL(
        "/vsimem/tiff_write_123_rgba_to_undefined.tif.aux.xml",
        gdal.VSI_STAT_EXISTS_FLAG | gdal.VSI_STAT_NATURE_FLAG | gdal.VSI_STAT_SIZE_FLAG,
    )
    assert statBuf is None, "did not expect PAM file"
    ds = gdal.Open("/vsimem/tiff_write_123_rgba_to_undefined.tif")
    assert ds.GetRasterBand(4).GetColorInterpretation() == gdal.GCI_Undefined
    ds = None
    gdaltest.tiff_drv.Delete("/vsimem/tiff_write_123_rgba_to_undefined.tif")


###############################################################################
# Test error cases with palette creation


def test_tiff_write_124():

    ds = gdaltest.tiff_drv.Create("/vsimem/tiff_write_124.tif", 1, 1, 3, gdal.GDT_Byte)

    with gdal.quiet_errors():
        # Test "SetColorTable() can only be called on band 1"
        ret = ds.GetRasterBand(2).SetColorTable(gdal.ColorTable())
    assert ret != 0

    with gdal.quiet_errors():
        # Test "SetColorTable() not supported for multi-sample TIFF files"
        ret = ds.GetRasterBand(1).SetColorTable(gdal.ColorTable())
    assert ret != 0

    ds = None

    ds = gdaltest.tiff_drv.Create(
        "/vsimem/tiff_write_124.tif", 1, 1, 1, gdal.GDT_UInt32
    )
    with gdal.quiet_errors():
        # Test "SetColorTable() only supported for Byte or UInt16 bands in TIFF format."
        ret = ds.GetRasterBand(1).SetColorTable(gdal.ColorTable())
    assert ret != 0
    ds = None

    with gdal.quiet_errors():
        # Test "SetColorTable() only supported for Byte or UInt16 bands in TIFF format."
        ds = gdaltest.tiff_drv.Create(
            "/vsimem/tiff_write_124.tif",
            1,
            1,
            1,
            gdal.GDT_UInt32,
            options=["PHOTOMETRIC=PALETTE"],
        )
    ds = None

    gdaltest.tiff_drv.Delete("/vsimem/tiff_write_124.tif")


###############################################################################
# Test out-of-memory conditions with SplitBand and SplitBitmapBand


def test_tiff_write_125():

    if gdal.GetConfigOption("SKIP_MEM_INTENSIVE_TEST") is not None:
        pytest.skip()

    ds = gdaltest.tiff_drv.Create(
        "/vsimem/tiff_write_125.tif",
        2147000000,
        5000,
        65535,
        options=["SPARSE_OK=YES", "BLOCKYSIZE=5000", "COMPRESS=LZW", "BIGTIFF=NO"],
    )
    ds = None

    ds = gdal.Open("/vsimem/tiff_write_125.tif")
    # Will not open on 32-bit due to overflow
    if ds is not None:
        with gdal.quiet_errors():
            ds.GetRasterBand(1).ReadBlock(0, 0)

    ds = gdal.GetDriverByName("GTiff").Create(
        "/vsimem/tiff_write_125.tif",
        2147000000,
        5000,
        1,
        options=[
            "NBITS=1",
            "SPARSE_OK=YES",
            "BLOCKYSIZE=5000",
            "COMPRESS=LZW",
            "BIGTIFF=NO",
        ],
    )
    ds = None

    ds = gdal.Open("/vsimem/tiff_write_125.tif")
    # Will not open on 32-bit due to overflow
    if ds is not None:
        with gdal.quiet_errors():
            ds.GetRasterBand(1).ReadBlock(0, 0)

    gdal.Unlink("/vsimem/tiff_write_125.tif")


###############################################################################
# Test implicit JPEG-in-TIFF overviews


@pytest.mark.require_creation_option("GTiff", "JPEG")
@pytest.mark.require_driver("JPEG")
def test_tiff_write_126():

    src_ds = gdal.Open("../gdrivers/data/small_world_400pct.vrt")

    options_list = [
        (
            ["COMPRESS=JPEG", "PHOTOMETRIC=YCBCR"],
            [48788, 56561, 56462],
            [61397, 2463, 2454, 2727],
            [29605, 33654, 34633],
            [10904, 10453, 10361],
        ),
        (
            ["COMPRESS=JPEG", "PHOTOMETRIC=YCBCR", "JPEGTABLESMODE=0"],
            [48788, 56561, 56462],
            [61397, 2463, 2454, 2727],
            [29605, 33654, 34633],
            [10904, 10453, 10361],
        ),
        (
            ["COMPRESS=JPEG", "PHOTOMETRIC=YCBCR", "TILED=YES"],
            [48788, 56561, 56462],
            [61397, 2463, 2454, 2727],
            [29605, 33654, 34633],
            [10904, 10453, 10361],
        ),
        (
            ["COMPRESS=JPEG", "PHOTOMETRIC=YCBCR", "BLOCKYSIZE=800"],
            [48788, 56561, 56462],
            [61397, 2463, 2454, 2727],
            [29605, 33654, 34633],
            [10904, 10453, 10361],
        ),
        (
            ["COMPRESS=JPEG", "PHOTOMETRIC=YCBCR", "BLOCKYSIZE=64"],
            [48788, 56561, 56462],
            [61397, 2463, 2454, 2727],
            [29605, 33654, 34633],
            [10904, 10453, 10361],
        ),
        (
            ["COMPRESS=JPEG"],
            [49887, 58937],
            [59311, 2826],
            [30829, 34806],
            [11664, 58937],
        ),
        (
            ["COMPRESS=JPEG", "INTERLEAVE=BAND"],
            [49887, 58937],
            [59311, 2826],
            [30829, 34806],
            [11664, 58937],
        ),
        (
            ["COMPRESS=JPEG", "INTERLEAVE=BAND", "TILED=YES"],
            [49887, 58937],
            [59311, 2826],
            [30829, 34806],
            [11664, 58937],
        ),
        (
            ["COMPRESS=JPEG", "INTERLEAVE=BAND", "BLOCKYSIZE=800"],
            [49887, 58937],
            [59311, 2826],
            [30829, 34806],
            [11664, 58937],
        ),
        (
            ["COMPRESS=JPEG", "INTERLEAVE=BAND", "BLOCKYSIZE=32"],
            [49887, 58937],
            [59311, 2826],
            [30829, 34806],
            [11664, 58937],
        ),
        (
            ["COMPRESS=JPEG", "BLOCKYSIZE=8"],
            [49887, 58937],
            [59311, 2826],
            [30829, 34806],
            [11664, 58937],
        ),
    ]

    for (options, cs1, cs2, cs3, cs4) in options_list:
        os.environ["JPEGMEM"] = "500M"
        ds = gdaltest.tiff_drv.CreateCopy(
            "/vsimem/tiff_write_126.tif", src_ds, options=options
        )
        ds = None
        del os.environ["JPEGMEM"]

        ds = gdal.Open("/vsimem/tiff_write_126.tif")
        # Officially we have 0 public overviews...
        assert ds.GetRasterBand(1).GetOverviewCount() == 0, options
        # But they do exist...
        cs = ds.GetRasterBand(1).GetOverview(0).Checksum()
        assert cs in cs1, options
        cs = ds.GetRasterBand(2).GetOverview(0).Checksum()
        assert cs in cs2, options
        cs = ds.GetRasterBand(1).GetOverview(1).Checksum()
        assert cs in cs3, options
        cs = ds.GetRasterBand(1).GetOverview(2).Checksum()
        assert cs in cs4, options
        assert ds.GetRasterBand(1).GetOverview(-1) is None, options
        assert ds.GetRasterBand(1).GetOverview(3) is None, options
        ovr_1_data = (
            ds.GetRasterBand(1).GetOverview(1).GetDataset().ReadRaster(0, 0, 400, 200)
        )
        subsampled_data = ds.ReadRaster(0, 0, 1600, 800, 400, 200)
        assert ovr_1_data == subsampled_data, options
        ds = None

        gdaltest.tiff_drv.Delete("/vsimem/tiff_write_126.tif")

    src_ds = gdal.Open("../gdrivers/data/small_world_400pct_1band.vrt")

    options_list = [
        (["COMPRESS=JPEG"], [49887, 58937], [30829, 34806], [11664, 58937]),
        (
            ["COMPRESS=JPEG", "TILED=YES"],
            [49887, 58937],
            [30829, 34806],
            [11664, 58937],
        ),
        (
            ["COMPRESS=JPEG", "BLOCKYSIZE=800"],
            [49887, 58937],
            [30829, 34806],
            [11664, 58937],
        ),
        (
            ["COMPRESS=JPEG", "BLOCKYSIZE=32"],
            [49887, 58937],
            [30829, 34806],
            [11664, 58937],
        ),
    ]

    for (options, cs1, cs3, cs4) in options_list:
        os.environ["JPEGMEM"] = "500M"
        ds = gdaltest.tiff_drv.CreateCopy(
            "/vsimem/tiff_write_126.tif", src_ds, options=options
        )
        ds = None
        del os.environ["JPEGMEM"]

        ds = gdal.Open("/vsimem/tiff_write_126.tif")
        # Officially we have 0 public overviews...
        assert ds.GetRasterBand(1).GetOverviewCount() == 0, options
        # But they do exist...
        cs = ds.GetRasterBand(1).GetOverview(0).Checksum()
        assert cs in cs1, options
        cs = ds.GetRasterBand(1).GetOverview(1).Checksum()
        assert cs in cs3, options
        cs = ds.GetRasterBand(1).GetOverview(2).Checksum()
        assert cs in cs4, options
        ovr_1_data = (
            ds.GetRasterBand(1).GetOverview(1).GetDataset().ReadRaster(0, 0, 400, 200)
        )
        subsampled_data = ds.ReadRaster(0, 0, 1600, 800, 400, 200)
        assert ovr_1_data == subsampled_data, options
        ds = None

        gdaltest.tiff_drv.Delete("/vsimem/tiff_write_126.tif")

    # Test single-strip, opened as split band
    src_ds = gdaltest.tiff_drv.Create("/vsimem/tiff_write_126_src.tif", 8, 2001)
    src_ds.GetRasterBand(1).Fill(255)
    ds = gdaltest.tiff_drv.CreateCopy(
        "/vsimem/tiff_write_126.tif",
        src_ds,
        options=["COMPRESS=JPEG", "BLOCKYSIZE=2001"],
    )
    src_ds = None
    gdaltest.tiff_drv.Delete("/vsimem/tiff_write_126_src.tif")
    ds = None

    ds = gdal.Open("/vsimem/tiff_write_126.tif")
    assert ds.GetRasterBand(1).GetBlockSize() == [8, 1]
    ovr_ds = ds.GetRasterBand(1).GetOverview(1).GetDataset()
    ovr_1_data = ovr_ds.ReadRaster(0, 0, ovr_ds.RasterXSize, ovr_ds.RasterYSize, 1, 1)
    subsampled_data = ds.ReadRaster(0, 0, ds.RasterXSize, ds.RasterYSize, 1, 1)
    assert ovr_1_data == subsampled_data
    ds = None

    gdaltest.tiff_drv.Delete("/vsimem/tiff_write_126.tif")

    # We need libtiff 4.0.4 (unreleased at that time)
    md = gdaltest.tiff_drv.GetMetadata()
    if md["LIBTIFF"] != "INTERNAL":
        print("skipping tests that will fail without internal libtiff")
        return

    # Test with completely sparse file
    ds = gdaltest.tiff_drv.Create(
        "/vsimem/tiff_write_126.tif",
        1024,
        1024,
        options=["COMPRESS=JPEG", "SPARSE_OK=YES"],
    )
    ds = None

    ds = gdal.Open("/vsimem/tiff_write_126.tif")
    assert ds.GetRasterBand(1).GetOverview(0) is not None
    assert ds.GetRasterBand(1).GetMetadataItem("JPEGTABLES", "TIFF") is not None
    assert ds.GetRasterBand(1).GetMetadataItem("BLOCK_OFFSET_0_0", "TIFF") is None
    assert ds.GetRasterBand(1).GetMetadataItem("BLOCK_SIZE_0_0", "TIFF") is None
    ds = None
    gdaltest.tiff_drv.Delete("/vsimem/tiff_write_126.tif")

    # Test with partially sparse file
    ds = gdaltest.tiff_drv.Create(
        "/vsimem/tiff_write_126.tif",
        1024,
        1024,
        3,
        options=["COMPRESS=JPEG", "SPARSE_OK=YES", "INTERLEAVE=BAND"],
    )
    # Fill band 3, but let blocks of band 1 unwritten.
    ds.GetRasterBand(3).Fill(0)
    ds = None

    ds = gdal.Open("/vsimem/tiff_write_126.tif")
    cs = ds.GetRasterBand(1).GetOverview(0).Checksum()
    assert cs == 0
    ds = None
    gdaltest.tiff_drv.Delete("/vsimem/tiff_write_126.tif")


###############################################################################
# Test setting/unsetting metadata in update mode (#5628)


def test_tiff_write_127():

    ds = gdaltest.tiff_drv.Create("/vsimem/tiff_write_127.tif", 1, 1)
    ds = None

    for i in range(2):

        ds = gdal.Open("/vsimem/tiff_write_127.tif", gdal.GA_Update)
        obj = ds if i == 0 else ds.GetRasterBand(1)
        obj.SetMetadata({"key": "value"})
        obj = None
        ds = None

        ds = gdal.Open("/vsimem/tiff_write_127.tif", gdal.GA_Update)
        obj = ds if i == 0 else ds.GetRasterBand(1)
        if obj.GetMetadataItem("key") != "value":
            print(i)
            pytest.fail(obj.GetMetadata())
        obj.SetMetadata({})
        obj = None
        ds = None

        ds = gdal.Open("/vsimem/tiff_write_127.tif", gdal.GA_Update)
        obj = ds if i == 0 else ds.GetRasterBand(1)
        assert not obj.GetMetadata(), i
        obj.SetMetadataItem("key", "value")
        obj = None
        ds = None

        ds = gdal.Open("/vsimem/tiff_write_127.tif", gdal.GA_Update)
        obj = ds if i == 0 else ds.GetRasterBand(1)
        assert obj.GetMetadataItem("key") == "value", i
        obj.SetMetadataItem("key", None)
        obj = None
        ds = None

        ds = gdal.Open("/vsimem/tiff_write_127.tif", gdal.GA_Update)
        obj = ds if i == 0 else ds.GetRasterBand(1)
        assert not obj.GetMetadata(), i
        obj = None
        ds = None

        statBuf = gdal.VSIStatL("/vsimem/tiff_write_127.tif.aux.xml")
        if statBuf is not None:
            print(i)
            pytest.fail("unexpected PAM file")

    gdaltest.tiff_drv.Delete("/vsimem/tiff_write_127.tif")


###############################################################################
# Test lossless copying of a CMYK JPEG into JPEG-in-TIFF (#5712)


@pytest.mark.require_creation_option("GTiff", "JPEG")
@pytest.mark.require_driver("JPEG")
def test_tiff_write_128():

    with gdal.config_option("GDAL_JPEG_TO_RGB", "NO"):
        src_ds = gdal.Open("../gdrivers/data/jpeg/rgb_ntf_cmyk.jpg")

    # Will received implicitly CMYK photometric interpretation.
    with gdal.config_option("GDAL_PAM_ENABLED", "NO"):
        ds = gdaltest.tiff_drv.CreateCopy(
            "/vsimem/tiff_write_128.tif", src_ds, options=["COMPRESS=JPEG"]
        )
        ds = None

    # We need to reopen in raw to avoig automatic CMYK->RGBA to trigger
    ds = gdal.Open("GTIFF_RAW:/vsimem/tiff_write_128.tif")
    for i in range(4):
        assert (
            src_ds.GetRasterBand(i + 1).GetColorInterpretation()
            == ds.GetRasterBand(i + 1).GetColorInterpretation()
        )
        assert (
            src_ds.GetRasterBand(i + 1).Checksum() == ds.GetRasterBand(i + 1).Checksum()
        )
    ds = None

    gdaltest.tiff_drv.Delete("/vsimem/tiff_write_128.tif")

    # Try with explicit CMYK photometric interpretation
    with gdal.config_option("GDAL_PAM_ENABLED", "NO"):
        ds = gdaltest.tiff_drv.CreateCopy(
            "/vsimem/tiff_write_128.tif",
            src_ds,
            options=["COMPRESS=JPEG", "PHOTOMETRIC=CMYK"],
        )
        ds = None

    # We need to reopen in raw to avoig automatic CMYK->RGBA to trigger
    ds = gdal.Open("GTIFF_RAW:/vsimem/tiff_write_128.tif")
    for i in range(4):
        assert (
            src_ds.GetRasterBand(i + 1).GetColorInterpretation()
            == ds.GetRasterBand(i + 1).GetColorInterpretation()
        )
        assert (
            src_ds.GetRasterBand(i + 1).Checksum() == ds.GetRasterBand(i + 1).Checksum()
        )
    ds = None

    gdaltest.tiff_drv.Delete("/vsimem/tiff_write_128.tif")

    # Try with more neutral colorspace in the case the source JPEG is not really CMYK (yes that happens !)
    with gdal.config_option("GDAL_PAM_ENABLED", "NO"):
        ds = gdaltest.tiff_drv.CreateCopy(
            "/vsimem/tiff_write_128.tif",
            src_ds,
            options=["COMPRESS=JPEG", "PHOTOMETRIC=MINISBLACK", "PROFILE=BASELINE"],
        )
        ds = None

    # Here we can reopen without GTIFF_RAW trick
    ds = gdal.Open("/vsimem/tiff_write_128.tif")
    for i in range(4):
        # The color interpretation will NOT be CMYK
        assert (
            src_ds.GetRasterBand(i + 1).GetColorInterpretation()
            != ds.GetRasterBand(i + 1).GetColorInterpretation()
        )
        assert (
            src_ds.GetRasterBand(i + 1).Checksum() == ds.GetRasterBand(i + 1).Checksum()
        )
    ds = None

    gdaltest.tiff_drv.Delete("/vsimem/tiff_write_128.tif")


###############################################################################
# Check effective guessing of existing JPEG quality


@pytest.mark.require_creation_option("GTiff", "JPEG")
def test_tiff_write_129():

    for jpegtablesmode in ["1", "3"]:
        for photometric in ["RGB", "YCBCR"]:
            cs_ref = 0
            for i in range(2):
                ds = gdaltest.tiff_drv.Create(
                    "/vsimem/tiff_write_129.tif",
                    64,
                    32,
                    3,
                    options=[
                        "COMPRESS=JPEG",
                        "TILED=YES",
                        "BLOCKXSIZE=32",
                        "BLOCKYSIZE=32",
                        "JPEG_QUALITY=50",
                        "PHOTOMETRIC=" + photometric,
                        "JPEGTABLESMODE=" + jpegtablesmode,
                    ],
                )
                src_ds = gdal.Open("data/rgbsmall.tif")
                data = src_ds.ReadRaster(0, 0, 32, 32)
                ds.WriteRaster(0, 0, 32, 32, data)

                # In second pass, we re-open the dataset
                if i == 1:
                    ds = None
                    ds = gdal.Open("/vsimem/tiff_write_129.tif", gdal.GA_Update)
                ds.WriteRaster(32, 0, 32, 32, data)
                ds = None

                ds = gdal.Open("/vsimem/tiff_write_129.tif")
                with gdaltest.SetCacheMax(0):
                    cs = ds.GetRasterBand(1).Checksum()
                ds = None
                gdaltest.tiff_drv.Delete("/vsimem/tiff_write_129.tif")

                if i == 0:
                    cs_ref = cs
                elif cs != cs_ref:
                    print(photometric)
                    print(i)
                    pytest.fail(jpegtablesmode)


###############################################################################
# Test cases where JPEG quality will fail


@pytest.mark.require_creation_option("GTiff", "JPEG")
def test_tiff_write_130():

    shutil.copyfile(
        "data/byte_jpg_unusual_jpegtable.tif", "tmp/byte_jpg_unusual_jpegtable.tif"
    )
    ds = gdal.Open("tmp/byte_jpg_unusual_jpegtable.tif", gdal.GA_Update)
    assert ds.GetRasterBand(1).Checksum() == 4771
    src_ds = gdal.Open("data/byte.tif", gdal.GA_Update)
    ds.WriteRaster(0, 0, 20, 20, src_ds.ReadRaster())
    src_ds = None
    ds = None
    ds = gdal.Open("tmp/byte_jpg_unusual_jpegtable.tif")
    assert ds.GetRasterBand(1).Checksum() == 4743
    ds = None
    os.unlink("tmp/byte_jpg_unusual_jpegtable.tif")

    shutil.copyfile(
        "data/byte_jpg_tablesmodezero.tif", "tmp/byte_jpg_tablesmodezero.tif"
    )
    ds = gdal.Open("tmp/byte_jpg_tablesmodezero.tif", gdal.GA_Update)
    assert ds.GetRasterBand(1).Checksum() == 4743
    src_ds = gdal.Open("data/byte.tif", gdal.GA_Update)
    ds.WriteRaster(0, 0, 20, 20, src_ds.ReadRaster())
    src_ds = None
    ds = None
    ds = gdal.Open("tmp/byte_jpg_tablesmodezero.tif")
    assert ds.GetRasterBand(1).Checksum() == 4743
    ds = None
    os.unlink("tmp/byte_jpg_tablesmodezero.tif")


###############################################################################
# Test LZMA compression


@pytest.mark.require_creation_option("GTiff", "LZMA")
def test_tiff_write_131(level=1):

    filename = "/vsimem/tiff_write_131.tif"
    src_ds = gdal.Open("data/byte.tif")
    ds = gdaltest.tiff_drv.CreateCopy(
        filename, src_ds, options=["COMPRESS=LZMA", "LZMA_PRESET=" + str(level)]
    )
    assert ds.GetRasterBand(1).Checksum() == 4672
    ds = None

    # LZMA requires an howful amount of memory even on small files
    if gdal.GetLastErrorMsg().find("cannot allocate memory") >= 0:
        gdal.Unlink(filename)
        pytest.skip()

    ds = gdal.Open(filename)
    assert ds.GetRasterBand(1).Checksum() == 4672
    ds = None

    gdal.Unlink(filename)


@pytest.mark.require_creation_option("GTiff", "LZMA")
def test_tiff_write_131_level_9():
    return test_tiff_write_131(level=9)


###############################################################################
# Test that PAM metadata is cleared when internal metadata is set (#5807)


def test_tiff_write_132():

    for i in range(2):

        ds = gdaltest.tiff_drv.Create("/vsimem/tiff_write_132.tif", 1, 1)
        ds = None

        # Open in read-only
        ds = gdal.Open("/vsimem/tiff_write_132.tif")
        ds.SetMetadataItem("FOO", "BAR")
        ds.GetRasterBand(1).SetMetadataItem("FOO", "BAR")
        ds = None

        # Check that PAM file exists
        assert gdal.VSIStatL("/vsimem/tiff_write_132.tif.aux.xml") is not None

        # Open in read-write
        ds = gdal.Open("/vsimem/tiff_write_132.tif", gdal.GA_Update)
        if i == 0:
            ds.SetMetadataItem("FOO", "BAZ")
            ds.GetRasterBand(1).SetMetadataItem("FOO", "BAZ")
        else:
            ds.SetMetadata({"FOO": "BAZ"})
            ds.GetRasterBand(1).SetMetadata({"FOO": "BAZ"})
        ds = None

        # Check that PAM file no longer exists
        assert gdal.VSIStatL("/vsimem/tiff_write_132.tif.aux.xml") is None, i

        ds = gdal.Open("/vsimem/tiff_write_132.tif")
        assert (
            ds.GetMetadataItem("FOO") == "BAZ"
            and ds.GetRasterBand(1).GetMetadataItem("FOO") == "BAZ"
        )
        ds = None

        gdaltest.tiff_drv.Delete("/vsimem/tiff_write_132.tif")


###############################################################################
# Test streaming capabilities


def test_tiff_write_133():

    src_ds = gdaltest.tiff_drv.Create(
        "/vsimem/tiff_write_133.tif", 1024, 1000, 3, options=["STREAMABLE_OUTPUT=YES"]
    )
    src_ds.SetGeoTransform([1, 2, 0, 3, 0, -2])
    srs = osr.SpatialReference()
    srs.SetFromUserInput("EPSG:32601")
    src_ds.SetProjection(srs.ExportToWkt())
    src_ds.SetMetadataItem("FOO", "BAR")
    src_ds.GetRasterBand(1).SetNoDataValue(127)
    src_ds.GetRasterBand(1).Fill(64)
    src_ds.GetRasterBand(2).Fill(127)
    src_ds.GetRasterBand(3).Fill(184)

    src_ds.FlushCache()
    with gdal.quiet_errors():
        ret = src_ds.SetProjection(srs.ExportToWkt())
    assert ret != 0
    with gdal.quiet_errors():
        ret = src_ds.SetGeoTransform([1, 2, 0, 3, 0, -4])
    assert ret != 0
    with gdal.quiet_errors():
        ret = src_ds.SetMetadataItem("FOO", "BAZ")
    assert ret != 0
    with gdal.quiet_errors():
        ret = src_ds.SetMetadata({})
    assert ret != 0
    with gdal.quiet_errors():
        ret = src_ds.GetRasterBand(1).SetMetadataItem("FOO", "BAZ")
    assert ret != 0
    with gdal.quiet_errors():
        ret = src_ds.GetRasterBand(1).SetMetadata({})
    assert ret != 0
    with gdal.quiet_errors():
        ret = src_ds.GetRasterBand(1).SetNoDataValue(0)
    assert ret != 0

    # Pixel interleaved
    out_ds = gdaltest.tiff_drv.CreateCopy(
        "/vsimem/tiff_write_133_dst.tif",
        src_ds,
        options=["STREAMABLE_OUTPUT=YES", "BLOCKYSIZE=32"],
    )
    out_ds = None

    with gdal.config_option("TIFF_READ_STREAMING", "YES"):
        ds = gdal.Open("/vsimem/tiff_write_133_dst.tif")
    assert ds.GetProjectionRef().find("32601") >= 0
    assert ds.GetGeoTransform() == (1.0, 2.0, 0.0, 3.0, 0.0, -2.0)
    assert ds.GetMetadataItem("FOO") == "BAR"
    assert ds.GetMetadataItem("UNORDERED_BLOCKS", "TIFF") is None

    with gdaltest.SetCacheMax(0):
        for y in range(1000):
            got_data = ds.ReadRaster(0, y, 1024, 1)
            assert got_data is not None

    ds.FlushCache()
    for y in range(1000):
        with gdal.quiet_errors():
            got_data = ds.ReadRaster(0, y, 1024, 1)
        assert got_data is None
    ds = None
    gdaltest.tiff_drv.Delete("/vsimem/tiff_write_133_dst.tif")

    # Tiled
    out_ds = gdaltest.tiff_drv.CreateCopy(
        "/vsimem/tiff_write_133_dst.tif",
        src_ds,
        options=["STREAMABLE_OUTPUT=YES", "TILED=YES"],
    )
    out_ds = None

    with gdal.config_option("TIFF_READ_STREAMING", "YES"):
        ds = gdal.Open("/vsimem/tiff_write_133_dst.tif")
    assert ds.GetProjectionRef().find("32601") >= 0
    assert ds.GetGeoTransform() == (1.0, 2.0, 0.0, 3.0, 0.0, -2.0)
    assert ds.GetMetadataItem("FOO") == "BAR"
    assert ds.GetMetadataItem("UNORDERED_BLOCKS", "TIFF") is None

    with gdaltest.SetCacheMax(0):
        for yblock in range(int((1000 + 256 - 1) / 256)):
            y = 256 * yblock
            ysize = 256
            if y + ysize > ds.RasterYSize:
                ysize = ds.RasterYSize - y
            for xblock in range(int((1024 + 256 - 1) / 256)):
                x = 256 * xblock
                xsize = 256
                if x + xsize > ds.RasterXSize:
                    xsize = ds.RasterXSize - x
                got_data = ds.ReadRaster(x, y, xsize, ysize)
                assert got_data is not None

    ds = None
    gdaltest.tiff_drv.Delete("/vsimem/tiff_write_133_dst.tif")

    # Band interleaved
    out_ds = gdaltest.tiff_drv.CreateCopy(
        "/vsimem/tiff_write_133_dst.tif",
        src_ds,
        options=["STREAMABLE_OUTPUT=YES", "INTERLEAVE=BAND"],
    )
    out_ds = None

    with gdal.config_option("TIFF_READ_STREAMING", "YES"):
        ds = gdal.Open("/vsimem/tiff_write_133_dst.tif")
    assert ds.GetMetadataItem("UNORDERED_BLOCKS", "TIFF") is None

    with gdaltest.SetCacheMax(0):
        for band in range(3):
            for y in range(1000):
                got_data = ds.GetRasterBand(band + 1).ReadRaster(0, y, 1024, 1)
                assert got_data is not None
    ds = None
    gdaltest.tiff_drv.Delete("/vsimem/tiff_write_133_dst.tif")

    # BIGTIFF
    md = gdaltest.tiff_drv.GetMetadata()
    if md["DMD_CREATIONOPTIONLIST"].find("BigTIFF") >= 0:
        out_ds = gdaltest.tiff_drv.CreateCopy(
            "/vsimem/tiff_write_133_dst.tif",
            src_ds,
            options=["STREAMABLE_OUTPUT=YES", "BIGTIFF=YES"],
        )
        out_ds = None

        with gdal.config_option("TIFF_READ_STREAMING", "YES"):
            ds = gdal.Open("/vsimem/tiff_write_133_dst.tif")
        assert ds.GetMetadataItem("UNORDERED_BLOCKS", "TIFF") is None

        with gdaltest.SetCacheMax(0):
            for y in range(1000):
                got_data = ds.ReadRaster(0, y, 1024, 1)
                assert got_data is not None

        ds = None
        gdaltest.tiff_drv.Delete("/vsimem/tiff_write_133_dst.tif")

    # Compression not supported
    with gdal.quiet_errors():
        out_ds = gdaltest.tiff_drv.CreateCopy(
            "/vsimem/tiff_write_133_dst.tif",
            src_ds,
            options=["STREAMABLE_OUTPUT=YES", "COMPRESS=DEFLATE"],
        )
    assert out_ds is None

    # Test writing into a non authorized file
    ds = gdaltest.tiff_drv.Create(
        "/foo/bar", 1024, 1000, 3, options=["STREAMABLE_OUTPUT=YES", "BLOCKYSIZE=1"]
    )
    assert ds is None

    with gdal.quiet_errors():
        out_ds = gdaltest.tiff_drv.CreateCopy(
            "/foo/bar", src_ds, options=["STREAMABLE_OUTPUT=YES"]
        )
    assert out_ds is None

    src_ds = None

    # Classical TIFF with IFD not at offset 8
    with gdal.config_option("TIFF_READ_STREAMING", "YES"), gdaltest.error_handler():
        ds = gdal.Open("data/byte.tif")
    assert ds is None

    # BigTIFF with IFD not at offset 16
    if md["DMD_CREATIONOPTIONLIST"].find("BigTIFF") >= 0:
        ds = gdaltest.tiff_drv.Create(
            "/vsimem/tiff_write_133.tif", 1024, 1000, 3, options=["BIGTIFF=YES"]
        )
        ds.GetRasterBand(1).Fill(0)
        ds.FlushCache()
        ds.SetGeoTransform([1, 2, 0, 3, 0, -2])
        ds = None

        with gdal.config_option("TIFF_READ_STREAMING", "YES"), gdaltest.error_handler():
            ds = gdal.Open("/vsimem/tiff_write_133.tif")
        assert ds is None

    # Test reading strips in not increasing order
    ds = gdaltest.tiff_drv.Create(
        "/vsimem/tiff_write_133.tif", 1024, 1000, 3, options=["BLOCKYSIZE=1"]
    )
    for y in range(1000):
        ds.WriteRaster(0, 1000 - y - 1, 1024, 1, "a" * (3 * 1024))
        ds.FlushCache()
    ds = None

    with gdal.config_option("TIFF_READ_STREAMING", "YES"), gdaltest.error_handler():
        ds = gdal.Open("/vsimem/tiff_write_133.tif")
    assert ds.GetMetadataItem("UNORDERED_BLOCKS", "TIFF") == "YES"

    with gdaltest.SetCacheMax(0):
        for y in range(1000):
            got_data = ds.ReadRaster(0, 1000 - y - 1, 1024, 1)
            assert got_data is not None

    # Test writing strips in not increasing order in a streamable output
    ds = gdaltest.tiff_drv.Create(
        "/vsimem/tiff_write_133.tif",
        1024,
        1000,
        3,
        options=["STREAMABLE_OUTPUT=YES", "BLOCKYSIZE=1"],
    )
    gdal.ErrorReset()
    with gdal.quiet_errors():
        ret = ds.WriteRaster(0, 999, 1024, 1, "a" * (3 * 1024))
        ds.FlushCache()
    assert gdal.GetLastErrorMsg() != ""
    ds = None

    # Test writing tiles in not increasing order in a streamable output
    ds = gdaltest.tiff_drv.Create(
        "/vsimem/tiff_write_133.tif",
        1024,
        1000,
        3,
        options=["STREAMABLE_OUTPUT=YES", "TILED=YES"],
    )
    gdal.ErrorReset()
    with gdal.quiet_errors():
        ret = ds.WriteRaster(256, 256, 256, 256, "a" * (3 * 256 * 256))
        ds.FlushCache()
    assert gdal.GetLastErrorMsg() != ""
    ds = None

    gdaltest.tiff_drv.Delete("/vsimem/tiff_write_133.tif")


###############################################################################
# Test DISCARD_LSB


def test_tiff_write_134():

    for interleave in ["BAND", "PIXEL"]:
        ds = gdaltest.tiff_drv.Create(
            "/vsimem/tiff_write_134.tif",
            1,
            1,
            3,
            options=["DISCARD_LSB=0,1,3", "INTERLEAVE=" + interleave],
        )
        ds.GetRasterBand(1).Fill(127)
        ds.GetRasterBand(2).Fill(127)
        ds.GetRasterBand(3).Fill(127)
        ds = None
        ds = gdal.Open("/vsimem/tiff_write_134.tif")
        val1 = struct.unpack("B", ds.GetRasterBand(1).ReadRaster())[0]
        val2 = struct.unpack("B", ds.GetRasterBand(2).ReadRaster())[0]
        val3 = struct.unpack("B", ds.GetRasterBand(3).ReadRaster())[0]
        assert val1 == 127 and val2 == 126 and val3 == 128
        ds = None
        gdaltest.tiff_drv.Delete("/vsimem/tiff_write_134.tif")

    src_ds = gdaltest.tiff_drv.Create("/vsimem/tiff_write_134_src.tif", 1, 1, 3)
    src_ds.GetRasterBand(1).Fill(127)
    src_ds.GetRasterBand(2).Fill(127)
    src_ds.GetRasterBand(3).Fill(255)
    ds = gdaltest.tiff_drv.CreateCopy(
        "/vsimem/tiff_write_134.tif", src_ds, options=["DISCARD_LSB=0,1,3"]
    )
    ds = None
    ds = gdal.Open("/vsimem/tiff_write_134.tif")
    val1 = struct.unpack("B", ds.GetRasterBand(1).ReadRaster())[0]
    val2 = struct.unpack("B", ds.GetRasterBand(2).ReadRaster())[0]
    val3 = struct.unpack("B", ds.GetRasterBand(3).ReadRaster())[0]
    assert val1 == 127 and val2 == 126 and val3 == 255
    ds = None
    gdaltest.tiff_drv.Delete("/vsimem/tiff_write_134_src.tif")
    gdaltest.tiff_drv.Delete("/vsimem/tiff_write_134.tif")

    for (inval, expected_val) in [
        (0, 0),
        (1, 0),
        (2, 0),
        (3, 0),
        (4, 8),
        (254, 255),
        (255, 255),
    ]:
        for interleave in ["BAND", "PIXEL"]:
            ds = gdaltest.tiff_drv.Create(
                "/vsimem/tiff_write_134.tif",
                1,
                1,
                2,
                gdal.GDT_Byte,
                options=["DISCARD_LSB=3", "INTERLEAVE=" + interleave],
            )
            ds.GetRasterBand(1).Fill(inval)
            ds = None
            ds = gdal.Open("/vsimem/tiff_write_134.tif")
            val1 = struct.unpack("B", ds.GetRasterBand(1).ReadRaster())[0]
            assert val1 == expected_val, (inval, expected_val)
            ds = None
            gdaltest.tiff_drv.Delete("/vsimem/tiff_write_134.tif")

    for (inval, expected_val) in [
        (-32768, -32768),
        (-32767, -32768),
        (-32764, -32768),
        (-8, -8),
        (-1, -8),  # this truncation is questionable
        (0, 0),
        (1, 0),
        (3, 0),
        (4, 8),
        (8, 8),
        (32766, 32760),
        (32767, 32760),
    ]:
        for interleave in ["BAND", "PIXEL"]:
            ds = gdaltest.tiff_drv.Create(
                "/vsimem/tiff_write_134.tif",
                1,
                1,
                2,
                gdal.GDT_Int16,
                options=["DISCARD_LSB=3", "INTERLEAVE=" + interleave],
            )
            ds.GetRasterBand(1).Fill(inval)
            ds = None
            ds = gdal.Open("/vsimem/tiff_write_134.tif")
            val1 = struct.unpack("h", ds.GetRasterBand(1).ReadRaster())[0]
            assert val1 == expected_val, (inval, expected_val)
            ds = None
            gdaltest.tiff_drv.Delete("/vsimem/tiff_write_134.tif")

    for (inval, expected_val) in [
        (0, 0),
        (1, 0),
        (3, 0),
        (4, 8),
        (8, 8),
        (65534, 65528),
        (65535, 65528),
    ]:
        for interleave in ["BAND", "PIXEL"]:
            ds = gdaltest.tiff_drv.Create(
                "/vsimem/tiff_write_134.tif",
                1,
                1,
                2,
                gdal.GDT_UInt16,
                options=["DISCARD_LSB=3", "INTERLEAVE=" + interleave],
            )
            ds.GetRasterBand(1).Fill(inval)
            ds = None
            ds = gdal.Open("/vsimem/tiff_write_134.tif")
            val1 = struct.unpack("H", ds.GetRasterBand(1).ReadRaster())[0]
            assert val1 == expected_val, (inval, expected_val)
            ds = None
            gdaltest.tiff_drv.Delete("/vsimem/tiff_write_134.tif")

    for interleave in ["BAND", "PIXEL"]:
        for dt in [
            gdal.GDT_Byte,
            gdal.GDT_Int16,
            gdal.GDT_UInt16,
            gdal.GDT_Int32,
            gdal.GDT_UInt32,
            gdal.GDT_Float32,
            gdal.GDT_Float64,
        ]:
            ds = gdaltest.tiff_drv.Create(
                "/vsimem/tiff_write_134.tif",
                1,
                1,
                3,
                dt,
                options=["DISCARD_LSB=3", "INTERLEAVE=" + interleave],
            )
            if dt == gdal.GDT_Int16:
                ds.GetRasterBand(1).Fill(-127)
            else:
                ds.GetRasterBand(1).Fill(127)
            ds.GetRasterBand(2).Fill(123)
            ds.GetRasterBand(3).Fill(127)
            ds = None
            ds = gdal.Open("/vsimem/tiff_write_134.tif")
            val1 = struct.unpack(
                "h", ds.GetRasterBand(1).ReadRaster(0, 0, 1, 1, 1, 1, gdal.GDT_Int16)
            )[0]
            val2 = struct.unpack(
                "h", ds.GetRasterBand(2).ReadRaster(0, 0, 1, 1, 1, 1, gdal.GDT_Int16)
            )[0]
            val3 = struct.unpack(
                "h", ds.GetRasterBand(3).ReadRaster(0, 0, 1, 1, 1, 1, gdal.GDT_Int16)
            )[0]
            if dt in (gdal.GDT_Float32, gdal.GDT_Float64):
                assert val1 == 127 and val2 == 123 and val3 == 127, (
                    interleave,
                    dt,
                    (val1, val2, val3),
                )
            elif dt == gdal.GDT_Int16:
                assert val1 == -128 and val2 == 120 and val3 == 128, (
                    interleave,
                    dt,
                    (val1, val2, val3),
                )
            else:
                assert val1 == 128 and val2 == 120 and val3 == 128, (
                    interleave,
                    dt,
                    (val1, val2, val3),
                )
            ds = None
            gdaltest.tiff_drv.Delete("/vsimem/tiff_write_134.tif")

    # Test with nodata
    for interleave in ["BAND", "PIXEL"]:
        for dt in [
            gdal.GDT_Byte,
            gdal.GDT_Int16,
            gdal.GDT_UInt16,
            gdal.GDT_Int32,
            gdal.GDT_UInt32,
            gdal.GDT_Float32,
            gdal.GDT_Float64,
        ]:
            ds = gdaltest.tiff_drv.Create(
                "/vsimem/tiff_write_134.tif",
                1,
                1,
                2,
                dt,
                options=["DISCARD_LSB=3", "INTERLEAVE=" + interleave],
            )
            ds.GetRasterBand(1).SetNoDataValue(127)
            ds.GetRasterBand(1).Fill(127)
            ds = None
            ds = gdal.Open("/vsimem/tiff_write_134.tif")
            val1 = struct.unpack(
                "B", ds.GetRasterBand(1).ReadRaster(0, 0, 1, 1, 1, 1, gdal.GDT_Byte)
            )[0]
            assert val1 == 127, (interleave, dt, val1)
            ds = None
            gdaltest.tiff_drv.Delete("/vsimem/tiff_write_134.tif")

    # Test with nodata and discarding non-nodata value would result to nodata without correction
    for interleave in ["BAND", "PIXEL"]:
        for dt in [
            gdal.GDT_Byte,
            gdal.GDT_Int16,
            gdal.GDT_UInt16,
            gdal.GDT_Int32,
            gdal.GDT_UInt32,
            gdal.GDT_Float32,
            gdal.GDT_Float64,
        ]:
            ds = gdaltest.tiff_drv.Create(
                "/vsimem/tiff_write_134.tif",
                1,
                1,
                2,
                dt,
                options=["DISCARD_LSB=3", "INTERLEAVE=" + interleave],
            )
            ds.GetRasterBand(1).SetNoDataValue(0)
            ds.GetRasterBand(1).Fill(1)
            ds = None
            ds = gdal.Open("/vsimem/tiff_write_134.tif")
            val1 = struct.unpack(
                "B", ds.GetRasterBand(1).ReadRaster(0, 0, 1, 1, 1, 1, gdal.GDT_Byte)
            )[0]
            if dt in (gdal.GDT_Float32, gdal.GDT_Float64):
                assert val1 == 1, (interleave, dt, val1)
            else:
                assert val1 == 8, (interleave, dt, val1)
            ds = None
            gdaltest.tiff_drv.Delete("/vsimem/tiff_write_134.tif")

    # Test with nodata out of range for integer values
    for interleave in ["BAND", "PIXEL"]:
        for dt in [
            gdal.GDT_Byte,
            gdal.GDT_Int16,
            gdal.GDT_UInt16,
            gdal.GDT_Int32,
            gdal.GDT_UInt32,
            gdal.GDT_Float32,
            gdal.GDT_Float64,
        ]:
            ds = gdaltest.tiff_drv.Create(
                "/vsimem/tiff_write_134.tif",
                1,
                1,
                2,
                dt,
                options=["DISCARD_LSB=3", "INTERLEAVE=" + interleave],
            )
            ds.GetRasterBand(1).SetNoDataValue(127.5)
            ds.GetRasterBand(1).Fill(127)
            ds = None
            ds = gdal.Open("/vsimem/tiff_write_134.tif")
            assert ds.GetRasterBand(1).GetNoDataValue() == 127.5
            val1 = struct.unpack(
                "B", ds.GetRasterBand(1).ReadRaster(0, 0, 1, 1, 1, 1, gdal.GDT_Byte)
            )[0]
            if dt in (gdal.GDT_Float32, gdal.GDT_Float64):
                assert val1 == 127, (interleave, dt, val1)
            else:
                assert val1 == 128, (interleave, dt, val1)
            ds = None
            gdaltest.tiff_drv.Delete("/vsimem/tiff_write_134.tif")

    # Test with some non-integer float value
    for interleave in ["BAND", "PIXEL"]:
        for dt in [gdal.GDT_Float32, gdal.GDT_Float64]:
            ds = gdaltest.tiff_drv.Create(
                "/vsimem/tiff_write_134.tif",
                1,
                1,
                2,
                dt,
                options=["DISCARD_LSB=3", "INTERLEAVE=" + interleave],
            )
            ds.GetRasterBand(1).Fill(-0.3)
            ds = None
            ds = gdal.Open("/vsimem/tiff_write_134.tif")
            val1 = struct.unpack(
                "d", ds.GetRasterBand(1).ReadRaster(0, 0, 1, 1, 1, 1, gdal.GDT_Float64)
            )[0]
            assert val1 != -0.3 and abs(val1 - -0.3) < 1e-5, dt
            ds = None
            gdaltest.tiff_drv.Delete("/vsimem/tiff_write_134.tif")

    # Test with nan
    for interleave in ["BAND", "PIXEL"]:
        for dt in [gdal.GDT_Float32, gdal.GDT_Float64]:
            ds = gdaltest.tiff_drv.Create(
                "/vsimem/tiff_write_134.tif",
                1,
                1,
                2,
                dt,
                options=["DISCARD_LSB=3", "INTERLEAVE=" + interleave],
            )
            ds.GetRasterBand(1).Fill(float("nan"))
            ds = None
            ds = gdal.Open("/vsimem/tiff_write_134.tif")
            val1 = struct.unpack(
                "f", ds.GetRasterBand(1).ReadRaster(0, 0, 1, 1, 1, 1, gdal.GDT_Float32)
            )[0]
            assert math.isnan(val1)
            ds = None
            gdaltest.tiff_drv.Delete("/vsimem/tiff_write_134.tif")

    # Error cases
    gdal.ErrorReset()
    with gdal.quiet_errors():
        gdaltest.tiff_drv.Create(
            "/vsimem/tiff_write_134.tif",
            1,
            1,
            options=["DISCARD_LSB=1", "PHOTOMETRIC=PALETTE"],
        )
    assert gdal.GetLastErrorMsg() != ""

    gdal.ErrorReset()
    with gdal.quiet_errors():
        # Too many elements
        gdaltest.tiff_drv.Create(
            "/vsimem/tiff_write_134.tif", 1, 1, options=["DISCARD_LSB=1,2"]
        )
    assert gdal.GetLastErrorMsg() != ""

    gdal.ErrorReset()
    with gdal.quiet_errors():
        # Too many elements
        gdaltest.tiff_drv.Create(
            "/vsimem/tiff_write_134.tif", 1, 1, options=["DISCARD_LSB=1", "NBITS=7"]
        )
    assert gdal.GetLastErrorMsg() != ""


###############################################################################
# Test clearing GCPs (#5945)


def test_tiff_write_135():

    # Simple clear
    src_ds = gdal.Open("data/gcps.vrt")
    ds = gdaltest.tiff_drv.CreateCopy("/vsimem/tiff_write_135.tif", src_ds)
    ds = None

    ds = gdal.Open("/vsimem/tiff_write_135.tif", gdal.GA_Update)
    ds.SetGCPs([], "")
    ds = None

    ds = gdal.Open("/vsimem/tiff_write_135.tif")
    assert not ds.GetGCPs()
    assert ds.GetGCPProjection() == ""
    ds = None

    # Double clear
    src_ds = gdal.Open("data/gcps.vrt")
    ds = gdaltest.tiff_drv.CreateCopy("/vsimem/tiff_write_135.tif", src_ds)
    ds = None

    ds = gdal.Open("/vsimem/tiff_write_135.tif", gdal.GA_Update)
    ds.SetGCPs([], "")
    ds.SetGCPs([], "")
    ds = None

    ds = gdal.Open("/vsimem/tiff_write_135.tif")
    assert not ds.GetGCPs()
    assert ds.GetGCPProjection() == ""
    ds = None

    # Clear + set geotransform and new projection
    src_ds = gdal.Open("data/gcps.vrt")
    ds = gdaltest.tiff_drv.CreateCopy("/vsimem/tiff_write_135.tif", src_ds)
    ds = None

    ds = gdal.Open("/vsimem/tiff_write_135.tif", gdal.GA_Update)
    ds.SetGCPs([], "")
    ds.SetGeoTransform([1, 2, 3, 4, 5, -6])
    srs = osr.SpatialReference()
    srs.SetFromUserInput("EPSG:32601")
    ds.SetProjection(srs.ExportToWkt())
    ds = None

    ds = gdal.Open("/vsimem/tiff_write_135.tif")
    assert not ds.GetGCPs()
    assert ds.GetGeoTransform() == (1, 2, 3, 4, 5, -6)
    assert ds.GetProjectionRef().find("32601") >= 0
    ds = None

    gdal.Unlink("/vsimem/tiff_write_135.tif")


###############################################################################
# Test writing a single-strip mono-bit dataset


def test_tiff_write_136():

    src_ds = gdaltest.tiff_drv.Create("/vsimem/tiff_write_136_src.tif", 8, 2001)
    src_ds.GetRasterBand(1).Fill(1)
    expected_cs = src_ds.GetRasterBand(1).Checksum()
    ds = gdaltest.tiff_drv.CreateCopy(
        "/vsimem/tiff_write_136.tif",
        src_ds,
        options=["NBITS=1", "COMPRESS=DEFLATE", "BLOCKYSIZE=2001"],
    )
    src_ds = None
    ds = None
    ds = gdal.Open("/vsimem/tiff_write_136.tif")
    cs = ds.GetRasterBand(1).Checksum()
    assert cs == expected_cs

    gdal.Unlink("/vsimem/tiff_write_136_src.tif")
    gdal.Unlink("/vsimem/tiff_write_136.tif")
    gdal.Unlink("/vsimem/tiff_write_136.tif.aux.xml")


###############################################################################
# Test multi-threaded writing


def test_tiff_write_137():

    src_ds = gdaltest.tiff_drv.Create("/vsimem/tiff_write_137_src.tif", 4000, 4000)
    src_ds.GetRasterBand(1).Fill(1)
    data = src_ds.GetRasterBand(1).ReadRaster()
    expected_cs = src_ds.GetRasterBand(1).Checksum()

    # Test NUM_THREADS as creation option
    ds = gdaltest.tiff_drv.CreateCopy(
        "/vsimem/tiff_write_137.tif",
        src_ds,
        options=["BLOCKYSIZE=16", "COMPRESS=DEFLATE", "NUM_THREADS=ALL_CPUS"],
    )
    src_ds = None
    ds = None
    ds = gdal.Open("/vsimem/tiff_write_137.tif")
    cs = ds.GetRasterBand(1).Checksum()
    ds = None
    assert cs == expected_cs

    # Test NUM_THREADS as creation option with Create()
    ds = gdaltest.tiff_drv.Create(
        "/vsimem/tiff_write_137.tif",
        4000,
        4000,
        1,
        options=["BLOCKYSIZE=16", "COMPRESS=DEFLATE", "NUM_THREADS=ALL_CPUS"],
    )
    ds.GetRasterBand(1).WriteRaster(0, 0, 4000, 4000, data)
    ds = None
    ds = gdal.Open("/vsimem/tiff_write_137.tif")
    cs = ds.GetRasterBand(1).Checksum()
    ds = None
    assert cs == expected_cs

    # Test NUM_THREADS as open option
    ds = gdaltest.tiff_drv.Create(
        "/vsimem/tiff_write_137.tif",
        4000,
        4000,
        options=["TILED=YES", "COMPRESS=DEFLATE", "PREDICTOR=2", "SPARSE_OK=YES"],
    )
    ds = None
    ds = gdal.OpenEx(
        "/vsimem/tiff_write_137.tif", gdal.OF_UPDATE, open_options=["NUM_THREADS=4"]
    )
    ds.GetRasterBand(1).Fill(1)
    ds = None
    ds = gdal.Open("/vsimem/tiff_write_137.tif")
    cs = ds.GetRasterBand(1).Checksum()
    ds = None
    assert cs == expected_cs

    # Ask data immediately while the block is compressed
    ds = gdaltest.tiff_drv.Create(
        "/vsimem/tiff_write_137.tif",
        4000,
        4000,
        options=["BLOCKYSIZE=3999", "COMPRESS=DEFLATE", "NUM_THREADS=4"],
    )
    ds.WriteRaster(0, 0, 1, 1, "A")
    ds.FlushCache()
    val = ds.ReadRaster(0, 0, 1, 1).decode("ascii")
    assert val == "A"
    ds = None

    gdal.Unlink("/vsimem/tiff_write_137_src.tif")
    gdal.Unlink("/vsimem/tiff_write_137.tif")

    # Test NUM_THREADS with raster == tile
    src_ds = gdal.Open("data/byte.tif")
    ds = gdaltest.tiff_drv.CreateCopy(
        "/vsimem/tiff_write_137.tif",
        src_ds,
        options=["BLOCKYSIZE=20", "COMPRESS=DEFLATE", "NUM_THREADS=ALL_CPUS"],
    )
    src_ds = None
    ds = None
    ds = gdal.Open("/vsimem/tiff_write_137.tif")
    cs = ds.GetRasterBand(1).Checksum()
    ds = None
    assert cs == 4672, expected_cs
    gdal.Unlink("/vsimem/tiff_write_137.tif")


###############################################################################
# Test that pixel-interleaved writing generates optimal size


def test_tiff_write_138():

    # Test that consecutive IWriteBlock() calls for the same block but in
    # different bands only generate a single tile write, and not 3 rewrites
    ds = gdal.GetDriverByName("GTiff").Create(
        "/vsimem/tiff_write_138.tif", 10, 1, 3, options=["COMPRESS=DEFLATE"]
    )
    ds.GetRasterBand(1).WriteRaster(0, 0, 10, 1, "A", buf_xsize=1, buf_ysize=1)
    ds.GetRasterBand(1).FlushCache()
    ds.GetRasterBand(2).WriteRaster(0, 0, 10, 1, "A", buf_xsize=1, buf_ysize=1)
    ds.GetRasterBand(2).FlushCache()
    ds.GetRasterBand(3).WriteRaster(0, 0, 10, 1, "A", buf_xsize=1, buf_ysize=1)
    ds.GetRasterBand(3).FlushCache()
    ds = None
    size = gdal.VSIStatL("/vsimem/tiff_write_138.tif").size
    assert size == 181

    # Test fix for #5999

    # Create a file with a huge block that will saturate the block cache.
    with gdaltest.SetCacheMax(1000000):
        tmp_ds = gdal.GetDriverByName("GTiff").Create(
            "/vsimem/tiff_write_138_saturate.tif", gdal.GetCacheMax(), 1
        )
        tmp_ds = None

        ds = gdal.GetDriverByName("GTiff").Create(
            "/vsimem/tiff_write_138.tif", 10, 1, 3, options=["COMPRESS=DEFLATE"]
        )
        ds.GetRasterBand(1).WriteRaster(0, 0, 10, 1, "A", buf_xsize=1, buf_ysize=1)
        ds.GetRasterBand(2).WriteRaster(0, 0, 10, 1, "A", buf_xsize=1, buf_ysize=1)
        ds.GetRasterBand(3).WriteRaster(0, 0, 10, 1, "A", buf_xsize=1, buf_ysize=1)
        # When internalizing the huge block, check that the 3 above dirty blocks
        # get written as a single tile write.
        tmp_ds = gdal.Open("/vsimem/tiff_write_138_saturate.tif")
        tmp_ds.GetRasterBand(1).Checksum()
        tmp_ds = None
        ds = None
        size = gdal.VSIStatL("/vsimem/tiff_write_138.tif").size
        assert size == 181

    gdal.Unlink("/vsimem/tiff_write_138.tif")
    gdal.Unlink("/vsimem/tiff_write_138_saturate.tif")


###############################################################################
# Test that pixel-interleaved writing generates optimal size


def test_tiff_write_139():

    drv = gdal.GetDriverByName("GTiff")
    # Only post 4.0.5 has the fix for non-byte swabing case
    has_inverted_swab_fix = drv.GetMetadataItem("LIBTIFF") == "INTERNAL"

    # In the byte case, there are optimizations for the 3 and 4 case. 1 is the general case
    for nbands in (1, 3, 4):

        ds = drv.Create(
            "/vsimem/tiff_write_139.tif",
            4,
            1,
            nbands,
            options=["PREDICTOR=2", "COMPRESS=DEFLATE"],
        )
        ref_content = struct.pack("B" * 4, 255, 0, 255, 0)
        for i in range(nbands):
            ds.GetRasterBand(i + 1).WriteRaster(0, 0, 4, 1, ref_content)
        ds = None
        ds = gdal.Open("/vsimem/tiff_write_139.tif")
        for i in range(nbands):
            content = ds.GetRasterBand(i + 1).ReadRaster()
            assert ref_content == content
        ds = None

        gdal.Unlink("/vsimem/tiff_write_139.tif")

    # Int16
    for endianness in ["NATIVE", "INVERTED"]:

        if endianness == "INVERTED" and not has_inverted_swab_fix:
            continue

        ds = drv.Create(
            "/vsimem/tiff_write_139.tif",
            6,
            1,
            1,
            gdal.GDT_Int16,
            options=["PREDICTOR=2", "COMPRESS=DEFLATE", "ENDIANNESS=%s" % endianness],
        )
        ref_content = struct.pack("h" * 6, -32768, 32767, -32768, 32767, -32768, 32767)
        ds.GetRasterBand(1).WriteRaster(0, 0, 6, 1, ref_content)
        ds = None
        ds = gdal.Open("/vsimem/tiff_write_139.tif")
        content = ds.GetRasterBand(1).ReadRaster()
        if ref_content != content:
            print(endianness)
            pytest.fail(struct.unpack("h" * 6, content))
        ds = None

        gdal.Unlink("/vsimem/tiff_write_139.tif")

    # UInt16 (same code path)
    for endianness in ["NATIVE", "INVERTED"]:

        if endianness == "INVERTED" and not has_inverted_swab_fix:
            continue

        ds = drv.Create(
            "/vsimem/tiff_write_139.tif",
            6,
            1,
            1,
            gdal.GDT_UInt16,
            options=["PREDICTOR=2", "COMPRESS=DEFLATE", "ENDIANNESS=%s" % endianness],
        )
        ref_content = struct.pack("H" * 6, 0, 65535, 0, 65535, 0, 65535)
        ds.GetRasterBand(1).WriteRaster(0, 0, 6, 1, ref_content)
        ds = None
        ds = gdal.Open("/vsimem/tiff_write_139.tif")
        content = ds.GetRasterBand(1).ReadRaster()
        if ref_content != content:
            print(endianness)
            pytest.fail(struct.unpack("H" * 6, content))
        ds = None

        gdal.Unlink("/vsimem/tiff_write_139.tif")

    # Int32
    for endianness in ["NATIVE", "INVERTED"]:

        if endianness == "INVERTED" and not has_inverted_swab_fix:
            continue

        ds = drv.Create(
            "/vsimem/tiff_write_139.tif",
            6,
            1,
            1,
            gdal.GDT_UInt32,
            options=["PREDICTOR=2", "COMPRESS=DEFLATE", "ENDIANNESS=%s" % endianness],
        )
        ref_content = struct.pack("I" * 6, 0, 2000000000, 0, 2000000000, 0, 2000000000)
        ds.GetRasterBand(1).WriteRaster(0, 0, 6, 1, ref_content)
        ds = None
        ds = gdal.Open("/vsimem/tiff_write_139.tif")
        content = ds.GetRasterBand(1).ReadRaster()
        if ref_content != content:
            print(endianness)
            pytest.fail(struct.unpack("I" * 6, content))
        ds = None

        gdal.Unlink("/vsimem/tiff_write_139.tif")

    # Test floating-point predictor
    # Seems to be broken with ENDIANNESS=INVERTED
    ds = drv.Create(
        "/vsimem/tiff_write_139.tif",
        4,
        1,
        1,
        gdal.GDT_Float64,
        options=["PREDICTOR=3", "COMPRESS=DEFLATE"],
    )
    ref_content = struct.pack("d" * 4, 1, -1e100, 1e10, -1e5)
    ds.GetRasterBand(1).WriteRaster(0, 0, 4, 1, ref_content)
    ds = None
    ds = gdal.Open("/vsimem/tiff_write_139.tif")
    content = ds.GetRasterBand(1).ReadRaster()
    assert ref_content == content, struct.unpack("d" * 4, content)
    ds = None

    gdal.Unlink("/vsimem/tiff_write_139.tif")


###############################################################################
# Test setting a band to alpha


def test_tiff_write_140():

    # Nominal case: set alpha to last band
    ds = gdaltest.tiff_drv.Create("/vsimem/tiff_write_140.tif", 1, 1, 5)
    ds.GetRasterBand(5).SetColorInterpretation(gdal.GCI_AlphaBand)
    ds = None
    statBuf = gdal.VSIStatL(
        "/vsimem/tiff_write_140.tif.aux.xml",
        gdal.VSI_STAT_EXISTS_FLAG | gdal.VSI_STAT_NATURE_FLAG | gdal.VSI_STAT_SIZE_FLAG,
    )
    assert statBuf is None, "did not expect PAM file"
    ds = gdal.Open("/vsimem/tiff_write_140.tif")
    assert ds.GetRasterBand(5).GetColorInterpretation() == gdal.GCI_AlphaBand
    ds = None

    # Strange case: set alpha to a band, but it is already set on another one
    ds = gdaltest.tiff_drv.Create("/vsimem/tiff_write_140.tif", 1, 1, 5)
    ds.GetRasterBand(2).SetColorInterpretation(gdal.GCI_AlphaBand)
    # Should emit a warning
    gdal.ErrorReset()
    with gdal.quiet_errors():
        ret = ds.GetRasterBand(5).SetColorInterpretation(gdal.GCI_AlphaBand)
    assert gdal.GetLastErrorMsg() != ""
    assert ret == 0
    ds = None
    statBuf = gdal.VSIStatL(
        "/vsimem/tiff_write_140.tif.aux.xml",
        gdal.VSI_STAT_EXISTS_FLAG | gdal.VSI_STAT_NATURE_FLAG | gdal.VSI_STAT_SIZE_FLAG,
    )
    assert statBuf is None, "did not expect PAM file"
    ds = gdal.Open("/vsimem/tiff_write_140.tif")
    assert ds.GetRasterBand(2).GetColorInterpretation() == gdal.GCI_AlphaBand
    assert ds.GetRasterBand(5).GetColorInterpretation() == gdal.GCI_AlphaBand
    ds = None

    # Strange case: set alpha to a band, but it is already set on another one (because of ALPHA=YES)
    ds = gdaltest.tiff_drv.Create(
        "/vsimem/tiff_write_140.tif", 1, 1, 5, options=["ALPHA=YES"]
    )
    # Should emit a warning mentioning ALPHA creation option.
    gdal.ErrorReset()
    with gdal.quiet_errors():
        ret = ds.GetRasterBand(5).SetColorInterpretation(gdal.GCI_AlphaBand)
    assert gdal.GetLastErrorMsg().find("ALPHA") >= 0
    assert ret == 0
    ds = None
    statBuf = gdal.VSIStatL(
        "/vsimem/tiff_write_140.tif.aux.xml",
        gdal.VSI_STAT_EXISTS_FLAG | gdal.VSI_STAT_NATURE_FLAG | gdal.VSI_STAT_SIZE_FLAG,
    )
    assert statBuf is None, "did not expect PAM file"
    ds = gdal.Open("/vsimem/tiff_write_140.tif")
    assert ds.GetRasterBand(2).GetColorInterpretation() == gdal.GCI_AlphaBand
    assert ds.GetRasterBand(5).GetColorInterpretation() == gdal.GCI_AlphaBand
    ds = None

    gdaltest.tiff_drv.Delete("/vsimem/tiff_write_140.tif")


###############################################################################
# Test GEOTIFF_KEYS_FLAVOR=ESRI_PE with EPSG:3857


def test_tiff_write_141():

    ds = gdaltest.tiff_drv.Create(
        "/vsimem/tiff_write_141.tif", 1, 1, options=["GEOTIFF_KEYS_FLAVOR=ESRI_PE"]
    )
    srs = osr.SpatialReference()
    srs.ImportFromEPSG(3857)
    ds.SetProjection(srs.ExportToWkt())
    ds = None

    ds = gdal.Open("/vsimem/tiff_write_141.tif")
    wkt = ds.GetProjectionRef()
    ds = None

    assert wkt.startswith('PROJCS["WGS 84 / Pseudo-Mercator"')

    assert 'EXTENSION["PROJ4"' in wkt

    gdaltest.tiff_drv.Delete("/vsimem/tiff_write_141.tif")


###############################################################################
# Test PixelIsPoint without SRS (#6225)


def test_tiff_write_142():

    ds = gdaltest.tiff_drv.Create("/vsimem/tiff_write_142.tif", 1, 1)
    ds.SetMetadataItem("AREA_OR_POINT", "Point")
    ds.SetGeoTransform([10, 1, 0, 100, 0, -1])
    ds = None

    src_ds = gdal.Open("/vsimem/tiff_write_142.tif")
    gdaltest.tiff_drv.CreateCopy("/vsimem/tiff_write_142_2.tif", src_ds)
    src_ds = None

    ds = gdal.Open("/vsimem/tiff_write_142_2.tif")
    gt = ds.GetGeoTransform()
    md = ds.GetMetadataItem("AREA_OR_POINT")
    ds = None

    gdaltest.tiff_drv.Delete("/vsimem/tiff_write_142.tif")
    gdaltest.tiff_drv.Delete("/vsimem/tiff_write_142_2.tif")

    gt_expected = (10, 1, 0, 100, 0, -1)
    assert gt == gt_expected, "did not get expected geotransform"

    assert md == "Point", "did not get expected AREA_OR_POINT value"


###############################################################################
# Check that we detect that free space isn't sufficient


def test_tiff_write_143():

    with gdal.quiet_errors():
        ds = gdaltest.tiff_drv.Create(
            "/vsimem/tiff_write_143.tif", 1000000000, 1000000000
        )
    assert ds is None


###############################################################################
# Test creating a real BigTIFF file > 4 GB with multiple directories (on filesystems supporting sparse files)


def test_tiff_write_144():

    # Determine if the filesystem supports sparse files (we don't want to create a real 10 GB
    # file !
    if not gdaltest.filesystem_supports_sparse_files("tmp"):
        pytest.skip()

    ds = gdal.GetDriverByName("GTiff").Create(
        "tmp/tiff_write_144.tif", 20, 20, 1, options=["BIGTIFF=YES"]
    )
    ds.GetRasterBand(1).Fill(255)
    ds = None

    # Extend the file to 4 GB
    f = open("tmp/tiff_write_144.tif", "rb+")
    f.seek(4294967296, 0)
    f.write(" ".encode("ascii"))
    f.close()

    ds = gdal.Open("tmp/tiff_write_144.tif", gdal.GA_Update)
    ds.BuildOverviews("NEAR", [2])
    ds = None

    ds = gdal.Open("tmp/tiff_write_144.tif")
    got_cs = ds.GetRasterBand(1).Checksum()
    got_cs_ovr = ds.GetRasterBand(1).GetOverview(0).Checksum()
    ds = None

    gdal.Unlink("tmp/tiff_write_144.tif")

    assert got_cs == 4873 and got_cs_ovr == 1218


###############################################################################
# Test various warnings / errors of Create()


def test_tiff_write_145():

    options_list = [
        {"bands": 65536, "expected_failure": True},
        {"creation_options": ["INTERLEAVE=foo"], "expected_failure": True},
        {"creation_options": ["COMPRESS=foo"], "expected_failure": False},
        {
            "creation_options": ["STREAMABLE_OUTPUT=YES", "SPARSE_OK=YES"],
            "expected_failure": True,
        },
        {
            "creation_options": ["STREAMABLE_OUTPUT=YES", "COPY_SRC_OVERVIEWS=YES"],
            "expected_failure": True,
        },
        {
            "use_tmp": True,
            "xsize": 100000,
            "ysize": 100000,
            "creation_options": ["BIGTIFF=NO"],
            "expected_failure": True,
        },
        {"creation_options": ["ENDIANNESS=foo"], "expected_failure": False},
        {"creation_options": ["NBITS=9"], "expected_failure": False},
        {
            "datatype": gdal.GDT_Float32,
            "creation_options": ["NBITS=8"],
            "expected_failure": False,
        },
        {
            "datatype": gdal.GDT_UInt16,
            "creation_options": ["NBITS=8"],
            "expected_failure": False,
        },
        {
            "datatype": gdal.GDT_UInt16,
            "creation_options": ["NBITS=17"],
            "expected_failure": False,
        },
        {
            "datatype": gdal.GDT_UInt32,
            "creation_options": ["NBITS=16"],
            "expected_failure": False,
        },
        {
            "datatype": gdal.GDT_UInt32,
            "creation_options": ["NBITS=33"],
            "expected_failure": False,
        },
        {
            "bands": 3,
            "creation_options": ["PHOTOMETRIC=YCBCR"],
            "expected_failure": True,
        },
        {
            "bands": 3,
            "creation_options": [
                "PHOTOMETRIC=YCBCR",
                "COMPRESS=JPEG",
                "INTERLEAVE=BAND",
            ],
            "expected_failure": True,
        },
        {
            "bands": 1,
            "creation_options": ["PHOTOMETRIC=YCBCR", "COMPRESS=JPEG"],
            "expected_failure": True,
        },
        {"creation_options": ["PHOTOMETRIC=foo"], "expected_failure": False},
        {"creation_options": ["PHOTOMETRIC=RGB"], "expected_failure": False},
        {
            "creation_options": ["TILED=YES", "BLOCKSIZE=1", "BLOCKYSIZE=1"],
            "expected_failure": True,
        },
    ]

    for options in options_list:
        xsize = options.get("xsize", 1)
        ysize = options.get("ysize", 1)
        bands = options.get("bands", 1)
        datatype = options.get("datatype", gdal.GDT_Byte)
        use_tmp = options.get("use_tmp", False)
        if use_tmp:
            filename = "tmp/tiff_write_145.tif"
        else:
            filename = "/vsimem/tiff_write_145.tif"
        creation_options = options.get("creation_options", [])
        gdal.Unlink(filename)
        gdal.ErrorReset()
        with gdal.quiet_errors():
            ds = gdaltest.tiff_drv.Create(
                filename, xsize, ysize, bands, datatype, options=creation_options
            )
        if ds is not None and options.get("expected_failure", False):
            print(options)
            pytest.fail("expected failure, but did not get it")
        elif ds is None and not options.get("expected_failure", False):
            print(options)
            pytest.fail("got failure, but did not expect it")
        ds = None
        # print(gdal.GetLastErrorMsg())
        if gdal.GetLastErrorMsg() == "":
            print(options)
            pytest.fail("did not get any warning/error")
        gdal.Unlink(filename)


###############################################################################
# Test implicit JPEG-in-TIFF overviews with RGBA (not completely sure this
# is a legal formulation since 4 bands should probably be seen as CMYK)


@pytest.mark.require_creation_option("GTiff", "JPEG")
@pytest.mark.require_driver("JPEG")
def test_tiff_write_146():

    tmp_ds = gdal.Translate("", "data/stefan_full_rgba.tif", format="MEM")
    original_stats = [
        tmp_ds.GetRasterBand(i + 1).ComputeStatistics(True) for i in range(4)
    ]
    gdal.Translate(
        "/vsimem/tiff_write_146.tif",
        "data/stefan_full_rgba.tif",
        options="-outsize 1000% 1000% -co COMPRESS=JPEG",
    )
    out_ds = gdal.Open("/vsimem/tiff_write_146.tif")
    got_stats = [
        out_ds.GetRasterBand(i + 1).GetOverview(2).ComputeStatistics(True)
        for i in range(4)
    ]
    out_ds = None
    gdal.GetDriverByName("GTiff").Delete("/vsimem/tiff_write_146.tif")

    for i in range(4):
        for j in range(4):
            assert (
                i == 2
                or j < 2
                or original_stats[i][j] == pytest.approx(got_stats[i][j], abs=5)
            ), "did not get expected statistics"


###############################################################################
# Test that we don't use implicit JPEG-in-TIFF overviews with CMYK when converting
# to RGBA


@pytest.mark.require_creation_option("GTiff", "JPEG")
@pytest.mark.require_driver("JPEG")
def test_tiff_write_147():

    with gdal.config_options({"GDAL_JPEG_TO_RGB": "NO", "GDAL_PAM_ENABLED": "NO"}):
        gdal.Translate(
            "/vsimem/tiff_write_147.tif",
            "../gdrivers/data/jpeg/rgb_ntf_cmyk.jpg",
            options="-outsize 1000% 1000% -co COMPRESS=JPEG -co PHOTOMETRIC=CMYK",
        )
    out_ds = gdal.Open("/vsimem/tiff_write_147.tif")
    assert out_ds.GetRasterBand(1).GetOverview(0) is None, "did not expected overview"
    out_ds = None
    gdal.GetDriverByName("GTiff").Delete("/vsimem/tiff_write_147.tif")


###############################################################################
# Test that we can use implicit JPEG-in-TIFF overviews with CMYK in raw mode


@pytest.mark.require_creation_option("GTiff", "JPEG")
@pytest.mark.require_driver("JPEG")
def test_tiff_write_148():

    with gdal.config_option("GDAL_JPEG_TO_RGB", "NO"):
        tmp_ds = gdal.Translate(
            "", "../gdrivers/data/jpeg/rgb_ntf_cmyk.jpg", format="MEM"
        )
    original_stats = [
        tmp_ds.GetRasterBand(i + 1).ComputeStatistics(True) for i in range(4)
    ]

    with gdal.config_options({"GDAL_JPEG_TO_RGB": "NO", "GDAL_PAM_ENABLED": "NO"}):
        gdal.Translate(
            "/vsimem/tiff_write_148.tif",
            "../gdrivers/data/jpeg/rgb_ntf_cmyk.jpg",
            options="-outsize 1000% 1000% -co COMPRESS=JPEG -co PHOTOMETRIC=CMYK",
        )
    out_ds = gdal.Open("GTIFF_RAW:/vsimem/tiff_write_148.tif")
    got_stats = [
        out_ds.GetRasterBand(i + 1).GetOverview(0).ComputeStatistics(True)
        for i in range(4)
    ]
    out_ds = None
    gdal.GetDriverByName("GTiff").Delete("/vsimem/tiff_write_148.tif")

    for i in range(4):
        for j in range(4):
            assert j < 2 or original_stats[i][j] == pytest.approx(
                got_stats[i][j], abs=5
            ), "did not get expected statistics"


###############################################################################
# Test filling missing blocks with nodata


def test_tiff_write_149():

    # Power-of-two bit depth
    ds = gdaltest.tiff_drv.Create("/vsimem/tiff_write_149.tif", 1, 1)
    ds.GetRasterBand(1).SetNoDataValue(127)
    ds = None
    ds = gdal.Open("/vsimem/tiff_write_149.tif")
    cs = ds.GetRasterBand(1).Checksum()
    ds = None
    assert cs == 1
    gdaltest.tiff_drv.Delete("/vsimem/tiff_write_149.tif")

    # Test implicit blocks
    expected_cs = 13626
    ds = gdaltest.tiff_drv.Create(
        "/vsimem/tiff_write_149.tif",
        40,
        30,
        2,
        gdal.GDT_UInt16,
        options=[
            "NBITS=12",
            "TILED=YES",
            "BLOCKXSIZE=16",
            "BLOCKYSIZE=16",
            "INTERLEAVE=BAND",
            "SPARSE_OK=YES",
        ],
    )
    ds.GetRasterBand(1).SetNoDataValue(127)
    ds.GetRasterBand(2).SetNoDataValue(127)
    ds = None
    ds = gdal.Open("/vsimem/tiff_write_149.tif")
    cs = ds.GetRasterBand(1).Checksum()
    ds = None
    assert cs == expected_cs
    gdaltest.tiff_drv.Delete("/vsimem/tiff_write_149.tif")

    # NBITS=12, SEPARATE. Checksum must be the same as in the implicit blocks case
    ds = gdaltest.tiff_drv.Create(
        "/vsimem/tiff_write_149.tif",
        40,
        30,
        2,
        gdal.GDT_UInt16,
        options=[
            "NBITS=12",
            "TILED=YES",
            "BLOCKXSIZE=16",
            "BLOCKYSIZE=16",
            "INTERLEAVE=BAND",
        ],
    )
    ds.GetRasterBand(1).SetNoDataValue(127)
    ds.GetRasterBand(2).SetNoDataValue(127)
    ds = None
    ds = gdal.Open("/vsimem/tiff_write_149.tif")
    cs = ds.GetRasterBand(1).Checksum()
    ds = None
    assert cs == expected_cs
    gdaltest.tiff_drv.Delete("/vsimem/tiff_write_149.tif")

    # NBITS=12, CONTIG. Checksum must be the same as in the implicit blocks case
    ds = gdaltest.tiff_drv.Create(
        "/vsimem/tiff_write_149.tif",
        40,
        30,
        2,
        gdal.GDT_UInt16,
        options=[
            "NBITS=12",
            "TILED=YES",
            "BLOCKXSIZE=16",
            "BLOCKYSIZE=16",
            "INTERLEAVE=PIXEL",
        ],
    )
    ds.GetRasterBand(1).SetNoDataValue(127)
    ds.GetRasterBand(2).SetNoDataValue(127)
    ds = None
    ds = gdal.Open("/vsimem/tiff_write_149.tif")
    cs = ds.GetRasterBand(1).Checksum()
    ds = None
    assert cs == expected_cs
    gdaltest.tiff_drv.Delete("/vsimem/tiff_write_149.tif")


###############################################################################
# Test failure when loading block from disk in IWriteBlock()


def test_tiff_write_150():

    shutil.copy("data/tiled_bad_offset.tif", "tmp/tiled_bad_offset.tif")
    ds = gdal.Open("tmp/tiled_bad_offset.tif", gdal.GA_Update)
    ds.GetRasterBand(1).Fill(0)
    gdal.ErrorReset()
    with gdal.quiet_errors():
        ds.FlushCache()
    assert gdal.GetLastErrorMsg() != ""
    ds = None
    gdaltest.tiff_drv.Delete("tmp/tiled_bad_offset.tif")


###############################################################################
# Test IWriteBlock() with more than 10 bands


def test_tiff_write_151():

    ds = gdaltest.tiff_drv.Create("/vsimem/tiff_write_151.tif", 1, 1, 11)
    ds = None
    ds = gdal.Open("/vsimem/tiff_write_151.tif", gdal.GA_Update)
    ds.GetRasterBand(1).Fill(1)
    ds = None
    ds = gdal.Open("/vsimem/tiff_write_151.tif", gdal.GA_Update)
    ds.GetRasterBand(1).Checksum()
    ds.GetRasterBand(2).Fill(1)
    ds.GetRasterBand(3).Fill(1)
    ds = None
    ds = gdal.Open("/vsimem/tiff_write_151.tif")
    assert ds.GetRasterBand(1).Checksum() == 1
    assert ds.GetRasterBand(2).Checksum() == 1
    assert ds.GetRasterBand(3).Checksum() == 1
    ds = None
    gdaltest.tiff_drv.Delete("/vsimem/tiff_write_151.tif")


###############################################################################
# Test flushing of blocks in a contig multi band file with Create()


def test_tiff_write_152():

    ds = gdaltest.tiff_drv.Create(
        "/vsimem/tiff_write_152.tif", 1, 1, 2, options=["NBITS=2"]
    )
    ds.GetRasterBand(2).SetNoDataValue(3)
    ds.GetRasterBand(2).Fill(1)
    ds = None
    ds = gdal.Open("/vsimem/tiff_write_152.tif")
    assert ds.GetRasterBand(1).Checksum() == 0
    assert ds.GetRasterBand(2).Checksum() == 1
    ds = None
    gdaltest.tiff_drv.Delete("/vsimem/tiff_write_152.tif")


###############################################################################
# Test that empty blocks are created in a filesystem sparse way


def test_tiff_write_153():

    target_dir = "tmp"

    if gdal.VSISupportsSparseFiles(target_dir) == 0:
        pytest.skip()

    gdaltest.tiff_drv.Create(target_dir + "/tiff_write_153.tif", 500, 500)

    f = gdal.VSIFOpenL(target_dir + "/tiff_write_153.tif", "rb")
    ret = gdal.VSIFGetRangeStatusL(f, 500 * 500, 1)
    gdal.VSIFCloseL(f)

    gdaltest.tiff_drv.Delete(target_dir + "/tiff_write_153.tif")

    assert ret != gdal.VSI_RANGE_STATUS_DATA


###############################################################################
# Test empty block writing skipping and SPARSE_OK in CreateCopy() and Open()


def test_tiff_write_154():

    src_ds = gdal.GetDriverByName("MEM").Create("", 500, 500)

    ds = gdaltest.tiff_drv.CreateCopy(
        "/vsimem/tiff_write_154.tif", src_ds, options=["BLOCKYSIZE=256"]
    )
    ds.FlushCache()
    # At that point empty blocks have not yet been flushed
    assert gdal.VSIStatL("/vsimem/tiff_write_154.tif").size == 162
    ds = None
    # Now they are and that's done in a filesystem sparse way. TODO: check this
    assert gdal.VSIStatL("/vsimem/tiff_write_154.tif").size == 256162
    gdaltest.tiff_drv.Delete("/vsimem/tiff_write_154.tif")

    ds = gdaltest.tiff_drv.CreateCopy(
        "/vsimem/tiff_write_154.tif",
        src_ds,
        options=["BLOCKYSIZE=256", "COMPRESS=DEFLATE"],
    )
    ds.FlushCache()
    # With compression, empty blocks are written right away
    # 461 is with libdeflate, 462 with zlib
    assert gdal.VSIStatL("/vsimem/tiff_write_154.tif").size in (461, 462)
    ds = None
    assert gdal.VSIStatL("/vsimem/tiff_write_154.tif").size in (461, 462)
    gdaltest.tiff_drv.Delete("/vsimem/tiff_write_154.tif")

    # SPARSE_OK in CreateCopy(): blocks are not written
    ds = gdaltest.tiff_drv.CreateCopy(
        "/vsimem/tiff_write_154.tif",
        src_ds,
        options=["SPARSE_OK=YES", "BLOCKYSIZE=256"],
    )
    ds = None
    assert gdal.VSIStatL("/vsimem/tiff_write_154.tif").size == 162
    # SPARSE_OK in Open()/update: blocks are not written
    ds = gdal.OpenEx(
        "/vsimem/tiff_write_154.tif", gdal.OF_UPDATE, open_options=["SPARSE_OK=YES"]
    )
    ds.GetRasterBand(1).Fill(0)
    ds = None
    assert gdal.VSIStatL("/vsimem/tiff_write_154.tif").size == 162
    ds = None
    # Default behaviour in Open()/update: blocks are written
    ds = gdal.OpenEx("/vsimem/tiff_write_154.tif", gdal.OF_UPDATE)
    ds.GetRasterBand(1).Fill(0)
    ds = None
    assert gdal.VSIStatL("/vsimem/tiff_write_154.tif").size == 250162
    ds = None
    gdaltest.tiff_drv.Delete("/vsimem/tiff_write_154.tif")

    # SPARSE_OK in CreateCopy() in compressed case (strips): blocks are not written
    ds = gdaltest.tiff_drv.CreateCopy(
        "/vsimem/tiff_write_154.tif",
        src_ds,
        options=["SPARSE_OK=YES", "BLOCKYSIZE=256", "COMPRESS=DEFLATE"],
    )
    ds = None
    assert gdal.VSIStatL("/vsimem/tiff_write_154.tif").size == 174
    gdaltest.tiff_drv.Delete("/vsimem/tiff_write_154.tif")

    # SPARSE_OK in CreateCopy() in compressed case (tiling): blocks are not written
    ds = gdaltest.tiff_drv.CreateCopy(
        "/vsimem/tiff_write_154.tif", src_ds, options=["SPARSE_OK=YES", "TILED=YES"]
    )
    ds = None
    assert gdal.VSIStatL("/vsimem/tiff_write_154.tif").size == 190
    gdaltest.tiff_drv.Delete("/vsimem/tiff_write_154.tif")

    # Test detection of 0 blocks for all data types
    for dt in [
        "signedbyte",
        gdal.GDT_Int16,
        gdal.GDT_UInt16,
        gdal.GDT_Int32,
        gdal.GDT_UInt32,
        gdal.GDT_Float32,
        gdal.GDT_Float64,
    ]:
        # SPARSE_OK in CreateCopy(): blocks are not written
        if dt == "signedbyte":
            src_ds = gdal.GetDriverByName("MEM").Create("", 500, 500, 1, gdal.GDT_Byte)
            options = ["SPARSE_OK=YES", "BLOCKYSIZE=256", "PIXELTYPE=SIGNEDBYTE"]
        else:
            src_ds = gdal.GetDriverByName("MEM").Create("", 500, 500, 1, dt)
            options = ["SPARSE_OK=YES", "BLOCKYSIZE=256"]
        gdaltest.tiff_drv.CreateCopy(
            "/vsimem/tiff_write_154.tif", src_ds, options=options
        )
        assert gdal.VSIStatL("/vsimem/tiff_write_154.tif").size == 162, dt

    # Test detection of nodata blocks with nodata != 0 for all data types
    for dt in [
        "signedbyte",
        gdal.GDT_Int16,
        gdal.GDT_UInt16,
        gdal.GDT_Int32,
        gdal.GDT_UInt32,
        gdal.GDT_Float32,
        gdal.GDT_Float64,
    ]:
        # SPARSE_OK in CreateCopy(): blocks are not written
        if dt == "signedbyte":
            src_ds = gdal.GetDriverByName("MEM").Create("", 500, 500, 1, gdal.GDT_Byte)
            options = ["SPARSE_OK=YES", "BLOCKYSIZE=256", "PIXELTYPE=SIGNEDBYTE"]
        else:
            src_ds = gdal.GetDriverByName("MEM").Create("", 500, 500, 1, dt)
            options = ["SPARSE_OK=YES", "BLOCKYSIZE=256"]
        src_ds.GetRasterBand(1).Fill(1)
        src_ds.GetRasterBand(1).SetNoDataValue(1)
        ds = gdaltest.tiff_drv.CreateCopy(
            "/vsimem/tiff_write_154.tif", src_ds, options=options
        )
        ds = None
        assert gdal.VSIStatL("/vsimem/tiff_write_154.tif").size == 174, dt

    # Test optimized detection when nodata==0, and with the last pixel != 0
    src_ds = gdal.GetDriverByName("MEM").Create("", 100, 1, 1)
    src_ds.GetRasterBand(1).Fill(0)
    src_ds.GetRasterBand(1).WriteRaster(99, 0, 1, 1, struct.pack("B" * 1, 1))
    gdaltest.tiff_drv.CreateCopy(
        "/vsimem/tiff_write_154.tif", src_ds, options=["SPARSE_OK=YES"]
    )
    assert gdal.VSIStatL("/vsimem/tiff_write_154.tif").size == 246

    gdaltest.tiff_drv.Delete("/vsimem/tiff_write_154.tif")

    # Test that setting nodata doesn't prevent blocks to be written (#6706)
    ds = gdal.GetDriverByName("GTiff").Create("/vsimem/tiff_write_154.tif", 1, 100, 1)
    ds.GetRasterBand(1).SetNoDataValue(1)
    ds = None
    ds = gdal.Open("/vsimem/tiff_write_154.tif")
    offset = ds.GetRasterBand(1).GetMetadataItem("BLOCK_OFFSET_0_0", "TIFF")
    ds = None
    gdaltest.tiff_drv.Delete("/vsimem/tiff_write_154.tif")
    assert not (offset is None or int(offset) == 0)


###############################################################################
# Test reading and writing band description


def test_tiff_write_155():

    tiff_drv = gdal.GetDriverByName("GTiff")

    ds = tiff_drv.Create("/vsimem/tiff_write_155.tif", 1, 1)
    ds.GetRasterBand(1).SetDescription("foo")
    ds = None

    assert gdal.VSIStatL("/vsimem/tiff_write_155.tif.aux.xml") is None

    ds = gdal.Open("/vsimem/tiff_write_155.tif")
    assert ds.GetRasterBand(1).GetDescription() == "foo"
    ds = None

    # Override in PAM
    ds = gdal.Open("/vsimem/tiff_write_155.tif")
    ds.GetRasterBand(1).SetDescription("bar")
    ds = None

    assert gdal.VSIStatL("/vsimem/tiff_write_155.tif.aux.xml") is not None

    ds = gdal.Open("/vsimem/tiff_write_155.tif")
    assert ds.GetRasterBand(1).GetDescription() == "bar"
    ds = None

    tiff_drv.Delete("/vsimem/tiff_write_155.tif")
    assert gdal.VSIStatL("/vsimem/tiff_write_155.tif.aux.xml") is None

    ds = tiff_drv.Create(
        "/vsimem/tiff_write_155.tif", 1, 1, options=["PROFILE=GeoTIFF"]
    )
    ds.GetRasterBand(1).SetDescription("foo")
    ds = None

    assert gdal.VSIStatL("/vsimem/tiff_write_155.tif.aux.xml") is not None

    ds = gdal.Open("/vsimem/tiff_write_155.tif")
    assert ds.GetRasterBand(1).GetDescription() == "foo"
    ds = None
    tiff_drv.Delete("/vsimem/tiff_write_155.tif")


###############################################################################
# Test GetDataCoverageStatus()


def test_tiff_write_156():

    ds = gdaltest.tiff_drv.Create(
        "/vsimem/tiff_write_156.tif",
        64,
        64,
        options=["SPARSE_OK=YES", "TILED=YES", "BLOCKXSIZE=32", "BLOCKYSIZE=32"],
    )
    ds.GetRasterBand(1).WriteRaster(0, 0, 1, 1, "X")

    (flags, pct) = ds.GetRasterBand(1).GetDataCoverageStatus(0, 0, 32, 32)
    assert flags == gdal.GDAL_DATA_COVERAGE_STATUS_DATA and pct == 100.0

    (flags, pct) = ds.GetRasterBand(1).GetDataCoverageStatus(32, 0, 32, 32)
    assert flags == gdal.GDAL_DATA_COVERAGE_STATUS_EMPTY and pct == 0.0

    (flags, pct) = ds.GetRasterBand(1).GetDataCoverageStatus(16, 16, 32, 32)
    assert (
        flags
        == gdal.GDAL_DATA_COVERAGE_STATUS_DATA | gdal.GDAL_DATA_COVERAGE_STATUS_EMPTY
        and pct == 25.0
    )

    ds = None
    gdaltest.tiff_drv.Delete("/vsimem/tiff_write_156.tif")

    # Test fix for #6703
    ds = gdaltest.tiff_drv.Create(
        "/vsimem/tiff_write_156.tif", 1, 512, options=["SPARSE_OK=YES", "BLOCKYSIZE=1"]
    )
    ds.GetRasterBand(1).WriteRaster(0, 100, 1, 1, "X")
    ds = None
    ds = gdal.Open("/vsimem/tiff_write_156.tif")
    flags, _ = ds.GetRasterBand(1).GetDataCoverageStatus(0, 100, 1, 1)
    assert flags == gdal.GDAL_DATA_COVERAGE_STATUS_DATA
    ds = None
    gdaltest.tiff_drv.Delete("/vsimem/tiff_write_156.tif")


###############################################################################
# Test Float16


def test_tiff_write_157():

    # Write controlled values of Float16
    vals = struct.pack(
        "H" * 14,
        0x0000,  # Positive zero
        0x8000,  # Negative zero
        0x7C00,  # Positive infinity
        0xFC00,  # Negative infinity
        0x7E00,  # Some positive quiet NaN
        0xFE00,  # Some negative quiet NaN
        0x3D00,  # 1.25
        0xBD00,  # -1.25
        0x0001,  # Smallest positive denormalized value
        0x8001,  # Smallest negative denormalized value
        0x03FF,  # Largest positive denormalized value
        0x83FF,  # Largest negative denormalized value
        0x0400,  # Smallest positive normalized value
        0x8400,  # Smallest negative normalized value
    )

    ds = gdaltest.tiff_drv.Create(
        "/vsimem/tiff_write_157.tif", 14, 1, 1, gdal.GDT_Float32, options=["NBITS=16"]
    )
    ds = None
    ds = gdal.Open("/vsimem/tiff_write_157.tif")
    offset = int(ds.GetRasterBand(1).GetMetadataItem("BLOCK_OFFSET_0_0", "TIFF"))
    ds = None

    f = gdal.VSIFOpenL("/vsimem/tiff_write_157.tif", "rb+")
    gdal.VSIFSeekL(f, offset, 0)
    gdal.VSIFWriteL(vals, 1, len(vals), f)
    gdal.VSIFCloseL(f)

    # Check that we properly deserialize Float16 values
    ds = gdal.Open("/vsimem/tiff_write_157.tif")
    assert ds.GetRasterBand(1).GetMetadataItem("NBITS", "IMAGE_STRUCTURE") == "16"
    got = struct.unpack("f" * 14, ds.ReadRaster())
    expected = [
        0.0,
        -0.0,
        gdaltest.posinf(),
        -gdaltest.posinf(),
        gdaltest.NaN(),
        gdaltest.NaN(),
        1.25,
        -1.25,
        5.9604644775390625e-08,
        -5.9604644775390625e-08,
        6.0975551605224609e-05,
        -6.0975551605224609e-05,
        6.103515625e-05,
        -6.103515625e-05,
    ]
    for i in range(14):
        if i == 4 or i == 5:
            assert got[i] != got[i]
        elif got[i] != pytest.approx(expected[i], abs=1e-15):
            print(got[i])
            print(expected[i])
            pytest.fail(i)

    # Check that we properly decode&re-encode Float16 values
    gdal.Translate("/vsimem/tiff_write_157_dst.tif", ds)
    ds = None

    ds = gdal.Open("/vsimem/tiff_write_157_dst.tif")
    offset = int(ds.GetRasterBand(1).GetMetadataItem("BLOCK_OFFSET_0_0", "TIFF"))
    ds = None

    f = gdal.VSIFOpenL("/vsimem/tiff_write_157_dst.tif", "rb")
    gdal.VSIFSeekL(f, offset, 0)
    vals_copied = gdal.VSIFReadL(1, 14 * 2, f)
    gdal.VSIFCloseL(f)

    if vals != vals_copied:
        print(struct.unpack("H" * 14, vals))
        pytest.fail(struct.unpack("H" * 14, vals_copied))

    gdaltest.tiff_drv.Delete("/vsimem/tiff_write_157.tif")
    gdaltest.tiff_drv.Delete("/vsimem/tiff_write_157_dst.tif")

    # Now try Float32 -> Float16 conversion
    ds = gdaltest.tiff_drv.Create(
        "/vsimem/tiff_write_157.tif", 18, 1, 1, gdal.GDT_Float32, options=["NBITS=16"]
    )
    vals = struct.pack(
        "I" * 18,
        0x00000000,  # Positive zero
        0x80000000,  # Negative zero
        0x7F800000,  # Positive infinity
        0xFF800000,  # Negative infinity
        0x7FC00000,  # Some positive quiet NaN
        0xFFC00000,  # Some negative quiet NaN
        0x7F800001,  # Some positive signaling NaN with significant that will get lost
        0xFF800001,  # Some negative signaling NaN with significant that will get lost
        0x3FA00000,  # 1.25
        0xBFA00000,  # -1.25
        0x00000001,  # Smallest positive denormalized value
        0x80000001,  # Smallest negative denormalized value
        0x007FFFFF,  # Largest positive denormalized value
        0x807FFFFF,  # Largest negative denormalized value
        0x00800000,  # Smallest positive normalized value
        0x80800000,  # Smallest negative normalized value
        0x33800000,  # 5.9604644775390625e-08 = Smallest number that can be converted as a float16 denormalized value
        0x47800000,  # 65536 --> converted to infinity
    )
    ds.GetRasterBand(1).WriteRaster(0, 0, 18, 1, vals, buf_type=gdal.GDT_Float32)
    with gdal.quiet_errors():
        ds.FlushCache()
    ds = None

    ds = gdal.Open("/vsimem/tiff_write_157.tif")
    got = struct.unpack("f" * 18, ds.ReadRaster())
    ds = None
    expected = (
        0.0,
        -0.0,
        gdaltest.posinf(),
        -gdaltest.posinf(),
        gdaltest.NaN(),
        gdaltest.NaN(),
        gdaltest.NaN(),
        gdaltest.NaN(),
        1.25,
        -1.25,
        0.0,
        -0.0,
        0.0,
        -0.0,
        0.0,
        -0.0,
        5.9604644775390625e-08,
        gdaltest.posinf(),
    )

    for i in range(18):
        if i in (4, 5, 6, 7):
            # NaN comparison doesn't work like you'd expect
            assert got[i] != got[i]
        else:
            assert got[i] == pytest.approx(expected[i], abs=1e-15)

    gdaltest.tiff_drv.Delete("/vsimem/tiff_write_157.tif")

    # Test pixel interleaved
    gdal.Translate(
        "/vsimem/tiff_write_157.tif",
        "../gdrivers/data/small_world.tif",
        options="-co NBITS=16 -ot Float32",
    )
    ds = gdal.Open("/vsimem/tiff_write_157.tif")
    cs = ds.GetRasterBand(1).Checksum()
    assert cs == 30111
    cs = ds.GetRasterBand(2).Checksum()
    assert cs == 32302
    ds = None

    gdaltest.tiff_drv.Delete("/vsimem/tiff_write_157.tif")


###############################################################################
# Test GetActualBlockSize() (perhaps not the best place for that...)


def test_tiff_write_158():

    ds = gdaltest.tiff_drv.Create(
        "/vsimem/tiff_write_158.tif",
        20,
        40,
        1,
        options=["TILED=YES", "BLOCKXSIZE=16", "BLOCKYSIZE=32"],
    )
    (w, h) = ds.GetRasterBand(1).GetActualBlockSize(0, 0)
    assert (w, h) == (16, 32)
    (w, h) = ds.GetRasterBand(1).GetActualBlockSize(1, 1)
    assert (w, h) == (4, 8)
    res = ds.GetRasterBand(1).GetActualBlockSize(2, 0)
    assert res is None
    res = ds.GetRasterBand(1).GetActualBlockSize(0, 2)
    assert res is None
    res = ds.GetRasterBand(1).GetActualBlockSize(-1, 0)
    assert res is None
    res = ds.GetRasterBand(1).GetActualBlockSize(0, -1)
    assert res is None
    ds = None

    gdaltest.tiff_drv.Delete("/vsimem/tiff_write_158.tif")


###############################################################################
# Test that COPY_SRC_OVERVIEWS creation option with JPEG compression
# result in a https://trac.osgeo.org/gdal/wiki/CloudOptimizedGeoTIFF


@pytest.mark.require_creation_option("GTiff", "JPEG")
def test_tiff_write_159():

    prev_table = ""
    for options in [[], ["JPEG_QUALITY=50"], ["PHOTOMETRIC=YCBCR"]]:

        src_ds = gdal.Translate("", "../gdrivers/data/small_world.tif", format="MEM")
        src_ds.BuildOverviews("NEAR", overviewlist=[2, 4])
        ds = gdaltest.tiff_drv.CreateCopy(
            "/vsimem/tiff_write_159.tif",
            src_ds,
            options=["COPY_SRC_OVERVIEWS=YES", "COMPRESS=JPEG"] + options,
        )
        ds = None
        src_ds = None

        ds = gdal.Open("/vsimem/tiff_write_159.tif")
        cs0 = ds.GetRasterBand(1).Checksum()
        cs1 = ds.GetRasterBand(1).GetOverview(0).Checksum()
        cs2 = ds.GetRasterBand(1).GetOverview(1).Checksum()
        assert not (cs0 == 0 or cs1 == 0 or cs2 == 0), options
        ifd_main = int(ds.GetRasterBand(1).GetMetadataItem("IFD_OFFSET", "TIFF"))
        ifd_ovr_0 = int(
            ds.GetRasterBand(1).GetOverview(0).GetMetadataItem("IFD_OFFSET", "TIFF")
        )
        ifd_ovr_1 = int(
            ds.GetRasterBand(1).GetOverview(1).GetMetadataItem("IFD_OFFSET", "TIFF")
        )
        data_ovr_1 = int(
            ds.GetRasterBand(1)
            .GetOverview(1)
            .GetMetadataItem("BLOCK_OFFSET_0_0", "TIFF")
        )
        data_ovr_0 = int(
            ds.GetRasterBand(1)
            .GetOverview(0)
            .GetMetadataItem("BLOCK_OFFSET_0_0", "TIFF")
        )
        data_main = int(ds.GetRasterBand(1).GetMetadataItem("BLOCK_OFFSET_0_0", "TIFF"))
        assert (
            ifd_main < ifd_ovr_0
            and ifd_ovr_0 < ifd_ovr_1
            and ifd_ovr_1 < data_ovr_1
            and data_ovr_1 < data_ovr_0
            and data_ovr_0 < data_main
        ), options
        table_main = ds.GetRasterBand(1).GetMetadataItem("JPEGTABLES", "TIFF")
        table_ovr_0 = (
            ds.GetRasterBand(1).GetOverview(0).GetMetadataItem("JPEGTABLES", "TIFF")
        )
        table_ovr_1 = (
            ds.GetRasterBand(1).GetOverview(1).GetMetadataItem("JPEGTABLES", "TIFF")
        )
        assert table_main == table_ovr_0 and table_ovr_0 == table_ovr_1, options
        # Check that the JPEG tables are different in the 3 modes
        assert table_main != prev_table, options
        prev_table = table_main
        ds = None

        gdaltest.tiff_drv.Delete("/vsimem/tiff_write_159.tif")

    for value in range(4):

        src_ds = gdal.Translate("", "data/byte.tif", format="MEM")
        src_ds.BuildOverviews("NEAR", overviewlist=[2])
        ds = gdaltest.tiff_drv.CreateCopy(
            "/vsimem/tiff_write_159.tif",
            src_ds,
            options=[
                "COPY_SRC_OVERVIEWS=YES",
                "COMPRESS=JPEG",
                "JPEGTABLESMODE=%d" % value,
            ],
        )
        ds = None
        src_ds = None

        ds = gdal.Open("/vsimem/tiff_write_159.tif")
        cs0 = ds.GetRasterBand(1).Checksum()
        cs1 = ds.GetRasterBand(1).GetOverview(0).Checksum()
        assert cs0 == 4743 and cs1 == 1133, value
        ds = None

        gdaltest.tiff_drv.Delete("/vsimem/tiff_write_159.tif")


###############################################################################
# Test the Create() interface with a BLOCKYSIZE > image height


def test_tiff_write_160():

    ds = gdaltest.tiff_drv.Create(
        "/vsimem/tiff_write_160.tif", 10, 10, options=["BLOCKYSIZE=11"]
    )
    ds.GetRasterBand(1).Fill(255)
    ds = None

    ds = gdal.Open("/vsimem/tiff_write_160.tif")
    cs = ds.GetRasterBand(1).Checksum()
    assert cs == 1218
    ds = None

    gdaltest.tiff_drv.Delete("/vsimem/tiff_write_160.tif")


###############################################################################
# Test setting GCPs on an image with already a geotransform and vice-versa (#6751)


def test_tiff_write_161():

    ds = gdaltest.tiff_drv.Create("/vsimem/tiff_write_161.tif", 1, 1)
    ds.SetGeoTransform([0, 1, 2, 3, 4, 5])
    ds = None

    ds = gdal.Open("/vsimem/tiff_write_161.tif", gdal.GA_Update)
    src_ds = gdal.Open("data/gcps.vrt")
    with gdal.quiet_errors():
        assert ds.SetGCPs(src_ds.GetGCPs(), "") == 0
    assert ds.GetGeoTransform(can_return_null=True) is None
    ds = None

    ds = gdal.Open("/vsimem/tiff_write_161.tif", gdal.GA_Update)
    assert ds.GetGCPs()
    assert ds.GetGeoTransform(can_return_null=True) is None
    with gdal.quiet_errors():
        assert ds.SetGeoTransform([0, 1, 2, 3, 4, 5]) == 0
    assert ds.GetGeoTransform() == (0.0, 1.0, 2.0, 3.0, 4.0, 5.0)
    assert not ds.GetGCPs()
    ds = None

    ds = gdal.Open("/vsimem/tiff_write_161.tif", gdal.GA_Update)
    assert not ds.GetGCPs()
    assert ds.GetGeoTransform() == (0.0, 1.0, 2.0, 3.0, 4.0, 5.0)
    ds = None

    gdaltest.tiff_drv.Delete("/vsimem/tiff_write_161.tif")


###############################################################################
# Test creating a JPEG compressed file with big tiles (#6757)


@pytest.mark.require_creation_option("GTiff", "JPEG")
def test_tiff_write_162():

    src_ds = gdal.GetDriverByName("MEM").Create("", 512, 512, 3)

    options = ["TILED=YES", "BLOCKXSIZE=512", "BLOCKYSIZE=512", "COMPRESS=JPEG"]

    gdaltest.tiff_drv.CreateCopy("/vsimem/tiff_write_162.tif", src_ds, options=options)

    assert gdal.GetLastErrorMsg() == ""

    gdaltest.tiff_drv.Delete("/vsimem/tiff_write_162.tif")


###############################################################################
# Test creating a file that would trigger strip chopping (#6924)


def test_tiff_write_163():

    # Was a libtiff 4.0.8 regression
    if gdaltest.tiff_drv.GetMetadataItem("LIBTIFF").find("4.0.8") >= 0:
        pytest.skip("Test broken with libtiff 4.0.8")

    gdal.Translate(
        "/vsimem/tiff_write_163.tif",
        "data/byte.tif",
        options="-outsize 1 20000 -co BLOCKYSIZE=20000 -co PROFILE=BASELINE",
    )
    ds = gdal.Open("/vsimem/tiff_write_163.tif")
    cs = ds.GetRasterBand(1).Checksum()
    assert cs == 47600
    # Check that IsBlockAvailable() works properly in that mode
    offset_0_2 = ds.GetRasterBand(1).GetMetadataItem("BLOCK_OFFSET_0_2", "TIFF")
    assert offset_0_2 == str(146 + 2 * 8192)
    ds = None

    gdaltest.tiff_drv.Delete("/vsimem/tiff_write_163.tif")


###############################################################################
# Test that we handle [0,1,0,0,0,1] geotransform as a regular geotransform


def test_tiff_write_164():

    ds = gdaltest.tiff_drv.Create("/vsimem/test.tif", 1, 1)
    ds.SetGeoTransform([0, 1, 0, 0, 0, 1])
    ds = None

    ds = gdal.Open("/vsimem/test.tif")
    gt = ds.GetGeoTransform(can_return_null=True)
    ds = None

    assert gt == (0, 1, 0, 0, 0, 1)

    # Test [0,1,0,0,0,-1] as well
    ds = gdaltest.tiff_drv.Create("/vsimem/test.tif", 1, 1)
    ds.SetGeoTransform([0, 1, 0, 0, 0, -1])
    ds = None

    ds = gdal.Open("/vsimem/test.tif")
    gt = ds.GetGeoTransform(can_return_null=True)
    ds = None

    assert gt == (0, 1, 0, 0, 0, -1)

    gdal.Unlink("/vsimem/test.tif")


###############################################################################
# Test the current behaviour of per-band nodata vs per-dataset serialization


def test_tiff_write_165():

    ds = gdaltest.tiff_drv.Create("/vsimem/test.tif", 1, 1, 3)
    ret = ds.GetRasterBand(1).SetNoDataValue(100)
    assert ret == 0

    with gdal.quiet_errors():
        ret = ds.GetRasterBand(2).SetNoDataValue(200)
    assert gdal.GetLastErrorMsg() != "", "warning expected, but not emitted"
    assert ret == 0

    nd = ds.GetRasterBand(1).GetNoDataValue()
    assert nd == 100

    nd = ds.GetRasterBand(2).GetNoDataValue()
    assert nd == 200

    ds = None

    ds = gdal.Open("/vsimem/test.tif")
    nd = ds.GetRasterBand(1).GetNoDataValue()
    ds = None

    assert nd == 200

    gdal.Unlink("/vsimem/test.tif")


###############################################################################
# Test reading & writing Z dimension for ModelTiepointTag and ModelPixelScaleTag (#7093)


def test_tiff_write_166(tmp_path):

    with gdaltest.config_option("GTIFF_REPORT_COMPD_CS", "YES"):
        ds = gdal.Open("data/tiff_vertcs_scale_offset.tif")
        assert ds.GetRasterBand(1).GetScale() == 2.0
        assert ds.GetRasterBand(1).GetOffset() == 10.0

    tmp_srcfilename = str(tmp_path / "byte.tif")
    shutil.copy("data/byte.tif", tmp_srcfilename)

    # Scale + offset through CreateCopy()
    gdal.Translate(
        "/vsimem/tiff_write_166.tif",
        tmp_srcfilename,
        options="-a_srs EPSG:26711+5773 -a_scale 2.0 -a_offset 10 -co PROFILE=GEOTIFF",
    )
    s = gdal.VSIStatL("/vsimem/tiff_write_166.tif.aux.xml")
    if s is not None:
        if gdaltest.is_travis_branch("sanitize"):
            pytest.skip("fails on sanitize for unknown reason")

        # Failure related to the change of https://github.com/OSGeo/gdal/pull/9040
        # But the above code *does* not go through the modified code path...
        # Not reproduced locally on a minimum Windows build
        if gdaltest.is_travis_branch("build-windows-minimum"):
            pytest.skip("fails on build-windows-minimum for unknown reason")
    assert s is None

    with gdaltest.config_option("GTIFF_REPORT_COMPD_CS", "YES"):
        ds = gdal.Open("/vsimem/tiff_write_166.tif")
        assert ds.GetRasterBand(1).GetScale() == 2.0
        assert ds.GetRasterBand(1).GetOffset() == 10.0

    ds = None
    gdal.Unlink("/vsimem/tiff_write_166.tif")

    # Offset only through CreateCopy()
    gdal.Translate(
        "/vsimem/tiff_write_166.tif",
        tmp_srcfilename,
        options="-a_srs EPSG:26711+5773 -a_offset 10 -co PROFILE=GEOTIFF",
    )
    assert gdal.VSIStatL("/vsimem/tiff_write_166.tif.aux.xml") is None

    with gdaltest.config_option("GTIFF_REPORT_COMPD_CS", "YES"):
        ds = gdal.Open("/vsimem/tiff_write_166.tif")
        assert ds.GetRasterBand(1).GetScale() == 1.0
        assert ds.GetRasterBand(1).GetOffset() == 10.0

    ds = None
    gdal.Unlink("/vsimem/tiff_write_166.tif")

    # Scale + offset through Create()
    ds = gdal.GetDriverByName("GTiff").Create(
        "/vsimem/tiff_write_166.tif", 1, 1, options=["PROFILE=GEOTIFF"]
    )
    sr = osr.SpatialReference()
    sr.SetFromUserInput("EPSG:26711+5773")
    ds.SetProjection(sr.ExportToWkt())
    ds.SetGeoTransform([440720, 60, 0, 3751320, 0, -60])
    ds.GetRasterBand(1).SetScale(2)
    ds.GetRasterBand(1).SetOffset(10)
    ds = None
    assert gdal.VSIStatL("/vsimem/tiff_write_166.tif.aux.xml") is None

    with gdaltest.config_option("GTIFF_REPORT_COMPD_CS", "YES"):
        ds = gdal.Open("/vsimem/tiff_write_166.tif")
        assert ds.GetRasterBand(1).GetScale() == 2.0
        assert ds.GetRasterBand(1).GetOffset() == 10.0
    ds = None
    gdal.Unlink("/vsimem/tiff_write_166.tif")

    # Scale only through Create()
    ds = gdal.GetDriverByName("GTiff").Create(
        "/vsimem/tiff_write_166.tif", 1, 1, options=["PROFILE=GEOTIFF"]
    )
    sr = osr.SpatialReference()
    sr.SetFromUserInput("EPSG:26711+5773")
    ds.SetProjection(sr.ExportToWkt())
    ds.SetGeoTransform([440720, 60, 0, 3751320, 0, -60])
    ds.GetRasterBand(1).SetScale(2)
    ds = None
    assert gdal.VSIStatL("/vsimem/tiff_write_166.tif.aux.xml") is None

    with gdaltest.config_option("GTIFF_REPORT_COMPD_CS", "YES"):
        ds = gdal.Open("/vsimem/tiff_write_166.tif")
        assert ds.GetRasterBand(1).GetScale() == 2.0
        assert ds.GetRasterBand(1).GetOffset() == 0.0
    ds = None
    gdal.Unlink("/vsimem/tiff_write_166.tif")

    # Offset only through through Create()
    ds = gdal.GetDriverByName("GTiff").Create(
        "/vsimem/tiff_write_166.tif", 1, 1, options=["PROFILE=GEOTIFF"]
    )
    sr = osr.SpatialReference()
    sr.SetFromUserInput("EPSG:26711+5773")
    ds.SetProjection(sr.ExportToWkt())
    ds.SetGeoTransform([440720, 60, 0, 3751320, 0, -60])
    ds.GetRasterBand(1).SetOffset(10)
    ds = None
    assert gdal.VSIStatL("/vsimem/tiff_write_166.tif.aux.xml") is None

    with gdaltest.config_option("GTIFF_REPORT_COMPD_CS", "YES"):
        ds = gdal.Open("/vsimem/tiff_write_166.tif")
        assert ds.GetRasterBand(1).GetScale() == 1.0
        assert ds.GetRasterBand(1).GetOffset() == 10.0
    ds = None
    gdal.Unlink("/vsimem/tiff_write_166.tif")


###############################################################################


def test_tiff_write_167_deflate_zlevel():

    src_ds = gdal.Open("data/rgbsmall.tif")
    gdal.GetDriverByName("GTiff").CreateCopy(
        "/vsimem/out.tif", src_ds, options=["COMPRESS=DEFLATE", "ZLEVEL=1"]
    )
    size1 = gdal.VSIStatL("/vsimem/out.tif").size

    gdal.GetDriverByName("GTiff").CreateCopy(
        "/vsimem/out.tif",
        src_ds,
        options=["COMPRESS=DEFLATE", "NUM_THREADS=2", "ZLEVEL=9"],
    )
    size2 = gdal.VSIStatL("/vsimem/out.tif").size
    gdal.Unlink("/vsimem/out.tif")

    assert size2 < size1

    ds = gdal.GetDriverByName("GTiff").Create(
        "/vsimem/out.tif",
        src_ds.RasterXSize,
        src_ds.RasterYSize,
        src_ds.RasterCount,
        options=["COMPRESS=DEFLATE", "ZLEVEL=9"],
    )
    ds.SetProjection(src_ds.GetProjectionRef())
    ds.SetGeoTransform(src_ds.GetGeoTransform())
    ds.WriteRaster(0, 0, src_ds.RasterXSize, src_ds.RasterYSize, src_ds.ReadRaster())
    ds = None

    size2_create = gdal.VSIStatL("/vsimem/out.tif").size
    gdal.Unlink("/vsimem/out.tif")

    assert size2 == size2_create


###############################################################################
# Test CCITTFAX3


def test_tiff_write_168_ccitfax3():

    ut = gdaltest.GDALTest(
        "GTiff", "oddsize1bit.tif", 1, 5918, options=["NBITS=1", "COMPRESS=CCITTFAX3"]
    )
    ut.testCreateCopy()


###############################################################################
# Test CCITTRLE


def test_tiff_write_169_ccitrle():

    ut = gdaltest.GDALTest(
        "GTiff", "oddsize1bit.tif", 1, 5918, options=["NBITS=1", "COMPRESS=CCITTRLE"]
    )
    ut.testCreateCopy()


###############################################################################
# Test invalid compression method


def test_tiff_write_170_invalid_compresion():

    src_ds = gdal.Open("data/byte.tif")
    with gdal.quiet_errors():
        gdal.GetDriverByName("GTiff").CreateCopy(
            "/vsimem/out.tif", src_ds, options=["COMPRESS=INVALID"]
        )
    assert gdal.GetLastErrorMsg() != ""
    gdal.Unlink("/vsimem/out.tif")


###############################################################################
# Test ZSTD compression


@pytest.mark.require_creation_option("GTiff", "ZSTD")
def test_tiff_write_171_zstd():

    ut = gdaltest.GDALTest(
        "GTiff", "byte.tif", 1, 4672, options=["COMPRESS=ZSTD", "ZSTD_LEVEL=1"]
    )
    ut.testCreateCopy()


###############################################################################
# Test ZSTD compression with PREDICTOR = 2


@pytest.mark.require_creation_option("GTiff", "ZSTD")
def test_tiff_write_171_zstd_predictor():

    ut = gdaltest.GDALTest(
        "GTiff",
        "byte.tif",
        1,
        4672,
        options=["COMPRESS=ZSTD", "ZSTD_LEVEL=1", "PREDICTOR=2"],
    )
    ut.testCreateCopy()


###############################################################################
# Test WEBP compression


@pytest.mark.parametrize("writeImageStructureMetadata", [True, False])
@pytest.mark.require_creation_option("GTiff", "WEBP")
def test_tiff_write_webp(writeImageStructureMetadata):

    filename = "/vsimem/test_tiff_write_webp.tif"
    src_ds = gdal.Open("data/md_ge_rgb_0010000.tif")
    with gdaltest.config_option(
        "GTIFF_WRITE_IMAGE_STRUCTURE_METADATA",
        "YES" if writeImageStructureMetadata else "NO",
    ):
        gdaltest.tiff_drv.CreateCopy(
            filename, src_ds, options=["COMPRESS=WEBP", "WEBP_LEVEL=50"]
        )
    ds = gdal.Open(filename)

    assert ds.GetMetadataItem("WEBP_LOSSLESS", "_DEBUG_") == "0"

    if writeImageStructureMetadata:
        assert ds.GetMetadataItem("WEBP_LEVEL", "_DEBUG_") == "50"
        assert ds.GetMetadata("IMAGE_STRUCTURE")["WEBP_LEVEL"] == "50"
    else:
        assert ds.GetMetadataItem("WEBP_LEVEL", "_DEBUG_") == "75"

    if writeImageStructureMetadata or gdal.GetDriverByName("WEBP") is not None:
        assert ds.GetMetadata("IMAGE_STRUCTURE")["COMPRESSION_REVERSIBILITY"] == "LOSSY"

    cs = [ds.GetRasterBand(i + 1).Checksum() for i in range(3)]
    assert cs != [0, 0, 0]

    gdaltest.tiff_drv.Delete(filename)
    gdal.Unlink("data/md_ge_rgb_0010000.tif.aux.xml")


###############################################################################
# Test WEBP compression with internal tiling


@pytest.mark.parametrize("writeImageStructureMetadata", [True, False])
@pytest.mark.require_creation_option("GTiff", "WEBP")
@pytest.mark.require_creation_option("GTiff", "WEBP_LOSSLESS")
def test_tiff_write_tiled_webp(writeImageStructureMetadata):

    filename = "/vsimem/tiff_write_tiled_webp.tif"
    src_ds = gdal.Open("data/md_ge_rgb_0010000.tif")
    with gdaltest.config_option(
        "GTIFF_WRITE_IMAGE_STRUCTURE_METADATA",
        "YES" if writeImageStructureMetadata else "NO",
    ):
        gdaltest.tiff_drv.CreateCopy(
            filename,
            src_ds,
            options=["COMPRESS=WEBP", "WEBP_LOSSLESS=true", "TILED=true"],
        )
    ds = gdal.Open(filename)
    if writeImageStructureMetadata:
        assert ds.GetMetadataItem("WEBP_LOSSLESS", "_DEBUG_") == "1"
    else:
        assert ds.GetMetadataItem("WEBP_LOSSLESS", "_DEBUG_") == "0"
    if writeImageStructureMetadata or gdal.GetDriverByName("WEBP"):
        assert (
            ds.GetMetadata("IMAGE_STRUCTURE")["COMPRESSION_REVERSIBILITY"] == "LOSSLESS"
        )
    assert ds.GetMetadataItem("WEBP_LEVEL", "IMAGE_STRUCTURE") is None
    cs = [ds.GetRasterBand(i + 1).Checksum() for i in range(3)]
    assert cs == [21212, 21053, 21349]

    ds = None
    ds = gdal.Open(filename, gdal.GA_Update)
    if writeImageStructureMetadata or gdal.GetDriverByName("WEBP"):
        assert ds.GetMetadataItem("WEBP_LOSSLESS", "_DEBUG_") == "1"
        assert (
            ds.GetMetadata("IMAGE_STRUCTURE")["COMPRESSION_REVERSIBILITY"] == "LOSSLESS"
        )
    ds = None

    gdaltest.tiff_drv.Delete(filename)
    gdal.Unlink("data/md_ge_rgb_0010000.tif.aux.xml")


###############################################################################
# Test WEBP compression with huge single strip


@pytest.mark.require_creation_option("GTiff", "WEBP")
def test_tiff_write_webp_huge_single_strip():

    filename = "/vsimem/tif_webp_huge_single_strip.tif"
    src_ds = gdal.Open("data/tif_webp_huge_single_strip.tif")
    gdaltest.tiff_drv.CreateCopy(
        filename, src_ds, options=["COMPRESS=WEBP", "BLOCKYSIZE=2001"]
    )
    ds = gdal.Open(filename)
    original_stats = [
        src_ds.GetRasterBand(i + 1).ComputeStatistics(True) for i in range(3)
    ]
    got_stats = [ds.GetRasterBand(i + 1).ComputeStatistics(True) for i in range(3)]
    ds = None
    src_ds = None

    for i in range(3):
        for j in range(4):
            assert original_stats[i][j] == pytest.approx(
                got_stats[i][j], abs=1e-1 * abs(original_stats[i][j])
            ), "did not get expected statistics"

    gdaltest.tiff_drv.Delete(filename)
    gdal.Unlink("data/tif_webp_huge_single_strip.tif.aux.xml")


###############################################################################
# GeoTIFF DGIWG tags


def test_tiff_write_172_geometadata_tiff_rsid():

    tmpfilename = "/vsimem/tiff_write_172_geometadata_tiff_rsid.tiff"
    ds = gdal.GetDriverByName("GTiff").Create(tmpfilename, 1, 1)
    ds.SetMetadataItem("GEO_METADATA", "foo")
    ds.SetMetadataItem("TIFF_RSID", "bar")
    ds = None

    ds = gdal.Open(tmpfilename, gdal.GA_Update)
    assert ds.GetMetadataItem("GEO_METADATA") == "foo", ds.GetMetadata()
    assert ds.GetMetadataItem("TIFF_RSID") == "bar", ds.GetMetadata()
    ds.SetMetadata({})
    ds = None

    ds = gdal.Open(tmpfilename)
    assert ds.GetMetadataItem("GEO_METADATA") is None, ds.GetMetadata()
    assert ds.GetMetadataItem("TIFF_RSID") is None, ds.GetMetadata()
    ds = None

    gdal.Unlink(tmpfilename)


###############################################################################
# Test LERC compression


@pytest.mark.require_creation_option("GTiff", "LERC")
def test_tiff_write_173_lerc():

    ut = gdaltest.GDALTest("GTiff", "byte.tif", 1, 4672, options=["COMPRESS=LERC"])
    ut.testCreateCopy()


###############################################################################
# Test LERC_DEFLATE compression


@pytest.mark.require_creation_option("GTiff", "LERC_DEFLATE")
def test_tiff_write_174_lerc_deflate():

    ut = gdaltest.GDALTest(
        "GTiff", "byte.tif", 1, 4672, options=["COMPRESS=LERC_DEFLATE"]
    )
    ut.testCreateCopy()


###############################################################################
# Test LERC_DEFLATE compression


@pytest.mark.require_creation_option("GTiff", "LERC_DEFLATE")
def test_tiff_write_174_lerc_deflate_with_level():

    ut = gdaltest.GDALTest(
        "GTiff", "byte.tif", 1, 4672, options=["COMPRESS=LERC_DEFLATE", "ZLEVEL=1"]
    )
    ut.testCreateCopy()


###############################################################################
# Test LERC_ZSTD compression


@pytest.mark.require_creation_option("GTiff", "LERC_ZSTD")
def test_tiff_write_175_lerc_zstd():

    ut = gdaltest.GDALTest("GTiff", "byte.tif", 1, 4672, options=["COMPRESS=LERC_ZSTD"])
    ut.testCreateCopy()


###############################################################################
# Test LERC_ZSTD compression


@pytest.mark.require_creation_option("GTiff", "LERC_ZSTD")
def test_tiff_write_175_lerc_zstd_with_level():

    ut = gdaltest.GDALTest(
        "GTiff", "byte.tif", 1, 4672, options=["COMPRESS=LERC_ZSTD", "ZSTD_LEVEL=1"]
    )
    ut.testCreateCopy()


###############################################################################
# Test LERC compression with MAX_Z_ERROR


@pytest.mark.require_creation_option("GTiff", "LERC")
def test_tiff_write_176_lerc_max_z_error():

    ut = gdaltest.GDALTest(
        "GTiff", "byte.tif", 1, 4529, options=["COMPRESS=LERC", "MAX_Z_ERROR=1"]
    )
    ut.testCreateCopy(skip_preclose_test=1)


###############################################################################
# Test LERC compression with several bands and tiling


@pytest.mark.require_creation_option("GTiff", "LERC")
def test_tiff_write_177_lerc_several_bands_tiling():

    filename = "/vsimem/tiff_write_177_lerc_several_bands_tiling.tif"
    gdal.Translate(
        filename,
        "../gdrivers/data/small_world.tif",
        creationOptions=["COMPRESS=LERC", "TILED=YES"],
    )
    ds = gdal.Open(filename)
    cs = [ds.GetRasterBand(i + 1).Checksum() for i in range(3)]
    ds = None
    gdal.Unlink(filename)
    assert cs == [30111, 32302, 40026]


###############################################################################
# Test LERC compression with alpha band


@pytest.mark.require_creation_option("GTiff", "LERC")
def test_tiff_write_178_lerc_with_alpha():

    filename = "/vsimem/tiff_write_178_lerc_with_alpha.tif"
    gdal.Translate(
        filename, "data/stefan_full_rgba.tif", creationOptions=["COMPRESS=LERC"]
    )
    ds = gdal.Open(filename)
    cs = [ds.GetRasterBand(i + 1).Checksum() for i in range(4)]
    ds = None
    gdal.Unlink(filename)
    assert cs == [12603, 58561, 36064, 10807]


###############################################################################
# Test LERC compression with alpha band with only 0 and 255


@pytest.mark.require_creation_option("GTiff", "LERC")
def test_tiff_write_178_lerc_with_alpha_0_and_255():

    filename = "/vsimem/tiff_write_178_lerc_with_alpha_0_and_255.tif"
    gdal.Translate(
        filename,
        "data/rgba_with_alpha_0_and_255.tif",
        creationOptions=["COMPRESS=LERC"],
    )
    ds = gdal.Open(filename)
    cs = [ds.GetRasterBand(i + 1).Checksum() for i in range(4)]
    ds = None
    gdal.Unlink(filename)
    assert cs == [13, 13, 13, 13]


###############################################################################
# Test LERC compression with different data types


@pytest.mark.require_creation_option("GTiff", "LERC")
def test_tiff_write_179_lerc_data_types():

    filename = "/vsimem/tiff_write_179_lerc_data_types.tif"
    for src_filename in [
        "uint16.tif",
        "int16.tif",
        "uint32.tif",
        "int32.tif",
        "float32.tif",
        "float64.tif",
    ]:
        gdal.Translate(
            filename, "data/" + src_filename, creationOptions=["COMPRESS=LERC"]
        )
        ds = gdal.Open(filename)
        cs = ds.GetRasterBand(1).Checksum()
        ds = None
        gdal.Unlink(filename)
        assert cs == 4672

    filename_tmp = filename + ".tmp.tif"
    with gdal.quiet_errors():
        gdal.Translate(
            filename_tmp, "data/byte.tif", creationOptions=["PIXELTYPE=SIGNEDBYTE"]
        )
    gdal.Translate(filename, filename_tmp, creationOptions=["COMPRESS=LERC"])
    gdal.Unlink(filename_tmp)
    ds = gdal.Open(filename)
    cs = ds.GetRasterBand(1).Checksum()
    ds = None
    gdal.Unlink(filename)
    assert cs == 1046

    gdal.ErrorReset()
    with gdal.quiet_errors():
        gdal.Translate(filename, "data/cfloat32.tif", creationOptions=["COMPRESS=LERC"])
    assert gdal.GetLastErrorMsg() != ""
    gdal.Unlink(filename)


###############################################################################
# Test LERC compression with several bands and separate


@pytest.mark.require_creation_option("GTiff", "LERC")
def test_tiff_write_180_lerc_separate():

    filename = "/vsimem/tiff_write_180_lerc_separate.tif"
    gdal.Translate(
        filename,
        "../gdrivers/data/small_world.tif",
        creationOptions=["COMPRESS=LERC", "INTERLEAVE=BAND"],
    )
    ds = gdal.Open(filename)
    cs = [ds.GetRasterBand(i + 1).Checksum() for i in range(3)]
    ds = None
    gdal.Unlink(filename)
    assert cs == [30111, 32302, 40026]


###############################################################################
# Test MAX_Z_ERROR_OVERVIEW effect while creating overviews
# on a newly created dataset


@pytest.mark.parametrize(
    "external_ovr,compression",
    [
        (True, "LERC_ZSTD"),
        (False, "LERC_ZSTD"),
        (True, "LERC_DEFLATE"),
        (False, "LERC_DEFLATE"),
    ],
)
def test_tiff_write_lerc_overview(external_ovr, compression):
    md = gdaltest.tiff_drv.GetMetadata()
    if compression not in md["DMD_CREATIONOPTIONLIST"]:
        pytest.skip()

    checksums = {}
    errors = [0, 10, 10]
    src_ds = gdal.Open("../gdrivers/data/utm.tif")
    for i, error in enumerate(errors):
        fname = "/vsimem/test_tiff_write_lerc_overview_%d" % i

        ds = gdal.GetDriverByName("GTiff").Create(
            fname,
            256,
            256,
            1,
            options=["COMPRESS=" + compression, "MAX_Z_ERROR=%f" % error],
        )
        data = src_ds.GetRasterBand(1).ReadRaster(0, 0, 512, 512, 256, 256)
        ds.GetRasterBand(1).WriteRaster(0, 0, 256, 256, data)
        if i == 2:
            error = 30
        options = {}
        if external_ovr:
            ds = None
            ds = gdal.Open(fname)
            options["COMPRESS_OVERVIEW"] = compression
        options["MAX_Z_ERROR_OVERVIEW"] = "%d" % error
        with gdaltest.config_options(options):
            ds.BuildOverviews("AVERAGE", overviewlist=[2, 4])

        ds = None

        ds = gdal.Open(fname)
        assert (
            ds.GetRasterBand(1)
            .GetOverview(0)
            .GetDataset()
            .GetMetadataItem("COMPRESSION", "IMAGE_STRUCTURE")
            == compression
        )
        checksums[i] = [
            ds.GetRasterBand(1).Checksum(),
            ds.GetRasterBand(1).GetOverview(0).Checksum(),
            ds.GetRasterBand(1).GetOverview(1).Checksum(),
        ]
        ds = None
        gdaltest.tiff_drv.Delete(fname)

    assert checksums[0][0] != checksums[1][0]
    assert checksums[0][1] != checksums[1][1]
    assert checksums[0][2] != checksums[1][2]

    assert checksums[0][0] != checksums[2][0]
    assert checksums[0][1] != checksums[2][1]
    assert checksums[0][2] != checksums[2][2]

    assert checksums[1][0] == checksums[2][0]
    assert checksums[1][1] != checksums[2][1]
    assert checksums[1][2] != checksums[2][2]


###############################################################################
# Test MAX_Z_ERROR_OVERVIEW creation option


@pytest.mark.parametrize("z_error", [0, 1.5])
@pytest.mark.require_creation_option("GTiff", "LERC")
def test_tiff_write_lerc_max_z_error_overview(tmp_vsimem, z_error):

    src_ds = gdal.Open("../gdrivers/data/utm.tif")
    data = src_ds.GetRasterBand(1).ReadRaster(0, 0, 512, 512, 256, 256)

    fname = str(tmp_vsimem / "test_tiff_write_lerc_max_z_error_overview.tif")
    options = ["COMPRESS=LERC"]
    if z_error > 0:
        options.append(f"MAX_Z_ERROR={z_error}")
    ds = gdal.GetDriverByName("GTiff").Create(
        fname,
        256,
        256,
        1,
        options=options,
    )
    ds.GetRasterBand(1).WriteRaster(0, 0, 256, 256, data)
    ds.BuildOverviews("AVERAGE", overviewlist=[2])
    ds = None
    ds = gdal.Open(fname)
    assert float(ds.GetMetadataItem("MAX_Z_ERROR", "_DEBUG_")) == z_error
    assert float(ds.GetMetadataItem("MAX_Z_ERROR_OVERVIEW", "_DEBUG_")) == z_error
    assert (
        float(
            ds.GetRasterBand(1)
            .GetOverview(0)
            .GetDataset()
            .GetMetadataItem("MAX_Z_ERROR", "_DEBUG_")
        )
        == z_error
    )
    ref_cs_main = ds.GetRasterBand(1).Checksum()
    ref_cs_ovr = ds.GetRasterBand(1).GetOverview(0).Checksum()
    ds = None

    fname = str(tmp_vsimem / "test_tiff_write_lerc_max_z_error_overview.tif")
    options = ["COMPRESS=LERC", "MAX_Z_ERROR_OVERVIEW=2.5"]
    if z_error > 0:
        options.append(f"MAX_Z_ERROR={z_error}")
    ds = gdal.GetDriverByName("GTiff").Create(
        fname,
        256,
        256,
        1,
        options=options,
    )
    ds.GetRasterBand(1).WriteRaster(0, 0, 256, 256, data)
    ds.BuildOverviews("AVERAGE", overviewlist=[2])
    ds = None
    ds = gdal.Open(fname)
    assert float(ds.GetMetadataItem("MAX_Z_ERROR", "_DEBUG_")) == z_error
    assert float(ds.GetMetadataItem("MAX_Z_ERROR_OVERVIEW", "_DEBUG_")) == 2.5
    assert (
        float(
            ds.GetRasterBand(1)
            .GetOverview(0)
            .GetDataset()
            .GetMetadataItem("MAX_Z_ERROR", "_DEBUG_")
        )
        == 2.5
    )
    got_cs_main = ds.GetRasterBand(1).Checksum()
    got_cs_ovr = ds.GetRasterBand(1).GetOverview(0).Checksum()
    ds = None

    assert got_cs_main == ref_cs_main
    assert got_cs_ovr != ref_cs_ovr


###############################################################################
# Test ZLEVEL_OVERVIEW effect while creating overviews
# on a newly created dataset


@pytest.mark.parametrize("external_ovr", [True, False])
@pytest.mark.require_creation_option("GTiff", "LERC_DEFLATE")
def test_tiff_write_lerc_zlevel(external_ovr):

    filesize = {}
    src_ds = gdal.Open("../gdrivers/data/utm.tif")
    for level in (1, 9):
        fname = "/vsimem/test_tiff_write_lerc_zlevel_%d" % level
        ds = gdal.GetDriverByName("GTiff").Create(
            fname, 256, 256, 1, options=["COMPRESS=LERC_DEFLATE"]
        )
        data = src_ds.GetRasterBand(1).ReadRaster(0, 0, 512, 512, 256, 256)
        ds.GetRasterBand(1).WriteRaster(0, 0, 256, 256, data)
        options = {"MAX_Z_ERROR_OVERVIEW": "10"}
        if external_ovr:
            ds = None
            ds = gdal.Open(fname)
            options["COMPRESS_OVERVIEW"] = "LERC_DEFLATE"
        options["ZLEVEL_OVERVIEW"] = "%d" % level
        with gdaltest.config_options(options):
            ds.BuildOverviews("AVERAGE", overviewlist=[2, 4])
        ds = None

        if external_ovr:
            filesize[level] = gdal.VSIStatL(fname + ".ovr").size
        else:
            filesize[level] = gdal.VSIStatL(fname).size
        gdaltest.tiff_drv.Delete(fname)

    assert filesize[1] > filesize[9]


###############################################################################
# Test ZSTD_LEVEL_OVERVIEW effect while creating overviews
# on a newly created dataset


@pytest.mark.parametrize("external_ovr", [True, False])
@pytest.mark.require_creation_option("GTiff", "LERC_ZSTD")
def test_tiff_write_lerc_zstd_level(external_ovr):

    filesize = {}
    src_ds = gdal.Open("../gdrivers/data/utm.tif")
    for level in (1, 22):
        fname = "/vsimem/test_tiff_write_lerc_zstd_level_%d" % level
        ds = gdal.GetDriverByName("GTiff").Create(
            fname, 256, 256, 1, options=["COMPRESS=LERC_ZSTD"]
        )
        data = src_ds.GetRasterBand(1).ReadRaster(0, 0, 512, 512, 256, 256)
        ds.GetRasterBand(1).WriteRaster(0, 0, 256, 256, data)
        options = {"MAX_Z_ERROR_OVERVIEW": "10"}
        if external_ovr:
            ds = None
            ds = gdal.Open(fname)
            options["COMPRESS_OVERVIEW"] = "LERC_ZSTD"
        options["ZSTD_LEVEL_OVERVIEW"] = "%d" % level
        with gdaltest.config_options(options):
            ds.BuildOverviews("AVERAGE", overviewlist=[2, 4])
        ds = None

        if external_ovr:
            filesize[level] = gdal.VSIStatL(fname + ".ovr").size
        else:
            filesize[level] = gdal.VSIStatL(fname).size
        gdaltest.tiff_drv.Delete(fname)

    assert filesize[1] > filesize[22]


###############################################################################
# Test set XMP metadata


def test_tiff_write_181_xmp():

    new_ds = gdaltest.tiff_drv.Create("tmp/test_181.tif", 1, 1)

    xmp_ds = gdal.Open("../gdrivers/data/gtiff/byte_with_xmp.tif")
    xmp = xmp_ds.GetMetadata("xml:XMP")
    xmp_ds = None
    assert "W5M0MpCehiHzreSzNTczkc9d" in xmp[0], "Wrong input file without XMP"

    new_ds.SetMetadata(xmp, "xml:XMP")
    new_ds = None

    # hopefully it's closed now!

    new_ds = gdal.Open("tmp/test_181.tif")
    read_xmp = new_ds.GetMetadata("xml:XMP")
    assert (
        read_xmp and "W5M0MpCehiHzreSzNTczkc9d" in read_xmp[0]
    ), "No XMP data written in output file"
    new_ds = None

    gdaltest.tiff_drv.Delete("tmp/test_181.tif")


def test_tiff_write_181_xmp_copy():

    src_ds = gdal.Open("../gdrivers/data/gtiff/byte_with_xmp.tif")

    filename = "tmp/test_181_copy.tif"
    new_ds = gdaltest.tiff_drv.CreateCopy(filename, src_ds)
    assert new_ds is not None
    src_ds = None

    new_ds = None
    new_ds = gdal.Open(filename)

    assert (
        int(new_ds.GetRasterBand(1).GetMetadataItem("IFD_OFFSET", "TIFF")) == 8
    ), "TIFF directory not at the beginning"

    xmp = new_ds.GetMetadata("xml:XMP")
    new_ds = None
    assert "W5M0MpCehiHzreSzNTczkc9d" in xmp[0], "Wrong input file without XMP"

    gdaltest.tiff_drv.Delete(filename)


###############################################################################
# Test delete XMP from a dataset


def test_tiff_write_182_xmp_delete():

    shutil.copyfile("../gdrivers/data/gtiff/byte_with_xmp.tif", "tmp/test_182.tif")

    chg_ds = gdal.Open("tmp/test_182.tif", gdal.GA_Update)
    read_xmp = chg_ds.GetMetadata("xml:XMP")
    assert (
        read_xmp and "W5M0MpCehiHzreSzNTczkc9d" in read_xmp[0]
    ), "No XMP data written in output file"
    chg_ds.SetMetadata(None, "xml:XMP")
    chg_ds = None

    again_ds = gdal.Open("tmp/test_182.tif")
    read_xmp = again_ds.GetMetadata("xml:XMP")
    assert not read_xmp, "XMP data not removed"
    again_ds = None

    gdaltest.tiff_drv.Delete("tmp/test_182.tif")


###############################################################################


def test_tiff_write_183_createcopy_append_subdataset():

    tmpfilename = "/vsimem/test_tiff_write_183_createcopy_append_subdataset.tif"
    gdal.Translate(tmpfilename, "data/byte.tif")
    gdal.Translate(
        tmpfilename, "data/utmsmall.tif", creationOptions=["APPEND_SUBDATASET=YES"]
    )

    ds = gdal.Open(tmpfilename)
    assert ds.GetRasterBand(1).Checksum() == 4672

    ds = gdal.Open("GTIFF_DIR:2:" + tmpfilename)
    assert ds.GetRasterBand(1).Checksum() == 50054

    ds = None
    gdal.Unlink(tmpfilename)


###############################################################################


def test_tiff_write_184_create_append_subdataset():

    tmpfilename = "/vsimem/test_tiff_write_184_create_append_subdataset.tif"
    gdal.Translate(tmpfilename, "data/byte.tif")
    ds = gdal.GetDriverByName("GTiff").Create(
        tmpfilename, 1, 1, options=["APPEND_SUBDATASET=YES"]
    )
    ds.GetRasterBand(1).Fill(255)
    ds = None

    ds = gdal.Open(tmpfilename)
    assert ds.GetRasterBand(1).Checksum() == 4672

    ds = gdal.Open("GTIFF_DIR:2:" + tmpfilename)
    assert ds.GetRasterBand(1).Checksum() == 3

    ds = None
    gdal.Unlink(tmpfilename)


###############################################################################
# Test LERC compression with Create() and BuildOverviews()
# Fixes https://github.com/OSGeo/gdal/issues/1257


@pytest.mark.require_creation_option("GTiff", "LERC")
def test_tiff_write_185_lerc_create_and_overview():

    filename = "/vsimem/test_tiff_write_185_lerc_create_and_overview.tif"
    ds = gdaltest.tiff_drv.Create(filename, 20, 20, options=["COMPRESS=LERC_DEFLATE"])
    src_ds = gdal.Open("data/byte.tif")
    ds.WriteRaster(0, 0, 20, 20, src_ds.ReadRaster())
    gdal.ErrorReset()
    ds.BuildOverviews("NEAR", [2])
    assert gdal.GetLastErrorMsg() == ""
    ds = None
    ds = gdal.Open(filename)
    cs = ds.GetRasterBand(1).Checksum()
    cs_ovr = ds.GetRasterBand(1).GetOverview(0).Checksum()
    gdal.Unlink(filename)
    assert (cs, cs_ovr) == (4672, 1087)

    filename2 = "/vsimem/test_tiff_write_185_lerc_create_and_overview_copy.tif"
    gdaltest.tiff_drv.CreateCopy(
        filename2, ds, options=["COMPRESS=LERC_DEFLATE", "COPY_SRC_OVERVIEWS=YES"]
    )
    assert gdal.GetLastErrorMsg() == ""
    ds = gdal.Open(filename2)
    cs = ds.GetRasterBand(1).Checksum()
    cs_ovr = ds.GetRasterBand(1).GetOverview(0).Checksum()
    gdal.Unlink(filename2)
    assert (cs, cs_ovr) == (4672, 1087)


###############################################################################


def check_libtiff_internal_or_at_least(expected_maj, expected_min, expected_micro):

    md = gdal.GetDriverByName("GTiff").GetMetadata()
    if md["LIBTIFF"] == "INTERNAL":
        return True
    if md["LIBTIFF"].startswith("LIBTIFF, Version "):
        version = md["LIBTIFF"][len("LIBTIFF, Version ") :]
        version = version[0 : version.find("\n")]
        got_maj, got_min, got_micro = version.split(".")
        got_maj = int(got_maj)
        got_min = int(got_min)
        got_micro = int(got_micro)
        if got_maj > expected_maj:
            return True
        if got_maj < expected_maj:
            return False
        if got_min > expected_min:
            return True
        if got_min < expected_min:
            return False
        return got_micro >= expected_micro
    return False


###############################################################################
# Test writing a deflate compressed file with a uncompressed strip larger than 4 GB
#


@pytest.mark.slow()
def test_tiff_write_deflate_4GB():

    if not check_libtiff_internal_or_at_least(4, 0, 11):
        pytest.skip()

    ref_ds = gdal.GetDriverByName("MEM").Create("", 20, 20)
    ref_ds.GetRasterBand(1).Fill(127)

    gdal.Translate(
        "/vsimem/out.tif",
        ref_ds,
        options="-co TILED=YES -co COMPRESS=DEFLATE -co BLOCKXSIZE=50000 -co BLOCKYSIZE=86000 -outsize 50000 86000",
    )

    ds = gdal.Open("/vsimem/out.tif")
    data = ds.ReadRaster(
        0, 0, ds.RasterXSize, ds.RasterYSize, buf_xsize=20, buf_ysize=20
    )
    assert data == ref_ds.ReadRaster()
    ds = None

    gdal.Unlink("/vsimem/out.tif")


###############################################################################
# Test rewriting a LZW strip/tile that is very close to 8 KB with larger data


def test_tiff_write_rewrite_lzw_strip():

    if not check_libtiff_internal_or_at_least(4, 0, 11):
        pytest.skip()

    src_data = open("data/bug_gh_1439_to_be_updated_lzw.tif", "rb").read()
    tmpfilename = "/vsimem/out.tif"
    gdal.FileFromMemBuffer(tmpfilename, src_data)
    ds = gdal.Open(tmpfilename, gdal.GA_Update)
    src_ds = gdal.Open("data/bug_gh_1439_update_lzw.tif")
    ds.WriteRaster(0, 0, 4096, 1, src_ds.ReadRaster())
    ds = None
    ds = gdal.Open(tmpfilename)
    gdal.ErrorReset()
    assert ds.GetRasterBand(1).ReadRaster(0, 1, 4096, 1)
    assert gdal.GetLastErrorMsg() == ""

    gdal.Unlink(tmpfilename)


###############################################################################
# Test COPY_SRC_OVERVIEWS on a configuration with overviews, mask, but no
# overview on the mask


def test_tiff_write_overviews_mask_no_ovr_on_mask():

    tmpfile = "/vsimem/test_tiff_write_overviews_mask_no_ovr_on_mask.tif"
    ds = gdaltest.tiff_drv.Create(tmpfile, 100, 100)
    ds.GetRasterBand(1).Fill(255)
    ds.CreateMaskBand(gdal.GMF_PER_DATASET)

    ds = gdal.Open(tmpfile)
    gdal.ErrorReset()
    with gdal.quiet_errors():
        ds.BuildOverviews("NEAR", overviewlist=[2])
    assert (
        "Building external overviews whereas there is an internal mask is not fully supported. The overviews of the non-mask bands will be created, but not the overviews of the mask band."
        in gdal.GetLastErrorMsg()
    )
    # No overview on the mask
    assert ds.GetRasterBand(1).GetOverview(0).GetMaskFlags() == gdal.GMF_ALL_VALID
    ds = None

    tmpfile2 = "/vsimem/test_tiff_write_overviews_mask_no_ovr_on_mask_copy.tif"
    src_ds = gdal.Open(tmpfile)
    gdal.ErrorReset()
    with gdal.quiet_errors():
        ds = gdaltest.tiff_drv.CreateCopy(
            tmpfile2, src_ds, options=["COPY_SRC_OVERVIEWS=YES"]
        )
    assert (
        "Source dataset has a mask band on full resolution, overviews on the regular bands, but lacks overviews on the mask band."
        in gdal.GetLastErrorMsg()
    )
    assert ds
    ds = None
    src_ds = None

    ds = gdal.Open(tmpfile)
    assert ds.GetRasterBand(1).GetMaskFlags() == gdal.GMF_PER_DATASET
    # No overview on the mask
    assert ds.GetRasterBand(1).GetOverview(0).GetMaskFlags() == gdal.GMF_ALL_VALID
    ds = None

    gdaltest.tiff_drv.Delete(tmpfile)
    gdaltest.tiff_drv.Delete(tmpfile2)


###############################################################################
# Test that -co PHOTOMETRIC=YCBCR -co COMPRESS=JPEG does not create a TIFFTAG_GDAL_METADATA


@pytest.mark.require_creation_option("GTiff", "JPEG")
def test_tiff_write_no_gdal_metadata_tag_for_ycbcr_jpeg():

    tmpfile = "/vsimem/test_tiff_write_no_gdal_metadata_tag_for_ycbcr_jpeg.tif"
    assert gdaltest.tiff_drv.Create(
        tmpfile,
        16,
        16,
        3,
        gdal.GDT_Byte,
        options=["PHOTOMETRIC=YCBCR", "COMPRESS=JPEG"],
    )
    statBuf = gdal.VSIStatL(
        tmpfile + ".aux.xml",
        gdal.VSI_STAT_EXISTS_FLAG | gdal.VSI_STAT_NATURE_FLAG | gdal.VSI_STAT_SIZE_FLAG,
    )
    assert statBuf is None, "did not expect PAM file"

    ds = gdal.Open(tmpfile)
    assert (
        ds.GetMetadataItem("TIFFTAG_GDAL_METADATA", "_DEBUG_") is None
    ), "did not expect TIFFTAG_GDAL_METADATA tag"
    assert ds.GetRasterBand(1).GetColorInterpretation() == gdal.GCI_RedBand

    tmpfile2 = tmpfile + "2"
    assert gdaltest.tiff_drv.CreateCopy(
        tmpfile2, ds, options=["PHOTOMETRIC=YCBCR", "COMPRESS=JPEG"]
    )
    ds = None

    ds = gdal.Open(tmpfile2)
    assert (
        ds.GetMetadataItem("TIFFTAG_GDAL_METADATA", "_DEBUG_") is None
    ), "did not expect TIFFTAG_GDAL_METADATA tag"
    assert ds.GetRasterBand(1).GetColorInterpretation() == gdal.GCI_RedBand
    ds = None

    gdaltest.tiff_drv.Delete(tmpfile)
    gdaltest.tiff_drv.Delete(tmpfile2)


###############################################################################
# Test that repated flushing after SetGeoTransform() does not grow file size
# indefinitely


def test_tiff_write_setgeotransform_flush():

    tmpfile = "/vsimem/test_tiff_write_setgeotransform_flush.tif"
    gdal.GetDriverByName("GTiff").Create(tmpfile, 1, 1)
    ds = gdal.Open(tmpfile, gdal.GA_Update)
    ds.SetGeoTransform([2, 0, 1, 49, 0, -1])
    for i in range(10):
        ds.FlushCache()
    ds = None

    assert gdal.VSIStatL(tmpfile).size < 1000

    gdaltest.tiff_drv.Delete(tmpfile)


###############################################################################
# Test that compression parameters are taken into account in Create() mode


def test_tiff_write_compression_create_and_createcopy():

    md = gdaltest.tiff_drv.GetMetadata()
    tests = []

    if "DEFLATE" in md["DMD_CREATIONOPTIONLIST"]:
        tests.append(
            (["COMPRESS=DEFLATE", "ZLEVEL=1"], ["COMPRESS=DEFLATE", "ZLEVEL=9"])
        )

    if "LZMA" in md["DMD_CREATIONOPTIONLIST"]:
        tests.append(
            (["COMPRESS=LZMA", "LZMA_PRESET=1"], ["COMPRESS=LZMA", "LZMA_PRESET=9"])
        )

    if "JPEG" in md["DMD_CREATIONOPTIONLIST"]:
        tests.append(
            (["COMPRESS=JPEG", "JPEG_QUALITY=95"], ["COMPRESS=JPEG", "JPEG_QUALITY=50"])
        )

    if "ZSTD" in md["DMD_CREATIONOPTIONLIST"]:
        tests.append(
            (["COMPRESS=ZSTD", "ZSTD_LEVEL=1"], ["COMPRESS=ZSTD", "ZSTD_LEVEL=9"])
        )

    # FIXME: this test randomly fails, especially on Windows, but also on Linux,
    # for a unknown reason. Nothing suspicious with Valgrind however
    # if 'LERC_DEFLATE' in md['DMD_CREATIONOPTIONLIST']:
    #   tests.append((['COMPRESS=LERC_DEFLATE', 'ZLEVEL=1'],['COMPRESS=LERC_DEFLATE', 'ZLEVEL=9']))

    if "WEBP" in md["DMD_CREATIONOPTIONLIST"]:
        tests.append(
            (["COMPRESS=WEBP", "WEBP_LEVEL=95"], ["COMPRESS=WEBP", "WEBP_LEVEL=15"])
        )

    if "JXL" in md["DMD_CREATIONOPTIONLIST"]:
        tests.append(
            (["COMPRESS=JXL", "JXL_LOSSLESS=YES"], ["COMPRESS=JXL", "JXL_LOSSLESS=NO"])
        )
        tests.append(
            (
                ["COMPRESS=JXL", "JXL_LOSSLESS=YES", "JXL_EFFORT=3"],
                ["COMPRESS=JXL", "JXL_LOSSLESS=YES", "JXL_EFFORT=9"],
            )
        )
        tests.append(
            (
                ["COMPRESS=JXL", "JXL_LOSSLESS=NO", "JXL_DISTANCE=0.1"],
                ["COMPRESS=JXL", "JXL_LOSSLESS=NO", "JXL_DISTANCE=3"],
            )
        )

    new_tests = []
    for (before, after) in tests:
        new_tests.append((before, after))
        new_tests.append(
            (
                before + ["COPY_SRC_OVERVIEWS=YES", "TILED=YES", "NUM_THREADS=2"],
                after + ["COPY_SRC_OVERVIEWS=YES", "TILED=YES", "NUM_THREADS=2"],
            )
        )
    tests = new_tests

    tmpfile = "/vsimem/test_tiff_write_compression_create.tif"

    src_ds = gdal.Open("data/rgbsmall.tif")
    data = src_ds.ReadRaster()
    for (before, after) in tests:
        ds = gdaltest.tiff_drv.Create(
            tmpfile,
            src_ds.RasterXSize,
            src_ds.RasterYSize,
            src_ds.RasterCount,
            options=before,
        )
        ds.WriteRaster(0, 0, src_ds.RasterXSize, src_ds.RasterYSize, data)
        ds = None
        size_before = gdal.VSIStatL(tmpfile).size
        ds = gdaltest.tiff_drv.Create(
            tmpfile,
            src_ds.RasterXSize,
            src_ds.RasterYSize,
            src_ds.RasterCount,
            options=after,
        )
        ds.WriteRaster(0, 0, src_ds.RasterXSize, src_ds.RasterYSize, data)
        ds = None
        size_after = gdal.VSIStatL(tmpfile).size
        assert size_after < size_before, (before, after, size_before, size_after)
        print(before, after, size_before, size_after)

        gdaltest.tiff_drv.CreateCopy(tmpfile, src_ds, options=before)
        size_before = gdal.VSIStatL(tmpfile).size
        gdaltest.tiff_drv.CreateCopy(tmpfile, src_ds, options=after)
        size_after = gdal.VSIStatL(tmpfile).size
        assert size_after < size_before, (before, after, size_before, size_after)

    gdaltest.tiff_drv.Delete(tmpfile)


###############################################################################
# Attempt at creating a file with more tile arrays larger than 2 GB


def test_tiff_write_too_many_tiles():

    src_ds = gdal.Open(
        '<VRTDataset rasterXSize="40000000" rasterYSize="40000000"><VRTRasterBand dataType="Byte" band="1"/></VRTDataset>'
    )
    with gdal.quiet_errors():
        assert not gdaltest.tiff_drv.CreateCopy(
            "/vsimem/tmp.tif", src_ds, options=["TILED=YES"]
        )
    assert "File too large regarding tile size" in gdal.GetLastErrorMsg()

    with gdaltest.tempfile(
        "/vsimem/test_tiff_write_too_many_tiles.vrt",
        '<VRTDataset rasterXSize="40000000" rasterYSize="40000000"><VRTRasterBand dataType="Byte" band="1"/></VRTDataset>',
    ):
        src_ds = gdal.Open("/vsimem/test_tiff_write_too_many_tiles.vrt")
        gdal.ErrorReset()
        with gdaltest.config_option("GDAL_TIFF_OVR_BLOCKSIZE", "128"):
            with gdal.quiet_errors():
                src_ds.BuildOverviews("NEAR", [2])
        assert "File too large regarding tile size" in gdal.GetLastErrorMsg()


###############################################################################
#


@pytest.mark.require_creation_option("GTiff", "JPEG")
def test_tiff_write_jpeg_incompatible_of_paletted():

    src_ds = gdal.Open("data/test_average_palette.tif")
    with gdal.quiet_errors():
        assert not gdaltest.tiff_drv.CreateCopy(
            "/vsimem/tmp.tif", src_ds, options=["COMPRESS=JPEG"]
        )
    gdal.Unlink("/vsimem/tmp.tif")


###############################################################################
# Test blocksize overriding while creating (internal) overviews
# on a newly created dataset


@pytest.mark.parametrize("blockSize,numThreads", [[64, None], [256, 8]])
def test_tiff_write_internal_ovr_blocksize(blockSize, numThreads):

    src_ds = gdal.Open("../gdrivers/data/utm.tif")
    fname = "tmp/tiff_write_internal_ovr_bs%d.tif" % blockSize

    ds = gdal.GetDriverByName("GTiff").Create(
        fname,
        1024,
        1024,
        1,
        options=["TILED=YES", "COMPRESS=LZW", "BLOCKXSIZE=512", "BLOCKYSIZE=512"],
    )

    data = src_ds.GetRasterBand(1).ReadRaster(0, 0, 512, 512, 1024, 1024)
    ds.GetRasterBand(1).WriteRaster(0, 0, 1024, 1024, data)
    opts = {"GDAL_TIFF_OVR_BLOCKSIZE": "%d" % blockSize}
    if numThreads:
        opts["GDAL_NUM_THREADS"] = str(numThreads)
    with gdaltest.config_options(opts):
        ds.BuildOverviews("AVERAGE", overviewlist=[2])

    src_ds = None
    ds = None

    ds = gdal.Open(fname)
    (bsx, bsy) = ds.GetRasterBand(1).GetOverview(0).GetBlockSize()
    assert bsx == blockSize
    assert bsy == blockSize
    ds = None
    gdaltest.tiff_drv.Delete(fname)


###############################################################################
# Test blocksize propagation while creating (internal) overviews
# on a newly created dataset


@pytest.mark.parametrize("blockSize,numThreads", [[64, None], [256, 8]])
def test_tiff_write_internal_ovr_default_blocksize(blockSize, numThreads):

    src_ds = gdal.Open("../gdrivers/data/utm.tif")
    fname = "tmp/tiff_write_internal_ovr_default_bs%d.tif" % blockSize

    ds = gdal.GetDriverByName("GTiff").Create(
        fname,
        1024,
        1024,
        1,
        options=[
            "TILED=YES",
            "COMPRESS=LZW",
            "BLOCKXSIZE=%d" % blockSize,
            "BLOCKYSIZE=%d" % blockSize,
        ],
    )

    data = src_ds.GetRasterBand(1).ReadRaster(0, 0, 512, 512, 1024, 1024)
    ds.GetRasterBand(1).WriteRaster(0, 0, 1024, 1024, data)
    opts = {}
    if numThreads:
        opts["GDAL_NUM_THREADS"] = str(numThreads)
    with gdaltest.config_options(opts):
        ds.BuildOverviews("AVERAGE", overviewlist=[2])

    src_ds = None
    ds = None

    ds = gdal.Open(fname)
    (bsx, bsy) = ds.GetRasterBand(1).GetOverview(0).GetBlockSize()
    assert bsx == blockSize
    assert bsy == blockSize
    ds = None
    gdaltest.tiff_drv.Delete(fname)


###############################################################################
# Test LERC compression with Float32/Float64


@pytest.mark.parametrize(
    "gdalDataType,structType", [[gdal.GDT_Float32, "f"], [gdal.GDT_Float64, "d"]]
)
@pytest.mark.require_creation_option("GTiff", "LERC")
def test_tiff_write_lerc_float(gdalDataType, structType):

    src_ds = gdal.GetDriverByName("MEM").Create("", 2, 1, 1, gdalDataType)
    src_ds.GetRasterBand(1).WriteRaster(
        0, 0, 2, 1, struct.pack(structType * 2, 0.5, 1.5)
    )
    filename = "/vsimem/test.tif"
    gdaltest.tiff_drv.CreateCopy(filename, src_ds, options=["COMPRESS=LERC"])
    ds = gdal.Open(filename)
    assert struct.unpack(structType * 2, ds.ReadRaster()) == (0.5, 1.5)
    ds = None
    gdal.Unlink(filename)


###############################################################################


def lerc_version_at_least_3():

    LERC_VERSION_MAJOR = gdal.GetDriverByName("GTiff").GetMetadataItem(
        "LERC_VERSION_MAJOR", "LERC"
    )
    return LERC_VERSION_MAJOR and int(LERC_VERSION_MAJOR) >= 3


###############################################################################
# Test LERC compression with Float32/Float64 and nan


@pytest.mark.parametrize(
    "gdalDataType,structType",
    [
        (gdal.GDT_Float32, "f"),
        (gdal.GDT_Float64, "d"),
    ],
)
@pytest.mark.parametrize("repeat", [1, 100])
@pytest.mark.parametrize("interleave", ["PIXEL", "BAND"])
@pytest.mark.parametrize(
    "values",
    [
        [(0.5,)],
        [(0.5, 1.5)],
        [(float("nan"),)],
        [(0.5, float("nan"))],
        [(0.5, 0.75, 1), (1.5, 1.75, 2)],
        [(float("nan"),), (float("nan"),)],
        [(0.5, float("nan"), 1), (1.5, float("nan"), 2)],
        [
            (0.5, float("nan"), 1),
            (1.5, 1.75, float("nan")),
        ],  # This one requires liblerc >= 3.0 since we need multiple masks
    ],
)
@pytest.mark.require_creation_option("GTiff", "LERC")
def test_tiff_write_lerc_float_with_nan(
    gdalDataType, structType, values, repeat, interleave
):

    bandCount = len(values)

    if (
        bandCount == 2
        and True in [math.isnan(x) for x in values[0]]
        and not (
            check_libtiff_internal_or_at_least(4, 6, 1) and lerc_version_at_least_3()
        )
    ):
        pytest.skip(
            "multiple band with NaN in same strile only supported if libtiff >= 4.6.1 and liblerc >= 3.0"
        )

    width = len(values[0] * repeat)
    src_ds = gdal.GetDriverByName("MEM").Create("", width, 1, bandCount, gdalDataType)
    for i in range(bandCount):
        src_ds.GetRasterBand(i + 1).WriteRaster(
            0, 0, width, 1, array.array(structType, values[i] * repeat).tobytes()
        )
    filename = "/vsimem/test.tif"
    gdaltest.tiff_drv.CreateCopy(
        filename, src_ds, options=["COMPRESS=LERC", "INTERLEAVE=" + interleave]
    )
    ds = gdal.Open(filename)
    for i in range(bandCount):
        got_data = struct.unpack(
            structType * width, ds.GetRasterBand(i + 1).ReadRaster()
        )
        for j in range(width):
            if math.isnan((values[i] * repeat)[j]):
                assert math.isnan(got_data[j])
            else:
                assert got_data[j] == (values[i] * repeat)[j]
    ds = None
    gdal.Unlink(filename)


###############################################################################


@pytest.mark.parametrize("tiled", [False, True])
@pytest.mark.require_creation_option("GTiff", "LERC")
def test_tiff_write_lerc_float_with_nan_random(tmp_vsimem, tiled):

    """Stress test the floating-point LERC encoder, with several masks per strile"""

    width = 128
    height = 128
    bands = 100
    src_ds = gdal.GetDriverByName("MEM").Create(
        "", width, height, bands, gdal.GDT_Float32
    )

    import random

    band_values = []
    for i in range(bands):
        # Generate random float values, but with at least 1/3 of nan in them in
        # some bands and 2/3 in others
        values = [int(random.random() * ((1 << 32) - 1)) for _ in range(width * height)]
        values = array.array("I", values).tobytes()
        values = [
            x if random.random() > (0.33 if (i % 2) == 0 else 0.67) else float("nan")
            for x in struct.unpack("f" * (width * height), values)
        ]
        band_values.append(values)
        values = array.array("f", values).tobytes()
        src_ds.GetRasterBand(i + 1).WriteRaster(0, 0, width, height, values)

    filename = str(tmp_vsimem / "test_tiff_write_lerc_float_with_nan_random.tif")
    if tiled:
        options = ["COMPRESS=LERC", "TILED=YES", "BLOCKXSIZE=96", "BLOCKYSIZE=112"]
    else:
        options = ["COMPRESS=LERC", "BLOCKYSIZE=112"]
    gdaltest.tiff_drv.CreateCopy(filename, src_ds, options=options)

    if not (check_libtiff_internal_or_at_least(4, 6, 1) and lerc_version_at_least_3()):
        pytest.skip(
            "multiple band with NaN in same strile only supported if libtiff >= 4.6.1 and liblerc >= 3.0"
        )

    ds = gdal.Open(filename)
    for i in range(bands):
        got_data = struct.unpack(
            "f" * (width * height), ds.GetRasterBand(i + 1).ReadRaster()
        )
        for j in range(width * height):
            if math.isnan(band_values[i][j]):
                assert math.isnan(got_data[j])
            else:
                assert got_data[j] == band_values[i][j]
    ds = None


###############################################################################
# Test JXL compression


@pytest.mark.parametrize("lossless", ["YES", "NO", None])
@pytest.mark.parametrize("writeImageStructureMetadata", [True, False])
@pytest.mark.require_creation_option("GTiff", "JXL")
def test_tiff_write_jpegxl_byte_single_band(lossless, writeImageStructureMetadata):

    outfile = "/vsimem/test_tiff_write_jpegxl_byte_single_band.tif"
    options = ["COMPRESS=JXL"]
    if lossless:
        options += ["JXL_LOSSLESS=" + lossless]
    if lossless == "NO":
        options += ["JXL_DISTANCE=0.5", "JXL_EFFORT=4"]
    with gdaltest.config_option(
        "GTIFF_WRITE_IMAGE_STRUCTURE_METADATA",
        "YES" if writeImageStructureMetadata else "NO",
    ):
        gdaltest.tiff_drv.CreateCopy(
            outfile, gdal.Open("data/byte.tif"), options=options
        )
    ds = gdal.Open(outfile)

    if writeImageStructureMetadata:
        assert ds.GetMetadataItem("JXL_LOSSLESS", "_DEBUG_") == (
            "0" if lossless == "NO" else "1"
        )
        if lossless == "NO":
            assert float(ds.GetMetadataItem("JXL_DISTANCE", "_DEBUG_")) == 0.5
            assert ds.GetMetadataItem("JXL_EFFORT", "_DEBUG_") == "4"
    else:
        # Default values
        assert ds.GetMetadataItem("JXL_LOSSLESS", "_DEBUG_") == "1"
        assert float(ds.GetMetadataItem("JXL_DISTANCE", "_DEBUG_")) == 1.0
        assert ds.GetMetadataItem("JXL_EFFORT", "_DEBUG_") == "5"

    if writeImageStructureMetadata:
        assert ds.GetMetadataItem("COMPRESSION_REVERSIBILITY", "IMAGE_STRUCTURE") == (
            "LOSSY" if lossless == "NO" else "LOSSLESS"
        )
    elif gdal.GetDriverByName("JPEGXL") is not None:
        assert ds.GetMetadataItem("COMPRESSION_REVERSIBILITY", "IMAGE_STRUCTURE") == (
            "LOSSY" if lossless == "NO" else "LOSSLESS (possibly)"
        )
    cs = ds.GetRasterBand(1).Checksum()
    if lossless == "NO":
        assert cs != 0 and cs != 4672
    else:
        assert cs == 4672
    ds = None

    ds = gdal.Open(outfile, gdal.GA_Update)
    if writeImageStructureMetadata:
        assert ds.GetMetadataItem("JXL_LOSSLESS", "_DEBUG_") == (
            "0" if lossless == "NO" else "1"
        )
    if writeImageStructureMetadata:
        assert ds.GetMetadataItem("COMPRESSION_REVERSIBILITY", "IMAGE_STRUCTURE") == (
            "LOSSY" if lossless == "NO" else "LOSSLESS"
        )
    elif gdal.GetDriverByName("JPEGXL") is not None:
        assert ds.GetMetadataItem("COMPRESSION_REVERSIBILITY", "IMAGE_STRUCTURE") == (
            "LOSSY" if lossless == "NO" else "LOSSLESS (possibly)"
        )
    ds = None

    gdaltest.tiff_drv.Delete(outfile)


###############################################################################
# Test JXL compression


@pytest.mark.require_creation_option("GTiff", "JXL")
def test_tiff_write_jpegxl_byte_three_band():

    ut = gdaltest.GDALTest("GTiff", "rgbsmall.tif", 1, 21212, options=["COMPRESS=JXL"])
    ut.testCreateCopy()


###############################################################################
# Test JXL compression


@pytest.mark.require_creation_option("GTiff", "JXL")
def test_tiff_write_jpegxl_uint16_single_band():

    ut = gdaltest.GDALTest("GTiff", "uint16.tif", 1, 4672, options=["COMPRESS=JXL"])
    ut.testCreateCopy()


###############################################################################
# Test JXL_ALPHA_DISTANCE option


@pytest.mark.require_creation_option("GTiff", "JXL_ALPHA_DISTANCE")
def test_tiff_write_jpegxl_alpha_distance_zero():

    drv = gdal.GetDriverByName("GTiff")

    src_ds = gdal.Open("data/stefan_full_rgba.tif")
    filename = "/vsimem/test_tiff_write_jpegxl_alpha_distance_zero.tif"
    drv.CreateCopy(
        filename,
        src_ds,
        options=["COMPRESS=JXL", "JXL_LOSSLESS=NO", "JXL_ALPHA_DISTANCE=0"],
    )
    ds = gdal.Open(filename)
    assert float(ds.GetMetadataItem("JXL_ALPHA_DISTANCE", "IMAGE_STRUCTURE")) == 0
    assert ds.GetRasterBand(1).Checksum() != src_ds.GetRasterBand(1).Checksum()
    assert ds.GetRasterBand(4).Checksum() == src_ds.GetRasterBand(4).Checksum()
    ds = None

    gdal.Unlink(filename)


###############################################################################
# Test creating overviews with NaN nodata


def test_tiff_write_overviews_nan_nodata():

    filename = "/vsimem/test_tiff_write_overviews_nan_nodata.tif"
    ds = gdal.GetDriverByName("GTiff").Create(
        filename, 32, 32, 1, gdal.GDT_Float32, options=["TILED=YES", "SPARSE_OK=YES"]
    )
    ds.GetRasterBand(1).SetNoDataValue(float("nan"))
    ds.BuildOverviews("NONE", [2, 4])
    ds = None
    ds = gdal.Open(filename)
    assert ds.GetRasterBand(1).GetOverviewCount() == 2
    ds = None
    gdal.Unlink(filename)


###############################################################################
# Test support for coordinate epoch


def test_tiff_write_coordinate_epoch():

    ds = gdal.GetDriverByName("GTiff").Create(
        "/vsimem/test_tiff_write_coordinate_epoch.tif", 1, 1
    )
    srs = osr.SpatialReference()
    srs.ImportFromEPSG(4326)
    srs.SetCoordinateEpoch(2021.3)
    ds.SetGeoTransform([0, 1, 0, 0, 0, -1])
    ds.SetSpatialRef(srs)
    ds = None

    ds = gdal.Open("/vsimem/test_tiff_write_coordinate_epoch.tif")
    srs = ds.GetSpatialRef()
    assert srs.GetCoordinateEpoch() == 2021.3
    ds = None

    gdal.Unlink("/vsimem/test_tiff_write_coordinate_epoch.tif")


###############################################################################
# Test scenario with multiple IFDs and directory rewriting
# https://github.com/OSGeo/gdal/issues/3746


@pytest.mark.parametrize("reopen", [True, False])
def test_tiff_write_multiple_ifds_directory_rewriting(reopen):

    filename = "/vsimem/out.tif"
    ds = gdal.GetDriverByName("GTiff").Create(
        filename, 32, 32, options=["TILED=YES", "SPARSE_OK=YES"]
    )
    ds.BuildOverviews("NONE", [2])
    if reopen:
        ds = None
        ds = gdal.Open(filename, gdal.GA_Update)

    ds.GetRasterBand(1).GetOverview(0).Fill(2)

    # Rewrite second IFD
    ds.GetRasterBand(1).GetOverview(0).SetNoDataValue(0)
    # Rewrite first IFD
    ds.GetRasterBand(1).SetNoDataValue(3)

    ds = None

    ds = gdal.Open(filename)
    mm = ds.GetRasterBand(1).GetOverview(0).ComputeRasterMinMax()
    ds = None

    gdal.Unlink(filename)
    assert mm == (2, 2)


###############################################################################
# Test SetSpatialRef() on a read-only dataset


def test_tiff_write_setspatialref_read_only():

    filename = "/vsimem/out.tif"
    gdal.GetDriverByName("GTiff").Create(filename, 1, 1)

    ds = gdal.Open(filename)
    srs = osr.SpatialReference()
    srs.ImportFromEPSG(4326)
    assert ds.SetSpatialRef(srs) == gdal.CE_None
    ds = None

    assert gdal.VSIStatL(filename + ".aux.xml") is not None

    ds = gdal.Open(filename)
    got_srs = ds.GetSpatialRef()
    assert got_srs
    assert got_srs.GetAuthorityCode(None) == "4326"
    ds = None

    gdal.GetDriverByName("GTiff").Delete(filename)


###############################################################################
# Test SetSpatialRef() on a read-only dataset, overriding TIFF tags


def test_tiff_write_setspatialref_read_only_override_tifftags():

    filename = "/vsimem/out.tif"
    ds = gdal.GetDriverByName("GTiff").Create(filename, 1, 1)
    srs = osr.SpatialReference()
    srs.ImportFromEPSG(32631)
    assert ds.SetSpatialRef(srs) == gdal.CE_None
    ds = None

    ds = gdal.Open(filename)
    srs = osr.SpatialReference()
    srs.ImportFromEPSG(4326)
    assert ds.SetSpatialRef(srs) == gdal.CE_None
    ds = None

    assert gdal.VSIStatL(filename + ".aux.xml") is not None

    ds = gdal.Open(filename)
    got_srs = ds.GetSpatialRef()
    assert got_srs
    assert got_srs.GetAuthorityCode(None) == "4326"
    ds = None

    ds = gdal.Open(filename, gdal.GA_Update)
    srs = osr.SpatialReference()
    srs.ImportFromEPSG(32632)
    assert ds.SetSpatialRef(srs) == gdal.CE_None
    ds = None

    assert gdal.VSIStatL(filename + ".aux.xml") is None

    ds = gdal.Open(filename)
    got_srs = ds.GetSpatialRef()
    assert got_srs
    assert got_srs.GetAuthorityCode(None) == "32632"
    ds = None

    gdal.GetDriverByName("GTiff").Delete(filename)


###############################################################################
# Test SetGeoTransform() on a read-only dataset


def test_tiff_write_setgeotransform_read_only():

    filename = "/vsimem/out.tif"
    gdal.GetDriverByName("GTiff").Create(filename, 1, 1)

    ds = gdal.Open(filename)
    gt = [2, 1, 0, 49, 0, -1]
    assert ds.SetGeoTransform(gt) == gdal.CE_None
    ds = None

    assert gdal.VSIStatL(filename + ".aux.xml") is not None

    ds = gdal.Open(filename)
    got_gt = [x for x in ds.GetGeoTransform()]
    assert got_gt == gt
    ds = None

    gdal.GetDriverByName("GTiff").Delete(filename)


###############################################################################
# Test SetGeoTransform() on a read-only dataset, overriding TIFF tags


def test_tiff_write_setgeotransform_read_only_override_tifftags():

    filename = "/vsimem/out.tif"
    ds = gdal.GetDriverByName("GTiff").Create(filename, 1, 1)
    assert ds.SetGeoTransform([3, 1, 0, 50, 0, -1]) == gdal.CE_None
    ds = None

    ds = gdal.Open(filename)
    gt = [2, 1, 0, 49, 0, -1]
    assert ds.SetGeoTransform(gt) == gdal.CE_None
    ds = None

    assert gdal.VSIStatL(filename + ".aux.xml") is not None

    ds = gdal.Open(filename)
    got_gt = [x for x in ds.GetGeoTransform()]
    assert got_gt == gt
    ds = None

    ds = gdal.Open(filename, gdal.GA_Update)
    gt = [4, 1, 0, 51, 0, -1]
    assert ds.SetGeoTransform(gt) == gdal.CE_None
    ds = None

    assert gdal.VSIStatL(filename + ".aux.xml") is None

    ds = gdal.Open(filename)
    got_gt = [x for x in ds.GetGeoTransform()]
    assert got_gt == gt
    ds = None

    gdal.GetDriverByName("GTiff").Delete(filename)


###############################################################################
# Test SetGCPs() on a read-only dataset


def test_tiff_write_setgcps_read_only():

    filename = "/vsimem/out.tif"
    gdal.GetDriverByName("GTiff").Create(filename, 1, 1)

    ds = gdal.Open(filename)
    gcps = [gdal.GCP(0, 1, 2, 3, 4)]
    srs = osr.SpatialReference()
    srs.ImportFromEPSG(4326)
    assert ds.SetGCPs(gcps, srs) == gdal.CE_None
    ds = None

    assert gdal.VSIStatL(filename + ".aux.xml") is not None

    ds = gdal.Open(filename)
    got_gcps = ds.GetGCPs()
    assert len(got_gcps) == len(gcps)
    assert got_gcps[0].GCPPixel == gcps[0].GCPPixel
    assert got_gcps[0].GCPLine == gcps[0].GCPLine
    assert got_gcps[0].GCPX == gcps[0].GCPX
    assert got_gcps[0].GCPY == gcps[0].GCPY
    got_srs = ds.GetGCPSpatialRef()
    assert got_srs
    assert got_srs.GetAuthorityCode(None) == "4326"
    ds = None

    gdal.GetDriverByName("GTiff").Delete(filename)


###############################################################################
# Test SetGCPs() on a read-only dataset, overriding TIFF tags


def test_tiff_write_setgcps_read_only_override_tifftags():

    filename = "/vsimem/out.tif"
    ds = gdal.GetDriverByName("GTiff").Create(filename, 1, 1)
    gcps = [gdal.GCP(5, 6, 7, 8, 9)]
    assert ds.SetGCPs(gcps, None) == gdal.CE_None
    ds = None

    ds = gdal.Open(filename)
    gcps = [gdal.GCP(0, 1, 2, 3, 4)]
    srs = osr.SpatialReference()
    srs.ImportFromEPSG(4326)
    assert ds.SetGCPs(gcps, srs) == gdal.CE_None
    ds = None

    assert gdal.VSIStatL(filename + ".aux.xml") is not None

    ds = gdal.Open(filename)
    got_gcps = ds.GetGCPs()
    assert len(got_gcps) == len(gcps)
    assert got_gcps[0].GCPPixel == gcps[0].GCPPixel
    assert got_gcps[0].GCPLine == gcps[0].GCPLine
    assert got_gcps[0].GCPX == gcps[0].GCPX
    assert got_gcps[0].GCPY == gcps[0].GCPY
    got_srs = ds.GetGCPSpatialRef()
    assert got_srs
    assert got_srs.GetAuthorityCode(None) == "4326"
    ds = None

    ds = gdal.Open(filename, gdal.GA_Update)
    gcps = [gdal.GCP(10, 11, 12, 13, 14)]
    assert ds.SetGCPs(gcps, None) == gdal.CE_None
    ds = None

    assert gdal.VSIStatL(filename + ".aux.xml") is None

    ds = gdal.Open(filename)
    got_gcps = ds.GetGCPs()
    assert len(got_gcps) == len(gcps)
    assert got_gcps[0].GCPPixel == gcps[0].GCPPixel
    assert got_gcps[0].GCPLine == gcps[0].GCPLine
    assert got_gcps[0].GCPX == gcps[0].GCPX
    assert got_gcps[0].GCPY == gcps[0].GCPY
    assert ds.GetGCPSpatialRef() is None
    ds = None

    gdal.GetDriverByName("GTiff").Delete(filename)


###############################################################################
# Test SetNoDataValue() and DeleteNoDataValue() on a read-only dataset


def test_tiff_write_nodata_read_only():

    filename = "/vsimem/out.tif"
    gdal.GetDriverByName("GTiff").Create(filename, 1, 1)

    ds = gdal.Open(filename)
    assert ds.GetRasterBand(1).SetNoDataValue(123) == gdal.CE_None
    ds = None

    assert gdal.VSIStatL(filename + ".aux.xml") is not None

    ds = gdal.Open(filename)
    assert ds.GetRasterBand(1).GetNoDataValue() == 123
    assert ds.GetRasterBand(1).DeleteNoDataValue() == gdal.CE_None
    ds = None

    ds = gdal.Open(filename)
    assert ds.GetRasterBand(1).GetNoDataValue() is None
    ds = None

    gdal.GetDriverByName("GTiff").Delete(filename)


###############################################################################
# Test SetNoDataValue() on a read-only dataset, overriding TIFF tags


def test_tiff_write_nodata_read_only_overriding_tifftags():

    filename = "/vsimem/out.tif"
    ds = gdal.GetDriverByName("GTiff").Create(filename, 1, 1)
    assert ds.GetRasterBand(1).SetNoDataValue(0) == gdal.CE_None
    ds = None

    ds = gdal.Open(filename)
    assert ds.GetRasterBand(1).SetNoDataValue(123) == gdal.CE_None
    ds = None

    assert gdal.VSIStatL(filename + ".aux.xml") is not None

    ds = gdal.Open(filename)
    assert ds.GetRasterBand(1).GetNoDataValue() == 123
    ds = None

    ds = gdal.Open(filename, gdal.GA_Update)
    assert ds.GetRasterBand(1).SetNoDataValue(1) == gdal.CE_None
    ds = None

    ds = gdal.Open(filename)
    assert ds.GetRasterBand(1).GetNoDataValue() == 1
    ds = None

    gdal.GetDriverByName("GTiff").Delete(filename)


###############################################################################
# Test Dataset SetMetadataItem() on a read-only dataset


def test_tiff_write_dataset_setmetadataitem_read_only():

    filename = "/vsimem/out.tif"
    gdal.GetDriverByName("GTiff").Create(filename, 1, 1)

    ds = gdal.Open(filename)
    assert ds.SetMetadataItem("FOO", "BAR", "BAZ") == gdal.CE_None
    ds = None

    assert gdal.VSIStatL(filename + ".aux.xml") is not None

    ds = gdal.Open(filename)
    assert ds.GetMetadataItem("FOO", "BAZ") == "BAR"
    ds = None

    gdal.GetDriverByName("GTiff").Delete(filename)


###############################################################################
# Test Dataset SetMetadata() on a read-only dataset


def test_tiff_write_dataset_setmetadata_read_only():

    filename = "/vsimem/out.tif"
    gdal.GetDriverByName("GTiff").Create(filename, 1, 1)

    ds = gdal.Open(filename)
    assert ds.SetMetadata({"FOO": "BAR"}, "BAZ") == gdal.CE_None
    ds = None

    assert gdal.VSIStatL(filename + ".aux.xml") is not None

    ds = gdal.Open(filename)
    assert ds.GetMetadataItem("FOO", "BAZ") == "BAR"
    ds = None

    gdal.GetDriverByName("GTiff").Delete(filename)


###############################################################################
# Test Band SetMetadataItem() on a read-only dataset


def test_tiff_write_band_setmetadataitem_read_only():

    filename = "/vsimem/out.tif"
    gdal.GetDriverByName("GTiff").Create(filename, 1, 1)

    ds = gdal.Open(filename)
    assert ds.GetRasterBand(1).SetMetadataItem("FOO", "BAR", "BAZ") == gdal.CE_None
    ds = None

    assert gdal.VSIStatL(filename + ".aux.xml") is not None

    ds = gdal.Open(filename)
    assert ds.GetRasterBand(1).GetMetadataItem("FOO", "BAZ") == "BAR"
    ds = None

    gdal.GetDriverByName("GTiff").Delete(filename)


###############################################################################
# Test Band SetMetadata() on a read-only dataset


def test_tiff_write_band_setmetadata_read_only():

    filename = "/vsimem/out.tif"
    gdal.GetDriverByName("GTiff").Create(filename, 1, 1)

    ds = gdal.Open(filename)
    assert ds.GetRasterBand(1).SetMetadata({"FOO": "BAR"}, "BAZ") == gdal.CE_None
    ds = None

    assert gdal.VSIStatL(filename + ".aux.xml") is not None

    ds = gdal.Open(filename)
    assert ds.GetRasterBand(1).GetMetadataItem("FOO", "BAZ") == "BAR"
    ds = None

    gdal.GetDriverByName("GTiff").Delete(filename)


###############################################################################
# Test SetColorTable() on a read-only dataset


def test_tiff_write_setcolortable_read_only():

    filename = "/vsimem/out.tif"
    gdal.GetDriverByName("GTiff").Create(filename, 1, 1)

    ds = gdal.Open(filename)
    ct = gdal.ColorTable()
    ct.SetColorEntry(0, (1, 2, 3, 255))
    assert ds.GetRasterBand(1).SetRasterColorTable(ct) == gdal.CE_None
    assert ds.GetRasterBand(1).GetColorInterpretation() == gdal.GCI_PaletteIndex
    ds = None

    assert gdal.VSIStatL(filename + ".aux.xml") is not None

    ds = gdal.Open(filename)
    ct = ds.GetRasterBand(1).GetRasterColorTable()
    assert ct is not None
    assert ct.GetColorEntry(0) == (1, 2, 3, 255)
    assert ds.GetRasterBand(1).GetColorInterpretation() == gdal.GCI_PaletteIndex
    ds = None

    gdal.GetDriverByName("GTiff").Delete(filename)


###############################################################################
# Test SetColorTable() on a read-only dataset, overriding TIFF tags


def test_tiff_write_setcolortable_read_only_overriding_tifftags():

    filename = "/vsimem/out.tif"
    ds = gdal.GetDriverByName("GTiff").Create(filename, 1, 1)
    ct = gdal.ColorTable()
    ct.SetColorEntry(0, (1, 2, 3, 255))
    assert ds.GetRasterBand(1).SetRasterColorTable(ct) == gdal.CE_None
    assert ds.GetRasterBand(1).GetColorInterpretation() == gdal.GCI_PaletteIndex
    ds = None

    ds = gdal.Open(filename)
    ct = gdal.ColorTable()
    ct.SetColorEntry(0, (4, 5, 6, 255))
    assert ds.GetRasterBand(1).SetRasterColorTable(ct) == gdal.CE_None
    assert ds.GetRasterBand(1).GetColorInterpretation() == gdal.GCI_PaletteIndex
    ds = None

    assert gdal.VSIStatL(filename + ".aux.xml") is not None

    ds = gdal.Open(filename)
    assert ct is not None
    assert ct.GetColorEntry(0) == (4, 5, 6, 255)
    assert ds.GetRasterBand(1).GetColorInterpretation() == gdal.GCI_PaletteIndex
    ds = None

    ds = gdal.Open(filename, gdal.GA_Update)
    ct = gdal.ColorTable()
    ct.SetColorEntry(0, (7, 8, 9, 255))
    assert ds.GetRasterBand(1).SetRasterColorTable(ct) == gdal.CE_None
    assert ds.GetRasterBand(1).GetColorInterpretation() == gdal.GCI_PaletteIndex
    ds = None

    assert gdal.VSIStatL(filename + ".aux.xml") is None

    ds = gdal.Open(filename)
    assert ct is not None
    assert ct.GetColorEntry(0) == (7, 8, 9, 255)
    assert ds.GetRasterBand(1).GetColorInterpretation() == gdal.GCI_PaletteIndex
    ds = None

    gdal.GetDriverByName("GTiff").Delete(filename)


###############################################################################
# Test setting incompatible settings for PREDICTOR


@pytest.mark.parametrize(
    "dt, options",
    [
        (gdal.GDT_UInt16, ["PREDICTOR=2", "NBITS=12"]),
        (gdal.GDT_UInt32, ["PREDICTOR=3"]),
        (gdal.GDT_UInt16, ["PREDICTOR=invalid"]),
    ],
)
def test_tiff_write_incompatible_predictor(dt, options):

    filename = "/vsimem/out.tif"
    with gdal.quiet_errors():
        assert (
            gdal.GetDriverByName("GTiff").Create(
                filename, 1, 1, 1, dt, options + ["COMPRESS=LZW"]
            )
            is None
        )


###############################################################################
# Test PREDICTOR=2 with 64 bit samples


def test_tiff_write_predictor_2_float64():

    md = gdal.GetDriverByName("GTiff").GetMetadata()
    if md["LIBTIFF"] != "INTERNAL":
        pytest.skip("libtiff > 4.3.0 or internal libtiff needed")

    filename = "/vsimem/out.tif"
    ds = gdal.GetDriverByName("GTiff").Create(
        filename, 2, 1, 1, gdal.GDT_Float64, ["COMPRESS=LZW", "PREDICTOR=2"]
    )
    data = struct.pack("d" * 2, 1, 2)
    ds.GetRasterBand(1).WriteRaster(0, 0, 2, 1, data)
    ds = None
    ds = gdal.Open(filename)
    assert ds.GetMetadataItem("PREDICTOR", "IMAGE_STRUCTURE") == "2"
    assert ds.ReadRaster() == data
    ds = None
    gdal.Unlink(filename)


###############################################################################


def test_tiff_write_uint64():

    ut = gdaltest.GDALTest("GTiff", "gtiff/uint64_full_range.tif", 1, 1)
    ut.testCreateCopy()


###############################################################################


def test_tiff_write_uint64_nodata():

    filename = "/vsimem/test_tiff_write_uint64_nodata.tif"
    ds = gdal.GetDriverByName("GTiff").Create(filename, 1, 1, 1, gdal.GDT_UInt64)
    val = (1 << 64) - 1
    assert ds.GetRasterBand(1).SetNoDataValue(val) == gdal.CE_None
    ds = None

    filename_copy = "/vsimem/test_tiff_write_uint64_nodata_filename_copy.tif"
    ds = gdal.Open(filename)
    assert ds.GetRasterBand(1).GetNoDataValue() == val
    ds = gdal.GetDriverByName("GTiff").CreateCopy(filename_copy, ds)
    ds = None

    ds = gdal.Open(filename_copy)
    assert ds.GetRasterBand(1).GetNoDataValue() == val
    ds = None

    gdal.GetDriverByName("GTiff").Delete(filename)
    gdal.GetDriverByName("GTiff").Delete(filename_copy)


###############################################################################


def test_tiff_write_int64():

    ut = gdaltest.GDALTest("GTiff", "gtiff/int64_full_range.tif", 1, 65535)
    ut.testCreateCopy()


###############################################################################


def test_tiff_write_int64_nodata():

    filename = "/vsimem/test_tiff_write_int64_nodata.tif"
    ds = gdal.GetDriverByName("GTiff").Create(filename, 1, 1, 1, gdal.GDT_Int64)
    val = -(1 << 63)
    assert ds.GetRasterBand(1).SetNoDataValue(val) == gdal.CE_None
    ds = None

    filename_copy = "/vsimem/test_tiff_write_int64_nodata_filename_copy.tif"
    ds = gdal.Open(filename)
    assert ds.GetRasterBand(1).GetNoDataValue() == val
    ds = gdal.GetDriverByName("GTiff").CreateCopy(filename_copy, ds)
    ds = None

    ds = gdal.Open(filename_copy)
    assert ds.GetRasterBand(1).GetNoDataValue() == val
    ds = None

    gdal.GetDriverByName("GTiff").Delete(filename)
    gdal.GetDriverByName("GTiff").Delete(filename_copy)


###############################################################################
# Check IsMaskBand() on an alpha band


def test_tiff_write_alpha_ismaskband():

    filename = "/vsimem/out.tif"
    ds = gdal.GetDriverByName("GTiff").Create(filename, 1, 1, 2)
    ds.GetRasterBand(2).SetColorInterpretation(gdal.GCI_AlphaBand)
    assert not ds.GetRasterBand(1).IsMaskBand()
    assert ds.GetRasterBand(2).IsMaskBand()
    ds = None

    gdal.GetDriverByName("GTiff").Delete(filename)


###############################################################################
# Test scenario of https://github.com/OSGeo/gdal/issues/5580


def test_tiff_write_overview_building_and_approx_stats():

    filename = "/vsimem/out.tif"
    gdal.GetDriverByName("GTiff").Create(filename, 512, 512)
    ds = gdal.Open(filename)
    ds.BuildOverviews("NEAREST", [2, 4, 8])
    ds.GetRasterBand(1).ComputeStatistics(1)
    ds = None

    gdal.GetDriverByName("GTiff").Delete(filename)


###############################################################################
# Test scenario of https://github.com/OSGeo/gdal/issues/6015


@pytest.mark.parametrize("setmetadata_before", [True, False])
def test_tiff_write_setgeotransform_and_setmetadata(setmetadata_before):

    filename = "/vsimem/out.tif"
    ds = gdal.GetDriverByName("GTiff").Create(filename, 1, 1)
    ds.SetGeoTransform([1, 2, 3, 4, 5, -6])
    ds = None
    ds = gdal.Open(filename, gdal.GA_Update)
    if setmetadata_before:
        ds.SetMetadata([])
    ds.SetGeoTransform([10, 20, 30, 40, 50, -60])
    if not setmetadata_before:
        ds.SetMetadata([])
    ds = None
    ds = gdal.Open(filename)
    assert ds.GetGeoTransform() == (10, 20, 30, 40, 50, -60)
    ds = None

    gdal.GetDriverByName("GTiff").Delete(filename)


@pytest.mark.parametrize("getspatialref_before", [True, False])
def test_tiff_write_setgeotransform_and_getspatialref(getspatialref_before):

    filename = "/vsimem/out.tif"
    ds = gdal.GetDriverByName("GTiff").Create(filename, 1, 1)
    ds.SetGeoTransform([1, 2, 3, 4, 5, -6])
    ds = None
    ds = gdal.Open(filename, gdal.GA_Update)
    if getspatialref_before:
        ds.GetSpatialRef()
    ds.SetGeoTransform([10, 20, 30, 40, 50, -60])
    if not getspatialref_before:
        ds.GetSpatialRef()
    ds = None
    ds = gdal.Open(filename)
    assert ds.GetGeoTransform() == (10, 20, 30, 40, 50, -60)
    ds = None

    gdal.GetDriverByName("GTiff").Delete(filename)


###############################################################################
# Test CreateCopy() on a source dataset that has an alpha band not in last
# band


@pytest.mark.parametrize("options", [["PROFILE=BASELINE"], []])
def test_tiff_write_createcopy_alpha_not_in_last_band(options):

    tmpfilename = "/vsimem/test_tiff_write_createcopy_alpha_not_in_last_band.tif"

    src_ds = gdal.GetDriverByName("MEM").Create("", 1, 1, 6)
    src_ds.GetRasterBand(5).SetColorInterpretation(gdal.GCI_AlphaBand)

    # Try with implied MINISBLACK photometric interpretation
    gdal.GetDriverByName("GTiff").CreateCopy(tmpfilename, src_ds, options=options)
    statBuf = gdal.VSIStatL(
        tmpfilename + ".aux.xml",
        gdal.VSI_STAT_EXISTS_FLAG | gdal.VSI_STAT_NATURE_FLAG | gdal.VSI_STAT_SIZE_FLAG,
    )
    assert statBuf is None, "did not expect PAM file"
    ds = gdal.Open(tmpfilename)
    assert ds.GetMetadataItem("TIFFTAG_EXTRASAMPLES", "_DEBUG_") == "0,0,0,2,0"
    assert ds.GetRasterBand(1).GetColorInterpretation() == gdal.GCI_GrayIndex
    assert ds.GetRasterBand(2).GetColorInterpretation() == gdal.GCI_Undefined
    assert ds.GetRasterBand(3).GetColorInterpretation() == gdal.GCI_Undefined
    assert ds.GetRasterBand(4).GetColorInterpretation() == gdal.GCI_Undefined
    assert ds.GetRasterBand(5).GetColorInterpretation() == gdal.GCI_AlphaBand
    assert ds.GetRasterBand(6).GetColorInterpretation() == gdal.GCI_Undefined
    ds = None

    # Try with explicit RGB photometric interpretation
    gdal.GetDriverByName("GTiff").CreateCopy(
        tmpfilename, src_ds, options=["PHOTOMETRIC=RGB"] + options
    )
    statBuf = gdal.VSIStatL(
        tmpfilename + ".aux.xml",
        gdal.VSI_STAT_EXISTS_FLAG | gdal.VSI_STAT_NATURE_FLAG | gdal.VSI_STAT_SIZE_FLAG,
    )
    assert statBuf is None, "did not expect PAM file"
    ds = gdal.Open(tmpfilename)
    assert ds.GetMetadataItem("TIFFTAG_EXTRASAMPLES", "_DEBUG_") == "0,2,0"
    assert ds.GetRasterBand(1).GetColorInterpretation() == gdal.GCI_RedBand
    assert ds.GetRasterBand(2).GetColorInterpretation() == gdal.GCI_GreenBand
    assert ds.GetRasterBand(3).GetColorInterpretation() == gdal.GCI_BlueBand
    assert ds.GetRasterBand(4).GetColorInterpretation() == gdal.GCI_Undefined
    assert ds.GetRasterBand(5).GetColorInterpretation() == gdal.GCI_AlphaBand
    assert ds.GetRasterBand(6).GetColorInterpretation() == gdal.GCI_Undefined
    ds = None

    # Try with implied RGB photometric interpretation
    src_ds.GetRasterBand(1).SetColorInterpretation(gdal.GCI_RedBand)
    src_ds.GetRasterBand(2).SetColorInterpretation(gdal.GCI_GreenBand)
    src_ds.GetRasterBand(3).SetColorInterpretation(gdal.GCI_BlueBand)
    gdal.GetDriverByName("GTiff").CreateCopy(tmpfilename, src_ds, options=options)
    statBuf = gdal.VSIStatL(
        tmpfilename + ".aux.xml",
        gdal.VSI_STAT_EXISTS_FLAG | gdal.VSI_STAT_NATURE_FLAG | gdal.VSI_STAT_SIZE_FLAG,
    )
    assert statBuf is None, "did not expect PAM file"
    ds = gdal.Open(tmpfilename)
    assert ds.GetMetadataItem("TIFFTAG_EXTRASAMPLES", "_DEBUG_") == "0,2,0"
    assert ds.GetRasterBand(1).GetColorInterpretation() == gdal.GCI_RedBand
    assert ds.GetRasterBand(2).GetColorInterpretation() == gdal.GCI_GreenBand
    assert ds.GetRasterBand(3).GetColorInterpretation() == gdal.GCI_BlueBand
    assert ds.GetRasterBand(4).GetColorInterpretation() == gdal.GCI_Undefined
    assert ds.GetRasterBand(5).GetColorInterpretation() == gdal.GCI_AlphaBand
    assert ds.GetRasterBand(6).GetColorInterpretation() == gdal.GCI_Undefined
    ds = None

    gdal.GetDriverByName("GTiff").Delete(tmpfilename)


###############################################################################
# Test JXL compression
@pytest.mark.require_creation_option("GTiff", "JXL")
def test_tiff_write_jpegxl_band_combinations():

    tmpfilename = "/vsimem/test_tiff_write_jpegxl_band_combinations.tif"

    src_ds = gdal.GetDriverByName("MEM").Create("", 64, 64, 6)
    for b in range(6):
        bnd = src_ds.GetRasterBand(b + 1)
        bnd.Fill(b + 1)
        bnd.FlushCache()
        assert bnd.Checksum() != 0, "bnd.Fill failed"

    cilists = [
        [gdal.GCI_RedBand],
        [gdal.GCI_RedBand, gdal.GCI_Undefined],
        [gdal.GCI_RedBand, gdal.GCI_AlphaBand],
        [gdal.GCI_Undefined, gdal.GCI_AlphaBand],
        [gdal.GCI_RedBand, gdal.GCI_GreenBand, gdal.GCI_BlueBand],
        [gdal.GCI_RedBand, gdal.GCI_GreenBand, gdal.GCI_BlueBand, gdal.GCI_AlphaBand],
        [
            gdal.GCI_RedBand,
            gdal.GCI_GreenBand,
            gdal.GCI_BlueBand,
            gdal.GCI_AlphaBand,
            gdal.GCI_Undefined,
        ],
        [
            gdal.GCI_RedBand,
            gdal.GCI_GreenBand,
            gdal.GCI_BlueBand,
            gdal.GCI_Undefined,
            gdal.GCI_Undefined,
        ],
        [
            gdal.GCI_RedBand,
            gdal.GCI_GreenBand,
            gdal.GCI_BlueBand,
            gdal.GCI_Undefined,
            gdal.GCI_AlphaBand,
        ],
        [
            gdal.GCI_RedBand,
            gdal.GCI_GreenBand,
            gdal.GCI_AlphaBand,
            gdal.GCI_Undefined,
            gdal.GCI_BlueBand,
        ],
    ]

    types = [
        gdal.GDT_Byte,
        gdal.GDT_UInt16,
        gdal.GDT_Float32,
    ]

    creationOptions = [
        ["TILED=YES", "COMPRESS=JXL", "INTERLEAVE=BAND"],
        ["TILED=YES", "COMPRESS=JXL", "INTERLEAVE=PIXEL"],
    ]

    jpegxl_drv = gdal.GetDriverByName("JPEGXL")

    for dtype in types:
        for copts in creationOptions:
            for cilist in cilists:
                bandlist = [idx + 1 for idx in range(len(cilist))]
                vrtds = gdal.Translate(
                    "", src_ds, format="vrt", bandList=bandlist, outputType=dtype
                )
                for idx, ci in enumerate(cilist):
                    vrtds.GetRasterBand(idx + 1).SetColorInterpretation(ci)

                ds = gdal.Translate(tmpfilename, vrtds, creationOptions=copts)
                ds = None
                # print(dtype, copts, cilist)
                ds = gdal.Open(tmpfilename)
                for idx in range(len(cilist)):
                    gdal.ErrorReset()
                    got_cs = ds.GetRasterBand(idx + 1).Checksum()
                    assert gdal.GetLastErrorMsg() == ""
                    assert got_cs == src_ds.GetRasterBand(idx + 1).Checksum(), (
                        dtype,
                        copts,
                        cilist,
                        idx,
                    )

                # Check that color interpretation inside JXL data is properly encoded
                if jpegxl_drv and "INTERLEAVE=PIXEL" in copts:
                    jxl_offset = ds.GetRasterBand(1).GetMetadataItem(
                        "BLOCK_OFFSET_0_0", "TIFF"
                    )
                    jxl_ds = gdal.Open(
                        "/vsisubfile/%s_-1,%s" % (jxl_offset, tmpfilename)
                    )
                    assert jxl_ds
                    for idx in range(len(cilist)):
                        got_cs = jxl_ds.GetRasterBand(idx + 1).Checksum()
                        assert got_cs == src_ds.GetRasterBand(idx + 1).Checksum(), (
                            dtype,
                            copts,
                            cilist,
                            idx,
                        )

                    if (
                        vrtds.RasterCount >= 3
                        and vrtds.GetRasterBand(1).GetColorInterpretation()
                        == gdal.GCI_RedBand
                        and vrtds.GetRasterBand(2).GetColorInterpretation()
                        == gdal.GCI_GreenBand
                        and vrtds.GetRasterBand(3).GetColorInterpretation()
                        == gdal.GCI_BlueBand
                    ):
                        assert (
                            jxl_ds.GetRasterBand(1).GetColorInterpretation()
                            == gdal.GCI_RedBand
                        )
                        assert (
                            jxl_ds.GetRasterBand(2).GetColorInterpretation()
                            == gdal.GCI_GreenBand
                        )
                        assert (
                            jxl_ds.GetRasterBand(3).GetColorInterpretation()
                            == gdal.GCI_BlueBand
                        )
                    # Check that alpha band is preserved
                    for idx in range(len(cilist)):
                        if (
                            vrtds.GetRasterBand(idx + 1).GetColorInterpretation()
                            == gdal.GCI_AlphaBand
                        ):
                            assert (
                                jxl_ds.GetRasterBand(idx + 1).GetColorInterpretation()
                                == gdal.GCI_AlphaBand
                            )

                vrtds = None
                ds = None
                gdal.Unlink(tmpfilename)


###############################################################################
# Test turning on lossy WEBP compression if WEBP_LEVEL_OVERVIEW specified


@pytest.mark.require_creation_option("GTiff", "WEBP")
@pytest.mark.require_driver("WEBP")
def test_tiff_write_webp_overview_turn_on_lossy_if_webp_level():

    tmpfilename = (
        "/vsimem/test_tiff_write_webp_overview_turn_on_lossy_if_webp_level.tif"
    )

    ds = gdal.Translate(
        tmpfilename,
        "../gdrivers/data/small_world.tif",
        options="-outsize 10 10 -co COMPRESS=WEBP -co WEBP_LOSSLESS=YES",
    )
    ds.BuildOverviews("NEAR", [2], options=["WEBP_LEVEL=50"])
    ds = None
    ds = gdal.Open(tmpfilename)
    assert (
        ds.GetMetadataItem("COMPRESSION_REVERSIBILITY", "IMAGE_STRUCTURE") == "LOSSLESS"
    )
    assert (
        ds.GetRasterBand(1)
        .GetOverview(0)
        .GetDataset()
        .GetMetadataItem("COMPRESSION_REVERSIBILITY", "IMAGE_STRUCTURE")
        == "LOSSY"
    )
    ds = None

    gdal.Unlink(tmpfilename)


###############################################################################
# Test lossless extraction of a JPEG compressed tile to JPEG


@pytest.mark.parametrize("extra_options", ["-co PHOTOMETRIC=YCBCR", ""])
@pytest.mark.require_creation_option("GTiff", "JPEG")
@pytest.mark.require_driver("JPEG")
def test_tiff_write_lossless_extraction_of_JPEG_tile(extra_options):

    tmpfilename_gtiff = "/vsimem/test_tiff_write_lossless_extraction_of_JPEG_tile.tif"
    gdal.Translate(
        tmpfilename_gtiff,
        "../gdrivers/data/small_world.tif",
        options="-co TILED=YES -co BLOCKXSIZE=128 -co BLOCKYSIZE=128 -co COMPRESS=JPEG "
        + extra_options,
    )

    tmpfilename_jpg = "/vsimem/test_tiff_write_lossless_extraction_of_JPEG_tile.jpg"
    gdal.Translate(
        tmpfilename_jpg,
        tmpfilename_gtiff,
        options="-srcwin 0 0 128 128 -co LOSSLESS_COPY=YES",
    )
    gtiff_ds = gdal.Open(tmpfilename_gtiff)
    jpeg_ds = gdal.Open(tmpfilename_jpg)
    assert jpeg_ds.ReadRaster() == gtiff_ds.ReadRaster(0, 0, 128, 128)

    gdal.Unlink(tmpfilename_gtiff)
    gdal.Unlink(tmpfilename_jpg)
    gdal.Unlink(tmpfilename_jpg + ".aux.xml")


###############################################################################
# Test lossless extraction of a JPEGXL compressed tile to JPEGXL


@pytest.mark.require_creation_option("GTiff", "JXL")
@pytest.mark.require_driver("JPEGXL")
def test_tiff_write_lossless_extraction_of_JPEGXL_tile():

    tmpfilename_gtiff = "/vsimem/test_tiff_write_lossless_extraction_of_JPEGXL_tile.tif"
    gdal.Translate(
        tmpfilename_gtiff,
        "../gdrivers/data/small_world.tif",
        options="-co TILED=YES -co BLOCKXSIZE=128 -co BLOCKYSIZE=128 -co COMPRESS=JXL",
    )

    tmpfilename_jxl = "/vsimem/test_tiff_write_lossless_extraction_of_JPEGXL_tile.jxl"
    gdal.Translate(
        tmpfilename_jxl,
        tmpfilename_gtiff,
        options="-srcwin 128 0 128 128 -co LOSSLESS_COPY=YES -co LOSSLESS=NO",
    )
    assert gdal.VSIStatL(tmpfilename_jxl + ".aux.xml") is None
    gtiff_ds = gdal.Open(tmpfilename_gtiff)
    jpeg_ds = gdal.Open(tmpfilename_jxl)
    assert (
        jpeg_ds.GetGeoTransform()[0]
        == gtiff_ds.GetGeoTransform()[0] + 128 * gtiff_ds.GetGeoTransform()[1]
    )
    assert jpeg_ds.ReadRaster() == gtiff_ds.ReadRaster(128, 0, 128, 128)

    gdal.Unlink(tmpfilename_gtiff)
    gdal.Unlink(tmpfilename_jxl)


###############################################################################
# Test band based write block cache by-pass optimization


@pytest.mark.parametrize(
    "xsize,ysize,nbands,dt,interleave,tiled,blockxsize,blockysize,write_dt",
    [
        # Raster size multiple of block size
        (128, 64, 1, gdal.GDT_Byte, "BAND", True, 32, 16, gdal.GDT_Byte),
        # Write with a different data type
        (128, 64, 1, gdal.GDT_Byte, "BAND", True, 32, 16, gdal.GDT_UInt16),
        # Non-byte data type
        (128, 64, 1, gdal.GDT_UInt16, "BAND", True, 32, 16, gdal.GDT_UInt16),
        # Raster size is NOT a multiple of block size
        (130, 65, 1, gdal.GDT_Byte, "BAND", True, 32, 16, gdal.GDT_Byte),
        # Multiple bands
        (128, 64, 2, gdal.GDT_Byte, "BAND", True, 32, 16, gdal.GDT_Byte),
        # Non tiled
        (128, 64, 1, gdal.GDT_Byte, "BAND", False, 128, 16, gdal.GDT_Byte),
    ],
)
def test_tiff_write_band_block_cache_bypass_optim(
    xsize, ysize, nbands, dt, interleave, tiled, blockxsize, blockysize, write_dt
):

    mem_ds = gdal.GetDriverByName("MEM").Create("", xsize, ysize, nbands, dt)
    if dt == gdal.GDT_Byte:
        dtype = "B"
        maxval = 255
    elif dt == gdal.GDT_UInt16:
        dtype = "H"
        maxval = 65535
    else:
        assert False, "unhandled case"
    write_dt_size = gdal.GetDataTypeSize(write_dt) // 8

    tmpfilename = "/vsimem/test_tiff_write_band_block_cache_bypass_optim.tif"
    options = ["INTERLEAVE=" + interleave]
    if tiled:
        options += ["TILED=YES"]
    options += ["BLOCKXSIZE=" + str(blockxsize)]
    options += ["BLOCKYSIZE=" + str(blockysize)]

    # Write one non-edge tile
    if blockxsize < xsize:
        xoff = blockxsize * 2
        width = blockxsize
    else:
        xoff = 0
        width = xsize
    yoff = blockysize * 3
    height = blockysize
    ds = gdal.GetDriverByName("GTiff").Create(
        tmpfilename, xsize, ysize, nbands, dt, options=options
    )
    assert ds.GetRasterBand(1).GetMetadataItem("HAS_BLOCK_CACHE", "_DEBUG_") == "0"
    mem_ds.GetRasterBand(1).WriteRaster(
        0,
        0,
        xsize,
        ysize,
        array.array(dtype, [i % maxval for i in range(xsize * ysize)]),
    )
    data = mem_ds.GetRasterBand(1).ReadRaster(
        xoff, yoff, width, height, buf_type=write_dt
    )
    mem_ds.GetRasterBand(1).WriteRaster(
        0, 0, xsize, ysize, array.array(dtype, [0] * (xsize * ysize))
    )
    mem_ds.GetRasterBand(1).WriteRaster(
        xoff, yoff, width, height, data, buf_type=write_dt
    )
    ds.GetRasterBand(1).WriteRaster(xoff, yoff, width, height, data, buf_type=write_dt)
    assert ds.GetRasterBand(1).GetMetadataItem("HAS_BLOCK_CACHE", "_DEBUG_") == "0"
    ds = None

    ds = gdal.Open(tmpfilename)
    assert ds.GetRasterBand(1).ReadRaster() == mem_ds.GetRasterBand(1).ReadRaster()
    ds = None

    # Same as above, but with non-contiguous buffer spacings (actually transposed buffer)
    ds = gdal.GetDriverByName("GTiff").Create(
        tmpfilename, xsize, ysize, nbands, dt, options=options
    )
    assert ds.GetRasterBand(1).GetMetadataItem("HAS_BLOCK_CACHE", "_DEBUG_") == "0"
    mem_ds.GetRasterBand(1).WriteRaster(
        0,
        0,
        xsize,
        ysize,
        array.array(dtype, [i % maxval for i in range(xsize * ysize)]),
    )
    data = mem_ds.GetRasterBand(1).ReadRaster(
        xoff,
        yoff,
        width,
        height,
        buf_type=write_dt,
        buf_pixel_space=write_dt_size * height,
        buf_line_space=write_dt_size,
    )
    mem_ds.GetRasterBand(1).WriteRaster(
        0, 0, xsize, ysize, array.array(dtype, [0] * (xsize * ysize))
    )
    mem_ds.GetRasterBand(1).WriteRaster(
        xoff,
        yoff,
        width,
        height,
        data,
        buf_type=write_dt,
        buf_pixel_space=write_dt_size * height,
        buf_line_space=write_dt_size,
    )
    ds.GetRasterBand(1).WriteRaster(
        xoff,
        yoff,
        width,
        height,
        data,
        buf_type=write_dt,
        buf_pixel_space=write_dt_size * height,
        buf_line_space=write_dt_size,
    )
    assert ds.GetRasterBand(1).GetMetadataItem("HAS_BLOCK_CACHE", "_DEBUG_") == "0"
    ds = None

    ds = gdal.Open(tmpfilename)
    assert ds.GetRasterBand(1).ReadRaster() == mem_ds.GetRasterBand(1).ReadRaster()
    ds = None

    if blockxsize < xsize:
        # Write one right-most tile
        xoff = ((xsize - 1) // blockxsize) * blockxsize
        width = min(blockxsize, xsize - xoff)
        yoff = blockysize * 3
        height = blockysize
        ds = gdal.GetDriverByName("GTiff").Create(
            tmpfilename, xsize, ysize, nbands, dt, options=options
        )
        assert ds.GetRasterBand(1).GetMetadataItem("HAS_BLOCK_CACHE", "_DEBUG_") == "0"
        mem_ds.GetRasterBand(1).WriteRaster(
            0,
            0,
            xsize,
            ysize,
            array.array(dtype, [i % maxval for i in range(xsize * ysize)]),
        )
        data = mem_ds.GetRasterBand(1).ReadRaster(
            xoff, yoff, width, height, buf_type=write_dt
        )
        mem_ds.GetRasterBand(1).WriteRaster(
            0, 0, xsize, ysize, array.array(dtype, [0] * (xsize * ysize))
        )
        mem_ds.GetRasterBand(1).WriteRaster(
            xoff, yoff, width, height, data, buf_type=write_dt
        )
        ds.GetRasterBand(1).WriteRaster(
            xoff, yoff, width, height, data, buf_type=write_dt
        )
        assert ds.GetRasterBand(1).GetMetadataItem("HAS_BLOCK_CACHE", "_DEBUG_") == "0"
        ds = None

        ds = gdal.Open(tmpfilename)
        assert ds.GetRasterBand(1).ReadRaster() == mem_ds.GetRasterBand(1).ReadRaster()
        ds = None

    # Write one bottom-most tile
    if blockxsize < xsize:
        xoff = blockxsize * 2
        width = blockxsize
    else:
        xoff = 0
        width = xsize
    yoff = ((ysize - 1) // blockysize) * blockysize
    height = min(blockysize, ysize - yoff)
    ds = gdal.GetDriverByName("GTiff").Create(
        tmpfilename, xsize, ysize, nbands, dt, options=options
    )
    assert ds.GetRasterBand(1).GetMetadataItem("HAS_BLOCK_CACHE", "_DEBUG_") == "0"
    mem_ds.GetRasterBand(1).WriteRaster(
        0,
        0,
        xsize,
        ysize,
        array.array(dtype, [i % maxval for i in range(xsize * ysize)]),
    )
    data = mem_ds.GetRasterBand(1).ReadRaster(
        xoff, yoff, width, height, buf_type=write_dt
    )
    mem_ds.GetRasterBand(1).WriteRaster(
        0, 0, xsize, ysize, array.array(dtype, [0] * (xsize * ysize))
    )
    mem_ds.GetRasterBand(1).WriteRaster(
        xoff, yoff, width, height, data, buf_type=write_dt
    )
    ds.GetRasterBand(1).WriteRaster(xoff, yoff, width, height, data, buf_type=write_dt)
    assert ds.GetRasterBand(1).GetMetadataItem("HAS_BLOCK_CACHE", "_DEBUG_") == "0"
    ds = None

    ds = gdal.Open(tmpfilename)
    assert ds.GetRasterBand(1).ReadRaster() == mem_ds.GetRasterBand(1).ReadRaster()
    ds = None

    # Whole band writing
    ds = gdal.GetDriverByName("GTiff").Create(
        tmpfilename, xsize, ysize, nbands, dt, options=options
    )
    assert ds.GetRasterBand(1).GetMetadataItem("HAS_BLOCK_CACHE", "_DEBUG_") == "0"
    mem_ds.GetRasterBand(1).WriteRaster(
        0,
        0,
        xsize,
        ysize,
        array.array(dtype, [i % maxval for i in range(xsize * ysize)]),
    )
    data = mem_ds.GetRasterBand(1).ReadRaster(buf_type=write_dt)
    ds.GetRasterBand(1).WriteRaster(0, 0, xsize, ysize, data, buf_type=write_dt)
    assert ds.GetRasterBand(1).GetMetadataItem("HAS_BLOCK_CACHE", "_DEBUG_") == "0"
    ds = None

    ds = gdal.Open(tmpfilename)
    assert ds.GetRasterBand(1).ReadRaster() == mem_ds.GetRasterBand(1).ReadRaster()
    ds = None

    gdal.Unlink(tmpfilename)


def test_tiff_write_band_block_cache_bypass_optim_non_triggered():

    xsize, ysize, nbands, dt, interleave, tiled, blockxsize, blockysize = (
        128,
        64,
        1,
        gdal.GDT_Byte,
        "BAND",
        True,
        32,
        16,
    )
    dtype = "B"

    tmpfilename = "/vsimem/test_tiff_write_band_block_cache_bypass_optim.tif"
    options = ["INTERLEAVE=" + interleave]
    if tiled:
        options += ["TILED=YES"]
    options += ["BLOCKXSIZE=" + str(blockxsize)]
    options += ["BLOCKYSIZE=" + str(blockysize)]

    # "warm-up": test that optim triggers in situation where it should
    ds = gdal.GetDriverByName("GTiff").Create(
        tmpfilename, xsize, ysize, nbands, dt, options=options
    )
    ds.GetRasterBand(1).WriteRaster(
        0, 0, blockxsize, blockysize, array.array(dtype, [0] * blockxsize * blockysize)
    )
    assert ds.GetRasterBand(1).GetMetadataItem("HAS_BLOCK_CACHE", "_DEBUG_") == "0"
    ds = None

    # left of window not aligned on block boundaries
    ds = gdal.GetDriverByName("GTiff").Create(
        tmpfilename, xsize, ysize, nbands, dt, options=options
    )
    ds.GetRasterBand(1).WriteRaster(
        1, 0, blockxsize, blockysize, array.array(dtype, [0] * blockxsize * blockysize)
    )
    assert ds.GetRasterBand(1).GetMetadataItem("HAS_BLOCK_CACHE", "_DEBUG_") == "1"
    ds = None

    # right of window not aligned on block boundaries
    ds = gdal.GetDriverByName("GTiff").Create(
        tmpfilename, xsize, ysize, nbands, dt, options=options
    )
    ds.GetRasterBand(1).WriteRaster(
        0,
        0,
        blockxsize - 1,
        blockysize,
        array.array(dtype, [0] * (blockxsize - 1) * blockysize),
    )
    assert ds.GetRasterBand(1).GetMetadataItem("HAS_BLOCK_CACHE", "_DEBUG_") == "1"
    ds = None

    # top of window not aligned on block boundaries
    ds = gdal.GetDriverByName("GTiff").Create(
        tmpfilename, xsize, ysize, nbands, dt, options=options
    )
    ds.GetRasterBand(1).WriteRaster(
        0, 1, blockxsize, blockysize, array.array(dtype, [0] * blockxsize * blockysize)
    )
    assert ds.GetRasterBand(1).GetMetadataItem("HAS_BLOCK_CACHE", "_DEBUG_") == "1"
    ds = None

    # bottom of window not aligned on block boundaries
    ds = gdal.GetDriverByName("GTiff").Create(
        tmpfilename, xsize, ysize, nbands, dt, options=options
    )
    ds.GetRasterBand(1).WriteRaster(
        0,
        0,
        blockxsize,
        blockysize - 1,
        array.array(dtype, [0] * blockxsize * (blockysize - 1)),
    )
    assert ds.GetRasterBand(1).GetMetadataItem("HAS_BLOCK_CACHE", "_DEBUG_") == "1"
    ds = None

    # read operation involving block cache done before
    ds = gdal.GetDriverByName("GTiff").Create(
        tmpfilename, xsize, ysize, nbands, dt, options=options
    )
    ds.GetRasterBand(1).ReadRaster()
    ds.GetRasterBand(1).WriteRaster(
        0, 0, blockxsize, blockysize, array.array(dtype, [0] * blockxsize * blockysize)
    )
    assert ds.GetRasterBand(1).GetMetadataItem("HAS_BLOCK_CACHE", "_DEBUG_") == "1"
    ds = None

    # write operation involving block cache done before
    ds = gdal.GetDriverByName("GTiff").Create(
        tmpfilename, xsize, ysize, nbands, dt, options=options
    )
    ds.GetRasterBand(1).Fill(0)
    ds.GetRasterBand(1).WriteRaster(
        0, 0, blockxsize, blockysize, array.array(dtype, [0] * blockxsize * blockysize)
    )
    assert ds.GetRasterBand(1).GetMetadataItem("HAS_BLOCK_CACHE", "_DEBUG_") == "1"
    ds = None

    # odd bit case
    ds = gdal.GetDriverByName("GTiff").Create(
        tmpfilename, xsize, ysize, nbands, dt, options=options + ["NBITS=7"]
    )
    ds.GetRasterBand(1).WriteRaster(
        0, 0, blockxsize, blockysize, array.array(dtype, [0] * blockxsize * blockysize)
    )
    assert ds.GetRasterBand(1).GetMetadataItem("HAS_BLOCK_CACHE", "_DEBUG_") == "1"
    ds = None

    # bufxsize != xsize
    ds = gdal.GetDriverByName("GTiff").Create(
        tmpfilename, xsize, ysize, nbands, dt, options=options
    )
    ds.GetRasterBand(1).WriteRaster(
        0,
        0,
        blockxsize,
        blockysize,
        array.array(dtype, [0] * (blockxsize - 1) * blockysize),
        buf_xsize=blockxsize - 1,
    )
    assert ds.GetRasterBand(1).GetMetadataItem("HAS_BLOCK_CACHE", "_DEBUG_") == "1"
    ds = None

    # bufysize != ysize
    ds = gdal.GetDriverByName("GTiff").Create(
        tmpfilename, xsize, ysize, nbands, dt, options=options
    )
    ds.GetRasterBand(1).WriteRaster(
        0,
        0,
        blockxsize,
        blockysize,
        array.array(dtype, [0] * blockxsize * (blockysize - 1)),
        buf_ysize=blockysize - 1,
    )
    assert ds.GetRasterBand(1).GetMetadataItem("HAS_BLOCK_CACHE", "_DEBUG_") == "1"
    ds = None

    gdal.Unlink(tmpfilename)


###############################################################################
# Test dataset based write block cache by-pass optimization


@pytest.mark.parametrize(
    "xsize,ysize,nbands,dt,interleave,tiled,blockxsize,blockysize,write_dt",
    [
        # Raster size multiple of block size
        (128, 64, 3, gdal.GDT_Byte, "PIXEL", True, 32, 16, gdal.GDT_Byte),
        # Write with a different data type
        (128, 64, 3, gdal.GDT_Byte, "PIXEL", True, 32, 16, gdal.GDT_UInt16),
        # Non-byte data type
        (128, 64, 3, gdal.GDT_UInt16, "PIXEL", True, 32, 16, gdal.GDT_UInt16),
        # Raster size is NOT a multiple of block size
        (130, 65, 3, gdal.GDT_Byte, "PIXEL", True, 32, 16, gdal.GDT_Byte),
        # Multiple bands
        (128, 64, 3, gdal.GDT_Byte, "PIXEL", True, 32, 16, gdal.GDT_Byte),
        # Non tiled
        (128, 64, 3, gdal.GDT_Byte, "PIXEL", False, 128, 16, gdal.GDT_Byte),
    ],
)
def test_tiff_write_dataset_block_cache_bypass_optim(
    xsize, ysize, nbands, dt, interleave, tiled, blockxsize, blockysize, write_dt
):

    mem_ds = gdal.GetDriverByName("MEM").Create("", xsize, ysize, nbands, dt)
    if dt == gdal.GDT_Byte:
        dtype = "B"
        maxval = 255
    elif dt == gdal.GDT_UInt16:
        dtype = "H"
        maxval = 65535
    else:
        assert False, "unhandled case"
    write_dt_size = gdal.GetDataTypeSize(write_dt) // 8

    tmpfilename = "/vsimem/test_tiff_write_band_block_cache_bypass_optim.tif"
    options = ["INTERLEAVE=" + interleave]
    if tiled:
        options += ["TILED=YES"]
    options += ["BLOCKXSIZE=" + str(blockxsize)]
    options += ["BLOCKYSIZE=" + str(blockysize)]

    # Write one non-edge tile
    if blockxsize < xsize:
        xoff = blockxsize * 2
        width = blockxsize
    else:
        xoff = 0
        width = xsize
    yoff = blockysize * 3
    height = blockysize
    ds = gdal.GetDriverByName("GTiff").Create(
        tmpfilename, xsize, ysize, nbands, dt, options=options
    )
    assert ds.GetRasterBand(1).GetMetadataItem("HAS_BLOCK_CACHE", "_DEBUG_") == "0"
    mem_ds.WriteRaster(
        0,
        0,
        xsize,
        ysize,
        array.array(dtype, [i % maxval for i in range(nbands * xsize * ysize)]),
    )
    data = mem_ds.ReadRaster(xoff, yoff, width, height, buf_type=write_dt)
    mem_ds.WriteRaster(
        0, 0, xsize, ysize, array.array(dtype, [0] * (nbands * xsize * ysize))
    )
    mem_ds.WriteRaster(xoff, yoff, width, height, data, buf_type=write_dt)
    ds.WriteRaster(xoff, yoff, width, height, data, buf_type=write_dt)
    assert ds.GetRasterBand(1).GetMetadataItem("HAS_BLOCK_CACHE", "_DEBUG_") == "0"
    ds = None

    ds = gdal.Open(tmpfilename)
    assert ds.ReadRaster() == mem_ds.ReadRaster()
    ds = None

    # Same as above but using pixel-interleaved buffer
    ds = gdal.GetDriverByName("GTiff").Create(
        tmpfilename, xsize, ysize, nbands, dt, options=options
    )
    assert ds.GetRasterBand(1).GetMetadataItem("HAS_BLOCK_CACHE", "_DEBUG_") == "0"
    mem_ds.WriteRaster(
        0,
        0,
        xsize,
        ysize,
        array.array(dtype, [i % maxval for i in range(nbands * xsize * ysize)]),
    )
    buf_pixel_space = nbands * write_dt_size
    buf_line_space = buf_pixel_space * width
    data = mem_ds.ReadRaster(
        xoff,
        yoff,
        width,
        height,
        buf_type=write_dt,
        buf_pixel_space=buf_pixel_space,
        buf_line_space=buf_line_space,
        buf_band_space=write_dt_size,
    )
    mem_ds.WriteRaster(
        0, 0, xsize, ysize, array.array(dtype, [0] * (nbands * xsize * ysize))
    )
    mem_ds.WriteRaster(
        xoff,
        yoff,
        width,
        height,
        data,
        buf_type=write_dt,
        buf_pixel_space=buf_pixel_space,
        buf_line_space=buf_line_space,
        buf_band_space=write_dt_size,
    )
    ds.WriteRaster(
        xoff,
        yoff,
        width,
        height,
        data,
        buf_type=write_dt,
        buf_pixel_space=buf_pixel_space,
        buf_line_space=buf_line_space,
        buf_band_space=write_dt_size,
    )
    assert ds.GetRasterBand(1).GetMetadataItem("HAS_BLOCK_CACHE", "_DEBUG_") == "0"
    ds = None

    ds = gdal.Open(tmpfilename)
    assert ds.ReadRaster() == mem_ds.ReadRaster()
    ds = None

    # Same as first case but with reversed band order
    ds = gdal.GetDriverByName("GTiff").Create(
        tmpfilename, xsize, ysize, nbands, dt, options=options
    )
    assert ds.GetRasterBand(1).GetMetadataItem("HAS_BLOCK_CACHE", "_DEBUG_") == "0"
    mem_ds.WriteRaster(
        0,
        0,
        xsize,
        ysize,
        array.array(dtype, [i % maxval for i in range(nbands * xsize * ysize)]),
    )
    band_list = [i + 1 for i in range(nbands)][::-1]
    data = mem_ds.ReadRaster(
        xoff, height, width, blockysize, buf_type=write_dt, band_list=band_list
    )
    mem_ds.WriteRaster(
        0, 0, xsize, ysize, array.array(dtype, [0] * (nbands * xsize * ysize))
    )
    mem_ds.WriteRaster(
        xoff, height, width, blockysize, data, buf_type=write_dt, band_list=band_list
    )
    ds.WriteRaster(
        xoff, height, width, blockysize, data, buf_type=write_dt, band_list=band_list
    )
    assert ds.GetRasterBand(1).GetMetadataItem("HAS_BLOCK_CACHE", "_DEBUG_") == "0"
    ds = None

    ds = gdal.Open(tmpfilename)
    assert ds.ReadRaster() == mem_ds.ReadRaster()
    ds = None

    # Whole dataset writing
    ds = gdal.GetDriverByName("GTiff").Create(
        tmpfilename, xsize, ysize, nbands, dt, options=options
    )
    assert ds.GetRasterBand(1).GetMetadataItem("HAS_BLOCK_CACHE", "_DEBUG_") == "0"
    mem_ds.WriteRaster(
        0,
        0,
        xsize,
        ysize,
        array.array(dtype, [i % maxval for i in range(nbands * xsize * ysize)]),
    )
    data = mem_ds.ReadRaster(buf_type=write_dt)
    ds.WriteRaster(0, 0, xsize, ysize, data, buf_type=write_dt)
    assert ds.GetRasterBand(1).GetMetadataItem("HAS_BLOCK_CACHE", "_DEBUG_") == "0"
    ds = None

    ds = gdal.Open(tmpfilename)
    assert ds.ReadRaster() == mem_ds.ReadRaster()
    ds = None

    gdal.Unlink(tmpfilename)


def test_tiff_write_dataset_block_cache_bypass_optim_non_triggered():

    xsize, ysize, nbands, dt, interleave, tiled, blockxsize, blockysize = (
        128,
        64,
        3,
        gdal.GDT_Byte,
        "PIXEL",
        True,
        32,
        16,
    )
    dtype = "B"

    tmpfilename = "/vsimem/test_tiff_write_band_block_cache_bypass_optim.tif"
    options = ["INTERLEAVE=" + interleave]
    if tiled:
        options += ["TILED=YES"]
    options += ["BLOCKXSIZE=" + str(blockxsize)]
    options += ["BLOCKYSIZE=" + str(blockysize)]

    # "warm-up": test that optim triggers in situation where it should
    ds = gdal.GetDriverByName("GTiff").Create(
        tmpfilename, xsize, ysize, nbands, dt, options=options
    )
    ds.WriteRaster(
        0,
        0,
        blockxsize,
        blockysize,
        array.array(dtype, [0] * nbands * blockxsize * blockysize),
    )
    assert ds.GetRasterBand(1).GetMetadataItem("HAS_BLOCK_CACHE", "_DEBUG_") == "0"
    ds = None

    # left of window not aligned on block boundaries
    ds = gdal.GetDriverByName("GTiff").Create(
        tmpfilename, xsize, ysize, nbands, dt, options=options
    )
    ds.WriteRaster(
        1,
        0,
        blockxsize,
        blockysize,
        array.array(dtype, [0] * nbands * blockxsize * blockysize),
    )
    assert ds.GetRasterBand(1).GetMetadataItem("HAS_BLOCK_CACHE", "_DEBUG_") == "1"
    ds = None

    # right of window not aligned on block boundaries
    ds = gdal.GetDriverByName("GTiff").Create(
        tmpfilename, xsize, ysize, nbands, dt, options=options
    )
    ds.WriteRaster(
        0,
        0,
        blockxsize - 1,
        blockysize,
        array.array(dtype, [0] * nbands * (blockxsize - 1) * blockysize),
    )
    assert ds.GetRasterBand(1).GetMetadataItem("HAS_BLOCK_CACHE", "_DEBUG_") == "1"
    ds = None

    # top of window not aligned on block boundaries
    ds = gdal.GetDriverByName("GTiff").Create(
        tmpfilename, xsize, ysize, nbands, dt, options=options
    )
    ds.WriteRaster(
        0,
        1,
        blockxsize,
        blockysize,
        array.array(dtype, [0] * nbands * blockxsize * blockysize),
    )
    assert ds.GetRasterBand(1).GetMetadataItem("HAS_BLOCK_CACHE", "_DEBUG_") == "1"
    ds = None

    # bottom of window not aligned on block boundaries
    ds = gdal.GetDriverByName("GTiff").Create(
        tmpfilename, xsize, ysize, nbands, dt, options=options
    )
    ds.WriteRaster(
        0,
        0,
        blockxsize,
        blockysize - 1,
        array.array(dtype, [0] * nbands * blockxsize * (blockysize - 1)),
    )
    assert ds.GetRasterBand(1).GetMetadataItem("HAS_BLOCK_CACHE", "_DEBUG_") == "1"
    ds = None

    # read operation involving block cache done before
    for i in range(nbands):
        ds = gdal.GetDriverByName("GTiff").Create(
            tmpfilename, xsize, ysize, nbands, dt, options=options
        )
        ds.GetRasterBand(i + 1).ReadRaster()
        ds.WriteRaster(
            0,
            0,
            blockxsize,
            blockysize,
            array.array(dtype, [0] * nbands * blockxsize * blockysize),
        )
        assert ds.GetRasterBand(1).GetMetadataItem("HAS_BLOCK_CACHE", "_DEBUG_") == "1"
        ds = None

    # write operation involving block cache done before
    for i in range(nbands):
        ds = gdal.GetDriverByName("GTiff").Create(
            tmpfilename, xsize, ysize, nbands, dt, options=options
        )
        ds.GetRasterBand(i + 1).Fill(0)
        ds.WriteRaster(
            0,
            0,
            blockxsize,
            blockysize,
            array.array(dtype, [0] * nbands * blockxsize * blockysize),
        )
        assert ds.GetRasterBand(1).GetMetadataItem("HAS_BLOCK_CACHE", "_DEBUG_") == "1"
        ds = None

    # odd bit case
    ds = gdal.GetDriverByName("GTiff").Create(
        tmpfilename, xsize, ysize, nbands, dt, options=options + ["NBITS=7"]
    )
    ds.WriteRaster(
        0,
        0,
        blockxsize,
        blockysize,
        array.array(dtype, [0] * nbands * blockxsize * blockysize),
    )
    assert ds.GetRasterBand(1).GetMetadataItem("HAS_BLOCK_CACHE", "_DEBUG_") == "1"
    ds = None

    # bufxsize != xsize
    ds = gdal.GetDriverByName("GTiff").Create(
        tmpfilename, xsize, ysize, nbands, dt, options=options
    )
    ds.WriteRaster(
        0,
        0,
        blockxsize,
        blockysize,
        array.array(dtype, [0] * nbands * (blockxsize - 1) * blockysize),
        buf_xsize=blockxsize - 1,
    )
    assert ds.GetRasterBand(1).GetMetadataItem("HAS_BLOCK_CACHE", "_DEBUG_") == "1"
    ds = None

    # bufysize != ysize
    ds = gdal.GetDriverByName("GTiff").Create(
        tmpfilename, xsize, ysize, nbands, dt, options=options
    )
    ds.WriteRaster(
        0,
        0,
        blockxsize,
        blockysize,
        array.array(dtype, [0] * nbands * blockxsize * (blockysize - 1)),
        buf_ysize=blockysize - 1,
    )
    assert ds.GetRasterBand(1).GetMetadataItem("HAS_BLOCK_CACHE", "_DEBUG_") == "1"
    ds = None

    # bandscount != bands
    ds = gdal.GetDriverByName("GTiff").Create(
        tmpfilename, xsize, ysize, nbands, dt, options=options
    )
    ds.WriteRaster(
        0,
        0,
        blockxsize,
        blockysize,
        array.array(dtype, [0] * (nbands - 1) * blockxsize * blockysize),
        band_list=[i + 1 for i in range(nbands - 1)],
        buf_ysize=blockysize - 1,
    )
    assert ds.GetRasterBand(1).GetMetadataItem("HAS_BLOCK_CACHE", "_DEBUG_") == "1"
    ds = None

    gdal.Unlink(tmpfilename)


def test_tiff_write_offset_in_GDAL_METADATA_tag_metadata_in_pam():

    filename = "/vsimem/test_tiff_write_offset_in_GDAL_METADATA_tag_metadata_in_pam.tif"
    ds = gdal.GetDriverByName("GTiff").Create(filename, 1, 1)
    ds.GetRasterBand(1).SetScale(0.01)
    ds = None
    ds = gdal.Open(filename)
    # this goes into PAM
    ds.GetRasterBand(1).SetMetadataItem("foo", "bar")
    ds = None
    ds = gdal.Open(filename)
    assert ds.GetRasterBand(1).GetScale() == 0.01
    assert ds.GetRasterBand(1).GetMetadataItem("foo") == "bar"
    ds = None
    gdal.GetDriverByName("GTiff").Delete(filename)


@pytest.mark.skipif(
    not check_libtiff_internal_or_at_least(4, 6, 0),
    reason="libtiff internal or >=4.6.0 needed",
)
@pytest.mark.require_creation_option("GTiff", "WEBP")
def test_tiff_write_webp_lossless_exact():

    filename = "/vsimem/test_tiff_write_webp_lossless_exact.tif"
    ds = gdal.GetDriverByName("GTiff").Create(
        filename,
        10,
        10,
        4,
        options=["PHOTOMETRIC=RGB", "ALPHA=YES", "COMPRESS=WEBP", "WEBP_LOSSLESS=YES"],
    )
    ds.GetRasterBand(1).Fill(1)
    ds.GetRasterBand(2).Fill(2)
    ds.GetRasterBand(3).Fill(3)
    ds.GetRasterBand(4).Fill(0)
    ds = None
    ds = gdal.Open(filename)
    assert [ds.GetRasterBand(i + 1).Checksum() for i in range(4)] == [100, 200, 300, 0]
    ds = None
    gdal.GetDriverByName("GTiff").Delete(filename)


###############################################################################
def test_tiff_write_copy_mdd():

    src_ds = gdal.GetDriverByName("MEM").Create("", 1, 1)
    src_ds.SetMetadataItem("FOO", "BAR")
    src_ds.SetMetadataItem("BAR", "BAZ", "OTHER_DOMAIN")
    src_ds.SetMetadataItem("should_not", "be_copied", "IMAGE_STRUCTURE")

    filename = "/vsimem/test_tiff_write_copy_mdd.tif"

    gdal.GetDriverByName("GTiff").CreateCopy(filename, src_ds)
    ds = gdal.Open(filename)
    assert set(ds.GetMetadataDomainList()) == set(
        ["", "IMAGE_STRUCTURE", "DERIVED_SUBDATASETS"]
    )
    assert ds.GetMetadata_Dict() == {"FOO": "BAR"}
    assert ds.GetMetadata_Dict("OTHER_DOMAIN") == {}
    assert ds.GetMetadata_Dict("IMAGE_STRUCTURE") == {"INTERLEAVE": "BAND"}
    ds = None

    gdal.GetDriverByName("GTiff").CreateCopy(
        filename, src_ds, options=["COPY_SRC_MDD=NO"]
    )
    ds = gdal.Open(filename)
    assert ds.GetMetadata_Dict() == {}
    assert ds.GetMetadata_Dict("OTHER_DOMAIN") == {}
    ds = None

    gdal.GetDriverByName("GTiff").CreateCopy(
        filename, src_ds, options=["COPY_SRC_MDD=YES"]
    )
    ds = gdal.Open(filename)
    assert set(ds.GetMetadataDomainList()) == set(
        ["", "IMAGE_STRUCTURE", "DERIVED_SUBDATASETS", "OTHER_DOMAIN"]
    )
    assert ds.GetMetadata_Dict() == {"FOO": "BAR"}
    assert ds.GetMetadata_Dict("OTHER_DOMAIN") == {"BAR": "BAZ"}
    assert ds.GetMetadata_Dict("IMAGE_STRUCTURE") == {"INTERLEAVE": "BAND"}
    ds = None

    gdal.GetDriverByName("GTiff").CreateCopy(
        filename, src_ds, options=["SRC_MDD=OTHER_DOMAIN"]
    )
    ds = gdal.Open(filename)
    assert ds.GetMetadata_Dict() == {}
    assert ds.GetMetadata_Dict("OTHER_DOMAIN") == {"BAR": "BAZ"}
    ds = None

    gdal.GetDriverByName("GTiff").CreateCopy(
        filename, src_ds, options=["SRC_MDD=", "SRC_MDD=OTHER_DOMAIN"]
    )
    ds = gdal.Open(filename)
    assert ds.GetMetadata_Dict() == {"FOO": "BAR"}
    assert ds.GetMetadata_Dict("OTHER_DOMAIN") == {"BAR": "BAZ"}
    ds = None

    gdal.Unlink(filename)


###############################################################################
# Test writing more GCPs than supported


@pytest.mark.parametrize("with_initial_gcps", [False, True])
def test_tiff_write_too_many_gcps(tmp_vsimem, with_initial_gcps):

    filename = str(tmp_vsimem / "test.tif")
    ds = gdal.GetDriverByName("GTiff").Create(filename, 1, 1)
    if with_initial_gcps:
        assert ds.SetGCPs([gdal.GCP(0, 1, 2, 3, 4)] * 10, None) == gdal.CE_None
        ds.Close()
        ds = gdal.Open(filename, gdal.GA_Update)
    gcp_count = int(math.ceil(65535 / 6))
    gcps = [gdal.GCP(0, 1, 2, 3, 4)] * gcp_count
    with gdal.quiet_errors():
        assert ds.SetGCPs(gcps, None) == gdal.CE_None
    assert (
        f"Trying to write {gcp_count} GCPs, whereas the maximum supported in GeoTIFF tag is 10922. Falling back to writing them to PAM"
        in gdal.GetLastErrorMsg()
    )
    ds = None

    assert gdal.VSIStatL(filename + ".aux.xml")

    ds = gdal.Open(filename)
    assert ds.GetGCPCount() == gcp_count
    ds = None

    gdal.Unlink(filename + ".aux.xml")

    ds = gdal.Open(filename)
    assert ds.GetGCPCount() == 0
    ds = None
