COMPMID-582: Add validation to channel_extract kernels.

Change-Id: I6413a05f6870a0d04f12d7348269b15297ae8493
Reviewed-on: https://eu-gerrit-1.euhpc.arm.com/114696
Tested-by: Jenkins <bsgcomp@arm.com>
Reviewed-by: Anthony Barbier <anthony.barbier@arm.com>
diff --git a/arm_compute/core/Utils.h b/arm_compute/core/Utils.h
index 111eac0..b3ebf5e 100644
--- a/arm_compute/core/Utils.h
+++ b/arm_compute/core/Utils.h
@@ -237,9 +237,25 @@
 {
     switch(format)
     {
+        // Single planar formats have a single plane
+        case Format::U8:
+        case Format::U16:
+        case Format::S16:
+        case Format::U32:
+        case Format::S32:
+        case Format::F16:
+        case Format::F32:
+        case Format::UV88:
+        case Format::RGB888:
+        case Format::RGBA8888:
+        case Format::YUYV422:
+        case Format::UYVY422:
+            return 0;
+        // Multi planar formats
         case Format::NV12:
         case Format::NV21:
         {
+            // Channel U and V share the same plane of format UV88
             switch(channel)
             {
                 case Channel::Y:
@@ -274,6 +290,131 @@
     }
 }
 
+/** Return the channel index of a given channel given an input format.
+ *
+ * @param[in] format  Input format
+ * @param[in] channel Input channel
+ *
+ * @return The channel index of the specific channel of the specific format
+ */
+inline int channel_idx_from_format(Format format, Channel channel)
+{
+    switch(format)
+    {
+        case Format::RGB888:
+        {
+            switch(channel)
+            {
+                case Channel::R:
+                    return 0;
+                case Channel::G:
+                    return 1;
+                case Channel::B:
+                    return 2;
+                default:
+                    ARM_COMPUTE_ERROR("Not supported channel");
+                    return 0;
+            }
+        }
+        case Format::RGBA8888:
+        {
+            switch(channel)
+            {
+                case Channel::R:
+                    return 0;
+                case Channel::G:
+                    return 1;
+                case Channel::B:
+                    return 2;
+                case Channel::A:
+                    return 3;
+                default:
+                    ARM_COMPUTE_ERROR("Not supported channel");
+                    return 0;
+            }
+        }
+        case Format::YUYV422:
+        {
+            switch(channel)
+            {
+                case Channel::Y:
+                    return 0;
+                case Channel::U:
+                    return 1;
+                case Channel::V:
+                    return 3;
+                default:
+                    ARM_COMPUTE_ERROR("Not supported channel");
+                    return 0;
+            }
+        }
+        case Format::UYVY422:
+        {
+            switch(channel)
+            {
+                case Channel::Y:
+                    return 1;
+                case Channel::U:
+                    return 0;
+                case Channel::V:
+                    return 2;
+                default:
+                    ARM_COMPUTE_ERROR("Not supported channel");
+                    return 0;
+            }
+        }
+        case Format::NV12:
+        {
+            switch(channel)
+            {
+                case Channel::Y:
+                    return 0;
+                case Channel::U:
+                    return 0;
+                case Channel::V:
+                    return 1;
+                default:
+                    ARM_COMPUTE_ERROR("Not supported channel");
+                    return 0;
+            }
+        }
+        case Format::NV21:
+        {
+            switch(channel)
+            {
+                case Channel::Y:
+                    return 0;
+                case Channel::U:
+                    return 1;
+                case Channel::V:
+                    return 0;
+                default:
+                    ARM_COMPUTE_ERROR("Not supported channel");
+                    return 0;
+            }
+        }
+        case Format::YUV444:
+        case Format::IYUV:
+        {
+            switch(channel)
+            {
+                case Channel::Y:
+                    return 0;
+                case Channel::U:
+                    return 0;
+                case Channel::V:
+                    return 0;
+                default:
+                    ARM_COMPUTE_ERROR("Not supported channel");
+                    return 0;
+            }
+        }
+        default:
+            ARM_COMPUTE_ERROR("Not supported format");
+            return 0;
+    }
+}
+
 /** Return the number of planes for a given format
  *
  * @param[in] format Input format
@@ -384,6 +525,28 @@
     return DataType::UNKNOWN;
 }
 
+/** Return true if the given format has horizontal subsampling.
+ *
+ * @param[in] format Format to determine subsampling.
+ *
+ * @return True if the format can be subsampled horizontaly.
+ */
+inline bool has_format_horizontal_subsampling(Format format)
+{
+    return (format == Format::YUYV422 || format == Format::UYVY422 || format == Format::NV12 || format == Format::NV21 || format == Format::IYUV || format == Format::UV88) ? true : false;
+}
+
+/** Return true if the given format has vertical subsampling.
+ *
+ * @param[in] format Format to determine subsampling.
+ *
+ * @return True if the format can be subsampled verticaly.
+ */
+inline bool has_format_vertical_subsampling(Format format)
+{
+    return (format == Format::NV12 || format == Format::NV21 || format == Format::IYUV || format == Format::UV88) ? true : false;
+}
+
 /** Separate a 2D convolution into two 1D convolutions
  *
  * @param[in]  conv     2D convolution
@@ -491,6 +654,68 @@
     return out_shape;
 }
 
+/** Adjust tensor shape size if width or height are odd for a given multi-planar format. No modification is done for other formats.
+ *
+ * @note Adding here a few links discussing the issue of odd size and sharing the same solution:
+ *       Android Source: https://android.googlesource.com/platform/frameworks/base/+/refs/heads/master/graphics/java/android/graphics/YuvImage.java
+ *       WebM: https://groups.google.com/a/webmproject.org/forum/#!topic/webm-discuss/LaCKpqiDTXM
+ *       libYUV: https://bugs.chromium.org/p/libyuv/issues/detail?id=198&can=1&q=odd%20width
+ *       YUVPlayer: https://sourceforge.net/p/raw-yuvplayer/bugs/1/
+ *
+ * @param[in, out] shape  Tensor shape of 2D size
+ * @param[in]      format Format of the tensor
+ *
+ */
+inline TensorShape adjust_odd_shape(const TensorShape &shape, Format format)
+{
+    TensorShape output{ shape };
+
+    // Force width to be even for formats which require subsampling of the U and V channels
+    if(has_format_horizontal_subsampling(format))
+    {
+        output.set(0, output.x() & ~1U);
+    }
+
+    // Force height to be even for formats which require subsampling of the U and V channels
+    if(has_format_vertical_subsampling(format))
+    {
+        output.set(1, output.y() & ~1U);
+    }
+
+    return output;
+}
+
+/** Calculate subsampled shape for a given format and channel
+ *
+ * @param[in] shape   Shape of the tensor to calculate the extracted channel.
+ * @param[in] format  Format of the tensor.
+ * @param[in] channel Channel to create tensor shape to be extracted.
+ *
+ * @return The subsampled tensor shape.
+ */
+inline TensorShape calculate_subsampled_shape(const TensorShape &shape, Format format, Channel channel = Channel::UNKNOWN)
+{
+    TensorShape output{ shape };
+
+    // Subsample shape only for U or V channel
+    if(Channel::U == channel || Channel::V == channel || Channel::UNKNOWN == channel)
+    {
+        // Subsample width for the tensor shape when channel is U or V
+        if(has_format_horizontal_subsampling(format))
+        {
+            output.set(0, output.x() / 2U);
+        }
+
+        // Subsample height for the tensor shape when channel is U or V
+        if(has_format_vertical_subsampling(format))
+        {
+            output.set(1, output.y() / 2U);
+        }
+    }
+
+    return output;
+}
+
 /** Calculate accurary required by the horizontal and vertical convolution computations
  *
  * @param[in] conv_col Pointer to the vertical vector of the separated convolution filter
diff --git a/arm_compute/core/Validate.h b/arm_compute/core/Validate.h
index 4ef0e11..b04d293 100644
--- a/arm_compute/core/Validate.h
+++ b/arm_compute/core/Validate.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2016, 2017 ARM Limited.
+ * Copyright (c) 2016-2018 ARM Limited.
  *
  * SPDX-License-Identifier: MIT
  *
@@ -271,6 +271,38 @@
 #define ARM_COMPUTE_RETURN_ERROR_ON_MISMATCHING_DIMENSIONS(...) \
     ARM_COMPUTE_RETURN_ON_ERROR(::arm_compute::error_on_mismatching_dimensions(__func__, __FILE__, __LINE__, __VA_ARGS__))
 
+/** Return an error if the passed tensor objects are not even.
+ *
+ * @param[in] function Function in which the error occurred.
+ * @param[in] file     Name of the file where the error occurred.
+ * @param[in] line     Line on which the error occurred.
+ * @param[in] format   Format to check if odd shape is allowed
+ * @param[in] tensor1  The first object to be compared for odd shape.
+ * @param[in] tensors  (Optional) Further allowed objects.
+ *
+ * @return Status
+ */
+template <typename... Ts>
+arm_compute::Status error_on_tensors_not_even(const char *function, const char *file, int line,
+                                              const Format &format, const ITensor *tensor1, Ts... tensors)
+{
+    ARM_COMPUTE_RETURN_ERROR_ON_LOC(tensor1 == nullptr, function, file, line);
+    ARM_COMPUTE_RETURN_ON_ERROR(::arm_compute::error_on_nullptr(function, file, line, std::forward<Ts>(tensors)...));
+    const std::array < const ITensor *, 1 + sizeof...(Ts) > tensors_info_array{ { tensor1, std::forward<Ts>(tensors)... } };
+    ARM_COMPUTE_RETURN_ERROR_ON_LOC_MSG(std::any_of(tensors_info_array.cbegin(), tensors_info_array.cend(), [&](const ITensor * tensor)
+    {
+        const TensorShape correct_shape = adjust_odd_shape(tensor->info()->tensor_shape(), format);
+        return detail::have_different_dimensions(tensor->info()->tensor_shape(), correct_shape, 2);
+    }),
+    function, file, line, "Tensor shape has odd dimensions");
+    return arm_compute::Status{};
+}
+
+#define ARM_COMPUTE_ERROR_ON_TENSORS_NOT_EVEN(...) \
+    ARM_COMPUTE_ERROR_THROW_ON(::arm_compute::error_on_tensors_not_even(__func__, __FILE__, __LINE__, __VA_ARGS__))
+#define ARM_COMPUTE_RETURN_ERROR_ON_TENSORS_NOT_EVEN(...) \
+    ARM_COMPUTE_RETURN_ON_ERROR(::arm_compute::error_on_tensors_not_even(__func__, __FILE__, __LINE__, __VA_ARGS__))
+
 /** Return an error if the passed two tensor infos have different shapes from the given dimension
  *
  * @param[in] function      Function in which the error occurred.