Added support for native image decoding (#808)

This added support for native image decoding on Windows & Apple platforms.
This helps us remove libpng & libjpeg completely on these platforms, and
in the meantime support more image formats thanks to OS vendors,
This commit is contained in:
Chester Liu 2024-09-26 09:17:55 +08:00 коммит произвёл GitHub
Родитель f90a04606b
Коммит e424838708
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: B5690EEEBB952194
8 изменённых файлов: 413 добавлений и 8 удалений

Просмотреть файл

@ -71,6 +71,7 @@ option(OCOS_ENABLE_BERT_TOKENIZER "Enable the BertTokenizer building" ON)
option(OCOS_ENABLE_BLINGFIRE "Enable operators depending on the Blingfire library" ON)
option(OCOS_ENABLE_MATH "Enable math tensor operators building" ON)
option(OCOS_ENABLE_DLIB "Enable operators like Inverse depending on DLIB" ON)
option(OCOS_ENABLE_VENDOR_IMAGE_CODECS "Enable and use vendor image codecs if supported over libpng & libjpeg" OFF)
option(OCOS_ENABLE_OPENCV_CODECS "Enable cv2 and vision operators that require opencv imgcodecs." ON)
option(OCOS_ENABLE_CV2 "Enable the operators in `operators/cv2`" ON)
option(OCOS_ENABLE_VISION "Enable the operators in `operators/vision`" ON)
@ -737,11 +738,26 @@ if(OCOS_ENABLE_C_API)
list(APPEND _TARGET_LIB_SRC ${audio_TARGET_SRC})
endif()
if(OCOS_ENABLE_DLIB)
include(ext_imgcodecs)
file(GLOB cv2_TARGET_SRC "shared/api/c_api_processor.*" "shared/api/image_*.*")
list(APPEND _TARGET_LIB_SRC ${cv2_TARGET_SRC})
target_include_directories(ocos_operators PUBLIC ${libPNG_SOURCE_DIR} ${libJPEG_SOURCE_DIR})
target_link_libraries(ocos_operators PUBLIC ${PNG_LIBRARY} ${JPEG_LIBRARY})
if(OCOS_ENABLE_VENDOR_IMAGE_CODECS)
add_compile_definitions(OCOS_ENABLE_VENDOR_IMAGE_CODECS)
if(WIN32)
# Use WIC on Windows. Nothing to be done
elseif(CMAKE_SYSTEM_NAME STREQUAL "Darwin" OR CMAKE_SYSTEM_NAME STREQUAL "iOS")
# Use ImageIO on Apple platforms
target_link_libraries(ocos_operators PRIVATE "-framework CoreFoundation" "-framework CoreGraphics" "-framework ImageIO")
else()
# Fallback to libpng & libjpeg on all other platforms
include(ext_imgcodecs)
target_include_directories(ocos_operators PUBLIC ${libPNG_SOURCE_DIR} ${libJPEG_SOURCE_DIR})
target_link_libraries(ocos_operators PUBLIC ${PNG_LIBRARY} ${JPEG_LIBRARY})
endif()
else()
include(ext_imgcodecs)
target_include_directories(ocos_operators PUBLIC ${libPNG_SOURCE_DIR} ${libJPEG_SOURCE_DIR})
target_link_libraries(ocos_operators PUBLIC ${PNG_LIBRARY} ${JPEG_LIBRARY})
endif()
endif()
endif()

Просмотреть файл

@ -4,6 +4,10 @@
set(OCOS_ENABLE_GPT2_TOKENIZER ON CACHE INTERNAL "" FORCE)
set(OCOS_ENABLE_C_API ON CACHE INTERNAL "" FORCE)
set(OCOS_ENABLE_DLIB ON CACHE INTERNAL "" FORCE)
set(OCOS_ENABLE_OPENCV_CODECS OFF CACHE INTERNAL "" FORCE)
set(OCOS_ENABLE_CV2 OFF CACHE INTERNAL "" FORCE)
set(OCOS_ENABLE_VISION OFF CACHE INTERNAL "" FORCE)
set(OCOS_ENABLE_VENDOR_IMAGE_CODECS ON CACHE INTERNAL "" FORCE)
set(OCOS_ENABLE_MATH ON CACHE INTERNAL "" FORCE)
set(OCOS_ENABLE_AUDIO ON CACHE INTERNAL "" FORCE)

Просмотреть файл

@ -10,6 +10,20 @@
#include "op_def_struct.h"
#include "ext_status.h"
OrtxStatus image_decoder(const ortc::Tensor<uint8_t>& input, ortc::Tensor<uint8_t>& output);
struct DecodeImage {
template <typename DictT>
OrtxStatus Init(const DictT& attrs) {
return {};
}
OrtxStatus Compute(const ortc::Tensor<uint8_t>& input, ortc::Tensor<uint8_t>& output) {
return image_decoder(input, output);
}
};
class JMemorySourceManager : public jpeg_source_mgr {
public:
// Constructor

Просмотреть файл

@ -0,0 +1,116 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
#pragma once
#include <CoreFoundation/CoreFoundation.h>
#include <ImageIO/ImageIO.h>
#include "op_def_struct.h"
#include "ext_status.h"
struct DecodeImage {
template <typename DictT>
OrtxStatus Init(const DictT& attrs) {
CFStringRef optionKeys[2];
CFTypeRef optionValues[2];
optionKeys[0] = kCGImageSourceShouldCache;
optionValues[0] = (CFTypeRef)kCFBooleanFalse;
// Only Integer image data is currently supported
optionKeys[1] = kCGImageSourceShouldAllowFloat;
optionValues[1] = (CFTypeRef)kCFBooleanFalse;
imageSourceOptions_ = CFDictionaryCreate(NULL, (const void**)optionKeys, (const void**)optionValues, 2,
&kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
return {};
}
OrtxStatus Compute(const ortc::Tensor<uint8_t>& input, ortc::Tensor<uint8_t>& output) {
const auto& dimensions = input.Shape();
if (dimensions.size() != 1ULL) {
return {kOrtxErrorInvalidArgument, "[ImageDecoder]: Only raw image formats are supported."};
}
// Get data & the length
const uint8_t* encoded_image_data = input.Data();
const int64_t encoded_image_data_len = input.NumberOfElement();
// check whether it's too small for a image
if (encoded_image_data_len < 8) {
return {kOrtxErrorInvalidArgument, "[ImageDecoder]: Invalid image data."};
}
OrtxStatus status{};
CFDataRef imageData = NULL;
CGImageRef image = NULL;
CGImageSourceRef imageSource;
imageData = CFDataCreate(NULL, encoded_image_data, encoded_image_data_len);
if (imageData == nullptr) {
return {kOrtxErrorInternal, "[ImageDecoder]: Failed to create CFData."};
}
imageSource = CGImageSourceCreateWithData(imageData, imageSourceOptions_);
CFRelease(imageData);
if (imageSource == nullptr) {
return {kOrtxErrorInternal, "[ImageDecoder]: Failed to create CGImageSource."};
}
image = CGImageSourceCreateImageAtIndex(imageSource, 0, NULL);
CFRelease(imageSource);
if (image == nullptr) {
return {kOrtxErrorInternal, "[ImageDecoder]: Failed to create CGImage."};
}
const int64_t width = static_cast<int64_t>(CGImageGetWidth(image));
const int64_t height = static_cast<int64_t>(CGImageGetHeight(image));
const int64_t channels = 3;
std::vector<int64_t> output_dimensions{height, width, channels};
uint8_t* decoded_image_data = output.Allocate(output_dimensions);
if (decoded_image_data == nullptr) {
return {kOrtxErrorInvalidArgument, "[ImageDecoder]: Failed to allocate memory for decoded image data."};
}
// CoreGraphics don't support 24BPP. We get 32BPP with alpha first and then extract the 24BPP data we need.
const size_t _32bpp_channel = 4;
const size_t _32bpp_bytesPerRow = width * _32bpp_channel;
const size_t _32bpp_bitmapByteCount = width * height * _32bpp_channel;
auto _32bpp_bitmapData = std::make_unique<std::byte[]>(_32bpp_bitmapByteCount);
// Ask for the sRGB color space.
const CGColorSpaceRef colorSpace = CGColorSpaceCreateWithName(kCGColorSpaceSRGB);
if (colorSpace == nullptr) {
return {kOrtxErrorInternal, "[ImageDecoder]: Failed to create CGColorSpace."};
}
const CGBitmapInfo _32bpp_bitmapInfo = kCGBitmapByteOrder32Big | (CGBitmapInfo)kCGImageAlphaPremultipliedLast;
CGContextRef context = CGBitmapContextCreate(_32bpp_bitmapData.get(), width, height, 8 /** bitsPerComponent */,
_32bpp_bytesPerRow, colorSpace, _32bpp_bitmapInfo);
CFRelease(colorSpace);
if (context == nullptr) {
return {kOrtxErrorInternal, "[ImageDecoder]: Failed to create CGBitmapContext."};
}
const CGRect rect = CGRectMake(0, 0, width, height);
CGContextDrawImage(context, rect, image);
CFRelease(context);
uint8_t* ptr = (uint8_t*)_32bpp_bitmapData.get();
for (int i = 0; i < width * height; i++) {
*(decoded_image_data++) = *(ptr)++; // R
*(decoded_image_data++) = *(ptr)++; // G
*(decoded_image_data++) = *(ptr)++; // B
ptr++; // Skip A
}
return status;
}
~DecodeImage() { CFRelease(imageSourceOptions_); }
private:
CFDictionaryRef imageSourceOptions_{NULL};
};

Просмотреть файл

@ -0,0 +1,149 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
#pragma once
#pragma comment(lib, "Windowscodecs.lib")
#include <wincodec.h>
#include <wincodecsdk.h>
#include <winrt/base.h>
#include "op_def_struct.h"
#include "ext_status.h"
struct DecodeImage {
template <typename DictT>
OrtxStatus Init(const DictT& attrs) {
HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
if (FAILED(hr)) {
return {kOrtxErrorInternal, "[ImageDecoder]: Failed when CoInitialize."};
}
// Create the COM imaging factory
hr = CoCreateInstance(CLSID_WICImagingFactory, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pIWICFactory_));
if (FAILED(hr)) {
return {kOrtxErrorInternal, "[ImageDecoder]: Failed to create pIWICFactory."};
}
return {};
}
OrtxStatus Compute(const ortc::Tensor<uint8_t>& input, ortc::Tensor<uint8_t>& output) {
const auto& dimensions = input.Shape();
if (dimensions.size() != 1ULL) {
return {kOrtxErrorInvalidArgument, "[ImageDecoder]: Only raw image formats are supported."};
}
// Get data & the length
const uint8_t* encoded_image_data = input.Data();
const int64_t encoded_image_data_len = input.NumberOfElement();
// check it's a PNG image or JPEG image
if (encoded_image_data_len < 8) {
return {kOrtxErrorInvalidArgument, "[ImageDecoder]: Invalid image data."};
}
OrtxStatus status{};
winrt::com_ptr<IWICBitmapDecoder> pIDecoder;
winrt::com_ptr<IWICStream> pIWICStream;
winrt::com_ptr<IWICBitmapFrameDecode> pIDecoderFrame;
winrt::com_ptr<IWICComponentInfo> pIComponentInfo;
WICPixelFormatGUID pixelFormat;
// Create a WIC stream to map onto the memory.
HRESULT hr = pIWICFactory_->CreateStream(pIWICStream.put());
if (FAILED(hr)) {
return {kOrtxErrorInternal, "[ImageDecoder]: Failed to create pIWICStream."};
}
static_assert(sizeof(uint8_t) == sizeof(unsigned char));
// Initialize the stream with the memory pointer and size.
hr = pIWICStream->InitializeFromMemory((unsigned char*)input.Data(), static_cast<DWORD>(input.NumberOfElement()));
if (FAILED(hr)) {
return {kOrtxErrorInternal, "[ImageDecoder]: Failed when pIWICStream->InitializeFromMemory."};
}
// Create a decoder for the stream.
hr = pIWICFactory_->CreateDecoderFromStream(pIWICStream.get(), // Image to be decoded
NULL, // Do not prefer a particular vendor
WICDecodeMetadataCacheOnDemand, // Cache metadata when needed
pIDecoder.put() // Pointer to the decoder
);
if (FAILED(hr)) {
return {kOrtxErrorInternal, "[ImageDecoder]: Failed to create pIDecoder."};
}
// Retrieve the first bitmap frame.
hr = pIDecoder->GetFrame(0, pIDecoderFrame.put());
if (FAILED(hr)) {
return {kOrtxErrorInternal, "[ImageDecoder]: Failed when pIDecoder->GetFrame."};
}
// Now get a POINTER to an instance of the Pixel Format
hr = pIDecoderFrame->GetPixelFormat(&pixelFormat);
if (FAILED(hr)) {
return {kOrtxErrorInternal, "[ImageDecoder]: Failed when pIDecoderFrame->GetPixelFormat."};
}
hr = pIWICFactory_->CreateComponentInfo(pixelFormat, pIComponentInfo.put());
if (FAILED(hr)) {
return {kOrtxErrorInternal, "[ImageDecoder]: Failed when pIWICFactory->CreateComponentInfo."};
}
// Get IWICPixelFormatInfo from IWICComponentInfo
IWICPixelFormatInfo2* pIPixelFormatInfo = NULL;
hr = pIComponentInfo.as(__uuidof(IWICPixelFormatInfo2), reinterpret_cast<void**>(&pIPixelFormatInfo));
if (FAILED(hr)) {
return {kOrtxErrorInternal, "[ImageDecoder]: Failed to query IWICPixelFormatInfo."};
}
UINT uiWidth = 0;
UINT uiHeight = 0;
hr = pIDecoderFrame->GetSize(&uiWidth, &uiHeight);
if (FAILED(hr)) {
return {kOrtxErrorInternal, "[ImageDecoder]: pIDecoderFrame->GetSize."};
}
const int height = static_cast<int>(uiHeight);
const int width = static_cast<int>(uiWidth);
const int channels = 3; // Asks for RGB
std::vector<int64_t> output_dimensions{height, width, channels};
uint8_t* decoded_image_data = output.Allocate(output_dimensions);
if (decoded_image_data == nullptr) {
return {kOrtxErrorInvalidArgument, "[ImageDecoder]: Failed to allocate memory for decoded image data."};
}
// Convert to 24 bytes per pixel RGB format if needed
if (pixelFormat != GUID_WICPixelFormat24bppRGB) {
IWICBitmapSource* pConverted = NULL;
hr = WICConvertBitmapSource(GUID_WICPixelFormat24bppRGB, pIDecoderFrame.get(), &pConverted);
if (FAILED(hr)) {
return {kOrtxErrorInternal, "[ImageDecoder]: Failed when WICConvertBitmapSource."};
}
// Upcast to make winrt::com_ptr happy. Should be fine because we only use CopyPixels.
pIDecoderFrame.attach((IWICBitmapFrameDecode *)pConverted);
}
const int rowStride = uiWidth * sizeof(uint8_t) * channels;
hr = pIDecoderFrame->CopyPixels(NULL, rowStride, static_cast<UINT>(output.SizeInBytes()), decoded_image_data);
if (FAILED(hr)) {
return {kOrtxErrorInternal, "[ImageDecoder]: Failed when pIDecoderFrame->CopyPixels."};
}
return status;
}
~DecodeImage() {
CoUninitialize();
}
private:
winrt::com_ptr<IWICImagingFactory> pIWICFactory_;
};

Просмотреть файл

@ -8,7 +8,17 @@
#include "image_processor.h"
#include "c_api_utils.hpp"
#if OCOS_ENABLE_VENDOR_IMAGE_CODECS
#if WIN32
#include "image_decoder_win32.hpp"
#elif __APPLE__
#include "image_decoder_darwin.hpp"
#else
#include "image_decoder.hpp"
#endif
#else
#include "image_decoder.hpp"
#endif
#include "image_transforms.hpp"
#include "image_transforms_phi_3.hpp"
@ -21,7 +31,7 @@ ort_extensions::LoadRawImages(const std::initializer_list<const char*>& image_pa
}
Operation::KernelRegistry ImageProcessor::kernel_registry_ = {
{"DecodeImage", []() { return CreateKernelInstance(image_decoder); }},
{"DecodeImage", []() { return CreateKernelInstance(&DecodeImage::Compute); }},
{"Resize", []() { return CreateKernelInstance(&Resize::Compute); }},
{"Rescale", []() { return CreateKernelInstance(&Rescale::Compute); }},
{"Normalize", []() { return CreateKernelInstance(&Normalize::Compute); }},

Двоичные данные
test/data/processor/canoe.tif Normal file

Двоичный файл не отображается.

Просмотреть файл

@ -8,11 +8,24 @@
#include "gtest/gtest.h"
#include "shared/api/c_api_utils.hpp"
#if OCOS_ENABLE_VENDOR_IMAGE_CODECS
#if WIN32
#include "shared/api/image_decoder_win32.hpp"
#elif __APPLE__
#include "shared/api/image_decoder_darwin.hpp"
#else
#include "shared/api/image_decoder.hpp"
#endif
#else
#include "shared/api/image_decoder.hpp"
#endif
using namespace ort_extensions;
TEST(ImgDecoderTest, TestPngDecoder) {
DecodeImage image_decoder;
image_decoder.Init(NULL);
std::vector<uint8_t> png_data;
std::filesystem::path png_path = "data/processor/exceltable.png";
std::ifstream png_file(png_path, std::ios::binary);
@ -25,8 +38,8 @@ TEST(ImgDecoderTest, TestPngDecoder) {
ortc::Tensor<uint8_t> png_tensor({static_cast<int64_t>(png_data.size())}, png_data.data());
ortc::Tensor<uint8_t> out_tensor{&CppAllocator::Instance()};
auto status = image_decoder(png_tensor, out_tensor);
ASSERT_TRUE(status.IsOk());
auto status = image_decoder.Compute(png_tensor, out_tensor);
ASSERT_TRUE(status.IsOk()) << status.ToString();
ASSERT_EQ(out_tensor.Shape(), std::vector<int64_t>({206, 487, 3}));
auto out_range = out_tensor.Data() + 0;
@ -47,6 +60,8 @@ TEST(ImgDecoderTest, TestPngDecoder) {
}
TEST(ImageDecoderTest, TestJpegDecoder) {
DecodeImage image_decoder;
image_decoder.Init(NULL);
std::vector<uint8_t> jpeg_data;
std::filesystem::path jpeg_path = "data/processor/australia.jpg";
std::ifstream jpeg_file(jpeg_path, std::ios::binary);
@ -59,14 +74,41 @@ TEST(ImageDecoderTest, TestJpegDecoder) {
ortc::Tensor<uint8_t> jpeg_tensor({static_cast<int64_t>(jpeg_data.size())}, jpeg_data.data());
ortc::Tensor<uint8_t> out_tensor{&CppAllocator::Instance()};
auto status = image_decoder(jpeg_tensor, out_tensor);
ASSERT_TRUE(status.IsOk());
auto status = image_decoder.Compute(jpeg_tensor, out_tensor);
ASSERT_TRUE(status.IsOk()) << status.ToString();
ASSERT_EQ(out_tensor.Shape(), std::vector<int64_t>({876, 1300, 3}));
auto out_range = out_tensor.Data() + 0;
ASSERT_EQ(std::vector<uint8_t>(out_range, out_range + 12),
std::vector<uint8_t>({48, 14, 5, 48, 14, 5, 48, 14, 5, 48, 14, 5}));
#if OCOS_ENABLE_VENDOR_IMAGE_CODECS
#if WIN32
out_range = out_tensor.Data() + 1296 * 3;
ASSERT_EQ(std::vector<uint8_t>(out_range, out_range + 12),
std::vector<uint8_t>({228, 234, 222, 228, 235, 219, 219, 221, 200, 203, 201, 178}));
out_range = out_tensor.Data() + 438 * 1300 * 3;
ASSERT_EQ(std::vector<uint8_t>(out_range, out_range + 12),
std::vector<uint8_t>({84, 68, 53, 86, 70, 55, 92, 76, 60, 101, 86, 65}));
out_range = out_tensor.Data() + 875 * 1300 * 3 + 1296 * 3;
ASSERT_EQ(std::vector<uint8_t>(out_range, out_range + 12),
std::vector<uint8_t>({208, 210, 197, 204, 206, 193, 198, 200, 187, 194, 196, 183}));
#elif __APPLE__
out_range = out_tensor.Data() + 1296 * 3;
ASSERT_EQ(std::vector<uint8_t>(out_range, out_range + 12),
std::vector<uint8_t>({225, 236, 222, 228, 235, 219, 218, 220, 199, 203, 201, 178}));
out_range = out_tensor.Data() + 438 * 1300 * 3;
ASSERT_EQ(std::vector<uint8_t>(out_range, out_range + 12),
std::vector<uint8_t>({84, 68, 53, 86, 70, 55, 92, 76, 59, 101, 86, 65}));
out_range = out_tensor.Data() + 875 * 1300 * 3 + 1296 * 3;
ASSERT_EQ(std::vector<uint8_t>(out_range, out_range + 12),
std::vector<uint8_t>({209, 211, 198, 204, 206, 193, 198, 200, 187, 194, 196, 183}));
#else
out_range = out_tensor.Data() + 1296 * 3;
ASSERT_EQ(std::vector<uint8_t>(out_range, out_range + 12),
std::vector<uint8_t>({221, 237, 224, 225, 236, 219, 218, 222, 199, 203, 202, 174}));
@ -78,4 +120,58 @@ TEST(ImageDecoderTest, TestJpegDecoder) {
out_range = out_tensor.Data() + 875 * 1300 * 3 + 1296 * 3;
ASSERT_EQ(std::vector<uint8_t>(out_range, out_range + 12),
std::vector<uint8_t>({208, 210, 197, 204, 206, 193, 198, 200, 187, 194, 196, 183}));
#endif
#else
out_range = out_tensor.Data() + 1296 * 3;
ASSERT_EQ(std::vector<uint8_t>(out_range, out_range + 12),
std::vector<uint8_t>({221, 237, 224, 225, 236, 219, 218, 222, 199, 203, 202, 174}));
out_range = out_tensor.Data() + 438 * 1300 * 3;
ASSERT_EQ(std::vector<uint8_t>(out_range, out_range + 12),
std::vector<uint8_t>({84, 68, 55, 86, 70, 55, 92, 77, 58, 101, 86, 65}));
out_range = out_tensor.Data() + 875 * 1300 * 3 + 1296 * 3;
ASSERT_EQ(std::vector<uint8_t>(out_range, out_range + 12),
std::vector<uint8_t>({208, 210, 197, 204, 206, 193, 198, 200, 187, 194, 196, 183}));
#endif
}
#if OCOS_ENABLE_VENDOR_IMAGE_CODECS
#if defined(WIN32) || defined(__APPLE__)
TEST(ImageDecoderTest, TestTiffDecoder) {
DecodeImage image_decoder;
image_decoder.Init(NULL);
std::vector<uint8_t> tiff_data;
std::filesystem::path tiff_path = "data/processor/canoe.tif";
std::ifstream tiff_file(tiff_path, std::ios::binary);
ASSERT_TRUE(tiff_file.is_open());
tiff_file.seekg(0, std::ios::end);
tiff_data.resize(tiff_file.tellg());
tiff_file.seekg(0, std::ios::beg);
tiff_file.read(reinterpret_cast<char*>(tiff_data.data()), tiff_data.size());
tiff_file.close();
ortc::Tensor<uint8_t> tiff_tensor({static_cast<int64_t>(tiff_data.size())}, tiff_data.data());
ortc::Tensor<uint8_t> out_tensor{&CppAllocator::Instance()};
auto status = image_decoder.Compute(tiff_tensor, out_tensor);
ASSERT_TRUE(status.IsOk()) << status.ToString();
ASSERT_EQ(out_tensor.Shape(), std::vector<int64_t>({207, 346, 3}));
auto out_range = out_tensor.Data() + 0;
ASSERT_EQ(std::vector<uint8_t>(out_range, out_range + 12),
std::vector<uint8_t>({66, 74, 57, 66, 74, 57, 66, 74, 57, 74, 66, 49}));
out_range = out_tensor.Data() + 477 * 3;
ASSERT_EQ(std::vector<uint8_t>(out_range, out_range + 12),
std::vector<uint8_t>({41, 41, 41, 33, 33, 33, 41, 41, 49, 33, 33, 33}));
out_range = out_tensor.Data() + 103 * 346 * 3;
ASSERT_EQ(std::vector<uint8_t>(out_range, out_range + 12),
std::vector<uint8_t>({24, 24, 24, 16, 16, 24, 16, 16, 24, 16, 16, 24}));
out_range = out_tensor.Data() + 206 * 346 * 3 + 342 * 3;
ASSERT_EQ(std::vector<uint8_t>(out_range, out_range + 12),
std::vector<uint8_t>({82, 66, 49, 74, 66, 57, 74, 66, 49, 82, 74, 57}));
}
#endif
#endif