csharp/0xC0000054/libheif-sharp/src/HeifEncoder.cs

HeifEncoder.cs
/*
 * .NET bindings for libheif.
 * Copyright (c) 2020, 2021 Nicholas Hayes
 *
 * Portions Copyright (c) 2017 struktur AG, Dirk Farin 
 *
 * This file is part of libheif-sharp.
 *
 * libheif-sharp is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation, either version 3 of
 * the License, or (at your option) any later version.
 *
 * libheif-sharp 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 Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with libheif-sharp.  If not, see .
 */

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Globalization;
using LibHeifSharp.Interop;
using LibHeifSharp.Properties;
using LibHeifSharp.ResourceManagement;

namespace LibHeifSharp
{
    /// 
    /// A LibHeif encoder instance
    /// 
    /// 
    /// 
    public sealed clast HeifEncoder : Disposable
    {
        private SafeHeifEncoder encoder;
        private bool lossyQualitySet;
        private readonly Lazy encoderParameterList;
        private readonly Lazy encoderParameterTypes;

        /// 
        /// Initializes a new instance of the  clast.
        /// 
        /// The encoder.
        internal HeifEncoder(SafeHeifEncoder encoder)
        {
            Validate.IsNotNull(encoder, nameof(encoder));

            this.encoder = encoder;
            this.lossyQualitySet = false;
            this.encoderParameterList = new Lazy(GetEncoderParameterList);
            this.encoderParameterTypes = new Lazy(GetEncoderParameterTypes);
        }

        /// 
        /// Gets a list of the supported encoder parameters.
        /// 
        /// 
        /// A list of the supported encoder parameters.
        /// 
        /// An error occurred when creating the encoder parameters list.
        /// The object has been disposed.
        public IReadOnlyList EncoderParameters
        {
            get
            {
                VerifyNotDisposed();

                return this.encoderParameterList.Value;
            }
        }

        /// 
        /// Gets the encoder handle.
        /// 
        /// 
        /// The encoder handle.
        /// 
        /// The object has been disposed.
        internal SafeHeifEncoder SafeHeifEncoder
        {
            get
            {
                VerifyNotDisposed();

                return this.encoder;
            }
        }

        /// 
        /// Sets the encoder lossy quality.
        /// 
        /// The quality value.
        ///  must be in the range of [0, 100] inclusive.
        /// A LibHeif error occurred.
        /// The object has been disposed.
        public void SetLossyQuality(int quality)
        {
            if (quality < 0 || quality > 100)
            {
                ExceptionUtil.ThrowArgumentOutOfRangeException(nameof(quality), "Must be in the range of [0, 100].");
            }

            VerifyNotDisposed();

            var error = LibHeifNative.heif_encoder_set_lossy_quality(this.encoder, quality);
            error.ThrowIfError();
            this.lossyQualitySet = true;
        }

        /// 
        /// Sets the lossless compression support.
        /// 
        /// 
        ///  if the encoder should use lossless compression; otherwise, .
        /// 
        /// A LibHeif error occurred.
        /// The object has been disposed.
        public void SetLossless(bool lossless)
        {
            VerifyNotDisposed();

            var error = LibHeifNative.heif_encoder_set_lossless(this.encoder, lossless);
            error.ThrowIfError();

            if (lossless && !this.lossyQualitySet)
            {
                // LibHeif requires the lossy quality to be always be set, if it has
                // not been set the encoder will produce a corrupted image.
                error = LibHeifNative.heif_encoder_set_lossy_quality(this.encoder, 50);
                error.ThrowIfError();
                this.lossyQualitySet = true;
            }
        }

        /// 
        /// Sets an encoder parameter.
        /// 
        /// The parameter.
        /// The parameter value.
        ///  is null.
        /// A LibHeif error occurred.
        /// The object has been disposed.
        public void SetParameter(HeifBooleanEncoderParameter parameter, bool value)
        {
            Validate.IsNotNull(parameter, nameof(parameter));

            SetParameter(parameter.Name, value);
        }

        /// 
        /// Sets an encoder parameter.
        /// 
        /// The parameter.
        /// The parameter value.
        ///  is null.
        /// A LibHeif error occurred.
        /// The object has been disposed.
        public void SetParameter(HeifIntegerEncoderParameter parameter, int value)
        {
            Validate.IsNotNull(parameter, nameof(parameter));

            SetParameter(parameter.Name, value);
        }

        /// 
        /// Sets an encoder parameter.
        /// 
        /// The parameter.
        /// The parameter value.
        /// 
        ///  is null.
        ///
        /// -or-
        ///
        ///  is null.
        /// 
        /// A LibHeif error occurred.
        /// The object has been disposed.
        public void SetParameter(HeifStringEncoderParameter parameter, string value)
        {
            Validate.IsNotNull(parameter, nameof(parameter));

            SetStringParameter(parameter.Name, value, coerceParameterType: false);
        }

        /// 
        /// Sets an encoder parameter.
        /// 
        /// The parameter name.
        /// The parameter value.
        ///  is null.
        /// A LibHeif error occurred.
        /// The object has been disposed.
        public void SetParameter(string name, bool value)
        {
            Validate.IsNotNull(name, nameof(name));
            VerifyNotDisposed();

            SetBooleanParameter(name, value);
        }

        /// 
        /// Sets an encoder parameter.
        /// 
        /// The parameter name.
        /// The parameter value.
        ///  is null.
        /// A LibHeif error occurred.
        /// The object has been disposed.
        public void SetParameter(string name, int value)
        {
            Validate.IsNotNull(name, nameof(name));
            VerifyNotDisposed();

            SetIntegerParameter(name, value);
        }

        /// 
        /// Sets an encoder parameter.
        /// 
        /// The parameter name.
        /// The parameter value.
        /// 
        ///  is null.
        ///
        /// -or-
        ///
        ///  is null.
        /// 
        /// 
        /// A LibHeif error occurred.
        ///
        /// -or-
        ///
        /// Unable to convert the parameter to the correct type.
        /// 
        /// The object has been disposed.
        public void SetParameter(string name, string value)
        {
            SetStringParameter(name, value, coerceParameterType: true);
        }

        /// 
        /// Releases unmanaged and - optionally - managed resources.
        /// 
        /// true to release both managed and unmanaged resources; false to release only unmanaged resources.
        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                DisposableUtil.Free(ref this.encoder);
            }

            base.Dispose(disposing);
        }

        /// 
        /// Gets the encoder parameter list.
        /// 
        /// The encoder parameter list.
        /// An error occurred when creating the encoder parameter.
        private unsafe ReadOnlyCollection GetEncoderParameterList()
        {
            var encoderParameters = new List();

            var parameterList = LibHeifNative.heif_encoder_list_parameters(this.encoder);

            if (parameterList.Value != IntPtr.Zero)
            {
                var encoderParameter = (heif_encoder_parameter*)parameterList.Value;

                while (*encoderParameter != heif_encoder_parameter.Null)
                {
                    encoderParameters.Add(HeifEncoderParameterFactory.Create(this.encoder, *encoderParameter));

                    encoderParameter++;
                }
            }

            return encoderParameters.AsReadOnly();
        }

        /// 
        /// Gets the encoder parameter type dictionary.
        /// 
        /// The encoder parameter type dictionary.
        /// An error occurred when creating the encoder parameter.
        private IReadOnlyDictionary GetEncoderParameterTypes()
        {
            var encoderParameterList = this.encoderParameterList.Value;

            var parameterTypes = new Dictionary(encoderParameterList.Count,
                                                                                  StringComparer.OrdinalIgnoreCase);

            for (int i = 0; i < encoderParameterList.Count; i++)
            {
                var parameter = encoderParameterList[i];

                parameterTypes.Add(parameter.Name, parameter.ParameterType);
            }

            return parameterTypes;
        }

        /// 
        /// Sets a Boolean encoder parameter.
        /// 
        /// The parameter name.
        /// The parameter value.
        /// A LibHeif error occurred.
        private void SetBooleanParameter(string name, bool value)
        {
            var error = LibHeifNative.heif_encoder_set_parameter_boolean(this.encoder, name, value);
            error.ThrowIfError();
        }

        /// 
        /// Sets an integer encoder parameter.
        /// 
        /// The parameter name.
        /// The parameter value.
        /// A LibHeif error occurred.
        private void SetIntegerParameter(string name, int value)
        {
            var error = LibHeifNative.heif_encoder_set_parameter_integer(this.encoder, name, value);
            error.ThrowIfError();
        }

        /// 
        /// Sets a string encoder parameter.
        /// 
        /// The parameter name.
        /// The parameter value.
        /// 
        ///  if the parameter should be coerced to the correct type; otherwise, .
        /// 
        /// 
        ///  is null.
        ///
        /// -or-
        ///
        ///  is null.
        /// 
        /// 
        /// A LibHeif error occurred.
        ///
        /// -or-
        ///
        /// Unable to convert the parameter to the correct type.
        /// 
        /// The object has been disposed.
        private void SetStringParameter(string name, string value, bool coerceParameterType)
        {
            Validate.IsNotNull(name, nameof(name));
            Validate.IsNotNull(value, nameof(value));
            VerifyNotDisposed();

            if (coerceParameterType)
            {
                // Some encoders expect the method that is called to match the parameter type.
                // Attempting to set a Boolean or Integer parameter as a string will cause an invalid parameter error.
                if (this.encoderParameterTypes.Value.TryGetValue(name, out var type))
                {
                    switch (type)
                    {
                        case HeifEncoderParameterType.Boolean:
                            if (TryConvertStringToBoolean(value, out bool boolValue))
                            {
                                SetBooleanParameter(name, boolValue);
                            }
                            else
                            {
                                throw new HeifException(string.Format(CultureInfo.CurrentCulture,
                                                                      Resources.CoerceStringValueToBooleanFailedFormat,
                                                                      nameof(value)));
                            }
                            return;
                        case HeifEncoderParameterType.Integer:
                            if (int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out int intValue))
                            {
                                SetIntegerParameter(name, intValue);
                            }
                            else
                            {
                                throw new HeifException(string.Format(CultureInfo.CurrentCulture,
                                                                      Resources.CoerceStringValueToIntegerFailedFormat,
                                                                      nameof(value)));
                            }
                            return;
                        case HeifEncoderParameterType.String:
                            // The parameter is the correct type.
                            break;
                        default:
                            throw new HeifException($"Unknown { nameof(HeifEncoderParameterType) }: { type }.");
                    }
                }
            }

            var error = LibHeifNative.heif_encoder_set_parameter_string(this.encoder, name, value);
            error.ThrowIfError();
        }

        private static bool TryConvertStringToBoolean(string value, out bool result)
        {
            if (!string.IsNullOrWhiteSpace(value))
            {
                if (bool.TryParse(value, out result))
                {
                    return true;
                }
                else if (value.Equals("1", StringComparison.Ordinal))
                {
                    result = true;
                    return true;
                }
                else if (value.Equals("0", StringComparison.Ordinal))
                {
                    result = false;
                    return true;
                }
            }

            result = false;
            return false;
        }
    }
}