chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,216 @@
|
||||
set(the_description "Image I/O")
|
||||
ocv_add_module(imgcodecs opencv_imgproc WRAP java objc python)
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# CMake file for imgcodecs. See root CMakeLists.txt
|
||||
# Some parts taken from version of Hartmut Seichter, HIT Lab NZ.
|
||||
# Jose Luis Blanco, 2008
|
||||
# ----------------------------------------------------------------------------
|
||||
|
||||
ocv_clear_vars(GRFMT_LIBS)
|
||||
|
||||
if(HAVE_WINRT_CX AND NOT WINRT)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /ZW")
|
||||
endif()
|
||||
|
||||
if (HAVE_AVIF)
|
||||
ocv_include_directories(${AVIF_INCLUDE_DIR})
|
||||
list(APPEND GRFMT_LIBS ${AVIF_LIBRARY})
|
||||
endif()
|
||||
|
||||
if(HAVE_JPEG)
|
||||
ocv_include_directories(${JPEG_INCLUDE_DIR} ${${JPEG_LIBRARY}_BINARY_DIR})
|
||||
list(APPEND GRFMT_LIBS ${JPEG_LIBRARIES})
|
||||
endif()
|
||||
|
||||
if(HAVE_WEBP)
|
||||
add_definitions(-DHAVE_WEBP)
|
||||
ocv_include_directories(${WEBP_INCLUDE_DIR})
|
||||
list(APPEND GRFMT_LIBS ${WEBP_LIBRARIES})
|
||||
endif()
|
||||
|
||||
if(HAVE_SPNG)
|
||||
add_definitions(${SPNG_DEFINITIONS})
|
||||
ocv_include_directories(${SPNG_INCLUDE_DIR})
|
||||
list(APPEND GRFMT_LIBS ${SPNG_LIBRARY})
|
||||
endif()
|
||||
|
||||
if(HAVE_PNG)
|
||||
add_definitions(${PNG_DEFINITIONS})
|
||||
ocv_include_directories(${PNG_INCLUDE_DIR})
|
||||
list(APPEND GRFMT_LIBS ${PNG_LIBRARIES})
|
||||
endif()
|
||||
|
||||
if(HAVE_GDCM)
|
||||
ocv_include_directories(${GDCM_INCLUDE_DIRS})
|
||||
list(APPEND GRFMT_LIBS ${GDCM_LIBRARIES})
|
||||
endif()
|
||||
|
||||
if(HAVE_TIFF)
|
||||
ocv_include_directories(${TIFF_INCLUDE_DIR})
|
||||
list(APPEND GRFMT_LIBS ${TIFF_LIBRARIES})
|
||||
endif()
|
||||
|
||||
if(HAVE_JPEGXL)
|
||||
ocv_include_directories(${JPEGXL_INCLUDE_DIRS})
|
||||
message(STATUS "JPEGXL_INCLUDE_DIRS: ${JPEGXL_INCLUDE_DIRS}")
|
||||
list(APPEND GRFMT_LIBS ${JPEGXL_LIBRARIES})
|
||||
endif()
|
||||
|
||||
if(HAVE_OPENJPEG)
|
||||
ocv_include_directories(${OPENJPEG_INCLUDE_DIRS})
|
||||
list(APPEND GRFMT_LIBS ${OPENJPEG_LIBRARIES})
|
||||
endif()
|
||||
|
||||
if(HAVE_JASPER)
|
||||
ocv_include_directories(${JASPER_INCLUDE_DIR})
|
||||
list(APPEND GRFMT_LIBS ${JASPER_LIBRARIES})
|
||||
if(OPENCV_IO_FORCE_JASPER)
|
||||
add_definitions(-DOPENCV_IMGCODECS_FORCE_JASPER=1)
|
||||
else()
|
||||
message(STATUS "imgcodecs: Jasper codec is disabled in runtime. Details: https://github.com/opencv/opencv/issues/14058")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(HAVE_OPENEXR)
|
||||
include_directories(SYSTEM ${OPENEXR_INCLUDE_PATHS})
|
||||
list(APPEND GRFMT_LIBS ${OPENEXR_LIBRARIES})
|
||||
if(OPENCV_IO_FORCE_OPENEXR
|
||||
OR NOT BUILD_OPENEXR # external OpenEXR versions are not disabled
|
||||
)
|
||||
add_definitions(-DOPENCV_IMGCODECS_USE_OPENEXR=1)
|
||||
else()
|
||||
message(STATUS "imgcodecs: OpenEXR codec is disabled in runtime. Details: https://github.com/opencv/opencv/issues/21326")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(HAVE_PNG OR HAVE_TIFF OR HAVE_OPENEXR OR HAVE_SPNG OR HAVE_JPEGXL)
|
||||
ocv_include_directories(${ZLIB_INCLUDE_DIRS})
|
||||
list(APPEND GRFMT_LIBS ${ZLIB_LIBRARIES})
|
||||
endif()
|
||||
|
||||
if(HAVE_GDAL)
|
||||
include_directories(SYSTEM ${GDAL_INCLUDE_DIR})
|
||||
list(APPEND GRFMT_LIBS ${GDAL_LIBRARY})
|
||||
endif()
|
||||
|
||||
if(HAVE_IMGCODEC_GIF)
|
||||
add_definitions(-DHAVE_IMGCODEC_GIF)
|
||||
endif()
|
||||
|
||||
if(HAVE_IMGCODEC_HDR)
|
||||
add_definitions(-DHAVE_IMGCODEC_HDR)
|
||||
endif()
|
||||
|
||||
if(HAVE_IMGCODEC_SUNRASTER)
|
||||
add_definitions(-DHAVE_IMGCODEC_SUNRASTER)
|
||||
endif()
|
||||
|
||||
if(HAVE_IMGCODEC_PXM)
|
||||
add_definitions(-DHAVE_IMGCODEC_PXM)
|
||||
endif()
|
||||
|
||||
if (HAVE_IMGCODEC_PFM)
|
||||
add_definitions(-DHAVE_IMGCODEC_PFM)
|
||||
endif()
|
||||
|
||||
file(GLOB grfmt_hdrs ${CMAKE_CURRENT_LIST_DIR}/src/grfmt*.hpp)
|
||||
file(GLOB grfmt_srcs ${CMAKE_CURRENT_LIST_DIR}/src/grfmt*.cpp)
|
||||
|
||||
list(APPEND grfmt_hdrs ${CMAKE_CURRENT_LIST_DIR}/src/bitstrm.hpp)
|
||||
list(APPEND grfmt_srcs ${CMAKE_CURRENT_LIST_DIR}/src/bitstrm.cpp)
|
||||
list(APPEND grfmt_hdrs ${CMAKE_CURRENT_LIST_DIR}/src/rgbe.hpp)
|
||||
list(APPEND grfmt_srcs ${CMAKE_CURRENT_LIST_DIR}/src/rgbe.cpp)
|
||||
list(APPEND grfmt_hdrs ${CMAKE_CURRENT_LIST_DIR}/src/exif.hpp)
|
||||
list(APPEND grfmt_srcs ${CMAKE_CURRENT_LIST_DIR}/src/exif.cpp)
|
||||
|
||||
source_group("Src\\grfmts" FILES ${grfmt_hdrs} ${grfmt_srcs})
|
||||
|
||||
set(imgcodecs_hdrs
|
||||
${CMAKE_CURRENT_LIST_DIR}/src/precomp.hpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/src/utils.hpp
|
||||
)
|
||||
|
||||
set(imgcodecs_srcs
|
||||
${CMAKE_CURRENT_LIST_DIR}/src/loadsave.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/src/utils.cpp
|
||||
)
|
||||
|
||||
file(GLOB imgcodecs_ext_hdrs
|
||||
"${CMAKE_CURRENT_LIST_DIR}/include/opencv2/*.hpp"
|
||||
"${CMAKE_CURRENT_LIST_DIR}/include/opencv2/${name}/*.hpp"
|
||||
"${CMAKE_CURRENT_LIST_DIR}/include/opencv2/${name}/*.h"
|
||||
"${CMAKE_CURRENT_LIST_DIR}/include/opencv2/${name}/legacy/*.h"
|
||||
)
|
||||
|
||||
if(APPLE OR APPLE_FRAMEWORK)
|
||||
list(APPEND imgcodecs_srcs ${CMAKE_CURRENT_LIST_DIR}/src/apple_conversions.h)
|
||||
list(APPEND imgcodecs_srcs ${CMAKE_CURRENT_LIST_DIR}/src/apple_conversions.mm)
|
||||
endif()
|
||||
if(IOS OR XROS)
|
||||
list(APPEND imgcodecs_srcs ${CMAKE_CURRENT_LIST_DIR}/src/ios_conversions.mm)
|
||||
list(APPEND IMGCODECS_LIBRARIES "-framework UIKit")
|
||||
endif()
|
||||
if(APPLE AND (NOT IOS) AND (NOT XROS))
|
||||
list(APPEND imgcodecs_srcs ${CMAKE_CURRENT_LIST_DIR}/src/macosx_conversions.mm)
|
||||
list(APPEND IMGCODECS_LIBRARIES "-framework AppKit")
|
||||
endif()
|
||||
if(APPLE_FRAMEWORK)
|
||||
list(APPEND IMGCODECS_LIBRARIES "-framework Accelerate" "-framework CoreGraphics" "-framework QuartzCore")
|
||||
endif()
|
||||
|
||||
if(TRUE)
|
||||
# these variables are set by 'ocv_append_build_options(IMGCODECS ...)'
|
||||
foreach(P ${IMGCODECS_INCLUDE_DIRS})
|
||||
ocv_include_directories(${P})
|
||||
endforeach()
|
||||
|
||||
foreach(P ${IMGCODECS_LIBRARY_DIRS})
|
||||
link_directories(${P})
|
||||
endforeach()
|
||||
endif()
|
||||
|
||||
source_group("Src" FILES ${imgcodecs_srcs} ${imgcodecs_hdrs})
|
||||
source_group("Include" FILES ${imgcodecs_ext_hdrs})
|
||||
ocv_set_module_sources(HEADERS ${imgcodecs_ext_hdrs} SOURCES ${imgcodecs_srcs} ${imgcodecs_hdrs} ${grfmt_srcs} ${grfmt_hdrs})
|
||||
ocv_module_include_directories()
|
||||
|
||||
ocv_create_module(${GRFMT_LIBS} ${IMGCODECS_LIBRARIES})
|
||||
|
||||
macro(ocv_imgcodecs_configure_target)
|
||||
if(APPLE)
|
||||
add_apple_compiler_options(${the_module})
|
||||
endif()
|
||||
|
||||
if(MSVC AND NOT BUILD_SHARED_LIBS AND BUILD_WITH_STATIC_CRT)
|
||||
set_target_properties(${the_module} PROPERTIES LINK_FLAGS "/NODEFAULTLIB:atlthunk.lib /NODEFAULTLIB:atlsd.lib /NODEFAULTLIB:libcmt.lib /DEBUG")
|
||||
endif()
|
||||
|
||||
ocv_warnings_disable(CMAKE_CXX_FLAGS -Wno-deprecated-declarations)
|
||||
endmacro()
|
||||
|
||||
if(NOT BUILD_opencv_world)
|
||||
ocv_imgcodecs_configure_target()
|
||||
endif()
|
||||
|
||||
ocv_add_accuracy_tests()
|
||||
if(TARGET opencv_test_imgcodecs AND HAVE_JASPER AND "$ENV{OPENCV_IO_ENABLE_JASPER}")
|
||||
ocv_target_compile_definitions(opencv_test_imgcodecs PRIVATE OPENCV_IMGCODECS_ENABLE_JASPER_TESTS=1)
|
||||
endif()
|
||||
if(TARGET opencv_test_imgcodecs AND HAVE_OPENEXR AND "$ENV{OPENCV_IO_ENABLE_OPENEXR}")
|
||||
ocv_target_compile_definitions(opencv_test_imgcodecs PRIVATE OPENCV_IMGCODECS_ENABLE_OPENEXR_TESTS=1)
|
||||
endif()
|
||||
if(TARGET opencv_test_imgcodecs AND ((HAVE_PNG AND NOT (PNG_VERSION_STRING VERSION_LESS "1.6.31")) OR HAVE_SPNG))
|
||||
# details: https://github.com/glennrp/libpng/commit/68cb0aaee3de6371b81a4613476d9b33e43e95b1
|
||||
ocv_target_compile_definitions(opencv_test_imgcodecs PRIVATE OPENCV_IMGCODECS_PNG_WITH_EXIF=1)
|
||||
endif()
|
||||
if(TARGET opencv_test_imgcodecs AND ((HAVE_PNG AND NOT (PNG_VERSION_STRING VERSION_LESS "1.6.45")) OR HAVE_SPNG))
|
||||
# cICP does not support in spng
|
||||
# cICP support added in libpng 1.6.45
|
||||
ocv_target_compile_definitions(opencv_test_imgcodecs PRIVATE OPENCV_IMGCODECS_PNG_WITH_cICP=1)
|
||||
endif()
|
||||
if(TARGET opencv_test_imgcodecs AND HAVE_PNG AND PNG_VERSION_STRING VERSION_LESS "1.6.0")
|
||||
# Old libpng (< 1.6.0) is known to have lower precision in internal RGB-to-Gray calculation for 16-bit images.
|
||||
ocv_target_compile_definitions(opencv_test_imgcodecs PRIVATE OPENCV_IMGCODECS_PNG_EPS_16BIT_GRAY=9)
|
||||
endif()
|
||||
ocv_add_perf_tests()
|
||||
@@ -0,0 +1,781 @@
|
||||
/*M///////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
|
||||
//
|
||||
// By downloading, copying, installing or using the software you agree to this license.
|
||||
// If you do not agree to this license, do not download, install,
|
||||
// copy or use the software.
|
||||
//
|
||||
//
|
||||
// License Agreement
|
||||
// For Open Source Computer Vision Library
|
||||
//
|
||||
// Copyright (C) 2000-2008, Intel Corporation, all rights reserved.
|
||||
// Copyright (C) 2009, Willow Garage Inc., all rights reserved.
|
||||
// Third party copyrights are property of their respective owners.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without modification,
|
||||
// are permitted provided that the following conditions are met:
|
||||
//
|
||||
// * Redistribution's of source code must retain the above copyright notice,
|
||||
// this list of conditions and the following disclaimer.
|
||||
//
|
||||
// * Redistribution's in binary form must reproduce the above copyright notice,
|
||||
// this list of conditions and the following disclaimer in the documentation
|
||||
// and/or other materials provided with the distribution.
|
||||
//
|
||||
// * The name of the copyright holders may not be used to endorse or promote products
|
||||
// derived from this software without specific prior written permission.
|
||||
//
|
||||
// This software is provided by the copyright holders and contributors "as is" and
|
||||
// any express or implied warranties, including, but not limited to, the implied
|
||||
// warranties of merchantability and fitness for a particular purpose are disclaimed.
|
||||
// In no event shall the Intel Corporation or contributors be liable for any direct,
|
||||
// indirect, incidental, special, exemplary, or consequential damages
|
||||
// (including, but not limited to, procurement of substitute goods or services;
|
||||
// loss of use, data, or profits; or business interruption) however caused
|
||||
// and on any theory of liability, whether in contract, strict liability,
|
||||
// or tort (including negligence or otherwise) arising in any way out of
|
||||
// the use of this software, even if advised of the possibility of such damage.
|
||||
//
|
||||
//M*/
|
||||
|
||||
#ifndef OPENCV_IMGCODECS_HPP
|
||||
#define OPENCV_IMGCODECS_HPP
|
||||
|
||||
#include "opencv2/core.hpp"
|
||||
|
||||
/**
|
||||
@defgroup imgcodecs Image file reading and writing
|
||||
@{
|
||||
@defgroup imgcodecs_flags Flags used for image file reading and writing
|
||||
@defgroup imgcodecs_ios iOS glue
|
||||
@defgroup imgcodecs_macosx MacOS(OSX) glue
|
||||
@}
|
||||
*/
|
||||
|
||||
//////////////////////////////// image codec ////////////////////////////////
|
||||
namespace cv
|
||||
{
|
||||
|
||||
//! @addtogroup imgcodecs
|
||||
//! @{
|
||||
|
||||
//! @addtogroup imgcodecs_flags
|
||||
//! @{
|
||||
|
||||
//! Imread flags
|
||||
//! @note IMREAD_COLOR_BGR (IMREAD_COLOR) and IMREAD_COLOR_RGB can not be set at the same time.
|
||||
enum ImreadModes {
|
||||
IMREAD_UNCHANGED = -1, //!< If set, return the loaded image as is (with alpha channel, otherwise it gets cropped). Ignore EXIF orientation.
|
||||
IMREAD_GRAYSCALE = 0, //!< If set, always convert image to the single channel grayscale image (codec internal conversion).
|
||||
IMREAD_COLOR_BGR = 1, //!< If set, always convert image to the 3 channel BGR color image.
|
||||
IMREAD_COLOR = 1, //!< Same as IMREAD_COLOR_BGR.
|
||||
IMREAD_ANYDEPTH = 2, //!< If set, return 16-bit/32-bit image when the input has the corresponding depth, otherwise convert it to 8-bit.
|
||||
IMREAD_ANYCOLOR = 4, //!< If set, the image is read in any possible color format.
|
||||
IMREAD_LOAD_GDAL = 8, //!< If set, use the gdal driver for loading the image.
|
||||
IMREAD_REDUCED_GRAYSCALE_2 = 16, //!< If set, always convert image to the single channel grayscale image and the image size reduced 1/2.
|
||||
IMREAD_REDUCED_COLOR_2 = 17, //!< If set, always convert image to the 3 channel BGR color image and the image size reduced 1/2.
|
||||
IMREAD_REDUCED_GRAYSCALE_4 = 32, //!< If set, always convert image to the single channel grayscale image and the image size reduced 1/4.
|
||||
IMREAD_REDUCED_COLOR_4 = 33, //!< If set, always convert image to the 3 channel BGR color image and the image size reduced 1/4.
|
||||
IMREAD_REDUCED_GRAYSCALE_8 = 64, //!< If set, always convert image to the single channel grayscale image and the image size reduced 1/8.
|
||||
IMREAD_REDUCED_COLOR_8 = 65, //!< If set, always convert image to the 3 channel BGR color image and the image size reduced 1/8.
|
||||
IMREAD_IGNORE_ORIENTATION = 128, //!< If set, do not rotate the image according to EXIF's orientation flag.
|
||||
IMREAD_COLOR_RGB = 256, //!< If set, always convert image to the 3 channel RGB color image.
|
||||
};
|
||||
|
||||
//! Imwrite flags
|
||||
enum ImwriteFlags {
|
||||
IMWRITE_JPEG_QUALITY = 1, //!< For JPEG, it can be a quality from 0 to 100 (the higher is the better). Default value is 95.
|
||||
IMWRITE_JPEG_PROGRESSIVE = 2, //!< Enable JPEG features, 0 or 1, default is False.
|
||||
IMWRITE_JPEG_OPTIMIZE = 3, //!< Enable JPEG features, 0 or 1, default is False.
|
||||
IMWRITE_JPEG_RST_INTERVAL = 4, //!< JPEG restart interval, 0 - 65535, default is 0 - no restart.
|
||||
IMWRITE_JPEG_LUMA_QUALITY = 5, //!< Separate luma quality level, 0 - 100, default is -1 - don't use. If JPEG_LIB_VERSION < 70, Not supported.
|
||||
IMWRITE_JPEG_CHROMA_QUALITY = 6, //!< Separate chroma quality level, 0 - 100, default is -1 - don't use. If JPEG_LIB_VERSION < 70, Not supported.
|
||||
IMWRITE_JPEG_SAMPLING_FACTOR = 7, //!< For JPEG, set sampling factor. See cv::ImwriteJPEGSamplingFactorParams.
|
||||
IMWRITE_PNG_COMPRESSION = 16, //!< For PNG, it can be the compression level from 0 to 9. A higher value means a smaller size and longer compression time. If specified, strategy is changed to IMWRITE_PNG_STRATEGY_DEFAULT (Z_DEFAULT_STRATEGY). Default value is 1 (best speed setting).
|
||||
IMWRITE_PNG_STRATEGY = 17, //!< For PNG, One of cv::ImwritePNGFlags, default is IMWRITE_PNG_STRATEGY_RLE.
|
||||
IMWRITE_PNG_BILEVEL = 18, //!< For PNG, Binary level PNG, 0 or 1, default is 0. For APNG, it is not supported.
|
||||
IMWRITE_PNG_FILTER = 19, //!< For PNG, One of cv::ImwritePNGFilterFlags, default is IMWRITE_PNG_FILTER_SUB. For APNG, it is not supported.
|
||||
IMWRITE_PNG_ZLIBBUFFER_SIZE = 20, //!< For PNG with libpng, sets the size of the internal zlib compression buffer in bytes, from 6 to 1048576(1024 KiB). Default is 8192(8 KiB). For normal use, 131072(128 KiB) or 262144(256 KiB) may be sufficient. If WITH_SPNG=ON, it is not supported. For APNG, it is not supported.
|
||||
IMWRITE_PXM_BINARY = 32, //!< For PPM, PGM, or PBM, it can be a binary format flag, 0 or 1. Default value is 1.
|
||||
IMWRITE_EXR_TYPE = (3 << 4) + 0 /* 48 */, //!< override EXR storage type (FLOAT (FP32) is default)
|
||||
IMWRITE_EXR_COMPRESSION = (3 << 4) + 1 /* 49 */, //!< override EXR compression type (ZIP_COMPRESSION = 3 is default)
|
||||
IMWRITE_EXR_DWA_COMPRESSION_LEVEL = (3 << 4) + 2 /* 50 */, //!< override EXR DWA compression level (45 is default)
|
||||
IMWRITE_WEBP_QUALITY = 64, //!< For WEBP, it can be a lossy quality from 1 to 100 (the higher is the better) for IMWRITE_WEBP_LOSSLESS_OFF. By default (without this parameter) or if quality > 100, IMWRITE_WEBP_LOSSLESS_ON is used instead.
|
||||
IMWRITE_WEBP_LOSSLESS_MODE = 65, //!< For WEBP, it can be a lossless compression strategy. See cv::ImwriteWEBPLosslessMode. Default is IMWRITE_WEBP_LOSSLESS_OFF. For Animated WEBP, it is not supported.
|
||||
IMWRITE_HDR_COMPRESSION = (5 << 4) + 0 /* 80 */, //!< specify HDR compression
|
||||
IMWRITE_PAM_TUPLETYPE = 128,//!< For PAM, sets the TUPLETYPE field to the corresponding string value that is defined for the format
|
||||
IMWRITE_TIFF_RESUNIT = 256,//!< For TIFF, use to specify which DPI resolution unit to set. See ImwriteTiffResolutionUnitFlags. Default is IMWRITE_TIFF_RESOLUTION_UNIT_INCH.
|
||||
IMWRITE_TIFF_XDPI = 257,//!< For TIFF, use to specify the X direction DPI
|
||||
IMWRITE_TIFF_YDPI = 258,//!< For TIFF, use to specify the Y direction DPI
|
||||
IMWRITE_TIFF_COMPRESSION = 259,//!< For TIFF, use to specify the image compression scheme. See cv::ImwriteTiffCompressionFlags. The compression scheme can be specified by this flag; the default is LZW compression, except for 32F depth where it is NONE
|
||||
IMWRITE_TIFF_ROWSPERSTRIP = 278,//!< For TIFF, use to specify the number of rows per strip.
|
||||
IMWRITE_TIFF_PREDICTOR = 317,//!< For TIFF, use to specify predictor. See cv::ImwriteTiffPredictorFlags. Default is IMWRITE_TIFF_PREDICTOR_HORIZONTAL .
|
||||
IMWRITE_JPEG2000_COMPRESSION_X1000 = 272,//!< For JPEG2000, use to specify the target compression rate (multiplied by 1000). The value can be from 0 to 1000. Default is 1000.
|
||||
IMWRITE_AVIF_QUALITY = 512,//!< For AVIF, it can be a quality between 0 and 100 (the higher the better). Default is 95.
|
||||
IMWRITE_AVIF_DEPTH = 513,//!< For AVIF, it can be 8, 10 or 12. If >8, it is stored/read as CV_16U. Default is 8.
|
||||
IMWRITE_AVIF_SPEED = 514,//!< For AVIF, it is between 0 (slowest) and 10(fastest). Default is 9.
|
||||
IMWRITE_JPEGXL_QUALITY = 640,//!< For JPEG XL, it can be a quality from 0 to 100 (the higher is the better). Default value is 95. If set, distance parameter is re-calicurated from quality level automatically. This parameter request libjxl v0.10 or later.
|
||||
IMWRITE_JPEGXL_EFFORT = 641,//!< For JPEG XL, encoder effort/speed level without affecting decoding speed; it is between 1 (fastest) and 10 (slowest). Default is 7.
|
||||
IMWRITE_JPEGXL_DISTANCE = 642,//!< For JPEG XL, distance level for lossy compression: target max butteraugli distance, lower = higher quality, 0 = lossless; range: 0 .. 25. Default is 1.
|
||||
IMWRITE_JPEGXL_DECODING_SPEED = 643,//!< For JPEG XL, decoding speed tier for the provided options; minimum is 0 (slowest to decode, best quality/density), and maximum is 4 (fastest to decode, at the cost of some quality/density). Default is 0.
|
||||
IMWRITE_BMP_COMPRESSION = 768, //!< For BMP, use to specify compress parameter for 32bpp image. Default is IMWRITE_BMP_COMPRESSION_BITFIELDS. See cv::ImwriteBMPCompressionFlags.
|
||||
IMWRITE_GIF_LOOP = 1024, //!< Not functional since 4.12.0. Replaced by cv::Animation::loop_count.
|
||||
IMWRITE_GIF_SPEED = 1025, //!< Not functional since 4.12.0. Replaced by cv::Animation::durations.
|
||||
IMWRITE_GIF_QUALITY = 1026, //!< For GIF, it can be a quality from 1 to 8. Default is 2. See cv::ImwriteGifCompressionFlags.
|
||||
IMWRITE_GIF_DITHER = 1027, //!< For GIF, it can be a quality from -1(most dither) to 3(no dither). Default is 0.
|
||||
IMWRITE_GIF_TRANSPARENCY = 1028, //!< For GIF, the alpha channel lower than this will be set to transparent. Default is 1.
|
||||
IMWRITE_GIF_COLORTABLE = 1029 //!< For GIF, 0 means global color table is used, 1 means local color table is used. Default is 0.
|
||||
};
|
||||
|
||||
enum ImwriteJPEGSamplingFactorParams {
|
||||
IMWRITE_JPEG_SAMPLING_FACTOR_411 = 0x411111, //!< 4x1,1x1,1x1
|
||||
IMWRITE_JPEG_SAMPLING_FACTOR_420 = 0x221111, //!< 2x2,1x1,1x1(Default)
|
||||
IMWRITE_JPEG_SAMPLING_FACTOR_422 = 0x211111, //!< 2x1,1x1,1x1
|
||||
IMWRITE_JPEG_SAMPLING_FACTOR_440 = 0x121111, //!< 1x2,1x1,1x1
|
||||
IMWRITE_JPEG_SAMPLING_FACTOR_444 = 0x111111 //!< 1x1,1x1,1x1(No subsampling)
|
||||
};
|
||||
|
||||
enum ImwriteTiffCompressionFlags {
|
||||
IMWRITE_TIFF_COMPRESSION_NONE = 1, //!< dump mode
|
||||
IMWRITE_TIFF_COMPRESSION_CCITTRLE = 2, //!< CCITT modified Huffman RLE
|
||||
IMWRITE_TIFF_COMPRESSION_CCITTFAX3 = 3, //!< CCITT Group 3 fax encoding
|
||||
IMWRITE_TIFF_COMPRESSION_CCITT_T4 = 3, //!< CCITT T.4 (TIFF 6 name)
|
||||
IMWRITE_TIFF_COMPRESSION_CCITTFAX4 = 4, //!< CCITT Group 4 fax encoding
|
||||
IMWRITE_TIFF_COMPRESSION_CCITT_T6 = 4, //!< CCITT T.6 (TIFF 6 name)
|
||||
IMWRITE_TIFF_COMPRESSION_LZW = 5, //!< Lempel-Ziv & Welch
|
||||
IMWRITE_TIFF_COMPRESSION_OJPEG = 6, //!< !6.0 JPEG
|
||||
IMWRITE_TIFF_COMPRESSION_JPEG = 7, //!< %JPEG DCT compression
|
||||
IMWRITE_TIFF_COMPRESSION_T85 = 9, //!< !TIFF/FX T.85 JBIG compression
|
||||
IMWRITE_TIFF_COMPRESSION_T43 = 10, //!< !TIFF/FX T.43 colour by layered JBIG compression
|
||||
IMWRITE_TIFF_COMPRESSION_NEXT = 32766, //!< NeXT 2-bit RLE
|
||||
IMWRITE_TIFF_COMPRESSION_CCITTRLEW = 32771, //!< #1 w/ word alignment
|
||||
IMWRITE_TIFF_COMPRESSION_PACKBITS = 32773, //!< Macintosh RLE
|
||||
IMWRITE_TIFF_COMPRESSION_THUNDERSCAN = 32809, //!< ThunderScan RLE
|
||||
IMWRITE_TIFF_COMPRESSION_IT8CTPAD = 32895, //!< IT8 CT w/padding
|
||||
IMWRITE_TIFF_COMPRESSION_IT8LW = 32896, //!< IT8 Linework RLE
|
||||
IMWRITE_TIFF_COMPRESSION_IT8MP = 32897, //!< IT8 Monochrome picture
|
||||
IMWRITE_TIFF_COMPRESSION_IT8BL = 32898, //!< IT8 Binary line art
|
||||
IMWRITE_TIFF_COMPRESSION_PIXARFILM = 32908, //!< Pixar companded 10bit LZW
|
||||
IMWRITE_TIFF_COMPRESSION_PIXARLOG = 32909, //!< Pixar companded 11bit ZIP
|
||||
IMWRITE_TIFF_COMPRESSION_DEFLATE = 32946, //!< Deflate compression, legacy tag
|
||||
IMWRITE_TIFF_COMPRESSION_ADOBE_DEFLATE = 8, //!< Deflate compression, as recognized by Adobe
|
||||
IMWRITE_TIFF_COMPRESSION_DCS = 32947, //!< Kodak DCS encoding
|
||||
IMWRITE_TIFF_COMPRESSION_JBIG = 34661, //!< ISO JBIG
|
||||
IMWRITE_TIFF_COMPRESSION_SGILOG = 34676, //!< SGI Log Luminance RLE
|
||||
IMWRITE_TIFF_COMPRESSION_SGILOG24 = 34677, //!< SGI Log 24-bit packed
|
||||
IMWRITE_TIFF_COMPRESSION_JP2000 = 34712, //!< Leadtools JPEG2000
|
||||
IMWRITE_TIFF_COMPRESSION_LERC = 34887, //!< ESRI Lerc codec: https://github.com/Esri/lerc
|
||||
IMWRITE_TIFF_COMPRESSION_LZMA = 34925, //!< LZMA2
|
||||
IMWRITE_TIFF_COMPRESSION_ZSTD = 50000, //!< ZSTD: WARNING not registered in Adobe-maintained registry
|
||||
IMWRITE_TIFF_COMPRESSION_WEBP = 50001, //!< WEBP: WARNING not registered in Adobe-maintained registry
|
||||
IMWRITE_TIFF_COMPRESSION_JXL = 50002 //!< JPEGXL: WARNING not registered in Adobe-maintained registry
|
||||
};
|
||||
|
||||
enum ImwriteTiffPredictorFlags {
|
||||
IMWRITE_TIFF_PREDICTOR_NONE = 1, //!< no prediction scheme used
|
||||
IMWRITE_TIFF_PREDICTOR_HORIZONTAL = 2, //!< horizontal differencing
|
||||
IMWRITE_TIFF_PREDICTOR_FLOATINGPOINT = 3 //!< floating point predictor
|
||||
};
|
||||
|
||||
enum ImwriteTiffResolutionUnitFlags {
|
||||
IMWRITE_TIFF_RESOLUTION_UNIT_NONE = 1, //!< no absolute unit of measurement.
|
||||
IMWRITE_TIFF_RESOLUTION_UNIT_INCH = 2, //!< inch
|
||||
IMWRITE_TIFF_RESOLUTION_UNIT_CENTIMETER = 3, //!< centimeter
|
||||
};
|
||||
|
||||
enum ImwriteEXRTypeFlags {
|
||||
// IMWRITE_EXR_TYPE_UNIT = 0, // not supported
|
||||
IMWRITE_EXR_TYPE_HALF = 1, //!< store as HALF (FP16)
|
||||
IMWRITE_EXR_TYPE_FLOAT = 2 //!< store as FP32 (default)
|
||||
};
|
||||
|
||||
enum ImwriteEXRCompressionFlags {
|
||||
IMWRITE_EXR_COMPRESSION_NO = 0, //!< no compression
|
||||
IMWRITE_EXR_COMPRESSION_RLE = 1, //!< run length encoding
|
||||
IMWRITE_EXR_COMPRESSION_ZIPS = 2, //!< zlib compression, one scan line at a time
|
||||
IMWRITE_EXR_COMPRESSION_ZIP = 3, //!< zlib compression, in blocks of 16 scan lines
|
||||
IMWRITE_EXR_COMPRESSION_PIZ = 4, //!< piz-based wavelet compression
|
||||
IMWRITE_EXR_COMPRESSION_PXR24 = 5, //!< lossy 24-bit float compression
|
||||
IMWRITE_EXR_COMPRESSION_B44 = 6, //!< lossy 4-by-4 pixel block compression, fixed compression rate
|
||||
IMWRITE_EXR_COMPRESSION_B44A = 7, //!< lossy 4-by-4 pixel block compression, flat fields are compressed more
|
||||
IMWRITE_EXR_COMPRESSION_DWAA = 8, //!< lossy DCT based compression, in blocks of 32 scanlines. More efficient for partial buffer access. Supported since OpenEXR 2.2.0.
|
||||
IMWRITE_EXR_COMPRESSION_DWAB = 9, //!< lossy DCT based compression, in blocks of 256 scanlines. More efficient space wise and faster to decode full frames than DWAA_COMPRESSION. Supported since OpenEXR 2.2.0.
|
||||
};
|
||||
|
||||
//! Imwrite PNG specific flags used to tune the compression algorithm.
|
||||
/** These flags will be modify the way of PNG image compression and will be passed to the underlying zlib processing stage.
|
||||
|
||||
- The effect of IMWRITE_PNG_STRATEGY_FILTERED is to force more Huffman coding and less string matching; it is somewhat intermediate between IMWRITE_PNG_STRATEGY_DEFAULT and IMWRITE_PNG_STRATEGY_HUFFMAN_ONLY.
|
||||
- IMWRITE_PNG_STRATEGY_RLE is designed to be almost as fast as IMWRITE_PNG_STRATEGY_HUFFMAN_ONLY, but give better compression for PNG image data.
|
||||
- The strategy parameter only affects the compression ratio but not the correctness of the compressed output even if it is not set appropriately.
|
||||
- IMWRITE_PNG_STRATEGY_FIXED prevents the use of dynamic Huffman codes, allowing for a simpler decoder for special applications.
|
||||
*/
|
||||
enum ImwritePNGFlags {
|
||||
IMWRITE_PNG_STRATEGY_DEFAULT = 0, //!< Use this value for normal data.
|
||||
IMWRITE_PNG_STRATEGY_FILTERED = 1, //!< Use this value for data produced by a filter (or predictor).Filtered data consists mostly of small values with a somewhat random distribution. In this case, the compression algorithm is tuned to compress them better.
|
||||
IMWRITE_PNG_STRATEGY_HUFFMAN_ONLY = 2, //!< Use this value to force Huffman encoding only (no string match).
|
||||
IMWRITE_PNG_STRATEGY_RLE = 3, //!< Use this value to limit match distances to one (run-length encoding).
|
||||
IMWRITE_PNG_STRATEGY_FIXED = 4 //!< Using this value prevents the use of dynamic Huffman codes, allowing for a simpler decoder for special applications.
|
||||
};
|
||||
|
||||
//! Imwrite PNG specific values for IMWRITE_PNG_FILTER parameter key
|
||||
enum ImwritePNGFilterFlags {
|
||||
IMWRITE_PNG_FILTER_NONE = 8, //!< Applies no filter to the PNG image (useful when you want to save the raw pixel data without any compression filter).
|
||||
IMWRITE_PNG_FILTER_SUB = 16, //!< Applies the "sub" filter, which calculates the difference between the current byte and the previous byte in the row.
|
||||
IMWRITE_PNG_FILTER_UP = 32, //!< applies the "up" filter, which calculates the difference between the current byte and the corresponding byte directly above it.
|
||||
IMWRITE_PNG_FILTER_AVG = 64, //!< applies the "average" filter, which calculates the average of the byte to the left and the byte above.
|
||||
IMWRITE_PNG_FILTER_PAETH = 128, //!< applies the "Paeth" filter, a more complex filter that predicts the next pixel value based on neighboring pixels.
|
||||
IMWRITE_PNG_FAST_FILTERS = (IMWRITE_PNG_FILTER_NONE | IMWRITE_PNG_FILTER_SUB | IMWRITE_PNG_FILTER_UP), //!< This is a combination of IMWRITE_PNG_FILTER_NONE, IMWRITE_PNG_FILTER_SUB, and IMWRITE_PNG_FILTER_UP, typically used for faster compression.
|
||||
IMWRITE_PNG_ALL_FILTERS = (IMWRITE_PNG_FAST_FILTERS | IMWRITE_PNG_FILTER_AVG | IMWRITE_PNG_FILTER_PAETH) //!< This combines all available filters (NONE, SUB, UP, AVG, and PAETH), which will attempt to apply all of them for the best possible compression.
|
||||
};
|
||||
|
||||
//! Imwrite PAM specific tupletype flags used to define the 'TUPLETYPE' field of a PAM file.
|
||||
enum ImwritePAMFlags {
|
||||
IMWRITE_PAM_FORMAT_NULL = 0,
|
||||
IMWRITE_PAM_FORMAT_BLACKANDWHITE = 1,
|
||||
IMWRITE_PAM_FORMAT_GRAYSCALE = 2,
|
||||
IMWRITE_PAM_FORMAT_GRAYSCALE_ALPHA = 3,
|
||||
IMWRITE_PAM_FORMAT_RGB = 4,
|
||||
IMWRITE_PAM_FORMAT_RGB_ALPHA = 5
|
||||
};
|
||||
|
||||
//! Imwrite WEBP specific values for IMWRITE_WEBP_LOSSLESS_MODE parameter key.
|
||||
enum ImwriteWEBPLosslessMode {
|
||||
IMWRITE_WEBP_LOSSLESS_OFF = 0, //!< Lossy compression mode. Uses IMWRITE_WEBP_QUALITY to control compression. (Default)
|
||||
//!< @note If IMWRITE_WEBP_QUALITY is not specified, it falls back to IMWRITE_WEBP_LOSSLESS_ON to maintain backward compatibility.
|
||||
IMWRITE_WEBP_LOSSLESS_ON = 1, //!< Standard lossless compression. May modify or discard RGB values of fully transparent pixels to improve compression ratio.
|
||||
IMWRITE_WEBP_LOSSLESS_PRESERVE_COLOR = 2, //!< Exact lossless compression. Preserves all RGB data even for pixels with 0 alpha (equivalent to WebP's exact flag).
|
||||
};
|
||||
|
||||
//! Imwrite HDR specific values for IMWRITE_HDR_COMPRESSION parameter key
|
||||
enum ImwriteHDRCompressionFlags {
|
||||
IMWRITE_HDR_COMPRESSION_NONE = 0,
|
||||
IMWRITE_HDR_COMPRESSION_RLE = 1
|
||||
};
|
||||
|
||||
//! Imwrite BMP specific values for IMWRITE_BMP_COMPRESSION parameter key.
|
||||
enum ImwriteBMPCompressionFlags {
|
||||
IMWRITE_BMP_COMPRESSION_RGB = 0, //!< Use BI_RGB. OpenCV v4.12.0 or before supports to encode with this compression only.
|
||||
IMWRITE_BMP_COMPRESSION_BITFIELDS = 3, //!< Use BI_BITFIELDS. OpenCV v4.13.0 or later can support to encode with this compression. (only for 32 BPP images)
|
||||
};
|
||||
|
||||
//! Imwrite GIF specific values for IMWRITE_GIF_QUALITY parameter key, if larger than 3, then its related to the size of the color table.
|
||||
enum ImwriteGIFCompressionFlags {
|
||||
IMWRITE_GIF_FAST_NO_DITHER = 1,
|
||||
IMWRITE_GIF_FAST_FLOYD_DITHER = 2,
|
||||
IMWRITE_GIF_COLORTABLE_SIZE_8 = 3,
|
||||
IMWRITE_GIF_COLORTABLE_SIZE_16 = 4,
|
||||
IMWRITE_GIF_COLORTABLE_SIZE_32 = 5,
|
||||
IMWRITE_GIF_COLORTABLE_SIZE_64 = 6,
|
||||
IMWRITE_GIF_COLORTABLE_SIZE_128 = 7,
|
||||
IMWRITE_GIF_COLORTABLE_SIZE_256 = 8
|
||||
};
|
||||
|
||||
enum ImageMetadataType
|
||||
{
|
||||
IMAGE_METADATA_UNKNOWN = -1, // Used when metadata type is unrecognized or not set
|
||||
|
||||
IMAGE_METADATA_EXIF = 0, // EXIF metadata (e.g., camera info, GPS, orientation)
|
||||
IMAGE_METADATA_XMP = 1, // XMP metadata (eXtensible Metadata Platform - Adobe format)
|
||||
IMAGE_METADATA_ICCP = 2, // ICC Profile (color profile for color management)
|
||||
IMAGE_METADATA_CICP = 3, // cICP Profile (video signal type)
|
||||
|
||||
IMAGE_METADATA_MAX = 3 // Highest valid index (usually used for bounds checking)
|
||||
};
|
||||
|
||||
//! @} imgcodecs_flags
|
||||
|
||||
/** @brief Represents an animation with multiple frames.
|
||||
The `Animation` struct is designed to store and manage data for animated sequences such as those from animated formats (e.g., GIF, AVIF, APNG, WebP).
|
||||
It provides support for looping, background color settings, frame timing, and frame storage.
|
||||
*/
|
||||
struct CV_EXPORTS_W_SIMPLE Animation
|
||||
{
|
||||
//! Number of times the animation should loop. 0 means infinite looping.
|
||||
/*! @note At some file format, when N is set, whether it is displayed N or N+1 times depends on the implementation of the user application. This loop times behaviour has not been documented clearly.
|
||||
* - (GIF) See https://issues.chromium.org/issues/40459899
|
||||
* And animated GIF with loop is extended with the Netscape Application Block(NAB), which it not a part of GIF89a specification. See https://en.wikipedia.org/wiki/GIF#Animated_GIF .
|
||||
* - (WebP) See https://issues.chromium.org/issues/41276895
|
||||
*/
|
||||
CV_PROP_RW int loop_count;
|
||||
//! Background color of the animation in BGRA format.
|
||||
CV_PROP_RW Scalar bgcolor;
|
||||
//! Duration for each frame in milliseconds.
|
||||
/*! @note (GIF) Due to file format limitation
|
||||
* - Durations must be multiples of 10 milliseconds. Any provided value will be rounded down to the nearest 10ms (e.g., 88ms → 80ms).
|
||||
* - 0ms(or smaller than expected in user application) duration may cause undefined behavior, e.g. it is handled with default duration.
|
||||
* - Over 65535 * 10 milliseconds duration is not supported.
|
||||
*/
|
||||
CV_PROP_RW std::vector<int> durations;
|
||||
//! Vector of frames, where each Mat represents a single frame.
|
||||
CV_PROP_RW std::vector<Mat> frames;
|
||||
//! image that can be used for the format in addition to the animation or if animation is not supported in the reader (like in PNG).
|
||||
CV_PROP_RW Mat still_image;
|
||||
|
||||
/** @brief Constructs an Animation object with optional loop count and background color.
|
||||
|
||||
@param loopCount An integer representing the number of times the animation should loop:
|
||||
- `0` (default) indicates infinite looping, meaning the animation will replay continuously.
|
||||
- Positive values denote finite repeat counts, allowing the animation to play a limited number of times.
|
||||
- If a negative value or a value beyond the maximum of `0xffff` (65535) is provided, it is reset to `0`
|
||||
(infinite looping) to maintain valid bounds.
|
||||
|
||||
@param bgColor A `Scalar` object representing the background color in BGR format:
|
||||
- Defaults to `Scalar()`, indicating an empty color (usually transparent if supported).
|
||||
- This background color provides a solid fill behind frames that have transparency, ensuring a consistent display appearance.
|
||||
*/
|
||||
CV_WRAP Animation(int loopCount = 0, Scalar bgColor = Scalar());
|
||||
};
|
||||
|
||||
/** @brief Loads an image from a file.
|
||||
|
||||
@anchor imread
|
||||
|
||||
The `imread` function loads an image from the specified file and returns OpenCV matrix. If the image cannot be
|
||||
read (because of a missing file, improper permissions, or unsupported/invalid format), the function
|
||||
returns an empty matrix.
|
||||
|
||||
Currently, the following file formats are supported:
|
||||
|
||||
- Windows bitmaps - \*.bmp, \*.dib (always supported)
|
||||
- GIF files - \*.gif (always supported)
|
||||
- JPEG files - \*.jpeg, \*.jpg, \*.jpe (see the *Note* section)
|
||||
- JPEG 2000 files - \*.jp2 (see the *Note* section)
|
||||
- Portable Network Graphics - \*.png (see the *Note* section)
|
||||
- WebP - \*.webp (see the *Note* section)
|
||||
- AVIF - \*.avif (see the *Note* section)
|
||||
- Portable image format - \*.pbm, \*.pgm, \*.ppm, \*.pxm, \*.pnm (always supported)
|
||||
- PFM files - \*.pfm (see the *Note* section)
|
||||
- Sun rasters - \*.sr, \*.ras (always supported)
|
||||
- TIFF files - \*.tiff, \*.tif (see the *Note* section)
|
||||
- OpenEXR Image files - \*.exr (see the *Note* section)
|
||||
- Radiance HDR - \*.hdr, \*.pic (always supported)
|
||||
- Raster and Vector geospatial data supported by GDAL (see the *Note* section)
|
||||
|
||||
@note
|
||||
- The function determines the type of an image by its content, not by the file extension.
|
||||
- In the case of color images, the decoded images will have the channels stored in **B G R** order.
|
||||
- When using IMREAD_GRAYSCALE, the codec's internal grayscale conversion will be used, if available.
|
||||
Results may differ from the output of cvtColor().
|
||||
- On Microsoft Windows\* and Mac OS\*, the codecs shipped with OpenCV (libjpeg, libpng, libtiff,
|
||||
and libjasper) are used by default. So, OpenCV can always read JPEGs, PNGs, and TIFFs. On Mac OS,
|
||||
there is also an option to use native Mac OS image readers. However, beware that currently these
|
||||
native image loaders give images with different pixel values because of the color management embedded
|
||||
into Mac OS.
|
||||
- On Linux\*, BSD flavors, and other Unix-like open-source operating systems, OpenCV looks for
|
||||
codecs supplied with the OS. Ensure the relevant packages are installed (including development
|
||||
files, such as "libjpeg-dev" in Debian\* and Ubuntu\*) to get codec support, or turn
|
||||
on the OPENCV_BUILD_3RDPARTY_LIBS flag in CMake.
|
||||
- If the *WITH_GDAL* flag is set to true in CMake and @ref IMREAD_LOAD_GDAL is used to load the image,
|
||||
the [GDAL](http://www.gdal.org) driver will be used to decode the image, supporting
|
||||
[Raster](http://www.gdal.org/formats_list.html) and [Vector](http://www.gdal.org/ogr_formats.html) formats.
|
||||
- If EXIF information is embedded in the image file, the EXIF orientation will be taken into account,
|
||||
and thus the image will be rotated accordingly unless the flags @ref IMREAD_IGNORE_ORIENTATION
|
||||
or @ref IMREAD_UNCHANGED are passed.
|
||||
- Use the IMREAD_UNCHANGED flag to preserve the floating-point values from PFM images.
|
||||
- By default, the number of pixels must be less than 2^30. This limit can be changed by setting
|
||||
the environment variable `OPENCV_IO_MAX_IMAGE_PIXELS`. See @ref tutorial_env_reference.
|
||||
|
||||
@param filename Name of the file to be loaded.
|
||||
@param flags Flag that can take values of cv::ImreadModes, default with cv::IMREAD_COLOR_BGR.
|
||||
*/
|
||||
CV_EXPORTS_W Mat imread( const String& filename, int flags = IMREAD_COLOR_BGR );
|
||||
|
||||
/** @brief Loads an image from a file.
|
||||
|
||||
This is an overloaded member function, provided for convenience. It differs from the above function only in what argument(s) it accepts and the return value.
|
||||
@param filename Name of file to be loaded.
|
||||
@param dst object in which the image will be loaded.
|
||||
@param flags Flag that can take values of cv::ImreadModes, default with cv::IMREAD_COLOR_BGR.
|
||||
@note
|
||||
The image passing through the img parameter can be pre-allocated. The memory is reused if the shape and the type match with the load image.
|
||||
*/
|
||||
CV_EXPORTS_W void imread( const String& filename, OutputArray dst, int flags = IMREAD_COLOR_BGR );
|
||||
|
||||
/** @brief Reads an image from a file along with associated metadata.
|
||||
|
||||
This function behaves similarly to cv::imread(), loading an image from the specified file.
|
||||
In addition to the image pixel data, it also attempts to extract any available metadata
|
||||
embedded in the file (such as EXIF, XMP, etc.), depending on file format support.
|
||||
|
||||
@note In the case of color images, the decoded images will have the channels stored in **B G R** order.
|
||||
@param filename Name of the file to be loaded.
|
||||
@param metadataTypes Output vector with types of metadata chunks returned in metadata, see ImageMetadataType.
|
||||
@param metadata Output vector of vectors or vector of matrices to store the retrieved metadata.
|
||||
@param flags Flag that can take values of cv::ImreadModes, default with cv::IMREAD_ANYCOLOR.
|
||||
|
||||
@return The loaded image as a cv::Mat object. If the image cannot be read, the function returns an empty matrix.
|
||||
*/
|
||||
CV_EXPORTS_W Mat imreadWithMetadata( const String& filename, CV_OUT std::vector<int>& metadataTypes,
|
||||
OutputArrayOfArrays metadata, int flags = IMREAD_ANYCOLOR);
|
||||
|
||||
/** @brief Loads a multi-page image from a file.
|
||||
|
||||
The function imreadmulti loads a multi-page image from the specified file into a vector of Mat objects.
|
||||
@param filename Name of file to be loaded.
|
||||
@param mats A vector of Mat objects holding each page.
|
||||
@param flags Flag that can take values of cv::ImreadModes, default with cv::IMREAD_ANYCOLOR.
|
||||
@sa cv::imread
|
||||
*/
|
||||
CV_EXPORTS_W bool imreadmulti(const String& filename, CV_OUT std::vector<Mat>& mats, int flags = IMREAD_ANYCOLOR);
|
||||
|
||||
/** @brief Loads images of a multi-page image from a file.
|
||||
|
||||
The function imreadmulti loads a specified range from a multi-page image from the specified file into a vector of Mat objects.
|
||||
@param filename Name of file to be loaded.
|
||||
@param mats A vector of Mat objects holding each page.
|
||||
@param start Start index of the image to load
|
||||
@param count Count number of images to load
|
||||
@param flags Flag that can take values of cv::ImreadModes, default with cv::IMREAD_ANYCOLOR.
|
||||
@sa cv::imread
|
||||
*/
|
||||
CV_EXPORTS_W bool imreadmulti(const String& filename, CV_OUT std::vector<Mat>& mats, int start, int count, int flags = IMREAD_ANYCOLOR);
|
||||
|
||||
/** @example samples/cpp/tutorial_code/imgcodecs/animations.cpp
|
||||
An example to show usage of cv::imreadanimation and cv::imwriteanimation functions.
|
||||
Check @ref tutorial_animations "the corresponding tutorial" for more details
|
||||
*/
|
||||
|
||||
/** @brief Loads frames from an animated image file into an Animation structure.
|
||||
|
||||
The function imreadanimation loads frames from an animated image file (e.g., GIF, AVIF, APNG, WEBP) into the provided Animation struct.
|
||||
|
||||
@param filename A string containing the path to the file.
|
||||
@param animation A reference to an Animation structure where the loaded frames will be stored. It should be initialized before the function is called.
|
||||
@param start The index of the first frame to load. This is optional and defaults to 0.
|
||||
@param count The number of frames to load. This is optional and defaults to 32767.
|
||||
|
||||
@return Returns true if the file was successfully loaded and frames were extracted; returns false otherwise.
|
||||
*/
|
||||
CV_EXPORTS_W bool imreadanimation(const String& filename, CV_OUT Animation& animation, int start = 0, int count = INT16_MAX);
|
||||
|
||||
/** @brief Loads frames from an animated image buffer into an Animation structure.
|
||||
|
||||
The function imdecodeanimation loads frames from an animated image buffer (e.g., GIF, AVIF, APNG, WEBP) into the provided Animation struct.
|
||||
|
||||
@param buf A reference to an InputArray containing the image buffer.
|
||||
@param animation A reference to an Animation structure where the loaded frames will be stored. It should be initialized before the function is called.
|
||||
@param start The index of the first frame to load. This is optional and defaults to 0.
|
||||
@param count The number of frames to load. This is optional and defaults to 32767.
|
||||
|
||||
@return Returns true if the buffer was successfully loaded and frames were extracted; returns false otherwise.
|
||||
*/
|
||||
CV_EXPORTS_W bool imdecodeanimation(InputArray buf, CV_OUT Animation& animation, int start = 0, int count = INT16_MAX);
|
||||
|
||||
/** @brief Saves an Animation to a specified file.
|
||||
|
||||
The function imwriteanimation saves the provided Animation data to the specified file in an animated format.
|
||||
Supported formats depend on the implementation and may include formats like GIF, AVIF, APNG, or WEBP.
|
||||
|
||||
@param filename The name of the file where the animation will be saved. The file extension determines the format.
|
||||
@param animation A constant reference to an Animation struct containing the frames and metadata to be saved.
|
||||
@param params Optional format-specific parameters encoded as pairs (paramId_1, paramValue_1, paramId_2, paramValue_2, ...).
|
||||
These parameters are used to specify additional options for the encoding process. Refer to `cv::ImwriteFlags` for details on possible parameters.
|
||||
|
||||
@return Returns true if the animation was successfully saved; returns false otherwise.
|
||||
*/
|
||||
CV_EXPORTS_W bool imwriteanimation(const String& filename, const Animation& animation, const std::vector<int>& params = std::vector<int>());
|
||||
|
||||
/** @brief Encodes an Animation to a memory buffer.
|
||||
|
||||
The function imencodeanimation encodes the provided Animation data into a memory
|
||||
buffer in an animated format. Supported formats depend on the implementation and
|
||||
may include formats like GIF, AVIF, APNG, or WEBP.
|
||||
|
||||
@param ext The file extension that determines the format of the encoded data.
|
||||
@param animation A constant reference to an Animation struct containing the
|
||||
frames and metadata to be encoded.
|
||||
@param buf A reference to a vector of unsigned chars where the encoded data will
|
||||
be stored.
|
||||
@param params Optional format-specific parameters encoded as pairs (paramId_1,
|
||||
paramValue_1, paramId_2, paramValue_2, ...). These parameters are used to
|
||||
specify additional options for the encoding process. Refer to `cv::ImwriteFlags`
|
||||
for details on possible parameters.
|
||||
|
||||
@return Returns true if the animation was successfully encoded; returns false otherwise.
|
||||
*/
|
||||
CV_EXPORTS_W bool imencodeanimation(const String& ext, const Animation& animation, CV_OUT std::vector<uchar>& buf, const std::vector<int>& params = std::vector<int>());
|
||||
|
||||
/** @brief Returns the number of images inside the given file
|
||||
|
||||
The function imcount returns the number of pages in a multi-page image (e.g. TIFF), the number of frames in an animation (e.g. AVIF), and 1 otherwise.
|
||||
If the image cannot be decoded, 0 is returned.
|
||||
@param filename Name of file to be loaded.
|
||||
@param flags Flag that can take values of cv::ImreadModes, default with cv::IMREAD_ANYCOLOR.
|
||||
@todo when cv::IMREAD_LOAD_GDAL flag used the return value will be 0 or 1 because OpenCV's GDAL decoder doesn't support multi-page reading yet.
|
||||
*/
|
||||
CV_EXPORTS_W size_t imcount(const String& filename, int flags = IMREAD_ANYCOLOR);
|
||||
|
||||
/** @brief Saves an image to a specified file.
|
||||
|
||||
The function imwrite saves the image to the specified file. The image format is chosen based on the
|
||||
filename extension (see cv::imread for the list of extensions). In general, only 8-bit unsigned (CV_8U)
|
||||
single-channel or 3-channel (with 'BGR' channel order) images
|
||||
can be saved using this function, with these exceptions:
|
||||
|
||||
- With BMP encoder, 8-bit unsigned (CV_8U) images can be saved.
|
||||
- BMP images with an alpha channel can be saved using this function.
|
||||
To achieve this, create an 8-bit 4-channel (CV_8UC4) BGRA image, ensuring the alpha channel is the last component.
|
||||
Fully transparent pixels should have an alpha value of 0, while fully opaque pixels should have an alpha value of 255.
|
||||
OpenCV v4.13.0 or later use BI_BITFIELDS compression as default. See IMWRITE_BMP_COMPRESSION.
|
||||
- With OpenEXR encoder, only 32-bit float (CV_32F) images can be saved. More than 4 channels can be saved. (imread can load it then.)
|
||||
- 8-bit unsigned (CV_8U) images are not supported.
|
||||
- With Radiance HDR encoder, non 64-bit float (CV_64F) images can be saved.
|
||||
- All images will be converted to 32-bit float (CV_32F).
|
||||
- With JPEG 2000 encoder, 8-bit unsigned (CV_8U) and 16-bit unsigned (CV_16U) images can be saved.
|
||||
- With JPEG XL encoder, 8-bit unsigned (CV_8U), 16-bit unsigned (CV_16U) and 32-bit float(CV_32F) images can be saved.
|
||||
- JPEG XL images with an alpha channel can be saved using this function.
|
||||
To achieve this, create an 8-bit 4-channel (CV_8UC4) / 16-bit 4-channel (CV_16UC4) / 32-bit float 4-channel (CV_32FC4) BGRA image, ensuring the alpha channel is the last component.
|
||||
Fully transparent pixels should have an alpha value of 0, while fully opaque pixels should have an alpha value of 255/65535/1.0.
|
||||
- With PAM encoder, 8-bit unsigned (CV_8U) and 16-bit unsigned (CV_16U) images can be saved.
|
||||
- With PNG encoder, 8-bit unsigned (CV_8U) and 16-bit unsigned (CV_16U) images can be saved.
|
||||
- PNG images with an alpha channel can be saved using this function.
|
||||
To achieve this, create an 8-bit 4-channel (CV_8UC4) / 16-bit 4-channel (CV_16UC4) BGRA image, ensuring the alpha channel is the last component.
|
||||
Fully transparent pixels should have an alpha value of 0, while fully opaque pixels should have an alpha value of 255/65535(see the code sample below).
|
||||
- With PGM/PPM encoder, 8-bit unsigned (CV_8U) and 16-bit unsigned (CV_16U) images can be saved.
|
||||
- With TIFF encoder, 8-bit unsigned (CV_8U), 8-bit signed (CV_8S),
|
||||
16-bit unsigned (CV_16U), 16-bit signed (CV_16S),
|
||||
32-bit signed (CV_32S),
|
||||
32-bit float (CV_32F) and 64-bit float (CV_64F) images can be saved.
|
||||
- Multiple images (vector of Mat) can be saved in TIFF format (see the code sample below).
|
||||
- 32-bit float 3-channel (CV_32FC3) TIFF images can be saved
|
||||
using the LogLuv high dynamic range encoding (4 bytes per pixel) through TIFF_COMPRESSION_SGILOG or
|
||||
(3 bytes per pixel) through TIFF_COMPRESSION_SGILOG24.
|
||||
- Other compression schemes (LZW...) are supported as well for 32F depth, but the efficiency might not
|
||||
be very good for the floating-point representation bit patterns.
|
||||
- With GIF encoder, 8-bit unsigned (CV_8U) images can be saved.
|
||||
- GIF images with an alpha channel can be saved using this function.
|
||||
To achieve this, create an 8-bit 4-channel (CV_8UC4) BGRA image, ensuring the alpha channel is the last component.
|
||||
Fully transparent pixels should have an alpha value of 0, while fully opaque pixels should have an alpha value of 255.
|
||||
- 8-bit single-channel images (CV_8UC1) are not supported due to GIF's limitation to indexed color formats.
|
||||
- With AVIF encoder, 8-bit unsigned (CV_8U) and 16-bit unsigned (CV_16U) images can be saved.
|
||||
- CV_16U images can be saved as only 10-bit or 12-bit (not 16-bit). See IMWRITE_AVIF_DEPTH.
|
||||
- AVIF images with an alpha channel can be saved using this function.
|
||||
To achieve this, create an 8-bit 4-channel (CV_8UC4) / 16-bit 4-channel (CV_16UC4) BGRA image, ensuring the alpha channel is the last component.
|
||||
Fully transparent pixels should have an alpha value of 0, while fully opaque pixels should have an alpha value of 255 (8-bit) / 1023 (10-bit) / 4095 (12-bit) (see the code sample below).
|
||||
|
||||
If the image format is not supported, the image will be converted to 8-bit unsigned (CV_8U) and saved that way.
|
||||
|
||||
If the format, depth or channel order is different, use
|
||||
Mat::convertTo and cv::cvtColor to convert it before saving. Or, use the universal FileStorage I/O
|
||||
functions to save the image to XML or YAML format.
|
||||
|
||||
The sample below shows how to create a BGRA image, how to set custom compression parameters and save it to a PNG file.
|
||||
It also demonstrates how to save multiple images in a TIFF file:
|
||||
@include snippets/imgcodecs_imwrite.cpp
|
||||
@param filename Name of the file.
|
||||
@param img (Mat or vector of Mat) Image or Images to be saved.
|
||||
@param params Format-specific parameters encoded as pairs (paramId_1, paramValue_1, paramId_2, paramValue_2, ... .) see cv::ImwriteFlags
|
||||
@return true if the image is successfully written to the specified file; false otherwise.
|
||||
*/
|
||||
CV_EXPORTS_W bool imwrite( const String& filename, InputArray img,
|
||||
const std::vector<int>& params = std::vector<int>());
|
||||
|
||||
/** @brief Saves an image to a specified file with metadata
|
||||
|
||||
The function imwriteWithMetadata saves the image to the specified file. It does the same thing as imwrite, but additionally writes metadata if the corresponding format supports it.
|
||||
@param filename Name of the file. As with imwrite, image format is determined by the file extension.
|
||||
@param img (Mat or vector of Mat) Image or Images to be saved.
|
||||
@param metadataTypes Vector with types of metadata chucks stored in metadata to write, see ImageMetadataType.
|
||||
@param metadata Vector of vectors or vector of matrices with chunks of metadata to store into the file
|
||||
@param params Format-specific parameters encoded as pairs (paramId_1, paramValue_1, paramId_2, paramValue_2, ... .) see cv::ImwriteFlags
|
||||
*/
|
||||
CV_EXPORTS_W bool imwriteWithMetadata( const String& filename, InputArray img,
|
||||
const std::vector<int>& metadataTypes,
|
||||
InputArrayOfArrays& metadata,
|
||||
const std::vector<int>& params = std::vector<int>());
|
||||
|
||||
//! @brief multi-image overload for bindings
|
||||
CV_WRAP static inline
|
||||
bool imwritemulti(const String& filename, InputArrayOfArrays img,
|
||||
const std::vector<int>& params = std::vector<int>())
|
||||
{
|
||||
return imwrite(filename, img, params);
|
||||
}
|
||||
|
||||
/** @brief Reads an image from a buffer in memory.
|
||||
|
||||
The function imdecode reads an image from the specified buffer in the memory. If the buffer is too short or
|
||||
contains invalid data, the function returns an empty matrix ( Mat::data==NULL ).
|
||||
|
||||
See cv::imread for the list of supported formats and flags description.
|
||||
|
||||
@note In the case of color images, the decoded images will have the channels stored in **B G R** order.
|
||||
@param buf Input array or vector of bytes.
|
||||
@param flags Flag that can take values of cv::ImreadModes.
|
||||
*/
|
||||
CV_EXPORTS_W Mat imdecode( InputArray buf, int flags );
|
||||
|
||||
/** @brief Reads an image from a memory buffer and extracts associated metadata.
|
||||
|
||||
This function decodes an image from the specified memory buffer. If the buffer is too short or
|
||||
contains invalid data, the function returns an empty matrix ( Mat::data==NULL ).
|
||||
|
||||
See cv::imread for the list of supported formats and flags description.
|
||||
|
||||
@note In the case of color images, the decoded images will have the channels stored in **B G R** order.
|
||||
@param buf Input array or vector of bytes containing the encoded image data.
|
||||
@param metadataTypes Output vector with types of metadata chucks returned in metadata, see cv::ImageMetadataType
|
||||
@param metadata Output vector of vectors or vector of matrices to store the retrieved metadata
|
||||
@param flags Flag that can take values of cv::ImreadModes, default with cv::IMREAD_ANYCOLOR.
|
||||
|
||||
@return The decoded image as a cv::Mat object. If decoding fails, the function returns an empty matrix.
|
||||
*/
|
||||
CV_EXPORTS_W Mat imdecodeWithMetadata( InputArray buf, CV_OUT std::vector<int>& metadataTypes,
|
||||
OutputArrayOfArrays metadata, int flags = IMREAD_ANYCOLOR );
|
||||
|
||||
/** @overload
|
||||
@param buf Input array or vector of bytes.
|
||||
@param flags Flag that can take values of cv::ImreadModes, default with cv::IMREAD_ANYCOLOR.
|
||||
@param dst The optional output placeholder for the decoded matrix. It can save the image
|
||||
reallocations when the function is called repeatedly for images of the same size. In case of decoder
|
||||
failure the function returns empty cv::Mat object, but does not release user-provided dst buffer.
|
||||
*/
|
||||
CV_EXPORTS Mat imdecode( InputArray buf, int flags, Mat* dst);
|
||||
|
||||
/** @brief Reads a multi-page image from a buffer in memory.
|
||||
|
||||
The function imdecodemulti reads a multi-page image from the specified buffer in the memory. If the buffer is too short or
|
||||
contains invalid data, the function returns false.
|
||||
|
||||
See cv::imreadmulti for the list of supported formats and flags description.
|
||||
|
||||
@note In the case of color images, the decoded images will have the channels stored in **B G R** order.
|
||||
@param buf Input array or vector of bytes.
|
||||
@param flags Flag that can take values of cv::ImreadModes.
|
||||
@param mats A vector of Mat objects holding each page, if more than one.
|
||||
@param range A continuous selection of pages.
|
||||
*/
|
||||
CV_EXPORTS_W bool imdecodemulti(InputArray buf, int flags, CV_OUT std::vector<Mat>& mats, const cv::Range& range = Range::all());
|
||||
|
||||
/** @brief Encodes an image into a memory buffer.
|
||||
|
||||
The function imencode compresses the image and stores it in the memory buffer that is resized to fit the
|
||||
result. See cv::imwrite for the list of supported formats and flags description.
|
||||
|
||||
@param ext File extension that defines the output format. Must include a leading period.
|
||||
@param img Image to be compressed.
|
||||
@param buf Output buffer resized to fit the compressed image.
|
||||
@param params Format-specific parameters. See cv::imwrite and cv::ImwriteFlags.
|
||||
*/
|
||||
CV_EXPORTS_W bool imencode( const String& ext, InputArray img,
|
||||
CV_OUT std::vector<uchar>& buf,
|
||||
const std::vector<int>& params = std::vector<int>());
|
||||
|
||||
/** @brief Encodes an image into a memory buffer.
|
||||
|
||||
The function imencode compresses the image and stores it in the memory buffer that is resized to fit the
|
||||
result. See cv::imwrite for the list of supported formats and flags description.
|
||||
|
||||
@param ext File extension that defines the output format. Must include a leading period.
|
||||
@param img Image to be compressed.
|
||||
@param metadataTypes Vector with types of metadata chucks stored in metadata to write, see ImageMetadataType.
|
||||
@param metadata Vector of vectors or vector of matrices with chunks of metadata to store into the file
|
||||
@param buf Output buffer resized to fit the compressed image.
|
||||
@param params Format-specific parameters. See cv::imwrite and cv::ImwriteFlags.
|
||||
*/
|
||||
CV_EXPORTS_W bool imencodeWithMetadata( const String& ext, InputArray img,
|
||||
const std::vector<int>& metadataTypes,
|
||||
InputArrayOfArrays metadata,
|
||||
CV_OUT std::vector<uchar>& buf,
|
||||
const std::vector<int>& params = std::vector<int>());
|
||||
|
||||
/** @brief Encodes array of images into a memory buffer.
|
||||
|
||||
The function is analog to cv::imencode for in-memory multi-page image compression.
|
||||
See cv::imwrite for the list of supported formats and flags description.
|
||||
|
||||
@param ext File extension that defines the output format. Must include a leading period.
|
||||
@param imgs Vector of images to be written.
|
||||
@param buf Output buffer resized to fit the compressed data.
|
||||
@param params Format-specific parameters. See cv::imwrite and cv::ImwriteFlags.
|
||||
*/
|
||||
CV_EXPORTS_W bool imencodemulti( const String& ext, InputArrayOfArrays imgs,
|
||||
CV_OUT std::vector<uchar>& buf,
|
||||
const std::vector<int>& params = std::vector<int>());
|
||||
|
||||
/** @brief Checks if the specified image file can be decoded by OpenCV.
|
||||
|
||||
The function haveImageReader checks if OpenCV is capable of reading the specified file.
|
||||
This can be useful for verifying support for a given image format before attempting to load an image.
|
||||
|
||||
@param filename The name of the file to be checked.
|
||||
@return true if an image reader for the specified file is available and the file can be opened, false otherwise.
|
||||
|
||||
@note The function checks the availability of image codecs that are either built into OpenCV or dynamically loaded.
|
||||
It does not load the image codec implementation and decode data, but uses signature check.
|
||||
If the file cannot be opened or the format is unsupported, the function will return false.
|
||||
|
||||
@sa cv::haveImageWriter, cv::imread, cv::imdecode
|
||||
*/
|
||||
CV_EXPORTS_W bool haveImageReader( const String& filename );
|
||||
|
||||
/** @brief Checks if the specified image file or specified file extension can be encoded by OpenCV.
|
||||
|
||||
The function haveImageWriter checks if OpenCV is capable of writing images with the specified file extension.
|
||||
This can be useful for verifying support for a given image format before attempting to save an image.
|
||||
|
||||
@param filename The name of the file or the file extension (e.g., ".jpg", ".png").
|
||||
It is recommended to provide the file extension rather than the full file name.
|
||||
@return true if an image writer for the specified extension is available, false otherwise.
|
||||
|
||||
@note The function checks the availability of image codecs that are either built into OpenCV or dynamically loaded.
|
||||
It does not check for the actual existence of the file but rather the ability to write files of the given type.
|
||||
|
||||
@sa cv::haveImageReader, cv::imwrite, cv::imencode
|
||||
*/
|
||||
CV_EXPORTS_W bool haveImageWriter( const String& filename );
|
||||
|
||||
/** @brief To read multi-page images on demand
|
||||
|
||||
The ImageCollection class provides iterator API to read multi-page images on demand. Create iterator
|
||||
to the collection of the images and iterate over the collection. Decode the necessary page with operator*.
|
||||
|
||||
The performance of page decoding is O(1) if collection is increment sequentially. If the user wants to access random page,
|
||||
then the time Complexity is O(n) because the collection has to be reinitialized every time in order to go to the correct page.
|
||||
However, the intermediate pages are not decoded during the process, so typically it's quite fast.
|
||||
This is required because multi-page codecs does not support going backwards.
|
||||
After decoding the one page, it is stored inside the collection cache. Hence, trying to get Mat object from already decoded page is O(1).
|
||||
If you need memory, you can use .releaseCache() method to release cached index.
|
||||
The space complexity is O(n) if all pages are decoded into memory. The user is able to decode and release images on demand.
|
||||
*/
|
||||
class CV_EXPORTS ImageCollection {
|
||||
public:
|
||||
struct CV_EXPORTS iterator {
|
||||
iterator(ImageCollection* col);
|
||||
iterator(ImageCollection* col, int end);
|
||||
Mat& operator*();
|
||||
Mat* operator->();
|
||||
iterator& operator++();
|
||||
iterator operator++(int);
|
||||
friend bool operator== (const iterator& a, const iterator& b) { return a.m_curr == b.m_curr; }
|
||||
friend bool operator!= (const iterator& a, const iterator& b) { return a.m_curr != b.m_curr; }
|
||||
|
||||
private:
|
||||
ImageCollection* m_pCollection;
|
||||
int m_curr;
|
||||
};
|
||||
|
||||
ImageCollection();
|
||||
ImageCollection(const String& filename, int flags);
|
||||
void init(const String& img, int flags);
|
||||
size_t size() const;
|
||||
const Mat& at(int index);
|
||||
const Mat& operator[](int index);
|
||||
void releaseCache(int index);
|
||||
iterator begin();
|
||||
iterator end();
|
||||
|
||||
class Impl;
|
||||
Ptr<Impl> getImpl();
|
||||
protected:
|
||||
Ptr<Impl> pImpl;
|
||||
};
|
||||
|
||||
//! @} imgcodecs
|
||||
|
||||
} // cv
|
||||
|
||||
#endif //OPENCV_IMGCODECS_HPP
|
||||
@@ -0,0 +1,48 @@
|
||||
/*M///////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
|
||||
//
|
||||
// By downloading, copying, installing or using the software you agree to this license.
|
||||
// If you do not agree to this license, do not download, install,
|
||||
// copy or use the software.
|
||||
//
|
||||
//
|
||||
// License Agreement
|
||||
// For Open Source Computer Vision Library
|
||||
//
|
||||
// Copyright (C) 2000-2008, Intel Corporation, all rights reserved.
|
||||
// Copyright (C) 2009, Willow Garage Inc., all rights reserved.
|
||||
// Copyright (C) 2013, OpenCV Foundation, all rights reserved.
|
||||
// Third party copyrights are property of their respective owners.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without modification,
|
||||
// are permitted provided that the following conditions are met:
|
||||
//
|
||||
// * Redistribution's of source code must retain the above copyright notice,
|
||||
// this list of conditions and the following disclaimer.
|
||||
//
|
||||
// * Redistribution's in binary form must reproduce the above copyright notice,
|
||||
// this list of conditions and the following disclaimer in the documentation
|
||||
// and/or other materials provided with the distribution.
|
||||
//
|
||||
// * The name of the copyright holders may not be used to endorse or promote products
|
||||
// derived from this software without specific prior written permission.
|
||||
//
|
||||
// This software is provided by the copyright holders and contributors "as is" and
|
||||
// any express or implied warranties, including, but not limited to, the implied
|
||||
// warranties of merchantability and fitness for a particular purpose are disclaimed.
|
||||
// In no event shall the Intel Corporation or contributors be liable for any direct,
|
||||
// indirect, incidental, special, exemplary, or consequential damages
|
||||
// (including, but not limited to, procurement of substitute goods or services;
|
||||
// loss of use, data, or profits; or business interruption) however caused
|
||||
// and on any theory of liability, whether in contract, strict liability,
|
||||
// or tort (including negligence or otherwise) arising in any way out of
|
||||
// the use of this software, even if advised of the possibility of such damage.
|
||||
//
|
||||
//M*/
|
||||
|
||||
#ifdef __OPENCV_BUILD
|
||||
#error this is a compatibility header which should not be used inside the OpenCV library
|
||||
#endif
|
||||
|
||||
#include "opencv2/imgcodecs.hpp"
|
||||
@@ -0,0 +1 @@
|
||||
#error "This header with legacy C API declarations has been removed from OpenCV. Legacy constants are available from legacy/constants_c.h file."
|
||||
@@ -0,0 +1,59 @@
|
||||
|
||||
/*M///////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
|
||||
//
|
||||
// By downloading, copying, installing or using the software you agree to this license.
|
||||
// If you do not agree to this license, do not download, install,
|
||||
// copy or use the software.
|
||||
//
|
||||
//
|
||||
// License Agreement
|
||||
// For Open Source Computer Vision Library
|
||||
//
|
||||
// Copyright (C) 2000-2008, Intel Corporation, all rights reserved.
|
||||
// Copyright (C) 2009, Willow Garage Inc., all rights reserved.
|
||||
// Third party copyrights are property of their respective owners.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without modification,
|
||||
// are permitted provided that the following conditions are met:
|
||||
//
|
||||
// * Redistribution's of source code must retain the above copyright notice,
|
||||
// this list of conditions and the following disclaimer.
|
||||
//
|
||||
// * Redistribution's in binary form must reproduce the above copyright notice,
|
||||
// this list of conditions and the following disclaimer in the documentation
|
||||
// and/or other materials provided with the distribution.
|
||||
//
|
||||
// * The name of the copyright holders may not be used to endorse or promote products
|
||||
// derived from this software without specific prior written permission.
|
||||
//
|
||||
// This software is provided by the copyright holders and contributors "as is" and
|
||||
// any express or implied warranties, including, but not limited to, the implied
|
||||
// warranties of merchantability and fitness for a particular purpose are disclaimed.
|
||||
// In no event shall the Intel Corporation or contributors be liable for any direct,
|
||||
// indirect, incidental, special, exemplary, or consequential damages
|
||||
// (including, but not limited to, procurement of substitute goods or services;
|
||||
// loss of use, data, or profits; or business interruption) however caused
|
||||
// and on any theory of liability, whether in contract, strict liability,
|
||||
// or tort (including negligence or otherwise) arising in any way out of
|
||||
// the use of this software, even if advised of the possibility of such damage.
|
||||
//
|
||||
//M*/
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import <Accelerate/Accelerate.h>
|
||||
#import <AVFoundation/AVFoundation.h>
|
||||
#import <ImageIO/ImageIO.h>
|
||||
#include "opencv2/core.hpp"
|
||||
|
||||
//! @addtogroup imgcodecs_ios
|
||||
//! @{
|
||||
|
||||
CV_EXPORTS CGImageRef MatToCGImage(const cv::Mat& image) CF_RETURNS_RETAINED;
|
||||
CV_EXPORTS void CGImageToMat(const CGImageRef image, cv::Mat& m, bool alphaExist = false);
|
||||
CV_EXPORTS UIImage* MatToUIImage(const cv::Mat& image);
|
||||
CV_EXPORTS void UIImageToMat(const UIImage* image,
|
||||
cv::Mat& m, bool alphaExist = false);
|
||||
|
||||
//! @}
|
||||
@@ -0,0 +1,54 @@
|
||||
// This file is part of OpenCV project.
|
||||
// It is subject to the license terms in the LICENSE file found in the top-level directory
|
||||
// of this distribution and at http://opencv.org/license.html.
|
||||
|
||||
#ifndef OPENCV_IMGCODECS_LEGACY_CONSTANTS_H
|
||||
#define OPENCV_IMGCODECS_LEGACY_CONSTANTS_H
|
||||
|
||||
/* duplicate of "ImreadModes" enumeration for better compatibility with OpenCV 3.x */
|
||||
enum
|
||||
{
|
||||
/* 8bit, color or not */
|
||||
CV_LOAD_IMAGE_UNCHANGED =-1,
|
||||
/* 8bit, gray */
|
||||
CV_LOAD_IMAGE_GRAYSCALE =0,
|
||||
/* ?, color */
|
||||
CV_LOAD_IMAGE_COLOR =1,
|
||||
/* any depth, ? */
|
||||
CV_LOAD_IMAGE_ANYDEPTH =2,
|
||||
/* ?, any color */
|
||||
CV_LOAD_IMAGE_ANYCOLOR =4,
|
||||
/* ?, no rotate */
|
||||
CV_LOAD_IMAGE_IGNORE_ORIENTATION =128
|
||||
};
|
||||
|
||||
/* duplicate of "ImwriteFlags" enumeration for better compatibility with OpenCV 3.x */
|
||||
enum
|
||||
{
|
||||
CV_IMWRITE_JPEG_QUALITY =1,
|
||||
CV_IMWRITE_JPEG_PROGRESSIVE =2,
|
||||
CV_IMWRITE_JPEG_OPTIMIZE =3,
|
||||
CV_IMWRITE_JPEG_RST_INTERVAL =4,
|
||||
CV_IMWRITE_JPEG_LUMA_QUALITY =5,
|
||||
CV_IMWRITE_JPEG_CHROMA_QUALITY =6,
|
||||
CV_IMWRITE_PNG_COMPRESSION =16,
|
||||
CV_IMWRITE_PNG_STRATEGY =17,
|
||||
CV_IMWRITE_PNG_BILEVEL =18,
|
||||
CV_IMWRITE_PNG_STRATEGY_DEFAULT =0,
|
||||
CV_IMWRITE_PNG_STRATEGY_FILTERED =1,
|
||||
CV_IMWRITE_PNG_STRATEGY_HUFFMAN_ONLY =2,
|
||||
CV_IMWRITE_PNG_STRATEGY_RLE =3,
|
||||
CV_IMWRITE_PNG_STRATEGY_FIXED =4,
|
||||
CV_IMWRITE_PXM_BINARY =32,
|
||||
CV_IMWRITE_EXR_TYPE = 48,
|
||||
CV_IMWRITE_WEBP_QUALITY =64,
|
||||
CV_IMWRITE_PAM_TUPLETYPE = 128,
|
||||
CV_IMWRITE_PAM_FORMAT_NULL = 0,
|
||||
CV_IMWRITE_PAM_FORMAT_BLACKANDWHITE = 1,
|
||||
CV_IMWRITE_PAM_FORMAT_GRAYSCALE = 2,
|
||||
CV_IMWRITE_PAM_FORMAT_GRAYSCALE_ALPHA = 3,
|
||||
CV_IMWRITE_PAM_FORMAT_RGB = 4,
|
||||
CV_IMWRITE_PAM_FORMAT_RGB_ALPHA = 5,
|
||||
};
|
||||
|
||||
#endif // OPENCV_IMGCODECS_LEGACY_CONSTANTS_H
|
||||
@@ -0,0 +1,20 @@
|
||||
// This file is part of OpenCV project.
|
||||
// It is subject to the license terms in the LICENSE file found in the top-level directory
|
||||
// of this distribution and at http://opencv.org/license.html.
|
||||
|
||||
#if !defined(__APPLE__) || !defined(__MACH__)
|
||||
#error This header should be used in macOS ObjC/Swift projects.
|
||||
#endif
|
||||
|
||||
#import <AppKit/AppKit.h>
|
||||
#include "opencv2/core.hpp"
|
||||
|
||||
//! @addtogroup imgcodecs_macosx
|
||||
//! @{
|
||||
|
||||
CV_EXPORTS CGImageRef MatToCGImage(const cv::Mat& image) CF_RETURNS_RETAINED;
|
||||
CV_EXPORTS void CGImageToMat(const CGImageRef image, cv::Mat& m, bool alphaExist = false);
|
||||
CV_EXPORTS NSImage* MatToNSImage(const cv::Mat& image);
|
||||
CV_EXPORTS void NSImageToMat(const NSImage* image, cv::Mat& m, bool alphaExist = false);
|
||||
|
||||
//! @}
|
||||
@@ -0,0 +1,100 @@
|
||||
package org.opencv.test.imgcodecs;
|
||||
|
||||
import org.opencv.core.Mat;
|
||||
import org.opencv.core.MatOfByte;
|
||||
import org.opencv.core.MatOfInt;
|
||||
import org.opencv.imgproc.Imgproc;
|
||||
import org.opencv.imgcodecs.Imgcodecs;
|
||||
import org.opencv.imgcodecs.Animation;
|
||||
import org.opencv.test.OpenCVTestCase;
|
||||
import org.opencv.test.OpenCVTestRunner;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class ImgcodecsTest extends OpenCVTestCase {
|
||||
|
||||
public void testAnimation() {
|
||||
if (!Imgcodecs.haveImageWriter("*.apng")) {
|
||||
return;
|
||||
}
|
||||
|
||||
Mat src = Imgcodecs.imread(OpenCVTestRunner.LENA_PATH, Imgcodecs.IMREAD_REDUCED_COLOR_4);
|
||||
assertFalse(src.empty());
|
||||
|
||||
Mat rgb = new Mat();
|
||||
Imgproc.cvtColor(src, rgb, Imgproc.COLOR_BGR2RGB);
|
||||
|
||||
Animation animation = new Animation();
|
||||
List<Mat> frames = new ArrayList<>();
|
||||
MatOfInt durations = new MatOfInt(100, 100);
|
||||
|
||||
frames.add(src);
|
||||
frames.add(rgb);
|
||||
|
||||
animation.set_frames(frames);
|
||||
animation.set_durations(durations);
|
||||
|
||||
String filename = OpenCVTestRunner.getTempFileName("png");
|
||||
assertTrue(Imgcodecs.imwriteanimation(filename, animation));
|
||||
|
||||
Animation readAnimation = new Animation();
|
||||
assertTrue(Imgcodecs.imreadanimation(filename, readAnimation));
|
||||
|
||||
List<Mat> readFrames = readAnimation.get_frames();
|
||||
assertTrue(readFrames.size() == 2);
|
||||
}
|
||||
|
||||
public void testImdecode() {
|
||||
fail("Not yet implemented");
|
||||
}
|
||||
|
||||
public void testImencodeStringMatListOfByte() {
|
||||
MatOfByte buff = new MatOfByte();
|
||||
assertEquals(0, buff.total());
|
||||
assertTrue( Imgcodecs.imencode(".jpg", gray127, buff) );
|
||||
assertFalse(0 == buff.total());
|
||||
}
|
||||
|
||||
public void testImencodeStringMatListOfByteListOfInteger() {
|
||||
MatOfInt params40 = new MatOfInt(Imgcodecs.IMWRITE_JPEG_QUALITY, 40);
|
||||
MatOfInt params90 = new MatOfInt(Imgcodecs.IMWRITE_JPEG_QUALITY, 90);
|
||||
/* or
|
||||
MatOfInt params = new MatOfInt();
|
||||
params.fromArray(Imgcodecs.IMWRITE_JPEG_QUALITY, 40);
|
||||
*/
|
||||
MatOfByte buff40 = new MatOfByte();
|
||||
MatOfByte buff90 = new MatOfByte();
|
||||
|
||||
assertTrue( Imgcodecs.imencode(".jpg", rgbLena, buff40, params40) );
|
||||
assertTrue( Imgcodecs.imencode(".jpg", rgbLena, buff90, params90) );
|
||||
|
||||
assertTrue(buff40.total() > 0);
|
||||
assertTrue(buff40.total() < buff90.total());
|
||||
}
|
||||
|
||||
public void testImreadString() {
|
||||
dst = Imgcodecs.imread(OpenCVTestRunner.LENA_PATH);
|
||||
assertFalse(dst.empty());
|
||||
assertEquals(3, dst.channels());
|
||||
assertTrue(512 == dst.cols());
|
||||
assertTrue(512 == dst.rows());
|
||||
}
|
||||
|
||||
public void testImreadStringInt() {
|
||||
dst = Imgcodecs.imread(OpenCVTestRunner.LENA_PATH, Imgcodecs.IMREAD_GRAYSCALE);
|
||||
assertFalse(dst.empty());
|
||||
assertEquals(1, dst.channels());
|
||||
assertTrue(512 == dst.cols());
|
||||
assertTrue(512 == dst.rows());
|
||||
}
|
||||
|
||||
public void testImwriteStringMat() {
|
||||
fail("Not yet implemented");
|
||||
}
|
||||
|
||||
public void testImwriteStringMatListOfInteger() {
|
||||
fail("Not yet implemented");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"SourceMap" : {
|
||||
"visionos" : "ios"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
//
|
||||
// MatConverters.h
|
||||
//
|
||||
// Created by Giles Payne on 2020/03/03.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
#import "opencv2/core.hpp"
|
||||
#else
|
||||
#define CV_EXPORTS
|
||||
#endif
|
||||
|
||||
#import "Mat.h"
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
CV_EXPORTS @interface MatConverters : NSObject
|
||||
|
||||
+(CGImageRef)convertMatToCGImageRef:(Mat*)mat CF_RETURNS_RETAINED;
|
||||
+(Mat*)convertCGImageRefToMat:(CGImageRef)image;
|
||||
+(Mat*)convertCGImageRefToMat:(CGImageRef)image alphaExist:(BOOL)alphaExist;
|
||||
+(UIImage*)converMatToUIImage:(Mat*)mat;
|
||||
+(Mat*)convertUIImageToMat:(UIImage*)image;
|
||||
+(Mat*)convertUIImageToMat:(UIImage*)image alphaExist:(BOOL)alphaExist;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,40 @@
|
||||
//
|
||||
// MatConverters.mm
|
||||
//
|
||||
// Created by Giles Payne on 2020/03/03.
|
||||
//
|
||||
|
||||
#import "MatConverters.h"
|
||||
#import <opencv2/imgcodecs/ios.h>
|
||||
|
||||
@implementation MatConverters
|
||||
|
||||
+(CGImageRef)convertMatToCGImageRef:(Mat*)mat {
|
||||
return MatToCGImage(mat.nativeRef);
|
||||
}
|
||||
|
||||
+(Mat*)convertCGImageRefToMat:(CGImageRef)image {
|
||||
return [MatConverters convertCGImageRefToMat:image alphaExist:NO];
|
||||
}
|
||||
|
||||
+(Mat*)convertCGImageRefToMat:(CGImageRef)image alphaExist:(BOOL)alphaExist {
|
||||
Mat* mat = [Mat new];
|
||||
CGImageToMat(image, mat.nativeRef, (bool)alphaExist);
|
||||
return mat;
|
||||
}
|
||||
|
||||
+(UIImage*)converMatToUIImage:(Mat*)mat {
|
||||
return MatToUIImage(mat.nativeRef);
|
||||
}
|
||||
|
||||
+(Mat*)convertUIImageToMat:(UIImage*)image {
|
||||
return [MatConverters convertUIImageToMat:image alphaExist:NO];
|
||||
}
|
||||
|
||||
+(Mat*)convertUIImageToMat:(UIImage*)image alphaExist:(BOOL)alphaExist {
|
||||
Mat* mat = [Mat new];
|
||||
UIImageToMat(image, mat.nativeRef, (bool)alphaExist);
|
||||
return mat;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,27 @@
|
||||
//
|
||||
// MatQuickLook.h
|
||||
//
|
||||
// Created by Giles Payne on 2021/07/18.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
#import "opencv2/core.hpp"
|
||||
#else
|
||||
#define CV_EXPORTS
|
||||
#endif
|
||||
|
||||
#import "Mat.h"
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
CV_EXPORTS @interface MatQuickLook : NSObject
|
||||
|
||||
+ (id)matDebugQuickLookObject:(Mat*)mat;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,158 @@
|
||||
//
|
||||
// MatQuickLook.mm
|
||||
//
|
||||
// Created by Giles Payne on 2021/07/18.
|
||||
//
|
||||
|
||||
#import "MatQuickLook.h"
|
||||
#import "Rect2i.h"
|
||||
#import "Core.h"
|
||||
#import "Imgproc.h"
|
||||
#import <opencv2/imgcodecs/ios.h>
|
||||
|
||||
#define SIZE 20
|
||||
|
||||
static UIFont* getCMU() {
|
||||
return [UIFont fontWithName:@"CMU Serif" size:SIZE];
|
||||
}
|
||||
|
||||
static UIFont* getBodoni72() {
|
||||
return [UIFont fontWithName:@"Bodoni 72" size:SIZE];
|
||||
}
|
||||
|
||||
static UIFont* getAnySerif() {
|
||||
#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 130000
|
||||
if (@available(iOS 13.0, *)) {
|
||||
return [UIFont fontWithDescriptor:[[UIFontDescriptor preferredFontDescriptorWithTextStyle:UIFontTextStyleBody] fontDescriptorWithDesign:UIFontDescriptorSystemDesignSerif] size:SIZE];
|
||||
} else {
|
||||
return nil;
|
||||
}
|
||||
#else
|
||||
return nil;
|
||||
#endif
|
||||
}
|
||||
|
||||
static UIFont* getSystemFont() {
|
||||
return [UIFont systemFontOfSize:SIZE];
|
||||
}
|
||||
|
||||
typedef UIFont* (*FontGetter)();
|
||||
|
||||
@implementation MatQuickLook
|
||||
|
||||
+ (NSString*)makeLabel:(BOOL)isIntType val:(NSNumber*)num {
|
||||
if (isIntType) {
|
||||
return [NSString stringWithFormat:@"%d", num.intValue];
|
||||
} else {
|
||||
int exponent = 1 + (int)log10(abs(num.doubleValue));
|
||||
if (num.doubleValue == (double)num.intValue && num.doubleValue < 10000 && num.doubleValue > -10000) {
|
||||
return [NSString stringWithFormat:@"%d", num.intValue];;
|
||||
} else if (exponent <= 5 && exponent >= -1) {
|
||||
return [NSString stringWithFormat:[NSString stringWithFormat:@"%%%d.%df", 6, MIN(5 - exponent, 4)], num.doubleValue];
|
||||
} else {
|
||||
return [[[NSString stringWithFormat:@"%.2e", num.doubleValue] stringByReplacingOccurrencesOfString:@"e+0" withString:@"e"] stringByReplacingOccurrencesOfString:@"e-0" withString:@"e-"];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+ (void)relativeLine:(UIBezierPath*)path relX:(CGFloat)x relY:(CGFloat)y {
|
||||
CGPoint curr = path.currentPoint;
|
||||
[path addLineToPoint:CGPointMake(curr.x + x, curr.y + y)];
|
||||
}
|
||||
|
||||
+ (id)matDebugQuickLookObject:(Mat*)mat {
|
||||
if ([mat dims] == 2 && [mat rows] <= 10 && [mat cols] <= 10 && [mat channels] == 1) {
|
||||
FontGetter fontGetters[] = { getCMU, getBodoni72, getAnySerif, getSystemFont };
|
||||
UIFont* font = nil;
|
||||
for (int fontGetterIndex = 0; font==nil && fontGetterIndex < (sizeof(fontGetters)) / (sizeof(fontGetters[0])); fontGetterIndex++) {
|
||||
font = fontGetters[fontGetterIndex]();
|
||||
}
|
||||
int elements = [mat rows] * [mat cols];
|
||||
NSDictionary<NSAttributedStringKey,id>* textFontAttributes = @{ NSFontAttributeName: font, NSForegroundColorAttributeName: UIColor.blackColor };
|
||||
NSMutableArray<NSNumber*>* rawData = [NSMutableArray new];
|
||||
for (int dataIndex = 0; dataIndex < elements; dataIndex++) {
|
||||
[rawData addObject:[NSNumber numberWithDouble:0]];
|
||||
}
|
||||
[mat get:0 col: 0 data: rawData];
|
||||
BOOL isIntType = [mat depth] <= CV_32S;
|
||||
NSMutableArray<NSString*>* labels = [NSMutableArray new];
|
||||
NSMutableDictionary<NSString*, NSValue*>* boundingRects = [NSMutableDictionary dictionaryWithCapacity:elements];
|
||||
int maxWidth = 0, maxHeight = 0;
|
||||
for (NSNumber* number in rawData) {
|
||||
NSString* label = [MatQuickLook makeLabel:isIntType val:number];
|
||||
[labels addObject:label];
|
||||
CGRect boundingRect = [label boundingRectWithSize:CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX) options:NSStringDrawingUsesLineFragmentOrigin attributes:textFontAttributes context:nil];
|
||||
if (boundingRect.size.width > maxWidth) {
|
||||
maxWidth = boundingRect.size.width;
|
||||
}
|
||||
if (boundingRect.size.height > maxHeight) {
|
||||
maxHeight = boundingRect.size.height;
|
||||
}
|
||||
boundingRects[label] = [NSValue valueWithCGRect:boundingRect];
|
||||
}
|
||||
|
||||
int rowGap = 6;
|
||||
int colGap = 6;
|
||||
int borderGap = 8;
|
||||
int lineThickness = 3;
|
||||
int lipWidth = 6;
|
||||
int imageWidth = 2 * (borderGap + lipWidth) + maxWidth * [mat cols] + colGap * ([mat cols] - 1);
|
||||
int imageHeight = 2 * (borderGap + lipWidth) + maxHeight * [mat rows] + rowGap * ([mat rows] - 1);
|
||||
|
||||
UIBezierPath* leftBracket = [UIBezierPath new];
|
||||
[leftBracket moveToPoint:CGPointMake(borderGap, borderGap)];
|
||||
[MatQuickLook relativeLine:leftBracket relX:0 relY:imageHeight - 2 * borderGap];
|
||||
[MatQuickLook relativeLine:leftBracket relX:lineThickness + lipWidth relY:0];
|
||||
[MatQuickLook relativeLine:leftBracket relX:0 relY:-lineThickness];
|
||||
[MatQuickLook relativeLine:leftBracket relX:-lipWidth relY:0];
|
||||
[MatQuickLook relativeLine:leftBracket relX:0 relY:-(imageHeight - 2 * (borderGap + lineThickness))];
|
||||
[MatQuickLook relativeLine:leftBracket relX:lipWidth relY:0];
|
||||
[MatQuickLook relativeLine:leftBracket relX:0 relY:-lineThickness];
|
||||
[leftBracket closePath];
|
||||
CGAffineTransform reflect = CGAffineTransformConcat(CGAffineTransformMakeTranslation(-imageWidth, 0), CGAffineTransformMakeScale(-1, 1));
|
||||
UIBezierPath* rightBracket = [leftBracket copy];
|
||||
[rightBracket applyTransform:reflect];
|
||||
|
||||
CGRect rect = CGRectMake(0, 0, imageWidth, imageHeight);
|
||||
UIGraphicsBeginImageContextWithOptions(rect.size, false, 0.0);
|
||||
[UIColor.whiteColor setFill];
|
||||
UIRectFill(rect);
|
||||
[UIColor.blackColor setFill];
|
||||
[leftBracket fill];
|
||||
[rightBracket fill];
|
||||
[labels enumerateObjectsUsingBlock:^(id label, NSUInteger index, BOOL *stop)
|
||||
{
|
||||
CGRect boundingRect = boundingRects[label].CGRectValue;
|
||||
int row = (int)index / [mat cols];
|
||||
int col = (int)index % [mat cols];
|
||||
int x = borderGap + lipWidth + col * (maxWidth + colGap) + (maxWidth - boundingRect.size.width) / 2;
|
||||
int y = borderGap + lipWidth + row * (maxHeight + rowGap) + (maxHeight - boundingRect.size.height) / 2;
|
||||
CGRect textRect = CGRectMake(x, y, boundingRect.size.width, boundingRect.size.height);
|
||||
[label drawInRect:textRect withAttributes:textFontAttributes];
|
||||
}];
|
||||
UIImage* image = UIGraphicsGetImageFromCurrentImageContext();
|
||||
UIGraphicsEndImageContext();
|
||||
return image;
|
||||
} else if (([mat dims] == 2) && ([mat type] == CV_8U || [mat type] == CV_8UC3 || [mat type] == CV_8UC4)) {
|
||||
return [mat toUIImage];
|
||||
} else if ([mat dims] == 2 && [mat channels] == 1) {
|
||||
Mat* normalized = [Mat new];
|
||||
[Core normalize:mat dst:normalized alpha:0 beta:255 norm_type:NORM_MINMAX dtype:CV_8U];
|
||||
Mat* normalizedKey = [[Mat alloc] initWithRows:[mat rows] + 10 cols:[mat cols] type:CV_8U];
|
||||
std::vector<char> key;
|
||||
for (int index = 0; index < [mat cols]; index++) {
|
||||
key.push_back((char)(index * 256 / [mat cols]));
|
||||
}
|
||||
for (int index = 0; index < 10; index++) {
|
||||
[normalizedKey put:@[[NSNumber numberWithInt:index], [NSNumber numberWithInt:0]] count:[mat cols] byteBuffer:key.data()];
|
||||
}
|
||||
[normalized copyTo:[normalizedKey submatRoi:[[Rect2i alloc] initWithX:0 y:10 width:[mat cols] height:[mat rows]]]];
|
||||
Mat* colorMap = [Mat new];
|
||||
[Imgproc applyColorMap:normalizedKey dst:colorMap colormap:COLORMAP_JET];
|
||||
[Imgproc cvtColor:colorMap dst:colorMap code:COLOR_BGR2RGB];
|
||||
return [colorMap toUIImage];
|
||||
}
|
||||
return [mat description];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,32 @@
|
||||
//
|
||||
// Mat+Converters.h
|
||||
//
|
||||
// Created by Masaya Tsuruta on 2020/10/08.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
#import "opencv2/core.hpp"
|
||||
#else
|
||||
#define CV_EXPORTS
|
||||
#endif
|
||||
|
||||
#import "Mat.h"
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <AppKit/AppKit.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
CV_EXPORTS @interface MatConverters : NSObject
|
||||
|
||||
+(CGImageRef)convertMatToCGImageRef:(Mat*)mat CF_RETURNS_RETAINED;
|
||||
+(Mat*)convertCGImageRefToMat:(CGImageRef)image;
|
||||
+(Mat*)convertCGImageRefToMat:(CGImageRef)image alphaExist:(BOOL)alphaExist;
|
||||
+(NSImage*)converMatToNSImage:(Mat*)mat;
|
||||
+(Mat*)convertNSImageToMat:(NSImage*)image;
|
||||
+(Mat*)convertNSImageToMat:(NSImage*)image alphaExist:(BOOL)alphaExist;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,40 @@
|
||||
//
|
||||
// MatConverters.mm
|
||||
//
|
||||
// Created by Masaya Tsuruta on 2020/10/08.
|
||||
//
|
||||
|
||||
#import "MatConverters.h"
|
||||
#import <opencv2/imgcodecs/macosx.h>
|
||||
|
||||
@implementation MatConverters
|
||||
|
||||
+(CGImageRef)convertMatToCGImageRef:(Mat*)mat {
|
||||
return MatToCGImage(mat.nativeRef);
|
||||
}
|
||||
|
||||
+(Mat*)convertCGImageRefToMat:(CGImageRef)image {
|
||||
return [MatConverters convertCGImageRefToMat:image alphaExist:NO];
|
||||
}
|
||||
|
||||
+(Mat*)convertCGImageRefToMat:(CGImageRef)image alphaExist:(BOOL)alphaExist {
|
||||
Mat* mat = [Mat new];
|
||||
CGImageToMat(image, mat.nativeRef, (bool)alphaExist);
|
||||
return mat;
|
||||
}
|
||||
|
||||
+(NSImage*)converMatToNSImage:(Mat*)mat {
|
||||
return MatToNSImage(mat.nativeRef);
|
||||
}
|
||||
|
||||
+(Mat*)convertNSImageToMat:(NSImage*)image {
|
||||
return [MatConverters convertNSImageToMat:image alphaExist:NO];
|
||||
}
|
||||
|
||||
+(Mat*)convertNSImageToMat:(NSImage*)image alphaExist:(BOOL)alphaExist {
|
||||
Mat* mat = [Mat new];
|
||||
NSImageToMat(image, mat.nativeRef, (bool)alphaExist);
|
||||
return mat;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,27 @@
|
||||
//
|
||||
// MatQuickLook.h
|
||||
//
|
||||
// Created by Giles Payne on 2021/07/18.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
#import "opencv2/core.hpp"
|
||||
#else
|
||||
#define CV_EXPORTS
|
||||
#endif
|
||||
|
||||
#import "Mat.h"
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <AppKit/AppKit.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
CV_EXPORTS @interface MatQuickLook : NSObject
|
||||
|
||||
+ (id)matDebugQuickLookObject:(Mat*)mat;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,158 @@
|
||||
//
|
||||
// MatQuickLook.mm
|
||||
//
|
||||
// Created by Giles Payne on 2021/07/18.
|
||||
//
|
||||
|
||||
#import "MatQuickLook.h"
|
||||
#import "MatConverters.h"
|
||||
#import "Rect2i.h"
|
||||
#import "Core.h"
|
||||
#import "Imgproc.h"
|
||||
#import <opencv2/imgcodecs/macosx.h>
|
||||
|
||||
#define SIZE 20
|
||||
|
||||
static NSFont* getCMU() {
|
||||
return [NSFont fontWithName:@"CMU Serif" size:SIZE];
|
||||
}
|
||||
|
||||
static NSFont* getBodoni72() {
|
||||
return [NSFont fontWithName:@"Bodoni 72" size:SIZE];
|
||||
}
|
||||
|
||||
static NSFont* getAnySerif() {
|
||||
#if defined(__MAC_OS_X_VERSION_MAX_ALLOWED) && __MAC_OS_X_VERSION_MAX_ALLOWED >= 110000
|
||||
if (@available(macOS 11.0, *)) {
|
||||
return [NSFont fontWithDescriptor:[[NSFontDescriptor preferredFontDescriptorForTextStyle:NSFontTextStyleBody options:@{}] fontDescriptorWithDesign:NSFontDescriptorSystemDesignSerif] size:SIZE];
|
||||
} else {
|
||||
return nil;
|
||||
}
|
||||
#else
|
||||
return nil;
|
||||
#endif
|
||||
}
|
||||
|
||||
static NSFont* getSystemFont() {
|
||||
return [NSFont systemFontOfSize:SIZE];
|
||||
}
|
||||
|
||||
typedef NSFont* (*FontGetter)();
|
||||
|
||||
@implementation MatQuickLook
|
||||
|
||||
+ (NSString*)makeLabel:(BOOL)isIntType val:(NSNumber*)num {
|
||||
if (isIntType) {
|
||||
return [NSString stringWithFormat:@"%d", num.intValue];
|
||||
} else {
|
||||
int exponent = 1 + (int)log10(abs(num.doubleValue));
|
||||
if (num.doubleValue == (double)num.intValue && num.doubleValue < 10000 && num.doubleValue > -10000) {
|
||||
return [NSString stringWithFormat:@"%d", num.intValue];;
|
||||
} else if (exponent <= 5 && exponent >= -1) {
|
||||
return [NSString stringWithFormat:[NSString stringWithFormat:@"%%%d.%df", 6, MIN(5 - exponent, 4)], num.doubleValue];
|
||||
} else {
|
||||
return [[[NSString stringWithFormat:@"%.2e", num.doubleValue] stringByReplacingOccurrencesOfString:@"e+0" withString:@"e"] stringByReplacingOccurrencesOfString:@"e-0" withString:@"e-"];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+ (id)matDebugQuickLookObject:(Mat*)mat {
|
||||
// for smallish Mat objects display as a matrix
|
||||
if ([mat dims] == 2 && [mat rows] <= 10 && [mat cols] <= 10 && [mat channels] == 1) {
|
||||
FontGetter fontGetters[] = { getCMU, getBodoni72, getAnySerif, getSystemFont };
|
||||
NSFont* font = nil;
|
||||
for (int fontGetterIndex = 0; font==nil && fontGetterIndex < (sizeof(fontGetters)) / (sizeof(fontGetters[0])); fontGetterIndex++) {
|
||||
font = fontGetters[fontGetterIndex]();
|
||||
}
|
||||
int elements = [mat rows] * [mat cols];
|
||||
NSDictionary<NSAttributedStringKey,id>* textFontAttributes = @{ NSFontAttributeName: font, NSForegroundColorAttributeName: NSColor.blackColor };
|
||||
NSMutableArray<NSNumber*>* rawData = [NSMutableArray new];
|
||||
for (int dataIndex = 0; dataIndex < elements; dataIndex++) {
|
||||
[rawData addObject:[NSNumber numberWithDouble:0]];
|
||||
}
|
||||
[mat get:0 col: 0 data: rawData];
|
||||
BOOL isIntType = [mat depth] <= CV_32S;
|
||||
NSMutableArray<NSString*>* labels = [NSMutableArray new];
|
||||
NSMutableDictionary<NSString*, NSValue*>* boundingRects = [NSMutableDictionary dictionaryWithCapacity:elements];
|
||||
int maxWidth = 0, maxHeight = 0;
|
||||
for (NSNumber* number in rawData) {
|
||||
NSString* label = [MatQuickLook makeLabel:isIntType val:number];
|
||||
[labels addObject:label];
|
||||
NSRect boundingRect = [label boundingRectWithSize:NSMakeSize(CGFLOAT_MAX, CGFLOAT_MAX) options:NSStringDrawingUsesLineFragmentOrigin attributes:textFontAttributes];
|
||||
if (boundingRect.size.width > maxWidth) {
|
||||
maxWidth = boundingRect.size.width;
|
||||
}
|
||||
if (boundingRect.size.height > maxHeight) {
|
||||
maxHeight = boundingRect.size.height;
|
||||
}
|
||||
boundingRects[label] = [NSValue valueWithRect:boundingRect];
|
||||
}
|
||||
|
||||
int rowGap = 8;
|
||||
int colGap = 8;
|
||||
int borderGap = 9;
|
||||
int lineThickness = 4;
|
||||
int lipWidth = 8;
|
||||
int imageWidth = 2 * (borderGap + lipWidth) + maxWidth * [mat cols] + colGap * ([mat cols] - 1);
|
||||
int imageHeight = 2 * (borderGap + lipWidth) + maxHeight * [mat rows] + rowGap * ([mat rows] - 1);
|
||||
NSImage* image = [[NSImage alloc] initWithSize:NSMakeSize(imageWidth, imageHeight)];
|
||||
NSBezierPath* leftBracket = [NSBezierPath new];
|
||||
[leftBracket moveToPoint:NSMakePoint(borderGap, borderGap)];
|
||||
[leftBracket relativeLineToPoint:NSMakePoint(0, imageHeight - 2 * borderGap)];
|
||||
[leftBracket relativeLineToPoint:NSMakePoint(lineThickness + lipWidth, 0)];
|
||||
[leftBracket relativeLineToPoint:NSMakePoint(0, -lineThickness)];
|
||||
[leftBracket relativeLineToPoint:NSMakePoint(-lipWidth, 0)];
|
||||
[leftBracket relativeLineToPoint:NSMakePoint(0, -(imageHeight - 2 * (borderGap + lineThickness)))];
|
||||
[leftBracket relativeLineToPoint:NSMakePoint(lipWidth, 0)];
|
||||
[leftBracket relativeLineToPoint:NSMakePoint(0, -lineThickness)];
|
||||
[leftBracket relativeLineToPoint:NSMakePoint(-(lineThickness + lipWidth), 0)];
|
||||
NSAffineTransform* reflect = [NSAffineTransform new];
|
||||
[reflect scaleXBy:-1 yBy:1];
|
||||
[reflect translateXBy:-imageWidth yBy:0];
|
||||
NSBezierPath* rightBracket = [leftBracket copy];
|
||||
[rightBracket transformUsingAffineTransform:reflect];
|
||||
|
||||
[image lockFocus];
|
||||
[NSColor.whiteColor drawSwatchInRect:NSMakeRect(0, 0, imageWidth, imageHeight)];
|
||||
[NSColor.blackColor set];
|
||||
[leftBracket fill];
|
||||
[rightBracket fill];
|
||||
|
||||
[labels enumerateObjectsUsingBlock:^(id label, NSUInteger index, BOOL *stop)
|
||||
{
|
||||
NSRect boundingRect = boundingRects[label].rectValue;
|
||||
int row = [mat rows] - 1 - ((int)index / [mat cols]);
|
||||
int col = (int)index % [mat cols];
|
||||
int x = borderGap + lipWidth + col * (maxWidth + colGap) + (maxWidth - boundingRect.size.width) / 2;
|
||||
int y = borderGap + lipWidth + row * (maxHeight + rowGap) + (maxHeight - boundingRect.size.height) / 2;
|
||||
NSRect textRect = NSMakeRect(x, y, boundingRect.size.width, boundingRect.size.height);
|
||||
[label drawInRect:textRect withAttributes:textFontAttributes];
|
||||
}];
|
||||
[image unlockFocus];
|
||||
return image;
|
||||
} else if (([mat dims] == 2) && ([mat type] == CV_8U || [mat type] == CV_8UC3 || [mat type] == CV_8UC4)) {
|
||||
// convert to NSImage if the Mats has 2 dimensions and a type and number of channels consistent with it being a image
|
||||
return [mat toNSImage];
|
||||
} else if ([mat dims] == 2 && [mat channels] == 1) {
|
||||
// for other Mats with 2 dimensions and one channel - generate heat map
|
||||
Mat* normalized = [Mat new];
|
||||
[Core normalize:mat dst:normalized alpha:0 beta:255 norm_type:NORM_MINMAX dtype:CV_8U];
|
||||
Mat* normalizedKey = [[Mat alloc] initWithRows:[mat rows] + 10 cols:[mat cols] type:CV_8U];
|
||||
std::vector<char> key;
|
||||
for (int index = 0; index < [mat cols]; index++) {
|
||||
key.push_back((char)(index * 256 / [mat cols]));
|
||||
}
|
||||
for (int index = 0; index < 10; index++) {
|
||||
[normalizedKey put:@[[NSNumber numberWithInt:index], [NSNumber numberWithInt:0]] count:[mat cols] byteBuffer:key.data()];
|
||||
}
|
||||
[normalized copyTo:[normalizedKey submatRoi:[[Rect2i alloc] initWithX:0 y:10 width:[mat cols] height:[mat rows]]]];
|
||||
Mat* colorMap = [Mat new];
|
||||
[Imgproc applyColorMap:normalizedKey dst:colorMap colormap:COLORMAP_JET];
|
||||
[Imgproc cvtColor:colorMap dst:colorMap code:COLOR_BGR2RGB];
|
||||
return [colorMap toNSImage];
|
||||
}
|
||||
//everything just return the Mat description
|
||||
return [mat description];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,50 @@
|
||||
//
|
||||
// Imgcodecs.swift
|
||||
//
|
||||
// Created by Giles Payne on 2020/02/10.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
import OpenCV
|
||||
|
||||
class ImgcodecsTest: OpenCVTestCase {
|
||||
|
||||
let LENA_PATH = Bundle(for: ImgcodecsTest.self).path(forResource:"lena", ofType:"png", inDirectory:"resources")!
|
||||
|
||||
func testImencodeStringMatListOfByte() {
|
||||
var buff = [UInt8]()
|
||||
XCTAssert(Imgcodecs.imencode(ext: ".jpg", img: gray127, buf: &buff))
|
||||
XCTAssertFalse(0 == buff.count)
|
||||
}
|
||||
|
||||
func testImencodeStringMatListOfByteListOfInteger() {
|
||||
let params40:[Int32] = [ImwriteFlags.IMWRITE_JPEG_QUALITY.rawValue, 40]
|
||||
let params90:[Int32] = [ImwriteFlags.IMWRITE_JPEG_QUALITY.rawValue, 90]
|
||||
|
||||
var buff40 = [UInt8]()
|
||||
var buff90 = [UInt8]()
|
||||
|
||||
XCTAssert(Imgcodecs.imencode(ext: ".jpg", img: rgbLena, buf: &buff40, params: params40))
|
||||
XCTAssert(Imgcodecs.imencode(ext: ".jpg", img: rgbLena, buf: &buff90, params: params90))
|
||||
|
||||
XCTAssert(buff40.count > 0)
|
||||
XCTAssert(buff40.count < buff90.count)
|
||||
}
|
||||
|
||||
func testImreadString() {
|
||||
dst = Imgcodecs.imread(filename: LENA_PATH)
|
||||
XCTAssertFalse(dst.empty())
|
||||
XCTAssertEqual(3, dst.channels())
|
||||
XCTAssert(512 == dst.cols())
|
||||
XCTAssert(512 == dst.rows())
|
||||
}
|
||||
|
||||
func testImreadStringInt() {
|
||||
dst = Imgcodecs.imread(filename: LENA_PATH, flags: 0)
|
||||
XCTAssertFalse(dst.empty());
|
||||
XCTAssertEqual(1, dst.channels());
|
||||
XCTAssert(512 == dst.cols());
|
||||
XCTAssert(512 == dst.rows());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,281 @@
|
||||
// This file is part of OpenCV project.
|
||||
// It is subject to the license terms in the LICENSE file found in the top-level directory
|
||||
// of this distribution and at http://opencv.org/license.html
|
||||
|
||||
#include "perf_precomp.hpp"
|
||||
|
||||
namespace opencv_test
|
||||
{
|
||||
|
||||
using namespace perf;
|
||||
|
||||
static Animation makeCirclesAnimation(Size size = Size(320, 240), int type = CV_8UC4, int nbits = 8, int frameCount = 40)
|
||||
{
|
||||
struct AnimatedCircle {
|
||||
cv::Point2f pos;
|
||||
cv::Point2f velocity;
|
||||
float radius;
|
||||
float radius_speed;
|
||||
cv::Scalar color;
|
||||
cv::Scalar border_color;
|
||||
};
|
||||
|
||||
const int numCircles = 80;
|
||||
const int maxval = (1 << nbits) - 1;
|
||||
|
||||
cv::RNG rng = theRNG();
|
||||
std::vector<AnimatedCircle> circles;
|
||||
Animation animation;
|
||||
|
||||
// Initialize animated circles
|
||||
for (int i = 0; i < numCircles; ++i) {
|
||||
AnimatedCircle c;
|
||||
c.pos = cv::Point2f(rng.uniform(0.f, (float)size.width),
|
||||
rng.uniform(0.f, (float)size.height));
|
||||
c.velocity = cv::Point2f(rng.uniform(-2.f, 2.f),
|
||||
rng.uniform(-2.f, 2.f));
|
||||
c.radius = rng.uniform(10.f, 40.f);
|
||||
c.radius_speed = rng.uniform(-0.5f, 0.5f);
|
||||
c.color = cv::Scalar(rng.uniform(0, maxval),
|
||||
rng.uniform(0, maxval),
|
||||
rng.uniform(0, maxval),
|
||||
rng.uniform(230, maxval));
|
||||
c.border_color = c.color;
|
||||
circles.push_back(c);
|
||||
}
|
||||
|
||||
// Generate frames
|
||||
for (int frame = 0; frame < frameCount; ++frame) {
|
||||
cv::Mat img(size, type, cv::Scalar(20, 0, 10, 128));
|
||||
|
||||
for (size_t i = 0; i < circles.size(); ++i) {
|
||||
AnimatedCircle& c = circles[i];
|
||||
|
||||
// Update position
|
||||
c.pos += c.velocity;
|
||||
|
||||
// Bounce on edges
|
||||
if (c.pos.x < 0 || c.pos.x > size.width) c.velocity.x *= -1;
|
||||
if (c.pos.y < 0 || c.pos.y > size.height) c.velocity.y *= -1;
|
||||
|
||||
// Update radius
|
||||
c.radius += c.radius_speed;
|
||||
if (c.radius < 10.f || c.radius > 80.f) {
|
||||
c.radius_speed *= -1;
|
||||
c.radius = std::max(10.f, std::min(c.radius, 80.f));
|
||||
}
|
||||
|
||||
c.color = c.color - Scalar(c.velocity.x, 0, c.velocity.y, rng.uniform(1, 4));
|
||||
|
||||
// Draw
|
||||
cv::circle(img, c.pos, (int)c.radius, c.color, cv::FILLED, cv::LINE_AA);
|
||||
cv::circle(img, c.pos, (int)c.radius, c.border_color, 1, cv::LINE_AA);
|
||||
}
|
||||
|
||||
animation.frames.push_back(img);
|
||||
animation.durations.push_back(20); // milliseconds
|
||||
}
|
||||
|
||||
for (int i = (int)animation.frames.size() - 1; i >= 0; --i) {
|
||||
animation.frames.push_back(animation.frames[i].clone());
|
||||
animation.durations.push_back(15);
|
||||
}
|
||||
return animation;
|
||||
}
|
||||
|
||||
typedef perf::TestBaseWithParam<std::string> Decode;
|
||||
typedef perf::TestBaseWithParam<std::string> Encode;
|
||||
|
||||
const string exts[] = {
|
||||
#ifdef HAVE_AVIF
|
||||
".avif",
|
||||
#endif
|
||||
".bmp",
|
||||
#ifdef HAVE_IMGCODEC_GIF
|
||||
".gif",
|
||||
#endif
|
||||
#if (defined(HAVE_JASPER) && defined(OPENCV_IMGCODECS_ENABLE_JASPER_TESTS)) \
|
||||
|| defined(HAVE_OPENJPEG)
|
||||
".jp2",
|
||||
#endif
|
||||
#ifdef HAVE_JPEG
|
||||
".jpg",
|
||||
#endif
|
||||
#ifdef HAVE_JPEGXL
|
||||
".jxl",
|
||||
#endif
|
||||
#ifdef HAVE_PNG
|
||||
".png",
|
||||
#endif
|
||||
#ifdef HAVE_IMGCODEC_PXM
|
||||
".ppm",
|
||||
#endif
|
||||
#ifdef HAVE_IMGCODEC_SUNRASTER
|
||||
".ras",
|
||||
#endif
|
||||
#ifdef HAVE_TIFF
|
||||
".tiff",
|
||||
#endif
|
||||
#ifdef HAVE_WEBP
|
||||
".webp",
|
||||
#endif
|
||||
};
|
||||
|
||||
const string exts_multi[] = {
|
||||
#ifdef HAVE_AVIF
|
||||
".avif",
|
||||
#endif
|
||||
#ifdef HAVE_IMGCODEC_GIF
|
||||
".gif",
|
||||
#endif
|
||||
#ifdef HAVE_PNG
|
||||
".png",
|
||||
#endif
|
||||
#ifdef HAVE_TIFF
|
||||
".tiff",
|
||||
#endif
|
||||
#ifdef HAVE_WEBP
|
||||
".webp",
|
||||
#endif
|
||||
};
|
||||
|
||||
const string exts_anim[] = {
|
||||
#ifdef HAVE_AVIF
|
||||
".avif",
|
||||
#endif
|
||||
#ifdef HAVE_IMGCODEC_GIF
|
||||
".gif",
|
||||
#endif
|
||||
#ifdef HAVE_PNG
|
||||
".png",
|
||||
#endif
|
||||
#ifdef HAVE_WEBP
|
||||
".webp",
|
||||
#endif
|
||||
};
|
||||
|
||||
#ifdef HAVE_PNG
|
||||
|
||||
PERF_TEST_P(Decode, bgr, testing::ValuesIn(exts))
|
||||
{
|
||||
String filename = getDataPath("perf/1920x1080.png");
|
||||
|
||||
Mat src = imread(filename);
|
||||
EXPECT_FALSE(src.empty()) << "Cannot open test image perf/1920x1080.png";
|
||||
vector<uchar> buf;
|
||||
EXPECT_TRUE(imencode(GetParam(), src, buf));
|
||||
|
||||
TEST_CYCLE() imdecode(buf, IMREAD_UNCHANGED);
|
||||
|
||||
SANITY_CHECK_NOTHING();
|
||||
}
|
||||
|
||||
PERF_TEST_P(Decode, rgb, testing::ValuesIn(exts))
|
||||
{
|
||||
String filename = getDataPath("perf/1920x1080.png");
|
||||
|
||||
Mat src = imread(filename);
|
||||
EXPECT_FALSE(src.empty()) << "Cannot open test image perf/1920x1080.png";
|
||||
vector<uchar> buf;
|
||||
EXPECT_TRUE(imencode(GetParam(), src, buf));
|
||||
|
||||
TEST_CYCLE() imdecode(buf, IMREAD_COLOR_RGB);
|
||||
|
||||
SANITY_CHECK_NOTHING();
|
||||
}
|
||||
|
||||
PERF_TEST_P(Encode, bgr, testing::ValuesIn(exts))
|
||||
{
|
||||
String filename = getDataPath("perf/1920x1080.png");
|
||||
|
||||
Mat src = imread(filename);
|
||||
EXPECT_FALSE(src.empty()) << "Cannot open test image perf/1920x1080.png";
|
||||
vector<uchar> buf;
|
||||
|
||||
TEST_CYCLE() imencode(GetParam(), src, buf);
|
||||
|
||||
std::cout << " Encoded buffer size: " << buf.size()
|
||||
<< " bytes, Compression ratio: " << std::fixed << std::setprecision(2)
|
||||
<< (static_cast<double>(buf.size()) / (src.total() * src.channels())) * 100.0 << "%" << std::endl;
|
||||
|
||||
SANITY_CHECK_NOTHING();
|
||||
}
|
||||
|
||||
PERF_TEST_P(Encode, multi, testing::ValuesIn(exts_multi))
|
||||
{
|
||||
String filename = getDataPath("perf/1920x1080.png");
|
||||
vector<Mat> vec;
|
||||
EXPECT_TRUE(imreadmulti(filename, vec));
|
||||
vec.push_back(vec.back().clone());
|
||||
circle(vec.back(), Point(100, 100), 45, Scalar(0, 0, 255, 0), 2, LINE_AA);
|
||||
vector<uchar> buf;
|
||||
EXPECT_TRUE(imwrite("test" + GetParam(), vec));
|
||||
|
||||
TEST_CYCLE() imencode(GetParam(), vec, buf);
|
||||
|
||||
std::cout << " Encoded buffer size: " << buf.size()
|
||||
<< " bytes, Compression ratio: " << std::fixed << std::setprecision(2)
|
||||
<< (static_cast<double>(buf.size()) / (vec[0].total() * vec[0].channels())) * 100.0 << "%" << std::endl;
|
||||
|
||||
SANITY_CHECK_NOTHING();
|
||||
}
|
||||
|
||||
#endif // HAVE_PNG
|
||||
|
||||
PERF_TEST_P(Encode, animation, testing::ValuesIn(exts_anim))
|
||||
{
|
||||
Animation animation = makeCirclesAnimation();
|
||||
|
||||
TEST_CYCLE()
|
||||
{
|
||||
vector<uchar> buf;
|
||||
EXPECT_TRUE(imencodeanimation(GetParam().c_str(), animation, buf));
|
||||
}
|
||||
|
||||
SANITY_CHECK_NOTHING();
|
||||
}
|
||||
|
||||
PERF_TEST_P(Encode, multi_page, testing::ValuesIn(exts_multi))
|
||||
{
|
||||
Animation animation = makeCirclesAnimation();
|
||||
|
||||
TEST_CYCLE()
|
||||
{
|
||||
vector<uchar> buf;
|
||||
EXPECT_TRUE(imencodemulti(GetParam().c_str(), animation.frames, buf));
|
||||
}
|
||||
|
||||
SANITY_CHECK_NOTHING();
|
||||
}
|
||||
|
||||
PERF_TEST_P(Decode, animation, testing::ValuesIn(exts_anim))
|
||||
{
|
||||
Animation animation = makeCirclesAnimation();
|
||||
vector<uchar> buf;
|
||||
ASSERT_TRUE(imencodeanimation(GetParam().c_str(), animation, buf));
|
||||
|
||||
TEST_CYCLE()
|
||||
{
|
||||
Animation tmp_animation;
|
||||
EXPECT_TRUE(imdecodeanimation(buf, tmp_animation));
|
||||
}
|
||||
|
||||
SANITY_CHECK_NOTHING();
|
||||
}
|
||||
|
||||
PERF_TEST_P(Decode, multi_page, testing::ValuesIn(exts_multi))
|
||||
{
|
||||
Animation animation = makeCirclesAnimation();
|
||||
vector<uchar> buf;
|
||||
ASSERT_TRUE(imencodemulti(GetParam().c_str(), animation.frames, buf));
|
||||
|
||||
TEST_CYCLE()
|
||||
{
|
||||
vector<Mat> tmp_frames;
|
||||
EXPECT_TRUE(imdecodemulti(buf, IMREAD_UNCHANGED, tmp_frames));
|
||||
}
|
||||
|
||||
SANITY_CHECK_NOTHING();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
@@ -0,0 +1,60 @@
|
||||
// This file is part of OpenCV project.
|
||||
// It is subject to the license terms in the LICENSE file found in the top-level directory
|
||||
// of this distribution and at http://opencv.org/license.html
|
||||
#include "perf_precomp.hpp"
|
||||
|
||||
namespace opencv_test
|
||||
{
|
||||
|
||||
#ifdef HAVE_JPEG
|
||||
|
||||
using namespace perf;
|
||||
|
||||
PERF_TEST(JPEG, Decode)
|
||||
{
|
||||
String filename = getDataPath("stitching/boat1.jpg");
|
||||
|
||||
FILE *f = fopen(filename.c_str(), "rb");
|
||||
fseek(f, 0, SEEK_END);
|
||||
long len = ftell(f);
|
||||
fseek(f, 0, SEEK_SET);
|
||||
vector<uchar> file_buf((size_t)len);
|
||||
EXPECT_EQ(len, (long)fread(&file_buf[0], 1, (size_t)len, f));
|
||||
fclose(f); f = NULL;
|
||||
|
||||
TEST_CYCLE() imdecode(file_buf, IMREAD_UNCHANGED);
|
||||
|
||||
SANITY_CHECK_NOTHING();
|
||||
}
|
||||
|
||||
PERF_TEST(JPEG, Decode_rgb)
|
||||
{
|
||||
String filename = getDataPath("stitching/boat1.jpg");
|
||||
|
||||
FILE *f = fopen(filename.c_str(), "rb");
|
||||
fseek(f, 0, SEEK_END);
|
||||
long len = ftell(f);
|
||||
fseek(f, 0, SEEK_SET);
|
||||
vector<uchar> file_buf((size_t)len);
|
||||
EXPECT_EQ(len, (long)fread(&file_buf[0], 1, (size_t)len, f));
|
||||
fclose(f); f = NULL;
|
||||
|
||||
TEST_CYCLE() imdecode(file_buf, IMREAD_COLOR_RGB);
|
||||
|
||||
SANITY_CHECK_NOTHING();
|
||||
}
|
||||
|
||||
PERF_TEST(JPEG, Encode)
|
||||
{
|
||||
String filename = getDataPath("stitching/boat1.jpg");
|
||||
cv::Mat src = imread(filename);
|
||||
|
||||
vector<uchar> buf;
|
||||
TEST_CYCLE() imencode(".jpg", src, buf);
|
||||
|
||||
SANITY_CHECK_NOTHING();
|
||||
}
|
||||
|
||||
#endif // HAVE_JPEG
|
||||
|
||||
} // namespace
|
||||
@@ -0,0 +1,10 @@
|
||||
// This file is part of OpenCV project.
|
||||
// It is subject to the license terms in the LICENSE file found in the top-level directory
|
||||
// of this distribution and at http://opencv.org/license.html
|
||||
#include "perf_precomp.hpp"
|
||||
|
||||
#if defined(HAVE_HPX)
|
||||
#include <hpx/hpx_main.hpp>
|
||||
#endif
|
||||
|
||||
CV_PERF_TEST_MAIN(imgcodecs)
|
||||
@@ -0,0 +1,90 @@
|
||||
// This file is part of OpenCV project.
|
||||
// It is subject to the license terms in the LICENSE file found in the top-level directory
|
||||
// of this distribution and at http://opencv.org/license.html
|
||||
|
||||
#include "perf_precomp.hpp"
|
||||
|
||||
namespace opencv_test
|
||||
{
|
||||
|
||||
#if defined(HAVE_PNG) || defined(HAVE_SPNG)
|
||||
|
||||
using namespace perf;
|
||||
|
||||
CV_ENUM(PNGStrategy, IMWRITE_PNG_STRATEGY_DEFAULT, IMWRITE_PNG_STRATEGY_FILTERED, IMWRITE_PNG_STRATEGY_HUFFMAN_ONLY, IMWRITE_PNG_STRATEGY_RLE, IMWRITE_PNG_STRATEGY_FIXED);
|
||||
CV_ENUM(PNGFilters, IMWRITE_PNG_FILTER_NONE, IMWRITE_PNG_FILTER_SUB, IMWRITE_PNG_FILTER_UP, IMWRITE_PNG_FILTER_AVG, IMWRITE_PNG_FILTER_PAETH, IMWRITE_PNG_FAST_FILTERS, IMWRITE_PNG_ALL_FILTERS);
|
||||
|
||||
typedef perf::TestBaseWithParam<testing::tuple<PNGStrategy, PNGFilters, int>> PNG;
|
||||
|
||||
PERF_TEST(PNG, decode)
|
||||
{
|
||||
String filename = getDataPath("perf/2560x1600.png");
|
||||
|
||||
FILE *f = fopen(filename.c_str(), "rb");
|
||||
fseek(f, 0, SEEK_END);
|
||||
long len = ftell(f);
|
||||
fseek(f, 0, SEEK_SET);
|
||||
vector<uchar> file_buf((size_t)len);
|
||||
EXPECT_EQ(len, (long)fread(&file_buf[0], 1, (size_t)len, f));
|
||||
fclose(f); f = NULL;
|
||||
|
||||
TEST_CYCLE() imdecode(file_buf, IMREAD_UNCHANGED);
|
||||
|
||||
SANITY_CHECK_NOTHING();
|
||||
}
|
||||
|
||||
PERF_TEST(PNG, decode_rgb)
|
||||
{
|
||||
String filename = getDataPath("perf/2560x1600.png");
|
||||
|
||||
FILE *f = fopen(filename.c_str(), "rb");
|
||||
fseek(f, 0, SEEK_END);
|
||||
long len = ftell(f);
|
||||
fseek(f, 0, SEEK_SET);
|
||||
vector<uchar> file_buf((size_t)len);
|
||||
EXPECT_EQ(len, (long)fread(&file_buf[0], 1, (size_t)len, f));
|
||||
fclose(f); f = NULL;
|
||||
|
||||
TEST_CYCLE() imdecode(file_buf, IMREAD_COLOR_RGB);
|
||||
|
||||
SANITY_CHECK_NOTHING();
|
||||
}
|
||||
|
||||
PERF_TEST(PNG, encode)
|
||||
{
|
||||
String filename = getDataPath("perf/2560x1600.png");
|
||||
cv::Mat src = imread(filename);
|
||||
|
||||
vector<uchar> buf;
|
||||
TEST_CYCLE() imencode(".png", src, buf);
|
||||
|
||||
SANITY_CHECK_NOTHING();
|
||||
}
|
||||
|
||||
PERF_TEST_P(PNG, params,
|
||||
testing::Combine(
|
||||
testing::Values(IMWRITE_PNG_STRATEGY_DEFAULT, IMWRITE_PNG_STRATEGY_FILTERED, IMWRITE_PNG_STRATEGY_HUFFMAN_ONLY, IMWRITE_PNG_STRATEGY_RLE, IMWRITE_PNG_STRATEGY_FIXED),
|
||||
testing::Values(IMWRITE_PNG_FILTER_NONE, IMWRITE_PNG_FILTER_SUB, IMWRITE_PNG_FILTER_UP, IMWRITE_PNG_FILTER_AVG, IMWRITE_PNG_FILTER_PAETH, IMWRITE_PNG_FAST_FILTERS, IMWRITE_PNG_ALL_FILTERS),
|
||||
testing::Values(1, 6)))
|
||||
{
|
||||
String filename = getDataPath("perf/1920x1080.png");
|
||||
const int strategy = get<0>(GetParam());
|
||||
const int filter = get<1>(GetParam());
|
||||
const int level = get<2>(GetParam());
|
||||
|
||||
Mat src = imread(filename);
|
||||
EXPECT_FALSE(src.empty()) << "Cannot open test image perf/1920x1080.png";
|
||||
vector<uchar> buf;
|
||||
|
||||
TEST_CYCLE() imencode(".png", src, buf, { IMWRITE_PNG_COMPRESSION, level, IMWRITE_PNG_STRATEGY, strategy, IMWRITE_PNG_FILTER, filter });
|
||||
|
||||
std::cout << " Encoded buffer size: " << buf.size()
|
||||
<< " bytes, Compression ratio: " << std::fixed << std::setprecision(2)
|
||||
<< (static_cast<double>(buf.size()) / (src.total() * src.channels())) * 100.0 << "%" << std::endl;
|
||||
|
||||
SANITY_CHECK_NOTHING();
|
||||
}
|
||||
|
||||
#endif // HAVE_PNG
|
||||
|
||||
} // namespace
|
||||
@@ -0,0 +1,10 @@
|
||||
// This file is part of OpenCV project.
|
||||
// It is subject to the license terms in the LICENSE file found in the top-level directory
|
||||
// of this distribution and at http://opencv.org/license.html
|
||||
#ifndef __OPENCV_PERF_PRECOMP_HPP__
|
||||
#define __OPENCV_PERF_PRECOMP_HPP__
|
||||
|
||||
#include "opencv2/ts.hpp"
|
||||
#include "opencv2/imgcodecs.hpp"
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,28 @@
|
||||
// This file is part of OpenCV project.
|
||||
// It is subject to the license terms in the LICENSE file found in the top-level directory
|
||||
// of this distribution and at http://opencv.org/license.html.
|
||||
|
||||
#ifndef OPENCV_IMGCODECS_APPLE_CONVERSIONS_H
|
||||
#define OPENCV_IMGCODECS_APPLE_CONVERSIONS_H
|
||||
|
||||
#include "opencv2/core.hpp"
|
||||
#import <Accelerate/Accelerate.h>
|
||||
|
||||
#include <TargetConditionals.h>
|
||||
|
||||
#ifdef HAVE_AVFOUNDATION
|
||||
#import <AVFoundation/AVFoundation.h>
|
||||
#endif
|
||||
|
||||
#if TARGET_OS_IPHONE || (defined(MAC_OS_X_VERSION_MAX_ALLOWED) && MAC_OS_X_VERSION_MAX_ALLOWED >= 1080)
|
||||
#import <ImageIO/ImageIO.h>
|
||||
#else
|
||||
#import <ApplicationServices/ApplicationServices.h>
|
||||
#import <CoreFoundation/CoreFoundation.h>
|
||||
#import <Foundation/Foundation.h>
|
||||
#endif
|
||||
|
||||
CV_EXPORTS CGImageRef MatToCGImage(const cv::Mat& image) CF_RETURNS_RETAINED;
|
||||
CV_EXPORTS void CGImageToMat(const CGImageRef image, cv::Mat& m, bool alphaExist);
|
||||
|
||||
#endif // OPENCV_IMGCODECS_APPLE_CONVERSIONS_H
|
||||
@@ -0,0 +1,100 @@
|
||||
// This file is part of OpenCV project.
|
||||
// It is subject to the license terms in the LICENSE file found in the top-level directory
|
||||
// of this distribution and at http://opencv.org/license.html.
|
||||
|
||||
#include "apple_conversions.h"
|
||||
#include "precomp.hpp"
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#ifndef __bridge
|
||||
#define __bridge
|
||||
#endif
|
||||
|
||||
CGImageRef MatToCGImage(const cv::Mat& image) {
|
||||
NSData *data = [NSData dataWithBytes:image.data
|
||||
length:image.step.p[0] * image.rows];
|
||||
|
||||
CGColorSpaceRef colorSpace;
|
||||
|
||||
if (image.elemSize() == 1) {
|
||||
colorSpace = CGColorSpaceCreateDeviceGray();
|
||||
} else {
|
||||
colorSpace = CGColorSpaceCreateDeviceRGB();
|
||||
}
|
||||
|
||||
CGDataProviderRef provider =
|
||||
CGDataProviderCreateWithCFData((__bridge CFDataRef)data);
|
||||
|
||||
// Preserve alpha transparency, if exists
|
||||
bool alpha = image.channels() == 4;
|
||||
CGBitmapInfo bitmapInfo = (CGBitmapInfo)(alpha ? kCGImageAlphaLast : kCGImageAlphaNone) | (CGBitmapInfo)kCGBitmapByteOrderDefault;
|
||||
|
||||
// Creating CGImage from cv::Mat
|
||||
CGImageRef imageRef = CGImageCreate(image.cols,
|
||||
image.rows,
|
||||
8 * image.elemSize1(),
|
||||
8 * image.elemSize(),
|
||||
image.step.p[0],
|
||||
colorSpace,
|
||||
bitmapInfo,
|
||||
provider,
|
||||
NULL,
|
||||
false,
|
||||
kCGRenderingIntentDefault
|
||||
);
|
||||
|
||||
CGDataProviderRelease(provider);
|
||||
CGColorSpaceRelease(colorSpace);
|
||||
|
||||
return imageRef;
|
||||
}
|
||||
|
||||
void CGImageToMat(const CGImageRef image, cv::Mat& m, bool alphaExist) {
|
||||
CGColorSpaceRef colorSpace = CGImageGetColorSpace(image);
|
||||
CGFloat cols = CGImageGetWidth(image), rows = CGImageGetHeight(image);
|
||||
CGContextRef contextRef;
|
||||
CGBitmapInfo bitmapInfo = kCGImageAlphaPremultipliedLast;
|
||||
if (CGColorSpaceGetModel(colorSpace) == kCGColorSpaceModelMonochrome)
|
||||
{
|
||||
m.create(rows, cols, CV_8UC1); // 8 bits per component, 1 channel
|
||||
bitmapInfo = kCGImageAlphaNone;
|
||||
if (!alphaExist)
|
||||
bitmapInfo = kCGImageAlphaNone;
|
||||
else
|
||||
m = cv::Scalar(0);
|
||||
contextRef = CGBitmapContextCreate(m.data, m.cols, m.rows, 8,
|
||||
m.step[0], colorSpace,
|
||||
bitmapInfo);
|
||||
}
|
||||
else if (CGColorSpaceGetModel(colorSpace) == kCGColorSpaceModelIndexed)
|
||||
{
|
||||
// CGBitmapContextCreate() does not support indexed color spaces.
|
||||
colorSpace = CGColorSpaceCreateDeviceRGB();
|
||||
m.create(rows, cols, CV_8UC4); // 8 bits per component, 4 channels
|
||||
if (!alphaExist)
|
||||
bitmapInfo = (CGBitmapInfo)kCGImageAlphaNoneSkipLast |
|
||||
(CGBitmapInfo)kCGBitmapByteOrderDefault;
|
||||
else
|
||||
m = cv::Scalar(0);
|
||||
contextRef = CGBitmapContextCreate(m.data, m.cols, m.rows, 8,
|
||||
m.step[0], colorSpace,
|
||||
bitmapInfo);
|
||||
CGColorSpaceRelease(colorSpace);
|
||||
}
|
||||
else
|
||||
{
|
||||
m.create(rows, cols, CV_8UC4); // 8 bits per component, 4 channels
|
||||
if (!alphaExist)
|
||||
bitmapInfo = (CGBitmapInfo)kCGImageAlphaNoneSkipLast |
|
||||
(CGBitmapInfo)kCGBitmapByteOrderDefault;
|
||||
else
|
||||
m = cv::Scalar(0);
|
||||
contextRef = CGBitmapContextCreate(m.data, m.cols, m.rows, 8,
|
||||
m.step[0], colorSpace,
|
||||
bitmapInfo);
|
||||
}
|
||||
CGContextDrawImage(contextRef, CGRectMake(0, 0, cols, rows),
|
||||
image);
|
||||
CGContextRelease(contextRef);
|
||||
}
|
||||
@@ -0,0 +1,581 @@
|
||||
// This file is part of OpenCV project.
|
||||
// It is subject to the license terms in the LICENSE file found in the top-level
|
||||
// directory of this distribution and at http://opencv.org/license.html
|
||||
|
||||
#include "precomp.hpp"
|
||||
#include "bitstrm.hpp"
|
||||
#include "utils.hpp"
|
||||
|
||||
namespace cv
|
||||
{
|
||||
|
||||
const int BS_DEF_BLOCK_SIZE = 1<<15;
|
||||
|
||||
///////////////////////// RBaseStream ////////////////////////////
|
||||
|
||||
bool RBaseStream::isOpened()
|
||||
{
|
||||
return m_is_opened;
|
||||
}
|
||||
|
||||
void RBaseStream::allocate()
|
||||
{
|
||||
if( !m_allocated )
|
||||
{
|
||||
m_start = new uchar[m_block_size];
|
||||
m_end = m_start + m_block_size;
|
||||
m_current = m_end;
|
||||
m_allocated = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
RBaseStream::RBaseStream()
|
||||
{
|
||||
m_start = m_end = m_current = 0;
|
||||
m_file = 0;
|
||||
m_block_pos = 0;
|
||||
m_block_size = BS_DEF_BLOCK_SIZE;
|
||||
m_is_opened = false;
|
||||
m_allocated = false;
|
||||
}
|
||||
|
||||
|
||||
RBaseStream::~RBaseStream()
|
||||
{
|
||||
close(); // Close files
|
||||
release(); // free buffers
|
||||
}
|
||||
|
||||
|
||||
void RBaseStream::readBlock()
|
||||
{
|
||||
setPos( getPos() ); // normalize position
|
||||
|
||||
if( m_file == 0 )
|
||||
{
|
||||
if( m_block_pos == 0 && m_current < m_end )
|
||||
return;
|
||||
throw RBS_THROW_EOS;
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
// See https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/fseek-fseeki64?view=msvc-170
|
||||
// See https://en.wikipedia.org/wiki/64-bit_computing#64-bit_data_models
|
||||
// - Windows uses LLP64 data model, sizeof long int = 32.
|
||||
// - Linux uses LP64 data model, sizeof long int = 64.
|
||||
// So for Windows, we have to use _fseeki64() instead of fseek().
|
||||
_fseeki64( m_file, m_block_pos, SEEK_SET );
|
||||
#else
|
||||
fseek( m_file, m_block_pos, SEEK_SET );
|
||||
#endif
|
||||
|
||||
size_t readed = fread( m_start, 1, m_block_size, m_file );
|
||||
m_end = m_start + readed;
|
||||
|
||||
if( readed == 0 || m_current >= m_end )
|
||||
throw RBS_THROW_EOS;
|
||||
}
|
||||
|
||||
|
||||
bool RBaseStream::open( const String& filename )
|
||||
{
|
||||
close();
|
||||
allocate();
|
||||
|
||||
m_file = fopen( filename.c_str(), "rb" );
|
||||
if( m_file )
|
||||
{
|
||||
m_is_opened = true;
|
||||
setPos(0);
|
||||
readBlock();
|
||||
}
|
||||
return m_file != 0;
|
||||
}
|
||||
|
||||
bool RBaseStream::open( const Mat& buf )
|
||||
{
|
||||
close();
|
||||
if( buf.empty() )
|
||||
return false;
|
||||
CV_Assert(buf.isContinuous());
|
||||
m_start = buf.data;
|
||||
m_end = m_start + buf.cols*buf.rows*buf.elemSize();
|
||||
m_allocated = false;
|
||||
m_is_opened = true;
|
||||
setPos(0);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void RBaseStream::close()
|
||||
{
|
||||
if( m_file )
|
||||
{
|
||||
fclose( m_file );
|
||||
m_file = 0;
|
||||
}
|
||||
m_is_opened = false;
|
||||
if( !m_allocated )
|
||||
m_start = m_end = m_current = 0;
|
||||
}
|
||||
|
||||
|
||||
void RBaseStream::release()
|
||||
{
|
||||
if( m_allocated )
|
||||
delete[] m_start;
|
||||
m_start = m_end = m_current = 0;
|
||||
m_allocated = false;
|
||||
}
|
||||
|
||||
|
||||
void RBaseStream::setPos( int64_t pos )
|
||||
{
|
||||
CV_Assert(isOpened() && pos >= 0);
|
||||
|
||||
if( !m_file )
|
||||
{
|
||||
m_current = m_start + pos;
|
||||
m_block_pos = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
int offset = pos % m_block_size;
|
||||
int64_t old_block_pos = m_block_pos;
|
||||
m_block_pos = pos - offset;
|
||||
m_current = m_start + offset;
|
||||
if (old_block_pos != m_block_pos)
|
||||
readBlock();
|
||||
}
|
||||
|
||||
|
||||
int64_t RBaseStream::getPos()
|
||||
{
|
||||
CV_Assert(isOpened());
|
||||
int64_t pos = static_cast<int64_t>((m_current - m_start) + m_block_pos);
|
||||
CV_Assert(pos >= m_block_pos); // overflow check
|
||||
CV_Assert(pos >= 0); // overflow check
|
||||
return pos;
|
||||
}
|
||||
|
||||
void RBaseStream::skip( int64_t bytes )
|
||||
{
|
||||
CV_Assert(bytes >= 0);
|
||||
uchar* old = m_current;
|
||||
m_current += bytes;
|
||||
CV_Assert(m_current >= old); // overflow check
|
||||
}
|
||||
|
||||
///////////////////////// RLByteStream ////////////////////////////
|
||||
|
||||
RLByteStream::~RLByteStream()
|
||||
{
|
||||
}
|
||||
|
||||
int RLByteStream::getByte()
|
||||
{
|
||||
uchar *current = m_current;
|
||||
int val;
|
||||
|
||||
if( current >= m_end )
|
||||
{
|
||||
readBlock();
|
||||
current = m_current;
|
||||
}
|
||||
|
||||
CV_Assert(current < m_end);
|
||||
|
||||
val = *((uchar*)current);
|
||||
m_current = current + 1;
|
||||
return val;
|
||||
}
|
||||
|
||||
|
||||
int RLByteStream::getBytes( void* buffer, int count )
|
||||
{
|
||||
uchar* data = (uchar*)buffer;
|
||||
int readed = 0;
|
||||
CV_Assert(count >= 0);
|
||||
|
||||
while( count > 0 )
|
||||
{
|
||||
int l;
|
||||
|
||||
for(;;)
|
||||
{
|
||||
l = (int)(m_end - m_current);
|
||||
if( l > count ) l = count;
|
||||
if( l > 0 ) break;
|
||||
readBlock();
|
||||
}
|
||||
memcpy( data, m_current, l );
|
||||
m_current += l;
|
||||
data += l;
|
||||
count -= l;
|
||||
readed += l;
|
||||
}
|
||||
return readed;
|
||||
}
|
||||
|
||||
|
||||
//////////// RLByteStream & RMByteStream <Get[d]word>s ////////////////
|
||||
|
||||
RMByteStream::~RMByteStream()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
int RLByteStream::getWord()
|
||||
{
|
||||
uchar *current = m_current;
|
||||
int val;
|
||||
|
||||
if( current+1 < m_end )
|
||||
{
|
||||
val = current[0] + (current[1] << 8);
|
||||
m_current = current + 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
val = getByte();
|
||||
val|= getByte() << 8;
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
|
||||
int RLByteStream::getDWord()
|
||||
{
|
||||
uchar *current = m_current;
|
||||
int val;
|
||||
|
||||
if( current+3 < m_end )
|
||||
{
|
||||
val = current[0] + (current[1] << 8) +
|
||||
(current[2] << 16) + (current[3] << 24);
|
||||
m_current = current + 4;
|
||||
}
|
||||
else
|
||||
{
|
||||
val = getByte();
|
||||
val |= getByte() << 8;
|
||||
val |= getByte() << 16;
|
||||
val |= getByte() << 24;
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
|
||||
int RMByteStream::getWord()
|
||||
{
|
||||
uchar *current = m_current;
|
||||
int val;
|
||||
|
||||
if( current+1 < m_end )
|
||||
{
|
||||
val = (current[0] << 8) + current[1];
|
||||
m_current = current + 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
val = getByte() << 8;
|
||||
val|= getByte();
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
|
||||
int RMByteStream::getDWord()
|
||||
{
|
||||
uchar *current = m_current;
|
||||
int val;
|
||||
|
||||
if( current+3 < m_end )
|
||||
{
|
||||
val = (current[0] << 24) + (current[1] << 16) +
|
||||
(current[2] << 8) + current[3];
|
||||
m_current = current + 4;
|
||||
}
|
||||
else
|
||||
{
|
||||
val = getByte() << 24;
|
||||
val |= getByte() << 16;
|
||||
val |= getByte() << 8;
|
||||
val |= getByte();
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
/////////////////////////// WBaseStream /////////////////////////////////
|
||||
|
||||
// WBaseStream - base class for output streams
|
||||
WBaseStream::WBaseStream()
|
||||
{
|
||||
m_start = m_end = m_current = 0;
|
||||
m_file = 0;
|
||||
m_block_pos = 0;
|
||||
m_block_size = BS_DEF_BLOCK_SIZE;
|
||||
m_is_opened = false;
|
||||
m_buf = 0;
|
||||
}
|
||||
|
||||
|
||||
WBaseStream::~WBaseStream()
|
||||
{
|
||||
close();
|
||||
release();
|
||||
}
|
||||
|
||||
|
||||
bool WBaseStream::isOpened()
|
||||
{
|
||||
return m_is_opened;
|
||||
}
|
||||
|
||||
|
||||
void WBaseStream::allocate()
|
||||
{
|
||||
if( !m_start )
|
||||
m_start = new uchar[m_block_size];
|
||||
|
||||
m_end = m_start + m_block_size;
|
||||
m_current = m_start;
|
||||
}
|
||||
|
||||
|
||||
bool WBaseStream::writeBlock()
|
||||
{
|
||||
int size = (int)(m_current - m_start);
|
||||
|
||||
CV_Assert(isOpened());
|
||||
if( size == 0 )
|
||||
return true;
|
||||
|
||||
if( m_buf )
|
||||
{
|
||||
size_t sz = m_buf->size();
|
||||
m_buf->resize( sz + size );
|
||||
memcpy( &(*m_buf)[sz], m_start, size );
|
||||
m_current = m_start;
|
||||
m_block_pos += size;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
size_t written = fwrite( m_start, 1, size, m_file );
|
||||
m_current = m_start;
|
||||
m_block_pos += size;
|
||||
return written == (size_t)size;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool WBaseStream::open( const String& filename )
|
||||
{
|
||||
close();
|
||||
allocate();
|
||||
|
||||
m_file = fopen( filename.c_str(), "wb" );
|
||||
if( m_file )
|
||||
{
|
||||
m_is_opened = true;
|
||||
m_block_pos = 0;
|
||||
m_current = m_start;
|
||||
}
|
||||
return m_file != 0;
|
||||
}
|
||||
|
||||
bool WBaseStream::open( std::vector<uchar>& buf )
|
||||
{
|
||||
close();
|
||||
allocate();
|
||||
|
||||
m_buf = &buf;
|
||||
m_is_opened = true;
|
||||
m_block_pos = 0;
|
||||
m_current = m_start;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void WBaseStream::close()
|
||||
{
|
||||
if( m_is_opened )
|
||||
writeBlock();
|
||||
if( m_file )
|
||||
{
|
||||
fclose( m_file );
|
||||
m_file = 0;
|
||||
}
|
||||
m_buf = 0;
|
||||
m_is_opened = false;
|
||||
}
|
||||
|
||||
|
||||
void WBaseStream::release()
|
||||
{
|
||||
if( m_start )
|
||||
delete[] m_start;
|
||||
m_start = m_end = m_current = 0;
|
||||
}
|
||||
|
||||
|
||||
int WBaseStream::getPos()
|
||||
{
|
||||
CV_Assert(isOpened());
|
||||
return m_block_pos + (int)(m_current - m_start);
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////// WLByteStream ///////////////////////////////////
|
||||
|
||||
WLByteStream::~WLByteStream()
|
||||
{
|
||||
}
|
||||
|
||||
bool WLByteStream::putByte( int val )
|
||||
{
|
||||
*m_current++ = (uchar)val;
|
||||
if( m_current >= m_end )
|
||||
return writeBlock();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool WLByteStream::putBytes( const void* buffer, int count )
|
||||
{
|
||||
uchar* data = (uchar*)buffer;
|
||||
|
||||
CV_Assert(data && m_current && count >= 0);
|
||||
|
||||
while( count )
|
||||
{
|
||||
int l = (int)(m_end - m_current);
|
||||
|
||||
if( l > count )
|
||||
l = count;
|
||||
|
||||
if( l > 0 )
|
||||
{
|
||||
memcpy( m_current, data, l );
|
||||
m_current += l;
|
||||
data += l;
|
||||
count -= l;
|
||||
}
|
||||
if( m_current == m_end )
|
||||
{
|
||||
bool written = writeBlock();
|
||||
if (!written)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool WLByteStream::putWord( int val )
|
||||
{
|
||||
uchar *current = m_current;
|
||||
|
||||
if( current+1 < m_end )
|
||||
{
|
||||
current[0] = (uchar)val;
|
||||
current[1] = (uchar)(val >> 8);
|
||||
m_current = current + 2;
|
||||
if( m_current == m_end )
|
||||
return writeBlock();
|
||||
}
|
||||
else
|
||||
{
|
||||
putByte(val);
|
||||
putByte(val >> 8);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool WLByteStream::putDWord( int val )
|
||||
{
|
||||
uchar *current = m_current;
|
||||
|
||||
if( current+3 < m_end )
|
||||
{
|
||||
current[0] = (uchar)val;
|
||||
current[1] = (uchar)(val >> 8);
|
||||
current[2] = (uchar)(val >> 16);
|
||||
current[3] = (uchar)(val >> 24);
|
||||
m_current = current + 4;
|
||||
if( m_current == m_end )
|
||||
return writeBlock();
|
||||
}
|
||||
else
|
||||
{
|
||||
putByte(val);
|
||||
putByte(val >> 8);
|
||||
putByte(val >> 16);
|
||||
putByte(val >> 24);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////// WMByteStream ///////////////////////////////////
|
||||
|
||||
WMByteStream::~WMByteStream()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
bool WMByteStream::putWord( int val )
|
||||
{
|
||||
uchar *current = m_current;
|
||||
|
||||
if( current+1 < m_end )
|
||||
{
|
||||
current[0] = (uchar)(val >> 8);
|
||||
current[1] = (uchar)val;
|
||||
m_current = current + 2;
|
||||
if( m_current == m_end )
|
||||
return writeBlock();
|
||||
}
|
||||
else
|
||||
{
|
||||
putByte(val >> 8);
|
||||
putByte(val);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool WMByteStream::putDWord( int val )
|
||||
{
|
||||
uchar *current = m_current;
|
||||
|
||||
if( current+3 < m_end )
|
||||
{
|
||||
current[0] = (uchar)(val >> 24);
|
||||
current[1] = (uchar)(val >> 16);
|
||||
current[2] = (uchar)(val >> 8);
|
||||
current[3] = (uchar)val;
|
||||
m_current = current + 4;
|
||||
if( m_current == m_end )
|
||||
return writeBlock();
|
||||
}
|
||||
else
|
||||
{
|
||||
putByte(val >> 24);
|
||||
putByte(val >> 16);
|
||||
putByte(val >> 8);
|
||||
putByte(val);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
// This file is part of OpenCV project.
|
||||
// It is subject to the license terms in the LICENSE file found in the top-level
|
||||
// directory of this distribution and at http://opencv.org/license.html
|
||||
|
||||
#ifndef _BITSTRM_H_
|
||||
#define _BITSTRM_H_
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
namespace cv
|
||||
{
|
||||
|
||||
#define DECLARE_RBS_EXCEPTION(name) \
|
||||
class RBS_ ## name ## _Exception : public cv::Exception \
|
||||
{ \
|
||||
public: \
|
||||
RBS_ ## name ## _Exception(int code_, const String& err_, const String& func_, const String& file_, int line_) : \
|
||||
cv::Exception(code_, err_, func_, file_, line_) \
|
||||
{} \
|
||||
};
|
||||
DECLARE_RBS_EXCEPTION(THROW_EOS)
|
||||
#define RBS_THROW_EOS RBS_THROW_EOS_Exception(cv::Error::StsError, "Unexpected end of input stream", CV_Func, __FILE__, __LINE__)
|
||||
DECLARE_RBS_EXCEPTION(THROW_FORB)
|
||||
#define RBS_THROW_FORB RBS_THROW_FORB_Exception(cv::Error::StsError, "Forrbidden huffman code", CV_Func, __FILE__, __LINE__)
|
||||
DECLARE_RBS_EXCEPTION(BAD_HEADER)
|
||||
#define RBS_BAD_HEADER RBS_BAD_HEADER_Exception(cv::Error::StsError, "Invalid header", CV_Func, __FILE__, __LINE__)
|
||||
|
||||
#define CHECK_WRITE(action) \
|
||||
if (!action) \
|
||||
{ \
|
||||
return false; \
|
||||
}
|
||||
|
||||
typedef unsigned long ulong;
|
||||
|
||||
// class RBaseStream - base class for other reading streams.
|
||||
class RBaseStream
|
||||
{
|
||||
public:
|
||||
//methods
|
||||
RBaseStream();
|
||||
virtual ~RBaseStream();
|
||||
|
||||
virtual bool open( const String& filename );
|
||||
virtual bool open( const Mat& buf );
|
||||
virtual void close();
|
||||
bool isOpened();
|
||||
void setPos( int64_t pos );
|
||||
int64_t getPos();
|
||||
void skip( int64_t bytes );
|
||||
|
||||
protected:
|
||||
|
||||
bool m_allocated;
|
||||
uchar* m_start;
|
||||
uchar* m_end;
|
||||
uchar* m_current;
|
||||
FILE* m_file;
|
||||
int m_block_size;
|
||||
int64_t m_block_pos;
|
||||
bool m_is_opened;
|
||||
|
||||
virtual void readBlock();
|
||||
virtual void release();
|
||||
virtual void allocate();
|
||||
};
|
||||
|
||||
|
||||
// class RLByteStream - uchar-oriented stream.
|
||||
// l in prefix means that the least significant uchar of a multi-uchar value goes first
|
||||
class RLByteStream : public RBaseStream
|
||||
{
|
||||
public:
|
||||
virtual ~RLByteStream();
|
||||
|
||||
int getByte();
|
||||
int getBytes( void* buffer, int count );
|
||||
int getWord();
|
||||
int getDWord();
|
||||
};
|
||||
|
||||
// class RMBitStream - uchar-oriented stream.
|
||||
// m in prefix means that the most significant uchar of a multi-uchar value go first
|
||||
class RMByteStream : public RLByteStream
|
||||
{
|
||||
public:
|
||||
virtual ~RMByteStream();
|
||||
|
||||
int getWord();
|
||||
int getDWord();
|
||||
};
|
||||
|
||||
// WBaseStream - base class for output streams
|
||||
class WBaseStream
|
||||
{
|
||||
public:
|
||||
//methods
|
||||
WBaseStream();
|
||||
virtual ~WBaseStream();
|
||||
|
||||
virtual bool open( const String& filename );
|
||||
virtual bool open( std::vector<uchar>& buf );
|
||||
virtual void close();
|
||||
bool isOpened();
|
||||
int getPos();
|
||||
|
||||
protected:
|
||||
|
||||
uchar* m_start;
|
||||
uchar* m_end;
|
||||
uchar* m_current;
|
||||
int m_block_size;
|
||||
int m_block_pos;
|
||||
FILE* m_file;
|
||||
bool m_is_opened;
|
||||
std::vector<uchar>* m_buf;
|
||||
|
||||
virtual bool writeBlock();
|
||||
virtual void release();
|
||||
virtual void allocate();
|
||||
};
|
||||
|
||||
|
||||
// class WLByteStream - uchar-oriented stream.
|
||||
// l in prefix means that the least significant uchar of a multi-byte value goes first
|
||||
class WLByteStream : public WBaseStream
|
||||
{
|
||||
public:
|
||||
virtual ~WLByteStream();
|
||||
|
||||
bool putByte( int val );
|
||||
bool putBytes( const void* buffer, int count );
|
||||
bool putWord( int val );
|
||||
bool putDWord( int val );
|
||||
};
|
||||
|
||||
|
||||
// class WLByteStream - uchar-oriented stream.
|
||||
// m in prefix means that the least significant uchar of a multi-byte value goes last
|
||||
class WMByteStream : public WLByteStream
|
||||
{
|
||||
public:
|
||||
virtual ~WMByteStream();
|
||||
bool putWord( int val );
|
||||
bool putDWord( int val );
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif/*_BITSTRM_H_*/
|
||||
@@ -0,0 +1,643 @@
|
||||
/*M///////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
|
||||
//
|
||||
// By downloading, copying, installing or using the software you agree to this license.
|
||||
// If you do not agree to this license, do not download, install,
|
||||
// copy or use the software.
|
||||
//
|
||||
//
|
||||
// License Agreement
|
||||
// For Open Source Computer Vision Library
|
||||
//
|
||||
// Copyright (C) 2000-2008, Intel Corporation, all rights reserved.
|
||||
// Copyright (C) 2009, Willow Garage Inc., all rights reserved.
|
||||
// Third party copyrights are property of their respective owners.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without modification,
|
||||
// are permitted provided that the following conditions are met:
|
||||
//
|
||||
// * Redistribution's of source code must retain the above copyright notice,
|
||||
// this list of conditions and the following disclaimer.
|
||||
//
|
||||
// * Redistribution's in binary form must reproduce the above copyright notice,
|
||||
// this list of conditions and the following disclaimer in the documentation
|
||||
// and/or other materials provided with the distribution.
|
||||
//
|
||||
// * The name of the copyright holders may not be used to endorse or promote products
|
||||
// derived from this software without specific prior written permission.
|
||||
//
|
||||
// This software is provided by the copyright holders and contributors "as is" and
|
||||
// any express or implied warranties, including, but not limited to, the implied
|
||||
// warranties of merchantability and fitness for a particular purpose are disclaimed.
|
||||
// In no event shall the Intel Corporation or contributors be liable for any direct,
|
||||
// indirect, incidental, special, exemplary, or consequential damages
|
||||
// (including, but not limited to, procurement of substitute goods or services;
|
||||
// loss of use, data, or profits; or business interruption) however caused
|
||||
// and on any theory of liability, whether in contract, strict liability,
|
||||
// or tort (including negligence or otherwise) arising in any way out of
|
||||
// the use of this software, even if advised of the possibility of such damage.
|
||||
//
|
||||
//M*/
|
||||
|
||||
#include "precomp.hpp"
|
||||
#include "exif.hpp"
|
||||
#include "opencv2/core/utils/logger.hpp"
|
||||
|
||||
namespace {
|
||||
|
||||
class ExifParsingError {
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
namespace cv
|
||||
{
|
||||
|
||||
static std::string HexStringToBytes(const char* hexstring, size_t hexstring_len, size_t expected_length);
|
||||
|
||||
// Converts the 'hexstring' (of at most 'hexstring_len' bytes) which contains
|
||||
// 2-byte character representations of hex values to raw data.
|
||||
// 'hexstring' may contain values consisting of [A-F][a-f][0-9] in pairs,
|
||||
// e.g., 7af2..., separated by any number of newlines.
|
||||
// 'expected_length' is the anticipated processed size.
|
||||
// On success the raw buffer is returned with its length equivalent to
|
||||
// 'expected_length'. An empty buffer is returned if the processed length is
|
||||
// less than 'expected_length' or any character aside from those above is
|
||||
// encountered. All reads are bounded by 'hexstring_len'.
|
||||
static std::string HexStringToBytes(const char* hexstring,
|
||||
size_t hexstring_len, size_t expected_length) {
|
||||
const char* src = hexstring;
|
||||
const char* const src_end = hexstring + hexstring_len;
|
||||
size_t actual_length = 0;
|
||||
std::string raw_data;
|
||||
raw_data.resize(expected_length);
|
||||
char* dst = const_cast<char*>(raw_data.data());
|
||||
|
||||
while (actual_length < expected_length && src < src_end) {
|
||||
if (*src == '\n') { ++src; continue; }
|
||||
if (src + 1 >= src_end) break; // need two characters to form a byte
|
||||
char val[3];
|
||||
char* end;
|
||||
val[0] = src[0];
|
||||
val[1] = src[1];
|
||||
val[2] = '\0';
|
||||
*dst++ = static_cast<uint8_t>(strtol(val, &end, 16));
|
||||
if (end != val + 2) break;
|
||||
src += 2;
|
||||
++actual_length;
|
||||
}
|
||||
|
||||
if (actual_length != expected_length) {
|
||||
raw_data.clear();
|
||||
}
|
||||
return raw_data;
|
||||
}
|
||||
|
||||
ExifEntry_t::ExifEntry_t() :
|
||||
field_float(0), field_double(0), field_u32(0), field_s32(0),
|
||||
tag(INVALID_TAG), field_u16(0), field_s16(0), field_u8(0), field_s8(0)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief ExifReader constructor
|
||||
*/
|
||||
ExifReader::ExifReader() : m_format(NONE)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief ExifReader destructor
|
||||
*/
|
||||
ExifReader::~ExifReader()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Get tag value by tag number
|
||||
*
|
||||
* @param [in] tag The tag number
|
||||
*
|
||||
* @return ExifEntru_t structure. Caller has to know what tag it calls in order to extract proper field from the structure ExifEntry_t
|
||||
*
|
||||
*/
|
||||
ExifEntry_t ExifReader::getTag(const ExifTagName tag) const
|
||||
{
|
||||
ExifEntry_t entry;
|
||||
std::map<int, ExifEntry_t>::const_iterator it = m_exif.find(tag);
|
||||
|
||||
if( it != m_exif.end() )
|
||||
{
|
||||
entry = it->second;
|
||||
}
|
||||
return entry;
|
||||
}
|
||||
|
||||
const std::vector<unsigned char>& ExifReader::getData() const
|
||||
{
|
||||
return m_data;
|
||||
}
|
||||
|
||||
bool ExifReader::processRawProfile(const char* profile, size_t profile_len) {
|
||||
if (profile == nullptr || profile_len == 0) return false;
|
||||
|
||||
const char* src = profile;
|
||||
const char* const profile_end = profile + profile_len;
|
||||
|
||||
// ImageMagick formats 'raw profiles' as
|
||||
// '\n<name>\n<length>(%8lu)\n<hex payload>\n'. Every scan below is bounded by
|
||||
// 'profile_end' so that a malformed (e.g. not NUL-terminated, or missing a
|
||||
// newline) profile can never be read past its buffer.
|
||||
if (*src != '\n') {
|
||||
CV_LOG_WARNING(NULL, cv::format("Malformed raw profile, expected '\\n' got '\\x%.2X'", (unsigned char)*src));
|
||||
return false;
|
||||
}
|
||||
++src;
|
||||
|
||||
// skip the profile name up to the next newline
|
||||
while (src < profile_end && *src != '\n') ++src;
|
||||
if (src >= profile_end) { // name not terminated within the buffer
|
||||
CV_LOG_WARNING(NULL, "Malformed raw profile, profile name is not terminated");
|
||||
return false;
|
||||
}
|
||||
++src;
|
||||
|
||||
// the length token runs up to the next newline; parse it from a bounded,
|
||||
// NUL-terminated copy (strtol handles the leading spaces of the %8lu field).
|
||||
const char* len_begin = src;
|
||||
while (src < profile_end && *src != '\n') ++src;
|
||||
if (src >= profile_end) { // length not terminated within the buffer
|
||||
CV_LOG_WARNING(NULL, "Malformed raw profile, length field is not terminated");
|
||||
return false;
|
||||
}
|
||||
const std::string len_str(len_begin, src);
|
||||
++src; // 'src' now points to the hex payload
|
||||
|
||||
char* parse_end = nullptr;
|
||||
const long declared_length = strtol(len_str.c_str(), &parse_end, 10);
|
||||
if (parse_end == len_str.c_str() || *parse_end != '\0') { // not a clean decimal number
|
||||
CV_LOG_WARNING(NULL, "Malformed raw profile, length field is not a valid number");
|
||||
return false;
|
||||
}
|
||||
|
||||
// the payload starts with a 6-byte "Exif\0\0" header, so a shorter declared
|
||||
// length underflows the size and pointer handed to parseExif() below. it also
|
||||
// cannot be larger than the profile text that carries it (two hex characters
|
||||
// encode one byte), so reject an over-large value to avoid a huge speculative
|
||||
// allocation in HexStringToBytes().
|
||||
if (declared_length < 6 || static_cast<size_t>(declared_length) > profile_len) {
|
||||
CV_LOG_WARNING(NULL, cv::format("Malformed raw profile, declared length %ld is out of range (profile size %llu)",
|
||||
declared_length, (unsigned long long)profile_len));
|
||||
return false;
|
||||
}
|
||||
const size_t expected_length = static_cast<size_t>(declared_length);
|
||||
|
||||
std::string payload = HexStringToBytes(src, (size_t)(profile_end - src), expected_length);
|
||||
if (payload.size() == 0) { // fewer hex bytes than the declared length
|
||||
CV_LOG_WARNING(NULL, "Malformed raw profile, hex payload shorter than declared length");
|
||||
return false;
|
||||
}
|
||||
|
||||
return parseExif((unsigned char*)payload.c_str() + 6, expected_length - 6);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Parsing the exif data buffer and prepare (internal) exif directory
|
||||
*
|
||||
* @param [in] data The data buffer to read EXIF data starting with endianness
|
||||
* @param [in] size The size of the data buffer
|
||||
*
|
||||
* @return true if parsing was successful
|
||||
* false in case of unsuccessful parsing
|
||||
*/
|
||||
bool ExifReader::parseExif(unsigned char* data, const size_t size)
|
||||
{
|
||||
// Populate m_data, then call parseExif() (private)
|
||||
if( data && size > 0 )
|
||||
{
|
||||
m_data.assign(data, data + size);
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
parseExif();
|
||||
if( !m_exif.empty() )
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
catch( ExifParsingError& ) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Filling m_exif member with exif directory elements
|
||||
* This is internal function and is not exposed to client
|
||||
*
|
||||
* The function doesn't return any value. In case of unsuccessful parsing
|
||||
* the m_exif member is not filled up
|
||||
*/
|
||||
void ExifReader::parseExif()
|
||||
{
|
||||
m_format = getFormat();
|
||||
|
||||
if( !checkTagMark() )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t offset = getStartOffset();
|
||||
|
||||
size_t numEntry = getNumDirEntry( offset );
|
||||
|
||||
offset += 2; //go to start of tag fields
|
||||
|
||||
for( size_t entry = 0; entry < numEntry; entry++ )
|
||||
{
|
||||
ExifEntry_t exifEntry = parseExifEntry( offset );
|
||||
m_exif.insert( std::make_pair( exifEntry.tag, exifEntry ) );
|
||||
offset += tiffFieldSize;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get endianness of exif information
|
||||
* This is internal function and is not exposed to client
|
||||
*
|
||||
* @return INTEL, MOTO or NONE
|
||||
*/
|
||||
Endianness_t ExifReader::getFormat() const
|
||||
{
|
||||
if (m_data.size() < 1)
|
||||
return NONE;
|
||||
|
||||
if( m_data.size() > 1 && m_data[0] != m_data[1] )
|
||||
{
|
||||
return NONE;
|
||||
}
|
||||
|
||||
if( m_data[0] == 'I' )
|
||||
{
|
||||
return INTEL;
|
||||
}
|
||||
|
||||
if( m_data[0] == 'M' )
|
||||
{
|
||||
return MOTO;
|
||||
}
|
||||
|
||||
return NONE;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Checking whether Tag Mark (0x002A) correspond to one contained in the Jpeg file
|
||||
* This is internal function and is not exposed to client
|
||||
*
|
||||
* @return true if tag mark equals 0x002A, false otherwise
|
||||
*/
|
||||
bool ExifReader::checkTagMark() const
|
||||
{
|
||||
uint16_t tagMark = getU16( 2 );
|
||||
|
||||
if( tagMark != tagMarkRequired )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief The utility function for extracting actual offset exif IFD0 info is started from
|
||||
* This is internal function and is not exposed to client
|
||||
*
|
||||
* @return offset of IFD0 field
|
||||
*/
|
||||
uint32_t ExifReader::getStartOffset() const
|
||||
{
|
||||
return getU32( 4 );
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get the number of Directory Entries in Jpeg file
|
||||
*
|
||||
* @return The number of directory entries
|
||||
*/
|
||||
size_t ExifReader::getNumDirEntry(const size_t offsetNumDir) const
|
||||
{
|
||||
return getU16( offsetNumDir );
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Parsing particular entry in exif directory
|
||||
* This is internal function and is not exposed to client
|
||||
*
|
||||
* Entries are divided into 12-bytes blocks each
|
||||
* Each block corresponds the following structure:
|
||||
*
|
||||
* +------+-------------+-------------------+------------------------+
|
||||
* | Type | Data format | Num of components | Data or offset to data |
|
||||
* +======+=============+===================+========================+
|
||||
* | TTTT | ffff | NNNNNNNN | DDDDDDDD |
|
||||
* +------+-------------+-------------------+------------------------+
|
||||
*
|
||||
* Details can be found here: http://www.media.mit.edu/pia/Research/deepview/exif.html
|
||||
*
|
||||
* @param [in] offset Offset to entry in bytes inside raw exif data
|
||||
* @return ExifEntry_t structure which corresponds to particular entry
|
||||
*
|
||||
*/
|
||||
ExifEntry_t ExifReader::parseExifEntry(const size_t offset)
|
||||
{
|
||||
ExifEntry_t entry;
|
||||
uint16_t tagNum = getExifTag( offset );
|
||||
entry.tag = tagNum;
|
||||
|
||||
switch( tagNum )
|
||||
{
|
||||
case IMAGE_DESCRIPTION:
|
||||
entry.field_str = getString( offset );
|
||||
break;
|
||||
case MAKE:
|
||||
entry.field_str = getString( offset );
|
||||
break;
|
||||
case MODEL:
|
||||
entry.field_str = getString( offset );
|
||||
break;
|
||||
case ORIENTATION:
|
||||
entry.field_u16 = getOrientation( offset );
|
||||
break;
|
||||
case XRESOLUTION:
|
||||
entry.field_u_rational = getResolution( offset );
|
||||
break;
|
||||
case YRESOLUTION:
|
||||
entry.field_u_rational = getResolution( offset );
|
||||
break;
|
||||
case RESOLUTION_UNIT:
|
||||
entry.field_u16 = getResolutionUnit( offset );
|
||||
break;
|
||||
case SOFTWARE:
|
||||
entry.field_str = getString( offset );
|
||||
break;
|
||||
case DATE_TIME:
|
||||
entry.field_str = getString( offset );
|
||||
break;
|
||||
case WHITE_POINT:
|
||||
entry.field_u_rational = getWhitePoint( offset );
|
||||
break;
|
||||
case PRIMARY_CHROMATICIES:
|
||||
entry.field_u_rational = getPrimaryChromaticies( offset );
|
||||
break;
|
||||
case Y_CB_CR_COEFFICIENTS:
|
||||
entry.field_u_rational = getYCbCrCoeffs( offset );
|
||||
break;
|
||||
case Y_CB_CR_POSITIONING:
|
||||
entry.field_u16 = getYCbCrPos( offset );
|
||||
break;
|
||||
case REFERENCE_BLACK_WHITE:
|
||||
entry.field_u_rational = getRefBW( offset );
|
||||
break;
|
||||
case COPYRIGHT:
|
||||
entry.field_str = getString( offset );
|
||||
break;
|
||||
case EXIF_OFFSET:
|
||||
break;
|
||||
default:
|
||||
entry.tag = INVALID_TAG;
|
||||
break;
|
||||
}
|
||||
return entry;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get tag number from raw exif data
|
||||
* This is internal function and is not exposed to client
|
||||
* @param [in] offset Offset to entry in bytes inside raw exif data
|
||||
* @return tag number
|
||||
*/
|
||||
uint16_t ExifReader::getExifTag(const size_t offset) const
|
||||
{
|
||||
return getU16( offset );
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get string information from raw exif data
|
||||
* This is internal function and is not exposed to client
|
||||
* @param [in] offset Offset to entry in bytes inside raw exif data
|
||||
* @return string value
|
||||
*/
|
||||
std::string ExifReader::getString(const size_t offset) const
|
||||
{
|
||||
size_t size = getU32( offset + 4 );
|
||||
size_t dataOffset = 8; // position of data in the field
|
||||
if( size > maxDataSize )
|
||||
{
|
||||
dataOffset = getU32( offset + 8 );
|
||||
}
|
||||
if (dataOffset > m_data.size() || dataOffset + size > m_data.size()) {
|
||||
throw ExifParsingError();
|
||||
}
|
||||
std::vector<uint8_t>::const_iterator it = m_data.begin() + dataOffset;
|
||||
std::string result( it, it + size ); //copy vector content into result
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get unsigned short data from raw exif data
|
||||
* This is internal function and is not exposed to client
|
||||
* @param [in] offset Offset to entry in bytes inside raw exif data
|
||||
* @return Unsigned short data
|
||||
*/
|
||||
uint16_t ExifReader::getU16(const size_t offset) const
|
||||
{
|
||||
if (offset + 1 >= m_data.size())
|
||||
throw ExifParsingError();
|
||||
|
||||
if( m_format == INTEL )
|
||||
{
|
||||
return m_data[offset] + ( m_data[offset + 1] << 8 );
|
||||
}
|
||||
return ( m_data[offset] << 8 ) + m_data[offset + 1];
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get unsigned 32-bit data from raw exif data
|
||||
* This is internal function and is not exposed to client
|
||||
* @param [in] offset Offset to entry in bytes inside raw exif data
|
||||
* @return Unsigned 32-bit data
|
||||
*/
|
||||
uint32_t ExifReader::getU32(const size_t offset) const
|
||||
{
|
||||
if (offset + 3 >= m_data.size())
|
||||
throw ExifParsingError();
|
||||
|
||||
if( m_format == INTEL )
|
||||
{
|
||||
return m_data[offset] +
|
||||
( m_data[offset + 1] << 8 ) +
|
||||
( m_data[offset + 2] << 16 ) +
|
||||
( m_data[offset + 3] << 24 );
|
||||
}
|
||||
|
||||
return ( m_data[offset] << 24 ) +
|
||||
( m_data[offset + 1] << 16 ) +
|
||||
( m_data[offset + 2] << 8 ) +
|
||||
m_data[offset + 3];
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get unsigned rational data from raw exif data
|
||||
* This is internal function and is not exposed to client
|
||||
* @param [in] offset Offset to entry in bytes inside raw exif data
|
||||
* @return Unsigned rational data
|
||||
*
|
||||
* "rational" means a fractional value, it contains 2 signed/unsigned long integer value,
|
||||
* and the first represents the numerator, the second, the denominator.
|
||||
*/
|
||||
u_rational_t ExifReader::getURational(const size_t offset) const
|
||||
{
|
||||
uint32_t numerator = getU32( offset );
|
||||
uint32_t denominator = getU32( offset + 4 );
|
||||
|
||||
return std::make_pair( numerator, denominator );
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get orientation information from raw exif data
|
||||
* This is internal function and is not exposed to client
|
||||
* @param [in] offset Offset to entry in bytes inside raw exif data
|
||||
* @return orientation number
|
||||
*/
|
||||
uint16_t ExifReader::getOrientation(const size_t offset) const
|
||||
{
|
||||
return getU16( offset + 8 );
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get resolution information from raw exif data
|
||||
* This is internal function and is not exposed to client
|
||||
* @param [in] offset Offset to entry in bytes inside raw exif data
|
||||
* @return resolution value
|
||||
*/
|
||||
std::vector<u_rational_t> ExifReader::getResolution(const size_t offset) const
|
||||
{
|
||||
std::vector<u_rational_t> result;
|
||||
uint32_t rationalOffset = getU32( offset + 8 );
|
||||
result.push_back( getURational( rationalOffset ) );
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get resolution unit from raw exif data
|
||||
* This is internal function and is not exposed to client
|
||||
* @param [in] offset Offset to entry in bytes inside raw exif data
|
||||
* @return resolution unit value
|
||||
*/
|
||||
uint16_t ExifReader::getResolutionUnit(const size_t offset) const
|
||||
{
|
||||
return getU16( offset + 8 );
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get White Point information from raw exif data
|
||||
* This is internal function and is not exposed to client
|
||||
* @param [in] offset Offset to entry in bytes inside raw exif data
|
||||
* @return White Point value
|
||||
*
|
||||
* If the image uses CIE Standard Illumination D65(known as international
|
||||
* standard of 'daylight'), the values are '3127/10000,3290/10000'.
|
||||
*/
|
||||
std::vector<u_rational_t> ExifReader::getWhitePoint(const size_t offset) const
|
||||
{
|
||||
std::vector<u_rational_t> result;
|
||||
uint32_t rationalOffset = getU32( offset + 8 );
|
||||
result.push_back( getURational( rationalOffset ) );
|
||||
result.push_back( getURational( rationalOffset + 8 ) );
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get Primary Chromaticies information from raw exif data
|
||||
* This is internal function and is not exposed to client
|
||||
* @param [in] offset Offset to entry in bytes inside raw exif data
|
||||
* @return vector with primary chromaticies values
|
||||
*
|
||||
*/
|
||||
std::vector<u_rational_t> ExifReader::getPrimaryChromaticies(const size_t offset) const
|
||||
{
|
||||
std::vector<u_rational_t> result;
|
||||
uint32_t rationalOffset = getU32( offset + 8 );
|
||||
for( size_t i = 0; i < primaryChromaticiesComponents; i++ )
|
||||
{
|
||||
result.push_back( getURational( rationalOffset ) );
|
||||
rationalOffset += 8;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get YCbCr Coefficients information from raw exif data
|
||||
* This is internal function and is not exposed to client
|
||||
* @param [in] offset Offset to entry in bytes inside raw exif data
|
||||
* @return vector with YCbCr coefficients values
|
||||
*
|
||||
*/
|
||||
std::vector<u_rational_t> ExifReader::getYCbCrCoeffs(const size_t offset) const
|
||||
{
|
||||
std::vector<u_rational_t> result;
|
||||
uint32_t rationalOffset = getU32( offset + 8 );
|
||||
for( size_t i = 0; i < ycbcrCoeffs; i++ )
|
||||
{
|
||||
result.push_back( getURational( rationalOffset ) );
|
||||
rationalOffset += 8;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get YCbCr Positioning information from raw exif data
|
||||
* This is internal function and is not exposed to client
|
||||
* @param [in] offset Offset to entry in bytes inside raw exif data
|
||||
* @return vector with YCbCr positioning value
|
||||
*
|
||||
*/
|
||||
uint16_t ExifReader::getYCbCrPos(const size_t offset) const
|
||||
{
|
||||
return getU16( offset + 8 );
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get Reference Black&White point information from raw exif data
|
||||
* This is internal function and is not exposed to client
|
||||
* @param [in] offset Offset to entry in bytes inside raw exif data
|
||||
* @return vector with reference BW points
|
||||
*
|
||||
* In case of YCbCr format, first 2 show black/white of Y, next 2 are Cb,
|
||||
* last 2 are Cr. In case of RGB format, first 2 show black/white of R,
|
||||
* next 2 are G, last 2 are B.
|
||||
*
|
||||
*/
|
||||
std::vector<u_rational_t> ExifReader::getRefBW(const size_t offset) const
|
||||
{
|
||||
const size_t rationalFieldSize = 8;
|
||||
std::vector<u_rational_t> result;
|
||||
uint32_t rationalOffset = getU32( offset + rationalFieldSize );
|
||||
for( size_t i = 0; i < refBWComponents; i++ )
|
||||
{
|
||||
result.push_back( getURational( rationalOffset ) );
|
||||
rationalOffset += rationalFieldSize;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
} //namespace cv
|
||||
@@ -0,0 +1,238 @@
|
||||
/*M///////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
|
||||
//
|
||||
// By downloading, copying, installing or using the software you agree to this license.
|
||||
// If you do not agree to this license, do not download, install,
|
||||
// copy or use the software.
|
||||
//
|
||||
//
|
||||
// License Agreement
|
||||
// For Open Source Computer Vision Library
|
||||
//
|
||||
// Copyright (C) 2000-2008, Intel Corporation, all rights reserved.
|
||||
// Copyright (C) 2009, Willow Garage Inc., all rights reserved.
|
||||
// Third party copyrights are property of their respective owners.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without modification,
|
||||
// are permitted provided that the following conditions are met:
|
||||
//
|
||||
// * Redistribution's of source code must retain the above copyright notice,
|
||||
// this list of conditions and the following disclaimer.
|
||||
//
|
||||
// * Redistribution's in binary form must reproduce the above copyright notice,
|
||||
// this list of conditions and the following disclaimer in the documentation
|
||||
// and/or other materials provided with the distribution.
|
||||
//
|
||||
// * The name of the copyright holders may not be used to endorse or promote products
|
||||
// derived from this software without specific prior written permission.
|
||||
//
|
||||
// This software is provided by the copyright holders and contributors "as is" and
|
||||
// any express or implied warranties, including, but not limited to, the implied
|
||||
// warranties of merchantability and fitness for a particular purpose are disclaimed.
|
||||
// In no event shall the Intel Corporation or contributors be liable for any direct,
|
||||
// indirect, incidental, special, exemplary, or consequential damages
|
||||
// (including, but not limited to, procurement of substitute goods or services;
|
||||
// loss of use, data, or profits; or business interruption) however caused
|
||||
// and on any theory of liability, whether in contract, strict liability,
|
||||
// or tort (including negligence or otherwise) arising in any way out of
|
||||
// the use of this software, even if advised of the possibility of such damage.
|
||||
//
|
||||
//M*/
|
||||
|
||||
|
||||
#ifndef _OPENCV_EXIF_HPP_
|
||||
#define _OPENCV_EXIF_HPP_
|
||||
|
||||
#include <cstdio>
|
||||
#include <map>
|
||||
#include <utility>
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <iostream>
|
||||
|
||||
namespace cv
|
||||
{
|
||||
|
||||
/**
|
||||
* @brief Base Exif tags used by IFD0 (main image)
|
||||
*/
|
||||
enum ExifTagName
|
||||
{
|
||||
IMAGE_DESCRIPTION = 0x010E, ///< Image Description: ASCII string
|
||||
MAKE = 0x010F, ///< Description of manufacturer: ASCII string
|
||||
MODEL = 0x0110, ///< Description of camera model: ASCII string
|
||||
ORIENTATION = 0x0112, ///< Orientation of the image: unsigned short
|
||||
XRESOLUTION = 0x011A, ///< Resolution of the image across X axis: unsigned rational
|
||||
YRESOLUTION = 0x011B, ///< Resolution of the image across Y axis: unsigned rational
|
||||
RESOLUTION_UNIT = 0x0128, ///< Resolution units. '1' no-unit, '2' inch, '3' centimeter
|
||||
SOFTWARE = 0x0131, ///< Shows firmware(internal software of digicam) version number
|
||||
DATE_TIME = 0x0132, ///< Date/Time of image was last modified
|
||||
WHITE_POINT = 0x013E, ///< Chromaticity of white point of the image
|
||||
PRIMARY_CHROMATICIES = 0x013F, ///< Chromaticity of the primaries of the image
|
||||
Y_CB_CR_COEFFICIENTS = 0x0211, ///< constant to translate an image from YCbCr to RGB format
|
||||
Y_CB_CR_POSITIONING = 0x0213, ///< Chroma sample point of subsampling pixel array
|
||||
REFERENCE_BLACK_WHITE = 0x0214, ///< Reference value of black point/white point
|
||||
COPYRIGHT = 0x8298, ///< Copyright information
|
||||
EXIF_OFFSET = 0x8769, ///< Offset to Exif Sub IFD
|
||||
INVALID_TAG = 0xFFFF ///< Shows that the tag was not recognized
|
||||
};
|
||||
|
||||
enum Endianness_t
|
||||
{
|
||||
INTEL = 0x49,
|
||||
MOTO = 0x4D,
|
||||
NONE = 0x00
|
||||
};
|
||||
|
||||
typedef std::pair<uint32_t, uint32_t> u_rational_t;
|
||||
|
||||
/**
|
||||
* @brief Entry which contains possible values for different exif tags
|
||||
*/
|
||||
struct ExifEntry_t
|
||||
{
|
||||
ExifEntry_t();
|
||||
|
||||
std::vector<u_rational_t> field_u_rational; ///< vector of rational fields
|
||||
std::string field_str; ///< any kind of textual information
|
||||
|
||||
float field_float; ///< Currently is not used
|
||||
double field_double; ///< Currently is not used
|
||||
|
||||
uint32_t field_u32; ///< Unsigned 32-bit value
|
||||
int32_t field_s32; ///< Signed 32-bit value
|
||||
|
||||
uint16_t tag; ///< Tag number
|
||||
|
||||
uint16_t field_u16; ///< Unsigned 16-bit value
|
||||
int16_t field_s16; ///< Signed 16-bit value
|
||||
uint8_t field_u8; ///< Unsigned 8-bit value
|
||||
int8_t field_s8; ///< Signed 8-bit value
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Picture orientation which may be taken from EXIF
|
||||
* Orientation usually matters when the picture is taken by
|
||||
* smartphone or other camera with orientation sensor support
|
||||
* Corresponds to EXIF 2.3 Specification
|
||||
*/
|
||||
enum ImageOrientation
|
||||
{
|
||||
IMAGE_ORIENTATION_TL = 1, ///< Horizontal (normal)
|
||||
IMAGE_ORIENTATION_TR = 2, ///< Mirrored horizontal
|
||||
IMAGE_ORIENTATION_BR = 3, ///< Rotate 180
|
||||
IMAGE_ORIENTATION_BL = 4, ///< Mirrored vertical
|
||||
IMAGE_ORIENTATION_LT = 5, ///< Mirrored horizontal & rotate 270 CW
|
||||
IMAGE_ORIENTATION_RT = 6, ///< Rotate 90 CW
|
||||
IMAGE_ORIENTATION_RB = 7, ///< Mirrored horizontal & rotate 90 CW
|
||||
IMAGE_ORIENTATION_LB = 8 ///< Rotate 270 CW
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Reading exif information from Jpeg file
|
||||
*
|
||||
* Usage example for getting the orientation of the image:
|
||||
*
|
||||
* @code
|
||||
* std::ifstream stream(filename,std::ios_base::in | std::ios_base::binary);
|
||||
* ExifReader reader(stream);
|
||||
* if( reader.parse() )
|
||||
* {
|
||||
* int orientation = reader.getTag(Orientation).field_u16;
|
||||
* }
|
||||
* @endcode
|
||||
*
|
||||
*/
|
||||
class ExifReader
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief ExifReader constructor. Constructs an object of exif reader
|
||||
*/
|
||||
ExifReader();
|
||||
~ExifReader();
|
||||
|
||||
bool processRawProfile(const char* profile, size_t profile_len);
|
||||
|
||||
/**
|
||||
* @brief Parse the file with exif info
|
||||
*
|
||||
* @param [in] data The data buffer to read EXIF data starting with endianness
|
||||
* @param [in] size The size of the data buffer
|
||||
*
|
||||
* @return true if successful parsing
|
||||
* false if parsing error
|
||||
*/
|
||||
|
||||
bool parseExif(unsigned char* data, const size_t size);
|
||||
|
||||
/**
|
||||
* @brief Get tag info by tag number
|
||||
*
|
||||
* @param [in] tag The tag number
|
||||
* @return ExifEntru_t structure. Caller has to know what tag it calls in order to extract proper field from the structure ExifEntry_t
|
||||
*/
|
||||
ExifEntry_t getTag( const ExifTagName tag ) const;
|
||||
|
||||
/**
|
||||
* @brief Get the whole exif buffer
|
||||
*/
|
||||
const std::vector<unsigned char>& getData() const;
|
||||
|
||||
private:
|
||||
std::vector<unsigned char> m_data;
|
||||
std::map<int, ExifEntry_t > m_exif;
|
||||
Endianness_t m_format;
|
||||
|
||||
void parseExif();
|
||||
bool checkTagMark() const;
|
||||
|
||||
size_t getNumDirEntry( const size_t offsetNumDir ) const;
|
||||
uint32_t getStartOffset() const;
|
||||
uint16_t getExifTag( const size_t offset ) const;
|
||||
uint16_t getU16( const size_t offset ) const;
|
||||
uint32_t getU32( const size_t offset ) const;
|
||||
uint16_t getOrientation( const size_t offset ) const;
|
||||
uint16_t getResolutionUnit( const size_t offset ) const;
|
||||
uint16_t getYCbCrPos( const size_t offset ) const;
|
||||
|
||||
Endianness_t getFormat() const;
|
||||
|
||||
ExifEntry_t parseExifEntry( const size_t offset );
|
||||
|
||||
u_rational_t getURational( const size_t offset ) const;
|
||||
|
||||
std::string getString( const size_t offset ) const;
|
||||
std::vector<u_rational_t> getResolution( const size_t offset ) const;
|
||||
std::vector<u_rational_t> getWhitePoint( const size_t offset ) const;
|
||||
std::vector<u_rational_t> getPrimaryChromaticies( const size_t offset ) const;
|
||||
std::vector<u_rational_t> getYCbCrCoeffs( const size_t offset ) const;
|
||||
std::vector<u_rational_t> getRefBW( const size_t offset ) const;
|
||||
|
||||
private:
|
||||
static const uint16_t tagMarkRequired = 0x2A;
|
||||
|
||||
//max size of data in tag.
|
||||
//'DDDDDDDD' contains the value of that Tag. If its size is over 4bytes,
|
||||
//'DDDDDDDD' contains the offset to data stored address.
|
||||
static const size_t maxDataSize = 4;
|
||||
|
||||
//bytes per tag field
|
||||
static const size_t tiffFieldSize = 12;
|
||||
|
||||
//number of primary chromaticies components
|
||||
static const size_t primaryChromaticiesComponents = 6;
|
||||
|
||||
//number of YCbCr coefficients in field
|
||||
static const size_t ycbcrCoeffs = 3;
|
||||
|
||||
//number of Reference Black&White components
|
||||
static const size_t refBWComponents = 6;
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
||||
#endif /* _OPENCV_EXIF_HPP_ */
|
||||
@@ -0,0 +1,437 @@
|
||||
// This file is part of OpenCV project.
|
||||
// It is subject to the license terms in the LICENSE file found in the top-level
|
||||
// directory of this distribution and at http://opencv.org/license.html
|
||||
|
||||
#include "precomp.hpp"
|
||||
|
||||
#ifdef HAVE_AVIF
|
||||
|
||||
#include <avif/avif.h>
|
||||
#include <fstream>
|
||||
#include <memory>
|
||||
|
||||
#include <opencv2/core/utils/configuration.private.hpp>
|
||||
#include <opencv2/core/utils/logger.hpp>
|
||||
#include "opencv2/imgproc.hpp"
|
||||
#include "grfmt_avif.hpp"
|
||||
|
||||
#define CV_AVIF_USE_QUALITY \
|
||||
(AVIF_VERSION > ((0 * 1000000) + (11 * 10000) + (1 * 100)))
|
||||
|
||||
#if !CV_AVIF_USE_QUALITY
|
||||
#define AVIF_QUALITY_LOSSLESS 100
|
||||
#define AVIF_QUALITY_WORST 0
|
||||
#define AVIF_QUALITY_BEST 100
|
||||
|
||||
#endif
|
||||
|
||||
namespace cv {
|
||||
namespace {
|
||||
|
||||
struct AvifImageDeleter {
|
||||
void operator()(avifImage *image) { avifImageDestroy(image); }
|
||||
};
|
||||
|
||||
using AvifImageUniquePtr = std::unique_ptr<avifImage, AvifImageDeleter>;
|
||||
|
||||
avifResult CopyToMat(const avifImage *image, int channels, bool useRGB , Mat *mat) {
|
||||
CV_Assert((int)image->height == mat->rows);
|
||||
CV_Assert((int)image->width == mat->cols);
|
||||
if (channels == 1) {
|
||||
const cv::Mat image_wrap =
|
||||
cv::Mat(image->height, image->width,
|
||||
CV_MAKE_TYPE((image->depth == 8) ? CV_8U : CV_16U, 1),
|
||||
image->yuvPlanes[0], image->yuvRowBytes[0]);
|
||||
if ((image->depth == 8 && mat->depth() == CV_8U) ||
|
||||
(image->depth > 8 && mat->depth() == CV_16U)) {
|
||||
image_wrap.copyTo(*mat);
|
||||
} else {
|
||||
CV_Assert(image->depth > 8 && mat->depth() == CV_8U);
|
||||
image_wrap.convertTo(*mat, CV_8U, 1. / (1 << (image->depth - 8)));
|
||||
}
|
||||
return AVIF_RESULT_OK;
|
||||
}
|
||||
avifRGBImage rgba;
|
||||
avifRGBImageSetDefaults(&rgba, image);
|
||||
if (channels == 3) {
|
||||
if (useRGB)
|
||||
rgba.format = AVIF_RGB_FORMAT_RGB;
|
||||
else
|
||||
rgba.format = AVIF_RGB_FORMAT_BGR;
|
||||
} else {
|
||||
CV_Assert(channels == 4);
|
||||
rgba.format = AVIF_RGB_FORMAT_BGRA;
|
||||
}
|
||||
rgba.rowBytes = mat->step[0];
|
||||
rgba.depth = (mat->depth() == CV_16U) ? image->depth : 8;
|
||||
rgba.pixels = reinterpret_cast<uint8_t *>(mat->data);
|
||||
return avifImageYUVToRGB(image, &rgba);
|
||||
}
|
||||
|
||||
AvifImageUniquePtr ConvertToAvif(const cv::Mat &img, bool lossless, int bit_depth,
|
||||
const std::vector<std::vector<uchar> >& metadata) {
|
||||
CV_Assert(img.depth() == CV_8U || img.depth() == CV_16U);
|
||||
|
||||
const int width = img.cols;
|
||||
const int height = img.rows;
|
||||
|
||||
avifImage *result;
|
||||
|
||||
if (img.channels() == 1) {
|
||||
result = avifImageCreateEmpty();
|
||||
if (result == nullptr) return nullptr;
|
||||
result->width = width;
|
||||
result->height = height;
|
||||
result->depth = bit_depth;
|
||||
result->yuvFormat = AVIF_PIXEL_FORMAT_YUV400;
|
||||
result->colorPrimaries = AVIF_COLOR_PRIMARIES_UNSPECIFIED;
|
||||
result->transferCharacteristics = AVIF_TRANSFER_CHARACTERISTICS_UNSPECIFIED;
|
||||
result->matrixCoefficients = AVIF_MATRIX_COEFFICIENTS_UNSPECIFIED;
|
||||
result->yuvRange = AVIF_RANGE_FULL;
|
||||
result->yuvPlanes[0] = img.data;
|
||||
result->yuvRowBytes[0] = img.step[0];
|
||||
result->imageOwnsYUVPlanes = AVIF_FALSE;
|
||||
} else if (lossless) {
|
||||
result =
|
||||
avifImageCreate(width, height, bit_depth, AVIF_PIXEL_FORMAT_YUV444);
|
||||
if (result == nullptr) return nullptr;
|
||||
result->colorPrimaries = AVIF_COLOR_PRIMARIES_UNSPECIFIED;
|
||||
result->transferCharacteristics = AVIF_TRANSFER_CHARACTERISTICS_UNSPECIFIED;
|
||||
result->matrixCoefficients = AVIF_MATRIX_COEFFICIENTS_IDENTITY;
|
||||
result->yuvRange = AVIF_RANGE_FULL;
|
||||
} else {
|
||||
result =
|
||||
avifImageCreate(width, height, bit_depth, AVIF_PIXEL_FORMAT_YUV420);
|
||||
if (result == nullptr) return nullptr;
|
||||
result->colorPrimaries = AVIF_COLOR_PRIMARIES_BT709;
|
||||
result->transferCharacteristics = AVIF_TRANSFER_CHARACTERISTICS_SRGB;
|
||||
result->matrixCoefficients = AVIF_MATRIX_COEFFICIENTS_BT601;
|
||||
result->yuvRange = AVIF_RANGE_FULL;
|
||||
}
|
||||
|
||||
if (!metadata.empty()) {
|
||||
const std::vector<uchar>& metadata_exif = metadata[IMAGE_METADATA_EXIF];
|
||||
const std::vector<uchar>& metadata_xmp = metadata[IMAGE_METADATA_XMP];
|
||||
const std::vector<uchar>& metadata_iccp = metadata[IMAGE_METADATA_ICCP];
|
||||
#if AVIF_VERSION_MAJOR >= 1
|
||||
if ((!metadata_exif.empty() &&
|
||||
avifImageSetMetadataExif(result, (const uint8_t *)metadata_exif.data(),
|
||||
metadata_exif.size()) != AVIF_RESULT_OK) ||
|
||||
(!metadata_xmp.empty() &&
|
||||
avifImageSetMetadataXMP(result, (const uint8_t *)metadata_xmp.data(),
|
||||
metadata_xmp.size()) != AVIF_RESULT_OK) ||
|
||||
(!metadata_iccp.empty() &&
|
||||
avifImageSetProfileICC(result, (const uint8_t *)metadata_iccp.data(),
|
||||
metadata_iccp.size()) != AVIF_RESULT_OK)) {
|
||||
avifImageDestroy(result);
|
||||
return nullptr;
|
||||
}
|
||||
#else
|
||||
if (!metadata_exif.empty())
|
||||
avifImageSetMetadataExif(result, (const uint8_t*)metadata_exif.data(), metadata_exif.size());
|
||||
if (!metadata_xmp.empty())
|
||||
avifImageSetMetadataXMP(result, (const uint8_t*)metadata_xmp.data(), metadata_xmp.size());
|
||||
if (!metadata_iccp.empty())
|
||||
avifImageSetProfileICC(result, (const uint8_t*)metadata_iccp.data(), metadata_iccp.size());
|
||||
#endif
|
||||
}
|
||||
|
||||
if (img.channels() > 1) {
|
||||
avifRGBImage rgba;
|
||||
avifRGBImageSetDefaults(&rgba, result);
|
||||
if (img.channels() == 3) {
|
||||
rgba.format = AVIF_RGB_FORMAT_BGR;
|
||||
} else {
|
||||
CV_Assert(img.channels() == 4);
|
||||
rgba.format = AVIF_RGB_FORMAT_BGRA;
|
||||
}
|
||||
rgba.rowBytes = (uint32_t)img.step[0];
|
||||
rgba.depth = bit_depth;
|
||||
rgba.pixels =
|
||||
const_cast<uint8_t *>(reinterpret_cast<const uint8_t *>(img.data));
|
||||
|
||||
if (avifImageRGBToYUV(result, &rgba) != AVIF_RESULT_OK) {
|
||||
avifImageDestroy(result);
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
return AvifImageUniquePtr(result);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// 64Mb limit to avoid memory saturation.
|
||||
static const size_t kParamMaxFileSize = utils::getConfigurationParameterSizeT(
|
||||
"OPENCV_IMGCODECS_AVIF_MAX_FILE_SIZE", 64 * 1024 * 1024);
|
||||
|
||||
static constexpr size_t kAvifSignatureSize = 500;
|
||||
|
||||
AvifDecoder::AvifDecoder() {
|
||||
m_buf_supported = true;
|
||||
channels_ = 0;
|
||||
decoder_ = nullptr;
|
||||
}
|
||||
|
||||
AvifDecoder::~AvifDecoder() {
|
||||
if (decoder_ != nullptr) avifDecoderDestroy(decoder_);
|
||||
}
|
||||
|
||||
size_t AvifDecoder::signatureLength() const { return kAvifSignatureSize; }
|
||||
|
||||
#define OPENCV_AVIF_CHECK_STATUS(X, ENCDEC) \
|
||||
{ \
|
||||
const avifResult status = (X); \
|
||||
if (status != AVIF_RESULT_OK) { \
|
||||
const std::string error(ENCDEC->diag.error); \
|
||||
CV_Error(Error::StsParseError, \
|
||||
error + " " + avifResultToString(status)); \
|
||||
return false; \
|
||||
} \
|
||||
}
|
||||
|
||||
bool AvifDecoder::checkSignature(const String &signature) const {
|
||||
std::unique_ptr<avifDecoder, decltype(&avifDecoderDestroy)> decoder(
|
||||
avifDecoderCreate(), avifDecoderDestroy);
|
||||
if (!decoder) return false;
|
||||
decoder->strictFlags = AVIF_STRICT_DISABLED;
|
||||
OPENCV_AVIF_CHECK_STATUS(
|
||||
avifDecoderSetIOMemory(
|
||||
decoder.get(), reinterpret_cast<const uint8_t *>(signature.c_str()),
|
||||
signature.size()),
|
||||
decoder);
|
||||
decoder->io->sizeHint = 1e9;
|
||||
const avifResult status = avifDecoderParse(decoder.get());
|
||||
return (status == AVIF_RESULT_OK || status == AVIF_RESULT_TRUNCATED_DATA);
|
||||
}
|
||||
|
||||
ImageDecoder AvifDecoder::newDecoder() const { return makePtr<AvifDecoder>(); }
|
||||
|
||||
bool AvifDecoder::readHeader() {
|
||||
if (decoder_)
|
||||
return true;
|
||||
|
||||
decoder_ = avifDecoderCreate();
|
||||
if (!decoder_) {
|
||||
CV_Error(Error::StsNoMem, "Failed to create AVIF decoder");
|
||||
return false;
|
||||
}
|
||||
decoder_->strictFlags = AVIF_STRICT_DISABLED;
|
||||
if (!m_buf.empty()) {
|
||||
CV_Assert(m_buf.type() == CV_8UC1);
|
||||
CV_Assert(m_buf.rows == 1);
|
||||
}
|
||||
|
||||
OPENCV_AVIF_CHECK_STATUS(
|
||||
m_buf.empty()
|
||||
? avifDecoderSetIOFile(decoder_, m_filename.c_str())
|
||||
: avifDecoderSetIOMemory(
|
||||
decoder_, reinterpret_cast<const uint8_t *>(m_buf.data),
|
||||
m_buf.total()),
|
||||
decoder_);
|
||||
OPENCV_AVIF_CHECK_STATUS(avifDecoderParse(decoder_), decoder_);
|
||||
|
||||
if (!decoder_->image) {
|
||||
CV_Error(Error::StsParseError, "AVIF image is null after parsing");
|
||||
return false;
|
||||
}
|
||||
|
||||
m_width = decoder_->image->width;
|
||||
m_height = decoder_->image->height;
|
||||
m_frame_count = decoder_->imageCount;
|
||||
channels_ = (decoder_->image->yuvFormat == AVIF_PIXEL_FORMAT_YUV400) ? 1 : 3;
|
||||
if (decoder_->alphaPresent) ++channels_;
|
||||
bit_depth_ = decoder_->image->depth;
|
||||
CV_Assert(bit_depth_ == 8 || bit_depth_ == 10 || bit_depth_ == 12);
|
||||
m_type = CV_MAKETYPE(bit_depth_ == 8 ? CV_8U : CV_16U, channels_);
|
||||
is_first_image_ = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AvifDecoder::readData(Mat &img) {
|
||||
CV_CheckGE(m_width, 0, "");
|
||||
CV_CheckGE(m_height, 0, "");
|
||||
|
||||
CV_CheckEQ(img.cols, m_width, "");
|
||||
CV_CheckEQ(img.rows, m_height, "");
|
||||
CV_CheckType(
|
||||
img.type(),
|
||||
(img.channels() == 1 || img.channels() == 3 || img.channels() == 4) &&
|
||||
(img.depth() == CV_8U || img.depth() == CV_16U),
|
||||
"AVIF only supports 1, 3, 4 channels and CV_8U and CV_16U");
|
||||
|
||||
Mat read_img;
|
||||
if (img.channels() == channels_) {
|
||||
read_img = img;
|
||||
} else {
|
||||
// Use the asked depth but keep the number of channels. OpenCV and not
|
||||
// libavif will do the color conversion.
|
||||
read_img.create(m_height, m_width, CV_MAKE_TYPE(img.depth(), channels_));
|
||||
}
|
||||
|
||||
if (is_first_image_) {
|
||||
if (!nextPage()) return false;
|
||||
is_first_image_ = false;
|
||||
}
|
||||
|
||||
if (CopyToMat(decoder_->image, channels_, m_use_rgb, &read_img) != AVIF_RESULT_OK) {
|
||||
CV_Error(Error::StsInternal, "Cannot convert from AVIF to Mat");
|
||||
return false;
|
||||
}
|
||||
|
||||
m_animation.durations.push_back(decoder_->imageTiming.duration * 1000);
|
||||
|
||||
if (decoder_->image->exif.size > 0) {
|
||||
m_exif.parseExif(decoder_->image->exif.data, decoder_->image->exif.size);
|
||||
}
|
||||
|
||||
if (img.channels() == channels_) {
|
||||
// We already wrote to the right buffer.
|
||||
} else {
|
||||
if (channels_ == 1 && img.channels() == 3) {
|
||||
cvtColor(read_img, img, COLOR_GRAY2BGR);
|
||||
} else if (channels_ == 1 && img.channels() == 4) {
|
||||
cvtColor(read_img, img, COLOR_GRAY2BGRA);
|
||||
} else if (channels_ == 3 && img.channels() == 1) {
|
||||
cvtColor(read_img, img, COLOR_BGR2GRAY);
|
||||
} else if (channels_ == 3 && img.channels() == 4) {
|
||||
cvtColor(read_img, img, COLOR_BGR2BGRA);
|
||||
} else if (channels_ == 4 && img.channels() == 1) {
|
||||
cvtColor(read_img, img, COLOR_BGRA2GRAY);
|
||||
} else if (channels_ == 4 && img.channels() == 3) {
|
||||
cvtColor(read_img, img, COLOR_BGRA2BGR);
|
||||
} else {
|
||||
CV_Error(Error::StsInternal, "");
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AvifDecoder::nextPage() {
|
||||
const avifResult status = avifDecoderNextImage(decoder_);
|
||||
if (status == AVIF_RESULT_NO_IMAGES_REMAINING) return false;
|
||||
if (status != AVIF_RESULT_OK) {
|
||||
const std::string error(decoder_->diag.error);
|
||||
CV_Error(Error::StsParseError, error + " " + avifResultToString(status));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
AvifEncoder::AvifEncoder() {
|
||||
m_description = "AVIF files (*.avif)";
|
||||
m_buf_supported = true;
|
||||
m_support_metadata.assign((size_t)IMAGE_METADATA_MAX + 1, false);
|
||||
m_support_metadata[(size_t)IMAGE_METADATA_EXIF] = true;
|
||||
m_support_metadata[(size_t)IMAGE_METADATA_XMP] = true;
|
||||
m_support_metadata[(size_t)IMAGE_METADATA_ICCP] = true;
|
||||
encoder_ = avifEncoderCreate();
|
||||
m_supported_encode_key = { IMWRITE_AVIF_QUALITY, IMWRITE_AVIF_DEPTH, IMWRITE_AVIF_SPEED };
|
||||
}
|
||||
|
||||
AvifEncoder::~AvifEncoder() {
|
||||
if (encoder_) avifEncoderDestroy(encoder_);
|
||||
}
|
||||
|
||||
bool AvifEncoder::isFormatSupported(int depth) const {
|
||||
return (depth == CV_8U || depth == CV_16U);
|
||||
}
|
||||
|
||||
bool AvifEncoder::writeanimation(const Animation& animation,
|
||||
const std::vector<int> ¶ms) {
|
||||
int bit_depth = 8;
|
||||
int speed = AVIF_SPEED_FASTEST;
|
||||
for (size_t i = 0; i < params.size(); i += 2) {
|
||||
const int value = params[i + 1];
|
||||
if (params[i] == IMWRITE_AVIF_QUALITY) {
|
||||
const int quality = std::min(std::max(value, AVIF_QUALITY_WORST),
|
||||
AVIF_QUALITY_BEST);
|
||||
if (value != quality) {
|
||||
CV_LOG_WARNING(nullptr, cv::format("The value(%d) for IMWRITE_AVIF_QUALITY must be between 0 to 100. It is fallbacked to %d", value, quality));
|
||||
}
|
||||
#if CV_AVIF_USE_QUALITY
|
||||
encoder_->quality = quality;
|
||||
#else
|
||||
encoder_->minQuantizer = encoder_->maxQuantizer =
|
||||
(AVIF_QUANTIZER_BEST_QUALITY - AVIF_QUANTIZER_WORST_QUALITY) *
|
||||
quality / (AVIF_QUALITY_BEST - AVIF_QUALITY_WORST) +
|
||||
AVIF_QUANTIZER_WORST_QUALITY;
|
||||
#endif
|
||||
} else if (params[i] == IMWRITE_AVIF_DEPTH) {
|
||||
bit_depth = value;
|
||||
if ((bit_depth != 8) && (bit_depth !=10) && (bit_depth !=12))
|
||||
{
|
||||
bit_depth = 8;
|
||||
CV_LOG_WARNING(nullptr, cv::format("The value(%d) for IMWRITE_AVIF_DEPTH must be 8, 10 or 12. It is fallbacked to %d", value, bit_depth));
|
||||
}
|
||||
} else if (params[i] == IMWRITE_AVIF_SPEED) {
|
||||
speed = std::min(std::max(value,0),10);
|
||||
if (value != speed) {
|
||||
CV_LOG_WARNING(nullptr, cv::format("The value(%d) for IMWRITE_AVIF_SPEED must be between 0 to 10. It is fallbacked to %d", value, speed));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
avifRWData output_ori = AVIF_DATA_EMPTY;
|
||||
std::unique_ptr<avifRWData, decltype(&avifRWDataFree)> output(&output_ori,
|
||||
avifRWDataFree);
|
||||
#if CV_AVIF_USE_QUALITY
|
||||
const bool do_lossless = (encoder_->quality == AVIF_QUALITY_LOSSLESS);
|
||||
#else
|
||||
const bool do_lossless =
|
||||
(encoder_->minQuantizer == AVIF_QUANTIZER_BEST_QUALITY &&
|
||||
encoder_->maxQuantizer == AVIF_QUANTIZER_BEST_QUALITY);
|
||||
#endif
|
||||
encoder_->speed = speed;
|
||||
|
||||
const avifAddImageFlags flag = (animation.frames.size() == 1)
|
||||
? AVIF_ADD_IMAGE_FLAG_SINGLE
|
||||
: AVIF_ADD_IMAGE_FLAG_NONE;
|
||||
std::vector<AvifImageUniquePtr> images;
|
||||
for (const cv::Mat &img : animation.frames) {
|
||||
CV_CheckType(
|
||||
img.type(),
|
||||
(bit_depth == 8 && img.depth() == CV_8U) ||
|
||||
((bit_depth == 10 || bit_depth == 12) && img.depth() == CV_16U),
|
||||
"AVIF only supports bit depth of 8 with CV_8U input or "
|
||||
"bit depth of 10 or 12 with CV_16U input");
|
||||
|
||||
CV_Check(img.channels(),
|
||||
img.channels() == 1 || img.channels() == 3 || img.channels() == 4,
|
||||
"AVIF only supports 1, 3, 4 channels");
|
||||
|
||||
auto avifImg = ConvertToAvif(img, do_lossless, bit_depth, m_metadata);
|
||||
if (!avifImg) {
|
||||
CV_Error(Error::StsError, "Failed to convert Mat to AVIF image");
|
||||
return false;
|
||||
}
|
||||
images.emplace_back(std::move(avifImg));
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < images.size(); i++)
|
||||
{
|
||||
OPENCV_AVIF_CHECK_STATUS(
|
||||
avifEncoderAddImage(encoder_, images[i].get(), animation.durations[i], flag),
|
||||
encoder_);
|
||||
}
|
||||
|
||||
encoder_->timescale = 1000;
|
||||
OPENCV_AVIF_CHECK_STATUS(avifEncoderFinish(encoder_, output.get()), encoder_);
|
||||
|
||||
if (m_buf) {
|
||||
m_buf->resize(output->size);
|
||||
std::memcpy(m_buf->data(), output->data, output->size);
|
||||
} else {
|
||||
std::ofstream(m_filename, std::ofstream::binary)
|
||||
.write(reinterpret_cast<char *>(output->data), output->size);
|
||||
}
|
||||
|
||||
return (output->size > 0);
|
||||
}
|
||||
|
||||
ImageEncoder AvifEncoder::newEncoder() const { return makePtr<AvifEncoder>(); }
|
||||
|
||||
} // namespace cv
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,56 @@
|
||||
// This file is part of OpenCV project.
|
||||
// It is subject to the license terms in the LICENSE file found in the top-level
|
||||
// directory of this distribution and at http://opencv.org/license.html
|
||||
|
||||
#ifndef _GRFMT_AVIF_H_
|
||||
#define _GRFMT_AVIF_H_
|
||||
|
||||
#include "grfmt_base.hpp"
|
||||
|
||||
#ifdef HAVE_AVIF
|
||||
|
||||
struct avifDecoder;
|
||||
struct avifEncoder;
|
||||
struct avifRWData;
|
||||
|
||||
namespace cv {
|
||||
|
||||
class AvifDecoder CV_FINAL : public BaseImageDecoder {
|
||||
public:
|
||||
AvifDecoder();
|
||||
~AvifDecoder();
|
||||
|
||||
bool readHeader() CV_OVERRIDE;
|
||||
bool readData(Mat& img) CV_OVERRIDE;
|
||||
bool nextPage() CV_OVERRIDE;
|
||||
|
||||
size_t signatureLength() const CV_OVERRIDE;
|
||||
bool checkSignature(const String& signature) const CV_OVERRIDE;
|
||||
ImageDecoder newDecoder() const CV_OVERRIDE;
|
||||
|
||||
protected:
|
||||
int channels_;
|
||||
int bit_depth_;
|
||||
avifDecoder* decoder_;
|
||||
bool is_first_image_;
|
||||
};
|
||||
|
||||
class AvifEncoder CV_FINAL : public BaseImageEncoder {
|
||||
public:
|
||||
AvifEncoder();
|
||||
~AvifEncoder() CV_OVERRIDE;
|
||||
|
||||
bool isFormatSupported(int depth) const CV_OVERRIDE;
|
||||
bool writeanimation(const Animation& animation, const std::vector<int>& params) CV_OVERRIDE;
|
||||
|
||||
ImageEncoder newEncoder() const CV_OVERRIDE;
|
||||
|
||||
private:
|
||||
avifEncoder* encoder_;
|
||||
};
|
||||
|
||||
} // namespace cv
|
||||
|
||||
#endif
|
||||
|
||||
#endif /*_GRFMT_AVIF_H_*/
|
||||
@@ -0,0 +1,248 @@
|
||||
/*M///////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
|
||||
//
|
||||
// By downloading, copying, installing or using the software you agree to this license.
|
||||
// If you do not agree to this license, do not download, install,
|
||||
// copy or use the software.
|
||||
//
|
||||
//
|
||||
// Intel License Agreement
|
||||
// For Open Source Computer Vision Library
|
||||
//
|
||||
// Copyright (C) 2000, Intel Corporation, all rights reserved.
|
||||
// Third party copyrights are property of their respective owners.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without modification,
|
||||
// are permitted provided that the following conditions are met:
|
||||
//
|
||||
// * Redistribution's of source code must retain the above copyright notice,
|
||||
// this list of conditions and the following disclaimer.
|
||||
//
|
||||
// * Redistribution's in binary form must reproduce the above copyright notice,
|
||||
// this list of conditions and the following disclaimer in the documentation
|
||||
// and/or other materials provided with the distribution.
|
||||
//
|
||||
// * The name of Intel Corporation may not be used to endorse or promote products
|
||||
// derived from this software without specific prior written permission.
|
||||
//
|
||||
// This software is provided by the copyright holders and contributors "as is" and
|
||||
// any express or implied warranties, including, but not limited to, the implied
|
||||
// warranties of merchantability and fitness for a particular purpose are disclaimed.
|
||||
// In no event shall the Intel Corporation or contributors be liable for any direct,
|
||||
// indirect, incidental, special, exemplary, or consequential damages
|
||||
// (including, but not limited to, procurement of substitute goods or services;
|
||||
// loss of use, data, or profits; or business interruption) however caused
|
||||
// and on any theory of liability, whether in contract, strict liability,
|
||||
// or tort (including negligence or otherwise) arising in any way out of
|
||||
// the use of this software, even if advised of the possibility of such damage.
|
||||
//
|
||||
//M*/
|
||||
|
||||
#include "precomp.hpp"
|
||||
|
||||
#include "grfmt_base.hpp"
|
||||
#include "bitstrm.hpp"
|
||||
#include <opencv2/core/utils/logger.hpp>
|
||||
|
||||
namespace cv
|
||||
{
|
||||
|
||||
BaseImageDecoder::BaseImageDecoder()
|
||||
{
|
||||
m_width = m_height = 0;
|
||||
m_type = -1;
|
||||
m_buf_supported = false;
|
||||
m_scale_denom = 1;
|
||||
m_use_rgb = false;
|
||||
m_frame_count = 1;
|
||||
m_read_options = 0;
|
||||
m_metadata.resize(IMAGE_METADATA_MAX + 1);
|
||||
}
|
||||
|
||||
bool BaseImageDecoder::haveMetadata(ImageMetadataType type) const
|
||||
{
|
||||
if (type == IMAGE_METADATA_EXIF)
|
||||
return !m_exif.getData().empty();
|
||||
return false;
|
||||
}
|
||||
|
||||
Mat BaseImageDecoder::getMetadata(ImageMetadataType type) const
|
||||
{
|
||||
auto makeMat = [](const std::vector<unsigned char>& data) -> Mat {
|
||||
return data.empty() ? Mat() : Mat(1, (int)data.size(), CV_8U, (void*)data.data());
|
||||
};
|
||||
|
||||
switch (type) {
|
||||
case IMAGE_METADATA_EXIF:
|
||||
return makeMat(m_exif.getData());
|
||||
|
||||
case IMAGE_METADATA_XMP:
|
||||
case IMAGE_METADATA_ICCP:
|
||||
case IMAGE_METADATA_CICP:
|
||||
return makeMat(m_metadata[type]);
|
||||
|
||||
default:
|
||||
CV_LOG_WARNING(NULL, "Unknown metadata type requested: " << static_cast<int>(type));
|
||||
break;
|
||||
}
|
||||
return Mat();
|
||||
}
|
||||
|
||||
ExifEntry_t BaseImageDecoder::getExifTag(const ExifTagName tag) const
|
||||
{
|
||||
return m_exif.getTag(tag);
|
||||
}
|
||||
|
||||
bool BaseImageDecoder::setSource( const String& filename )
|
||||
{
|
||||
m_filename = filename;
|
||||
m_buf.release();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BaseImageDecoder::setSource( const Mat& buf )
|
||||
{
|
||||
if( !m_buf_supported )
|
||||
return false;
|
||||
m_filename = String();
|
||||
m_buf = buf;
|
||||
return true;
|
||||
}
|
||||
|
||||
size_t BaseImageDecoder::signatureLength() const
|
||||
{
|
||||
return m_signature.size();
|
||||
}
|
||||
|
||||
bool BaseImageDecoder::checkSignature( const String& signature ) const
|
||||
{
|
||||
size_t len = signatureLength();
|
||||
return signature.size() >= len && memcmp( signature.c_str(), m_signature.c_str(), len ) == 0;
|
||||
}
|
||||
|
||||
int BaseImageDecoder::setScale( const int& scale_denom )
|
||||
{
|
||||
int temp = m_scale_denom;
|
||||
m_scale_denom = scale_denom;
|
||||
return temp;
|
||||
}
|
||||
|
||||
int BaseImageDecoder::setReadOptions(int read_options)
|
||||
{
|
||||
int temp = m_read_options;
|
||||
m_read_options = read_options;
|
||||
return temp;
|
||||
}
|
||||
|
||||
void BaseImageDecoder::setRGB(bool useRGB)
|
||||
{
|
||||
m_use_rgb = useRGB;
|
||||
}
|
||||
|
||||
ImageDecoder BaseImageDecoder::newDecoder() const
|
||||
{
|
||||
return ImageDecoder();
|
||||
}
|
||||
|
||||
BaseImageEncoder::BaseImageEncoder()
|
||||
{
|
||||
m_buf = 0;
|
||||
m_buf_supported = false;
|
||||
}
|
||||
|
||||
bool BaseImageEncoder::isFormatSupported( int depth ) const
|
||||
{
|
||||
return depth == CV_8U;
|
||||
}
|
||||
|
||||
bool BaseImageEncoder::isValidEncodeKey(const int key) const
|
||||
{
|
||||
auto first = m_supported_encode_key.begin();
|
||||
auto last = m_supported_encode_key.end();
|
||||
|
||||
return (std::find(first, last, key) != last);
|
||||
}
|
||||
|
||||
String BaseImageEncoder::getDescription() const
|
||||
{
|
||||
return m_description;
|
||||
}
|
||||
|
||||
bool BaseImageEncoder::setDestination( const String& filename )
|
||||
{
|
||||
m_filename = filename;
|
||||
m_buf = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BaseImageEncoder::setDestination( std::vector<uchar>& buf )
|
||||
{
|
||||
if( !m_buf_supported )
|
||||
return false;
|
||||
m_buf = &buf;
|
||||
m_buf->clear();
|
||||
m_filename = String();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BaseImageEncoder::addMetadata(ImageMetadataType type, const Mat& metadata)
|
||||
{
|
||||
CV_Assert_N(type >= IMAGE_METADATA_EXIF, type <= IMAGE_METADATA_MAX);
|
||||
if (metadata.empty())
|
||||
return true;
|
||||
size_t itype = (size_t)type;
|
||||
if (itype >= m_support_metadata.size() || !m_support_metadata[itype])
|
||||
return false;
|
||||
if (m_metadata.empty())
|
||||
m_metadata.resize((size_t)IMAGE_METADATA_MAX+1);
|
||||
CV_Assert(metadata.elemSize() == 1);
|
||||
CV_Assert(metadata.isContinuous());
|
||||
const unsigned char* data = metadata.ptr<unsigned char>();
|
||||
m_metadata[itype].assign(data, data + metadata.total());
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BaseImageEncoder::write(const Mat &img, const std::vector<int> ¶ms) {
|
||||
std::vector<Mat> img_vec(1, img);
|
||||
return writemulti(img_vec, params);
|
||||
}
|
||||
|
||||
bool BaseImageEncoder::writemulti(const std::vector<Mat>& img_vec, const std::vector<int>& params)
|
||||
{
|
||||
if(img_vec.size() > 1)
|
||||
CV_LOG_INFO(NULL, "Multi page image will be written as animation with 1 second frame duration.");
|
||||
|
||||
Animation animation;
|
||||
animation.frames = img_vec;
|
||||
|
||||
for (size_t i = 0; i < animation.frames.size(); i++)
|
||||
{
|
||||
animation.durations.push_back(1000);
|
||||
}
|
||||
return writeanimation(animation, params);
|
||||
}
|
||||
|
||||
bool BaseImageEncoder::writeanimation(const Animation&, const std::vector<int>& )
|
||||
{
|
||||
CV_LOG_WARNING(NULL, "No Animation encoder for specified file extension");
|
||||
return false;
|
||||
}
|
||||
|
||||
ImageEncoder BaseImageEncoder::newEncoder() const
|
||||
{
|
||||
return ImageEncoder();
|
||||
}
|
||||
|
||||
void BaseImageEncoder::throwOnError() const
|
||||
{
|
||||
if(!m_last_error.empty())
|
||||
{
|
||||
String msg = "Raw image encoder error: " + m_last_error;
|
||||
CV_Error( Error::BadImageSize, msg.c_str() );
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* End of file. */
|
||||
@@ -0,0 +1,305 @@
|
||||
// This file is part of OpenCV project.
|
||||
// It is subject to the license terms in the LICENSE file found in the top-level
|
||||
// directory of this distribution and at http://opencv.org/license.html
|
||||
|
||||
#ifndef _GRFMT_BASE_H_
|
||||
#define _GRFMT_BASE_H_
|
||||
|
||||
#include "utils.hpp"
|
||||
#include "bitstrm.hpp"
|
||||
#include "exif.hpp"
|
||||
|
||||
namespace cv
|
||||
{
|
||||
|
||||
class BaseImageDecoder;
|
||||
class BaseImageEncoder;
|
||||
typedef Ptr<BaseImageEncoder> ImageEncoder;
|
||||
typedef Ptr<BaseImageDecoder> ImageDecoder;
|
||||
|
||||
/**
|
||||
* @brief Base class for image decoders.
|
||||
*
|
||||
* The BaseImageDecoder class provides an abstract interface for decoding various image formats.
|
||||
* It defines common functionality like setting the image source, reading image headers,
|
||||
* and handling EXIF metadata. Derived classes must implement methods for reading image headers
|
||||
* and image data to handle format-specific decoding logic.
|
||||
*/
|
||||
|
||||
class BaseImageDecoder {
|
||||
public:
|
||||
/**
|
||||
* @brief Constructor for BaseImageDecoder.
|
||||
* Initializes the object and sets default values for member variables.
|
||||
*/
|
||||
BaseImageDecoder();
|
||||
|
||||
/**
|
||||
* @brief Virtual destructor for BaseImageDecoder.
|
||||
* Ensures proper cleanup of derived classes when deleted via a pointer to BaseImageDecoder.
|
||||
*/
|
||||
virtual ~BaseImageDecoder() {}
|
||||
|
||||
/**
|
||||
* @brief Get the width of the image.
|
||||
* @return The width of the image (in pixels).
|
||||
*/
|
||||
int width() const { return m_width; }
|
||||
|
||||
/**
|
||||
* @brief Get the height of the image.
|
||||
* @return The height of the image (in pixels).
|
||||
*/
|
||||
int height() const { return m_height; }
|
||||
|
||||
/**
|
||||
* @brief Get the number of frames in the image or animation.
|
||||
* @return The number of frames in the image.
|
||||
*/
|
||||
size_t getFrameCount() const { return m_frame_count; }
|
||||
|
||||
/**
|
||||
* @brief Set the internal m_frame_count variable to 1.
|
||||
*/
|
||||
void resetFrameCount() { m_frame_count = 1; }
|
||||
|
||||
/**
|
||||
* @brief Get the type of the image (e.g., color format, depth).
|
||||
* @return The type of the image.
|
||||
*/
|
||||
virtual int type() const { return m_type; }
|
||||
|
||||
/**
|
||||
* @brief Checks whether file contains metadata of the certain type.
|
||||
* @param type The type of metadata to look for
|
||||
*/
|
||||
virtual bool haveMetadata(ImageMetadataType type) const;
|
||||
|
||||
/**
|
||||
* @brief Retrieves metadata (if any) of the certain kind.
|
||||
* If there is no such metadata, the method returns empty array.
|
||||
*
|
||||
* @param type The type of metadata to look for
|
||||
*/
|
||||
virtual Mat getMetadata(ImageMetadataType type) const;
|
||||
|
||||
/**
|
||||
* @brief Fetch a specific EXIF tag from the image's metadata.
|
||||
* @param tag The EXIF tag to retrieve.
|
||||
* @return The EXIF entry corresponding to the tag.
|
||||
*/
|
||||
ExifEntry_t getExifTag(const ExifTagName tag) const;
|
||||
|
||||
/**
|
||||
* @brief Set the image source from a file.
|
||||
* @param filename The name of the file to load the image from.
|
||||
* @return true if the source was successfully set, false otherwise.
|
||||
*/
|
||||
virtual bool setSource(const String& filename);
|
||||
|
||||
/**
|
||||
* @brief Set the image source from a memory buffer.
|
||||
* @param buf The buffer containing the image data.
|
||||
* @return true if the source was successfully set, false otherwise.
|
||||
*/
|
||||
virtual bool setSource(const Mat& buf);
|
||||
|
||||
/**
|
||||
* @brief Set the scale factor for the image.
|
||||
* @param scale_denom The denominator of the scale factor (image is scaled down by 1/scale_denom).
|
||||
* @return The scale factor that was set.
|
||||
*/
|
||||
int setScale(const int& scale_denom);
|
||||
|
||||
/**
|
||||
* @brief Set read options for image decoding.
|
||||
*
|
||||
* This function sets internal flags that control various read-time behaviors
|
||||
* such as metadata extraction (e.g., XMP, ICC profiles, textual data)
|
||||
* during image decoding. The flags can be combined using bitwise OR to enable
|
||||
* multiple options simultaneously.
|
||||
*
|
||||
* @param options Bitwise OR of read option flags to enable.
|
||||
*
|
||||
* @return The previous value of the read options flags.
|
||||
*
|
||||
* @note Setting this has no effect unless the image format and decoder support
|
||||
* the selected options. Unknown flags will be ignored.
|
||||
*/
|
||||
int setReadOptions(int read_options);
|
||||
|
||||
/**
|
||||
* @brief Read the image header to extract basic properties (width, height, type).
|
||||
* This is a pure virtual function that must be implemented by derived classes.
|
||||
* @return true if the header was successfully read, false otherwise.
|
||||
*/
|
||||
virtual bool readHeader() = 0;
|
||||
|
||||
/**
|
||||
* @brief Read the image data into a Mat object.
|
||||
* This is a pure virtual function that must be implemented by derived classes.
|
||||
* @param img The Mat object where the image data will be stored.
|
||||
* @return true if the data was successfully read, false otherwise.
|
||||
*/
|
||||
virtual bool readData(Mat& img) = 0;
|
||||
|
||||
/**
|
||||
* @brief Set whether to decode the image in RGB order instead of the default BGR.
|
||||
* @param useRGB If true, the image will be decoded in RGB order.
|
||||
*/
|
||||
virtual void setRGB(bool useRGB);
|
||||
|
||||
/**
|
||||
* @brief Advance to the next page or frame of the image, if applicable.
|
||||
* The default implementation does nothing and returns false.
|
||||
* @return true if there is another page/frame, false otherwise.
|
||||
*/
|
||||
virtual bool nextPage() { return false; }
|
||||
|
||||
/**
|
||||
* @brief Get the length of the format signature used to identify the image format.
|
||||
* @return The length of the signature.
|
||||
*/
|
||||
virtual size_t signatureLength() const;
|
||||
|
||||
/**
|
||||
* @brief Check if the provided signature matches the expected format signature.
|
||||
* @param signature The signature to check.
|
||||
* @return true if the signature matches, false otherwise.
|
||||
*/
|
||||
virtual bool checkSignature(const String& signature) const;
|
||||
|
||||
const Animation& animation() const { return m_animation; };
|
||||
|
||||
/**
|
||||
* @brief Create and return a new instance of the derived image decoder.
|
||||
* @return A new ImageDecoder object.
|
||||
*/
|
||||
virtual ImageDecoder newDecoder() const;
|
||||
|
||||
protected:
|
||||
int m_width; ///< Width of the image (set by readHeader).
|
||||
int m_height; ///< Height of the image (set by readHeader).
|
||||
int m_type; ///< Image type (e.g., color depth, channel order).
|
||||
int m_scale_denom; ///< Scale factor denominator for resizing the image.
|
||||
String m_filename; ///< Name of the file that is being decoded.
|
||||
String m_signature; ///< Signature for identifying the image format.
|
||||
Mat m_buf; ///< Buffer holding the image data when loaded from memory.
|
||||
bool m_buf_supported; ///< Flag indicating whether buffer-based loading is supported.
|
||||
bool m_use_rgb; ///< Flag indicating whether to decode the image in RGB order.
|
||||
ExifReader m_exif; ///< Object for reading EXIF metadata from the image.
|
||||
size_t m_frame_count; ///< Number of frames in the image (for animations and multi-page images).
|
||||
Animation m_animation;
|
||||
int m_read_options;
|
||||
std::vector<std::vector<unsigned char> > m_metadata;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @brief Base class for image encoders.
|
||||
*
|
||||
* The BaseImageEncoder class provides an abstract interface for encoding images in various formats.
|
||||
* It defines common functionality like setting the destination (file or memory buffer), checking if
|
||||
* the format supports a specific image depth, and writing image data. Derived classes must implement
|
||||
* methods like writing the image data to handle format-specific encoding logic.
|
||||
*/
|
||||
class BaseImageEncoder {
|
||||
public:
|
||||
/**
|
||||
* @brief Constructor for BaseImageEncoder.
|
||||
* Initializes the object and sets default values for member variables.
|
||||
*/
|
||||
BaseImageEncoder();
|
||||
|
||||
/**
|
||||
* @brief Virtual destructor for BaseImageEncoder.
|
||||
* Ensures proper cleanup of derived classes when deleted via a pointer to BaseImageEncoder.
|
||||
*/
|
||||
virtual ~BaseImageEncoder() {}
|
||||
|
||||
/**
|
||||
* @brief Checks if the image format supports a specific image depth.
|
||||
* @param depth The depth (bit depth) of the image.
|
||||
* @return true if the format supports the specified depth, false otherwise.
|
||||
*/
|
||||
virtual bool isFormatSupported(int depth) const;
|
||||
|
||||
/**
|
||||
* @brief Validates if the encoder supports a specific key.
|
||||
* @param key The key of the encode parameter.
|
||||
* @return true if key is supported for this encoder, false otherwise.
|
||||
*/
|
||||
virtual bool isValidEncodeKey(const int key) const;
|
||||
|
||||
/**
|
||||
* @brief Set the destination for encoding as a file.
|
||||
* @param filename The name of the file to which the image will be written.
|
||||
* @return true if the destination was successfully set, false otherwise.
|
||||
*/
|
||||
virtual bool setDestination(const String& filename);
|
||||
|
||||
/**
|
||||
* @brief Set the destination for encoding as a memory buffer.
|
||||
* @param buf A reference to the buffer where the encoded image data will be stored.
|
||||
* @return true if the destination was successfully set, false otherwise.
|
||||
*/
|
||||
virtual bool setDestination(std::vector<uchar>& buf);
|
||||
|
||||
/**
|
||||
* @brief Sets the metadata to write together with the image data
|
||||
* @param type The type of metadata to add
|
||||
* @param metadata The packed metadata (Exif, XMP, ...)
|
||||
*/
|
||||
virtual bool addMetadata(ImageMetadataType type, const Mat& metadata);
|
||||
|
||||
/**
|
||||
* @brief Encode and write the image data.
|
||||
* @param img The Mat object containing the image data to be encoded.
|
||||
* @param params A vector of parameters controlling the encoding process (e.g., compression level).
|
||||
* @return true if the image was successfully written, false otherwise.
|
||||
*/
|
||||
virtual bool write(const Mat& img, const std::vector<int>& params);
|
||||
|
||||
/**
|
||||
* @brief Encode and write multiple images (e.g., for animated formats).
|
||||
* By default, this method returns false, indicating that the format does not support multi-image encoding.
|
||||
* @param img_vec A vector of Mat objects containing the images to be encoded.
|
||||
* @param params A vector of parameters controlling the encoding process.
|
||||
* @return true if multiple images were successfully written, false otherwise.
|
||||
*/
|
||||
virtual bool writemulti(const std::vector<Mat>& img_vec, const std::vector<int>& params);
|
||||
|
||||
virtual bool writeanimation(const Animation& animation, const std::vector<int>& params);
|
||||
|
||||
/**
|
||||
* @brief Get a description of the image encoder (e.g., the format it supports).
|
||||
* @return A string describing the encoder.
|
||||
*/
|
||||
virtual String getDescription() const;
|
||||
|
||||
/**
|
||||
* @brief Create and return a new instance of the derived image encoder.
|
||||
* @return A new ImageEncoder object.
|
||||
*/
|
||||
virtual ImageEncoder newEncoder() const;
|
||||
|
||||
/**
|
||||
* @brief Throw an exception based on the last error encountered during encoding.
|
||||
* This method can be used to propagate error conditions back to the caller.
|
||||
*/
|
||||
virtual void throwOnError() const;
|
||||
|
||||
protected:
|
||||
std::vector<std::vector<unsigned char> > m_metadata; // see IMAGE_METADATA_...
|
||||
std::vector<bool> m_support_metadata;
|
||||
String m_description; ///< Description of the encoder (e.g., format name, capabilities).
|
||||
String m_filename; ///< Destination file name for encoded data.
|
||||
std::vector<uchar>* m_buf; ///< Pointer to the buffer for encoded data if using memory-based destination.
|
||||
bool m_buf_supported; ///< Flag indicating whether buffer-based encoding is supported.
|
||||
String m_last_error; ///< Stores the last error message encountered during encoding.
|
||||
std::vector<int> m_supported_encode_key; ///< Supported encode key list
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif/*_GRFMT_BASE_H_*/
|
||||
@@ -0,0 +1,740 @@
|
||||
/*M///////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
|
||||
//
|
||||
// By downloading, copying, installing or using the software you agree to this license.
|
||||
// If you do not agree to this license, do not download, install,
|
||||
// copy or use the software.
|
||||
//
|
||||
//
|
||||
// License Agreement
|
||||
// For Open Source Computer Vision Library
|
||||
//
|
||||
// Copyright (C) 2000-2008, Intel Corporation, all rights reserved.
|
||||
// Copyright (C) 2009, Willow Garage Inc., all rights reserved.
|
||||
// Third party copyrights are property of their respective owners.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without modification,
|
||||
// are permitted provided that the following conditions are met:
|
||||
//
|
||||
// * Redistribution's of source code must retain the above copyright notice,
|
||||
// this list of conditions and the following disclaimer.
|
||||
//
|
||||
// * Redistribution's in binary form must reproduce the above copyright notice,
|
||||
// this list of conditions and the following disclaimer in the documentation
|
||||
// and/or other materials provided with the distribution.
|
||||
//
|
||||
// * The name of the copyright holders may not be used to endorse or promote products
|
||||
// derived from this software without specific prior written permission.
|
||||
//
|
||||
// This software is provided by the copyright holders and contributors "as is" and
|
||||
// any express or implied warranties, including, but not limited to, the implied
|
||||
// warranties of merchantability and fitness for a particular purpose are disclaimed.
|
||||
// In no event shall the Intel Corporation or contributors be liable for any direct,
|
||||
// indirect, incidental, special, exemplary, or consequential damages
|
||||
// (including, but not limited to, procurement of substitute goods or services;
|
||||
// loss of use, data, or profits; or business interruption) however caused
|
||||
// and on any theory of liability, whether in contract, strict liability,
|
||||
// or tort (including negligence or otherwise) arising in any way out of
|
||||
// the use of this software, even if advised of the possibility of such damage.
|
||||
//
|
||||
//M*/
|
||||
|
||||
#include "precomp.hpp"
|
||||
#include "grfmt_bmp.hpp"
|
||||
#include "opencv2/core/utils/logger.hpp"
|
||||
|
||||
namespace cv
|
||||
{
|
||||
|
||||
static const char* fmtSignBmp = "BM";
|
||||
|
||||
/************************ BMP decoder *****************************/
|
||||
|
||||
BmpDecoder::BmpDecoder()
|
||||
{
|
||||
m_signature = fmtSignBmp;
|
||||
m_offset = -1;
|
||||
m_buf_supported = true;
|
||||
m_origin = ORIGIN_TL;
|
||||
m_bpp = 0;
|
||||
m_rle_code = BMP_RGB;
|
||||
initMask();
|
||||
}
|
||||
|
||||
|
||||
BmpDecoder::~BmpDecoder()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
void BmpDecoder::close()
|
||||
{
|
||||
m_strm.close();
|
||||
}
|
||||
|
||||
ImageDecoder BmpDecoder::newDecoder() const
|
||||
{
|
||||
return makePtr<BmpDecoder>();
|
||||
}
|
||||
|
||||
bool BmpDecoder::readHeader()
|
||||
{
|
||||
bool result = false;
|
||||
bool iscolor = false;
|
||||
|
||||
if( !m_buf.empty() )
|
||||
{
|
||||
if( !m_strm.open( m_buf ) )
|
||||
return false;
|
||||
}
|
||||
else if( !m_strm.open( m_filename ))
|
||||
return false;
|
||||
|
||||
try
|
||||
{
|
||||
m_strm.skip( 10 );
|
||||
m_offset = m_strm.getDWord();
|
||||
|
||||
int size = m_strm.getDWord();
|
||||
CV_Assert(size > 0); // overflow, 2Gb limit
|
||||
|
||||
initMask();
|
||||
if( size >= 36 )
|
||||
{
|
||||
m_width = m_strm.getDWord();
|
||||
m_height = m_strm.getDWord();
|
||||
m_bpp = m_strm.getDWord() >> 16;
|
||||
int m_rle_code_ = m_strm.getDWord();
|
||||
CV_Assert(m_rle_code_ >= 0 && m_rle_code_ <= BMP_BITFIELDS);
|
||||
m_rle_code = (BmpCompression)m_rle_code_;
|
||||
m_strm.skip(12);
|
||||
int clrused = m_strm.getDWord();
|
||||
|
||||
if( m_bpp == 32 && m_rle_code == BMP_BITFIELDS && size >= 56 )
|
||||
{
|
||||
m_strm.skip(4); //important colors
|
||||
//0 is Red channel bit mask, 1 is Green channel bit mask, 2 is Blue channel bit mask, 3 is Alpha channel bit mask
|
||||
for( int index_rgba = 0; index_rgba < 4; ++index_rgba )
|
||||
{
|
||||
uint mask = m_strm.getDWord();
|
||||
m_rgba_mask[index_rgba] = mask;
|
||||
if(mask != 0)
|
||||
{
|
||||
int bit_count = 0;
|
||||
while(!(mask & 1))
|
||||
{
|
||||
mask >>= 1;
|
||||
++bit_count;
|
||||
}
|
||||
m_rgba_bit_offset[index_rgba] = bit_count;
|
||||
m_rgba_scale_factor[index_rgba] = 255.0f / mask;
|
||||
}
|
||||
}
|
||||
m_strm.skip( size - 56 );
|
||||
}
|
||||
else
|
||||
m_strm.skip( size - 36 );
|
||||
|
||||
if( m_width > 0 && m_height != 0 &&
|
||||
(((m_bpp == 1 || m_bpp == 4 || m_bpp == 8 ||
|
||||
m_bpp == 24 || m_bpp == 32 ) && m_rle_code == BMP_RGB) ||
|
||||
((m_bpp == 16 || m_bpp == 32) && (m_rle_code == BMP_RGB || m_rle_code == BMP_BITFIELDS)) ||
|
||||
(m_bpp == 4 && m_rle_code == BMP_RLE4) ||
|
||||
(m_bpp == 8 && m_rle_code == BMP_RLE8)))
|
||||
{
|
||||
iscolor = true;
|
||||
result = true;
|
||||
|
||||
if( m_bpp <= 8 )
|
||||
{
|
||||
CV_Assert(clrused >= 0 && clrused <= 256);
|
||||
memset(m_palette, 0, sizeof(m_palette));
|
||||
m_strm.getBytes(m_palette, (clrused == 0? 1<<m_bpp : clrused)*4 );
|
||||
iscolor = IsColorPalette( m_palette, m_bpp );
|
||||
}
|
||||
else if( m_bpp == 16 && m_rle_code == BMP_BITFIELDS )
|
||||
{
|
||||
int redmask = m_strm.getDWord();
|
||||
int greenmask = m_strm.getDWord();
|
||||
int bluemask = m_strm.getDWord();
|
||||
|
||||
if( bluemask == 0x1f && greenmask == 0x3e0 && redmask == 0x7c00 )
|
||||
m_bpp = 15;
|
||||
else if( bluemask == 0x1f && greenmask == 0x7e0 && redmask == 0xf800 )
|
||||
;
|
||||
else
|
||||
result = false;
|
||||
}
|
||||
else if (m_bpp == 32 && m_rle_code == BMP_BITFIELDS)
|
||||
{
|
||||
// 32bit BMP not require to check something - we can simply allow it to use
|
||||
;
|
||||
}
|
||||
else if( m_bpp == 16 && m_rle_code == BMP_RGB )
|
||||
m_bpp = 15;
|
||||
}
|
||||
}
|
||||
else if( size == 12 )
|
||||
{
|
||||
m_width = m_strm.getWord();
|
||||
m_height = m_strm.getWord();
|
||||
m_bpp = m_strm.getDWord() >> 16;
|
||||
m_rle_code = BMP_RGB;
|
||||
|
||||
if( m_width > 0 && m_height != 0 &&
|
||||
(m_bpp == 1 || m_bpp == 4 || m_bpp == 8 ||
|
||||
m_bpp == 24 || m_bpp == 32 ))
|
||||
{
|
||||
if( m_bpp <= 8 )
|
||||
{
|
||||
uchar buffer[256*3];
|
||||
int j, clrused = 1 << m_bpp;
|
||||
m_strm.getBytes( buffer, clrused*3 );
|
||||
for( j = 0; j < clrused; j++ )
|
||||
{
|
||||
m_palette[j].b = buffer[3*j+0];
|
||||
m_palette[j].g = buffer[3*j+1];
|
||||
m_palette[j].r = buffer[3*j+2];
|
||||
}
|
||||
}
|
||||
result = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch(...)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
// in 32 bit case alpha channel is used - so require CV_8UC4 type
|
||||
m_type = iscolor ? ((m_bpp == 32 && m_rle_code != BMP_RGB) ? CV_8UC4 : CV_8UC3 ) : CV_8UC1;
|
||||
m_origin = m_height > 0 ? ORIGIN_BL : ORIGIN_TL;
|
||||
if ( m_height == std::numeric_limits<int>::min() ) {
|
||||
// abs(std::numeric_limits<int>::min()) is undefined behavior.
|
||||
result = false;
|
||||
} else {
|
||||
m_height = std::abs(m_height);
|
||||
}
|
||||
|
||||
if( !result )
|
||||
{
|
||||
m_offset = -1;
|
||||
m_width = m_height = -1;
|
||||
m_strm.close();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
bool BmpDecoder::readData( Mat& img )
|
||||
{
|
||||
uchar* data = img.ptr();
|
||||
int step = validateToInt(img.step);
|
||||
bool color = img.channels() > 1;
|
||||
uchar gray_palette[256] = {0};
|
||||
bool result = false;
|
||||
int nch = color ? 3 : 1;
|
||||
|
||||
const int effective_bpp = (m_bpp != 15) ? m_bpp : 16;
|
||||
const RowPitchParams pitch_params = calculateRowPitch(m_width, effective_bpp, 4, "BMP");
|
||||
const int src_pitch = pitch_params.src_pitch;
|
||||
const int width3 = calculateRowSize(m_width, nch, "BMP");
|
||||
int y;
|
||||
|
||||
if( m_offset < 0 || !m_strm.isOpened())
|
||||
return false;
|
||||
|
||||
if( m_origin == ORIGIN_BL )
|
||||
{
|
||||
data += (m_height - 1)*(size_t)step;
|
||||
step = -step;
|
||||
}
|
||||
|
||||
AutoBuffer<uchar> _src, _bgr;
|
||||
_src.allocate(src_pitch + 32);
|
||||
|
||||
if( !color )
|
||||
{
|
||||
if( m_bpp <= 8 )
|
||||
{
|
||||
CvtPaletteToGray( m_palette, gray_palette, 1 << m_bpp );
|
||||
}
|
||||
const size_t bgr_size = static_cast<size_t>(m_width) * 3 + 32;
|
||||
CV_CheckLT(bgr_size, MAX_IMAGE_ROW_SIZE, "BMP: bgr buffer size exceeds maximum allowed size");
|
||||
_bgr.allocate(bgr_size);
|
||||
}
|
||||
uchar *src = _src.data(), *bgr = _bgr.data();
|
||||
|
||||
try
|
||||
{
|
||||
m_strm.setPos( m_offset );
|
||||
|
||||
switch( m_bpp )
|
||||
{
|
||||
/************************* 1 BPP ************************/
|
||||
case 1:
|
||||
for( y = 0; y < m_height; y++, data += step )
|
||||
{
|
||||
m_strm.getBytes( src, src_pitch );
|
||||
FillColorRow1( color ? data : bgr, src, m_width, m_palette );
|
||||
if( !color )
|
||||
icvCvt_BGR2Gray_8u_C3C1R( bgr, 0, data, 0, Size(m_width,1) );
|
||||
}
|
||||
result = true;
|
||||
break;
|
||||
|
||||
/************************* 4 BPP ************************/
|
||||
case 4:
|
||||
if( m_rle_code == BMP_RGB )
|
||||
{
|
||||
for( y = 0; y < m_height; y++, data += step )
|
||||
{
|
||||
m_strm.getBytes( src, src_pitch );
|
||||
if( color )
|
||||
FillColorRow4( data, src, m_width, m_palette );
|
||||
else
|
||||
FillGrayRow4( data, src, m_width, gray_palette );
|
||||
}
|
||||
result = true;
|
||||
}
|
||||
else if( m_rle_code == BMP_RLE4 ) // rle4 compression
|
||||
{
|
||||
uchar* line_end = data + width3;
|
||||
y = 0;
|
||||
|
||||
for(;;)
|
||||
{
|
||||
int code = m_strm.getWord();
|
||||
const int len = code & 255;
|
||||
code >>= 8;
|
||||
if( len != 0 ) // encoded mode
|
||||
{
|
||||
PaletteEntry clr[2];
|
||||
uchar gray_clr[2];
|
||||
int t = 0;
|
||||
|
||||
clr[0] = m_palette[code >> 4];
|
||||
clr[1] = m_palette[code & 15];
|
||||
gray_clr[0] = gray_palette[code >> 4];
|
||||
gray_clr[1] = gray_palette[code & 15];
|
||||
|
||||
uchar* end = data + len*nch;
|
||||
if( end > line_end ) goto decode_rle4_bad;
|
||||
do
|
||||
{
|
||||
if( color )
|
||||
WRITE_PIX( data, clr[t] );
|
||||
else
|
||||
*data = gray_clr[t];
|
||||
t ^= 1;
|
||||
}
|
||||
while( (data += nch) < end );
|
||||
}
|
||||
else if( code > 2 ) // absolute mode
|
||||
{
|
||||
if( data + code*nch > line_end ) goto decode_rle4_bad;
|
||||
int sz = (((code + 1)>>1) + 1) & (~1);
|
||||
CV_Assert((size_t)sz < _src.size());
|
||||
m_strm.getBytes(src, sz);
|
||||
if( color )
|
||||
data = FillColorRow4( data, src, code, m_palette );
|
||||
else
|
||||
data = FillGrayRow4( data, src, code, gray_palette );
|
||||
}
|
||||
else
|
||||
{
|
||||
ptrdiff_t x_shift3 = line_end - data;
|
||||
|
||||
if( code == 2 )
|
||||
{
|
||||
x_shift3 = m_strm.getByte()*nch;
|
||||
m_strm.getByte();
|
||||
}
|
||||
|
||||
if( color )
|
||||
data = FillUniColor( data, line_end, step, width3,
|
||||
y, m_height, x_shift3,
|
||||
m_palette[0] );
|
||||
else
|
||||
data = FillUniGray( data, line_end, step, width3,
|
||||
y, m_height, x_shift3,
|
||||
gray_palette[0] );
|
||||
|
||||
if( y >= m_height )
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
result = true;
|
||||
decode_rle4_bad: ;
|
||||
}
|
||||
break;
|
||||
|
||||
/************************* 8 BPP ************************/
|
||||
case 8:
|
||||
if( m_rle_code == BMP_RGB )
|
||||
{
|
||||
for( y = 0; y < m_height; y++, data += step )
|
||||
{
|
||||
m_strm.getBytes( src, src_pitch );
|
||||
if( color )
|
||||
FillColorRow8( data, src, m_width, m_palette );
|
||||
else
|
||||
FillGrayRow8( data, src, m_width, gray_palette );
|
||||
}
|
||||
result = true;
|
||||
}
|
||||
else if( m_rle_code == BMP_RLE8 ) // rle8 compression
|
||||
{
|
||||
uchar* line_end = data + width3;
|
||||
int line_end_flag = 0;
|
||||
y = 0;
|
||||
|
||||
for(;;)
|
||||
{
|
||||
int code = m_strm.getWord();
|
||||
int len = code & 255;
|
||||
code >>= 8;
|
||||
if( len != 0 ) // encoded mode
|
||||
{
|
||||
int prev_y = y;
|
||||
len *= nch;
|
||||
|
||||
if( data + len > line_end )
|
||||
goto decode_rle8_bad;
|
||||
|
||||
if( color )
|
||||
data = FillUniColor( data, line_end, step, width3,
|
||||
y, m_height, len,
|
||||
m_palette[code] );
|
||||
else
|
||||
data = FillUniGray( data, line_end, step, width3,
|
||||
y, m_height, len,
|
||||
gray_palette[code] );
|
||||
|
||||
line_end_flag = y - prev_y;
|
||||
|
||||
if( y >= m_height )
|
||||
break;
|
||||
}
|
||||
else if( code > 2 ) // absolute mode
|
||||
{
|
||||
int prev_y = y;
|
||||
int code3 = code*nch;
|
||||
|
||||
if( data + code3 > line_end )
|
||||
goto decode_rle8_bad;
|
||||
int sz = (code + 1) & (~1);
|
||||
CV_Assert((size_t)sz < _src.size());
|
||||
m_strm.getBytes(src, sz);
|
||||
if( color )
|
||||
data = FillColorRow8( data, src, code, m_palette );
|
||||
else
|
||||
data = FillGrayRow8( data, src, code, gray_palette );
|
||||
|
||||
line_end_flag = y - prev_y;
|
||||
}
|
||||
else
|
||||
{
|
||||
ptrdiff_t x_shift3 = line_end - data;
|
||||
int y_shift = m_height - y;
|
||||
|
||||
if( code || !line_end_flag || x_shift3 < width3 )
|
||||
{
|
||||
if( code == 2 )
|
||||
{
|
||||
x_shift3 = m_strm.getByte()*nch;
|
||||
y_shift = m_strm.getByte();
|
||||
}
|
||||
|
||||
x_shift3 += ((ptrdiff_t)y_shift * width3) & ((code == 0) - 1);
|
||||
|
||||
if( y >= m_height )
|
||||
break;
|
||||
|
||||
if( color )
|
||||
data = FillUniColor( data, line_end, step, width3,
|
||||
y, m_height, x_shift3,
|
||||
m_palette[0] );
|
||||
else
|
||||
data = FillUniGray( data, line_end, step, width3,
|
||||
y, m_height, x_shift3,
|
||||
gray_palette[0] );
|
||||
|
||||
if( y >= m_height )
|
||||
break;
|
||||
}
|
||||
|
||||
line_end_flag = 0;
|
||||
if( y >= m_height )
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
result = true;
|
||||
decode_rle8_bad: ;
|
||||
}
|
||||
break;
|
||||
/************************* 15 BPP ************************/
|
||||
case 15:
|
||||
for( y = 0; y < m_height; y++, data += step )
|
||||
{
|
||||
m_strm.getBytes( src, src_pitch );
|
||||
if( !color )
|
||||
icvCvt_BGR5552Gray_8u_C2C1R( src, 0, data, 0, Size(m_width,1) );
|
||||
else
|
||||
icvCvt_BGR5552BGR_8u_C2C3R( src, 0, data, 0, Size(m_width,1) );
|
||||
}
|
||||
result = true;
|
||||
break;
|
||||
/************************* 16 BPP ************************/
|
||||
case 16:
|
||||
for( y = 0; y < m_height; y++, data += step )
|
||||
{
|
||||
m_strm.getBytes( src, src_pitch );
|
||||
if( !color )
|
||||
icvCvt_BGR5652Gray_8u_C2C1R( src, 0, data, 0, Size(m_width,1) );
|
||||
else
|
||||
icvCvt_BGR5652BGR_8u_C2C3R( src, 0, data, 0, Size(m_width,1) );
|
||||
}
|
||||
result = true;
|
||||
break;
|
||||
/************************* 24 BPP ************************/
|
||||
case 24:
|
||||
for( y = 0; y < m_height; y++, data += step )
|
||||
{
|
||||
m_strm.getBytes( src, src_pitch );
|
||||
if(!color)
|
||||
icvCvt_BGR2Gray_8u_C3C1R( src, 0, data, 0, Size(m_width,1) );
|
||||
else
|
||||
memcpy( data, src, m_width*3 );
|
||||
}
|
||||
result = true;
|
||||
break;
|
||||
/************************* 32 BPP ************************/
|
||||
case 32:
|
||||
{
|
||||
bool has_bit_mask = (m_rgba_bit_offset[0] >= 0) && (m_rgba_bit_offset[1] >= 0) && (m_rgba_bit_offset[2] >= 0);
|
||||
for( y = 0; y < m_height; y++, data += step )
|
||||
{
|
||||
m_strm.getBytes( src, src_pitch );
|
||||
|
||||
if( !color )
|
||||
{
|
||||
if ( has_bit_mask )
|
||||
maskBGRAtoGray(data, src, m_width);
|
||||
else
|
||||
icvCvt_BGRA2Gray_8u_C4C1R( src, 0, data, 0, Size(m_width,1) );
|
||||
}
|
||||
else if( img.channels() == 3 )
|
||||
{
|
||||
if ( has_bit_mask )
|
||||
maskBGRA(data, src, m_width, false);
|
||||
else
|
||||
icvCvt_BGRA2BGR_8u_C4C3R(src, 0, data, 0, Size(m_width, 1));
|
||||
}
|
||||
else if ( img.channels() == 4 )
|
||||
{
|
||||
if ( has_bit_mask )
|
||||
maskBGRA(data, src, m_width, true);
|
||||
else
|
||||
memcpy(data, src, m_width * 4);
|
||||
}
|
||||
}
|
||||
}
|
||||
result = true;
|
||||
break;
|
||||
default:
|
||||
CV_Error(cv::Error::StsError, "Invalid/unsupported mode");
|
||||
}
|
||||
}
|
||||
catch(...)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
|
||||
if (m_use_rgb && color && img.channels() == 3)
|
||||
{
|
||||
cv::cvtColor(img, img, cv::COLOR_BGR2RGB);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void BmpDecoder::initMask()
|
||||
{
|
||||
memset(m_rgba_mask, 0, sizeof(m_rgba_mask));
|
||||
memset(m_rgba_bit_offset, -1, sizeof(m_rgba_bit_offset));
|
||||
for (size_t i = 0; i < 4; i++) {
|
||||
m_rgba_scale_factor[i] = 1.0f;
|
||||
}
|
||||
}
|
||||
|
||||
void BmpDecoder::maskBGRA(uchar* des, const uchar* src, int num, bool alpha_required)
|
||||
{
|
||||
int dest_stride = alpha_required ? 4 : 3;
|
||||
for( int i = 0; i < num; i++, des += dest_stride, src += 4 )
|
||||
{
|
||||
uint data = *((uint*)src);
|
||||
des[0] = (uchar)(((m_rgba_mask[2] & data) >> m_rgba_bit_offset[2]) * m_rgba_scale_factor[2]);
|
||||
des[1] = (uchar)(((m_rgba_mask[1] & data) >> m_rgba_bit_offset[1]) * m_rgba_scale_factor[1]);
|
||||
des[2] = (uchar)(((m_rgba_mask[0] & data) >> m_rgba_bit_offset[0]) * m_rgba_scale_factor[0]);
|
||||
if (alpha_required)
|
||||
{
|
||||
if (m_rgba_bit_offset[3] >= 0)
|
||||
des[3] = (uchar)(((m_rgba_mask[3] & data) >> m_rgba_bit_offset[3]) * m_rgba_scale_factor[3]);
|
||||
else
|
||||
des[3] = 255;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void BmpDecoder::maskBGRAtoGray(uchar* des, const uchar* src, int num)
|
||||
{
|
||||
for( int i = 0; i < num; i++, des++, src += 4 )
|
||||
{
|
||||
uint data = *((uint*)src);
|
||||
int red = (uchar)(((m_rgba_mask[0] & data) >> m_rgba_bit_offset[0]) * m_rgba_scale_factor[0]);
|
||||
int green = (uchar)(((m_rgba_mask[1] & data) >> m_rgba_bit_offset[1]) * m_rgba_scale_factor[1]);
|
||||
int blue = (uchar)(((m_rgba_mask[2] & data) >> m_rgba_bit_offset[2]) * m_rgba_scale_factor[2]);
|
||||
|
||||
*des = (uchar)(0.299f * red + 0.587f * green + 0.114f * blue);
|
||||
}
|
||||
}
|
||||
//////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
BmpEncoder::BmpEncoder()
|
||||
{
|
||||
m_description = "Windows bitmap (*.bmp;*.dib)";
|
||||
m_buf_supported = true;
|
||||
m_supported_encode_key = {IMWRITE_BMP_COMPRESSION};
|
||||
}
|
||||
|
||||
|
||||
BmpEncoder::~BmpEncoder()
|
||||
{
|
||||
}
|
||||
|
||||
ImageEncoder BmpEncoder::newEncoder() const
|
||||
{
|
||||
return makePtr<BmpEncoder>();
|
||||
}
|
||||
|
||||
bool BmpEncoder::write( const Mat& img, const std::vector<int>& params )
|
||||
{
|
||||
int width = img.cols, height = img.rows, channels = img.channels();
|
||||
int fileStep = (width*channels + 3) & -4;
|
||||
uchar zeropad[] = "\0\0\0\0";
|
||||
|
||||
WLByteStream strm;
|
||||
|
||||
if( m_buf )
|
||||
{
|
||||
if( !strm.open( *m_buf ) )
|
||||
return false;
|
||||
}
|
||||
else if( !strm.open( m_filename ))
|
||||
return false;
|
||||
|
||||
// sRGB colorspace requires BITMAPV5HEADER.
|
||||
// See https://learn.microsoft.com/en-us/windows/win32/api/wingdi/ns-wingdi-bitmapv5header
|
||||
bool useV5BitFields = true;
|
||||
for(size_t i = 0; i < params.size(); i+=2)
|
||||
{
|
||||
const int value = params[i+1];
|
||||
switch(params[i])
|
||||
{
|
||||
case IMWRITE_BMP_COMPRESSION:
|
||||
{
|
||||
switch(value) {
|
||||
case IMWRITE_BMP_COMPRESSION_RGB:
|
||||
useV5BitFields = false;
|
||||
break;
|
||||
case IMWRITE_BMP_COMPRESSION_BITFIELDS:
|
||||
useV5BitFields = true;
|
||||
break;
|
||||
default:
|
||||
useV5BitFields = true;
|
||||
CV_LOG_WARNING(nullptr, cv::format("The value(%d) for IMWRITE_BMP_COMPRESSION must be one of ImwriteBMPCompressionFlags. It is fallbacked to true", value));
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
useV5BitFields &= (channels == 4); // BMP_BITFIELDS requires 32 bit per pixel.
|
||||
|
||||
int bitmapHeaderSize = useV5BitFields ? 124 : 40;
|
||||
int paletteSize = channels > 1 ? 0 : 1024;
|
||||
int headerSize = 14 /* fileheader */ + bitmapHeaderSize + paletteSize;
|
||||
size_t fileSize = (size_t)fileStep*height + headerSize;
|
||||
PaletteEntry palette[256];
|
||||
|
||||
if( m_buf )
|
||||
m_buf->reserve( alignSize(fileSize + 16, 256) );
|
||||
|
||||
// write signature 'BM'
|
||||
CHECK_WRITE(strm.putBytes( fmtSignBmp, (int)strlen(fmtSignBmp) ));
|
||||
|
||||
// write file header
|
||||
CHECK_WRITE(strm.putDWord( validateToInt(fileSize) )); // file size
|
||||
CHECK_WRITE(strm.putDWord( 0 ));
|
||||
CHECK_WRITE(strm.putDWord( headerSize ));
|
||||
|
||||
// write bitmap header
|
||||
CHECK_WRITE(strm.putDWord( bitmapHeaderSize ));
|
||||
CHECK_WRITE(strm.putDWord( width ));
|
||||
CHECK_WRITE(strm.putDWord( height ));
|
||||
CHECK_WRITE(strm.putWord( 1 ));
|
||||
CHECK_WRITE(strm.putWord( channels << 3 ));
|
||||
CHECK_WRITE(strm.putDWord( useV5BitFields ? BMP_BITFIELDS : BMP_RGB ));
|
||||
CHECK_WRITE(strm.putDWord( 0 ));
|
||||
CHECK_WRITE(strm.putDWord( 0 ));
|
||||
CHECK_WRITE(strm.putDWord( 0 ));
|
||||
CHECK_WRITE(strm.putDWord( 0 ));
|
||||
CHECK_WRITE(strm.putDWord( 0 ));
|
||||
|
||||
if( useV5BitFields )
|
||||
{
|
||||
CHECK_WRITE(strm.putDWord( 0x00FF0000 )); // bV5RedMask
|
||||
CHECK_WRITE(strm.putDWord( 0x0000FF00 )); // bV5GreenMask
|
||||
CHECK_WRITE(strm.putDWord( 0x000000FF )); // bV5BlueMask
|
||||
CHECK_WRITE(strm.putDWord( 0xFF000000 )); // bV5AlphaMask
|
||||
CHECK_WRITE(strm.putBytes( "BGRs", 4)); // bV5CSType (sRGB)
|
||||
{ // bV5Endpoints
|
||||
for(int index_rgb = 0; index_rgb < 3; index_rgb ++ ){ // Red/Green/Blue
|
||||
CHECK_WRITE(strm.putDWord( 0 )); // ciexyzX
|
||||
CHECK_WRITE(strm.putDWord( 0 )); // ciexyzY
|
||||
CHECK_WRITE(strm.putDWord( 0 )); // ciexyzZ
|
||||
}
|
||||
}
|
||||
CHECK_WRITE(strm.putDWord( 0 )); // bV5GammaRed
|
||||
CHECK_WRITE(strm.putDWord( 0 )); // bV5GammaGreen
|
||||
CHECK_WRITE(strm.putDWord( 0 )); // bV5GammaBlue
|
||||
CHECK_WRITE(strm.putDWord( 0 )); // bV5Intent
|
||||
CHECK_WRITE(strm.putDWord( 0 )); // bV5ProfileData
|
||||
CHECK_WRITE(strm.putDWord( 0 )); // bV5ProfileSize
|
||||
CHECK_WRITE(strm.putDWord( 0 )); // bV5Reserved
|
||||
}
|
||||
|
||||
if( channels == 1 )
|
||||
{
|
||||
FillGrayPalette( palette, 8 );
|
||||
CHECK_WRITE(strm.putBytes( palette, sizeof(palette)));
|
||||
}
|
||||
|
||||
width *= channels;
|
||||
for( int y = height - 1; y >= 0; y-- )
|
||||
{
|
||||
CHECK_WRITE(strm.putBytes( img.ptr(y), width ));
|
||||
if( fileStep > width )
|
||||
{
|
||||
CHECK_WRITE(strm.putBytes( zeropad, fileStep - width ));
|
||||
}
|
||||
}
|
||||
|
||||
strm.close();
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
/*M///////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
|
||||
//
|
||||
// By downloading, copying, installing or using the software you agree to this license.
|
||||
// If you do not agree to this license, do not download, install,
|
||||
// copy or use the software.
|
||||
//
|
||||
//
|
||||
// License Agreement
|
||||
// For Open Source Computer Vision Library
|
||||
//
|
||||
// Copyright (C) 2000-2008, Intel Corporation, all rights reserved.
|
||||
// Copyright (C) 2009, Willow Garage Inc., all rights reserved.
|
||||
// Third party copyrights are property of their respective owners.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without modification,
|
||||
// are permitted provided that the following conditions are met:
|
||||
//
|
||||
// * Redistribution's of source code must retain the above copyright notice,
|
||||
// this list of conditions and the following disclaimer.
|
||||
//
|
||||
// * Redistribution's in binary form must reproduce the above copyright notice,
|
||||
// this list of conditions and the following disclaimer in the documentation
|
||||
// and/or other materials provided with the distribution.
|
||||
//
|
||||
// * The name of the copyright holders may not be used to endorse or promote products
|
||||
// derived from this software without specific prior written permission.
|
||||
//
|
||||
// This software is provided by the copyright holders and contributors "as is" and
|
||||
// any express or implied warranties, including, but not limited to, the implied
|
||||
// warranties of merchantability and fitness for a particular purpose are disclaimed.
|
||||
// In no event shall the Intel Corporation or contributors be liable for any direct,
|
||||
// indirect, incidental, special, exemplary, or consequential damages
|
||||
// (including, but not limited to, procurement of substitute goods or services;
|
||||
// loss of use, data, or profits; or business interruption) however caused
|
||||
// and on any theory of liability, whether in contract, strict liability,
|
||||
// or tort (including negligence or otherwise) arising in any way out of
|
||||
// the use of this software, even if advised of the possibility of such damage.
|
||||
//
|
||||
//M*/
|
||||
|
||||
#ifndef _GRFMT_BMP_H_
|
||||
#define _GRFMT_BMP_H_
|
||||
|
||||
#include "grfmt_base.hpp"
|
||||
|
||||
namespace cv
|
||||
{
|
||||
|
||||
enum BmpCompression
|
||||
{
|
||||
BMP_RGB = 0,
|
||||
BMP_RLE8 = 1,
|
||||
BMP_RLE4 = 2,
|
||||
BMP_BITFIELDS = 3
|
||||
};
|
||||
|
||||
|
||||
// Windows Bitmap reader
|
||||
class BmpDecoder CV_FINAL : public BaseImageDecoder
|
||||
{
|
||||
public:
|
||||
|
||||
BmpDecoder();
|
||||
~BmpDecoder() CV_OVERRIDE;
|
||||
|
||||
bool readData( Mat& img ) CV_OVERRIDE;
|
||||
bool readHeader() CV_OVERRIDE;
|
||||
void close();
|
||||
|
||||
ImageDecoder newDecoder() const CV_OVERRIDE;
|
||||
|
||||
protected:
|
||||
|
||||
void initMask();
|
||||
void maskBGRA(uchar* des, const uchar* src, int num, bool alpha_required);
|
||||
void maskBGRAtoGray(uchar* des, const uchar* src, int num);
|
||||
|
||||
enum Origin
|
||||
{
|
||||
ORIGIN_TL = 0,
|
||||
ORIGIN_BL = 1
|
||||
};
|
||||
|
||||
RLByteStream m_strm;
|
||||
PaletteEntry m_palette[256];
|
||||
Origin m_origin;
|
||||
int m_bpp;
|
||||
int64_t m_offset;
|
||||
BmpCompression m_rle_code;
|
||||
uint m_rgba_mask[4];
|
||||
int m_rgba_bit_offset[4];
|
||||
float m_rgba_scale_factor[4];
|
||||
};
|
||||
|
||||
|
||||
// ... writer
|
||||
class BmpEncoder CV_FINAL : public BaseImageEncoder
|
||||
{
|
||||
public:
|
||||
BmpEncoder();
|
||||
~BmpEncoder() CV_OVERRIDE;
|
||||
|
||||
bool write( const Mat& img, const std::vector<int>& params ) CV_OVERRIDE;
|
||||
|
||||
ImageEncoder newEncoder() const CV_OVERRIDE;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif/*_GRFMT_BMP_H_*/
|
||||
@@ -0,0 +1,937 @@
|
||||
/*M///////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
|
||||
//
|
||||
// By downloading, copying, installing or using the software you agree to this license.
|
||||
// If you do not agree to this license, do not download, install,
|
||||
// copy or use the software.
|
||||
//
|
||||
//
|
||||
// License Agreement
|
||||
// For Open Source Computer Vision Library
|
||||
//
|
||||
// Copyright (C) 2000-2008, Intel Corporation, all rights reserved.
|
||||
// Copyright (C) 2009, Willow Garage Inc., all rights reserved.
|
||||
// Third party copyrights are property of their respective owners.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without modification,
|
||||
// are permitted provided that the following conditions are met:
|
||||
//
|
||||
// * Redistribution's of source code must retain the above copyright notice,
|
||||
// this list of conditions and the following disclaimer.
|
||||
//
|
||||
// * Redistribution's in binary form must reproduce the above copyright notice,
|
||||
// this list of conditions and the following disclaimer in the documentation
|
||||
// and/or other materials provided with the distribution.
|
||||
//
|
||||
// * The name of the copyright holders may not be used to endorse or promote products
|
||||
// derived from this software without specific prior written permission.
|
||||
//
|
||||
// This software is provided by the copyright holders and contributors "as is" and
|
||||
// any express or implied warranties, including, but not limited to, the implied
|
||||
// warranties of merchantability and fitness for a particular purpose are disclaimed.
|
||||
// In no event shall the Intel Corporation or contributors be liable for any direct,
|
||||
// indirect, incidental, special, exemplary, or consequential damages
|
||||
// (including, but not limited to, procurement of substitute goods or services;
|
||||
// loss of use, data, or profits; or business interruption) however caused
|
||||
// and on any theory of liability, whether in contract, strict liability,
|
||||
// or tort (including negligence or otherwise) arising in any way out of
|
||||
// the use of this software, even if advised of the possibility of such damage.
|
||||
//
|
||||
//M*/
|
||||
|
||||
#include "precomp.hpp"
|
||||
|
||||
#ifdef HAVE_OPENEXR
|
||||
|
||||
#include <opencv2/core/utils/configuration.private.hpp>
|
||||
#include <opencv2/core/utils/logger.hpp>
|
||||
|
||||
#if defined _MSC_VER && _MSC_VER >= 1200
|
||||
# pragma warning( disable: 4100 4244 4267 )
|
||||
#endif
|
||||
|
||||
#if defined __GNUC__ && defined __APPLE__
|
||||
# pragma GCC diagnostic ignored "-Wshadow"
|
||||
#endif
|
||||
|
||||
/// C++ Standard Libraries
|
||||
#include <iostream>
|
||||
#include <stdexcept>
|
||||
|
||||
#include <ImfFrameBuffer.h>
|
||||
#include <ImfHeader.h>
|
||||
#include <ImfInputFile.h>
|
||||
#include <ImfOutputFile.h>
|
||||
#include <ImfChannelList.h>
|
||||
#include <ImfStandardAttributes.h>
|
||||
#include <half.h>
|
||||
#include "grfmt_exr.hpp"
|
||||
#include "OpenEXRConfig.h"
|
||||
|
||||
#if defined _WIN32
|
||||
|
||||
#undef UINT
|
||||
#define UINT ((Imf::PixelType)0)
|
||||
#undef HALF
|
||||
#define HALF ((Imf::PixelType)1)
|
||||
#undef FLOAT
|
||||
#define FLOAT ((Imf::PixelType)2)
|
||||
|
||||
#endif
|
||||
|
||||
namespace cv
|
||||
{
|
||||
|
||||
static bool isOpenEXREnabled()
|
||||
{
|
||||
static const bool PARAM_ENABLE_OPENEXR = utils::getConfigurationParameterBool("OPENCV_IO_ENABLE_OPENEXR",
|
||||
#ifdef OPENCV_IMGCODECS_USE_OPENEXR
|
||||
true
|
||||
#else
|
||||
false
|
||||
#endif
|
||||
);
|
||||
return PARAM_ENABLE_OPENEXR;
|
||||
}
|
||||
static void initOpenEXR()
|
||||
{
|
||||
if (!isOpenEXREnabled())
|
||||
{
|
||||
const char* message = "imgcodecs: OpenEXR codec is disabled. You can enable it via 'OPENCV_IO_ENABLE_OPENEXR' option. Refer for details and cautions here: https://github.com/opencv/opencv/issues/21326";
|
||||
CV_LOG_WARNING(NULL, message);
|
||||
CV_Error(Error::StsNotImplemented, message);
|
||||
}
|
||||
}
|
||||
|
||||
/////////////////////// ExrDecoder ///////////////////
|
||||
|
||||
ExrDecoder::ExrDecoder()
|
||||
{
|
||||
m_signature = "\x76\x2f\x31\x01";
|
||||
m_file = 0;
|
||||
m_red = m_green = m_blue = m_alpha = 0;
|
||||
m_type = ((Imf::PixelType)0);
|
||||
m_iscolor = false;
|
||||
m_bit_depth = 0;
|
||||
m_isfloat = false;
|
||||
m_ischroma = false;
|
||||
m_hasalpha = false;
|
||||
m_native_depth = false;
|
||||
m_multispectral = false;
|
||||
m_channels = 0;
|
||||
}
|
||||
|
||||
|
||||
ExrDecoder::~ExrDecoder()
|
||||
{
|
||||
close();
|
||||
}
|
||||
|
||||
|
||||
void ExrDecoder::close()
|
||||
{
|
||||
if( m_file )
|
||||
{
|
||||
delete m_file;
|
||||
m_file = 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
int ExrDecoder::type() const
|
||||
{
|
||||
return CV_MAKETYPE((m_isfloat ? CV_32F : CV_32S), (m_multispectral ? m_channels : (m_iscolor && m_hasalpha) ? 4 : m_iscolor ? 3 : m_hasalpha ? 2 : 1));
|
||||
}
|
||||
|
||||
|
||||
bool ExrDecoder::readHeader()
|
||||
{
|
||||
bool result = false;
|
||||
|
||||
m_file = new InputFile( m_filename.c_str() );
|
||||
|
||||
m_datawindow = m_file->header().dataWindow();
|
||||
m_width = m_datawindow.max.x - m_datawindow.min.x + 1;
|
||||
m_height = m_datawindow.max.y - m_datawindow.min.y + 1;
|
||||
|
||||
// the type HALF is converted to 32 bit float
|
||||
// and the other types supported by OpenEXR are 32 bit anyway
|
||||
m_bit_depth = 32;
|
||||
|
||||
if( hasChromaticities( m_file->header() ))
|
||||
m_chroma = chromaticities( m_file->header() );
|
||||
|
||||
const ChannelList &channels = m_file->header().channels();
|
||||
m_red = channels.findChannel( "R" );
|
||||
m_green = channels.findChannel( "G" );
|
||||
m_blue = channels.findChannel( "B" );
|
||||
m_alpha = channels.findChannel( "A" );
|
||||
m_multispectral = channels.findChannel( "0" ) != nullptr;
|
||||
|
||||
if( m_alpha ) // alpha channel supported in RGB, Y, and YC scenarios
|
||||
m_hasalpha = true;
|
||||
|
||||
if( m_red || m_green || m_blue )
|
||||
{
|
||||
m_iscolor = true;
|
||||
m_ischroma = false;
|
||||
result = true;
|
||||
}
|
||||
else if( m_multispectral )
|
||||
{
|
||||
m_channels = 0;
|
||||
for( auto it = channels.begin(); it != channels.end(); it++ )
|
||||
m_channels++;
|
||||
|
||||
m_iscolor = true; // ??? false
|
||||
m_ischroma = false;
|
||||
m_hasalpha = false;
|
||||
result = m_channels <= CV_CN_MAX;
|
||||
|
||||
for ( int i = 1; result && i < m_channels; i++ ) // channel 0 was found previously
|
||||
{
|
||||
const Channel *ch = channels.findChannel( std::to_string(i) );
|
||||
result = ch && ch->xSampling == 1 && ch->ySampling == 1; // subsampling is not supported
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
m_green = channels.findChannel( "Y" );
|
||||
if( !m_green )
|
||||
{
|
||||
m_green = channels.findChannel( "Z" ); // Distance of the front of a sample from the viewer
|
||||
}
|
||||
if( m_green )
|
||||
{
|
||||
m_ischroma = true;
|
||||
m_red = channels.findChannel( "RY" );
|
||||
m_blue = channels.findChannel( "BY" );
|
||||
m_iscolor = (m_blue || m_red);
|
||||
result = true;
|
||||
}
|
||||
else
|
||||
result = false;
|
||||
}
|
||||
|
||||
if( result )
|
||||
{
|
||||
m_type = FLOAT;
|
||||
m_isfloat = ( m_type == FLOAT );
|
||||
}
|
||||
|
||||
if( !result )
|
||||
close();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
bool ExrDecoder::readData( Mat& img )
|
||||
{
|
||||
m_native_depth = CV_MAT_DEPTH(type()) == img.depth();
|
||||
bool multispectral = img.channels() > 4;
|
||||
bool color = img.channels() > 2; // output mat has 3+ channels; Y or YA are the 1 and 2 channel scenario
|
||||
bool alphasupported = !multispectral && ( img.channels() % 2 == 0 ); // even number of channels indicates alpha
|
||||
int channels = 0;
|
||||
uchar* data = img.ptr();
|
||||
size_t step = img.step;
|
||||
bool justcopy = ( m_native_depth && (color == m_iscolor) );
|
||||
bool chromatorgb = ( m_ischroma && color );
|
||||
bool rgbtogray = ( !m_ischroma && m_iscolor && !color );
|
||||
bool result = true;
|
||||
FrameBuffer frame;
|
||||
const int defaultchannels = 3;
|
||||
int xsample[defaultchannels] = {1, 1, 1};
|
||||
char *buffer;
|
||||
CV_Assert(m_type == FLOAT);
|
||||
const size_t floatsize = sizeof(float);
|
||||
size_t xstep = m_native_depth ? floatsize : 1; // 4 bytes if native depth (FLOAT), otherwise converting to 1 byte U8 depth
|
||||
size_t ystep = 0;
|
||||
const int channelstoread = ( multispectral ? img.channels() : (m_iscolor && alphasupported) ? 4 :
|
||||
( (m_iscolor && !m_ischroma) || color) ? 3 : alphasupported ? 2 : 1 ); // number of channels to read may exceed channels in output img
|
||||
size_t xStride = floatsize * channelstoread;
|
||||
|
||||
if ( m_multispectral ) // possible gray/RGB conversions
|
||||
{
|
||||
CV_CheckChannelsEQ(img.channels(), CV_MAT_CN(type()), "OpenCV EXR decoder needs more number of channels for multispectral images. Use cv::IMREAD_UNCHANGED mode for imread."); // IMREAD_ANYCOLOR needed
|
||||
CV_CheckDepthEQ(img.depth(), CV_MAT_DEPTH(type()), "OpenCV EXR decoder supports CV_32F depth only for multispectral images. Use cv::IMREAD_UNCHANGED mode for imread."); // IMREAD_ANYDEPTH needed
|
||||
}
|
||||
CV_Assert( multispectral == m_multispectral && (!multispectral || justcopy) ); // should be true after previous checks
|
||||
|
||||
// See https://github.com/opencv/opencv/issues/26705
|
||||
// If ALGO_HINT_ACCURATE is set, read BGR and swap to RGB.
|
||||
// If ALGO_HINT_APPROX is set, read RGB directly.
|
||||
bool doReadRGB = m_use_rgb;
|
||||
bool doPostColorSwap = false; // After decoding, swap BGR to RGB
|
||||
if(m_use_rgb && (getDefaultAlgorithmHint() == ALGO_HINT_ACCURATE) )
|
||||
{
|
||||
doReadRGB = false;
|
||||
doPostColorSwap = true;
|
||||
}
|
||||
|
||||
AutoBuffer<char> copy_buffer;
|
||||
|
||||
if( !justcopy )
|
||||
{
|
||||
copy_buffer.allocate(floatsize * m_width * defaultchannels);
|
||||
buffer = copy_buffer.data();
|
||||
ystep = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
buffer = (char *)data;
|
||||
ystep = step;
|
||||
}
|
||||
|
||||
if( m_ischroma )
|
||||
{
|
||||
if( color )
|
||||
{
|
||||
if( m_blue )
|
||||
{
|
||||
frame.insert( "BY", Slice( m_type,
|
||||
buffer - m_datawindow.min.x * xStride - m_datawindow.min.y * ystep,
|
||||
xStride, ystep, m_blue->xSampling, m_blue->ySampling, 0.0 ));
|
||||
xsample[0] = m_blue->xSampling;
|
||||
}
|
||||
else
|
||||
{
|
||||
frame.insert( "BY", Slice( m_type,
|
||||
buffer - m_datawindow.min.x * xStride - m_datawindow.min.y * ystep,
|
||||
xStride, ystep, 1, 1, 0.0 ));
|
||||
}
|
||||
if( m_green )
|
||||
{
|
||||
frame.insert( "Y", Slice( m_type,
|
||||
buffer - m_datawindow.min.x * xStride - m_datawindow.min.y * ystep + floatsize,
|
||||
xStride, ystep, m_green->xSampling, m_green->ySampling, 0.0 ));
|
||||
xsample[1] = m_green->xSampling;
|
||||
}
|
||||
else
|
||||
{
|
||||
frame.insert( "Y", Slice( m_type,
|
||||
buffer - m_datawindow.min.x * xStride - m_datawindow.min.y * ystep + floatsize,
|
||||
xStride, ystep, 1, 1, 0.0 ));
|
||||
}
|
||||
if( m_red )
|
||||
{
|
||||
frame.insert( "RY", Slice( m_type,
|
||||
buffer - m_datawindow.min.x * xStride - m_datawindow.min.y * ystep + (floatsize * 2),
|
||||
xStride, ystep, m_red->xSampling, m_red->ySampling, 0.0 ));
|
||||
xsample[2] = m_red->xSampling;
|
||||
}
|
||||
else
|
||||
{
|
||||
frame.insert( "RY", Slice( m_type,
|
||||
buffer - m_datawindow.min.x * xStride - m_datawindow.min.y * ystep + (floatsize * 2),
|
||||
xStride, ystep, 1, 1, 0.0 ));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
frame.insert( "Y", Slice( m_type,
|
||||
buffer - m_datawindow.min.x * xStride - m_datawindow.min.y * ystep,
|
||||
xStride, ystep, m_green->xSampling, m_green->ySampling, 0.0 ));
|
||||
xsample[0] = m_green->xSampling;
|
||||
}
|
||||
}
|
||||
else if( m_multispectral )
|
||||
{
|
||||
for ( int i = 0; i < m_channels; i++ )
|
||||
{
|
||||
frame.insert( std::to_string(i), Slice( m_type,
|
||||
buffer - m_datawindow.min.x * xStride - m_datawindow.min.y * ystep + (floatsize * i),
|
||||
xStride, ystep, 1, 1, 0.0 ));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if( m_blue )
|
||||
{
|
||||
frame.insert( "B", Slice( m_type,
|
||||
buffer - m_datawindow.min.x * xStride - m_datawindow.min.y * ystep,
|
||||
xStride, ystep, m_blue->xSampling, m_blue->ySampling, 0.0 ));
|
||||
xsample[0] = m_blue->xSampling;
|
||||
}
|
||||
else
|
||||
{
|
||||
frame.insert( "B", Slice( m_type,
|
||||
buffer - m_datawindow.min.x * xStride - m_datawindow.min.y * ystep,
|
||||
xStride, ystep, 1, 1, 0.0 ));
|
||||
}
|
||||
if( m_green )
|
||||
{
|
||||
frame.insert( "G", Slice( m_type,
|
||||
buffer - m_datawindow.min.x * xStride - m_datawindow.min.y * ystep + floatsize,
|
||||
xStride, ystep, m_green->xSampling, m_green->ySampling, 0.0 ));
|
||||
xsample[1] = m_green->xSampling;
|
||||
}
|
||||
else
|
||||
{
|
||||
frame.insert( "G", Slice( m_type,
|
||||
buffer - m_datawindow.min.x * xStride - m_datawindow.min.y * ystep + floatsize,
|
||||
xStride, ystep, 1, 1, 0.0 ));
|
||||
}
|
||||
if( m_red )
|
||||
{
|
||||
frame.insert( "R", Slice( m_type,
|
||||
buffer - m_datawindow.min.x * xStride - m_datawindow.min.y * ystep + (floatsize * 2),
|
||||
xStride, ystep, m_red->xSampling, m_red->ySampling, 0.0 ));
|
||||
xsample[2] = m_red->xSampling;
|
||||
}
|
||||
else
|
||||
{
|
||||
frame.insert( "R", Slice( m_type,
|
||||
buffer - m_datawindow.min.x * xStride - m_datawindow.min.y * ystep + (floatsize * 2),
|
||||
xStride, ystep, 1, 1, 0.0 ));
|
||||
}
|
||||
}
|
||||
|
||||
if( justcopy && m_hasalpha && alphasupported )
|
||||
{ // alpha preserved only in justcopy scenario where alpha is desired (alphasupported)
|
||||
// and present in original file (m_hasalpha)
|
||||
CV_Assert(channelstoread == img.channels());
|
||||
int offset = (channelstoread - 1) * floatsize;
|
||||
frame.insert( "A", Slice( m_type,
|
||||
buffer - m_datawindow.min.x * xStride - m_datawindow.min.y * ystep + offset,
|
||||
xStride, ystep, m_alpha->xSampling, m_alpha->ySampling, 0.0 ));
|
||||
}
|
||||
|
||||
for (FrameBuffer::Iterator it = frame.begin(); it != frame.end(); it++) {
|
||||
channels++;
|
||||
}
|
||||
|
||||
CV_Assert(channels == channelstoread);
|
||||
|
||||
if( (channels != channelstoread) || (!justcopy && channels > defaultchannels) )
|
||||
{ // safety checking what ought to be true here
|
||||
close();
|
||||
return false;
|
||||
}
|
||||
|
||||
m_file->setFrameBuffer( frame );
|
||||
if( justcopy )
|
||||
{
|
||||
m_file->readPixels( m_datawindow.min.y, m_datawindow.max.y );
|
||||
|
||||
if( !m_multispectral )
|
||||
{
|
||||
if( m_iscolor )
|
||||
{
|
||||
if (doReadRGB)
|
||||
{
|
||||
if( m_red && (m_red->xSampling != 1 || m_red->ySampling != 1) )
|
||||
UpSample( data, channelstoread, step / xstep, m_red->xSampling, m_red->ySampling );
|
||||
if( m_green && (m_green->xSampling != 1 || m_green->ySampling != 1) )
|
||||
UpSample( data + xstep, channelstoread, step / xstep, m_green->xSampling, m_green->ySampling );
|
||||
if( m_blue && (m_blue->xSampling != 1 || m_blue->ySampling != 1) )
|
||||
UpSample( data + 2 * xstep, channelstoread, step / xstep, m_blue->xSampling, m_blue->ySampling );
|
||||
}
|
||||
else
|
||||
{
|
||||
if( m_blue && (m_blue->xSampling != 1 || m_blue->ySampling != 1) )
|
||||
UpSample( data, channelstoread, step / xstep, m_blue->xSampling, m_blue->ySampling );
|
||||
if( m_green && (m_green->xSampling != 1 || m_green->ySampling != 1) )
|
||||
UpSample( data + xstep, channelstoread, step / xstep, m_green->xSampling, m_green->ySampling );
|
||||
if( m_red && (m_red->xSampling != 1 || m_red->ySampling != 1) )
|
||||
UpSample( data + 2 * xstep, channelstoread, step / xstep, m_red->xSampling, m_red->ySampling );
|
||||
}
|
||||
}
|
||||
else if( m_green && (m_green->xSampling != 1 || m_green->ySampling != 1) )
|
||||
UpSample( data, channelstoread, step / xstep, m_green->xSampling, m_green->ySampling );
|
||||
|
||||
if( chromatorgb )
|
||||
{
|
||||
if (doReadRGB)
|
||||
ChromaToRGB( (float *)data, m_height, channelstoread, step / xstep );
|
||||
else
|
||||
ChromaToBGR( (float *)data, m_height, channelstoread, step / xstep );
|
||||
}
|
||||
}
|
||||
}
|
||||
else // m_multispectral should be false
|
||||
{
|
||||
uchar *out = data;
|
||||
int x, y;
|
||||
for( y = m_datawindow.min.y; y <= m_datawindow.max.y; y++ )
|
||||
{
|
||||
m_file->readPixels( y, y );
|
||||
|
||||
for( int i = 0; i < channels; i++ )
|
||||
{
|
||||
if( xsample[i] != 1 )
|
||||
UpSampleX( (float *)buffer + i, channels, xsample[i] );
|
||||
}
|
||||
if( rgbtogray )
|
||||
{
|
||||
RGBToGray( (float *)buffer, (float *)out );
|
||||
}
|
||||
else
|
||||
{
|
||||
if( chromatorgb )
|
||||
{
|
||||
if (doReadRGB)
|
||||
ChromaToRGB( (float *)buffer, 1, defaultchannels, step );
|
||||
else
|
||||
ChromaToBGR( (float *)buffer, 1, defaultchannels, step );
|
||||
}
|
||||
|
||||
if( m_type == FLOAT )
|
||||
{
|
||||
float *fi = (float *)buffer;
|
||||
for( x = 0; x < m_width * img.channels(); x++)
|
||||
{
|
||||
out[x] = cv::saturate_cast<uchar>(fi[x]);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
unsigned *ui = (unsigned *)buffer;
|
||||
for( x = 0; x < m_width * img.channels(); x++)
|
||||
{
|
||||
out[x] = cv::saturate_cast<uchar>(ui[x]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
out += step;
|
||||
}
|
||||
if( color )
|
||||
{
|
||||
if (doReadRGB)
|
||||
{
|
||||
if( m_red && (m_red->xSampling != 1 || m_red->ySampling != 1) )
|
||||
UpSampleY( data, defaultchannels, step / xstep, m_red->ySampling );
|
||||
if( m_green && (m_green->xSampling != 1 || m_green->ySampling != 1) )
|
||||
UpSampleY( data + xstep, defaultchannels, step / xstep, m_green->ySampling );
|
||||
if( m_blue && (m_blue->xSampling != 1 || m_blue->ySampling != 1) )
|
||||
UpSampleY( data + 2 * xstep, defaultchannels, step / xstep, m_blue->ySampling );
|
||||
}
|
||||
else
|
||||
{
|
||||
if( m_blue && (m_blue->xSampling != 1 || m_blue->ySampling != 1) )
|
||||
UpSampleY( data, defaultchannels, step / xstep, m_blue->ySampling );
|
||||
if( m_green && (m_green->xSampling != 1 || m_green->ySampling != 1) )
|
||||
UpSampleY( data + xstep, defaultchannels, step / xstep, m_green->ySampling );
|
||||
if( m_red && (m_red->xSampling != 1 || m_red->ySampling != 1) )
|
||||
UpSampleY( data + 2 * xstep, defaultchannels, step / xstep, m_red->ySampling );
|
||||
}
|
||||
}
|
||||
else if( m_green && (m_green->xSampling != 1 || m_green->ySampling != 1) )
|
||||
UpSampleY( data, 1, step / xstep, m_green->ySampling );
|
||||
}
|
||||
|
||||
close();
|
||||
|
||||
if(doPostColorSwap)
|
||||
{
|
||||
cvtColor( img, img, cv::COLOR_BGR2RGB );
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
// on entry pixel values are stored packed in the upper left corner of the image
|
||||
// this functions expands them by duplication to cover the whole image
|
||||
*/
|
||||
void ExrDecoder::UpSample( uchar *data, int xstep, int ystep, int xsample, int ysample )
|
||||
{
|
||||
for( int y = (m_height - 1) / ysample, yre = m_height - ysample; y >= 0; y--, yre -= ysample )
|
||||
{
|
||||
for( int x = (m_width - 1) / xsample, xre = m_width - xsample; x >= 0; x--, xre -= xsample )
|
||||
{
|
||||
for( int i = 0; i < ysample; i++ )
|
||||
{
|
||||
for( int n = 0; n < xsample; n++ )
|
||||
{
|
||||
if( !m_native_depth )
|
||||
data[(yre + i) * ystep + (xre + n) * xstep] = data[y * ystep + x * xstep];
|
||||
else if( m_type == FLOAT )
|
||||
((float *)data)[(yre + i) * ystep + (xre + n) * xstep] = ((float *)data)[y * ystep + x * xstep];
|
||||
else
|
||||
((unsigned *)data)[(yre + i) * ystep + (xre + n) * xstep] = ((unsigned *)data)[y * ystep + x * xstep];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
// on entry pixel values are stored packed in the upper left corner of the image
|
||||
// this functions expands them by duplication to cover the whole image
|
||||
*/
|
||||
void ExrDecoder::UpSampleX( float *data, int xstep, int xsample )
|
||||
{
|
||||
for( int x = (m_width - 1) / xsample, xre = m_width - xsample; x >= 0; x--, xre -= xsample )
|
||||
{
|
||||
for( int n = 0; n < xsample; n++ )
|
||||
{
|
||||
if( m_type == FLOAT )
|
||||
((float *)data)[(xre + n) * xstep] = ((float *)data)[x * xstep];
|
||||
else
|
||||
((unsigned *)data)[(xre + n) * xstep] = ((unsigned *)data)[x * xstep];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
// on entry pixel values are stored packed in the upper left corner of the image
|
||||
// this functions expands them by duplication to cover the whole image
|
||||
*/
|
||||
void ExrDecoder::UpSampleY( uchar *data, int xstep, int ystep, int ysample )
|
||||
{
|
||||
for( int y = m_height - ysample, yre = m_height - ysample; y >= 0; y -= ysample, yre -= ysample )
|
||||
{
|
||||
for( int x = 0; x < m_width; x++ )
|
||||
{
|
||||
for( int i = 1; i < ysample; i++ )
|
||||
{
|
||||
if( !m_native_depth )
|
||||
data[(yre + i) * ystep + x * xstep] = data[y * ystep + x * xstep];
|
||||
else if( m_type == FLOAT )
|
||||
((float *)data)[(yre + i) * ystep + x * xstep] = ((float *)data)[y * ystep + x * xstep];
|
||||
else
|
||||
((unsigned *)data)[(yre + i) * ystep + x * xstep] = ((unsigned *)data)[y * ystep + x * xstep];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
// algorithm from ImfRgbaYca.cpp
|
||||
*/
|
||||
void ExrDecoder::ChromaToBGR( float *data, int numlines, int xstep, int ystep )
|
||||
{
|
||||
for( int y = 0; y < numlines; y++ )
|
||||
{
|
||||
for( int x = 0; x < m_width; x++ )
|
||||
{
|
||||
double b, Y, r;
|
||||
if( m_type == FLOAT )
|
||||
{
|
||||
b = data[y * ystep + x * xstep];
|
||||
Y = data[y * ystep + x * xstep + 1];
|
||||
r = data[y * ystep + x * xstep + 2];
|
||||
}
|
||||
else
|
||||
{
|
||||
b = ((unsigned *)data)[y * ystep + x * xstep];
|
||||
Y = ((unsigned *)data)[y * ystep + x * xstep + 1];
|
||||
r = ((unsigned *)data)[y * ystep + x * xstep + 2];
|
||||
}
|
||||
r = (r + 1) * Y;
|
||||
b = (b + 1) * Y;
|
||||
Y = (Y - b * m_chroma.blue[1] - r * m_chroma.red[1]) / m_chroma.green[1];
|
||||
|
||||
if( m_type == FLOAT )
|
||||
{
|
||||
data[y * ystep + x * xstep] = (float)b;
|
||||
data[y * ystep + x * xstep + 1] = (float)Y;
|
||||
data[y * ystep + x * xstep + 2] = (float)r;
|
||||
}
|
||||
else
|
||||
{
|
||||
int t = cvRound(b);
|
||||
((unsigned *)data)[y * ystep + x * xstep + 0] = (unsigned)MAX(t, 0);
|
||||
t = cvRound(Y);
|
||||
((unsigned *)data)[y * ystep + x * xstep + 1] = (unsigned)MAX(t, 0);
|
||||
t = cvRound(r);
|
||||
((unsigned *)data)[y * ystep + x * xstep + 2] = (unsigned)MAX(t, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ExrDecoder::ChromaToRGB(float *data, int numlines, int xstep, int ystep)
|
||||
{
|
||||
for( int y = 0; y < numlines; y++ )
|
||||
{
|
||||
for( int x = 0; x < m_width; x++ )
|
||||
{
|
||||
double b, Y, r;
|
||||
if( m_type == FLOAT )
|
||||
{
|
||||
b = data[y * ystep + x * xstep];
|
||||
Y = data[y * ystep + x * xstep + 1];
|
||||
r = data[y * ystep + x * xstep + 2];
|
||||
}
|
||||
else
|
||||
{
|
||||
b = ((unsigned *)data)[y * ystep + x * xstep];
|
||||
Y = ((unsigned *)data)[y * ystep + x * xstep + 1];
|
||||
r = ((unsigned *)data)[y * ystep + x * xstep + 2];
|
||||
}
|
||||
r = (r + 1) * Y;
|
||||
b = (b + 1) * Y;
|
||||
Y = (Y - b * m_chroma.blue[1] - r * m_chroma.red[1]) / m_chroma.green[1];
|
||||
|
||||
if( m_type == FLOAT )
|
||||
{
|
||||
data[y * ystep + x * xstep] = (float)r;
|
||||
data[y * ystep + x * xstep + 1] = (float)Y;
|
||||
data[y * ystep + x * xstep + 2] = (float)b;
|
||||
}
|
||||
else
|
||||
{
|
||||
int t = cvRound(r);
|
||||
((unsigned *)data)[y * ystep + x * xstep + 0] = (unsigned)MAX(t, 0);
|
||||
t = cvRound(Y);
|
||||
((unsigned *)data)[y * ystep + x * xstep + 1] = (unsigned)MAX(t, 0);
|
||||
t = cvRound(b);
|
||||
((unsigned *)data)[y * ystep + x * xstep + 2] = (unsigned)MAX(t, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
// convert one row to gray
|
||||
*/
|
||||
void ExrDecoder::RGBToGray( float *in, float *out )
|
||||
{
|
||||
if( m_type == FLOAT )
|
||||
{
|
||||
if( m_native_depth )
|
||||
{
|
||||
for( int i = 0, n = 0; i < m_width; i++, n += 3 )
|
||||
out[i] = in[n] * m_chroma.blue[0] + in[n + 1] * m_chroma.green[0] + in[n + 2] * m_chroma.red[0];
|
||||
}
|
||||
else
|
||||
{
|
||||
uchar *o = (uchar *)out;
|
||||
for( int i = 0, n = 0; i < m_width; i++, n += 3 )
|
||||
o[i] = (uchar) (in[n] * m_chroma.blue[0] + in[n + 1] * m_chroma.green[0] + in[n + 2] * m_chroma.red[0]);
|
||||
}
|
||||
}
|
||||
else // UINT
|
||||
{
|
||||
if( m_native_depth )
|
||||
{
|
||||
unsigned *ui = (unsigned *)in;
|
||||
for( int i = 0; i < m_width * 3; i++ )
|
||||
ui[i] -= 0x80000000;
|
||||
int *si = (int *)in;
|
||||
for( int i = 0, n = 0; i < m_width; i++, n += 3 )
|
||||
((int *)out)[i] = int(si[n] * m_chroma.blue[0] + si[n + 1] * m_chroma.green[0] + si[n + 2] * m_chroma.red[0]);
|
||||
}
|
||||
else // how to best convert float to uchar?
|
||||
{
|
||||
unsigned *ui = (unsigned *)in;
|
||||
for( int i = 0, n = 0; i < m_width; i++, n += 3 )
|
||||
((uchar *)out)[i] = uchar((ui[n] * m_chroma.blue[0] + ui[n + 1] * m_chroma.green[0] + ui[n + 2] * m_chroma.red[0]) * (256.0 / 4294967296.0));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
ImageDecoder ExrDecoder::newDecoder() const
|
||||
{
|
||||
initOpenEXR();
|
||||
return makePtr<ExrDecoder>();
|
||||
}
|
||||
|
||||
/////////////////////// ExrEncoder ///////////////////
|
||||
|
||||
|
||||
ExrEncoder::ExrEncoder()
|
||||
{
|
||||
m_description = "OpenEXR Image files (*.exr)";
|
||||
m_supported_encode_key = {IMWRITE_EXR_TYPE, IMWRITE_EXR_COMPRESSION, IMWRITE_EXR_DWA_COMPRESSION_LEVEL};
|
||||
}
|
||||
|
||||
|
||||
ExrEncoder::~ExrEncoder()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
bool ExrEncoder::isFormatSupported( int depth ) const
|
||||
{
|
||||
return ( CV_MAT_DEPTH(depth) == CV_32F );
|
||||
}
|
||||
|
||||
|
||||
bool ExrEncoder::write( const Mat& img, const std::vector<int>& params )
|
||||
{
|
||||
int width = img.cols, height = img.rows;
|
||||
int depth = img.depth();
|
||||
CV_Assert( depth == CV_32F );
|
||||
int channels = img.channels();
|
||||
bool result = false;
|
||||
Header header( width, height );
|
||||
Imf::PixelType type = FLOAT;
|
||||
|
||||
for( size_t i = 0; i < params.size(); i += 2 )
|
||||
{
|
||||
const int value = params[i+1];
|
||||
if( params[i] == IMWRITE_EXR_TYPE )
|
||||
{
|
||||
switch( value )
|
||||
{
|
||||
case IMWRITE_EXR_TYPE_HALF:
|
||||
type = HALF;
|
||||
break;
|
||||
case IMWRITE_EXR_TYPE_FLOAT:
|
||||
type = FLOAT;
|
||||
break;
|
||||
default:
|
||||
type = FLOAT;
|
||||
CV_LOG_WARNING(nullptr, cv::format("The value(%d) for IMWRITE_EXR_TYPE must be one of ImwriteEXRTypeFlags. It is fallbacked to IMWRITE_EXR_TYPE_FLOAT", value));
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ( params[i] == IMWRITE_EXR_COMPRESSION )
|
||||
{
|
||||
switch ( value )
|
||||
{
|
||||
case IMWRITE_EXR_COMPRESSION_NO:
|
||||
header.compression() = NO_COMPRESSION;
|
||||
break;
|
||||
case IMWRITE_EXR_COMPRESSION_RLE:
|
||||
header.compression() = RLE_COMPRESSION;
|
||||
break;
|
||||
case IMWRITE_EXR_COMPRESSION_ZIPS:
|
||||
header.compression() = ZIPS_COMPRESSION;
|
||||
break;
|
||||
case IMWRITE_EXR_COMPRESSION_ZIP:
|
||||
header.compression() = ZIP_COMPRESSION;
|
||||
break;
|
||||
case IMWRITE_EXR_COMPRESSION_PIZ:
|
||||
header.compression() = PIZ_COMPRESSION;
|
||||
break;
|
||||
case IMWRITE_EXR_COMPRESSION_PXR24:
|
||||
header.compression() = PXR24_COMPRESSION;
|
||||
break;
|
||||
case IMWRITE_EXR_COMPRESSION_B44:
|
||||
header.compression() = B44_COMPRESSION;
|
||||
break;
|
||||
case IMWRITE_EXR_COMPRESSION_B44A:
|
||||
header.compression() = B44A_COMPRESSION;
|
||||
break;
|
||||
// version macros introduced in openexr 2.0.1.
|
||||
// - https://github.com/AcademySoftwareFoundation/openexr/commit/60cdff8a6f5c4e25a374e5f366d6e9b4efd869b3#diff-c4bae0726aebe410e407db9abd406d9cf2684f82dd8a08f46d84e8b7c35cf22aR67
|
||||
#if defined(OPENEXR_VERSION_MAJOR) && defined(OPENEXR_VERSION_MINOR) && OPENEXR_VERSION_MAJOR * 1000 + OPENEXR_VERSION_MINOR >= 2 * 1000 + 2
|
||||
// available since version 2.2.0
|
||||
case IMWRITE_EXR_COMPRESSION_DWAA:
|
||||
header.compression() = DWAA_COMPRESSION;
|
||||
break;
|
||||
case IMWRITE_EXR_COMPRESSION_DWAB:
|
||||
header.compression() = DWAB_COMPRESSION;
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
header.compression() = ZIP_COMPRESSION;
|
||||
CV_LOG_WARNING(nullptr, cv::format("The value(%d) for IMWRITE_EXR_COMPRESSION must be one of ImwriteEXRCompressionFlags. It is fallbacked to IMWRITE_EXR_COMPRESSION_ZIP", value));
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (params[i] == IMWRITE_EXR_DWA_COMPRESSION_LEVEL)
|
||||
{
|
||||
#if !defined(OPENEXR_VERSION_MAJOR)
|
||||
CV_LOG_ONCE_WARNING(NULL, "Setting `IMWRITE_EXR_DWA_COMPRESSION_LEVEL` not supported in unknown OpenEXR version possibly prior to 2.0.1 (version 3 is required)");
|
||||
#elif OPENEXR_VERSION_MAJOR < 3
|
||||
CV_LOG_ONCE_WARNING(NULL, "Setting `IMWRITE_EXR_DWA_COMPRESSION_LEVEL` not supported in OpenEXR version " + std::to_string(OPENEXR_VERSION_MAJOR) + " (version 3 is required)");
|
||||
#else
|
||||
// See https://github.com/AcademySoftwareFoundation/openexr/blob/v3.3.5/src/lib/OpenEXR/ImfDwaCompressor.cpp#L85
|
||||
header.dwaCompressionLevel() = std::max(params[i + 1], 0);
|
||||
if(value < 0) {
|
||||
CV_LOG_WARNING(nullptr, cv::format("The value(%d) for IMWRITE_EXR_DWA_COMPRESSION_LEVEL must be 0 or more. It is fallbacked to 0", value));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
if( channels == 3 || channels == 4 )
|
||||
{
|
||||
header.channels().insert( "R", Channel( type ) );
|
||||
header.channels().insert( "G", Channel( type ) );
|
||||
header.channels().insert( "B", Channel( type ) );
|
||||
//printf("bunt\n");
|
||||
}
|
||||
else if( channels == 1 || channels == 2 )
|
||||
{
|
||||
header.channels().insert( "Y", Channel( type ) );
|
||||
//printf("gray\n");
|
||||
}
|
||||
else if( channels > 4 )
|
||||
{
|
||||
for ( int i = 0; i < channels; i++ )
|
||||
header.channels().insert( std::to_string(i), Channel( type ) );
|
||||
//printf("multi-channel\n");
|
||||
}
|
||||
|
||||
if( channels % 2 == 0 && channels <= 4)
|
||||
{ // even number of channels indicates Alpha
|
||||
header.channels().insert( "A", Channel( type ) );
|
||||
}
|
||||
|
||||
OutputFile file( m_filename.c_str(), header );
|
||||
|
||||
FrameBuffer frame;
|
||||
|
||||
char *buffer;
|
||||
size_t bufferstep;
|
||||
int size;
|
||||
Mat exrMat;
|
||||
if( type == HALF )
|
||||
{
|
||||
img.convertTo(exrMat, CV_16F);
|
||||
buffer = (char *)const_cast<uchar *>( exrMat.ptr() );
|
||||
bufferstep = exrMat.step;
|
||||
size = 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
buffer = (char *)const_cast<uchar *>( img.ptr() );
|
||||
bufferstep = img.step;
|
||||
size = 4;
|
||||
}
|
||||
|
||||
if( channels == 3 || channels == 4 )
|
||||
{
|
||||
frame.insert( "B", Slice( type, buffer, size * channels, bufferstep ));
|
||||
frame.insert( "G", Slice( type, buffer + size, size * channels, bufferstep ));
|
||||
frame.insert( "R", Slice( type, buffer + size * 2, size * channels, bufferstep ));
|
||||
}
|
||||
else if( channels == 1 || channels == 2 )
|
||||
frame.insert( "Y", Slice( type, buffer, size * channels, bufferstep ));
|
||||
else if( channels > 4 )
|
||||
{
|
||||
for ( int i = 0; i < channels; i++ )
|
||||
frame.insert( std::to_string(i), Slice( type, buffer + size * i, size * channels, bufferstep ));
|
||||
}
|
||||
|
||||
if( channels % 2 == 0 && channels <= 4 )
|
||||
{ // even channel count indicates Alpha channel
|
||||
frame.insert( "A", Slice( type, buffer + size * (channels - 1), size * channels, bufferstep ));
|
||||
}
|
||||
|
||||
file.setFrameBuffer( frame );
|
||||
|
||||
result = true;
|
||||
try
|
||||
{
|
||||
file.writePixels( height );
|
||||
}
|
||||
catch(...)
|
||||
{
|
||||
result = false;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
ImageEncoder ExrEncoder::newEncoder() const
|
||||
{
|
||||
initOpenEXR();
|
||||
return makePtr<ExrEncoder>();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
/* End of file. */
|
||||
@@ -0,0 +1,127 @@
|
||||
/*M///////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
|
||||
//
|
||||
// By downloading, copying, installing or using the software you agree to this license.
|
||||
// If you do not agree to this license, do not download, install,
|
||||
// copy or use the software.
|
||||
//
|
||||
//
|
||||
// License Agreement
|
||||
// For Open Source Computer Vision Library
|
||||
//
|
||||
// Copyright (C) 2000-2008, Intel Corporation, all rights reserved.
|
||||
// Copyright (C) 2009, Willow Garage Inc., all rights reserved.
|
||||
// Third party copyrights are property of their respective owners.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without modification,
|
||||
// are permitted provided that the following conditions are met:
|
||||
//
|
||||
// * Redistribution's of source code must retain the above copyright notice,
|
||||
// this list of conditions and the following disclaimer.
|
||||
//
|
||||
// * Redistribution's in binary form must reproduce the above copyright notice,
|
||||
// this list of conditions and the following disclaimer in the documentation
|
||||
// and/or other materials provided with the distribution.
|
||||
//
|
||||
// * The name of the copyright holders may not be used to endorse or promote products
|
||||
// derived from this software without specific prior written permission.
|
||||
//
|
||||
// This software is provided by the copyright holders and contributors "as is" and
|
||||
// any express or implied warranties, including, but not limited to, the implied
|
||||
// warranties of merchantability and fitness for a particular purpose are disclaimed.
|
||||
// In no event shall the Intel Corporation or contributors be liable for any direct,
|
||||
// indirect, incidental, special, exemplary, or consequential damages
|
||||
// (including, but not limited to, procurement of substitute goods or services;
|
||||
// loss of use, data, or profits; or business interruption) however caused
|
||||
// and on any theory of liability, whether in contract, strict liability,
|
||||
// or tort (including negligence or otherwise) arising in any way out of
|
||||
// the use of this software, even if advised of the possibility of such damage.
|
||||
//
|
||||
//M*/
|
||||
|
||||
#ifndef _GRFMT_EXR_H_
|
||||
#define _GRFMT_EXR_H_
|
||||
|
||||
#ifdef HAVE_OPENEXR
|
||||
|
||||
#if defined __GNUC__ && defined __APPLE__
|
||||
# pragma GCC diagnostic ignored "-Wshadow"
|
||||
#endif
|
||||
|
||||
#include <ImfChromaticities.h>
|
||||
#include <ImfInputFile.h>
|
||||
#include <ImfChannelList.h>
|
||||
#include <ImathBox.h>
|
||||
#include <ImfRgbaFile.h>
|
||||
#include "grfmt_base.hpp"
|
||||
|
||||
namespace cv
|
||||
{
|
||||
|
||||
using namespace Imf;
|
||||
using namespace Imath;
|
||||
|
||||
/* libpng version only */
|
||||
|
||||
class ExrDecoder CV_FINAL : public BaseImageDecoder
|
||||
{
|
||||
public:
|
||||
|
||||
ExrDecoder();
|
||||
~ExrDecoder() CV_OVERRIDE;
|
||||
|
||||
int type() const CV_OVERRIDE;
|
||||
bool readData( Mat& img ) CV_OVERRIDE;
|
||||
bool readHeader() CV_OVERRIDE;
|
||||
void close();
|
||||
|
||||
ImageDecoder newDecoder() const CV_OVERRIDE;
|
||||
|
||||
protected:
|
||||
void UpSample( uchar *data, int xstep, int ystep, int xsample, int ysample );
|
||||
void UpSampleX( float *data, int xstep, int xsample );
|
||||
void UpSampleY( uchar *data, int xstep, int ystep, int ysample );
|
||||
void ChromaToBGR( float *data, int numlines, int xstep, int ystep );
|
||||
void ChromaToRGB( float *data, int numlines, int xstep, int ystep );
|
||||
void RGBToGray( float *in, float *out );
|
||||
|
||||
InputFile *m_file;
|
||||
Imf::PixelType m_type;
|
||||
Box2i m_datawindow;
|
||||
bool m_ischroma;
|
||||
const Channel *m_red;
|
||||
const Channel *m_green;
|
||||
const Channel *m_blue;
|
||||
const Channel *m_alpha;
|
||||
Chromaticities m_chroma;
|
||||
int m_bit_depth;
|
||||
bool m_native_depth;
|
||||
bool m_iscolor;
|
||||
bool m_isfloat;
|
||||
bool m_hasalpha;
|
||||
bool m_multispectral;
|
||||
int m_channels;
|
||||
|
||||
private:
|
||||
ExrDecoder(const ExrDecoder &); // copy disabled
|
||||
ExrDecoder& operator=(const ExrDecoder &); // assign disabled
|
||||
};
|
||||
|
||||
|
||||
class ExrEncoder CV_FINAL : public BaseImageEncoder
|
||||
{
|
||||
public:
|
||||
ExrEncoder();
|
||||
~ExrEncoder() CV_OVERRIDE;
|
||||
|
||||
bool isFormatSupported( int depth ) const CV_OVERRIDE;
|
||||
bool write( const Mat& img, const std::vector<int>& params ) CV_OVERRIDE;
|
||||
ImageEncoder newEncoder() const CV_OVERRIDE;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#endif/*_GRFMT_EXR_H_*/
|
||||
@@ -0,0 +1,585 @@
|
||||
/*M///////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
|
||||
//
|
||||
// By downloading, copying, installing or using the software you agree to this license.
|
||||
// If you do not agree to this license, do not download, install,
|
||||
// copy or use the software.
|
||||
//
|
||||
//
|
||||
// Intel License Agreement
|
||||
// For Open Source Computer Vision Library
|
||||
//
|
||||
// Copyright (C) 2000, Intel Corporation, all rights reserved.
|
||||
// Third party copyrights are property of their respective owners.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without modification,
|
||||
// are permitted provided that the following conditions are met:
|
||||
//
|
||||
// * Redistribution's of source code must retain the above copyright notice,
|
||||
// this list of conditions and the following disclaimer.
|
||||
//
|
||||
// * Redistribution's in binary form must reproduce the above copyright notice,
|
||||
// this list of conditions and the following disclaimer in the documentation
|
||||
// and/or other materials provided with the distribution.
|
||||
//
|
||||
// * The name of Intel Corporation may not be used to endorse or promote products
|
||||
// derived from this software without specific prior written permission.
|
||||
//
|
||||
// This software is provided by the copyright holders and contributors "as is" and
|
||||
// any express or implied warranties, including, but not limited to, the implied
|
||||
// warranties of merchantability and fitness for a particular purpose are disclaimed.
|
||||
// In no event shall the Intel Corporation or contributors be liable for any direct,
|
||||
// indirect, incidental, special, exemplary, or consequential damages
|
||||
// (including, but not limited to, procurement of substitute goods or services;
|
||||
// loss of use, data, or profits; or business interruption) however caused
|
||||
// and on any theory of liability, whether in contract, strict liability,
|
||||
// or tort (including negligence or otherwise) arising in any way out of
|
||||
// the use of this software, even if advised of the possibility of such damage.
|
||||
//
|
||||
//M*/
|
||||
#include "precomp.hpp"
|
||||
|
||||
// GDAL Macros
|
||||
#include "cvconfig.h"
|
||||
|
||||
#ifdef HAVE_GDAL
|
||||
|
||||
// Our Header
|
||||
#include "grfmt_gdal.hpp"
|
||||
|
||||
|
||||
/// C++ Standard Libraries
|
||||
#include <iostream>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
|
||||
|
||||
namespace cv{
|
||||
|
||||
|
||||
/**
|
||||
* Convert GDAL Palette Interpretation to OpenCV Pixel Type
|
||||
*/
|
||||
int gdalPaletteInterpretation2OpenCV( GDALPaletteInterp const& paletteInterp, GDALDataType const& gdalType ){
|
||||
|
||||
switch( paletteInterp ){
|
||||
|
||||
/// GRAYSCALE
|
||||
case GPI_Gray:
|
||||
if( gdalType == GDT_Byte ){ return CV_8UC1; }
|
||||
if( gdalType == GDT_UInt16 ){ return CV_16UC1; }
|
||||
if( gdalType == GDT_Int16 ){ return CV_16SC1; }
|
||||
if( gdalType == GDT_UInt32 ){ return CV_32SC1; }
|
||||
if( gdalType == GDT_Int32 ){ return CV_32SC1; }
|
||||
if( gdalType == GDT_Float32 ){ return CV_32FC1; }
|
||||
if( gdalType == GDT_Float64 ){ return CV_64FC1; }
|
||||
return -1;
|
||||
|
||||
/// RGB
|
||||
case GPI_RGB:
|
||||
if( gdalType == GDT_Byte ){ return CV_8UC3; }
|
||||
if( gdalType == GDT_UInt16 ){ return CV_16UC3; }
|
||||
if( gdalType == GDT_Int16 ){ return CV_16SC3; }
|
||||
if( gdalType == GDT_UInt32 ){ return CV_32SC3; }
|
||||
if( gdalType == GDT_Int32 ){ return CV_32SC3; }
|
||||
if( gdalType == GDT_Float32 ){ return CV_32FC3; }
|
||||
if( gdalType == GDT_Float64 ){ return CV_64FC3; }
|
||||
return -1;
|
||||
|
||||
|
||||
/// otherwise
|
||||
default:
|
||||
return -1;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert gdal type to opencv type
|
||||
*/
|
||||
int gdal2opencv( const GDALDataType& gdalType, const int& channels ){
|
||||
|
||||
switch( gdalType ){
|
||||
|
||||
/// UInt8
|
||||
case GDT_Byte:
|
||||
return CV_8UC(channels);
|
||||
|
||||
/// UInt16
|
||||
case GDT_UInt16:
|
||||
return CV_16UC(channels);
|
||||
|
||||
/// Int16
|
||||
case GDT_Int16:
|
||||
return CV_16SC(channels);
|
||||
|
||||
/// UInt32
|
||||
case GDT_UInt32:
|
||||
case GDT_Int32:
|
||||
return CV_32SC(channels);
|
||||
|
||||
case GDT_Float32:
|
||||
return CV_32FC(channels);
|
||||
|
||||
case GDT_Float64:
|
||||
return CV_64FC(channels);
|
||||
|
||||
default:
|
||||
std::cout << "Unknown GDAL Data Type" << std::endl;
|
||||
std::cout << "Type: " << GDALGetDataTypeName(gdalType) << std::endl;
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* GDAL Decoder Constructor
|
||||
*/
|
||||
GdalDecoder::GdalDecoder(){
|
||||
|
||||
// set a dummy signature
|
||||
m_signature="0";
|
||||
for( size_t i=0; i<160; i++ ){
|
||||
m_signature += "0";
|
||||
}
|
||||
|
||||
/// Register the driver
|
||||
GDALAllRegister();
|
||||
|
||||
m_driver = NULL;
|
||||
m_dataset = NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* GDAL Decoder Destructor
|
||||
*/
|
||||
GdalDecoder::~GdalDecoder(){
|
||||
|
||||
if( m_dataset != NULL ){
|
||||
close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert data range
|
||||
*/
|
||||
double range_cast( const GDALDataType& gdalType,
|
||||
const int& cvDepth,
|
||||
const double& value )
|
||||
{
|
||||
|
||||
// uint8 -> uint8
|
||||
if( gdalType == GDT_Byte && cvDepth == CV_8U ){
|
||||
return value;
|
||||
}
|
||||
// uint8 -> uint16
|
||||
if( gdalType == GDT_Byte && (cvDepth == CV_16U || cvDepth == CV_16S)){
|
||||
return (value*256);
|
||||
}
|
||||
|
||||
// uint8 -> uint32
|
||||
if( gdalType == GDT_Byte && (cvDepth == CV_32F || cvDepth == CV_32S)){
|
||||
return (value*16777216);
|
||||
}
|
||||
|
||||
// int16 -> uint8
|
||||
if( (gdalType == GDT_UInt16 || gdalType == GDT_Int16) && cvDepth == CV_8U ){
|
||||
return std::floor(value/256.0);
|
||||
}
|
||||
|
||||
// int16 -> int16
|
||||
if( (gdalType == GDT_UInt16 || gdalType == GDT_Int16) &&
|
||||
( cvDepth == CV_16U || cvDepth == CV_16S )){
|
||||
return value;
|
||||
}
|
||||
|
||||
// float32 -> float32
|
||||
// float64 -> float64
|
||||
if( (gdalType == GDT_Float32 || gdalType == GDT_Float64) &&
|
||||
( cvDepth == CV_32F || cvDepth == CV_64F )){
|
||||
return value;
|
||||
}
|
||||
|
||||
std::cout << GDALGetDataTypeName( gdalType ) << std::endl;
|
||||
std::cout << "warning: unknown range cast requested." << std::endl;
|
||||
return (value);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* There are some better mpl techniques for doing this.
|
||||
*/
|
||||
void write_pixel( const double& pixelValue,
|
||||
const GDALDataType& gdalType,
|
||||
const int& gdalChannels,
|
||||
Mat& image,
|
||||
const int& row,
|
||||
const int& col,
|
||||
const int& channel ){
|
||||
|
||||
// convert the pixel
|
||||
double newValue = range_cast(gdalType, image.depth(), pixelValue );
|
||||
|
||||
// input: 1 channel, output: 1 channel
|
||||
if( gdalChannels == 1 && image.channels() == 1 ){
|
||||
if( image.depth() == CV_8U ){ image.ptr<uchar>(row)[col] = newValue; }
|
||||
else if( image.depth() == CV_16U ){ image.ptr<unsigned short>(row)[col] = newValue; }
|
||||
else if( image.depth() == CV_16S ){ image.ptr<short>(row)[col] = newValue; }
|
||||
else if( image.depth() == CV_32S ){ image.ptr<int>(row)[col] = newValue; }
|
||||
else if( image.depth() == CV_32F ){ image.ptr<float>(row)[col] = newValue; }
|
||||
else if( image.depth() == CV_64F ){ image.ptr<double>(row)[col] = newValue; }
|
||||
else{ throw std::runtime_error("Unknown image depth, gdal: 1, img: 1"); }
|
||||
}
|
||||
|
||||
// input: 1 channel, output: 3 channel
|
||||
else if( gdalChannels == 1 && image.channels() == 3 ){
|
||||
if( image.depth() == CV_8U ){ image.ptr<Vec3b>(row)[col] = Vec3b(newValue,newValue,newValue); }
|
||||
else if( image.depth() == CV_16U ){ image.ptr<Vec3s>(row)[col] = Vec3s(newValue,newValue,newValue); }
|
||||
else if( image.depth() == CV_16S ){ image.ptr<Vec3s>(row)[col] = Vec3s(newValue,newValue,newValue); }
|
||||
else if( image.depth() == CV_32S ){ image.ptr<Vec3i>(row)[col] = Vec3i(newValue,newValue,newValue); }
|
||||
else if( image.depth() == CV_32F ){ image.ptr<Vec3f>(row)[col] = Vec3f(newValue,newValue,newValue); }
|
||||
else if( image.depth() == CV_64F ){ image.ptr<Vec3d>(row)[col] = Vec3d(newValue,newValue,newValue); }
|
||||
else{ throw std::runtime_error("Unknown image depth, gdal:1, img: 3"); }
|
||||
}
|
||||
|
||||
// input: 3 channel, output: 1 channel
|
||||
else if( gdalChannels == 3 && image.channels() == 1 ){
|
||||
if( image.depth() == CV_8U ){ image.ptr<uchar>(row)[col] += (newValue/3.0); }
|
||||
else{ throw std::runtime_error("Unknown image depth, gdal:3, img: 1"); }
|
||||
}
|
||||
|
||||
// input: 4 channel, output: 1 channel
|
||||
else if( gdalChannels == 4 && image.channels() == 1 ){
|
||||
if( image.depth() == CV_8U ){ image.ptr<uchar>(row)[col] = newValue; }
|
||||
else{ throw std::runtime_error("Unknown image depth, gdal: 4, image: 1"); }
|
||||
}
|
||||
|
||||
// input: 3 channel, output: 3 channel
|
||||
else if( gdalChannels == 3 && image.channels() == 3 ){
|
||||
if( image.depth() == CV_8U ){ (*image.ptr<Vec3b>(row,col))[channel] = newValue; }
|
||||
else if( image.depth() == CV_16U ){ (*image.ptr<Vec3s>(row,col))[channel] = newValue; }
|
||||
else if( image.depth() == CV_16S ){ (*image.ptr<Vec3s>(row,col))[channel] = newValue; }
|
||||
else if( image.depth() == CV_32S ){ (*image.ptr<Vec3i>(row,col))[channel] = newValue; }
|
||||
else if( image.depth() == CV_32F ){ (*image.ptr<Vec3f>(row,col))[channel] = newValue; }
|
||||
else if( image.depth() == CV_64F ){ (*image.ptr<Vec3d>(row,col))[channel] = newValue; }
|
||||
else{ throw std::runtime_error("Unknown image depth, gdal: 3, image: 3"); }
|
||||
}
|
||||
|
||||
// input: 4 channel, output: 3 channel
|
||||
else if( gdalChannels == 4 && image.channels() == 3 ){
|
||||
if( channel >= 4 ){ return; }
|
||||
else if( image.depth() == CV_8U && channel < 4 ){ (*image.ptr<Vec3b>(row,col))[channel] = newValue; }
|
||||
else if( image.depth() == CV_16U && channel < 4 ){ (*image.ptr<Vec3s>(row,col))[channel] = newValue; }
|
||||
else if( image.depth() == CV_16S && channel < 4 ){ (*image.ptr<Vec3s>(row,col))[channel] = newValue; }
|
||||
else if( image.depth() == CV_32S && channel < 4 ){ (*image.ptr<Vec3i>(row,col))[channel] = newValue; }
|
||||
else if( image.depth() == CV_32F && channel < 4 ){ (*image.ptr<Vec3f>(row,col))[channel] = newValue; }
|
||||
else if( image.depth() == CV_64F && channel < 4 ){ (*image.ptr<Vec3d>(row,col))[channel] = newValue; }
|
||||
else{ throw std::runtime_error("Unknown image depth, gdal: 4, image: 3"); }
|
||||
}
|
||||
|
||||
// input: 4 channel, output: 4 channel
|
||||
else if( gdalChannels == 4 && image.channels() == 4 ){
|
||||
if( image.depth() == CV_8U ){ (*image.ptr<Vec4b>(row,col))[channel] = newValue; }
|
||||
else if( image.depth() == CV_16U ){ (*image.ptr<Vec4s>(row,col))[channel] = newValue; }
|
||||
else if( image.depth() == CV_16S ){ (*image.ptr<Vec4s>(row,col))[channel] = newValue; }
|
||||
else if( image.depth() == CV_32S ){ (*image.ptr<Vec4i>(row,col))[channel] = newValue; }
|
||||
else if( image.depth() == CV_32F ){ (*image.ptr<Vec4f>(row,col))[channel] = newValue; }
|
||||
else if( image.depth() == CV_64F ){ (*image.ptr<Vec4d>(row,col))[channel] = newValue; }
|
||||
else{ throw std::runtime_error("Unknown image depth, gdal: 4, image: 4"); }
|
||||
}
|
||||
|
||||
// input: > 4 channels, output: > 4 channels
|
||||
else if( gdalChannels > 4 && image.channels() > 4 ){
|
||||
if( image.depth() == CV_8U ){ image.ptr<uchar>(row,col)[channel] = newValue; }
|
||||
else if( image.depth() == CV_16U ){ image.ptr<unsigned short>(row,col)[channel] = newValue; }
|
||||
else if( image.depth() == CV_16S ){ image.ptr<short>(row,col)[channel] = newValue; }
|
||||
else if( image.depth() == CV_32S ){ image.ptr<int>(row,col)[channel] = newValue; }
|
||||
else if( image.depth() == CV_32F ){ image.ptr<float>(row,col)[channel] = newValue; }
|
||||
else if( image.depth() == CV_64F ){ image.ptr<double>(row,col)[channel] = newValue; }
|
||||
else{ throw std::runtime_error("Unknown image depth, gdal: N, img: N"); }
|
||||
}
|
||||
// otherwise, throw an error
|
||||
else{
|
||||
throw std::runtime_error("error: can't convert types.");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
void write_ctable_pixel( const double& pixelValue,
|
||||
const GDALDataType& gdalType,
|
||||
GDALColorTable const* gdalColorTable,
|
||||
Mat& image,
|
||||
const int& y,
|
||||
const int& x,
|
||||
const int& c ){
|
||||
|
||||
if( gdalColorTable == NULL ){
|
||||
write_pixel( pixelValue, gdalType, 1, image, y, x, c );
|
||||
}
|
||||
|
||||
// if we are Grayscale, then do a straight conversion
|
||||
if( gdalColorTable->GetPaletteInterpretation() == GPI_Gray ){
|
||||
write_pixel( pixelValue, gdalType, 1, image, y, x, c );
|
||||
}
|
||||
|
||||
// if we are rgb, then convert here
|
||||
else if( gdalColorTable->GetPaletteInterpretation() == GPI_RGB ){
|
||||
|
||||
// get the pixel
|
||||
short r = gdalColorTable->GetColorEntry( (int)pixelValue )->c1;
|
||||
short g = gdalColorTable->GetColorEntry( (int)pixelValue )->c2;
|
||||
short b = gdalColorTable->GetColorEntry( (int)pixelValue )->c3;
|
||||
short a = gdalColorTable->GetColorEntry( (int)pixelValue )->c4;
|
||||
|
||||
write_pixel( r, gdalType, 4, image, y, x, 2 );
|
||||
write_pixel( g, gdalType, 4, image, y, x, 1 );
|
||||
write_pixel( b, gdalType, 4, image, y, x, 0 );
|
||||
if( image.channels() > 3 ){
|
||||
write_pixel( a, gdalType, 4, image, y, x, 1 );
|
||||
}
|
||||
}
|
||||
|
||||
// otherwise, set zeros
|
||||
else{
|
||||
write_pixel( pixelValue, gdalType, 1, image, y, x, c );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* read data
|
||||
*/
|
||||
bool GdalDecoder::readData( Mat& img ){
|
||||
|
||||
|
||||
// make sure the image is the proper size
|
||||
if( img.size() != Size(m_width, m_height) ){
|
||||
return false;
|
||||
}
|
||||
|
||||
// make sure the raster is alive
|
||||
if( m_dataset == NULL || m_driver == NULL ){
|
||||
return false;
|
||||
}
|
||||
|
||||
// set the image to zero
|
||||
img = 0;
|
||||
|
||||
// iterate over each raster band
|
||||
// note that OpenCV does bgr rather than rgb
|
||||
int nChannels = m_dataset->GetRasterCount();
|
||||
|
||||
GDALColorTable* gdalColorTable = NULL;
|
||||
if( m_dataset->GetRasterBand(1)->GetColorTable() != NULL ){
|
||||
gdalColorTable = m_dataset->GetRasterBand(1)->GetColorTable();
|
||||
}
|
||||
|
||||
const GDALDataType gdalType = m_dataset->GetRasterBand(1)->GetRasterDataType();
|
||||
int nRows, nCols;
|
||||
|
||||
if( nChannels > img.channels() ){
|
||||
nChannels = img.channels();
|
||||
}
|
||||
|
||||
for( int c = 0; c<nChannels; c++ ){
|
||||
|
||||
// get the GDAL Band
|
||||
GDALRasterBand* band = m_dataset->GetRasterBand(c+1);
|
||||
|
||||
/* Map palette band and gray band to color index 0 and red, green,
|
||||
blue, alpha bands to BGRA indexes. Note: ignoring HSL, CMY,
|
||||
CMYK, and YCbCr color spaces, rather than converting them
|
||||
to BGR. */
|
||||
int color = 0;
|
||||
switch (band->GetColorInterpretation()) {
|
||||
case GCI_PaletteIndex:
|
||||
case GCI_GrayIndex:
|
||||
case GCI_BlueBand:
|
||||
color = m_use_rgb ? 2 : 0;
|
||||
break;
|
||||
case GCI_GreenBand:
|
||||
color = 1;
|
||||
break;
|
||||
case GCI_RedBand:
|
||||
color = m_use_rgb ? 0 : 2;
|
||||
break;
|
||||
case GCI_AlphaBand:
|
||||
color = 3;
|
||||
break;
|
||||
case GCI_Undefined:
|
||||
color = c;
|
||||
break;
|
||||
default:
|
||||
CV_Error(cv::Error::StsError, "Invalid/unsupported mode");
|
||||
}
|
||||
|
||||
// make sure the image band has the same dimensions as the image
|
||||
if( band->GetXSize() != m_width || band->GetYSize() != m_height ){ return false; }
|
||||
|
||||
// grab the raster size
|
||||
nRows = band->GetYSize();
|
||||
nCols = band->GetXSize();
|
||||
|
||||
// create a temporary scanline pointer to store data
|
||||
double* scanline = new double[nCols];
|
||||
|
||||
#if GDAL_VERSION_NUM < GDAL_COMPUTE_VERSION(3,3,0)
|
||||
// FITS drivers on version GDAL prior to v3.3.0 return vertically mirrored results.
|
||||
// See https://github.com/OSGeo/gdal/pull/3520
|
||||
// See https://github.com/OSGeo/gdal/commit/ef0f86696d163e065943b27f50dcff77790a1311
|
||||
const bool isNeedVerticallyFlip = strncmp(m_dataset->GetDriverName(), "FITS", 4) == 0;
|
||||
#else
|
||||
const bool isNeedVerticallyFlip = false;
|
||||
#endif
|
||||
|
||||
// iterate over each row and column
|
||||
for( int y=0; y<nRows; y++ ){ // for GDAL
|
||||
const int yCv = isNeedVerticallyFlip ? (nRows - 1) - y : y ; // for OpenCV
|
||||
|
||||
// get the entire row
|
||||
CPLErr err = band->RasterIO( GF_Read, 0, y, nCols, 1, scanline, nCols, 1, GDT_Float64, 0, 0);
|
||||
CV_Assert(err == CE_None);
|
||||
|
||||
// set inside the image
|
||||
for( int x=0; x<nCols; x++ ){
|
||||
|
||||
// set depending on image types
|
||||
// given boost, I would use enable_if to speed up. Avoid for now.
|
||||
if( hasColorTable == false ){
|
||||
write_pixel( scanline[x], gdalType, nChannels, img, yCv, x, color );
|
||||
}
|
||||
else{
|
||||
write_ctable_pixel( scanline[x], gdalType, gdalColorTable, img, yCv, x, color );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// delete our temp pointer
|
||||
delete [] scanline;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Read image header
|
||||
*/
|
||||
bool GdalDecoder::readHeader(){
|
||||
|
||||
// load the dataset
|
||||
m_dataset = (GDALDataset*) GDALOpen( m_filename.c_str(), GA_ReadOnly);
|
||||
|
||||
// if dataset is null, then there was a problem
|
||||
if( m_dataset == NULL ){
|
||||
return false;
|
||||
}
|
||||
|
||||
// make sure we have pixel data inside the raster
|
||||
if( m_dataset->GetRasterCount() <= 0 ){
|
||||
return false;
|
||||
}
|
||||
|
||||
//extract the driver information
|
||||
m_driver = m_dataset->GetDriver();
|
||||
|
||||
// if the driver failed, then exit
|
||||
if( m_driver == NULL ){
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// get the image dimensions
|
||||
m_width = m_dataset->GetRasterXSize();
|
||||
m_height= m_dataset->GetRasterYSize();
|
||||
|
||||
// make sure we have at least one band/channel
|
||||
if( m_dataset->GetRasterCount() <= 0 ){
|
||||
return false;
|
||||
}
|
||||
|
||||
// check if we have a color palette
|
||||
int tempType;
|
||||
if( m_dataset->GetRasterBand(1)->GetColorInterpretation() == GCI_PaletteIndex ){
|
||||
|
||||
// remember that we have a color palette
|
||||
hasColorTable = true;
|
||||
|
||||
// if the color tables does not exist, then we failed
|
||||
if( m_dataset->GetRasterBand(1)->GetColorTable() == NULL ){
|
||||
return false;
|
||||
}
|
||||
|
||||
// otherwise, get the pixeltype
|
||||
else{
|
||||
// convert the palette interpretation to opencv type
|
||||
tempType = gdalPaletteInterpretation2OpenCV( m_dataset->GetRasterBand(1)->GetColorTable()->GetPaletteInterpretation(),
|
||||
m_dataset->GetRasterBand(1)->GetRasterDataType() );
|
||||
|
||||
if( tempType == -1 ){
|
||||
return false;
|
||||
}
|
||||
m_type = tempType;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// otherwise, we have standard channels
|
||||
else{
|
||||
|
||||
// remember that we don't have a color table
|
||||
hasColorTable = false;
|
||||
|
||||
// convert the datatype to opencv
|
||||
tempType = gdal2opencv( m_dataset->GetRasterBand(1)->GetRasterDataType(), m_dataset->GetRasterCount() );
|
||||
if( tempType == -1 ){
|
||||
return false;
|
||||
}
|
||||
m_type = tempType;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the module
|
||||
*/
|
||||
void GdalDecoder::close(){
|
||||
|
||||
|
||||
GDALClose((GDALDatasetH)m_dataset);
|
||||
m_dataset = NULL;
|
||||
m_driver = NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new decoder
|
||||
*/
|
||||
ImageDecoder GdalDecoder::newDecoder()const{
|
||||
return makePtr<GdalDecoder>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the file signature
|
||||
*/
|
||||
bool GdalDecoder::checkSignature( const String& signature )const{
|
||||
|
||||
// look for NITF
|
||||
std::string str(signature);
|
||||
if( str.substr(0,4).find("NITF") != std::string::npos ){
|
||||
return true;
|
||||
}
|
||||
|
||||
// look for DTED
|
||||
if( str.size() > 144 && str.substr(140,4) == "DTED" ){
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
} /// End of cv Namespace
|
||||
|
||||
#endif /**< End of HAVE_GDAL Definition */
|
||||
@@ -0,0 +1,166 @@
|
||||
/*M///////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
|
||||
//
|
||||
// By downloading, copying, installing or using the software you agree to this license.
|
||||
// If you do not agree to this license, do not download, install,
|
||||
// copy or use the software.
|
||||
//
|
||||
//
|
||||
// Intel License Agreement
|
||||
// For Open Source Computer Vision Library
|
||||
//
|
||||
// Copyright (C) 2000, Intel Corporation, all rights reserved.
|
||||
// Third party copyrights are property of their respective owners.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without modification,
|
||||
// are permitted provided that the following conditions are met:
|
||||
//
|
||||
// * Redistribution's of source code must retain the above copyright notice,
|
||||
// this list of conditions and the following disclaimer.
|
||||
//
|
||||
// * Redistribution's in binary form must reproduce the above copyright notice,
|
||||
// this list of conditions and the following disclaimer in the documentation
|
||||
// and/or other materials provided with the distribution.
|
||||
//
|
||||
// * The name of Intel Corporation may not be used to endorse or promote products
|
||||
// derived from this software without specific prior written permission.
|
||||
//
|
||||
// This software is provided by the copyright holders and contributors "as is" and
|
||||
// any express or implied warranties, including, but not limited to, the implied
|
||||
// warranties of merchantability and fitness for a particular purpose are disclaimed.
|
||||
// In no event shall the Intel Corporation or contributors be liable for any direct,
|
||||
// indirect, incidental, special, exemplary, or consequential damages
|
||||
// (including, but not limited to, procurement of substitute goods or services;
|
||||
// loss of use, data, or profits; or business interruption) however caused
|
||||
// and on any theory of liability, whether in contract, strict liability,
|
||||
// or tort (including negligence or otherwise) arising in any way out of
|
||||
// the use of this software, even if advised of the possibility of such damage.
|
||||
//
|
||||
//M*/
|
||||
|
||||
#ifndef __GRFMT_GDAL_HPP__
|
||||
#define __GRFMT_GDAL_HPP__
|
||||
|
||||
/// OpenCV FMT Base Type
|
||||
#include "grfmt_base.hpp"
|
||||
|
||||
/// Macro to make sure we specified GDAL in CMake
|
||||
#ifdef HAVE_GDAL
|
||||
|
||||
/// C++ Libraries
|
||||
#include <iostream>
|
||||
|
||||
/// Geospatial Data Abstraction Library
|
||||
#include <cpl_conv.h>
|
||||
#include <gdal_priv.h>
|
||||
#include <gdal.h>
|
||||
|
||||
|
||||
/// Start of CV Namespace
|
||||
namespace cv {
|
||||
|
||||
/**
|
||||
* Convert GDAL Pixel Range to OpenCV Pixel Range
|
||||
*/
|
||||
double range_cast( const GDALDataType& gdalType,
|
||||
const int& cvDepth,
|
||||
const double& value );
|
||||
|
||||
/**
|
||||
* Convert GDAL Palette Interpretation to OpenCV Pixel Type
|
||||
*/
|
||||
int gdalPaletteInterpretation2OpenCV( GDALPaletteInterp const& paletteInterp,
|
||||
GDALDataType const& gdalType );
|
||||
|
||||
/**
|
||||
* Convert a GDAL Raster Type to OpenCV Type
|
||||
*/
|
||||
int gdal2opencv( const GDALDataType& gdalType, const int& channels );
|
||||
|
||||
/**
|
||||
* Write an image to pixel
|
||||
*/
|
||||
void write_pixel( const double& pixelValue,
|
||||
GDALDataType const& gdalType,
|
||||
const int& gdalChannels,
|
||||
Mat& image,
|
||||
const int& row,
|
||||
const int& col,
|
||||
const int& channel );
|
||||
|
||||
/**
|
||||
* Write a color table pixel to the image
|
||||
*/
|
||||
void write_ctable_pixel( const double& pixelValue,
|
||||
const GDALDataType& gdalType,
|
||||
const GDALColorTable* gdalColorTable,
|
||||
Mat& image,
|
||||
const int& y,
|
||||
const int& x,
|
||||
const int& c );
|
||||
|
||||
/**
|
||||
* Loader for GDAL
|
||||
*/
|
||||
class GdalDecoder CV_FINAL : public BaseImageDecoder{
|
||||
|
||||
public:
|
||||
|
||||
/**
|
||||
* Default Constructor
|
||||
*/
|
||||
GdalDecoder();
|
||||
|
||||
/**
|
||||
* Destructor
|
||||
*/
|
||||
~GdalDecoder() CV_OVERRIDE;
|
||||
|
||||
/**
|
||||
* Read image data
|
||||
*/
|
||||
bool readData( Mat& img ) CV_OVERRIDE;
|
||||
|
||||
/**
|
||||
* Read the image header
|
||||
*/
|
||||
bool readHeader() CV_OVERRIDE;
|
||||
|
||||
/**
|
||||
* Close the module
|
||||
*/
|
||||
void close();
|
||||
|
||||
/**
|
||||
* Create a new decoder
|
||||
*/
|
||||
ImageDecoder newDecoder() const CV_OVERRIDE;
|
||||
|
||||
/**
|
||||
* Test the file signature
|
||||
*
|
||||
* In general, this should be avoided as the user should specifically request GDAL.
|
||||
* The reason is that GDAL tends to overlap with other image formats and it is probably
|
||||
* safer to use other formats first.
|
||||
*/
|
||||
virtual bool checkSignature( const String& signature ) const CV_OVERRIDE;
|
||||
|
||||
protected:
|
||||
|
||||
/// GDAL Dataset
|
||||
GDALDataset* m_dataset;
|
||||
|
||||
/// GDAL Driver
|
||||
GDALDriver* m_driver;
|
||||
|
||||
/// Check if we are reading from a color table
|
||||
bool hasColorTable;
|
||||
|
||||
}; /// End of GdalDecoder Class
|
||||
|
||||
} /// End of Namespace cv
|
||||
|
||||
#endif/*HAVE_GDAL*/
|
||||
|
||||
#endif/*__GRFMT_GDAL_HPP__*/
|
||||
@@ -0,0 +1,197 @@
|
||||
/*M///////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
|
||||
//
|
||||
// By downloading, copying, installing or using the software you agree to this license.
|
||||
// If you do not agree to this license, do not download, install,
|
||||
// copy or use the software.
|
||||
//
|
||||
//
|
||||
// License Agreement
|
||||
// For Open Source Computer Vision Library
|
||||
//
|
||||
// Copyright (C) 2000-2008, Intel Corporation, all rights reserved.
|
||||
// Copyright (C) 2009, Willow Garage Inc., all rights reserved.
|
||||
// Third party copyrights are property of their respective owners.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without modification,
|
||||
// are permitted provided that the following conditions are met:
|
||||
//
|
||||
// * Redistribution's of source code must retain the above copyright notice,
|
||||
// this list of conditions and the following disclaimer.
|
||||
//
|
||||
// * Redistribution's in binary form must reproduce the above copyright notice,
|
||||
// this list of conditions and the following disclaimer in the documentation
|
||||
// and/or other materials provided with the distribution.
|
||||
//
|
||||
// * The name of the copyright holders may not be used to endorse or promote products
|
||||
// derived from this software without specific prior written permission.
|
||||
//
|
||||
// This software is provided by the copyright holders and contributors "as is" and
|
||||
// any express or implied warranties, including, but not limited to, the implied
|
||||
// warranties of merchantability and fitness for a particular purpose are disclaimed.
|
||||
// In no event shall the Intel Corporation or contributors be liable for any direct,
|
||||
// indirect, incidental, special, exemplary, or consequential damages
|
||||
// (including, but not limited to, procurement of substitute goods or services;
|
||||
// loss of use, data, or profits; or business interruption) however caused
|
||||
// and on any theory of liability, whether in contract, strict liability,
|
||||
// or tort (including negligence or otherwise) arising in any way out of
|
||||
// the use of this software, even if advised of the possibility of such damage.
|
||||
//
|
||||
//M*/
|
||||
|
||||
#include "precomp.hpp"
|
||||
#include "grfmt_gdcm.hpp"
|
||||
|
||||
#ifdef HAVE_GDCM
|
||||
|
||||
//#define DBG(...) printf(__VA_ARGS__)
|
||||
#define DBG(...)
|
||||
|
||||
#include <gdcmImageReader.h>
|
||||
|
||||
static const size_t preamble_skip = 128;
|
||||
static const size_t magic_len = 4;
|
||||
|
||||
inline cv::String getMagic()
|
||||
{
|
||||
return cv::String("\x44\x49\x43\x4D", 4);
|
||||
}
|
||||
|
||||
namespace cv
|
||||
{
|
||||
|
||||
/************************ DICOM decoder *****************************/
|
||||
|
||||
DICOMDecoder::DICOMDecoder()
|
||||
{
|
||||
// DICOM preamble is 128 bytes (can have any value, defaults to 0) + 4 bytes magic number (DICM)
|
||||
m_signature = String(preamble_skip, (char)'\x0') + getMagic();
|
||||
m_buf_supported = false;
|
||||
}
|
||||
|
||||
bool DICOMDecoder::checkSignature( const String& signature ) const
|
||||
{
|
||||
if (signature.size() >= preamble_skip + magic_len)
|
||||
{
|
||||
if (signature.substr(preamble_skip, magic_len) == getMagic())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
DBG("GDCM | Signature does not match\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
ImageDecoder DICOMDecoder::newDecoder() const
|
||||
{
|
||||
return makePtr<DICOMDecoder>();
|
||||
}
|
||||
|
||||
bool DICOMDecoder::readHeader()
|
||||
{
|
||||
gdcm::ImageReader csImageReader;
|
||||
csImageReader.SetFileName(m_filename.c_str());
|
||||
if(!csImageReader.Read())
|
||||
{
|
||||
DBG("GDCM | Failed to open DICOM file\n");
|
||||
return(false);
|
||||
}
|
||||
|
||||
const gdcm::Image &csImage = csImageReader.GetImage();
|
||||
bool bOK = true;
|
||||
switch (csImage.GetPhotometricInterpretation().GetType())
|
||||
{
|
||||
case gdcm::PhotometricInterpretation::MONOCHROME1:
|
||||
case gdcm::PhotometricInterpretation::MONOCHROME2:
|
||||
{
|
||||
switch (csImage.GetPixelFormat().GetScalarType())
|
||||
{
|
||||
case gdcm::PixelFormat::INT8: m_type = CV_8SC1; break;
|
||||
case gdcm::PixelFormat::UINT8: m_type = CV_8UC1; break;
|
||||
case gdcm::PixelFormat::INT16: m_type = CV_16SC1; break;
|
||||
case gdcm::PixelFormat::UINT16: m_type = CV_16UC1; break;
|
||||
case gdcm::PixelFormat::INT32: m_type = CV_32SC1; break;
|
||||
case gdcm::PixelFormat::FLOAT32: m_type = CV_32FC1; break;
|
||||
case gdcm::PixelFormat::FLOAT64: m_type = CV_64FC1; break;
|
||||
default: bOK = false; DBG("GDCM | Monochrome scalar type not supported\n"); break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case gdcm::PhotometricInterpretation::RGB:
|
||||
{
|
||||
switch (csImage.GetPixelFormat().GetScalarType())
|
||||
{
|
||||
case gdcm::PixelFormat::UINT8: m_type = CV_8UC3; break;
|
||||
default: bOK = false; DBG("GDCM | RGB scalar type not supported\n"); break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
bOK = false;
|
||||
DBG("GDCM | PI not supported: %s\n", csImage.GetPhotometricInterpretation().GetString());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(bOK)
|
||||
{
|
||||
unsigned int ndim = csImage.GetNumberOfDimensions();
|
||||
if (ndim != 2)
|
||||
{
|
||||
DBG("GDCM | Invalid dimensions number: %d\n", ndim);
|
||||
bOK = false;
|
||||
}
|
||||
}
|
||||
if (bOK)
|
||||
{
|
||||
const unsigned int *piDimension = csImage.GetDimensions();
|
||||
m_height = piDimension[0];
|
||||
m_width = piDimension[1];
|
||||
if( ( m_width <=0 ) || ( m_height <=0 ) )
|
||||
{
|
||||
DBG("GDCM | Invalid dimensions: %d x %d\n", piDimension[0], piDimension[1]);
|
||||
bOK = false;
|
||||
}
|
||||
}
|
||||
|
||||
return(bOK);
|
||||
}
|
||||
|
||||
|
||||
bool DICOMDecoder::readData( Mat& csImage )
|
||||
{
|
||||
csImage.create(m_width,m_height,m_type);
|
||||
|
||||
gdcm::ImageReader csImageReader;
|
||||
csImageReader.SetFileName(m_filename.c_str());
|
||||
if(!csImageReader.Read())
|
||||
{
|
||||
DBG("GDCM | Failed to Read\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
const gdcm::Image &img = csImageReader.GetImage();
|
||||
|
||||
unsigned long len = img.GetBufferLength();
|
||||
if (len > csImage.elemSize() * csImage.total())
|
||||
{
|
||||
DBG("GDCM | Buffer is bigger than Mat: %ld > %ld * %ld\n", len, csImage.elemSize(), csImage.total());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!img.GetBuffer((char*)csImage.ptr()))
|
||||
{
|
||||
DBG("GDCM | Failed to GetBuffer\n");
|
||||
return false;
|
||||
}
|
||||
DBG("GDCM | Read OK\n");
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif // HAVE_GDCM
|
||||
@@ -0,0 +1,70 @@
|
||||
/*M///////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
|
||||
//
|
||||
// By downloading, copying, installing or using the software you agree to this license.
|
||||
// If you do not agree to this license, do not download, install,
|
||||
// copy or use the software.
|
||||
//
|
||||
//
|
||||
// License Agreement
|
||||
// For Open Source Computer Vision Library
|
||||
//
|
||||
// Copyright (C) 2000-2008, Intel Corporation, all rights reserved.
|
||||
// Copyright (C) 2009, Willow Garage Inc., all rights reserved.
|
||||
// Third party copyrights are property of their respective owners.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without modification,
|
||||
// are permitted provided that the following conditions are met:
|
||||
//
|
||||
// * Redistribution's of source code must retain the above copyright notice,
|
||||
// this list of conditions and the following disclaimer.
|
||||
//
|
||||
// * Redistribution's in binary form must reproduce the above copyright notice,
|
||||
// this list of conditions and the following disclaimer in the documentation
|
||||
// and/or other materials provided with the distribution.
|
||||
//
|
||||
// * The name of the copyright holders may not be used to endorse or promote products
|
||||
// derived from this software without specific prior written permission.
|
||||
//
|
||||
// This software is provided by the copyright holders and contributors "as is" and
|
||||
// any express or implied warranties, including, but not limited to, the implied
|
||||
// warranties of merchantability and fitness for a particular purpose are disclaimed.
|
||||
// In no event shall the Intel Corporation or contributors be liable for any direct,
|
||||
// indirect, incidental, special, exemplary, or consequential damages
|
||||
// (including, but not limited to, procurement of substitute goods or services;
|
||||
// loss of use, data, or profits; or business interruption) however caused
|
||||
// and on any theory of liability, whether in contract, strict liability,
|
||||
// or tort (including negligence or otherwise) arising in any way out of
|
||||
// the use of this software, even if advised of the possibility of such damage.
|
||||
//
|
||||
//M*/
|
||||
|
||||
#ifndef _GDCM_DICOM_H_
|
||||
#define _GDCM_DICOM_H_
|
||||
|
||||
#include "cvconfig.h"
|
||||
|
||||
#ifdef HAVE_GDCM
|
||||
|
||||
#include "grfmt_base.hpp"
|
||||
|
||||
namespace cv
|
||||
{
|
||||
|
||||
// DICOM image reader using GDCM
|
||||
class DICOMDecoder CV_FINAL : public BaseImageDecoder
|
||||
{
|
||||
public:
|
||||
DICOMDecoder();
|
||||
bool readData( Mat& img ) CV_OVERRIDE;
|
||||
bool readHeader() CV_OVERRIDE;
|
||||
ImageDecoder newDecoder() const CV_OVERRIDE;
|
||||
virtual bool checkSignature( const String& signature ) const CV_OVERRIDE;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // HAVE_GDCM
|
||||
|
||||
#endif/*_GDCM_DICOM_H_*/
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,195 @@
|
||||
// This file is part of OpenCV project.
|
||||
// It is subject to the license terms in the LICENSE file found in the top-level
|
||||
// directory of this distribution and at http://opencv.org/license.html
|
||||
|
||||
#ifndef OPENCV_GRFMT_GIF_HPP
|
||||
#define OPENCV_GRFMT_GIF_HPP
|
||||
#ifdef HAVE_IMGCODEC_GIF
|
||||
|
||||
#include "grfmt_base.hpp"
|
||||
|
||||
namespace cv
|
||||
{
|
||||
|
||||
// See https://www.w3.org/Graphics/GIF/spec-gif89a.txt
|
||||
// 23. Graphic Control Extension.
|
||||
// <Packed Fields>
|
||||
// Reserved : 3 bits
|
||||
// Disposal Method : 3 bits
|
||||
// User Input Flag : 1 bit
|
||||
// Transparent Color Flag : 1 bit
|
||||
constexpr int GIF_DISPOSE_METHOD_SHIFT = 2;
|
||||
constexpr int GIF_DISPOSE_METHOD_MASK = 7; // 0b111
|
||||
constexpr int GIF_TRANS_COLOR_FLAG_MASK = 1; // 0b1
|
||||
|
||||
enum GifDisposeMethod {
|
||||
GIF_DISPOSE_NA = 0,
|
||||
GIF_DISPOSE_NONE = 1,
|
||||
GIF_DISPOSE_RESTORE_BACKGROUND = 2,
|
||||
GIF_DISPOSE_RESTORE_PREVIOUS = 3,
|
||||
// 4-7 are reserved/undefined.
|
||||
|
||||
GIF_DISPOSE_MAX = GIF_DISPOSE_RESTORE_PREVIOUS,
|
||||
};
|
||||
|
||||
enum GifTransparentColorFlag {
|
||||
GIF_TRANSPARENT_INDEX_NOT_GIVEN = 0,
|
||||
GIF_TRANSPARENT_INDEX_GIVEN = 1,
|
||||
|
||||
GIF_TRANSPARENT_INDEX_MAX = GIF_TRANSPARENT_INDEX_GIVEN,
|
||||
};
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
//// GIF Decoder ////
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
class GifDecoder CV_FINAL : public BaseImageDecoder
|
||||
{
|
||||
public:
|
||||
GifDecoder();
|
||||
~GifDecoder() CV_OVERRIDE;
|
||||
|
||||
bool readHeader() CV_OVERRIDE;
|
||||
bool readData(Mat& img) CV_OVERRIDE;
|
||||
bool nextPage() CV_OVERRIDE;
|
||||
void close();
|
||||
|
||||
ImageDecoder newDecoder() const CV_OVERRIDE;
|
||||
|
||||
protected:
|
||||
RLByteStream m_strm;
|
||||
|
||||
int bgColor;
|
||||
int depth;
|
||||
int idx;
|
||||
|
||||
bool hasTransparentColor;
|
||||
uchar transparentColor;
|
||||
int top, left, width, height;
|
||||
|
||||
bool hasRead;
|
||||
std::vector<uchar> globalColorTable;
|
||||
std::vector<uchar> localColorTable;
|
||||
|
||||
int lzwMinCodeSize;
|
||||
int globalColorTableSize;
|
||||
int localColorTableSize;
|
||||
|
||||
Mat lastImage;
|
||||
std::vector<uchar> imgCodeStream;
|
||||
|
||||
struct lzwNodeD
|
||||
{
|
||||
int length;
|
||||
uchar suffix;
|
||||
std::vector<uchar> prefix;
|
||||
};
|
||||
|
||||
GifDisposeMethod readExtensions();
|
||||
void code2pixel(Mat& img, int start, int k);
|
||||
bool lzwDecode();
|
||||
bool getFrameCount_();
|
||||
bool skipHeader();
|
||||
};
|
||||
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
//// GIF Encoder ////
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
class GifEncoder CV_FINAL : public BaseImageEncoder {
|
||||
public:
|
||||
GifEncoder();
|
||||
~GifEncoder() CV_OVERRIDE;
|
||||
|
||||
bool writeanimation(const Animation& animation, const std::vector<int>& params) CV_OVERRIDE;
|
||||
|
||||
ImageEncoder newEncoder() const CV_OVERRIDE;
|
||||
|
||||
private:
|
||||
/** Color Quantization **/
|
||||
class OctreeColorQuant
|
||||
{
|
||||
struct OctreeNode
|
||||
{
|
||||
bool isLeaf;
|
||||
std::shared_ptr<OctreeNode> children[8]{};
|
||||
int level;
|
||||
uchar index;
|
||||
int leaf;
|
||||
int pixelCount;
|
||||
size_t redSum, greenSum, blueSum;
|
||||
|
||||
OctreeNode();
|
||||
};
|
||||
|
||||
std::shared_ptr<OctreeNode> root;
|
||||
std::vector<std::shared_ptr<OctreeNode>> m_nodeList[8];
|
||||
int32_t m_bitLength;
|
||||
int32_t m_maxColors;
|
||||
int32_t m_leafCount;
|
||||
uchar m_criticalTransparency;
|
||||
uchar r, g, b; // color under transparent color
|
||||
|
||||
public:
|
||||
explicit OctreeColorQuant(int maxColors = 256, int bitLength = 8, uchar criticalTransparency = 1);
|
||||
|
||||
int getPalette(uchar* colorTable);
|
||||
uchar getLeaf(uchar red, uchar green, uchar blue);
|
||||
|
||||
void addMat(const Mat& img);
|
||||
void addMats(const std::vector<Mat>& img_vec);
|
||||
void addColor(int red, int green, int blue);
|
||||
void reduceTree();
|
||||
void recurseReduce(const std::shared_ptr<OctreeNode>& node);
|
||||
};
|
||||
|
||||
enum GifDithering // normal dithering level is -1 to 2
|
||||
{
|
||||
GRFMT_GIF_None = 3,
|
||||
GRFMT_GIF_FloydSteinberg = 4
|
||||
};
|
||||
|
||||
WLByteStream strm;
|
||||
int m_width, m_height;
|
||||
|
||||
int globalColorTableSize;
|
||||
int localColorTableSize;
|
||||
|
||||
uchar criticalTransparency;
|
||||
uchar transparentColor;
|
||||
Vec3b transparentRGB;
|
||||
int top, left, width, height;
|
||||
|
||||
OctreeColorQuant quantG;
|
||||
OctreeColorQuant quantL;
|
||||
|
||||
std::vector<int16_t> lzwTable;
|
||||
std::vector<uchar> imgCodeStream;
|
||||
|
||||
std::vector<uchar> globalColorTable;
|
||||
std::vector<uchar> localColorTable;
|
||||
|
||||
// params
|
||||
int colorNum;
|
||||
int bitDepth;
|
||||
int dithering;
|
||||
int lzwMinCodeSize, lzwMaxCodeSize;
|
||||
bool fast;
|
||||
|
||||
bool writeFrames(const std::vector<Mat>& img_vec, const std::vector<int>& params);
|
||||
bool writeHeader(const std::vector<Mat>& img_vec, const int loopCount);
|
||||
bool writeFrame(const Mat& img, const int frameDelay);
|
||||
bool pixel2code(const Mat& img);
|
||||
void getColorTable(const std::vector<Mat>& img_vec, bool isGlobal);
|
||||
static int ditheringKernel(const Mat &img, Mat &img_, int depth, uchar transparency);
|
||||
bool lzwEncode();
|
||||
void close();
|
||||
};
|
||||
|
||||
|
||||
} // namespace cv
|
||||
|
||||
#endif // HAVE_IMGCODEC_GIF
|
||||
#endif //OPENCV_GRFMT_GIF_HPP
|
||||
@@ -0,0 +1,216 @@
|
||||
/*M///////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
|
||||
//
|
||||
// By downloading, copying, installing or using the software you agree to this license.
|
||||
// If you do not agree to this license, do not download, install,
|
||||
// copy or use the software.
|
||||
//
|
||||
//
|
||||
// License Agreement
|
||||
// For Open Source Computer Vision Library
|
||||
//
|
||||
// Copyright (C) 2000-2008, Intel Corporation, all rights reserved.
|
||||
// Copyright (C) 2009, Willow Garage Inc., all rights reserved.
|
||||
// Third party copyrights are property of their respective owners.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without modification,
|
||||
// are permitted provided that the following conditions are met:
|
||||
//
|
||||
// * Redistribution's of source code must retain the above copyright notice,
|
||||
// this list of conditions and the following disclaimer.
|
||||
//
|
||||
// * Redistribution's in binary form must reproduce the above copyright notice,
|
||||
// this list of conditions and the following disclaimer in the documentation
|
||||
// and/or other materials provided with the distribution.
|
||||
//
|
||||
// * The name of the copyright holders may not be used to endorse or promote products
|
||||
// derived from this software without specific prior written permission.
|
||||
//
|
||||
// This software is provided by the copyright holders and contributors "as is" and
|
||||
// any express or implied warranties, including, but not limited to, the implied
|
||||
// warranties of merchantability and fitness for a particular purpose are disclaimed.
|
||||
// In no event shall the Intel Corporation or contributors be liable for any direct,
|
||||
// indirect, incidental, special, exemplary, or consequential damages
|
||||
// (including, but not limited to, procurement of substitute goods or services;
|
||||
// loss of use, data, or profits; or business interruption) however caused
|
||||
// and on any theory of liability, whether in contract, strict liability,
|
||||
// or tort (including negligence or otherwise) arising in any way out of
|
||||
// the use of this software, even if advised of the possibility of such damage.
|
||||
//
|
||||
//M*/
|
||||
|
||||
#include "precomp.hpp"
|
||||
#include "grfmt_hdr.hpp"
|
||||
#include "rgbe.hpp"
|
||||
#include "opencv2/core/utils/logger.hpp"
|
||||
|
||||
#ifdef HAVE_IMGCODEC_HDR
|
||||
|
||||
namespace cv
|
||||
{
|
||||
|
||||
HdrDecoder::HdrDecoder()
|
||||
{
|
||||
m_signature = "#?RGBE";
|
||||
m_signature_alt = "#?RADIANCE";
|
||||
file = NULL;
|
||||
m_type = CV_32FC3;
|
||||
}
|
||||
|
||||
HdrDecoder::~HdrDecoder()
|
||||
{
|
||||
if(file) {
|
||||
fclose(file);
|
||||
}
|
||||
}
|
||||
|
||||
size_t HdrDecoder::signatureLength() const
|
||||
{
|
||||
return m_signature.size() > m_signature_alt.size() ?
|
||||
m_signature.size() : m_signature_alt.size();
|
||||
}
|
||||
|
||||
bool HdrDecoder::readHeader()
|
||||
{
|
||||
file = fopen(m_filename.c_str(), "rb");
|
||||
if(!file) {
|
||||
return false;
|
||||
}
|
||||
RGBE_ReadHeader(file, &m_width, &m_height, NULL);
|
||||
if(m_width <= 0 || m_height <= 0) {
|
||||
fclose(file);
|
||||
file = NULL;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool HdrDecoder::readData(Mat& _img)
|
||||
{
|
||||
Mat img(m_height, m_width, CV_32FC3);
|
||||
if(!file) {
|
||||
if(!readHeader()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
RGBE_ReadPixels_RLE(file, const_cast<float*>(img.ptr<float>()), img.cols, img.rows);
|
||||
fclose(file); file = NULL;
|
||||
|
||||
// NOTE: 'img' has type CV32FC3
|
||||
switch (_img.depth())
|
||||
{
|
||||
case CV_8U: img.convertTo(img, _img.depth(), 255); break;
|
||||
case CV_32F: break;
|
||||
default: CV_Error(Error::StsError, "Wrong expected image depth, allowed: CV_8U and CV_32F");
|
||||
}
|
||||
switch (_img.channels())
|
||||
{
|
||||
case 1: cvtColor(img, _img, COLOR_BGR2GRAY); break;
|
||||
case 3:
|
||||
// TODO, try to modify RGBE_ReadPixels_RLE to load rgb data directly.
|
||||
if (m_use_rgb)
|
||||
cv::cvtColor(img, _img, cv::COLOR_BGR2RGB);
|
||||
else
|
||||
img.copyTo(_img);
|
||||
break;
|
||||
default: CV_Error(Error::StsError, "Wrong expected image channels, allowed: 1 and 3");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool HdrDecoder::checkSignature( const String& signature ) const
|
||||
{
|
||||
if (signature.size() >= m_signature.size() &&
|
||||
0 == memcmp(signature.c_str(), m_signature.c_str(), m_signature.size())
|
||||
)
|
||||
return true;
|
||||
if (signature.size() >= m_signature_alt.size() &&
|
||||
0 == memcmp(signature.c_str(), m_signature_alt.c_str(), m_signature_alt.size())
|
||||
)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
ImageDecoder HdrDecoder::newDecoder() const
|
||||
{
|
||||
return makePtr<HdrDecoder>();
|
||||
}
|
||||
|
||||
HdrEncoder::HdrEncoder()
|
||||
{
|
||||
m_description = "Radiance HDR (*.hdr;*.pic)";
|
||||
m_supported_encode_key = {IMWRITE_HDR_COMPRESSION};
|
||||
}
|
||||
|
||||
HdrEncoder::~HdrEncoder()
|
||||
{
|
||||
}
|
||||
|
||||
bool HdrEncoder::write( const Mat& input_img, const std::vector<int>& params )
|
||||
{
|
||||
Mat img;
|
||||
CV_Assert(input_img.channels() == 3 || input_img.channels() == 1);
|
||||
if(input_img.channels() == 1) {
|
||||
std::vector<Mat> splitted(3, input_img);
|
||||
merge(splitted, img);
|
||||
} else {
|
||||
input_img.copyTo(img);
|
||||
}
|
||||
if(img.depth() != CV_32F) {
|
||||
img.convertTo(img, CV_32FC3, 1/255.0f);
|
||||
}
|
||||
|
||||
int compression = IMWRITE_HDR_COMPRESSION_RLE;
|
||||
for (size_t i = 0; i + 1 < params.size(); i += 2)
|
||||
{
|
||||
const int value = params[i+1];
|
||||
switch (params[i])
|
||||
{
|
||||
case IMWRITE_HDR_COMPRESSION:
|
||||
switch(value)
|
||||
{
|
||||
case IMWRITE_HDR_COMPRESSION_NONE:
|
||||
case IMWRITE_HDR_COMPRESSION_RLE:
|
||||
compression = value;
|
||||
break;
|
||||
default:
|
||||
compression = IMWRITE_HDR_COMPRESSION_RLE;
|
||||
CV_LOG_WARNING(nullptr, cv::format("The value(%d) for IMWRITE_HDR_COMPRESSION must be one of ImwriteHDRCompressionFlags. It is fallbacked to IMWRITE_HDR_COMPRESSION_RLE", value));
|
||||
break;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
CV_Check(compression, compression == IMWRITE_HDR_COMPRESSION_NONE || compression == IMWRITE_HDR_COMPRESSION_RLE, "");
|
||||
|
||||
FILE *fout = fopen(m_filename.c_str(), "wb");
|
||||
if(!fout) {
|
||||
return false;
|
||||
}
|
||||
|
||||
RGBE_WriteHeader(fout, img.cols, img.rows, NULL);
|
||||
if (compression == IMWRITE_HDR_COMPRESSION_RLE) {
|
||||
RGBE_WritePixels_RLE(fout, const_cast<float*>(img.ptr<float>()), img.cols, img.rows);
|
||||
} else {
|
||||
RGBE_WritePixels(fout, const_cast<float*>(img.ptr<float>()), img.cols * img.rows);
|
||||
}
|
||||
|
||||
fclose(fout);
|
||||
return true;
|
||||
}
|
||||
|
||||
ImageEncoder HdrEncoder::newEncoder() const
|
||||
{
|
||||
return makePtr<HdrEncoder>();
|
||||
}
|
||||
|
||||
bool HdrEncoder::isFormatSupported( int depth ) const {
|
||||
return depth != CV_64F;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif // HAVE_IMGCODEC_HDR
|
||||
@@ -0,0 +1,86 @@
|
||||
/*M///////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
|
||||
//
|
||||
// By downloading, copying, installing or using the software you agree to this license.
|
||||
// If you do not agree to this license, do not download, install,
|
||||
// copy or use the software.
|
||||
//
|
||||
//
|
||||
// License Agreement
|
||||
// For Open Source Computer Vision Library
|
||||
//
|
||||
// Copyright (C) 2000-2008, Intel Corporation, all rights reserved.
|
||||
// Copyright (C) 2009, Willow Garage Inc., all rights reserved.
|
||||
// Third party copyrights are property of their respective owners.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without modification,
|
||||
// are permitted provided that the following conditions are met:
|
||||
//
|
||||
// * Redistribution's of source code must retain the above copyright notice,
|
||||
// this list of conditions and the following disclaimer.
|
||||
//
|
||||
// * Redistribution's in binary form must reproduce the above copyright notice,
|
||||
// this list of conditions and the following disclaimer in the documentation
|
||||
// and/or other materials provided with the distribution.
|
||||
//
|
||||
// * The name of the copyright holders may not be used to endorse or promote products
|
||||
// derived from this software without specific prior written permission.
|
||||
//
|
||||
// This software is provided by the copyright holders and contributors "as is" and
|
||||
// any express or implied warranties, including, but not limited to, the implied
|
||||
// warranties of merchantability and fitness for a particular purpose are disclaimed.
|
||||
// In no event shall the Intel Corporation or contributors be liable for any direct,
|
||||
// indirect, incidental, special, exemplary, or consequential damages
|
||||
// (including, but not limited to, procurement of substitute goods or services;
|
||||
// loss of use, data, or profits; or business interruption) however caused
|
||||
// and on any theory of liability, whether in contract, strict liability,
|
||||
// or tort (including negligence or otherwise) arising in any way out of
|
||||
// the use of this software, even if advised of the possibility of such damage.
|
||||
//
|
||||
//M*/
|
||||
|
||||
#ifndef _GRFMT_HDR_H_
|
||||
#define _GRFMT_HDR_H_
|
||||
|
||||
#include "grfmt_base.hpp"
|
||||
|
||||
#ifdef HAVE_IMGCODEC_HDR
|
||||
|
||||
namespace cv
|
||||
{
|
||||
|
||||
// Radiance rgbe (.hdr) reader
|
||||
class HdrDecoder CV_FINAL : public BaseImageDecoder
|
||||
{
|
||||
public:
|
||||
HdrDecoder();
|
||||
~HdrDecoder() CV_OVERRIDE;
|
||||
bool readHeader() CV_OVERRIDE;
|
||||
bool readData( Mat& img ) CV_OVERRIDE;
|
||||
bool checkSignature( const String& signature ) const CV_OVERRIDE;
|
||||
ImageDecoder newDecoder() const CV_OVERRIDE;
|
||||
size_t signatureLength() const CV_OVERRIDE;
|
||||
protected:
|
||||
String m_signature_alt;
|
||||
FILE *file;
|
||||
};
|
||||
|
||||
// ... writer
|
||||
class HdrEncoder CV_FINAL : public BaseImageEncoder
|
||||
{
|
||||
public:
|
||||
HdrEncoder();
|
||||
~HdrEncoder() CV_OVERRIDE;
|
||||
bool write( const Mat& img, const std::vector<int>& params ) CV_OVERRIDE;
|
||||
ImageEncoder newEncoder() const CV_OVERRIDE;
|
||||
bool isFormatSupported( int depth ) const CV_OVERRIDE;
|
||||
protected:
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // HAVE_IMGCODEC_HDR
|
||||
|
||||
#endif/*_GRFMT_HDR_H_*/
|
||||
@@ -0,0 +1,949 @@
|
||||
/*M///////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
|
||||
//
|
||||
// By downloading, copying, installing or using the software you agree to this license.
|
||||
// If you do not agree to this license, do not download, install,
|
||||
// copy or use the software.
|
||||
//
|
||||
//
|
||||
// Intel License Agreement
|
||||
// For Open Source Computer Vision Library
|
||||
//
|
||||
// Copyright (C) 2000, Intel Corporation, all rights reserved.
|
||||
// Third party copyrights are property of their respective owners.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without modification,
|
||||
// are permitted provided that the following conditions are met:
|
||||
//
|
||||
// * Redistribution's of source code must retain the above copyright notice,
|
||||
// this list of conditions and the following disclaimer.
|
||||
//
|
||||
// * Redistribution's in binary form must reproduce the above copyright notice,
|
||||
// this list of conditions and the following disclaimer in the documentation
|
||||
// and/or other materials provided with the distribution.
|
||||
//
|
||||
// * The name of Intel Corporation may not be used to endorse or promote products
|
||||
// derived from this software without specific prior written permission.
|
||||
//
|
||||
// This software is provided by the copyright holders and contributors "as is" and
|
||||
// any express or implied warranties, including, but not limited to, the implied
|
||||
// warranties of merchantability and fitness for a particular purpose are disclaimed.
|
||||
// In no event shall the Intel Corporation or contributors be liable for any direct,
|
||||
// indirect, incidental, special, exemplary, or consequential damages
|
||||
// (including, but not limited to, procurement of substitute goods or services;
|
||||
// loss of use, data, or profits; or business interruption) however caused
|
||||
// and on any theory of liability, whether in contract, strict liability,
|
||||
// or tort (including negligence or otherwise) arising in any way out of
|
||||
// the use of this software, even if advised of the possibility of such damage.
|
||||
//
|
||||
//M*/
|
||||
|
||||
#include "precomp.hpp"
|
||||
#include "grfmt_jpeg.hpp"
|
||||
|
||||
#ifdef HAVE_JPEG
|
||||
|
||||
#include <opencv2/core/utils/logger.hpp>
|
||||
|
||||
#ifdef _MSC_VER
|
||||
//interaction between '_setjmp' and C++ object destruction is non-portable
|
||||
#pragma warning(disable: 4611)
|
||||
#endif
|
||||
|
||||
#include <stdio.h>
|
||||
#include <setjmp.h>
|
||||
|
||||
// the following defines are a hack to avoid multiple problems with frame pointer handling and setjmp
|
||||
// see http://gcc.gnu.org/ml/gcc/2011-10/msg00324.html for some details
|
||||
#define mingw_getsp(...) 0
|
||||
#define __builtin_frame_address(...) 0
|
||||
|
||||
#ifdef _WIN32
|
||||
|
||||
#define XMD_H // prevent redefinition of INT32
|
||||
#undef FAR // prevent FAR redefinition
|
||||
|
||||
#endif
|
||||
|
||||
#if defined _WIN32 && defined __GNUC__
|
||||
typedef unsigned char boolean;
|
||||
#endif
|
||||
|
||||
#undef FALSE
|
||||
#undef TRUE
|
||||
|
||||
extern "C" {
|
||||
#include "jpeglib.h"
|
||||
}
|
||||
|
||||
#ifndef CV_MANUAL_JPEG_STD_HUFF_TABLES
|
||||
#if defined(LIBJPEG_TURBO_VERSION_NUMBER) && LIBJPEG_TURBO_VERSION_NUMBER >= 1003090
|
||||
#define CV_MANUAL_JPEG_STD_HUFF_TABLES 0 // libjpeg-turbo handles standard huffman tables itself (jstdhuff.c)
|
||||
#else
|
||||
#define CV_MANUAL_JPEG_STD_HUFF_TABLES 1
|
||||
#endif
|
||||
#endif
|
||||
#if CV_MANUAL_JPEG_STD_HUFF_TABLES == 0
|
||||
#undef CV_MANUAL_JPEG_STD_HUFF_TABLES
|
||||
#endif
|
||||
|
||||
namespace cv
|
||||
{
|
||||
|
||||
struct JpegErrorMgr
|
||||
{
|
||||
struct jpeg_error_mgr pub;
|
||||
jmp_buf setjmp_buffer;
|
||||
};
|
||||
|
||||
struct JpegSource
|
||||
{
|
||||
struct jpeg_source_mgr pub;
|
||||
int skip;
|
||||
};
|
||||
|
||||
struct JpegState
|
||||
{
|
||||
jpeg_decompress_struct cinfo; // IJG JPEG codec structure
|
||||
JpegErrorMgr jerr; // error processing manager state
|
||||
JpegSource source; // memory buffer source
|
||||
};
|
||||
|
||||
/////////////////////// Error processing /////////////////////
|
||||
|
||||
METHODDEF(void)
|
||||
stub(j_decompress_ptr)
|
||||
{
|
||||
}
|
||||
|
||||
METHODDEF(boolean)
|
||||
fill_input_buffer(j_decompress_ptr)
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// emulating memory input stream
|
||||
|
||||
METHODDEF(void)
|
||||
skip_input_data(j_decompress_ptr cinfo, long num_bytes)
|
||||
{
|
||||
JpegSource* source = (JpegSource*) cinfo->src;
|
||||
|
||||
if( num_bytes > (long)source->pub.bytes_in_buffer )
|
||||
{
|
||||
// We need to skip more data than we have in the buffer.
|
||||
// This will force the JPEG library to suspend decoding.
|
||||
source->skip = (int)(num_bytes - source->pub.bytes_in_buffer);
|
||||
source->pub.next_input_byte += source->pub.bytes_in_buffer;
|
||||
source->pub.bytes_in_buffer = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Skip portion of the buffer
|
||||
source->pub.bytes_in_buffer -= num_bytes;
|
||||
source->pub.next_input_byte += num_bytes;
|
||||
source->skip = 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void jpeg_buffer_src(j_decompress_ptr cinfo, JpegSource* source)
|
||||
{
|
||||
cinfo->src = &source->pub;
|
||||
|
||||
// Prepare for suspending reader
|
||||
source->pub.init_source = stub;
|
||||
source->pub.fill_input_buffer = fill_input_buffer;
|
||||
source->pub.skip_input_data = skip_input_data;
|
||||
source->pub.resync_to_restart = jpeg_resync_to_restart;
|
||||
source->pub.term_source = stub;
|
||||
source->pub.bytes_in_buffer = 0; // forces fill_input_buffer on first read
|
||||
|
||||
source->skip = 0;
|
||||
}
|
||||
|
||||
|
||||
METHODDEF(void)
|
||||
error_exit( j_common_ptr cinfo )
|
||||
{
|
||||
JpegErrorMgr* err_mgr = (JpegErrorMgr*)(cinfo->err);
|
||||
|
||||
/* Return control to the setjmp point */
|
||||
longjmp( err_mgr->setjmp_buffer, 1 );
|
||||
}
|
||||
|
||||
|
||||
/////////////////////// JpegDecoder ///////////////////
|
||||
|
||||
|
||||
JpegDecoder::JpegDecoder()
|
||||
{
|
||||
m_signature = "\xFF\xD8\xFF";
|
||||
m_state = 0;
|
||||
m_f = 0;
|
||||
m_buf_supported = true;
|
||||
}
|
||||
|
||||
|
||||
JpegDecoder::~JpegDecoder()
|
||||
{
|
||||
close();
|
||||
}
|
||||
|
||||
|
||||
void JpegDecoder::close()
|
||||
{
|
||||
if( m_state )
|
||||
{
|
||||
JpegState* state = (JpegState*)m_state;
|
||||
jpeg_destroy_decompress( &state->cinfo );
|
||||
delete state;
|
||||
m_state = 0;
|
||||
}
|
||||
|
||||
if( m_f )
|
||||
{
|
||||
fclose( m_f );
|
||||
m_f = 0;
|
||||
}
|
||||
|
||||
m_width = m_height = 0;
|
||||
m_type = -1;
|
||||
}
|
||||
|
||||
ImageDecoder JpegDecoder::newDecoder() const
|
||||
{
|
||||
return makePtr<JpegDecoder>();
|
||||
}
|
||||
|
||||
bool JpegDecoder::readHeader()
|
||||
{
|
||||
volatile bool result = false;
|
||||
close();
|
||||
|
||||
JpegState* state = new JpegState;
|
||||
m_state = state;
|
||||
state->cinfo.err = jpeg_std_error(&state->jerr.pub);
|
||||
state->jerr.pub.error_exit = error_exit;
|
||||
|
||||
if( setjmp( state->jerr.setjmp_buffer ) == 0 )
|
||||
{
|
||||
jpeg_create_decompress( &state->cinfo );
|
||||
|
||||
if( !m_buf.empty() )
|
||||
{
|
||||
jpeg_buffer_src(&state->cinfo, &state->source);
|
||||
state->source.pub.next_input_byte = m_buf.ptr();
|
||||
state->source.pub.bytes_in_buffer = m_buf.cols*m_buf.rows*m_buf.elemSize();
|
||||
}
|
||||
else
|
||||
{
|
||||
m_f = fopen( m_filename.c_str(), "rb" );
|
||||
if( m_f )
|
||||
jpeg_stdio_src( &state->cinfo, m_f );
|
||||
}
|
||||
|
||||
if (state->cinfo.src != 0)
|
||||
{
|
||||
jpeg_save_markers(&state->cinfo, APP1, 0xffff);
|
||||
jpeg_save_markers(&state->cinfo, APP2, 0xffff);
|
||||
jpeg_read_header( &state->cinfo, TRUE );
|
||||
|
||||
const std::streamsize EXIF_HEADER_SIZE = 6; // "Exif\0\0"
|
||||
const std::streamsize XMP_HEADER_SIZE = 29; // "http://ns.adobe.com/xap/1.0/"
|
||||
const std::streamsize ICC_HEADER_SIZE = 14; // "ICC_PROFILE\0" + seq/total
|
||||
|
||||
for (jpeg_saved_marker_ptr cmarker = state->cinfo.marker_list; cmarker != nullptr; cmarker = cmarker->next)
|
||||
{
|
||||
// Handle APP1 marker: could be Exif or XMP
|
||||
if (cmarker->marker == APP1 && cmarker->data_length > EXIF_HEADER_SIZE)
|
||||
{
|
||||
unsigned char* data = cmarker->data;
|
||||
|
||||
// Check for Exif data
|
||||
if (std::memcmp(data, "Exif\0\0", EXIF_HEADER_SIZE) == 0)
|
||||
{
|
||||
m_exif.parseExif(data + EXIF_HEADER_SIZE, cmarker->data_length - EXIF_HEADER_SIZE);
|
||||
}
|
||||
// Check for XMP metadata
|
||||
else if (m_read_options && cmarker->data_length >= XMP_HEADER_SIZE &&
|
||||
std::memcmp(data, "http://ns.adobe.com/xap/1.0/", XMP_HEADER_SIZE) == 0)
|
||||
{
|
||||
std::vector<uchar>& xmp = m_metadata[IMAGE_METADATA_XMP];
|
||||
xmp.insert(xmp.end(), data, data + cmarker->data_length);
|
||||
}
|
||||
}
|
||||
|
||||
// Handle APP2 marker: typically contains ICC profile data
|
||||
if (m_read_options && cmarker->marker == APP2 && cmarker->data_length > ICC_HEADER_SIZE)
|
||||
{
|
||||
const unsigned char* data = cmarker->data;
|
||||
std::vector<uchar>& iccp = m_metadata[IMAGE_METADATA_ICCP];
|
||||
iccp.insert(iccp.end(), data + ICC_HEADER_SIZE, data + cmarker->data_length);
|
||||
}
|
||||
}
|
||||
|
||||
state->cinfo.scale_num=1;
|
||||
state->cinfo.scale_denom = m_scale_denom;
|
||||
m_scale_denom=1; // trick! to know which decoder used scale_denom see imread_
|
||||
jpeg_calc_output_dimensions(&state->cinfo);
|
||||
m_width = state->cinfo.output_width;
|
||||
m_height = state->cinfo.output_height;
|
||||
m_type = state->cinfo.num_components > 1 ? CV_8UC3 : CV_8UC1;
|
||||
result = true;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
#ifdef CV_MANUAL_JPEG_STD_HUFF_TABLES
|
||||
/***************************************************************************
|
||||
* following code is for supporting MJPEG image files
|
||||
* based on a message of Laurent Pinchart on the video4linux mailing list
|
||||
***************************************************************************/
|
||||
|
||||
/* JPEG DHT Segment for YCrCb omitted from MJPEG data */
|
||||
static
|
||||
unsigned char my_jpeg_odml_dht[0x1a4] = {
|
||||
0xff, 0xc4, 0x01, 0xa2,
|
||||
|
||||
0x00, 0x00, 0x01, 0x05, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b,
|
||||
|
||||
0x01, 0x00, 0x03, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b,
|
||||
|
||||
0x10, 0x00, 0x02, 0x01, 0x03, 0x03, 0x02, 0x04, 0x03, 0x05, 0x05, 0x04,
|
||||
0x04, 0x00, 0x00, 0x01, 0x7d,
|
||||
0x01, 0x02, 0x03, 0x00, 0x04, 0x11, 0x05, 0x12, 0x21, 0x31, 0x41, 0x06,
|
||||
0x13, 0x51, 0x61, 0x07,
|
||||
0x22, 0x71, 0x14, 0x32, 0x81, 0x91, 0xa1, 0x08, 0x23, 0x42, 0xb1, 0xc1,
|
||||
0x15, 0x52, 0xd1, 0xf0,
|
||||
0x24, 0x33, 0x62, 0x72, 0x82, 0x09, 0x0a, 0x16, 0x17, 0x18, 0x19, 0x1a,
|
||||
0x25, 0x26, 0x27, 0x28,
|
||||
0x29, 0x2a, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x43, 0x44, 0x45,
|
||||
0x46, 0x47, 0x48, 0x49,
|
||||
0x4a, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x63, 0x64, 0x65,
|
||||
0x66, 0x67, 0x68, 0x69,
|
||||
0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x83, 0x84, 0x85,
|
||||
0x86, 0x87, 0x88, 0x89,
|
||||
0x8a, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0xa2, 0xa3,
|
||||
0xa4, 0xa5, 0xa6, 0xa7,
|
||||
0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba,
|
||||
0xc2, 0xc3, 0xc4, 0xc5,
|
||||
0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8,
|
||||
0xd9, 0xda, 0xe1, 0xe2,
|
||||
0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xf1, 0xf2, 0xf3, 0xf4,
|
||||
0xf5, 0xf6, 0xf7, 0xf8,
|
||||
0xf9, 0xfa,
|
||||
|
||||
0x11, 0x00, 0x02, 0x01, 0x02, 0x04, 0x04, 0x03, 0x04, 0x07, 0x05, 0x04,
|
||||
0x04, 0x00, 0x01, 0x02, 0x77,
|
||||
0x00, 0x01, 0x02, 0x03, 0x11, 0x04, 0x05, 0x21, 0x31, 0x06, 0x12, 0x41,
|
||||
0x51, 0x07, 0x61, 0x71,
|
||||
0x13, 0x22, 0x32, 0x81, 0x08, 0x14, 0x42, 0x91, 0xa1, 0xb1, 0xc1, 0x09,
|
||||
0x23, 0x33, 0x52, 0xf0,
|
||||
0x15, 0x62, 0x72, 0xd1, 0x0a, 0x16, 0x24, 0x34, 0xe1, 0x25, 0xf1, 0x17,
|
||||
0x18, 0x19, 0x1a, 0x26,
|
||||
0x27, 0x28, 0x29, 0x2a, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x43, 0x44,
|
||||
0x45, 0x46, 0x47, 0x48,
|
||||
0x49, 0x4a, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x63, 0x64,
|
||||
0x65, 0x66, 0x67, 0x68,
|
||||
0x69, 0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x82, 0x83,
|
||||
0x84, 0x85, 0x86, 0x87,
|
||||
0x88, 0x89, 0x8a, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a,
|
||||
0xa2, 0xa3, 0xa4, 0xa5,
|
||||
0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8,
|
||||
0xb9, 0xba, 0xc2, 0xc3,
|
||||
0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6,
|
||||
0xd7, 0xd8, 0xd9, 0xda,
|
||||
0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xf2, 0xf3, 0xf4,
|
||||
0xf5, 0xf6, 0xf7, 0xf8,
|
||||
0xf9, 0xfa
|
||||
};
|
||||
|
||||
/*
|
||||
* Parse the DHT table.
|
||||
* This code comes from jpeg6b (jdmarker.c).
|
||||
*/
|
||||
static
|
||||
int my_jpeg_load_dht (struct jpeg_decompress_struct *info, unsigned char *dht,
|
||||
JHUFF_TBL *ac_tables[], JHUFF_TBL *dc_tables[])
|
||||
{
|
||||
unsigned int length = (dht[2] << 8) + dht[3] - 2;
|
||||
unsigned int pos = 4;
|
||||
unsigned int count, i;
|
||||
int index;
|
||||
|
||||
JHUFF_TBL **hufftbl;
|
||||
unsigned char bits[17];
|
||||
unsigned char huffval[256] = {0};
|
||||
|
||||
while (length > 16)
|
||||
{
|
||||
bits[0] = 0;
|
||||
index = dht[pos++];
|
||||
count = 0;
|
||||
for (i = 1; i <= 16; ++i)
|
||||
{
|
||||
bits[i] = dht[pos++];
|
||||
count += bits[i];
|
||||
}
|
||||
length -= 17;
|
||||
|
||||
if (count > 256 || count > length)
|
||||
return -1;
|
||||
|
||||
for (i = 0; i < count; ++i)
|
||||
huffval[i] = dht[pos++];
|
||||
length -= count;
|
||||
|
||||
if (index & 0x10)
|
||||
{
|
||||
index &= ~0x10;
|
||||
hufftbl = &ac_tables[index];
|
||||
}
|
||||
else
|
||||
hufftbl = &dc_tables[index];
|
||||
|
||||
if (index < 0 || index >= NUM_HUFF_TBLS)
|
||||
return -1;
|
||||
|
||||
if (*hufftbl == NULL)
|
||||
*hufftbl = jpeg_alloc_huff_table ((j_common_ptr)info);
|
||||
if (*hufftbl == NULL)
|
||||
return -1;
|
||||
|
||||
memcpy ((*hufftbl)->bits, bits, sizeof (*hufftbl)->bits);
|
||||
memcpy ((*hufftbl)->huffval, huffval, sizeof (*hufftbl)->huffval);
|
||||
}
|
||||
|
||||
if (length != 0)
|
||||
return -1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/***************************************************************************
|
||||
* end of code for supportting MJPEG image files
|
||||
* based on a message of Laurent Pinchart on the video4linux mailing list
|
||||
***************************************************************************/
|
||||
#endif // CV_MANUAL_JPEG_STD_HUFF_TABLES
|
||||
|
||||
bool JpegDecoder::readData( Mat& img )
|
||||
{
|
||||
volatile bool result = false;
|
||||
const bool color = img.channels() > 1;
|
||||
|
||||
if( m_state && m_width && m_height )
|
||||
{
|
||||
jpeg_decompress_struct* cinfo = &((JpegState*)m_state)->cinfo;
|
||||
JpegErrorMgr* jerr = &((JpegState*)m_state)->jerr;
|
||||
|
||||
if( setjmp( jerr->setjmp_buffer ) == 0 )
|
||||
{
|
||||
#ifdef CV_MANUAL_JPEG_STD_HUFF_TABLES
|
||||
/* check if this is a mjpeg image format */
|
||||
if ( cinfo->ac_huff_tbl_ptrs[0] == NULL &&
|
||||
cinfo->ac_huff_tbl_ptrs[1] == NULL &&
|
||||
cinfo->dc_huff_tbl_ptrs[0] == NULL &&
|
||||
cinfo->dc_huff_tbl_ptrs[1] == NULL )
|
||||
{
|
||||
/* yes, this is a mjpeg image format, so load the correct
|
||||
huffman table */
|
||||
my_jpeg_load_dht( cinfo,
|
||||
my_jpeg_odml_dht,
|
||||
cinfo->ac_huff_tbl_ptrs,
|
||||
cinfo->dc_huff_tbl_ptrs );
|
||||
}
|
||||
#endif
|
||||
|
||||
// See https://github.com/opencv/opencv/issues/25274
|
||||
// Conversion CMYK->BGR is not supported in libjpeg-turbo.
|
||||
// So supporting both directly and indirectly is necessary.
|
||||
bool doDirectRead = false;
|
||||
|
||||
if( color )
|
||||
{
|
||||
if( cinfo->num_components != 4 )
|
||||
{
|
||||
#ifdef JCS_EXTENSIONS
|
||||
cinfo->out_color_space = m_use_rgb ? JCS_EXT_RGB : JCS_EXT_BGR;
|
||||
cinfo->out_color_components = 3;
|
||||
doDirectRead = true; // BGR -> BGR
|
||||
#else
|
||||
cinfo->out_color_space = JCS_RGB;
|
||||
cinfo->out_color_components = 3;
|
||||
doDirectRead = m_use_rgb ? true : false; // RGB -> BGR
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
cinfo->out_color_space = JCS_CMYK;
|
||||
cinfo->out_color_components = 4;
|
||||
doDirectRead = false; // CMYK -> BGR
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if( cinfo->num_components != 4 )
|
||||
{
|
||||
cinfo->out_color_space = JCS_GRAYSCALE;
|
||||
cinfo->out_color_components = 1;
|
||||
doDirectRead = true; // GRAY -> GRAY
|
||||
}
|
||||
else
|
||||
{
|
||||
cinfo->out_color_space = JCS_CMYK;
|
||||
cinfo->out_color_components = 4;
|
||||
doDirectRead = false; // CMYK -> GRAY
|
||||
}
|
||||
}
|
||||
|
||||
jpeg_start_decompress( cinfo );
|
||||
|
||||
if( doDirectRead)
|
||||
{
|
||||
for( int iy = 0 ; iy < m_height; iy ++ )
|
||||
{
|
||||
uchar* data = img.ptr<uchar>(iy);
|
||||
if (jpeg_read_scanlines( cinfo, &data, 1 ) != 1) return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
JSAMPARRAY buffer = (*cinfo->mem->alloc_sarray)((j_common_ptr)cinfo,
|
||||
JPOOL_IMAGE, m_width*4, 1 );
|
||||
|
||||
for( int iy = 0 ; iy < m_height; iy ++ )
|
||||
{
|
||||
uchar* data = img.ptr<uchar>(iy);
|
||||
if (jpeg_read_scanlines( cinfo, buffer, 1 ) != 1) return false;
|
||||
|
||||
if( color )
|
||||
{
|
||||
if (m_use_rgb)
|
||||
{
|
||||
if( cinfo->out_color_components == 3 )
|
||||
icvCvt_BGR2RGB_8u_C3R( buffer[0], 0, data, 0, Size(m_width,1) );
|
||||
else
|
||||
icvCvt_CMYK2RGB_8u_C4C3R( buffer[0], 0, data, 0, Size(m_width,1) );
|
||||
}
|
||||
else
|
||||
{
|
||||
if( cinfo->out_color_components == 3 )
|
||||
icvCvt_RGB2BGR_8u_C3R( buffer[0], 0, data, 0, Size(m_width,1) );
|
||||
else
|
||||
icvCvt_CMYK2BGR_8u_C4C3R( buffer[0], 0, data, 0, Size(m_width,1) );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if( cinfo->out_color_components == 1 )
|
||||
memcpy( data, buffer[0], m_width );
|
||||
else
|
||||
icvCvt_CMYK2Gray_8u_C4C1R( buffer[0], 0, data, 0, Size(m_width,1) );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result = true;
|
||||
jpeg_finish_decompress( cinfo );
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/////////////////////// JpegEncoder ///////////////////
|
||||
|
||||
struct JpegDestination
|
||||
{
|
||||
struct jpeg_destination_mgr pub;
|
||||
std::vector<uchar> *buf, *dst;
|
||||
};
|
||||
|
||||
METHODDEF(void)
|
||||
stub(j_compress_ptr)
|
||||
{
|
||||
}
|
||||
|
||||
METHODDEF(void)
|
||||
term_destination (j_compress_ptr cinfo)
|
||||
{
|
||||
JpegDestination* dest = (JpegDestination*)cinfo->dest;
|
||||
size_t sz = dest->dst->size(), bufsz = dest->buf->size() - dest->pub.free_in_buffer;
|
||||
if( bufsz > 0 )
|
||||
{
|
||||
dest->dst->resize(sz + bufsz);
|
||||
memcpy( &(*dest->dst)[0] + sz, &(*dest->buf)[0], bufsz);
|
||||
}
|
||||
}
|
||||
|
||||
METHODDEF(boolean)
|
||||
empty_output_buffer (j_compress_ptr cinfo)
|
||||
{
|
||||
JpegDestination* dest = (JpegDestination*)cinfo->dest;
|
||||
size_t sz = dest->dst->size(), bufsz = dest->buf->size();
|
||||
dest->dst->resize(sz + bufsz);
|
||||
memcpy( &(*dest->dst)[0] + sz, &(*dest->buf)[0], bufsz);
|
||||
|
||||
dest->pub.next_output_byte = &(*dest->buf)[0];
|
||||
dest->pub.free_in_buffer = bufsz;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void jpeg_buffer_dest(j_compress_ptr cinfo, JpegDestination* destination)
|
||||
{
|
||||
cinfo->dest = &destination->pub;
|
||||
|
||||
destination->pub.init_destination = stub;
|
||||
destination->pub.empty_output_buffer = empty_output_buffer;
|
||||
destination->pub.term_destination = term_destination;
|
||||
}
|
||||
|
||||
|
||||
JpegEncoder::JpegEncoder()
|
||||
{
|
||||
m_description = "JPEG files (*.jpeg;*.jpg;*.jpe)";
|
||||
m_buf_supported = true;
|
||||
m_support_metadata.assign((size_t)IMAGE_METADATA_MAX + 1, false);
|
||||
m_support_metadata[(size_t)IMAGE_METADATA_EXIF] = true;
|
||||
m_support_metadata[(size_t)IMAGE_METADATA_XMP] = true;
|
||||
m_support_metadata[(size_t)IMAGE_METADATA_ICCP] = true;
|
||||
m_supported_encode_key = {IMWRITE_JPEG_QUALITY, IMWRITE_JPEG_PROGRESSIVE, IMWRITE_JPEG_OPTIMIZE, IMWRITE_JPEG_RST_INTERVAL, IMWRITE_JPEG_LUMA_QUALITY, IMWRITE_JPEG_CHROMA_QUALITY, IMWRITE_JPEG_SAMPLING_FACTOR};
|
||||
}
|
||||
|
||||
|
||||
JpegEncoder::~JpegEncoder()
|
||||
{
|
||||
}
|
||||
|
||||
ImageEncoder JpegEncoder::newEncoder() const
|
||||
{
|
||||
return makePtr<JpegEncoder>();
|
||||
}
|
||||
|
||||
bool JpegEncoder::write( const Mat& img, const std::vector<int>& params )
|
||||
{
|
||||
m_last_error.clear();
|
||||
|
||||
struct fileWrapper
|
||||
{
|
||||
FILE* f;
|
||||
|
||||
fileWrapper() : f(0) {}
|
||||
~fileWrapper() { if(f) fclose(f); }
|
||||
};
|
||||
volatile bool result = false;
|
||||
fileWrapper fw;
|
||||
int width = img.cols, height = img.rows;
|
||||
|
||||
std::vector<uchar> out_buf(1 << 12);
|
||||
|
||||
struct jpeg_compress_struct cinfo;
|
||||
JpegErrorMgr jerr;
|
||||
JpegDestination dest;
|
||||
|
||||
cinfo.err = jpeg_std_error(&jerr.pub);
|
||||
jerr.pub.error_exit = error_exit;
|
||||
jpeg_create_compress(&cinfo);
|
||||
|
||||
if( !m_buf )
|
||||
{
|
||||
fw.f = fopen( m_filename.c_str(), "wb" );
|
||||
if( !fw.f )
|
||||
goto _exit_;
|
||||
jpeg_stdio_dest( &cinfo, fw.f );
|
||||
}
|
||||
else
|
||||
{
|
||||
dest.dst = m_buf;
|
||||
dest.buf = &out_buf;
|
||||
|
||||
jpeg_buffer_dest( &cinfo, &dest );
|
||||
|
||||
dest.pub.next_output_byte = &out_buf[0];
|
||||
dest.pub.free_in_buffer = out_buf.size();
|
||||
}
|
||||
|
||||
if( setjmp( jerr.setjmp_buffer ) == 0 )
|
||||
{
|
||||
cinfo.image_width = width;
|
||||
cinfo.image_height = height;
|
||||
|
||||
int _channels = img.channels();
|
||||
int channels = _channels > 1 ? 3 : 1;
|
||||
|
||||
bool doDirectWrite = false;
|
||||
switch( _channels )
|
||||
{
|
||||
case 1:
|
||||
cinfo.input_components = 1;
|
||||
cinfo.in_color_space = JCS_GRAYSCALE;
|
||||
doDirectWrite = true; // GRAY -> GRAY
|
||||
break;
|
||||
case 3:
|
||||
#ifdef JCS_EXTENSIONS
|
||||
cinfo.input_components = 3;
|
||||
cinfo.in_color_space = JCS_EXT_BGR;
|
||||
doDirectWrite = true; // BGR -> BGR
|
||||
#else
|
||||
cinfo.input_components = 3;
|
||||
cinfo.in_color_space = JCS_RGB;
|
||||
doDirectWrite = false; // BGR -> RGB
|
||||
#endif
|
||||
break;
|
||||
case 4:
|
||||
#ifdef JCS_EXTENSIONS
|
||||
cinfo.input_components = 4;
|
||||
cinfo.in_color_space = JCS_EXT_BGRX;
|
||||
doDirectWrite = true; // BGRX -> BGRX
|
||||
#else
|
||||
cinfo.input_components = 3;
|
||||
cinfo.in_color_space = JCS_RGB;
|
||||
doDirectWrite = false; // BGRA -> RGB
|
||||
#endif
|
||||
break;
|
||||
default:
|
||||
CV_Error(cv::Error::StsError, cv::format("Unsupported number of _channels: %06d", _channels) );
|
||||
break;
|
||||
}
|
||||
|
||||
int quality = 95;
|
||||
int progressive = 0;
|
||||
int optimize = 0;
|
||||
int rst_interval = 0;
|
||||
int luma_quality = -1;
|
||||
int chroma_quality = -1;
|
||||
uint32_t sampling_factor = 0; // same as 0x221111
|
||||
|
||||
for( size_t i = 0; i < params.size(); i += 2 )
|
||||
{
|
||||
const int value = params[i+1];
|
||||
if( params[i] == IMWRITE_JPEG_QUALITY )
|
||||
{
|
||||
quality = MIN(MAX(value, 0), 100);
|
||||
if(value != quality) {
|
||||
CV_LOG_WARNING(nullptr, cv::format("The value(%d) for IMWRITE_JPEG_QUALITY must be between 0 to 100. It is fallbacked to %d", value, quality));
|
||||
}
|
||||
}
|
||||
|
||||
if( params[i] == IMWRITE_JPEG_PROGRESSIVE )
|
||||
{
|
||||
progressive = MIN(MAX(value,0), 1);
|
||||
if(value != progressive) {
|
||||
CV_LOG_WARNING(nullptr, cv::format("The value(%d) for IMWRITE_JPEG_PROGRESSIVE must be 0 or 1. It is fallbacked to %d", value, progressive));
|
||||
}
|
||||
}
|
||||
|
||||
if( params[i] == IMWRITE_JPEG_OPTIMIZE )
|
||||
{
|
||||
optimize = MIN(MAX(value,0), 1);
|
||||
if(value != optimize) {
|
||||
CV_LOG_WARNING(nullptr, cv::format("The value(%d) for IMWRITE_JPEG_OPTIMIZE must be 0 or 1. It is fallbacked to %d", value, optimize));
|
||||
}
|
||||
}
|
||||
|
||||
if( params[i] == IMWRITE_JPEG_LUMA_QUALITY )
|
||||
{
|
||||
if (value >= 0)
|
||||
{
|
||||
luma_quality = MIN(MAX(value, 0), 100);
|
||||
if(value != luma_quality) {
|
||||
CV_LOG_WARNING(nullptr, cv::format("The value(%d) for IMWRITE_JPEG_LUMA_QUALITY must be between 0 to 100. It is fallbacked to %d.", value, luma_quality));
|
||||
}
|
||||
|
||||
quality = luma_quality;
|
||||
|
||||
if (chroma_quality < 0)
|
||||
{
|
||||
chroma_quality = luma_quality;
|
||||
}
|
||||
} else {
|
||||
CV_LOG_WARNING(nullptr, cv::format("The value(%d) for IMWRITE_JPEG_LUMA_QUALITY must be between 0 to 100. It is ignored.", value));
|
||||
}
|
||||
}
|
||||
|
||||
if( params[i] == IMWRITE_JPEG_CHROMA_QUALITY )
|
||||
{
|
||||
if (params[i+1] >= 0)
|
||||
{
|
||||
chroma_quality = MIN(MAX(value, 0), 100);
|
||||
if(value != chroma_quality) {
|
||||
CV_LOG_WARNING(nullptr, cv::format("The value(%d) for IMWRITE_JPEG_CHROMA_QUALITY must be between 0 to 100. It is fallbacked to %d.", value, chroma_quality));
|
||||
}
|
||||
} else {
|
||||
CV_LOG_WARNING(nullptr, cv::format("The value(%d) for IMWRITE_JPEG_CHROMA_QUALITY must be between 0 to 100. It is ignored.", value));
|
||||
}
|
||||
}
|
||||
|
||||
if( params[i] == IMWRITE_JPEG_RST_INTERVAL )
|
||||
{
|
||||
rst_interval = MIN(MAX(value, 0), 65535L);
|
||||
if(value != rst_interval) {
|
||||
CV_LOG_WARNING(nullptr, cv::format("The value(%d) for IMWRITE_JPEG_RST_INTERVAL must be between 0 to 65535. It is fallbacked to %d.", value, rst_interval));
|
||||
}
|
||||
}
|
||||
|
||||
if( params[i] == IMWRITE_JPEG_SAMPLING_FACTOR )
|
||||
{
|
||||
sampling_factor = static_cast<uint32_t>(value);
|
||||
|
||||
switch ( sampling_factor )
|
||||
{
|
||||
case IMWRITE_JPEG_SAMPLING_FACTOR_411:
|
||||
case IMWRITE_JPEG_SAMPLING_FACTOR_420:
|
||||
case IMWRITE_JPEG_SAMPLING_FACTOR_422:
|
||||
case IMWRITE_JPEG_SAMPLING_FACTOR_440:
|
||||
case IMWRITE_JPEG_SAMPLING_FACTOR_444:
|
||||
// OK.
|
||||
break;
|
||||
|
||||
default:
|
||||
CV_LOG_WARNING(nullptr, cv::format("The value(%d) for IMWRITE_JPEG_SAMPLING_FACTOR must be one of ImwriteJPEGSamplingFactorParams. It is fallbacked to IMWRITE_JPEG_SAMPLING_FACTOR_420.", value));
|
||||
sampling_factor = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
jpeg_set_defaults( &cinfo );
|
||||
cinfo.restart_interval = rst_interval;
|
||||
|
||||
jpeg_set_quality( &cinfo, quality,
|
||||
TRUE /* limit to baseline-JPEG values */ );
|
||||
if( progressive )
|
||||
jpeg_simple_progression( &cinfo );
|
||||
if( optimize )
|
||||
cinfo.optimize_coding = TRUE;
|
||||
|
||||
if( (channels > 1) && ( sampling_factor != 0 ) )
|
||||
{
|
||||
cinfo.comp_info[0].h_samp_factor = (sampling_factor >> 20 ) & 0xF;
|
||||
cinfo.comp_info[0].v_samp_factor = (sampling_factor >> 16 ) & 0xF;
|
||||
cinfo.comp_info[1].h_samp_factor = (sampling_factor >> 12 ) & 0xF;
|
||||
cinfo.comp_info[1].v_samp_factor = (sampling_factor >> 8 ) & 0xF;
|
||||
cinfo.comp_info[2].h_samp_factor = (sampling_factor >> 4 ) & 0xF;
|
||||
cinfo.comp_info[2].v_samp_factor = (sampling_factor >> 0 ) & 0xF;
|
||||
}
|
||||
|
||||
if (luma_quality >= 0 && chroma_quality >= 0)
|
||||
{
|
||||
#if JPEG_LIB_VERSION >= 70
|
||||
cinfo.q_scale_factor[0] = jpeg_quality_scaling(luma_quality);
|
||||
cinfo.q_scale_factor[1] = jpeg_quality_scaling(chroma_quality);
|
||||
if ( luma_quality != chroma_quality )
|
||||
{
|
||||
/* disable subsampling - ref. Libjpeg.txt */
|
||||
cinfo.comp_info[0].v_samp_factor = 1;
|
||||
cinfo.comp_info[0].h_samp_factor = 1;
|
||||
cinfo.comp_info[1].v_samp_factor = 1;
|
||||
cinfo.comp_info[1].h_samp_factor = 1;
|
||||
cinfo.comp_info[2].v_samp_factor = 1;
|
||||
cinfo.comp_info[2].h_samp_factor = 1;
|
||||
}
|
||||
jpeg_default_qtables( &cinfo, TRUE );
|
||||
#else
|
||||
// See https://github.com/opencv/opencv/issues/25646
|
||||
CV_LOG_ONCE_WARNING(NULL, cv::format("IMWRITE_JPEG_LUMA/CHROMA_QUALITY are not supported bacause JPEG_LIB_VERSION < 70."));
|
||||
#endif // #if JPEG_LIB_VERSION >= 70
|
||||
}
|
||||
|
||||
jpeg_start_compress( &cinfo, TRUE );
|
||||
|
||||
if (!m_metadata.empty()) {
|
||||
const std::vector<uchar>& metadata_exif = m_metadata[IMAGE_METADATA_EXIF];
|
||||
size_t exif_size = metadata_exif.size();
|
||||
if (exif_size > 0u) {
|
||||
const char app1_exif_prefix[] = {'E', 'x', 'i', 'f', '\0', '\0'};
|
||||
size_t app1_exif_prefix_size = sizeof(app1_exif_prefix);
|
||||
size_t data_size = exif_size + app1_exif_prefix_size;
|
||||
|
||||
std::vector<uchar> metadata_app1(data_size);
|
||||
uchar* data = metadata_app1.data();
|
||||
memcpy(data, app1_exif_prefix, app1_exif_prefix_size);
|
||||
memcpy(data + app1_exif_prefix_size, metadata_exif.data(), exif_size);
|
||||
jpeg_write_marker(&cinfo, JPEG_APP0 + 1, data, (unsigned)data_size);
|
||||
}
|
||||
|
||||
const std::vector<uchar>& metadata_xmp = m_metadata[IMAGE_METADATA_XMP];
|
||||
size_t xmp_size = metadata_xmp.size();
|
||||
if (xmp_size > 0u) {
|
||||
jpeg_write_marker(&cinfo, JPEG_APP0 + 1, metadata_xmp.data(), (unsigned)xmp_size);
|
||||
}
|
||||
|
||||
const std::vector<uchar>& metadata_iccp = m_metadata[IMAGE_METADATA_ICCP];
|
||||
size_t iccp_size = metadata_iccp.size();
|
||||
if (iccp_size > 0u) {
|
||||
const char app1_iccp_prefix[] = {'I','C','C','_','P','R','O','F','I','L','E','\0','\1','\1'};
|
||||
size_t app1_iccp_prefix_size = sizeof(app1_iccp_prefix);
|
||||
size_t data_size = iccp_size + app1_iccp_prefix_size;
|
||||
|
||||
std::vector<uchar> metadata_app1(data_size);
|
||||
uchar* data = metadata_app1.data();
|
||||
memcpy(data, app1_iccp_prefix, app1_iccp_prefix_size);
|
||||
memcpy(data + app1_iccp_prefix_size, metadata_iccp.data(), iccp_size);
|
||||
jpeg_write_marker(&cinfo, JPEG_APP0 + 2, data, (unsigned)data_size);
|
||||
}
|
||||
}
|
||||
|
||||
if( doDirectWrite )
|
||||
{
|
||||
for( int y = 0; y < height; y++ )
|
||||
{
|
||||
uchar *data = const_cast<uchar*>(img.ptr<uchar>(y));
|
||||
jpeg_write_scanlines( &cinfo, &data, 1 );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
CV_Check(_channels, (_channels == 3) || (_channels == 4), "Unsupported number of channels(indirect write)");
|
||||
|
||||
AutoBuffer<uchar> _buffer;
|
||||
_buffer.allocate(width*channels);
|
||||
uchar *buffer = _buffer.data();
|
||||
|
||||
for( int y = 0; y < height; y++ )
|
||||
{
|
||||
uchar *data = const_cast<uchar*>(img.ptr<uchar>(y));
|
||||
if( _channels == 3 )
|
||||
{
|
||||
icvCvt_BGR2RGB_8u_C3R( data, 0, buffer, 0, Size(width,1) );
|
||||
}
|
||||
else // if( _channels == 4 )
|
||||
{
|
||||
icvCvt_BGRA2BGR_8u_C4C3R( data, 0, buffer, 0, Size(width,1), 2 );
|
||||
}
|
||||
jpeg_write_scanlines( &cinfo, &buffer, 1 );
|
||||
}
|
||||
}
|
||||
|
||||
jpeg_finish_compress( &cinfo );
|
||||
result = true;
|
||||
}
|
||||
|
||||
_exit_:
|
||||
|
||||
if(!result)
|
||||
{
|
||||
char jmsg_buf[JMSG_LENGTH_MAX];
|
||||
jerr.pub.format_message((j_common_ptr)&cinfo, jmsg_buf);
|
||||
m_last_error = jmsg_buf;
|
||||
}
|
||||
|
||||
jpeg_destroy_compress( &cinfo );
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
/* End of file. */
|
||||
@@ -0,0 +1,113 @@
|
||||
/*M///////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
|
||||
//
|
||||
// By downloading, copying, installing or using the software you agree to this license.
|
||||
// If you do not agree to this license, do not download, install,
|
||||
// copy or use the software.
|
||||
//
|
||||
//
|
||||
// License Agreement
|
||||
// For Open Source Computer Vision Library
|
||||
//
|
||||
// Copyright (C) 2000-2008, Intel Corporation, all rights reserved.
|
||||
// Copyright (C) 2009, Willow Garage Inc., all rights reserved.
|
||||
// Third party copyrights are property of their respective owners.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without modification,
|
||||
// are permitted provided that the following conditions are met:
|
||||
//
|
||||
// * Redistribution's of source code must retain the above copyright notice,
|
||||
// this list of conditions and the following disclaimer.
|
||||
//
|
||||
// * Redistribution's in binary form must reproduce the above copyright notice,
|
||||
// this list of conditions and the following disclaimer in the documentation
|
||||
// and/or other materials provided with the distribution.
|
||||
//
|
||||
// * The name of the copyright holders may not be used to endorse or promote products
|
||||
// derived from this software without specific prior written permission.
|
||||
//
|
||||
// This software is provided by the copyright holders and contributors "as is" and
|
||||
// any express or implied warranties, including, but not limited to, the implied
|
||||
// warranties of merchantability and fitness for a particular purpose are disclaimed.
|
||||
// In no event shall the Intel Corporation or contributors be liable for any direct,
|
||||
// indirect, incidental, special, exemplary, or consequential damages
|
||||
// (including, but not limited to, procurement of substitute goods or services;
|
||||
// loss of use, data, or profits; or business interruption) however caused
|
||||
// and on any theory of liability, whether in contract, strict liability,
|
||||
// or tort (including negligence or otherwise) arising in any way out of
|
||||
// the use of this software, even if advised of the possibility of such damage.
|
||||
//
|
||||
//M*/
|
||||
|
||||
#ifndef _GRFMT_JPEG_H_
|
||||
#define _GRFMT_JPEG_H_
|
||||
|
||||
#include "grfmt_base.hpp"
|
||||
#include "bitstrm.hpp"
|
||||
|
||||
#ifdef HAVE_JPEG
|
||||
|
||||
// IJG-based Jpeg codec
|
||||
|
||||
namespace cv
|
||||
{
|
||||
/**
|
||||
* @brief Jpeg markers that can be encountered in a Jpeg file
|
||||
*/
|
||||
enum AppMarkerTypes
|
||||
{
|
||||
SOI = 0xD8, SOF0 = 0xC0, SOF2 = 0xC2, DHT = 0xC4,
|
||||
DQT = 0xDB, DRI = 0xDD, SOS = 0xDA,
|
||||
|
||||
RST0 = 0xD0, RST1 = 0xD1, RST2 = 0xD2, RST3 = 0xD3,
|
||||
RST4 = 0xD4, RST5 = 0xD5, RST6 = 0xD6, RST7 = 0xD7,
|
||||
|
||||
APP0 = 0xE0, APP1 = 0xE1, APP2 = 0xE2, APP3 = 0xE3,
|
||||
APP4 = 0xE4, APP5 = 0xE5, APP6 = 0xE6, APP7 = 0xE7,
|
||||
APP8 = 0xE8, APP9 = 0xE9, APP10 = 0xEA, APP11 = 0xEB,
|
||||
APP12 = 0xEC, APP13 = 0xED, APP14 = 0xEE, APP15 = 0xEF,
|
||||
|
||||
COM = 0xFE, EOI = 0xD9
|
||||
};
|
||||
|
||||
|
||||
class JpegDecoder CV_FINAL : public BaseImageDecoder
|
||||
{
|
||||
public:
|
||||
|
||||
JpegDecoder();
|
||||
virtual ~JpegDecoder();
|
||||
|
||||
bool readData( Mat& img ) CV_OVERRIDE;
|
||||
bool readHeader() CV_OVERRIDE;
|
||||
void close();
|
||||
|
||||
ImageDecoder newDecoder() const CV_OVERRIDE;
|
||||
|
||||
protected:
|
||||
|
||||
FILE* m_f;
|
||||
void* m_state;
|
||||
|
||||
private:
|
||||
JpegDecoder(const JpegDecoder &); // copy disabled
|
||||
JpegDecoder& operator=(const JpegDecoder &); // assign disabled
|
||||
};
|
||||
|
||||
|
||||
class JpegEncoder CV_FINAL : public BaseImageEncoder
|
||||
{
|
||||
public:
|
||||
JpegEncoder();
|
||||
virtual ~JpegEncoder();
|
||||
|
||||
bool write( const Mat& img, const std::vector<int>& params ) CV_OVERRIDE;
|
||||
ImageEncoder newEncoder() const CV_OVERRIDE;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#endif/*_GRFMT_JPEG_H_*/
|
||||
@@ -0,0 +1,651 @@
|
||||
/*M///////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
|
||||
//
|
||||
// By downloading, copying, installing or using the software you agree to this license.
|
||||
// If you do not agree to this license, do not download, install,
|
||||
// copy or use the software.
|
||||
//
|
||||
//
|
||||
// License Agreement
|
||||
// For Open Source Computer Vision Library
|
||||
//
|
||||
// Copyright (C) 2000-2008, Intel Corporation, all rights reserved.
|
||||
// Copyright (C) 2009, Willow Garage Inc., all rights reserved.
|
||||
// Third party copyrights are property of their respective owners.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without modification,
|
||||
// are permitted provided that the following conditions are met:
|
||||
//
|
||||
// * Redistribution's of source code must retain the above copyright notice,
|
||||
// this list of conditions and the following disclaimer.
|
||||
//
|
||||
// * Redistribution's in binary form must reproduce the above copyright notice,
|
||||
// this list of conditions and the following disclaimer in the documentation
|
||||
// and/or other materials provided with the distribution.
|
||||
//
|
||||
// * The name of the copyright holders may not be used to endorse or promote products
|
||||
// derived from this software without specific prior written permission.
|
||||
//
|
||||
// This software is provided by the copyright holders and contributors "as is" and
|
||||
// any express or implied warranties, including, but not limited to, the implied
|
||||
// warranties of merchantability and fitness for a particular purpose are disclaimed.
|
||||
// In no event shall the Intel Corporation or contributors be liable for any direct,
|
||||
// indirect, incidental, special, exemplary, or consequential damages
|
||||
// (including, but not limited to, procurement of substitute goods or services;
|
||||
// loss of use, data, or profits; or business interruption) however caused
|
||||
// and on any theory of liability, whether in contract, strict liability,
|
||||
// or tort (including negligence or otherwise) arising in any way out of
|
||||
// the use of this software, even if advised of the possibility of such damage.
|
||||
//
|
||||
//M*/
|
||||
|
||||
#include "precomp.hpp"
|
||||
|
||||
#ifdef HAVE_JASPER
|
||||
#include <sstream>
|
||||
|
||||
#include <opencv2/core/utils/configuration.private.hpp>
|
||||
#include <opencv2/core/utils/logger.hpp>
|
||||
|
||||
#include "grfmt_jpeg2000.hpp"
|
||||
#include "opencv2/imgproc.hpp"
|
||||
|
||||
#ifdef _WIN32
|
||||
#define JAS_WIN_MSVC_BUILD 1
|
||||
#ifdef __GNUC__
|
||||
#define HAVE_STDINT_H 1
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#undef VERSION
|
||||
|
||||
#include <jasper/jasper.h>
|
||||
// FIXME bad hack
|
||||
#undef uchar
|
||||
#undef ulong
|
||||
|
||||
namespace cv
|
||||
{
|
||||
|
||||
struct JasperInitializer
|
||||
{
|
||||
JasperInitializer() { jas_init(); }
|
||||
~JasperInitializer() { jas_cleanup(); }
|
||||
};
|
||||
|
||||
static JasperInitializer& _initJasper()
|
||||
{
|
||||
static JasperInitializer initialize_jasper;
|
||||
return initialize_jasper;
|
||||
}
|
||||
|
||||
static bool isJasperEnabled()
|
||||
{
|
||||
static const bool PARAM_ENABLE_JASPER = utils::getConfigurationParameterBool("OPENCV_IO_ENABLE_JASPER",
|
||||
#ifdef OPENCV_IMGCODECS_FORCE_JASPER
|
||||
true
|
||||
#else
|
||||
false
|
||||
#endif
|
||||
);
|
||||
return PARAM_ENABLE_JASPER;
|
||||
}
|
||||
static JasperInitializer& initJasper()
|
||||
{
|
||||
if (isJasperEnabled())
|
||||
{
|
||||
return _initJasper();
|
||||
}
|
||||
else
|
||||
{
|
||||
const char* message = "imgcodecs: Jasper (JPEG-2000) codec is disabled. You can enable it via 'OPENCV_IO_ENABLE_JASPER' option. Refer for details and cautions here: https://github.com/opencv/opencv/issues/14058";
|
||||
CV_LOG_WARNING(NULL, message);
|
||||
CV_Error(Error::StsNotImplemented, message);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/////////////////////// Jpeg2KDecoder ///////////////////
|
||||
|
||||
Jpeg2KDecoder::Jpeg2KDecoder()
|
||||
{
|
||||
static const unsigned char signature_[12] = { 0, 0, 0, 0x0c, 'j', 'P', ' ', ' ', 13, 10, 0x87, 10};
|
||||
m_signature = String((const char*)signature_, (const char*)signature_ + sizeof(signature_));
|
||||
m_stream = 0;
|
||||
m_image = 0;
|
||||
}
|
||||
|
||||
|
||||
Jpeg2KDecoder::~Jpeg2KDecoder()
|
||||
{
|
||||
}
|
||||
|
||||
ImageDecoder Jpeg2KDecoder::newDecoder() const
|
||||
{
|
||||
initJasper();
|
||||
return makePtr<Jpeg2KDecoder>();
|
||||
}
|
||||
|
||||
void Jpeg2KDecoder::close()
|
||||
{
|
||||
if( m_stream )
|
||||
{
|
||||
CV_Assert(isJasperEnabled());
|
||||
jas_stream_close( (jas_stream_t*)m_stream );
|
||||
m_stream = 0;
|
||||
}
|
||||
|
||||
if( m_image )
|
||||
{
|
||||
CV_Assert(isJasperEnabled());
|
||||
jas_image_destroy( (jas_image_t*)m_image );
|
||||
m_image = 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool Jpeg2KDecoder::readHeader()
|
||||
{
|
||||
CV_Assert(isJasperEnabled());
|
||||
bool result = false;
|
||||
|
||||
close();
|
||||
jas_stream_t* stream = jas_stream_fopen( m_filename.c_str(), "rb" );
|
||||
m_stream = stream;
|
||||
|
||||
if( stream )
|
||||
{
|
||||
jas_image_t* image = jas_image_decode( stream, -1, 0 );
|
||||
m_image = image;
|
||||
if( image ) {
|
||||
CV_Assert(0 == (jas_image_tlx(image)) && "not supported");
|
||||
CV_Assert(0 == (jas_image_tly(image)) && "not supported");
|
||||
m_width = jas_image_width( image );
|
||||
m_height = jas_image_height( image );
|
||||
|
||||
int cntcmpts = 0; // count the known components
|
||||
int numcmpts = jas_image_numcmpts( image );
|
||||
int depth = 0;
|
||||
for( int i = 0; i < numcmpts; i++ )
|
||||
{
|
||||
int depth_i = jas_image_cmptprec( image, i );
|
||||
CV_Assert(depth == 0 || depth == depth_i); // component data type mismatch
|
||||
depth = MAX(depth, depth_i);
|
||||
if( jas_image_cmpttype( image, i ) > 2 )
|
||||
continue;
|
||||
int sgnd = jas_image_cmptsgnd(image, i);
|
||||
int xstart = jas_image_cmpttlx(image, i);
|
||||
int xend = jas_image_cmptbrx(image, i);
|
||||
int xstep = jas_image_cmpthstep(image, i);
|
||||
int ystart = jas_image_cmpttly(image, i);
|
||||
int yend = jas_image_cmptbry(image, i);
|
||||
int ystep = jas_image_cmptvstep(image, i);
|
||||
CV_Assert(sgnd == 0 && "not supported");
|
||||
CV_Assert(xstart == 0 && "not supported");
|
||||
CV_Assert(ystart == 0 && "not supported");
|
||||
CV_Assert(xstep == 1 && "not supported");
|
||||
CV_Assert(ystep == 1 && "not supported");
|
||||
CV_Assert(xend == m_width);
|
||||
CV_Assert(yend == m_height);
|
||||
cntcmpts++;
|
||||
}
|
||||
|
||||
if( cntcmpts )
|
||||
{
|
||||
CV_Assert(depth == 8 || depth == 16);
|
||||
CV_Assert(cntcmpts == 1 || cntcmpts == 3);
|
||||
m_type = CV_MAKETYPE(depth <= 8 ? CV_8U : CV_16U, cntcmpts > 1 ? 3 : 1);
|
||||
result = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if( !result )
|
||||
close();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static void Jpeg2KDecoder_close(Jpeg2KDecoder* ptr)
|
||||
{
|
||||
ptr->close();
|
||||
}
|
||||
|
||||
bool Jpeg2KDecoder::readData( Mat& img )
|
||||
{
|
||||
CV_Assert(isJasperEnabled());
|
||||
|
||||
Ptr<Jpeg2KDecoder> close_this(this, Jpeg2KDecoder_close);
|
||||
bool result = false;
|
||||
bool color = img.channels() > 1;
|
||||
uchar* data = img.ptr();
|
||||
size_t step = img.step;
|
||||
jas_stream_t* stream = (jas_stream_t*)m_stream;
|
||||
jas_image_t* image = (jas_image_t*)m_image;
|
||||
|
||||
#ifndef _WIN32
|
||||
// At least on some Linux instances the
|
||||
// system libjasper segfaults when
|
||||
// converting color to grey.
|
||||
// We do this conversion manually at the end.
|
||||
Mat clr;
|
||||
if (CV_MAT_CN(img.type()) < CV_MAT_CN(this->type()))
|
||||
{
|
||||
clr.create(img.size().height, img.size().width, this->type());
|
||||
color = true;
|
||||
data = clr.ptr();
|
||||
step = (int)clr.step;
|
||||
}
|
||||
#endif
|
||||
|
||||
if( stream && image )
|
||||
{
|
||||
bool convert;
|
||||
int colorspace;
|
||||
if( color )
|
||||
{
|
||||
convert = (jas_image_clrspc( image ) != JAS_CLRSPC_SRGB);
|
||||
colorspace = JAS_CLRSPC_SRGB;
|
||||
}
|
||||
else
|
||||
{
|
||||
convert = (jas_clrspc_fam( jas_image_clrspc( image ) ) != JAS_CLRSPC_FAM_GRAY);
|
||||
colorspace = JAS_CLRSPC_SGRAY; // TODO GENGRAY or SGRAY? (GENGRAY fails on Win.)
|
||||
}
|
||||
|
||||
// convert to the desired colorspace
|
||||
if( convert )
|
||||
{
|
||||
jas_cmprof_t *clrprof = jas_cmprof_createfromclrspc( colorspace );
|
||||
if( clrprof )
|
||||
{
|
||||
jas_image_t *_img = jas_image_chclrspc( image, clrprof, JAS_CMXFORM_INTENT_RELCLR );
|
||||
if( _img )
|
||||
{
|
||||
jas_image_destroy( image );
|
||||
m_image = image = _img;
|
||||
result = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
jas_cmprof_destroy(clrprof);
|
||||
CV_Error(Error::StsError, "JPEG 2000 LOADER ERROR: cannot convert colorspace");
|
||||
}
|
||||
jas_cmprof_destroy( clrprof );
|
||||
}
|
||||
else
|
||||
{
|
||||
CV_Error(Error::StsError, "JPEG 2000 LOADER ERROR: unable to create colorspace");
|
||||
}
|
||||
}
|
||||
else
|
||||
result = true;
|
||||
|
||||
if( result )
|
||||
{
|
||||
int ncmpts;
|
||||
int cmptlut[3];
|
||||
int swap_rb = m_use_rgb ? 0 : 2;
|
||||
if( color )
|
||||
{
|
||||
cmptlut[0] = jas_image_getcmptbytype( image, swap_rb );
|
||||
cmptlut[1] = jas_image_getcmptbytype( image, 1 );
|
||||
cmptlut[2] = jas_image_getcmptbytype( image, swap_rb^2 );
|
||||
if( cmptlut[0] < 0 || cmptlut[1] < 0 || cmptlut[2] < 0 )
|
||||
result = false;
|
||||
ncmpts = 3;
|
||||
}
|
||||
else
|
||||
{
|
||||
cmptlut[0] = jas_image_getcmptbytype( image, JAS_IMAGE_CT_GRAY_Y );
|
||||
if( cmptlut[0] < 0 )
|
||||
result = false;
|
||||
ncmpts = 1;
|
||||
}
|
||||
|
||||
if( result )
|
||||
{
|
||||
for( int i = 0; i < ncmpts; i++ )
|
||||
{
|
||||
int maxval = 1 << jas_image_cmptprec( image, cmptlut[i] );
|
||||
int offset = jas_image_cmptsgnd( image, cmptlut[i] ) ? maxval / 2 : 0;
|
||||
|
||||
int yend = jas_image_cmptbry( image, cmptlut[i] );
|
||||
int ystep = jas_image_cmptvstep( image, cmptlut[i] );
|
||||
int xend = jas_image_cmptbrx( image, cmptlut[i] );
|
||||
int xstep = jas_image_cmpthstep( image, cmptlut[i] );
|
||||
|
||||
jas_matrix_t *buffer = jas_matrix_create( yend / ystep, xend / xstep );
|
||||
if( buffer )
|
||||
{
|
||||
if( !jas_image_readcmpt( image, cmptlut[i], 0, 0, xend / xstep, yend / ystep, buffer ))
|
||||
{
|
||||
if( img.depth() == CV_8U )
|
||||
result = readComponent8u( data + i, buffer, validateToInt(step), cmptlut[i], maxval, offset, ncmpts );
|
||||
else
|
||||
result = readComponent16u( ((unsigned short *)data) + i, buffer, validateToInt(step / 2), cmptlut[i], maxval, offset, ncmpts );
|
||||
if( !result )
|
||||
{
|
||||
jas_matrix_destroy( buffer );
|
||||
CV_Error(Error::StsError, "JPEG2000 LOADER ERROR: failed to read component");
|
||||
}
|
||||
}
|
||||
jas_matrix_destroy( buffer );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
CV_Error(Error::StsError, "JPEG2000 LOADER ERROR: colorspace conversion failed");
|
||||
}
|
||||
}
|
||||
|
||||
CV_Assert(result == true);
|
||||
|
||||
#ifndef _WIN32
|
||||
if (!clr.empty())
|
||||
{
|
||||
cv::cvtColor(clr, img, COLOR_BGR2GRAY);
|
||||
}
|
||||
#endif
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
bool Jpeg2KDecoder::readComponent8u( uchar *data, void *_buffer,
|
||||
int step, int cmpt,
|
||||
int maxval, int offset, int ncmpts )
|
||||
{
|
||||
CV_Assert(isJasperEnabled());
|
||||
|
||||
jas_matrix_t* buffer = (jas_matrix_t*)_buffer;
|
||||
jas_image_t* image = (jas_image_t*)m_image;
|
||||
int xstart = jas_image_cmpttlx( image, cmpt );
|
||||
int xend = jas_image_cmptbrx( image, cmpt );
|
||||
int xstep = jas_image_cmpthstep( image, cmpt );
|
||||
int xoffset = jas_image_tlx( image );
|
||||
int ystart = jas_image_cmpttly( image, cmpt );
|
||||
int yend = jas_image_cmptbry( image, cmpt );
|
||||
int ystep = jas_image_cmptvstep( image, cmpt );
|
||||
int yoffset = jas_image_tly( image );
|
||||
int x, y, x1, y1, j;
|
||||
int rshift = cvRound(std::log(maxval/256.)/std::log(2.));
|
||||
int lshift = MAX(0, -rshift);
|
||||
rshift = MAX(0, rshift);
|
||||
int delta = (rshift > 0 ? 1 << (rshift - 1) : 0) + offset;
|
||||
|
||||
for( y = 0; y < yend - ystart; )
|
||||
{
|
||||
jas_seqent_t* pix_row = jas_matrix_getref( buffer, y / ystep, 0 );
|
||||
uchar* dst = data + (y - yoffset) * step - xoffset;
|
||||
|
||||
if( xstep == 1 )
|
||||
{
|
||||
if( maxval == 256 && offset == 0 )
|
||||
for( x = 0; x < xend - xstart; x++ )
|
||||
{
|
||||
int pix = pix_row[x];
|
||||
dst[x*ncmpts] = cv::saturate_cast<uchar>(pix);
|
||||
}
|
||||
else
|
||||
for( x = 0; x < xend - xstart; x++ )
|
||||
{
|
||||
int pix = ((pix_row[x] + delta) >> rshift) << lshift;
|
||||
dst[x*ncmpts] = cv::saturate_cast<uchar>(pix);
|
||||
}
|
||||
}
|
||||
else if( xstep == 2 && offset == 0 )
|
||||
for( x = 0, j = 0; x < xend - xstart; x += 2, j++ )
|
||||
{
|
||||
int pix = ((pix_row[j] + delta) >> rshift) << lshift;
|
||||
dst[x*ncmpts] = dst[(x+1)*ncmpts] = cv::saturate_cast<uchar>(pix);
|
||||
}
|
||||
else
|
||||
for( x = 0, j = 0; x < xend - xstart; j++ )
|
||||
{
|
||||
int pix = ((pix_row[j] + delta) >> rshift) << lshift;
|
||||
pix = cv::saturate_cast<uchar>(pix);
|
||||
for( x1 = x + xstep; x < x1; x++ )
|
||||
dst[x*ncmpts] = (uchar)pix;
|
||||
}
|
||||
y1 = y + ystep;
|
||||
for( ++y; y < y1; y++, dst += step )
|
||||
for( x = 0; x < xend - xstart; x++ )
|
||||
dst[x*ncmpts + step] = dst[x*ncmpts];
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool Jpeg2KDecoder::readComponent16u( unsigned short *data, void *_buffer,
|
||||
int step, int cmpt,
|
||||
int maxval, int offset, int ncmpts )
|
||||
{
|
||||
CV_Assert(isJasperEnabled());
|
||||
|
||||
jas_matrix_t* buffer = (jas_matrix_t*)_buffer;
|
||||
jas_image_t* image = (jas_image_t*)m_image;
|
||||
int xstart = jas_image_cmpttlx( image, cmpt );
|
||||
int xend = jas_image_cmptbrx( image, cmpt );
|
||||
int xstep = jas_image_cmpthstep( image, cmpt );
|
||||
int xoffset = jas_image_tlx( image );
|
||||
int ystart = jas_image_cmpttly( image, cmpt );
|
||||
int yend = jas_image_cmptbry( image, cmpt );
|
||||
int ystep = jas_image_cmptvstep( image, cmpt );
|
||||
int yoffset = jas_image_tly( image );
|
||||
int x, y, x1, y1, j;
|
||||
int rshift = cvRound(std::log(maxval/65536.)/std::log(2.));
|
||||
int lshift = MAX(0, -rshift);
|
||||
rshift = MAX(0, rshift);
|
||||
int delta = (rshift > 0 ? 1 << (rshift - 1) : 0) + offset;
|
||||
|
||||
for( y = 0; y < yend - ystart; )
|
||||
{
|
||||
jas_seqent_t* pix_row = jas_matrix_getref( buffer, y / ystep, 0 );
|
||||
ushort* dst = data + (y - yoffset) * step - xoffset;
|
||||
|
||||
if( xstep == 1 )
|
||||
{
|
||||
if( maxval == 65536 && offset == 0 )
|
||||
for( x = 0; x < xend - xstart; x++ )
|
||||
{
|
||||
int pix = pix_row[x];
|
||||
dst[x*ncmpts] = cv::saturate_cast<ushort>(pix);
|
||||
}
|
||||
else
|
||||
for( x = 0; x < xend - xstart; x++ )
|
||||
{
|
||||
int pix = ((pix_row[x] + delta) >> rshift) << lshift;
|
||||
dst[x*ncmpts] = cv::saturate_cast<ushort>(pix);
|
||||
}
|
||||
}
|
||||
else if( xstep == 2 && offset == 0 )
|
||||
for( x = 0, j = 0; x < xend - xstart; x += 2, j++ )
|
||||
{
|
||||
int pix = ((pix_row[j] + delta) >> rshift) << lshift;
|
||||
dst[x*ncmpts] = dst[(x+1)*ncmpts] = cv::saturate_cast<ushort>(pix);
|
||||
}
|
||||
else
|
||||
for( x = 0, j = 0; x < xend - xstart; j++ )
|
||||
{
|
||||
int pix = ((pix_row[j] + delta) >> rshift) << lshift;
|
||||
pix = cv::saturate_cast<ushort>(pix);
|
||||
for( x1 = x + xstep; x < x1; x++ )
|
||||
dst[x*ncmpts] = (ushort)pix;
|
||||
}
|
||||
y1 = y + ystep;
|
||||
for( ++y; y < y1; y++, dst += step )
|
||||
for( x = 0; x < xend - xstart; x++ )
|
||||
dst[x*ncmpts + step] = dst[x*ncmpts];
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/////////////////////// Jpeg2KEncoder ///////////////////
|
||||
|
||||
|
||||
Jpeg2KEncoder::Jpeg2KEncoder()
|
||||
{
|
||||
m_description = "JPEG-2000 files (*.jp2)";
|
||||
m_supported_encode_key = {IMWRITE_JPEG2000_COMPRESSION_X1000};
|
||||
}
|
||||
|
||||
|
||||
Jpeg2KEncoder::~Jpeg2KEncoder()
|
||||
{
|
||||
}
|
||||
|
||||
ImageEncoder Jpeg2KEncoder::newEncoder() const
|
||||
{
|
||||
initJasper();
|
||||
return makePtr<Jpeg2KEncoder>();
|
||||
}
|
||||
|
||||
bool Jpeg2KEncoder::isFormatSupported( int depth ) const
|
||||
{
|
||||
return depth == CV_8U || depth == CV_16U;
|
||||
}
|
||||
|
||||
|
||||
bool Jpeg2KEncoder::write( const Mat& _img, const std::vector<int>& params )
|
||||
{
|
||||
CV_Assert(isJasperEnabled());
|
||||
int width = _img.cols, height = _img.rows;
|
||||
int depth = _img.depth(), channels = _img.channels();
|
||||
depth = depth == CV_8U ? 8 : 16;
|
||||
|
||||
if( channels > 3 || channels < 1 )
|
||||
return false;
|
||||
|
||||
CV_Assert(params.size() % 2 == 0);
|
||||
double target_compression_rate = 1.0;
|
||||
for( size_t i = 0; i < params.size(); i += 2 )
|
||||
{
|
||||
const int value = params[i+1];
|
||||
switch(params[i])
|
||||
{
|
||||
case cv::IMWRITE_JPEG2000_COMPRESSION_X1000:
|
||||
{
|
||||
const int compression = std::min(std::max(value, 0), 1000);
|
||||
target_compression_rate = static_cast<double>(compression) / 1000.0;
|
||||
if(value != compression){
|
||||
CV_LOG_WARNING(nullptr, cv::format("The value(%d) for IMWRITE_JPEG2000_COMPRESSION_X1000 must be between 0 to 1000. It is fallbacked to %d", value, compression));
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
jas_image_cmptparm_t component_info[3];
|
||||
for( int i = 0; i < channels; i++ )
|
||||
{
|
||||
component_info[i].tlx = 0;
|
||||
component_info[i].tly = 0;
|
||||
component_info[i].hstep = 1;
|
||||
component_info[i].vstep = 1;
|
||||
component_info[i].width = width;
|
||||
component_info[i].height = height;
|
||||
component_info[i].prec = depth;
|
||||
component_info[i].sgnd = 0;
|
||||
}
|
||||
jas_image_t *img = jas_image_create( channels, component_info, (channels == 1) ? JAS_CLRSPC_SGRAY : JAS_CLRSPC_SRGB );
|
||||
if( !img )
|
||||
return false;
|
||||
|
||||
if(channels == 1)
|
||||
jas_image_setcmpttype( img, 0, JAS_IMAGE_CT_GRAY_Y );
|
||||
else
|
||||
{
|
||||
jas_image_setcmpttype( img, 0, JAS_IMAGE_CT_RGB_B );
|
||||
jas_image_setcmpttype( img, 1, JAS_IMAGE_CT_RGB_G );
|
||||
jas_image_setcmpttype( img, 2, JAS_IMAGE_CT_RGB_R );
|
||||
}
|
||||
|
||||
bool result;
|
||||
if( depth == 8 )
|
||||
result = writeComponent8u( img, _img );
|
||||
else
|
||||
result = writeComponent16u( img, _img );
|
||||
if( result )
|
||||
{
|
||||
jas_stream_t *stream = jas_stream_fopen( m_filename.c_str(), "wb" );
|
||||
if( stream )
|
||||
{
|
||||
std::stringstream options;
|
||||
options << "rate=" << target_compression_rate;
|
||||
|
||||
result = !jas_image_encode( img, stream, jas_image_strtofmt( (char*)"jp2" ), (char*)options.str().c_str() );
|
||||
|
||||
jas_stream_close( stream );
|
||||
}
|
||||
|
||||
}
|
||||
jas_image_destroy( img );
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
bool Jpeg2KEncoder::writeComponent8u( void *__img, const Mat& _img )
|
||||
{
|
||||
CV_Assert(isJasperEnabled());
|
||||
|
||||
jas_image_t* img = (jas_image_t*)__img;
|
||||
int w = _img.cols, h = _img.rows, ncmpts = _img.channels();
|
||||
jas_matrix_t *row = jas_matrix_create( 1, w );
|
||||
if(!row)
|
||||
return false;
|
||||
|
||||
for( int y = 0; y < h; y++ )
|
||||
{
|
||||
const uchar* data = _img.ptr(y);
|
||||
for( int i = 0; i < ncmpts; i++ )
|
||||
{
|
||||
for( int x = 0; x < w; x++)
|
||||
jas_matrix_setv( row, x, data[x * ncmpts + i] );
|
||||
jas_image_writecmpt( img, i, 0, y, w, 1, row );
|
||||
}
|
||||
}
|
||||
|
||||
jas_matrix_destroy( row );
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool Jpeg2KEncoder::writeComponent16u( void *__img, const Mat& _img )
|
||||
{
|
||||
CV_Assert(isJasperEnabled());
|
||||
|
||||
jas_image_t* img = (jas_image_t*)__img;
|
||||
int w = _img.cols, h = _img.rows, ncmpts = _img.channels();
|
||||
jas_matrix_t *row = jas_matrix_create( 1, w );
|
||||
if(!row)
|
||||
return false;
|
||||
|
||||
for( int y = 0; y < h; y++ )
|
||||
{
|
||||
const ushort* data = _img.ptr<ushort>(y);
|
||||
for( int i = 0; i < ncmpts; i++ )
|
||||
{
|
||||
for( int x = 0; x < w; x++)
|
||||
jas_matrix_setv( row, x, data[x * ncmpts + i] );
|
||||
jas_image_writecmpt( img, i, 0, y, w, 1, row );
|
||||
}
|
||||
}
|
||||
|
||||
jas_matrix_destroy( row );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
/* End of file. */
|
||||
@@ -0,0 +1,95 @@
|
||||
/*M///////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
|
||||
//
|
||||
// By downloading, copying, installing or using the software you agree to this license.
|
||||
// If you do not agree to this license, do not download, install,
|
||||
// copy or use the software.
|
||||
//
|
||||
//
|
||||
// License Agreement
|
||||
// For Open Source Computer Vision Library
|
||||
//
|
||||
// Copyright (C) 2000-2008, Intel Corporation, all rights reserved.
|
||||
// Copyright (C) 2009, Willow Garage Inc., all rights reserved.
|
||||
// Third party copyrights are property of their respective owners.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without modification,
|
||||
// are permitted provided that the following conditions are met:
|
||||
//
|
||||
// * Redistribution's of source code must retain the above copyright notice,
|
||||
// this list of conditions and the following disclaimer.
|
||||
//
|
||||
// * Redistribution's in binary form must reproduce the above copyright notice,
|
||||
// this list of conditions and the following disclaimer in the documentation
|
||||
// and/or other materials provided with the distribution.
|
||||
//
|
||||
// * The name of the copyright holders may not be used to endorse or promote products
|
||||
// derived from this software without specific prior written permission.
|
||||
//
|
||||
// This software is provided by the copyright holders and contributors "as is" and
|
||||
// any express or implied warranties, including, but not limited to, the implied
|
||||
// warranties of merchantability and fitness for a particular purpose are disclaimed.
|
||||
// In no event shall the Intel Corporation or contributors be liable for any direct,
|
||||
// indirect, incidental, special, exemplary, or consequential damages
|
||||
// (including, but not limited to, procurement of substitute goods or services;
|
||||
// loss of use, data, or profits; or business interruption) however caused
|
||||
// and on any theory of liability, whether in contract, strict liability,
|
||||
// or tort (including negligence or otherwise) arising in any way out of
|
||||
// the use of this software, even if advised of the possibility of such damage.
|
||||
//
|
||||
//M*/
|
||||
|
||||
#ifndef _GRFMT_JASPER_H_
|
||||
#define _GRFMT_JASPER_H_
|
||||
|
||||
#ifdef HAVE_JASPER
|
||||
|
||||
#include "grfmt_base.hpp"
|
||||
|
||||
namespace cv
|
||||
{
|
||||
|
||||
class Jpeg2KDecoder CV_FINAL : public BaseImageDecoder
|
||||
{
|
||||
public:
|
||||
|
||||
Jpeg2KDecoder();
|
||||
virtual ~Jpeg2KDecoder();
|
||||
|
||||
bool readData( Mat& img ) CV_OVERRIDE;
|
||||
bool readHeader() CV_OVERRIDE;
|
||||
void close();
|
||||
ImageDecoder newDecoder() const CV_OVERRIDE;
|
||||
|
||||
protected:
|
||||
bool readComponent8u( uchar *data, void *buffer, int step, int cmpt,
|
||||
int maxval, int offset, int ncmpts );
|
||||
bool readComponent16u( unsigned short *data, void *buffer, int step, int cmpt,
|
||||
int maxval, int offset, int ncmpts );
|
||||
|
||||
void *m_stream;
|
||||
void *m_image;
|
||||
};
|
||||
|
||||
|
||||
class Jpeg2KEncoder CV_FINAL : public BaseImageEncoder
|
||||
{
|
||||
public:
|
||||
Jpeg2KEncoder();
|
||||
virtual ~Jpeg2KEncoder();
|
||||
|
||||
bool isFormatSupported( int depth ) const CV_OVERRIDE;
|
||||
bool write( const Mat& img, const std::vector<int>& params ) CV_OVERRIDE;
|
||||
ImageEncoder newEncoder() const CV_OVERRIDE;
|
||||
|
||||
protected:
|
||||
bool writeComponent8u( void *img, const Mat& _img );
|
||||
bool writeComponent16u( void *img, const Mat& _img );
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#endif/*_GRFMT_JASPER_H_*/
|
||||
@@ -0,0 +1,821 @@
|
||||
// This file is part of OpenCV project.
|
||||
// It is subject to the license terms in the LICENSE file found in the top-level directory
|
||||
// of this distribution and at http://opencv.org/license.html.
|
||||
//
|
||||
// Copyright (C) 2020, Stefan Brüns <stefan.bruens@rwth-aachen.de>
|
||||
|
||||
#include "precomp.hpp"
|
||||
|
||||
#ifdef HAVE_OPENJPEG
|
||||
#include "grfmt_jpeg2000_openjpeg.hpp"
|
||||
|
||||
#include "opencv2/core/utils/logger.hpp"
|
||||
|
||||
namespace cv {
|
||||
|
||||
namespace {
|
||||
|
||||
String colorspaceName(COLOR_SPACE colorspace)
|
||||
{
|
||||
switch (colorspace)
|
||||
{
|
||||
case OPJ_CLRSPC_CMYK:
|
||||
return "CMYK";
|
||||
case OPJ_CLRSPC_SRGB:
|
||||
return "sRGB";
|
||||
case OPJ_CLRSPC_EYCC:
|
||||
return "e-YCC";
|
||||
case OPJ_CLRSPC_GRAY:
|
||||
return "grayscale";
|
||||
case OPJ_CLRSPC_SYCC:
|
||||
return "YUV";
|
||||
case OPJ_CLRSPC_UNKNOWN:
|
||||
return "unknown";
|
||||
case OPJ_CLRSPC_UNSPECIFIED:
|
||||
return "unspecified";
|
||||
default:
|
||||
CV_Error(Error::StsNotImplemented, "Invalid colorspace");
|
||||
}
|
||||
}
|
||||
|
||||
template <class T>
|
||||
struct ConstItTraits {
|
||||
using value_type = T;
|
||||
using difference_type = std::ptrdiff_t;
|
||||
using pointer = const T*;
|
||||
using reference = const T&;
|
||||
};
|
||||
|
||||
template <class T>
|
||||
struct NonConstItTraits {
|
||||
using value_type = T;
|
||||
using difference_type = std::ptrdiff_t;
|
||||
using pointer = T*;
|
||||
using reference = T&;
|
||||
};
|
||||
|
||||
/**
|
||||
* Iterator over the channel in continuous chunk of the memory e.g. in the one row of a Mat
|
||||
* No bounds checks are preformed due to keeping this class as simple as possible while
|
||||
* fulfilling RandomAccessIterator naming requirements.
|
||||
*
|
||||
* @tparam Traits holds information about value type and constness of the defined types
|
||||
*/
|
||||
template <class Traits>
|
||||
class ChannelsIterator
|
||||
{
|
||||
public:
|
||||
using difference_type = typename Traits::difference_type;
|
||||
using value_type = typename Traits::value_type;
|
||||
using pointer = typename Traits::pointer;
|
||||
using reference = typename Traits::reference;
|
||||
using iterator_category = std::random_access_iterator_tag;
|
||||
|
||||
ChannelsIterator(pointer ptr, std::size_t channel, std::size_t channels_count)
|
||||
: ptr_ { ptr + channel }, step_ { channels_count }
|
||||
{
|
||||
}
|
||||
|
||||
/* Element Access */
|
||||
reference operator*() const
|
||||
{
|
||||
return *ptr_;
|
||||
}
|
||||
|
||||
pointer operator->() const
|
||||
{
|
||||
return &(operator*());
|
||||
}
|
||||
|
||||
reference operator[](difference_type n) const
|
||||
{
|
||||
return *(*this + n);
|
||||
}
|
||||
|
||||
/* Iterator movement */
|
||||
ChannelsIterator<Traits>& operator++()
|
||||
{
|
||||
ptr_ += step_;
|
||||
return *this;
|
||||
}
|
||||
|
||||
ChannelsIterator<Traits> operator++(int)
|
||||
{
|
||||
ChannelsIterator ret(*this);
|
||||
++(*this);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ChannelsIterator<Traits>& operator--()
|
||||
{
|
||||
ptr_ -= step_;
|
||||
return *this;
|
||||
}
|
||||
|
||||
ChannelsIterator<Traits> operator--(int)
|
||||
{
|
||||
ChannelsIterator ret(*this);
|
||||
--(*this);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ChannelsIterator<Traits>& operator-=(difference_type n)
|
||||
{
|
||||
ptr_ -= n * step_;
|
||||
return *this;
|
||||
}
|
||||
|
||||
ChannelsIterator<Traits>& operator+=(difference_type n)
|
||||
{
|
||||
ptr_ += n * step_;
|
||||
return *this;
|
||||
}
|
||||
|
||||
ChannelsIterator<Traits> operator-(difference_type n) const
|
||||
{
|
||||
return ChannelsIterator<Traits>(*this) -= n;
|
||||
}
|
||||
|
||||
ChannelsIterator<Traits> operator+(difference_type n) const
|
||||
{
|
||||
return ChannelsIterator<Traits>(*this) += n;
|
||||
}
|
||||
|
||||
difference_type operator-(const ChannelsIterator<Traits>& other) const
|
||||
{
|
||||
return (ptr_ - other.ptr_) / step_;
|
||||
}
|
||||
|
||||
/* Comparison */
|
||||
bool operator==(const ChannelsIterator<Traits>& other) const CV_NOEXCEPT
|
||||
{
|
||||
return ptr_ == other.ptr_;
|
||||
}
|
||||
|
||||
bool operator!=(const ChannelsIterator<Traits>& other) const CV_NOEXCEPT
|
||||
{
|
||||
return !(*this == other);
|
||||
}
|
||||
|
||||
bool operator<(const ChannelsIterator<Traits>& other) const CV_NOEXCEPT
|
||||
{
|
||||
return ptr_ < other.ptr_;
|
||||
}
|
||||
|
||||
bool operator>(const ChannelsIterator<Traits>& other) const CV_NOEXCEPT
|
||||
{
|
||||
return other < *this;
|
||||
}
|
||||
|
||||
bool operator>=(const ChannelsIterator<Traits>& other) const CV_NOEXCEPT
|
||||
{
|
||||
return !(*this < other);
|
||||
}
|
||||
|
||||
bool operator<=(const ChannelsIterator<Traits>& other) const CV_NOEXCEPT
|
||||
{
|
||||
return !(other < *this);
|
||||
}
|
||||
|
||||
private:
|
||||
pointer ptr_{nullptr};
|
||||
std::size_t step_{1};
|
||||
};
|
||||
|
||||
template <class Traits>
|
||||
inline ChannelsIterator<Traits> operator+(typename Traits::difference_type n, const ChannelsIterator<Traits>& it)
|
||||
{
|
||||
return it + n;
|
||||
}
|
||||
|
||||
template<typename OutT, typename InT>
|
||||
void copyToMatImpl(std::vector<InT*>&& in, Mat& out, uint8_t shift)
|
||||
{
|
||||
using ChannelsIt = ChannelsIterator<NonConstItTraits<OutT>>;
|
||||
|
||||
Size size = out.size();
|
||||
if (out.isContinuous())
|
||||
{
|
||||
size.width *= size.height;
|
||||
size.height = 1;
|
||||
}
|
||||
|
||||
const bool isShiftRequired = shift != 0;
|
||||
|
||||
const std::size_t channelsCount = in.size();
|
||||
|
||||
if (isShiftRequired)
|
||||
{
|
||||
for (int i = 0; i < size.height; ++i)
|
||||
{
|
||||
auto rowPtr = out.ptr<OutT>(i);
|
||||
for (std::size_t c = 0; c < channelsCount; ++c)
|
||||
{
|
||||
const auto first = in[c];
|
||||
const auto last = first + size.width;
|
||||
auto dOut = ChannelsIt(rowPtr, c, channelsCount);
|
||||
std::transform(first, last, dOut, [shift](InT val) -> OutT { return static_cast<OutT>(val >> shift); });
|
||||
in[c] += size.width;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < size.height; ++i)
|
||||
{
|
||||
auto rowPtr = out.ptr<OutT>(i);
|
||||
for (std::size_t c = 0; c < channelsCount; ++c)
|
||||
{
|
||||
const auto first = in[c];
|
||||
const auto last = first + size.width;
|
||||
auto dOut = ChannelsIt(rowPtr, c, channelsCount);
|
||||
std::transform(first, last, dOut, [](InT val) -> OutT { return static_cast<OutT>(val); });
|
||||
in[c] += size.width;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template<typename InT>
|
||||
void copyToMat(std::vector<const InT*>&& in, Mat& out, uint8_t shift)
|
||||
{
|
||||
switch (out.depth())
|
||||
{
|
||||
case CV_8U:
|
||||
copyToMatImpl<uint8_t>(std::move(in), out, shift);
|
||||
break;
|
||||
case CV_16U:
|
||||
copyToMatImpl<uint16_t>(std::move(in), out, shift);
|
||||
break;
|
||||
default:
|
||||
CV_Error(Error::StsNotImplemented, "only depth CV_8U and CV16_U are supported");
|
||||
}
|
||||
}
|
||||
|
||||
template<typename InT, typename OutT>
|
||||
void copyFromMatImpl(const Mat& in, std::vector<OutT*>&& out)
|
||||
{
|
||||
using ChannelsIt = ChannelsIterator<ConstItTraits<InT>>;
|
||||
|
||||
Size size = in.size();
|
||||
if (in.isContinuous())
|
||||
{
|
||||
size.width *= size.height;
|
||||
size.height = 1;
|
||||
}
|
||||
|
||||
const std::size_t outChannelsCount = out.size();
|
||||
|
||||
for (int i = 0; i < size.height; ++i)
|
||||
{
|
||||
const InT* row = in.ptr<InT>(i);
|
||||
for (std::size_t c = 0; c < outChannelsCount; ++c)
|
||||
{
|
||||
auto first = ChannelsIt(row, c, outChannelsCount);
|
||||
auto last = first + size.width;
|
||||
out[c] = std::copy(first, last, out[c]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template<typename OutT>
|
||||
void copyFromMat(const Mat& in, std::vector<OutT*>&& out)
|
||||
{
|
||||
switch (in.depth())
|
||||
{
|
||||
case CV_8U:
|
||||
copyFromMatImpl<uint8_t>(in, std::move(out));
|
||||
break;
|
||||
case CV_16U:
|
||||
copyFromMatImpl<uint16_t>(in, std::move(out));
|
||||
break;
|
||||
default:
|
||||
CV_Error(Error::StsNotImplemented, "only depth CV_8U and CV16_U are supported");
|
||||
}
|
||||
}
|
||||
|
||||
void errorLogCallback(const char* msg, void* /* userData */)
|
||||
{
|
||||
CV_LOG_ERROR(NULL, cv::format("OpenJPEG2000: %s", msg));
|
||||
}
|
||||
|
||||
void warningLogCallback(const char* msg, void* /* userData */)
|
||||
{
|
||||
CV_LOG_WARNING(NULL, cv::format("OpenJPEG2000: %s", msg));
|
||||
}
|
||||
|
||||
void setupLogCallbacks(opj_codec_t* codec)
|
||||
{
|
||||
if (!opj_set_error_handler(codec, errorLogCallback, nullptr))
|
||||
{
|
||||
CV_LOG_WARNING(NULL, "OpenJPEG2000: can not set error log handler");
|
||||
}
|
||||
if (!opj_set_warning_handler(codec, warningLogCallback, nullptr))
|
||||
{
|
||||
CV_LOG_WARNING(NULL, "OpenJPEG2000: can not set warning log handler");
|
||||
}
|
||||
}
|
||||
|
||||
opj_dparameters setupDecoderParameters()
|
||||
{
|
||||
opj_dparameters parameters;
|
||||
opj_set_default_decoder_parameters(¶meters);
|
||||
return parameters;
|
||||
}
|
||||
|
||||
opj_cparameters setupEncoderParameters(const std::vector<int>& params)
|
||||
{
|
||||
opj_cparameters parameters;
|
||||
opj_set_default_encoder_parameters(¶meters);
|
||||
bool rate_is_specified = false;
|
||||
for (size_t i = 0; i < params.size(); i += 2)
|
||||
{
|
||||
const int value = params[i + 1];
|
||||
switch (params[i])
|
||||
{
|
||||
case cv::IMWRITE_JPEG2000_COMPRESSION_X1000:
|
||||
{
|
||||
const int compression = std::min(std::max(value, 1), 1000);
|
||||
parameters.tcp_rates[0] = 1000.f / static_cast<float>(compression);
|
||||
if(value != compression) {
|
||||
CV_LOG_WARNING(nullptr, cv::format("The value(%d) for IMWRITE_JPEG2000_COMPRESSION_X1000 must be between 1 to 1000. It is fallbacked to 1", value));
|
||||
}
|
||||
rate_is_specified = true;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
parameters.tcp_numlayers = 1;
|
||||
parameters.cp_disto_alloc = 1;
|
||||
if (!rate_is_specified)
|
||||
{
|
||||
parameters.tcp_rates[0] = 4;
|
||||
}
|
||||
return parameters;
|
||||
}
|
||||
|
||||
bool decodeSRGBData(const opj_image_t& inImg, cv::Mat& outImg, uint8_t shift, bool use_rgb)
|
||||
{
|
||||
using ImageComponents = std::vector<const OPJ_INT32*>;
|
||||
|
||||
const int inChannels = inImg.numcomps;
|
||||
const int outChannels = outImg.channels();
|
||||
|
||||
if (outChannels == 1)
|
||||
{
|
||||
// Assume gray (+ alpha) for 1 channels -> gray
|
||||
if (inChannels <= 2)
|
||||
{
|
||||
copyToMat(ImageComponents { inImg.comps[0].data }, outImg, shift);
|
||||
}
|
||||
// Assume RGB for >= 3 channels -> gray
|
||||
else
|
||||
{
|
||||
Mat tmp(outImg.size(), CV_MAKETYPE(outImg.depth(), 3));
|
||||
copyToMat(ImageComponents { inImg.comps[2].data, inImg.comps[1].data, inImg.comps[0].data },
|
||||
tmp, shift);
|
||||
cvtColor(tmp, outImg, COLOR_BGR2GRAY);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (inChannels >= 3)
|
||||
{
|
||||
int swap_rb = use_rgb ? 0 : 2;
|
||||
// Assume RGB (+ alpha) for 3 channels -> BGR
|
||||
ImageComponents incomps { inImg.comps[swap_rb].data, inImg.comps[1].data, inImg.comps[swap_rb^2].data };
|
||||
// Assume RGBA for 4 channels -> BGRA
|
||||
if (outChannels > 3)
|
||||
{
|
||||
incomps.push_back(inImg.comps[3].data);
|
||||
}
|
||||
copyToMat(std::move(incomps), outImg, shift);
|
||||
return true;
|
||||
}
|
||||
CV_LOG_ERROR(NULL,
|
||||
cv::format("OpenJPEG2000: unsupported conversion from %d components to %d for SRGB image decoding",
|
||||
inChannels, outChannels));
|
||||
return false;
|
||||
}
|
||||
|
||||
bool decodeGrayscaleData(const opj_image_t& inImg, cv::Mat& outImg, uint8_t shift, bool)
|
||||
{
|
||||
using ImageComponents = std::vector<const OPJ_INT32*>;
|
||||
|
||||
const int inChannels = inImg.numcomps;
|
||||
const int outChannels = outImg.channels();
|
||||
|
||||
if (outChannels == 1 || outChannels == 3)
|
||||
{
|
||||
copyToMat(ImageComponents(outChannels, inImg.comps[0].data), outImg, shift);
|
||||
return true;
|
||||
}
|
||||
CV_LOG_ERROR(NULL,
|
||||
cv::format("OpenJPEG2000: unsupported conversion from %d components to %d for Grayscale image decoding",
|
||||
inChannels, outChannels));
|
||||
return false;
|
||||
}
|
||||
|
||||
bool decodeSYCCData(const opj_image_t& inImg, cv::Mat& outImg, uint8_t shift, bool use_rgb)
|
||||
{
|
||||
using ImageComponents = std::vector<const OPJ_INT32*>;
|
||||
|
||||
const int inChannels = inImg.numcomps;
|
||||
const int outChannels = outImg.channels();
|
||||
|
||||
if (outChannels == 1) {
|
||||
copyToMat(ImageComponents { inImg.comps[0].data }, outImg, shift);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (outChannels == 3 && inChannels >= 3) {
|
||||
copyToMat(ImageComponents { inImg.comps[0].data, inImg.comps[1].data, inImg.comps[2].data },
|
||||
outImg, shift);
|
||||
if (use_rgb)
|
||||
cvtColor(outImg, outImg, COLOR_YUV2RGB);
|
||||
else
|
||||
cvtColor(outImg, outImg, COLOR_YUV2BGR);
|
||||
return true;
|
||||
}
|
||||
|
||||
CV_LOG_ERROR(NULL,
|
||||
cv::format("OpenJPEG2000: unsupported conversion from %d components to %d for YUV image decoding",
|
||||
inChannels, outChannels));
|
||||
return false;
|
||||
}
|
||||
|
||||
OPJ_SIZE_T opjReadFromBuffer(void* dist, OPJ_SIZE_T count, detail::OpjMemoryBuffer* buffer)
|
||||
{
|
||||
const OPJ_SIZE_T bytesToRead = std::min(buffer->availableBytes(), count);
|
||||
if (bytesToRead > 0)
|
||||
{
|
||||
memcpy(dist, buffer->pos, bytesToRead);
|
||||
buffer->pos += bytesToRead;
|
||||
return bytesToRead;
|
||||
}
|
||||
else
|
||||
{
|
||||
return static_cast<OPJ_SIZE_T>(-1);
|
||||
}
|
||||
}
|
||||
|
||||
OPJ_SIZE_T opjSkipFromBuffer(OPJ_SIZE_T count, detail::OpjMemoryBuffer* buffer) {
|
||||
const OPJ_SIZE_T bytesToSkip = std::min(buffer->availableBytes(), count);
|
||||
if (bytesToSkip > 0)
|
||||
{
|
||||
buffer->pos += bytesToSkip;
|
||||
return bytesToSkip;
|
||||
}
|
||||
else
|
||||
{
|
||||
return static_cast<OPJ_SIZE_T>(-1);
|
||||
}
|
||||
}
|
||||
|
||||
OPJ_BOOL opjSeekFromBuffer(OPJ_OFF_T count, detail::OpjMemoryBuffer* buffer)
|
||||
{
|
||||
// Count should stay positive to prevent unsigned overflow
|
||||
CV_DbgAssert(count > 0);
|
||||
// To provide proper comparison between OPJ_OFF_T and OPJ_SIZE_T, both should be
|
||||
// casted to uint64_t (On 32-bit systems sizeof(size_t) might be 4)
|
||||
CV_DbgAssert(static_cast<uint64_t>(count) < static_cast<uint64_t>(std::numeric_limits<OPJ_SIZE_T>::max()));
|
||||
const OPJ_SIZE_T pos = std::min(buffer->length, static_cast<OPJ_SIZE_T>(count));
|
||||
buffer->pos = buffer->begin + pos;
|
||||
return OPJ_TRUE;
|
||||
}
|
||||
|
||||
detail::StreamPtr opjCreateBufferInputStream(detail::OpjMemoryBuffer* buf)
|
||||
{
|
||||
detail::StreamPtr stream{ opj_stream_default_create(/* isInput */ true) };
|
||||
if (stream)
|
||||
{
|
||||
opj_stream_set_user_data(stream.get(), static_cast<void*>(buf), nullptr);
|
||||
opj_stream_set_user_data_length(stream.get(), buf->length);
|
||||
|
||||
opj_stream_set_read_function(stream.get(), (opj_stream_read_fn)(opjReadFromBuffer));
|
||||
opj_stream_set_skip_function(stream.get(), (opj_stream_skip_fn)(opjSkipFromBuffer));
|
||||
opj_stream_set_seek_function(stream.get(), (opj_stream_seek_fn)(opjSeekFromBuffer));
|
||||
}
|
||||
return stream;
|
||||
}
|
||||
|
||||
} // namespace <anonymous>
|
||||
|
||||
/////////////////////// Jpeg2KOpjDecoder ///////////////////
|
||||
|
||||
namespace detail {
|
||||
|
||||
Jpeg2KOpjDecoderBase::Jpeg2KOpjDecoderBase(OPJ_CODEC_FORMAT format)
|
||||
: format_(format)
|
||||
{
|
||||
m_buf_supported = true;
|
||||
}
|
||||
|
||||
|
||||
bool Jpeg2KOpjDecoderBase::readHeader()
|
||||
{
|
||||
if (!m_buf.empty()) {
|
||||
opjBuf_ = detail::OpjMemoryBuffer(m_buf);
|
||||
stream_ = opjCreateBufferInputStream(&opjBuf_);
|
||||
}
|
||||
else
|
||||
{
|
||||
stream_.reset(opj_stream_create_default_file_stream(m_filename.c_str(), OPJ_STREAM_READ));
|
||||
}
|
||||
if (!stream_)
|
||||
return false;
|
||||
|
||||
codec_.reset(opj_create_decompress(format_));
|
||||
if (!codec_)
|
||||
return false;
|
||||
|
||||
// Callbacks are cleared, when opj_destroy_codec is called,
|
||||
// They can provide some additional information for the user, about what goes wrong
|
||||
setupLogCallbacks(codec_.get());
|
||||
|
||||
opj_dparameters parameters = setupDecoderParameters();
|
||||
if (!opj_setup_decoder(codec_.get(), ¶meters))
|
||||
return false;
|
||||
|
||||
{
|
||||
opj_image_t* rawImage;
|
||||
if (!opj_read_header(stream_.get(), codec_.get(), &rawImage))
|
||||
return false;
|
||||
|
||||
image_.reset(rawImage);
|
||||
}
|
||||
|
||||
m_width = image_->x1 - image_->x0;
|
||||
m_height = image_->y1 - image_->y0;
|
||||
|
||||
/* Different components may have different precision,
|
||||
* so check all.
|
||||
*/
|
||||
bool hasAlpha = false;
|
||||
const int numcomps = image_->numcomps;
|
||||
CV_Check(numcomps, numcomps >= 1 && numcomps <= 4, "Unsupported number of components");
|
||||
for (int i = 0; i < numcomps; i++)
|
||||
{
|
||||
const opj_image_comp_t& comp = image_->comps[i];
|
||||
|
||||
if (comp.sgnd)
|
||||
{
|
||||
CV_Error(Error::StsNotImplemented, cv::format("OpenJPEG2000: Component %d/%d is signed", i, numcomps));
|
||||
}
|
||||
|
||||
if (hasAlpha && comp.alpha)
|
||||
{
|
||||
CV_Error(Error::StsNotImplemented, cv::format("OpenJPEG2000: Component %d/%d is duplicate alpha channel", i, numcomps));
|
||||
}
|
||||
|
||||
hasAlpha |= comp.alpha != 0;
|
||||
|
||||
if (comp.prec > 64)
|
||||
{
|
||||
CV_Error(Error::StsNotImplemented, "OpenJPEG2000: precision > 64 is not supported");
|
||||
}
|
||||
m_maxPrec = std::max(m_maxPrec, comp.prec);
|
||||
}
|
||||
|
||||
if (m_maxPrec < 8) {
|
||||
CV_Error(Error::StsNotImplemented, "OpenJPEG2000: Precision < 8 not supported");
|
||||
} else if (m_maxPrec == 8) {
|
||||
m_type = CV_MAKETYPE(CV_8U, numcomps);
|
||||
} else if (m_maxPrec <= 16) {
|
||||
m_type = CV_MAKETYPE(CV_16U, numcomps);
|
||||
} else if (m_maxPrec <= 23) {
|
||||
m_type = CV_MAKETYPE(CV_32F, numcomps);
|
||||
} else {
|
||||
m_type = CV_MAKETYPE(CV_64F, numcomps);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Jpeg2KOpjDecoderBase::readData( Mat& img )
|
||||
{
|
||||
using DecodeFunc = bool(*)(const opj_image_t&, cv::Mat&, uint8_t shift, bool use_rgb);
|
||||
|
||||
if (!opj_decode(codec_.get(), stream_.get(), image_.get()))
|
||||
{
|
||||
CV_Error(Error::StsError, "OpenJPEG2000: Decoding is failed");
|
||||
}
|
||||
|
||||
if (img.channels() == 2)
|
||||
{
|
||||
CV_Error(Error::StsNotImplemented,
|
||||
cv::format("OpenJPEG2000: Unsupported number of output channels. IN: %d OUT: 2", image_->numcomps));
|
||||
}
|
||||
|
||||
DecodeFunc decode = nullptr;
|
||||
switch (image_->color_space)
|
||||
{
|
||||
case OPJ_CLRSPC_UNKNOWN:
|
||||
/* FALLTHRU */
|
||||
case OPJ_CLRSPC_UNSPECIFIED:
|
||||
CV_LOG_WARNING(NULL, "OpenJPEG2000: Image has unknown or unspecified color space, SRGB is assumed");
|
||||
/* FALLTHRU */
|
||||
case OPJ_CLRSPC_SRGB:
|
||||
decode = decodeSRGBData;
|
||||
break;
|
||||
case OPJ_CLRSPC_GRAY:
|
||||
decode = decodeGrayscaleData;
|
||||
break;
|
||||
case OPJ_CLRSPC_SYCC:
|
||||
decode = decodeSYCCData;
|
||||
break;
|
||||
default:
|
||||
CV_Error(Error::StsNotImplemented,
|
||||
cv::format("OpenJPEG2000: Unsupported color space conversion: %s -> %s",
|
||||
colorspaceName(image_->color_space).c_str(),
|
||||
(img.channels() == 1) ? "gray" : "BGR"));
|
||||
}
|
||||
|
||||
const int depth = img.depth();
|
||||
const OPJ_UINT32 outPrec = [depth]() {
|
||||
if (depth == CV_8U) return 8;
|
||||
if (depth == CV_16U) return 16;
|
||||
CV_Error(Error::StsNotImplemented,
|
||||
cv::format("OpenJPEG2000: output precision > 16 not supported: target depth %d", depth));
|
||||
}();
|
||||
const uint8_t shift = outPrec > m_maxPrec ? 0 : (uint8_t)(m_maxPrec - outPrec); // prec <= 64
|
||||
|
||||
const int inChannels = image_->numcomps;
|
||||
|
||||
CV_Assert(inChannels > 0);
|
||||
CV_Assert(image_->comps);
|
||||
for (int c = 0; c < inChannels; c++)
|
||||
{
|
||||
const opj_image_comp_t& comp = image_->comps[c];
|
||||
CV_CheckEQ((int)comp.dx, 1, "OpenJPEG2000: tiles are not supported");
|
||||
CV_CheckEQ((int)comp.dy, 1, "OpenJPEG2000: tiles are not supported");
|
||||
CV_CheckEQ((int)comp.x0, 0, "OpenJPEG2000: tiles are not supported");
|
||||
CV_CheckEQ((int)comp.y0, 0, "OpenJPEG2000: tiles are not supported");
|
||||
CV_CheckEQ((int)comp.w, img.cols, "OpenJPEG2000: tiles are not supported");
|
||||
CV_CheckEQ((int)comp.h, img.rows, "OpenJPEG2000: tiles are not supported");
|
||||
CV_Assert(comp.data && "OpenJPEG2000: missing component data (unsupported / broken input)");
|
||||
}
|
||||
|
||||
return decode(*image_, img, shift, m_use_rgb);
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
|
||||
Jpeg2KJP2OpjDecoder::Jpeg2KJP2OpjDecoder()
|
||||
: Jpeg2KOpjDecoderBase(OPJ_CODEC_JP2)
|
||||
{
|
||||
static const unsigned char JP2Signature[] = { 0, 0, 0, 0x0c, 'j', 'P', ' ', ' ', 13, 10, 0x87, 10 };
|
||||
m_signature = String((const char*) JP2Signature, sizeof(JP2Signature));
|
||||
}
|
||||
|
||||
ImageDecoder Jpeg2KJP2OpjDecoder::newDecoder() const
|
||||
{
|
||||
return makePtr<Jpeg2KJP2OpjDecoder>();
|
||||
}
|
||||
|
||||
Jpeg2KJ2KOpjDecoder::Jpeg2KJ2KOpjDecoder()
|
||||
: Jpeg2KOpjDecoderBase(OPJ_CODEC_J2K)
|
||||
{
|
||||
static const unsigned char J2KSignature[] = { 0xff, 0x4f, 0xff, 0x51 };
|
||||
m_signature = String((const char*) J2KSignature, sizeof(J2KSignature));
|
||||
}
|
||||
|
||||
ImageDecoder Jpeg2KJ2KOpjDecoder::newDecoder() const
|
||||
{
|
||||
return makePtr<Jpeg2KJ2KOpjDecoder>();
|
||||
}
|
||||
|
||||
/////////////////////// Jpeg2KOpjEncoder ///////////////////
|
||||
|
||||
Jpeg2KOpjEncoder::Jpeg2KOpjEncoder()
|
||||
{
|
||||
m_description = "JPEG-2000 files (*.jp2)";
|
||||
m_supported_encode_key = {IMWRITE_JPEG2000_COMPRESSION_X1000};
|
||||
}
|
||||
|
||||
ImageEncoder Jpeg2KOpjEncoder::newEncoder() const
|
||||
{
|
||||
return makePtr<Jpeg2KOpjEncoder>();
|
||||
}
|
||||
|
||||
bool Jpeg2KOpjEncoder::isFormatSupported(int depth) const
|
||||
{
|
||||
return depth == CV_8U || depth == CV_16U;
|
||||
}
|
||||
|
||||
bool Jpeg2KOpjEncoder::write(const Mat& img, const std::vector<int>& params)
|
||||
{
|
||||
CV_Assert(params.size() % 2 == 0);
|
||||
|
||||
const int channels = img.channels();
|
||||
CV_DbgAssert(channels > 0); // passed matrix is not empty
|
||||
if (channels > 4)
|
||||
{
|
||||
CV_Error(Error::StsNotImplemented, "OpenJPEG2000: only BGR(a) and gray (+ alpha) images supported");
|
||||
}
|
||||
|
||||
const int depth = img.depth();
|
||||
|
||||
const OPJ_UINT32 outPrec = [depth]() {
|
||||
if (depth == CV_8U) return 8;
|
||||
if (depth == CV_16U) return 16;
|
||||
CV_Error(Error::StsNotImplemented,
|
||||
cv::format("OpenJPEG2000: image precision > 16 not supported. Got: %d", depth));
|
||||
}();
|
||||
|
||||
opj_cparameters parameters = setupEncoderParameters(params);
|
||||
|
||||
std::vector<opj_image_cmptparm_t> compparams(channels);
|
||||
for (int i = 0; i < channels; i++) {
|
||||
compparams[i].prec = outPrec;
|
||||
compparams[i].bpp = outPrec;
|
||||
compparams[i].sgnd = 0; // unsigned for now
|
||||
compparams[i].dx = parameters.subsampling_dx;
|
||||
compparams[i].dy = parameters.subsampling_dy;
|
||||
compparams[i].w = img.size().width;
|
||||
compparams[i].h = img.size().height;
|
||||
}
|
||||
|
||||
|
||||
auto colorspace = (channels > 2) ? OPJ_CLRSPC_SRGB : OPJ_CLRSPC_GRAY;
|
||||
detail::ImagePtr image(opj_image_create(channels, compparams.data(), colorspace));
|
||||
if (!image)
|
||||
{
|
||||
CV_Error(Error::StsNotImplemented, "OpenJPEG2000: can not create image");
|
||||
}
|
||||
|
||||
if (channels == 2 || channels == 4)
|
||||
{
|
||||
image->comps[channels - 1].alpha = 1;
|
||||
}
|
||||
// we want the full image
|
||||
image->x0 = 0;
|
||||
image->y0 = 0;
|
||||
image->x1 = compparams[0].dx * compparams[0].w;
|
||||
image->y1 = compparams[0].dy * compparams[0].h;
|
||||
|
||||
// fill the component data arrays
|
||||
std::vector<OPJ_INT32*> outcomps(channels, nullptr);
|
||||
if (channels == 1)
|
||||
{
|
||||
outcomps.assign({ image->comps[0].data });
|
||||
}
|
||||
else if (channels == 2)
|
||||
{
|
||||
outcomps.assign({ image->comps[0].data, image->comps[1].data });
|
||||
}
|
||||
// Reversed order for BGR -> RGB conversion
|
||||
else if (channels == 3)
|
||||
{
|
||||
outcomps.assign({ image->comps[2].data, image->comps[1].data, image->comps[0].data });
|
||||
}
|
||||
else if (channels == 4)
|
||||
{
|
||||
outcomps.assign({ image->comps[2].data, image->comps[1].data, image->comps[0].data,
|
||||
image->comps[3].data });
|
||||
}
|
||||
// outcomps holds pointers to the data, so the actual data will be modified but won't be freed
|
||||
// The container is not needed after data was copied
|
||||
copyFromMat(img, std::move(outcomps));
|
||||
|
||||
detail::CodecPtr codec(opj_create_compress(OPJ_CODEC_JP2));
|
||||
if (!codec) {
|
||||
CV_Error(Error::StsNotImplemented, "OpenJPEG2000: can not create compression codec");
|
||||
}
|
||||
|
||||
setupLogCallbacks(codec.get());
|
||||
|
||||
if (!opj_setup_encoder(codec.get(), ¶meters, image.get()))
|
||||
{
|
||||
CV_Error(Error::StsNotImplemented, "OpenJPEG2000: Can not setup encoder");
|
||||
}
|
||||
|
||||
detail::StreamPtr stream(opj_stream_create_default_file_stream(m_filename.c_str(), OPJ_STREAM_WRITE));
|
||||
if (!stream)
|
||||
{
|
||||
CV_Error(Error::StsNotImplemented, "OpenJPEG2000: Can not create stream");
|
||||
}
|
||||
|
||||
if (!opj_start_compress(codec.get(), image.get(), stream.get()))
|
||||
{
|
||||
CV_Error(Error::StsNotImplemented, "OpenJPEG2000: Can not start compression");
|
||||
}
|
||||
|
||||
if (!opj_encode(codec.get(), stream.get()))
|
||||
{
|
||||
CV_Error(Error::StsNotImplemented, "OpenJPEG2000: Encoding failed");
|
||||
}
|
||||
|
||||
if (!opj_end_compress(codec.get(), stream.get()))
|
||||
{
|
||||
CV_Error(Error::StsNotImplemented, "OpenJPEG2000: Can not end compression");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
} // namespace cv
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,112 @@
|
||||
// This file is part of OpenCV project.
|
||||
// It is subject to the license terms in the LICENSE file found in the top-level directory
|
||||
// of this distribution and at http://opencv.org/license.html.
|
||||
//
|
||||
// Copyright (C) 2020, Stefan Brüns <stefan.bruens@rwth-aachen.de>
|
||||
|
||||
#ifndef _GRFMT_OPENJPEG_H_
|
||||
#define _GRFMT_OPENJPEG_H_
|
||||
|
||||
#ifdef HAVE_OPENJPEG
|
||||
|
||||
#include "grfmt_base.hpp"
|
||||
#include <openjpeg.h>
|
||||
|
||||
namespace cv {
|
||||
namespace detail {
|
||||
struct OpjStreamDeleter
|
||||
{
|
||||
void operator()(opj_stream_t* stream) const
|
||||
{
|
||||
opj_stream_destroy(stream);
|
||||
}
|
||||
};
|
||||
|
||||
struct OpjCodecDeleter
|
||||
{
|
||||
void operator()(opj_codec_t* codec) const
|
||||
{
|
||||
opj_destroy_codec(codec);
|
||||
}
|
||||
};
|
||||
|
||||
struct OpjImageDeleter
|
||||
{
|
||||
void operator()(opj_image_t* image) const
|
||||
{
|
||||
opj_image_destroy(image);
|
||||
}
|
||||
};
|
||||
|
||||
struct OpjMemoryBuffer {
|
||||
OPJ_BYTE* pos{nullptr};
|
||||
OPJ_BYTE* begin{nullptr};
|
||||
OPJ_SIZE_T length{0};
|
||||
|
||||
OpjMemoryBuffer() = default;
|
||||
|
||||
explicit OpjMemoryBuffer(cv::Mat& mat)
|
||||
: pos{ mat.ptr() }, begin{ mat.ptr() }, length{ mat.rows * mat.cols * mat.elemSize() }
|
||||
{
|
||||
}
|
||||
|
||||
OPJ_SIZE_T availableBytes() const CV_NOEXCEPT {
|
||||
return begin + length - pos;
|
||||
}
|
||||
};
|
||||
|
||||
using StreamPtr = std::unique_ptr<opj_stream_t, detail::OpjStreamDeleter>;
|
||||
using CodecPtr = std::unique_ptr<opj_codec_t, detail::OpjCodecDeleter>;
|
||||
using ImagePtr = std::unique_ptr<opj_image_t, detail::OpjImageDeleter>;
|
||||
|
||||
class Jpeg2KOpjDecoderBase : public BaseImageDecoder
|
||||
{
|
||||
public:
|
||||
Jpeg2KOpjDecoderBase(OPJ_CODEC_FORMAT format);
|
||||
|
||||
bool readData( Mat& img ) CV_OVERRIDE;
|
||||
bool readHeader() CV_OVERRIDE;
|
||||
|
||||
private:
|
||||
detail::StreamPtr stream_{nullptr};
|
||||
detail::CodecPtr codec_{nullptr};
|
||||
detail::ImagePtr image_{nullptr};
|
||||
|
||||
detail::OpjMemoryBuffer opjBuf_;
|
||||
|
||||
OPJ_UINT32 m_maxPrec = 0;
|
||||
OPJ_CODEC_FORMAT format_;
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
|
||||
class Jpeg2KJP2OpjDecoder CV_FINAL : public detail::Jpeg2KOpjDecoderBase {
|
||||
public:
|
||||
Jpeg2KJP2OpjDecoder();
|
||||
|
||||
ImageDecoder newDecoder() const CV_OVERRIDE;
|
||||
};
|
||||
|
||||
class Jpeg2KJ2KOpjDecoder CV_FINAL : public detail::Jpeg2KOpjDecoderBase {
|
||||
public:
|
||||
Jpeg2KJ2KOpjDecoder();
|
||||
|
||||
ImageDecoder newDecoder() const CV_OVERRIDE;
|
||||
};
|
||||
|
||||
class Jpeg2KOpjEncoder CV_FINAL : public BaseImageEncoder
|
||||
{
|
||||
public:
|
||||
Jpeg2KOpjEncoder();
|
||||
~Jpeg2KOpjEncoder() CV_OVERRIDE = default;
|
||||
|
||||
bool isFormatSupported( int depth ) const CV_OVERRIDE;
|
||||
bool write( const Mat& img, const std::vector<int>& params ) CV_OVERRIDE;
|
||||
ImageEncoder newEncoder() const CV_OVERRIDE;
|
||||
};
|
||||
|
||||
} //namespace cv
|
||||
|
||||
#endif
|
||||
|
||||
#endif/*_GRFMT_OPENJPEG_H_*/
|
||||
@@ -0,0 +1,670 @@
|
||||
// This file is part of OpenCV project.
|
||||
// It is subject to the license terms in the LICENSE file found in the top-level directory
|
||||
// of this distribution and at http://opencv.org/license.html.
|
||||
#include "precomp.hpp"
|
||||
#include "grfmt_jpegxl.hpp"
|
||||
|
||||
#ifdef HAVE_JPEGXL
|
||||
|
||||
#include <jxl/encode_cxx.h>
|
||||
#include <jxl/version.h>
|
||||
#include <opencv2/core/utils/logger.hpp>
|
||||
|
||||
namespace cv
|
||||
{
|
||||
// Callback functions for JpegXLDecoder
|
||||
static void cbRGBtoBGR_8U(void *opaque, size_t x, size_t y, size_t num_pixels, const void *pixels);
|
||||
static void cbRGBAtoBGRA_8U(void *opaque, size_t x, size_t y, size_t num_pixels, const void *pixels);
|
||||
static void cbRGBtoBGR_16U(void *opaque, size_t x, size_t y, size_t num_pixels, const void *pixels);
|
||||
static void cbRGBAtoBGRA_16U(void *opaque, size_t x, size_t y, size_t num_pixels, const void *pixels);
|
||||
static void cbRGBtoBGR_32F(void *opaque, size_t x, size_t y, size_t num_pixels, const void *pixels);
|
||||
static void cbRGBAtoBGRA_32F(void *opaque, size_t x, size_t y, size_t num_pixels, const void *pixels);
|
||||
static void cbRGBtoGRAY_8U(void *opaque, size_t x, size_t y, size_t num_pixels, const void *pixels);
|
||||
static void cbRGBAtoGRAY_8U(void *opaque, size_t x, size_t y, size_t num_pixels, const void *pixels);
|
||||
static void cbRGBtoGRAY_16U(void *opaque, size_t x, size_t y, size_t num_pixels, const void *pixels);
|
||||
static void cbRGBAtoGRAY_16U(void *opaque, size_t x, size_t y, size_t num_pixels, const void *pixels);
|
||||
static void cbRGBtoGRAY_32F(void *opaque, size_t x, size_t y, size_t num_pixels, const void *pixels);
|
||||
static void cbRGBAtoGRAY_32F(void *opaque, size_t x, size_t y, size_t num_pixels, const void *pixels);
|
||||
|
||||
/////////////////////// JpegXLDecoder ///////////////////
|
||||
|
||||
JpegXLDecoder::JpegXLDecoder() : m_f(nullptr, &fclose),
|
||||
m_read_buffer(16384,0) // 16KB chunks
|
||||
{
|
||||
m_signature = "\xFF\x0A";
|
||||
m_decoder = nullptr;
|
||||
m_buf_supported = true;
|
||||
m_type = -1;
|
||||
m_status = JXL_DEC_NEED_MORE_INPUT;
|
||||
m_is_mbuf_set = false;
|
||||
}
|
||||
|
||||
JpegXLDecoder::~JpegXLDecoder()
|
||||
{
|
||||
close();
|
||||
}
|
||||
|
||||
void JpegXLDecoder::close()
|
||||
{
|
||||
if (m_decoder)
|
||||
m_decoder.reset();
|
||||
if (m_f)
|
||||
m_f.reset();
|
||||
m_read_buffer = {};
|
||||
m_width = m_height = 0;
|
||||
m_type = -1;
|
||||
m_status = JXL_DEC_NEED_MORE_INPUT;
|
||||
m_is_mbuf_set = false;
|
||||
}
|
||||
|
||||
// see https://github.com/libjxl/libjxl/blob/v0.10.0/doc/format_overview.md
|
||||
size_t JpegXLDecoder::signatureLength() const
|
||||
{
|
||||
return 12; // For an ISOBMFF-based container
|
||||
}
|
||||
|
||||
bool JpegXLDecoder::checkSignature( const String& signature ) const
|
||||
{
|
||||
// A "naked" codestream.
|
||||
if (
|
||||
( signature.size() >= 2 ) &&
|
||||
( memcmp( signature.c_str(), "\xFF\x0A", 2 ) == 0 )
|
||||
)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// An ISOBMFF-based container.
|
||||
// 0x0000_000C_4A58_4C20_0D0A_870A.
|
||||
if (
|
||||
( signature.size() >= 12 ) &&
|
||||
( memcmp( signature.c_str(), "\x00\x00\x00\x0C\x4A\x58\x4C\x20\x0D\x0A\x87\x0A", 12 ) == 0 )
|
||||
)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
ImageDecoder JpegXLDecoder::newDecoder() const
|
||||
{
|
||||
return makePtr<JpegXLDecoder>();
|
||||
}
|
||||
|
||||
bool JpegXLDecoder::readHeader()
|
||||
{
|
||||
if (m_buf.empty()) {
|
||||
// Open file
|
||||
if (!m_f) {
|
||||
m_f.reset(fopen(m_filename.c_str(), "rb"));
|
||||
if (!m_f) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize decoder
|
||||
if (!m_decoder) {
|
||||
m_decoder = JxlDecoderMake(nullptr);
|
||||
if (!m_decoder)
|
||||
return false;
|
||||
// Subscribe to the basic info event
|
||||
JxlDecoderStatus status = JxlDecoderSubscribeEvents(m_decoder.get(), JXL_DEC_BASIC_INFO | JXL_DEC_FULL_IMAGE);
|
||||
if (status != JXL_DEC_SUCCESS)
|
||||
return false;
|
||||
}
|
||||
|
||||
// Set up parallel m_parallel_runner
|
||||
if (!m_parallel_runner) {
|
||||
m_parallel_runner = JxlThreadParallelRunnerMake(nullptr, cv::getNumThreads());
|
||||
if (JXL_DEC_SUCCESS != JxlDecoderSetParallelRunner(m_decoder.get(),
|
||||
JxlThreadParallelRunner,
|
||||
m_parallel_runner.get())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Reset to read header data stream
|
||||
m_is_mbuf_set = false;
|
||||
|
||||
return read();
|
||||
}
|
||||
|
||||
bool JpegXLDecoder::readData(Mat& img)
|
||||
{
|
||||
if (!m_decoder || m_width == 0 || m_height == 0 || m_type == -1)
|
||||
return false;
|
||||
|
||||
// Prepare to decode image
|
||||
const uint32_t scn = CV_MAT_CN(m_type); // from image
|
||||
const uint32_t dcn = (uint32_t)img.channels(); // to OpenCV
|
||||
const int depth = CV_MAT_DEPTH(img.type());
|
||||
JxlImageOutCallback cbFunc = nullptr;
|
||||
|
||||
CV_CheckChannels(scn, (scn == 1 || scn == 3 || scn == 4), "Unsupported src channels");
|
||||
CV_CheckChannels(dcn, (dcn == 1 || dcn == 3 || dcn == 4), "Unsupported dst channels");
|
||||
CV_CheckDepth(depth, (depth == CV_8U || depth == CV_16U || depth == CV_32F), "Unsupported depth");
|
||||
|
||||
m_format = {
|
||||
dcn,
|
||||
JXL_TYPE_UINT8, // (temporary)
|
||||
JXL_NATIVE_ENDIAN, // endianness
|
||||
0 // align stride to bytes
|
||||
};
|
||||
switch (depth) {
|
||||
case CV_8U: m_format.data_type = JXL_TYPE_UINT8; break;
|
||||
case CV_16U: m_format.data_type = JXL_TYPE_UINT16; break;
|
||||
case CV_32F: m_format.data_type = JXL_TYPE_FLOAT; break;
|
||||
default: break;
|
||||
}
|
||||
// libjxl cannot read to BGR pixel order directly.
|
||||
// So we have to use callback function to convert from RGB(A) to BGR(A).
|
||||
if (!m_use_rgb) {
|
||||
switch (dcn) {
|
||||
case 1: break;
|
||||
case 3: cbFunc = (depth == CV_32F)? cbRGBtoBGR_32F: (depth == CV_16U)? cbRGBtoBGR_16U: cbRGBtoBGR_8U; break;
|
||||
case 4: cbFunc = (depth == CV_32F)? cbRGBAtoBGRA_32F: (depth == CV_16U)? cbRGBAtoBGRA_16U: cbRGBAtoBGRA_8U; break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
// libjxl cannot convert from color image to gray image directly.
|
||||
// So we have to use callback function to convert from RGB(A) to GRAY.
|
||||
if( (scn >= 3) && (dcn == 1) )
|
||||
{
|
||||
m_format.num_channels = scn;
|
||||
switch (scn) {
|
||||
case 3: cbFunc = (depth == CV_32F)? cbRGBtoGRAY_32F: (depth == CV_16U)? cbRGBtoGRAY_16U: cbRGBtoGRAY_8U; break;
|
||||
case 4: cbFunc = (depth == CV_32F)? cbRGBAtoGRAY_32F: (depth == CV_16U)? cbRGBAtoGRAY_16U: cbRGBAtoGRAY_8U; break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
if(cbFunc != nullptr)
|
||||
{
|
||||
if (JXL_DEC_SUCCESS != JxlDecoderSetImageOutCallback(m_decoder.get(),
|
||||
&m_format,
|
||||
cbFunc,
|
||||
static_cast<void*>(&img)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}else{
|
||||
if (JXL_DEC_SUCCESS != JxlDecoderSetImageOutBuffer(m_decoder.get(),
|
||||
&m_format,
|
||||
img.ptr<uint8_t>(),
|
||||
img.total() * img.elemSize()))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return read();
|
||||
}
|
||||
|
||||
// Common reading routine for readHeader() and readBody()
|
||||
bool JpegXLDecoder::read()
|
||||
{
|
||||
// Start decoding loop
|
||||
do {
|
||||
// Check if we need more input
|
||||
if (m_status == JXL_DEC_NEED_MORE_INPUT) {
|
||||
uint8_t* data_ptr = nullptr;
|
||||
size_t data_len = 0;
|
||||
|
||||
if( !m_buf.empty() ) {
|
||||
// When data source in on memory
|
||||
if (m_is_mbuf_set) {
|
||||
// We expect m_buf contains whole JpegXL data stream.
|
||||
// If it had been truncated, m_status will be JXL_DEC_NEED_MORE_INPUT again.
|
||||
CV_LOG_WARNING(NULL, "Truncated JXL data in memory");
|
||||
return false;
|
||||
}
|
||||
data_ptr = m_buf.ptr();
|
||||
data_len = m_buf.total();
|
||||
m_is_mbuf_set = true;
|
||||
}
|
||||
else {
|
||||
// When data source is on file
|
||||
// Release input buffer if it had been set already. If not, there are no errors.
|
||||
size_t remaining = JxlDecoderReleaseInput(m_decoder.get());
|
||||
// Move any remaining bytes to the beginning
|
||||
if (remaining > 0)
|
||||
memmove(m_read_buffer.data(), m_read_buffer.data() + m_read_buffer.size() - remaining, remaining);
|
||||
// Read more data from file
|
||||
size_t bytes_read = fread(m_read_buffer.data() + remaining,
|
||||
1, m_read_buffer.size() - remaining, m_f.get());
|
||||
if (bytes_read == 0) {
|
||||
if (ferror(m_f.get())) {
|
||||
CV_LOG_WARNING(NULL, "Error reading input file");
|
||||
return false;
|
||||
}
|
||||
// If we reached EOF but decoder needs more input, file is truncated
|
||||
if (m_status == JXL_DEC_NEED_MORE_INPUT) {
|
||||
CV_LOG_WARNING(NULL, "Truncated JXL file");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
data_ptr = m_read_buffer.data();
|
||||
data_len = bytes_read + remaining;
|
||||
}
|
||||
|
||||
// Set input buffer
|
||||
// It must be kept until calling JxlDecoderReleaseInput() or m_decoder.reset().
|
||||
if (JXL_DEC_SUCCESS != JxlDecoderSetInput(m_decoder.get(), data_ptr, data_len)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Get the next decoder status
|
||||
m_status = JxlDecoderProcessInput(m_decoder.get());
|
||||
|
||||
// Handle different decoder states
|
||||
switch (m_status) {
|
||||
case JXL_DEC_BASIC_INFO: {
|
||||
if (m_type != -1)
|
||||
return false;
|
||||
|
||||
JxlBasicInfo info;
|
||||
if (JXL_DEC_SUCCESS != JxlDecoderGetBasicInfo(m_decoder.get(), &info))
|
||||
return false;
|
||||
|
||||
// total channels (Color + Alpha)
|
||||
const uint32_t ncn = info.num_color_channels + info.num_extra_channels;
|
||||
|
||||
m_width = info.xsize;
|
||||
m_height = info.ysize;
|
||||
int depth = (info.exponent_bits_per_sample > 0)?CV_32F:
|
||||
(info.bits_per_sample == 16)?CV_16U:
|
||||
(info.bits_per_sample == 8)?CV_8U: -1;
|
||||
if(depth == -1)
|
||||
{
|
||||
return false; // Return to readHeader()
|
||||
}
|
||||
m_type = CV_MAKETYPE( depth, ncn );
|
||||
return true;
|
||||
}
|
||||
case JXL_DEC_FULL_IMAGE: {
|
||||
// Image is ready
|
||||
break;
|
||||
}
|
||||
case JXL_DEC_ERROR: {
|
||||
close();
|
||||
return false;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
} while (m_status != JXL_DEC_SUCCESS);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Callback functopms
|
||||
static void cbRGBtoBGR_8U(void *opaque, size_t x, size_t y, size_t num_pixels, const void *pixels)
|
||||
{
|
||||
const uint8_t* src = static_cast<const uint8_t*>(pixels);
|
||||
|
||||
constexpr int dstStep = 3;
|
||||
const cv::Mat *pDst = static_cast<cv::Mat*>(opaque);
|
||||
uint8_t* dstBase = const_cast<uint8_t*>(pDst->ptr(y));
|
||||
uint8_t* dst = dstBase + x * dstStep;
|
||||
|
||||
icvCvt_RGB2BGR_8u_C3R( src, 0, dst, 0, Size(num_pixels , 1) );
|
||||
}
|
||||
static void cbRGBAtoBGRA_8U(void *opaque, size_t x, size_t y, size_t num_pixels, const void *pixels)
|
||||
{
|
||||
const uint8_t* src = static_cast<const uint8_t*>(pixels);
|
||||
|
||||
constexpr int dstStep = 4;
|
||||
const cv::Mat *pDst = static_cast<cv::Mat*>(opaque);
|
||||
uint8_t* dstBase = const_cast<uint8_t*>(reinterpret_cast<const uint8_t*>(pDst->ptr(y)));
|
||||
uint8_t* dst = dstBase + x * dstStep;
|
||||
|
||||
icvCvt_RGBA2BGRA_8u_C4R( src, 0, dst, 0, Size(num_pixels, 1) );
|
||||
}
|
||||
static void cbRGBtoBGR_16U(void *opaque, size_t x, size_t y, size_t num_pixels, const void *pixels)
|
||||
{
|
||||
const uint16_t* src = static_cast<const uint16_t*>(pixels);
|
||||
|
||||
constexpr int dstStep = 3;
|
||||
const cv::Mat *pDst = static_cast<cv::Mat*>(opaque);
|
||||
uint16_t* dstBase = const_cast<uint16_t*>(reinterpret_cast<const uint16_t*>(pDst->ptr(y)));
|
||||
uint16_t* dst = dstBase + x * dstStep;
|
||||
|
||||
icvCvt_BGR2RGB_16u_C3R( src, 0, dst, 0, Size(num_pixels, 1));
|
||||
}
|
||||
static void cbRGBAtoBGRA_16U(void *opaque, size_t x, size_t y, size_t num_pixels, const void *pixels)
|
||||
{
|
||||
const uint16_t* src = static_cast<const uint16_t*>(pixels);
|
||||
|
||||
constexpr int dstStep = 4;
|
||||
const cv::Mat *pDst = static_cast<cv::Mat*>(opaque);
|
||||
uint16_t* dstBase = const_cast<uint16_t*>(reinterpret_cast<const uint16_t*>(pDst->ptr(y)));
|
||||
uint16_t* dst = dstBase + x * dstStep;
|
||||
|
||||
icvCvt_BGRA2RGBA_16u_C4R( src, 0, dst, 0, Size(num_pixels, 1));
|
||||
}
|
||||
static void cbRGBtoBGR_32F(void *opaque, size_t x, size_t y, size_t num_pixels, const void *pixels)
|
||||
{
|
||||
constexpr int srcStep = 3;
|
||||
const uint32_t* src = static_cast<const uint32_t*>(pixels);
|
||||
|
||||
constexpr int dstStep = 3;
|
||||
const cv::Mat *pDst = static_cast<cv::Mat*>(opaque);
|
||||
uint32_t* dstBase = const_cast<uint32_t*>(reinterpret_cast<const uint32_t*>(pDst->ptr(y)));
|
||||
uint32_t* dst = dstBase + x * dstStep;
|
||||
|
||||
for(size_t i = 0 ; i < num_pixels; i++)
|
||||
{
|
||||
dst[ i * dstStep + 0 ] = src[ i * srcStep + 2];
|
||||
dst[ i * dstStep + 1 ] = src[ i * srcStep + 1];
|
||||
dst[ i * dstStep + 2 ] = src[ i * srcStep + 0];
|
||||
}
|
||||
}
|
||||
static void cbRGBAtoBGRA_32F(void *opaque, size_t x, size_t y, size_t num_pixels, const void *pixels)
|
||||
{
|
||||
constexpr int srcStep = 4;
|
||||
const uint32_t* src = static_cast<const uint32_t*>(pixels);
|
||||
|
||||
constexpr int dstStep = 4;
|
||||
const cv::Mat *pDst = static_cast<cv::Mat*>(opaque);
|
||||
uint32_t* dstBase = const_cast<uint32_t*>(reinterpret_cast<const uint32_t*>(pDst->ptr(y)));
|
||||
uint32_t* dst = dstBase + x * dstStep;
|
||||
|
||||
for(size_t i = 0 ; i < num_pixels; i++)
|
||||
{
|
||||
dst[ i * dstStep + 0 ] = src[ i * srcStep + 2];
|
||||
dst[ i * dstStep + 1 ] = src[ i * srcStep + 1];
|
||||
dst[ i * dstStep + 2 ] = src[ i * srcStep + 0];
|
||||
dst[ i * dstStep + 3 ] = src[ i * srcStep + 3];
|
||||
}
|
||||
}
|
||||
|
||||
static void cbRGBtoGRAY_8U(void *opaque, size_t x, size_t y, size_t num_pixels, const void *pixels)
|
||||
{
|
||||
const uint8_t* src = static_cast<const uint8_t*>(pixels);
|
||||
|
||||
constexpr int dstStep = 1;
|
||||
const cv::Mat *pDst = static_cast<cv::Mat*>(opaque);
|
||||
uint8_t* dstBase = const_cast<uint8_t*>(reinterpret_cast<const uint8_t*>(pDst->ptr(y)));
|
||||
uint8_t* dst = dstBase + x * dstStep;
|
||||
|
||||
icvCvt_BGR2Gray_8u_C3C1R(src, 0, dst, 0, Size(num_pixels, 1) );
|
||||
}
|
||||
static void cbRGBAtoGRAY_8U(void *opaque, size_t x, size_t y, size_t num_pixels, const void *pixels)
|
||||
{
|
||||
const uint8_t* src = static_cast<const uint8_t*>(pixels);
|
||||
|
||||
constexpr int dstStep = 1;
|
||||
const cv::Mat *pDst = static_cast<cv::Mat*>(opaque);
|
||||
uint8_t* dstBase = const_cast<uint8_t*>(reinterpret_cast<const uint8_t*>(pDst->ptr(y)));
|
||||
uint8_t* dst = dstBase + x * dstStep;
|
||||
|
||||
icvCvt_BGRA2Gray_8u_C4C1R(src, 0, dst, 0, Size(num_pixels, 1) );
|
||||
}
|
||||
static void cbRGBtoGRAY_16U(void *opaque, size_t x, size_t y, size_t num_pixels, const void *pixels)
|
||||
{
|
||||
const uint16_t* src = static_cast<const uint16_t*>(pixels);
|
||||
|
||||
constexpr int dstStep = 1;
|
||||
const cv::Mat *pDst = static_cast<cv::Mat*>(opaque);
|
||||
uint16_t* dstBase = const_cast<uint16_t*>(reinterpret_cast<const uint16_t*>(pDst->ptr(y)));
|
||||
uint16_t* dst = dstBase + x * dstStep;
|
||||
|
||||
icvCvt_BGRA2Gray_16u_CnC1R(src, 0, dst, 0, Size(num_pixels, 1), /* ncn= */ 3 );
|
||||
}
|
||||
static void cbRGBAtoGRAY_16U(void *opaque, size_t x, size_t y, size_t num_pixels, const void *pixels)
|
||||
{
|
||||
const uint16_t* src = static_cast<const uint16_t*>(pixels);
|
||||
|
||||
constexpr int dstStep = 1;
|
||||
const cv::Mat *pDst = static_cast<cv::Mat*>(opaque);
|
||||
uint16_t* dstBase = const_cast<uint16_t*>(reinterpret_cast<const uint16_t*>(pDst->ptr(y)));
|
||||
uint16_t* dst = dstBase + x * dstStep;
|
||||
|
||||
icvCvt_BGRA2Gray_16u_CnC1R(src, 0, dst, 0, Size(num_pixels, 1), /* ncn= */ 4 );
|
||||
}
|
||||
static void cbRGBtoGRAY_32F(void *opaque, size_t x, size_t y, size_t num_pixels, const void *pixels)
|
||||
{
|
||||
constexpr float cR = 0.299f;
|
||||
constexpr float cG = 0.587f;
|
||||
constexpr float cB = 1.000f - cR - cG;
|
||||
|
||||
constexpr int srcStep = 3;
|
||||
const float* src = static_cast<const float*>(pixels);
|
||||
|
||||
constexpr int dstStep = 1;
|
||||
const cv::Mat *pDst = static_cast<cv::Mat*>(opaque);
|
||||
float* dstBase = const_cast<float*>(reinterpret_cast<const float*>(pDst->ptr(y)));
|
||||
float* dst = dstBase + x * dstStep;
|
||||
|
||||
for(size_t i = 0 ; i < num_pixels; i++)
|
||||
{
|
||||
dst[ i * dstStep ] = src[ i * srcStep + 0] * cR +
|
||||
src[ i * srcStep + 1] * cG +
|
||||
src[ i * srcStep + 2] * cB;
|
||||
}
|
||||
}
|
||||
static void cbRGBAtoGRAY_32F(void *opaque, size_t x, size_t y, size_t num_pixels, const void *pixels)
|
||||
{
|
||||
constexpr float cR = 0.299f;
|
||||
constexpr float cG = 0.587f;
|
||||
constexpr float cB = 1.000f - cR - cG;
|
||||
|
||||
constexpr int srcStep = 4;
|
||||
const float* src = static_cast<const float*>(pixels);
|
||||
|
||||
constexpr int dstStep = 1;
|
||||
const cv::Mat *pDst = static_cast<cv::Mat*>(opaque);
|
||||
float* dstBase = const_cast<float*>(reinterpret_cast<const float*>(pDst->ptr(y)));
|
||||
float* dst = dstBase + x * dstStep;
|
||||
|
||||
for(size_t i = 0 ; i < num_pixels; i++)
|
||||
{
|
||||
dst[ i * dstStep ] = src[ i * srcStep + 0] * cR +
|
||||
src[ i * srcStep + 1] * cG +
|
||||
src[ i * srcStep + 2] * cB;
|
||||
}
|
||||
}
|
||||
|
||||
/////////////////////// JpegXLEncoder ///////////////////
|
||||
|
||||
JpegXLEncoder::JpegXLEncoder()
|
||||
{
|
||||
m_description = "JPEG XL files (*.jxl)";
|
||||
m_buf_supported = true;
|
||||
m_supported_encode_key = {IMWRITE_JPEGXL_QUALITY, IMWRITE_JPEGXL_EFFORT, IMWRITE_JPEGXL_DISTANCE, IMWRITE_JPEGXL_DECODING_SPEED};
|
||||
}
|
||||
|
||||
JpegXLEncoder::~JpegXLEncoder()
|
||||
{
|
||||
}
|
||||
|
||||
ImageEncoder JpegXLEncoder::newEncoder() const
|
||||
{
|
||||
return makePtr<JpegXLEncoder>();
|
||||
}
|
||||
|
||||
bool JpegXLEncoder::isFormatSupported( int depth ) const
|
||||
{
|
||||
return depth == CV_8U || depth == CV_16U || depth == CV_32F;
|
||||
}
|
||||
|
||||
bool JpegXLEncoder::write(const Mat& img, const std::vector<int>& params)
|
||||
{
|
||||
m_last_error.clear();
|
||||
|
||||
JxlEncoderPtr encoder = JxlEncoderMake(nullptr);
|
||||
if (!encoder)
|
||||
return false;
|
||||
|
||||
JxlThreadParallelRunnerPtr runner = JxlThreadParallelRunnerMake(
|
||||
/*memory_manager=*/nullptr, cv::getNumThreads());
|
||||
if (JXL_ENC_SUCCESS != JxlEncoderSetParallelRunner(encoder.get(), JxlThreadParallelRunner, runner.get()))
|
||||
return false;
|
||||
|
||||
CV_CheckDepth(img.depth(),
|
||||
( img.depth() == CV_8U || img.depth() == CV_16U || img.depth() == CV_32F ),
|
||||
"JPEG XL encoder only supports CV_8U, CV_16U, CV_32F");
|
||||
CV_CheckChannels(img.channels(),
|
||||
( img.channels() == 1 || img.channels() == 3 || img.channels() == 4) ,
|
||||
"JPEG XL encoder only supports 1, 3, 4 channels");
|
||||
|
||||
WLByteStream strm;
|
||||
if( m_buf ) {
|
||||
if( !strm.open( *m_buf ) )
|
||||
return false;
|
||||
}
|
||||
else if( !strm.open( m_filename )) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// get distance param for JxlBasicInfo.
|
||||
float distance = -1.0; // Negative means not set
|
||||
for( size_t i = 0; i < params.size(); i += 2 )
|
||||
{
|
||||
const int value = params[i+1];
|
||||
if( params[i] == IMWRITE_JPEGXL_QUALITY )
|
||||
{
|
||||
#if JPEGXL_MAJOR_VERSION > 0 || JPEGXL_MINOR_VERSION >= 10
|
||||
const int quality = MIN(MAX(value, 0), 100);
|
||||
if(value != quality) {
|
||||
CV_LOG_WARNING(nullptr, cv::format("The value(%d) for IMWRITE_JPEGXL_QUALITY must be between 0 to 100, It is fallbacked to %d", value, quality));
|
||||
}
|
||||
distance = JxlEncoderDistanceFromQuality(static_cast<float>(quality));
|
||||
#else
|
||||
CV_LOG_ONCE_WARNING(NULL, "Quality parameter is supported with libjxl v0.10.0 or later");
|
||||
#endif
|
||||
}
|
||||
if( params[i] == IMWRITE_JPEGXL_DISTANCE )
|
||||
{
|
||||
const int distanceInt = MIN(MAX(value, 0), 25);
|
||||
if(value != distanceInt) {
|
||||
CV_LOG_WARNING(nullptr, cv::format("The value(%d) for IMWRITE_JPEGXL_DISTANCE must be between 0 to 25, It is fallbacked to %d", value, distanceInt));
|
||||
}
|
||||
distance = static_cast<float>(distanceInt);
|
||||
}
|
||||
}
|
||||
|
||||
JxlBasicInfo info;
|
||||
JxlEncoderInitBasicInfo(&info);
|
||||
info.xsize = img.cols;
|
||||
info.ysize = img.rows;
|
||||
// Lossless encoding requires uses_original_profile = true.
|
||||
info.uses_original_profile = (distance == 0.0) ? JXL_TRUE : JXL_FALSE;
|
||||
|
||||
if( img.channels() == 4 )
|
||||
{
|
||||
info.num_color_channels = 3;
|
||||
info.num_extra_channels = 1;
|
||||
|
||||
info.bits_per_sample =
|
||||
info.alpha_bits = 8 * static_cast<int>(img.elemSize1());
|
||||
|
||||
info.exponent_bits_per_sample =
|
||||
info.alpha_exponent_bits = img.depth() == CV_32F ? 8 : 0;
|
||||
}else{
|
||||
info.num_color_channels = img.channels();
|
||||
info.bits_per_sample = 8 * static_cast<int>(img.elemSize1());
|
||||
info.exponent_bits_per_sample = img.depth() == CV_32F ? 8 : 0;
|
||||
}
|
||||
|
||||
if (JxlEncoderSetBasicInfo(encoder.get(), &info) != JXL_ENC_SUCCESS)
|
||||
return false;
|
||||
|
||||
JxlDataType type = JXL_TYPE_UINT8;
|
||||
if (img.depth() == CV_32F)
|
||||
type = JXL_TYPE_FLOAT;
|
||||
else if (img.depth() == CV_16U)
|
||||
type = JXL_TYPE_UINT16;
|
||||
JxlPixelFormat format = {(uint32_t)img.channels(), type, JXL_NATIVE_ENDIAN, 0};
|
||||
JxlColorEncoding color_encoding = {};
|
||||
JXL_BOOL is_gray(format.num_channels < 3 ? JXL_TRUE : JXL_FALSE);
|
||||
JxlColorEncodingSetToSRGB(&color_encoding, is_gray);
|
||||
if (JXL_ENC_SUCCESS != JxlEncoderSetColorEncoding(encoder.get(), &color_encoding))
|
||||
return false;
|
||||
|
||||
Mat image;
|
||||
switch ( img.channels() ) {
|
||||
case 3:
|
||||
cv::cvtColor(img, image, cv::COLOR_BGR2RGB);
|
||||
break;
|
||||
case 4:
|
||||
cv::cvtColor(img, image, cv::COLOR_BGRA2RGBA);
|
||||
break;
|
||||
case 1:
|
||||
default:
|
||||
if(img.isContinuous()) {
|
||||
image = img;
|
||||
} else {
|
||||
image = img.clone(); // reconstruction as continuous image.
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (!image.isContinuous())
|
||||
return false;
|
||||
|
||||
JxlEncoderFrameSettings* frame_settings = JxlEncoderFrameSettingsCreate(encoder.get(), nullptr);
|
||||
|
||||
// set frame settings with distance params
|
||||
if(distance == 0.0) // lossless
|
||||
{
|
||||
if( JXL_ENC_SUCCESS != JxlEncoderSetFrameLossless(frame_settings, JXL_TRUE) )
|
||||
{
|
||||
CV_LOG_WARNING(NULL, "Failed to call JxlEncoderSetFrameLossless()");
|
||||
}
|
||||
}
|
||||
else if(distance > 0.0) // lossy
|
||||
{
|
||||
if( JXL_ENC_SUCCESS != JxlEncoderSetFrameDistance(frame_settings, distance) )
|
||||
{
|
||||
CV_LOG_WARNING(NULL, "Failed to call JxlEncoderSetFrameDistance()");
|
||||
}
|
||||
}
|
||||
|
||||
// set frame settings from params if available
|
||||
for( size_t i = 0; i < params.size(); i += 2 )
|
||||
{
|
||||
if( params[i] == IMWRITE_JPEGXL_EFFORT )
|
||||
{
|
||||
int effort = params[i+1];
|
||||
effort = MIN(MAX(effort, 1), 10);
|
||||
JxlEncoderFrameSettingsSetOption(frame_settings, JXL_ENC_FRAME_SETTING_EFFORT, effort);
|
||||
}
|
||||
if( params[i] == IMWRITE_JPEGXL_DECODING_SPEED )
|
||||
{
|
||||
int speed = params[i+1];
|
||||
speed = MIN(MAX(speed, 0), 4);
|
||||
JxlEncoderFrameSettingsSetOption(frame_settings, JXL_ENC_FRAME_SETTING_DECODING_SPEED, speed);
|
||||
}
|
||||
}
|
||||
if (JXL_ENC_SUCCESS !=
|
||||
JxlEncoderAddImageFrame(frame_settings, &format,
|
||||
static_cast<const void*>(image.ptr<uint8_t>()),
|
||||
image.total() * image.elemSize())) {
|
||||
return false;
|
||||
}
|
||||
JxlEncoderCloseInput(encoder.get());
|
||||
|
||||
const size_t buffer_size = 16384; // 16KB chunks
|
||||
|
||||
std::vector<uint8_t> compressed(buffer_size);
|
||||
JxlEncoderStatus process_result = JXL_ENC_NEED_MORE_OUTPUT;
|
||||
while (process_result == JXL_ENC_NEED_MORE_OUTPUT) {
|
||||
uint8_t* next_out = compressed.data();
|
||||
size_t avail_out = buffer_size;
|
||||
process_result = JxlEncoderProcessOutput(encoder.get(), &next_out, &avail_out);
|
||||
if (JXL_ENC_ERROR == process_result)
|
||||
return false;
|
||||
const size_t write_size = buffer_size - avail_out;
|
||||
if ( strm.putBytes(compressed.data(), write_size) == false )
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
/* End of file. */
|
||||
@@ -0,0 +1,68 @@
|
||||
// This file is part of OpenCV project.
|
||||
// It is subject to the license terms in the LICENSE file found in the top-level directory
|
||||
// of this distribution and at http://opencv.org/license.html.
|
||||
#ifndef _GRFMT_JPEGXL_H_
|
||||
#define _GRFMT_JPEGXL_H_
|
||||
|
||||
#ifdef HAVE_JPEGXL
|
||||
|
||||
#include "grfmt_base.hpp"
|
||||
#include <jxl/decode_cxx.h>
|
||||
#include <jxl/thread_parallel_runner_cxx.h>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
|
||||
// Jpeg XL codec
|
||||
|
||||
namespace cv
|
||||
{
|
||||
|
||||
/**
|
||||
* @brief JpegXL codec using libjxl library
|
||||
*/
|
||||
|
||||
class JpegXLDecoder CV_FINAL : public BaseImageDecoder
|
||||
{
|
||||
public:
|
||||
|
||||
JpegXLDecoder();
|
||||
virtual ~JpegXLDecoder();
|
||||
|
||||
bool readData( Mat& img ) CV_OVERRIDE;
|
||||
bool readHeader() CV_OVERRIDE;
|
||||
void close();
|
||||
size_t signatureLength() const CV_OVERRIDE;
|
||||
bool checkSignature( const String& signature ) const CV_OVERRIDE;
|
||||
|
||||
ImageDecoder newDecoder() const CV_OVERRIDE;
|
||||
|
||||
protected:
|
||||
std::unique_ptr<FILE, int (*)(FILE*)> m_f;
|
||||
JxlDecoderPtr m_decoder;
|
||||
JxlThreadParallelRunnerPtr m_parallel_runner;
|
||||
JxlPixelFormat m_format;
|
||||
std::vector<uint8_t> m_read_buffer;
|
||||
JxlDecoderStatus m_status;
|
||||
bool m_is_mbuf_set;
|
||||
|
||||
private:
|
||||
bool read();
|
||||
};
|
||||
|
||||
|
||||
class JpegXLEncoder CV_FINAL : public BaseImageEncoder
|
||||
{
|
||||
public:
|
||||
JpegXLEncoder();
|
||||
virtual ~JpegXLEncoder();
|
||||
|
||||
bool isFormatSupported( int depth ) const CV_OVERRIDE;
|
||||
bool write( const Mat& img, const std::vector<int>& params ) CV_OVERRIDE;
|
||||
ImageEncoder newEncoder() const CV_OVERRIDE;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#endif/*_GRFMT_JPEGXL_H_*/
|
||||
@@ -0,0 +1,787 @@
|
||||
/*M///////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
|
||||
//
|
||||
// By downloading, copying, installing or using the software you agree to this license.
|
||||
// If you do not agree to this license, do not download, install,
|
||||
// copy or use the software.
|
||||
//
|
||||
//
|
||||
// License Agreement
|
||||
// For Open Source Computer Vision Library
|
||||
// (3-clause BSD License)
|
||||
//
|
||||
// Copyright (C) 2000-2016, Intel Corporation, all rights reserved.
|
||||
// Copyright (C) 2009-2011, Willow Garage Inc., all rights reserved.
|
||||
// Copyright (C) 2009-2016, NVIDIA Corporation, all rights reserved.
|
||||
// Copyright (C) 2010-2013, Advanced Micro Devices, Inc., all rights reserved.
|
||||
// Copyright (C) 2015-2016, OpenCV Foundation, all rights reserved.
|
||||
// Copyright (C) 2015-2016, Itseez Inc., all rights reserved.
|
||||
// Third party copyrights are property of their respective owners.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without modification,
|
||||
// are permitted provided that the following conditions are met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright notice,
|
||||
// this list of conditions and the following disclaimer.
|
||||
//
|
||||
// * Redistributions in binary form must reproduce the above copyright notice,
|
||||
// this list of conditions and the following disclaimer in the documentation
|
||||
// and/or other materials provided with the distribution.
|
||||
//
|
||||
// * Neither the names of the copyright holders nor the names of the contributors
|
||||
// may be used to endorse or promote products derived from this software
|
||||
// without specific prior written permission.
|
||||
//
|
||||
// This software is provided by the copyright holders and contributors "as is" and
|
||||
// any express or implied warranties, including, but not limited to, the implied
|
||||
// warranties of merchantability and fitness for a particular purpose are disclaimed.
|
||||
// In no event shall copyright holders or contributors be liable for any direct,
|
||||
// indirect, incidental, special, exemplary, or consequential damages
|
||||
// (including, but not limited to, procurement of substitute goods or services;
|
||||
// loss of use, data, or profits; or business interruption) however caused
|
||||
// and on any theory of liability, whether in contract, strict liability,
|
||||
// or tort (including negligence or otherwise) arising in any way out of
|
||||
// the use of this software, even if advised of the possibility of such damage.
|
||||
//
|
||||
//M*/
|
||||
|
||||
#include "precomp.hpp"
|
||||
|
||||
|
||||
#ifdef HAVE_IMGCODEC_PXM
|
||||
|
||||
#include <cerrno>
|
||||
|
||||
#include "utils.hpp"
|
||||
#include "grfmt_pam.hpp"
|
||||
#include "opencv2/core/utils/logger.hpp"
|
||||
|
||||
namespace cv {
|
||||
|
||||
/* the PAM related fields */
|
||||
#define MAX_PAM_HEADER_IDENITFIER_LENGTH 8
|
||||
#define MAX_PAM_HEADER_VALUE_LENGTH 255
|
||||
|
||||
/* PAM header fields */
|
||||
typedef enum {
|
||||
PAM_HEADER_NONE,
|
||||
PAM_HEADER_COMMENT,
|
||||
PAM_HEADER_ENDHDR,
|
||||
PAM_HEADER_HEIGHT,
|
||||
PAM_HEADER_WIDTH,
|
||||
PAM_HEADER_DEPTH,
|
||||
PAM_HEADER_MAXVAL,
|
||||
PAM_HEADER_TUPLTYPE,
|
||||
} PamHeaderFieldType;
|
||||
|
||||
struct pam_header_field {
|
||||
PamHeaderFieldType type;
|
||||
char identifier[MAX_PAM_HEADER_IDENITFIER_LENGTH+1];
|
||||
};
|
||||
|
||||
const static struct pam_header_field fields[] = {
|
||||
{PAM_HEADER_ENDHDR, "ENDHDR"},
|
||||
{PAM_HEADER_HEIGHT, "HEIGHT"},
|
||||
{PAM_HEADER_WIDTH, "WIDTH"},
|
||||
{PAM_HEADER_DEPTH, "DEPTH"},
|
||||
{PAM_HEADER_MAXVAL, "MAXVAL"},
|
||||
{PAM_HEADER_TUPLTYPE, "TUPLTYPE"},
|
||||
};
|
||||
#define PAM_FIELDS_NO (sizeof (fields) / sizeof ((fields)[0]))
|
||||
|
||||
typedef bool (*cvtFunc) (void *src, void *target, int width, int target_channels,
|
||||
int target_depth, bool use_rgb);
|
||||
|
||||
struct channel_layout {
|
||||
uint rchan, gchan, bchan, graychan;
|
||||
};
|
||||
|
||||
struct pam_format {
|
||||
uint fmt;
|
||||
char name[MAX_PAM_HEADER_VALUE_LENGTH+1];
|
||||
cvtFunc cvt_func;
|
||||
/* the channel layout that should be used when
|
||||
* imread_ creates a 3 channel or 1 channel image
|
||||
* used when no conversion function is available
|
||||
*/
|
||||
struct channel_layout layout;
|
||||
};
|
||||
|
||||
static bool rgb_convert (void *src, void *target, int width, int target_channels,
|
||||
int target_depth, bool use_rgb);
|
||||
|
||||
const static struct pam_format formats[] = {
|
||||
{IMWRITE_PAM_FORMAT_NULL, "", NULL, {0, 0, 0, 0} },
|
||||
{IMWRITE_PAM_FORMAT_BLACKANDWHITE, "BLACKANDWHITE", NULL, {0, 0, 0, 0} },
|
||||
{IMWRITE_PAM_FORMAT_GRAYSCALE, "GRAYSCALE", NULL, {0, 0, 0, 0} },
|
||||
{IMWRITE_PAM_FORMAT_GRAYSCALE_ALPHA, "GRAYSCALE_ALPHA", NULL, {0, 0, 0, 0} },
|
||||
{IMWRITE_PAM_FORMAT_RGB, "RGB", rgb_convert, {0, 1, 2, 0} },
|
||||
{IMWRITE_PAM_FORMAT_RGB_ALPHA, "RGB_ALPHA", NULL, {0, 1, 2, 0} },
|
||||
};
|
||||
#define PAM_FORMATS_NO (sizeof (fields) / sizeof ((fields)[0]))
|
||||
|
||||
/*
|
||||
* conversion functions
|
||||
*/
|
||||
|
||||
static bool
|
||||
rgb_convert (void *src, void *target, int width, int target_channels, int target_depth, bool use_rgb)
|
||||
{
|
||||
bool ret = false;
|
||||
if (target_channels == 3) {
|
||||
switch (target_depth) {
|
||||
case CV_8U:
|
||||
if (use_rgb)
|
||||
memcpy(target, src, sizeof(uchar) * width);
|
||||
else
|
||||
icvCvt_RGB2BGR_8u_C3R( (uchar*) src, 0, (uchar*) target, 0,
|
||||
Size(width,1) );
|
||||
ret = true;
|
||||
break;
|
||||
case CV_16U:
|
||||
if (use_rgb)
|
||||
memcpy(target, src, sizeof(ushort) * width);
|
||||
else
|
||||
icvCvt_RGB2BGR_16u_C3R( (ushort *)src, 0, (ushort *)target, 0,
|
||||
Size(width,1) );
|
||||
ret = true;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
} else if (target_channels == 1) {
|
||||
switch (target_depth) {
|
||||
case CV_8U:
|
||||
icvCvt_BGR2Gray_8u_C3C1R( (uchar*) src, 0, (uchar*) target, 0,
|
||||
Size(width,1), 2 );
|
||||
ret = true;
|
||||
break;
|
||||
case CV_16U:
|
||||
icvCvt_BGRA2Gray_16u_CnC1R( (ushort *)src, 0, (ushort *)target, 0,
|
||||
Size(width,1), 3, 2 );
|
||||
ret = true;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* copy functions used as a fall back for undefined formats
|
||||
* or simpler conversion options
|
||||
*/
|
||||
|
||||
static void
|
||||
basic_conversion (void *src, const struct channel_layout *layout, int src_sample_size,
|
||||
int src_width, void *target, int target_channels, int target_depth, bool use_rgb)
|
||||
{
|
||||
switch (target_depth) {
|
||||
case CV_8U:
|
||||
{
|
||||
uchar *d = (uchar *)target, *s = (uchar *)src,
|
||||
*end = ((uchar *)src) + src_width * src_sample_size;
|
||||
switch (target_channels) {
|
||||
case 1:
|
||||
for( ; s < end; d += 1, s += src_sample_size )
|
||||
d[0] = s[layout->graychan];
|
||||
break;
|
||||
case 3:
|
||||
if (use_rgb)
|
||||
for( ; s < end; d += 3, s += src_sample_size ) {
|
||||
d[0] = s[layout->rchan];
|
||||
d[1] = s[layout->gchan];
|
||||
d[2] = s[layout->bchan];
|
||||
}
|
||||
else
|
||||
for( ; s < end; d += 3, s += src_sample_size ) {
|
||||
d[0] = s[layout->bchan];
|
||||
d[1] = s[layout->gchan];
|
||||
d[2] = s[layout->rchan];
|
||||
}
|
||||
break;
|
||||
default:
|
||||
CV_Error(Error::StsInternal, "");
|
||||
}
|
||||
break;
|
||||
}
|
||||
case CV_16U:
|
||||
{
|
||||
ushort *d = (ushort *)target, *s = (ushort *)src,
|
||||
*end = ((ushort *)src) + src_width * src_sample_size;
|
||||
switch (target_channels) {
|
||||
case 1:
|
||||
for( ; s < end; d += 1, s += src_sample_size )
|
||||
d[0] = s[layout->graychan];
|
||||
break;
|
||||
case 3:
|
||||
if (use_rgb)
|
||||
for( ; s < end; d += 3, s += src_sample_size ) {
|
||||
d[0] = s[layout->rchan];
|
||||
d[1] = s[layout->gchan];
|
||||
d[2] = s[layout->bchan];
|
||||
}
|
||||
else
|
||||
for( ; s < end; d += 3, s += src_sample_size ) {
|
||||
d[0] = s[layout->bchan];
|
||||
d[1] = s[layout->gchan];
|
||||
d[2] = s[layout->rchan];
|
||||
}
|
||||
break;
|
||||
default:
|
||||
CV_Error(Error::StsInternal, "");
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
CV_Error(Error::StsInternal, "");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static
|
||||
bool ReadPAMHeaderLine(
|
||||
cv::RLByteStream& strm,
|
||||
CV_OUT PamHeaderFieldType &fieldtype,
|
||||
CV_OUT char value[MAX_PAM_HEADER_VALUE_LENGTH+1])
|
||||
{
|
||||
int code;
|
||||
char ident[MAX_PAM_HEADER_IDENITFIER_LENGTH+1] = {};
|
||||
|
||||
do {
|
||||
code = strm.getByte();
|
||||
} while ( isspace(code) );
|
||||
|
||||
if (code == '#') {
|
||||
/* we are in a comment, eat characters until linebreak */
|
||||
do
|
||||
{
|
||||
code = strm.getByte();
|
||||
} while( code != '\n' && code != '\r' );
|
||||
fieldtype = PAM_HEADER_COMMENT;
|
||||
return true;
|
||||
} else if (code == '\n' || code == '\r' ) {
|
||||
fieldtype = PAM_HEADER_NONE;
|
||||
return true;
|
||||
}
|
||||
|
||||
int ident_sz = 0;
|
||||
for (; ident_sz < MAX_PAM_HEADER_IDENITFIER_LENGTH; ident_sz++)
|
||||
{
|
||||
if (isspace(code))
|
||||
break;
|
||||
ident[ident_sz] = (char)code;
|
||||
code = strm.getByte();
|
||||
}
|
||||
CV_DbgAssert(ident_sz <= MAX_PAM_HEADER_IDENITFIER_LENGTH);
|
||||
ident[ident_sz] = 0;
|
||||
|
||||
/* we may have filled the buffer and still have data */
|
||||
if (!isspace(code))
|
||||
return false;
|
||||
|
||||
bool ident_found = false;
|
||||
for (uint i = 0; i < PAM_FIELDS_NO; i++)
|
||||
{
|
||||
if (0 == strncmp(fields[i].identifier, ident, std::min(ident_sz, MAX_PAM_HEADER_IDENITFIER_LENGTH) + 1))
|
||||
{
|
||||
fieldtype = fields[i].type;
|
||||
ident_found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!ident_found)
|
||||
return false;
|
||||
|
||||
memset(value, 0, sizeof(char) * (MAX_PAM_HEADER_VALUE_LENGTH + 1));
|
||||
/* we may have an identifier that has no value */
|
||||
if (code == '\n' || code == '\r')
|
||||
return true;
|
||||
|
||||
do {
|
||||
code = strm.getByte();
|
||||
} while (isspace(code));
|
||||
|
||||
/* read identifier value */
|
||||
int value_sz = 0;
|
||||
for (; value_sz < MAX_PAM_HEADER_VALUE_LENGTH; value_sz++)
|
||||
{
|
||||
if (code == '\n' || code == '\r')
|
||||
break;
|
||||
value[value_sz] = (char)code;
|
||||
code = strm.getByte();
|
||||
}
|
||||
CV_DbgAssert(value_sz <= MAX_PAM_HEADER_VALUE_LENGTH);
|
||||
value[value_sz] = 0;
|
||||
|
||||
int pos = value_sz;
|
||||
|
||||
/* should be terminated */
|
||||
if (code != '\n' && code != '\r')
|
||||
return false;
|
||||
|
||||
/* remove trailing white spaces */
|
||||
while (--pos >= 0 && isspace(value[pos]))
|
||||
value[pos] = 0;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static int ParseInt(const char *str, int len)
|
||||
{
|
||||
CV_Assert(len > 0);
|
||||
|
||||
int pos = 0;
|
||||
bool is_negative = false;
|
||||
if (str[0] == '-')
|
||||
{
|
||||
is_negative = true;
|
||||
pos++;
|
||||
CV_Assert(isdigit(str[pos]));
|
||||
}
|
||||
uint64_t number = 0;
|
||||
while (pos < len && isdigit(str[pos]))
|
||||
{
|
||||
char ch = str[pos];
|
||||
number = (number * 10) + (uint64_t)((int)ch - (int)'0');
|
||||
CV_Assert(number < INT_MAX);
|
||||
pos++;
|
||||
}
|
||||
if (pos < len)
|
||||
CV_Assert(str[pos] == 0);
|
||||
return (is_negative) ? -(int)number : (int)number;
|
||||
}
|
||||
|
||||
|
||||
|
||||
PAMDecoder::PAMDecoder()
|
||||
{
|
||||
m_offset = -1;
|
||||
m_buf_supported = true;
|
||||
bit_mode = false;
|
||||
selected_fmt = IMWRITE_PAM_FORMAT_NULL;
|
||||
m_maxval = 0;
|
||||
m_channels = 0;
|
||||
m_sampledepth = 0;
|
||||
}
|
||||
|
||||
|
||||
PAMDecoder::~PAMDecoder()
|
||||
{
|
||||
m_strm.close();
|
||||
}
|
||||
|
||||
size_t PAMDecoder::signatureLength() const
|
||||
{
|
||||
return 3;
|
||||
}
|
||||
|
||||
bool PAMDecoder::checkSignature( const String& signature ) const
|
||||
{
|
||||
return signature.size() >= 3 && signature[0] == 'P' &&
|
||||
signature[1] == '7' &&
|
||||
isspace(signature[2]);
|
||||
}
|
||||
|
||||
ImageDecoder PAMDecoder::newDecoder() const
|
||||
{
|
||||
return makePtr<PAMDecoder>();
|
||||
}
|
||||
|
||||
bool PAMDecoder::readHeader()
|
||||
{
|
||||
PamHeaderFieldType fieldtype = PAM_HEADER_NONE;
|
||||
char value[MAX_PAM_HEADER_VALUE_LENGTH+1];
|
||||
int byte;
|
||||
|
||||
if( !m_buf.empty() )
|
||||
{
|
||||
if( !m_strm.open(m_buf) )
|
||||
return false;
|
||||
}
|
||||
else if( !m_strm.open( m_filename ))
|
||||
return false;
|
||||
|
||||
try
|
||||
{
|
||||
byte = m_strm.getByte();
|
||||
if( byte != 'P' )
|
||||
throw RBS_BAD_HEADER;
|
||||
|
||||
byte = m_strm.getByte();
|
||||
if (byte != '7')
|
||||
throw RBS_BAD_HEADER;
|
||||
|
||||
byte = m_strm.getByte();
|
||||
if (byte != '\n' && byte != '\r')
|
||||
throw RBS_BAD_HEADER;
|
||||
|
||||
bool flds_endhdr = false, flds_height = false, flds_width = false, flds_depth = false, flds_maxval = false;
|
||||
|
||||
do {
|
||||
if (!ReadPAMHeaderLine(m_strm, fieldtype, value))
|
||||
throw RBS_BAD_HEADER;
|
||||
switch (fieldtype)
|
||||
{
|
||||
case PAM_HEADER_NONE:
|
||||
case PAM_HEADER_COMMENT:
|
||||
continue;
|
||||
case PAM_HEADER_ENDHDR:
|
||||
flds_endhdr = true;
|
||||
break;
|
||||
case PAM_HEADER_HEIGHT:
|
||||
if (flds_height)
|
||||
throw RBS_BAD_HEADER;
|
||||
m_height = ParseInt(value, MAX_PAM_HEADER_VALUE_LENGTH);
|
||||
flds_height = true;
|
||||
break;
|
||||
case PAM_HEADER_WIDTH:
|
||||
if (flds_width)
|
||||
throw RBS_BAD_HEADER;
|
||||
m_width = ParseInt(value, MAX_PAM_HEADER_VALUE_LENGTH);
|
||||
flds_width = true;
|
||||
break;
|
||||
case PAM_HEADER_DEPTH:
|
||||
if (flds_depth)
|
||||
throw RBS_BAD_HEADER;
|
||||
m_channels = ParseInt(value, MAX_PAM_HEADER_VALUE_LENGTH);
|
||||
flds_depth = true;
|
||||
break;
|
||||
case PAM_HEADER_MAXVAL:
|
||||
if (flds_maxval)
|
||||
throw RBS_BAD_HEADER;
|
||||
m_maxval = ParseInt(value, MAX_PAM_HEADER_VALUE_LENGTH);
|
||||
if ( m_maxval > 65535 )
|
||||
throw RBS_BAD_HEADER;
|
||||
m_sampledepth = (m_maxval > 255) ? CV_16U : CV_8U;
|
||||
if (m_maxval == 1)
|
||||
bit_mode = true;
|
||||
flds_maxval = true;
|
||||
break;
|
||||
case PAM_HEADER_TUPLTYPE:
|
||||
{
|
||||
bool format_found = false;
|
||||
for (uint i=0; i<PAM_FORMATS_NO; i++)
|
||||
{
|
||||
if (0 == strncmp(formats[i].name, value, MAX_PAM_HEADER_VALUE_LENGTH+1))
|
||||
{
|
||||
selected_fmt = formats[i].fmt;
|
||||
format_found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
CV_Assert(format_found);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw RBS_BAD_HEADER;
|
||||
}
|
||||
} while (fieldtype != PAM_HEADER_ENDHDR);
|
||||
|
||||
if (selected_fmt != IMWRITE_PAM_FORMAT_NULL && flds_depth) {
|
||||
if (selected_fmt == IMWRITE_PAM_FORMAT_BLACKANDWHITE && m_channels != 1) {
|
||||
CV_Error(Error::StsError, "fmt is IMWRITE_PAM_FORMAT_BLACKANDWHITE but number of channels is not 1");
|
||||
}
|
||||
if (selected_fmt == IMWRITE_PAM_FORMAT_GRAYSCALE && m_channels != 1) {
|
||||
CV_Error(Error::StsError, "fmt is IMWRITE_PAM_FORMAT_GRAYSCALE but number of channels is not 1");
|
||||
}
|
||||
if (selected_fmt == IMWRITE_PAM_FORMAT_GRAYSCALE_ALPHA && m_channels != 2) {
|
||||
CV_Error(Error::StsError, "fmt is IMWRITE_PAM_FORMAT_GRAYSCALE_ALPHA but number of channels is not 2");
|
||||
}
|
||||
if (selected_fmt == IMWRITE_PAM_FORMAT_RGB && m_channels != 3) {
|
||||
CV_Error(Error::StsError, "fmt is IMWRITE_PAM_FORMAT_RGB but number of channels is not 3");
|
||||
}
|
||||
if (selected_fmt == IMWRITE_PAM_FORMAT_RGB_ALPHA && m_channels != 4) {
|
||||
CV_Error(Error::StsError, "fmt is IMWRITE_PAM_FORMAT_RGB_ALPHA but number of channels is not 4");
|
||||
}
|
||||
}
|
||||
if (flds_endhdr && flds_height && flds_width && flds_depth && flds_maxval)
|
||||
{
|
||||
if (selected_fmt == IMWRITE_PAM_FORMAT_NULL)
|
||||
{
|
||||
if (m_channels == 1 && m_maxval == 1)
|
||||
selected_fmt = IMWRITE_PAM_FORMAT_BLACKANDWHITE;
|
||||
else if (m_channels == 1 && m_maxval < 256)
|
||||
selected_fmt = IMWRITE_PAM_FORMAT_GRAYSCALE;
|
||||
else if (m_channels == 3 && m_maxval < 256)
|
||||
selected_fmt = IMWRITE_PAM_FORMAT_RGB;
|
||||
else
|
||||
CV_Error(Error::StsError, "Can't determine selected_fmt (IMWRITE_PAM_FORMAT_NULL)");
|
||||
}
|
||||
CV_CheckDepth(m_sampledepth, m_sampledepth == CV_8U || m_sampledepth == CV_16U, "");
|
||||
CV_Check(m_channels, m_channels >= 1 && m_channels <= 4, "Unsupported number of channels");
|
||||
m_type = CV_MAKETYPE(m_sampledepth, m_channels);
|
||||
m_offset = m_strm.getPos();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// failed
|
||||
m_offset = -1;
|
||||
m_width = m_height = -1;
|
||||
m_strm.close();
|
||||
return false;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
m_offset = -1;
|
||||
m_width = m_height = -1;
|
||||
m_strm.close();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool PAMDecoder::readData(Mat& img)
|
||||
{
|
||||
uchar* data = img.ptr();
|
||||
const int target_channels = img.channels();
|
||||
size_t imp_stride = img.step;
|
||||
const int sample_depth = CV_ELEM_SIZE1(m_type);
|
||||
const int src_elems_per_row = m_width*m_channels;
|
||||
const int src_stride = src_elems_per_row*sample_depth;
|
||||
PaletteEntry palette[256] = {};
|
||||
const struct pam_format *fmt = NULL;
|
||||
struct channel_layout layout = { 0, 0, 0, 0 }; // normalized to 1-channel grey format
|
||||
|
||||
/* setting buffer to max data size so scaling up is possible */
|
||||
AutoBuffer<uchar> _src(src_elems_per_row * 2);
|
||||
uchar* src = _src.data();
|
||||
|
||||
if( m_offset < 0 || !m_strm.isOpened())
|
||||
return false;
|
||||
|
||||
if (selected_fmt != IMWRITE_PAM_FORMAT_NULL)
|
||||
fmt = &formats[selected_fmt];
|
||||
else {
|
||||
/* default layout handling */
|
||||
if (m_channels >= 3) {
|
||||
layout.bchan = 0;
|
||||
layout.gchan = 1;
|
||||
layout.rchan = 2;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
m_strm.setPos( m_offset );
|
||||
|
||||
/* the case where data fits the opencv matrix */
|
||||
if (m_sampledepth == img.depth() && target_channels == m_channels && !bit_mode) {
|
||||
/* special case for 16bit images with wrong endianness */
|
||||
if (m_sampledepth == CV_16U && !isBigEndian())
|
||||
{
|
||||
for (int y = 0; y < m_height; y++, data += imp_stride)
|
||||
{
|
||||
m_strm.getBytes( src, src_stride );
|
||||
for (int x = 0; x < src_elems_per_row; x++)
|
||||
{
|
||||
uchar v = src[x * 2];
|
||||
data[x * 2] = src[x * 2 + 1];
|
||||
data[x * 2 + 1] = v;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
m_strm.getBytes( data, src_stride * m_height );
|
||||
}
|
||||
|
||||
}
|
||||
else {
|
||||
/* black and white mode */
|
||||
if (bit_mode) {
|
||||
if( target_channels == 1 )
|
||||
{
|
||||
uchar gray_palette[2] = {0, 255};
|
||||
for (int y = 0; y < m_height; y++, data += imp_stride)
|
||||
{
|
||||
m_strm.getBytes( src, src_stride );
|
||||
FillGrayRow1( data, src, m_width, gray_palette );
|
||||
}
|
||||
} else if ( target_channels == 3 )
|
||||
{
|
||||
FillGrayPalette( palette, 1 , false );
|
||||
for (int y = 0; y < m_height; y++, data += imp_stride)
|
||||
{
|
||||
m_strm.getBytes( src, src_stride );
|
||||
FillColorRow1( data, src, m_width, palette );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
CV_Error(Error::StsError, cv::format("Unsupported value of target_channels: %d", target_channels));
|
||||
}
|
||||
} else {
|
||||
for (int y = 0; y < m_height; y++, data += imp_stride)
|
||||
{
|
||||
m_strm.getBytes( src, src_stride );
|
||||
|
||||
/* endianness correction */
|
||||
if( m_sampledepth == CV_16U && !isBigEndian() )
|
||||
{
|
||||
for (int x = 0; x < src_elems_per_row; x++)
|
||||
{
|
||||
uchar v = src[x * 2];
|
||||
src[x * 2] = src[x * 2 + 1];
|
||||
src[x * 2 + 1] = v;
|
||||
}
|
||||
}
|
||||
|
||||
/* scale down */
|
||||
if( img.depth() == CV_8U && m_sampledepth == CV_16U )
|
||||
{
|
||||
for (int x = 0; x < src_elems_per_row; x++)
|
||||
{
|
||||
int v = ((ushort *)src)[x];
|
||||
src[x] = (uchar)(v >> 8);
|
||||
}
|
||||
}
|
||||
|
||||
/* if we are only scaling up/down then we can then copy the data */
|
||||
if (target_channels == m_channels) {
|
||||
memcpy (data, src, imp_stride);
|
||||
}
|
||||
/* perform correct conversion based on format */
|
||||
else if (fmt) {
|
||||
bool funcout = false;
|
||||
if (fmt->cvt_func)
|
||||
funcout = fmt->cvt_func (src, data, m_width, target_channels,
|
||||
img.depth(), m_use_rgb);
|
||||
/* fall back to default if there is no conversion function or it
|
||||
* can't handle the specified characteristics
|
||||
*/
|
||||
if (!funcout)
|
||||
basic_conversion (src, &fmt->layout, m_channels,
|
||||
m_width, data, target_channels, img.depth(), m_use_rgb);
|
||||
|
||||
/* default to selecting the first available channels */
|
||||
} else {
|
||||
basic_conversion (src, &layout, m_channels,
|
||||
m_width, data, target_channels, img.depth(), m_use_rgb);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
PAMEncoder::PAMEncoder()
|
||||
{
|
||||
m_description = "Portable arbitrary format (*.pam)";
|
||||
m_buf_supported = true;
|
||||
m_supported_encode_key = {IMWRITE_PAM_TUPLETYPE};
|
||||
}
|
||||
|
||||
|
||||
PAMEncoder::~PAMEncoder()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
ImageEncoder PAMEncoder::newEncoder() const
|
||||
{
|
||||
return makePtr<PAMEncoder>();
|
||||
}
|
||||
|
||||
|
||||
bool PAMEncoder::isFormatSupported( int depth ) const
|
||||
{
|
||||
return depth == CV_8U || depth == CV_16U;
|
||||
}
|
||||
|
||||
|
||||
bool PAMEncoder::write( const Mat& img, const std::vector<int>& params )
|
||||
{
|
||||
|
||||
WLByteStream strm;
|
||||
|
||||
int width = img.cols, height = img.rows;
|
||||
int stride = width*(int)img.elemSize();
|
||||
const uchar* data = img.ptr();
|
||||
const struct pam_format *fmt = NULL;
|
||||
int x, y, tmp, bufsize = 256;
|
||||
|
||||
/* parse save file type */
|
||||
for( size_t i = 0; i < params.size(); i += 2 ) {
|
||||
const int value = params[i+1];
|
||||
if( params[i] == IMWRITE_PAM_TUPLETYPE ) {
|
||||
switch(value) {
|
||||
case IMWRITE_PAM_FORMAT_NULL:
|
||||
case IMWRITE_PAM_FORMAT_BLACKANDWHITE:
|
||||
case IMWRITE_PAM_FORMAT_GRAYSCALE:
|
||||
case IMWRITE_PAM_FORMAT_GRAYSCALE_ALPHA:
|
||||
case IMWRITE_PAM_FORMAT_RGB:
|
||||
case IMWRITE_PAM_FORMAT_RGB_ALPHA:
|
||||
fmt = &formats[value];
|
||||
break;
|
||||
default:
|
||||
CV_LOG_WARNING(nullptr, cv::format("The value(%d) for IMWRITE_PAM_TUPLETYPE must be one of ImwritePAMFlags. It is ignored", value));
|
||||
fmt = nullptr;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if( m_buf )
|
||||
{
|
||||
if( !strm.open(*m_buf) )
|
||||
return false;
|
||||
m_buf->reserve( alignSize(256 + stride*height, 256));
|
||||
}
|
||||
else if( !strm.open(m_filename) )
|
||||
return false;
|
||||
|
||||
tmp = width * (int)img.elemSize();
|
||||
|
||||
if (bufsize < tmp)
|
||||
bufsize = tmp;
|
||||
|
||||
AutoBuffer<char> _buffer(bufsize);
|
||||
char* buffer = _buffer.data();
|
||||
|
||||
/* write header */
|
||||
tmp = 0;
|
||||
tmp += snprintf( buffer, bufsize, "P7\n");
|
||||
tmp += snprintf( buffer + tmp, bufsize - tmp, "WIDTH %d\n", width);
|
||||
tmp += snprintf( buffer + tmp, bufsize - tmp, "HEIGHT %d\n", height);
|
||||
tmp += snprintf( buffer + tmp, bufsize - tmp, "DEPTH %d\n", img.channels());
|
||||
tmp += snprintf( buffer + tmp, bufsize - tmp, "MAXVAL %d\n", (1 << img.elemSize1()*8) - 1);
|
||||
if (fmt)
|
||||
tmp += snprintf( buffer + tmp, bufsize - tmp, "TUPLTYPE %s\n", fmt->name );
|
||||
snprintf( buffer + tmp, bufsize - tmp, "ENDHDR\n" );
|
||||
|
||||
strm.putBytes( buffer, (int)strlen(buffer) );
|
||||
/* write data */
|
||||
if (img.depth() == CV_8U)
|
||||
strm.putBytes( data, stride*height );
|
||||
else if (img.depth() == CV_16U) {
|
||||
/* fix endianness */
|
||||
if (!isBigEndian()) {
|
||||
for( y = 0; y < height; y++ ) {
|
||||
memcpy( buffer, img.ptr(y), stride );
|
||||
for( x = 0; x < stride; x += 2 )
|
||||
{
|
||||
uchar v = buffer[x];
|
||||
buffer[x] = buffer[x + 1];
|
||||
buffer[x + 1] = v;
|
||||
}
|
||||
strm.putBytes( buffer, stride );
|
||||
}
|
||||
} else
|
||||
strm.putBytes( data, stride*height );
|
||||
} else
|
||||
CV_Error(Error::StsInternal, "");
|
||||
|
||||
strm.close();
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,103 @@
|
||||
/*M///////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
|
||||
//
|
||||
// By downloading, copying, installing or using the software you agree to this license.
|
||||
// If you do not agree to this license, do not download, install,
|
||||
// copy or use the software.
|
||||
//
|
||||
//
|
||||
// License Agreement
|
||||
// For Open Source Computer Vision Library
|
||||
// (3-clause BSD License)
|
||||
//
|
||||
// Copyright (C) 2000-2016, Intel Corporation, all rights reserved.
|
||||
// Copyright (C) 2009-2011, Willow Garage Inc., all rights reserved.
|
||||
// Copyright (C) 2009-2016, NVIDIA Corporation, all rights reserved.
|
||||
// Copyright (C) 2010-2013, Advanced Micro Devices, Inc., all rights reserved.
|
||||
// Copyright (C) 2015-2016, OpenCV Foundation, all rights reserved.
|
||||
// Copyright (C) 2015-2016, Itseez Inc., all rights reserved.
|
||||
// Third party copyrights are property of their respective owners.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without modification,
|
||||
// are permitted provided that the following conditions are met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright notice,
|
||||
// this list of conditions and the following disclaimer.
|
||||
//
|
||||
// * Redistributions in binary form must reproduce the above copyright notice,
|
||||
// this list of conditions and the following disclaimer in the documentation
|
||||
// and/or other materials provided with the distribution.
|
||||
//
|
||||
// * Neither the names of the copyright holders nor the names of the contributors
|
||||
// may be used to endorse or promote products derived from this software
|
||||
// without specific prior written permission.
|
||||
//
|
||||
// This software is provided by the copyright holders and contributors "as is" and
|
||||
// any express or implied warranties, including, but not limited to, the implied
|
||||
// warranties of merchantability and fitness for a particular purpose are disclaimed.
|
||||
// In no event shall copyright holders or contributors be liable for any direct,
|
||||
// indirect, incidental, special, exemplary, or consequential damages
|
||||
// (including, but not limited to, procurement of substitute goods or services;
|
||||
// loss of use, data, or profits; or business interruption) however caused
|
||||
// and on any theory of liability, whether in contract, strict liability,
|
||||
// or tort (including negligence or otherwise) arising in any way out of
|
||||
// the use of this software, even if advised of the possibility of such damage.
|
||||
//
|
||||
//
|
||||
//M*/
|
||||
|
||||
//Based on "imgcodecs/src/grfmt_pxm.hpp"
|
||||
//Written by Dimitrios Katsaros <patcherwork@gmail.com>
|
||||
|
||||
#ifndef _OPENCV_PAM_HPP_
|
||||
#define _OPENCV_PAM_HPP_
|
||||
|
||||
#ifdef HAVE_IMGCODEC_PXM
|
||||
|
||||
#include "grfmt_base.hpp"
|
||||
#include "bitstrm.hpp"
|
||||
|
||||
namespace cv
|
||||
{
|
||||
|
||||
class PAMDecoder CV_FINAL : public BaseImageDecoder
|
||||
{
|
||||
public:
|
||||
|
||||
PAMDecoder();
|
||||
virtual ~PAMDecoder() CV_OVERRIDE;
|
||||
|
||||
bool readData( Mat& img ) CV_OVERRIDE;
|
||||
bool readHeader() CV_OVERRIDE;
|
||||
|
||||
size_t signatureLength() const CV_OVERRIDE;
|
||||
bool checkSignature( const String& signature ) const CV_OVERRIDE;
|
||||
ImageDecoder newDecoder() const CV_OVERRIDE;
|
||||
|
||||
protected:
|
||||
|
||||
RLByteStream m_strm;
|
||||
int64_t m_offset;
|
||||
int m_maxval, m_channels, m_sampledepth, selected_fmt;
|
||||
bool bit_mode;
|
||||
};
|
||||
|
||||
|
||||
class PAMEncoder CV_FINAL : public BaseImageEncoder
|
||||
{
|
||||
public:
|
||||
PAMEncoder();
|
||||
virtual ~PAMEncoder() CV_OVERRIDE;
|
||||
|
||||
bool isFormatSupported( int depth ) const CV_OVERRIDE;
|
||||
bool write( const Mat& img, const std::vector<int>& params ) CV_OVERRIDE;
|
||||
|
||||
ImageEncoder newEncoder() const CV_OVERRIDE;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#endif /* _OPENCV_PAM_HPP_ */
|
||||
@@ -0,0 +1,260 @@
|
||||
// This file is part of OpenCV project.
|
||||
// It is subject to the license terms in the LICENSE file found in the top-level directory
|
||||
// of this distribution and at http://opencv.org/license.html.
|
||||
|
||||
#include "precomp.hpp"
|
||||
#include "utils.hpp"
|
||||
#include "grfmt_pfm.hpp"
|
||||
#include <iostream>
|
||||
|
||||
#ifdef HAVE_IMGCODEC_PFM
|
||||
|
||||
namespace {
|
||||
|
||||
static_assert(sizeof(float) == 4, "float must be 32 bit.");
|
||||
|
||||
|
||||
bool is_byte_order_swapped(double scale)
|
||||
{
|
||||
// ".pfm" format file specifies that:
|
||||
// positive scale means big endianness;
|
||||
// negative scale means little endianness.
|
||||
|
||||
#ifdef WORDS_BIGENDIAN
|
||||
return scale < 0.0;
|
||||
#else
|
||||
return scale >= 0.0;
|
||||
#endif
|
||||
}
|
||||
|
||||
void swap_endianness(uint32_t& ui)
|
||||
{
|
||||
static const uint32_t A(0x000000ffU);
|
||||
static const uint32_t B(0x0000ff00U);
|
||||
static const uint32_t C(0x00ff0000U);
|
||||
static const uint32_t D(0xff000000U);
|
||||
|
||||
ui = ( (ui & A) << 24 )
|
||||
| ( (ui & B) << 8 )
|
||||
| ( (ui & C) >> 8 )
|
||||
| ( (ui & D) >> 24 );
|
||||
}
|
||||
|
||||
template<typename T> T atoT(const std::string& s);
|
||||
template<> int atoT<int>(const std::string& s) { return std::atoi(s.c_str()); }
|
||||
template<> double atoT<double>(const std::string& s) { return std::atof(s.c_str()); }
|
||||
|
||||
template<typename T>
|
||||
T read_number(cv::RLByteStream& strm)
|
||||
{
|
||||
// should be enough to take string representation of any number
|
||||
const size_t buffer_size = 2048;
|
||||
|
||||
std::vector<char> buffer(buffer_size, 0);
|
||||
for (size_t i = 0; i < buffer_size; ++i) {
|
||||
const int intc = strm.getByte();
|
||||
CV_Assert(intc >= -128 && intc < 128);
|
||||
char c = static_cast<char>(intc);
|
||||
if (std::isspace(c)) {
|
||||
break;
|
||||
}
|
||||
buffer[i] = c;
|
||||
}
|
||||
const std::string str(buffer.begin(), buffer.end());
|
||||
return atoT<T>(str);
|
||||
}
|
||||
|
||||
template<typename T> bool write_anything(cv::WLByteStream& strm, const T& t)
|
||||
{
|
||||
std::ostringstream ss;
|
||||
ss << t;
|
||||
return strm.putBytes(ss.str().c_str(), static_cast<int>(ss.str().size()));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
namespace cv {
|
||||
|
||||
PFMDecoder::~PFMDecoder()
|
||||
{
|
||||
}
|
||||
|
||||
PFMDecoder::PFMDecoder() : m_scale_factor(0), m_swap_byte_order(false)
|
||||
{
|
||||
m_buf_supported = true;
|
||||
}
|
||||
|
||||
bool PFMDecoder::readHeader()
|
||||
{
|
||||
if (!m_buf.empty())
|
||||
m_strm.open(m_buf);
|
||||
else
|
||||
m_strm.open(m_filename);
|
||||
|
||||
if( !m_strm.isOpened()) return false;
|
||||
|
||||
if (m_strm.getByte() != 'P') {
|
||||
CV_Error(Error::StsError, "Unexpected file type (expected P)");
|
||||
}
|
||||
|
||||
switch (m_strm.getByte()) {
|
||||
case 'f':
|
||||
m_type = CV_32FC1;
|
||||
break;
|
||||
case 'F':
|
||||
m_type = CV_32FC3;
|
||||
break;
|
||||
default:
|
||||
CV_Error(Error::StsError, "Unexpected file type (expected `f` or `F`)");
|
||||
}
|
||||
|
||||
if ('\n' != m_strm.getByte()) {
|
||||
CV_Error(Error::StsError, "Unexpected header format (expected line break)");
|
||||
}
|
||||
|
||||
|
||||
m_width = read_number<int>(m_strm);
|
||||
m_height = read_number<int>(m_strm);
|
||||
m_scale_factor = read_number<double>(m_strm);
|
||||
m_swap_byte_order = is_byte_order_swapped(m_scale_factor);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PFMDecoder::readData(Mat& mat)
|
||||
{
|
||||
if (!m_strm.isOpened()) {
|
||||
CV_Error(Error::StsError, "Unexpected status in data stream");
|
||||
}
|
||||
|
||||
Mat buffer(mat.size(), m_type);
|
||||
for (int y = m_height - 1; y >= 0; --y) {
|
||||
m_strm.getBytes(buffer.ptr(y), static_cast<int>(m_width * buffer.elemSize()));
|
||||
if (is_byte_order_swapped(m_scale_factor)) {
|
||||
for (int i = 0; i < m_width * buffer.channels(); ++i) {
|
||||
static_assert( sizeof(uint32_t) == sizeof(float),
|
||||
"uint32_t and float must have same size." );
|
||||
swap_endianness(buffer.ptr<uint32_t>(y)[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (buffer.channels() == 3 && !m_use_rgb) {
|
||||
cv::cvtColor(buffer, buffer, cv::COLOR_BGR2RGB);
|
||||
}
|
||||
|
||||
CV_Assert(fabs(m_scale_factor) > 0.0f);
|
||||
buffer *= 1.f / fabs(m_scale_factor);
|
||||
|
||||
buffer.convertTo(mat, mat.type());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
size_t PFMDecoder::signatureLength() const
|
||||
{
|
||||
return 3;
|
||||
}
|
||||
|
||||
bool PFMDecoder::checkSignature( const String& signature ) const
|
||||
{
|
||||
return signature.size() >= 3
|
||||
&& signature[0] == 'P'
|
||||
&& ( signature[1] == 'f' || signature[1] == 'F' )
|
||||
&& isspace(signature[2]);
|
||||
}
|
||||
|
||||
void PFMDecoder::close()
|
||||
{
|
||||
// noop
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
PFMEncoder::PFMEncoder()
|
||||
{
|
||||
m_description = "Portable image format - float (*.pfm)";
|
||||
m_buf_supported = true;
|
||||
}
|
||||
|
||||
PFMEncoder::~PFMEncoder()
|
||||
{
|
||||
}
|
||||
|
||||
bool PFMEncoder::isFormatSupported(int depth) const
|
||||
{
|
||||
// any depth will be converted into 32-bit float.
|
||||
CV_UNUSED(depth);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PFMEncoder::write(const Mat& img, const std::vector<int>& params)
|
||||
{
|
||||
CV_UNUSED(params);
|
||||
|
||||
WLByteStream strm;
|
||||
if (m_buf) {
|
||||
if (!strm.open(*m_buf)) {
|
||||
return false;
|
||||
} else {
|
||||
m_buf->reserve(alignSize(256 + sizeof(float) * img.channels() * img.total(), 256));
|
||||
}
|
||||
} else if (!strm.open(m_filename)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Mat float_img;
|
||||
CHECK_WRITE(strm.putByte('P'));
|
||||
switch (img.channels()) {
|
||||
case 1:
|
||||
CHECK_WRITE(strm.putByte('f'));
|
||||
img.convertTo(float_img, CV_32FC1);
|
||||
break;
|
||||
case 3:
|
||||
CHECK_WRITE(strm.putByte('F'));
|
||||
img.convertTo(float_img, CV_32FC3);
|
||||
break;
|
||||
default:
|
||||
CV_Error(Error::StsBadArg, "Expected 1 or 3 channel image.");
|
||||
}
|
||||
CHECK_WRITE(strm.putByte('\n'));
|
||||
|
||||
|
||||
CHECK_WRITE(write_anything(strm, float_img.cols));
|
||||
CHECK_WRITE(strm.putByte(' '));
|
||||
CHECK_WRITE(write_anything(strm, float_img.rows));
|
||||
CHECK_WRITE(strm.putByte('\n'));
|
||||
#ifdef WORDS_BIGENDIAN
|
||||
CHECK_WRITE(write_anything(strm, 1.0));
|
||||
#else
|
||||
CHECK_WRITE(write_anything(strm, -1.0));
|
||||
#endif
|
||||
|
||||
CHECK_WRITE(strm.putByte('\n'));
|
||||
|
||||
// Comments are not officially supported in this file format.
|
||||
// write_anything(strm, "# Generated by OpenCV " CV_VERSION "\n");
|
||||
|
||||
for (int y = float_img.rows - 1; y >= 0; --y)
|
||||
{
|
||||
if (float_img.channels() == 3) {
|
||||
const float* bgr_row = float_img.ptr<float>(y);
|
||||
size_t row_size = float_img.cols * float_img.channels();
|
||||
std::vector<float> rgb_row(row_size);
|
||||
for (int x = 0; x < float_img.cols; ++x) {
|
||||
rgb_row[x*3+0] = bgr_row[x*3+2];
|
||||
rgb_row[x*3+1] = bgr_row[x*3+1];
|
||||
rgb_row[x*3+2] = bgr_row[x*3+0];
|
||||
}
|
||||
CHECK_WRITE(strm.putBytes( reinterpret_cast<const uchar*>(rgb_row.data()),
|
||||
static_cast<int>(sizeof(float) * row_size) ));
|
||||
} else if (float_img.channels() == 1) {
|
||||
CHECK_WRITE(strm.putBytes(float_img.ptr(y), sizeof(float) * float_img.cols));
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif // HAVE_IMGCODEC_PFM
|
||||
@@ -0,0 +1,57 @@
|
||||
// This file is part of OpenCV project.
|
||||
// It is subject to the license terms in the LICENSE file found in the top-level directory
|
||||
// of this distribution and at http://opencv.org/license.html.
|
||||
|
||||
#ifndef _GRFMT_PFM_H_
|
||||
#define _GRFMT_PFM_H_
|
||||
|
||||
#include "grfmt_base.hpp"
|
||||
#include "bitstrm.hpp"
|
||||
|
||||
#ifdef HAVE_IMGCODEC_PFM
|
||||
namespace cv
|
||||
{
|
||||
|
||||
class PFMDecoder CV_FINAL : public BaseImageDecoder
|
||||
{
|
||||
public:
|
||||
PFMDecoder();
|
||||
virtual ~PFMDecoder() CV_OVERRIDE;
|
||||
|
||||
bool readData( Mat& img ) CV_OVERRIDE;
|
||||
bool readHeader() CV_OVERRIDE;
|
||||
void close();
|
||||
|
||||
size_t signatureLength() const CV_OVERRIDE;
|
||||
bool checkSignature( const String& signature ) const CV_OVERRIDE;
|
||||
ImageDecoder newDecoder() const CV_OVERRIDE
|
||||
{
|
||||
return makePtr<PFMDecoder>();
|
||||
}
|
||||
|
||||
private:
|
||||
RLByteStream m_strm;
|
||||
double m_scale_factor;
|
||||
bool m_swap_byte_order;
|
||||
};
|
||||
|
||||
class PFMEncoder CV_FINAL : public BaseImageEncoder
|
||||
{
|
||||
public:
|
||||
PFMEncoder();
|
||||
virtual ~PFMEncoder() CV_OVERRIDE;
|
||||
|
||||
bool isFormatSupported( int depth ) const CV_OVERRIDE;
|
||||
bool write( const Mat& img, const std::vector<int>& params ) CV_OVERRIDE;
|
||||
|
||||
ImageEncoder newEncoder() const CV_OVERRIDE
|
||||
{
|
||||
return makePtr<PFMEncoder>();
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // HAVE_IMGCODEC_PXM
|
||||
|
||||
#endif/*_GRFMT_PFM_H_*/
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,244 @@
|
||||
/*M///////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
|
||||
//
|
||||
// By downloading, copying, installing or using the software you agree to this license.
|
||||
// If you do not agree to this license, do not download, install,
|
||||
// copy or use the software.
|
||||
//
|
||||
//
|
||||
// License Agreement
|
||||
// For Open Source Computer Vision Library
|
||||
//
|
||||
// Copyright (C) 2000-2008, Intel Corporation, all rights reserved.
|
||||
// Copyright (C) 2009, Willow Garage Inc., all rights reserved.
|
||||
// Third party copyrights are property of their respective owners.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without modification,
|
||||
// are permitted provided that the following conditions are met:
|
||||
//
|
||||
// * Redistribution's of source code must retain the above copyright notice,
|
||||
// this list of conditions and the following disclaimer.
|
||||
//
|
||||
// * Redistribution's in binary form must reproduce the above copyright notice,
|
||||
// this list of conditions and the following disclaimer in the documentation
|
||||
// and/or other materials provided with the distribution.
|
||||
//
|
||||
// * The name of the copyright holders may not be used to endorse or promote products
|
||||
// derived from this software without specific prior written permission.
|
||||
//
|
||||
// This software is provided by the copyright holders and contributors "as is" and
|
||||
// any express or implied warranties, including, but not limited to, the implied
|
||||
// warranties of merchantability and fitness for a particular purpose are disclaimed.
|
||||
// In no event shall the Intel Corporation or contributors be liable for any direct,
|
||||
// indirect, incidental, special, exemplary, or consequential damages
|
||||
// (including, but not limited to, procurement of substitute goods or services;
|
||||
// loss of use, data, or profits; or business interruption) however caused
|
||||
// and on any theory of liability, whether in contract, strict liability,
|
||||
// or tort (including negligence or otherwise) arising in any way out of
|
||||
// the use of this software, even if advised of the possibility of such damage.
|
||||
//
|
||||
//M*/
|
||||
|
||||
#ifndef _GRFMT_PNG_H_
|
||||
#define _GRFMT_PNG_H_
|
||||
|
||||
#ifdef HAVE_PNG
|
||||
|
||||
#include "grfmt_base.hpp"
|
||||
#include "bitstrm.hpp"
|
||||
#include <png.h>
|
||||
#include <zlib.h>
|
||||
#include <vector>
|
||||
|
||||
namespace cv
|
||||
{
|
||||
|
||||
struct Chunk { std::vector<unsigned char> p; };
|
||||
struct OP { unsigned char* p; uint32_t size; int x, y, w, h, valid, filters; };
|
||||
|
||||
typedef struct {
|
||||
unsigned char r, g, b;
|
||||
} rgb;
|
||||
|
||||
class APNGFrame {
|
||||
public:
|
||||
|
||||
APNGFrame();
|
||||
|
||||
// Destructor
|
||||
~APNGFrame();
|
||||
|
||||
bool setMat(const cv::Mat& src, unsigned delayNum = 1, unsigned delayDen = 1000);
|
||||
|
||||
// Getters and Setters
|
||||
unsigned char* getPixels() const { return _pixels; }
|
||||
void setPixels(unsigned char* pixels);
|
||||
|
||||
unsigned int getWidth() const { return _width; }
|
||||
void setWidth(unsigned int width);
|
||||
|
||||
unsigned int getHeight() const { return _height; }
|
||||
void setHeight(unsigned int height);
|
||||
|
||||
unsigned char getColorType() const { return _colorType; }
|
||||
void setColorType(unsigned char colorType);
|
||||
|
||||
rgb* getPalette() { return _palette; }
|
||||
void setPalette(const rgb* palette);
|
||||
|
||||
unsigned char* getTransparency() { return _transparency; }
|
||||
void setTransparency(const unsigned char* transparency);
|
||||
|
||||
int getPaletteSize() const { return _paletteSize; }
|
||||
void setPaletteSize(int paletteSize);
|
||||
|
||||
int getTransparencySize() const { return _transparencySize; }
|
||||
void setTransparencySize(int transparencySize);
|
||||
|
||||
unsigned int getDelayNum() const { return _delayNum; }
|
||||
void setDelayNum(unsigned int delayNum);
|
||||
|
||||
unsigned int getDelayDen() const { return _delayDen; }
|
||||
void setDelayDen(unsigned int delayDen);
|
||||
|
||||
std::vector<png_bytep>& getRows() { return _rows; }
|
||||
|
||||
private:
|
||||
unsigned char* _pixels;
|
||||
unsigned int _width;
|
||||
unsigned int _height;
|
||||
unsigned char _colorType;
|
||||
rgb _palette[256];
|
||||
unsigned char _transparency[256];
|
||||
int _paletteSize;
|
||||
int _transparencySize;
|
||||
unsigned int _delayNum;
|
||||
unsigned int _delayDen;
|
||||
std::vector<png_bytep> _rows;
|
||||
};
|
||||
|
||||
class PngDecoder CV_FINAL : public BaseImageDecoder
|
||||
{
|
||||
public:
|
||||
PngDecoder();
|
||||
virtual ~PngDecoder();
|
||||
|
||||
bool readData( Mat& img ) CV_OVERRIDE;
|
||||
bool readHeader() CV_OVERRIDE;
|
||||
bool nextPage() CV_OVERRIDE;
|
||||
|
||||
ImageDecoder newDecoder() const CV_OVERRIDE;
|
||||
|
||||
private:
|
||||
static void readDataFromBuf(void* png_ptr, uchar* dst, size_t size);
|
||||
static void info_fn(png_structp png_ptr, png_infop info_ptr);
|
||||
static void row_fn(png_structp png_ptr, png_bytep new_row, png_uint_32 row_num, int pass);
|
||||
CV_NODISCARD_STD bool processing_start(void* frame_ptr, const Mat& img);
|
||||
CV_NODISCARD_STD bool processing_finish();
|
||||
void compose_frame(std::vector<png_bytep>& rows_dst, const std::vector<png_bytep>& rows_src, unsigned char bop, uint32_t x, uint32_t y, uint32_t w, uint32_t h, Mat& img);
|
||||
/**
|
||||
* @brief Reads data from an I/O source into the provided buffer.
|
||||
* @param buffer Pointer to the buffer where the data will be stored.
|
||||
* @param num_bytes Number of bytes to read into the buffer.
|
||||
* @return true if the operation is successful, false otherwise.
|
||||
*/
|
||||
CV_NODISCARD_STD bool readFromStreamOrBuffer(void* buffer, size_t num_bytes);
|
||||
uint32_t read_chunk(Chunk& chunk);
|
||||
CV_NODISCARD_STD bool InitPngPtr();
|
||||
void ClearPngPtr();
|
||||
|
||||
png_structp m_png_ptr = nullptr; // pointer to decompression structure
|
||||
png_infop m_info_ptr = nullptr; // pointer to image information structure
|
||||
int m_bit_depth;
|
||||
FILE* m_f;
|
||||
int m_color_type;
|
||||
Chunk m_chunkIHDR;
|
||||
int m_frame_no;
|
||||
size_t m_buf_pos;
|
||||
std::vector<Chunk> m_chunksInfo;
|
||||
APNGFrame frameRaw;
|
||||
APNGFrame frameNext;
|
||||
APNGFrame frameCur;
|
||||
Mat m_mat_raw;
|
||||
Mat m_mat_next;
|
||||
uint32_t w0;
|
||||
uint32_t h0;
|
||||
uint32_t x0;
|
||||
uint32_t y0;
|
||||
uint32_t delay_num;
|
||||
uint32_t delay_den;
|
||||
uint32_t dop;
|
||||
uint32_t bop;
|
||||
bool m_is_fcTL_loaded;
|
||||
bool m_is_IDAT_loaded;
|
||||
};
|
||||
|
||||
|
||||
class PngEncoder CV_FINAL : public BaseImageEncoder
|
||||
{
|
||||
public:
|
||||
PngEncoder();
|
||||
virtual ~PngEncoder();
|
||||
|
||||
bool isFormatSupported( int depth ) const CV_OVERRIDE;
|
||||
bool write( const Mat& img, const std::vector<int>& params ) CV_OVERRIDE;
|
||||
bool writeanimation(const Animation& animinfo, const std::vector<int>& params) CV_OVERRIDE;
|
||||
|
||||
ImageEncoder newEncoder() const CV_OVERRIDE;
|
||||
|
||||
protected:
|
||||
static void writeDataToBuf(void* png_ptr, unsigned char* src, size_t size);
|
||||
static void flushBuf(void* png_ptr);
|
||||
/**
|
||||
* @brief Writes data to an output destination, either a file stream or an in-memory buffer.
|
||||
*
|
||||
* This function handles two output scenarios:
|
||||
* 1. If a file stream is provided, the data is written to the stream using `fwrite`.
|
||||
* 2. If `stream` is null, the data is written to an in-memory buffer (`m_buf`), which is resized as needed.
|
||||
*
|
||||
* @param buffer Pointer to the data to be written.
|
||||
* @param num_bytes The number of bytes to be written.
|
||||
* @param stream Pointer to the file stream for writing. If null, the data is written to the in-memory buffer.
|
||||
* @return The number of bytes successfully written.
|
||||
* - For file-based writes, this is the number of bytes written to the stream.
|
||||
* - For buffer-based writes, this is the total number of bytes added to the buffer.
|
||||
*
|
||||
* @throws std::runtime_error If the in-memory buffer (`m_buf`) exceeds its maximum capacity.
|
||||
* @note If `num_bytes` is 0 or `buffer` is null, the function returns 0.
|
||||
*/
|
||||
size_t writeToStreamOrBuffer(void const* buffer, size_t num_bytes, FILE* stream);
|
||||
|
||||
private:
|
||||
void writeChunk(FILE* f, const char* name, unsigned char* data, uint32_t length);
|
||||
void writeIDATs(FILE* f, int frame, unsigned char* data, uint32_t length, uint32_t idat_size);
|
||||
void processRect(unsigned char* row, int rowbytes, int bpp, int stride, int h, unsigned char* rows);
|
||||
void deflateRectFin(unsigned char* zbuf, uint32_t* zsize, int bpp, int stride, unsigned char* rows, int zbuf_size, int n);
|
||||
void deflateRectOp(unsigned char* pdata, int x, int y, int w, int h, int bpp, int stride, int zbuf_size, int n);
|
||||
bool getRect(uint32_t w, uint32_t h, unsigned char* pimage1, unsigned char* pimage2, unsigned char* ptemp, uint32_t bpp, uint32_t stride, int zbuf_size, uint32_t has_tcolor, uint32_t tcolor, int n);
|
||||
|
||||
AutoBuffer<unsigned char> op_zbuf1;
|
||||
AutoBuffer<unsigned char> op_zbuf2;
|
||||
AutoBuffer<unsigned char> row_buf;
|
||||
AutoBuffer<unsigned char> sub_row;
|
||||
AutoBuffer<unsigned char> up_row;
|
||||
AutoBuffer<unsigned char> avg_row;
|
||||
AutoBuffer<unsigned char> paeth_row;
|
||||
z_stream op_zstream1;
|
||||
z_stream op_zstream2;
|
||||
OP op[6];
|
||||
rgb palette[256];
|
||||
unsigned char trns[256];
|
||||
uint32_t palsize, trnssize;
|
||||
uint32_t next_seq_num;
|
||||
int m_compression_level;
|
||||
int m_compression_strategy;
|
||||
int m_filter;
|
||||
bool m_isBilevel;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#endif/*_GRFMT_PNG_H_*/
|
||||
@@ -0,0 +1,630 @@
|
||||
/*M///////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
|
||||
//
|
||||
// By downloading, copying, installing or using the software you agree to this license.
|
||||
// If you do not agree to this license, do not download, install,
|
||||
// copy or use the software.
|
||||
//
|
||||
//
|
||||
// License Agreement
|
||||
// For Open Source Computer Vision Library
|
||||
//
|
||||
// Copyright (C) 2000-2008, Intel Corporation, all rights reserved.
|
||||
// Copyright (C) 2009, Willow Garage Inc., all rights reserved.
|
||||
// Third party copyrights are property of their respective owners.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without modification,
|
||||
// are permitted provided that the following conditions are met:
|
||||
//
|
||||
// * Redistribution's of source code must retain the above copyright notice,
|
||||
// this list of conditions and the following disclaimer.
|
||||
//
|
||||
// * Redistribution's in binary form must reproduce the above copyright notice,
|
||||
// this list of conditions and the following disclaimer in the documentation
|
||||
// and/or other materials provided with the distribution.
|
||||
//
|
||||
// * The name of the copyright holders may not be used to endorse or promote products
|
||||
// derived from this software without specific prior written permission.
|
||||
//
|
||||
// This software is provided by the copyright holders and contributors "as is" and
|
||||
// any express or implied warranties, including, but not limited to, the implied
|
||||
// warranties of merchantability and fitness for a particular purpose are disclaimed.
|
||||
// In no event shall the Intel Corporation or contributors be liable for any direct,
|
||||
// indirect, incidental, special, exemplary, or consequential damages
|
||||
// (including, but not limited to, procurement of substitute goods or services;
|
||||
// loss of use, data, or profits; or business interruption) however caused
|
||||
// and on any theory of liability, whether in contract, strict liability,
|
||||
// or tort (including negligence or otherwise) arising in any way out of
|
||||
// the use of this software, even if advised of the possibility of such damage.
|
||||
//
|
||||
//M*/
|
||||
|
||||
#include "precomp.hpp"
|
||||
#include "utils.hpp"
|
||||
#include "grfmt_pxm.hpp"
|
||||
#include <opencv2/core/utils/logger.hpp>
|
||||
|
||||
#ifdef HAVE_IMGCODEC_PXM
|
||||
|
||||
namespace cv
|
||||
{
|
||||
|
||||
///////////////////////// P?M reader //////////////////////////////
|
||||
|
||||
static int ReadNumber(RLByteStream& strm, int maxdigits = 0)
|
||||
{
|
||||
int code;
|
||||
int64 val = 0;
|
||||
int digits = 0;
|
||||
|
||||
code = strm.getByte();
|
||||
|
||||
while (!isdigit(code))
|
||||
{
|
||||
if (code == '#' )
|
||||
{
|
||||
do
|
||||
{
|
||||
code = strm.getByte();
|
||||
}
|
||||
while (code != '\n' && code != '\r');
|
||||
code = strm.getByte();
|
||||
}
|
||||
else if (isspace(code))
|
||||
{
|
||||
while (isspace(code))
|
||||
code = strm.getByte();
|
||||
}
|
||||
else
|
||||
{
|
||||
#if 1
|
||||
CV_Error_(Error::StsError, ("PXM: Unexpected code in ReadNumber(): 0x%x (%d)", code, code));
|
||||
#else
|
||||
code = strm.getByte();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
do
|
||||
{
|
||||
val = val*10 + (code - '0');
|
||||
CV_Assert(val <= INT_MAX && "PXM: ReadNumber(): result is too large");
|
||||
digits++;
|
||||
if (maxdigits != 0 && digits >= maxdigits) break;
|
||||
code = strm.getByte();
|
||||
}
|
||||
while (isdigit(code));
|
||||
|
||||
return (int)val;
|
||||
}
|
||||
|
||||
|
||||
PxMDecoder::PxMDecoder()
|
||||
{
|
||||
m_offset = -1;
|
||||
m_buf_supported = true;
|
||||
m_bpp = 0;
|
||||
m_binary = false;
|
||||
m_maxval = 0;
|
||||
}
|
||||
|
||||
|
||||
PxMDecoder::~PxMDecoder()
|
||||
{
|
||||
close();
|
||||
}
|
||||
|
||||
size_t PxMDecoder::signatureLength() const
|
||||
{
|
||||
return 3;
|
||||
}
|
||||
|
||||
bool PxMDecoder::checkSignature( const String& signature ) const
|
||||
{
|
||||
return signature.size() >= 3 && signature[0] == 'P' &&
|
||||
'1' <= signature[1] && signature[1] <= '6' &&
|
||||
isspace(signature[2]);
|
||||
}
|
||||
|
||||
ImageDecoder PxMDecoder::newDecoder() const
|
||||
{
|
||||
return makePtr<PxMDecoder>();
|
||||
}
|
||||
|
||||
void PxMDecoder::close()
|
||||
{
|
||||
m_strm.close();
|
||||
}
|
||||
|
||||
|
||||
bool PxMDecoder::readHeader()
|
||||
{
|
||||
bool result = false;
|
||||
|
||||
if( !m_buf.empty() )
|
||||
{
|
||||
if( !m_strm.open(m_buf) )
|
||||
return false;
|
||||
}
|
||||
else if( !m_strm.open( m_filename ))
|
||||
return false;
|
||||
|
||||
try
|
||||
{
|
||||
int code = m_strm.getByte();
|
||||
if( code != 'P' )
|
||||
throw RBS_BAD_HEADER;
|
||||
|
||||
code = m_strm.getByte();
|
||||
switch( code )
|
||||
{
|
||||
case '1': case '4': m_bpp = 1; break;
|
||||
case '2': case '5': m_bpp = 8; break;
|
||||
case '3': case '6': m_bpp = 24; break;
|
||||
default: throw RBS_BAD_HEADER;
|
||||
}
|
||||
|
||||
m_binary = code >= '4';
|
||||
m_type = m_bpp > 8 ? CV_8UC3 : CV_8UC1;
|
||||
|
||||
m_width = ReadNumber(m_strm);
|
||||
m_height = ReadNumber(m_strm);
|
||||
|
||||
m_maxval = m_bpp == 1 ? 1 : ReadNumber(m_strm);
|
||||
if( m_maxval > 65535 )
|
||||
throw RBS_BAD_HEADER;
|
||||
|
||||
//if( m_maxval > 255 ) m_binary = false; nonsense
|
||||
if( m_maxval > 255 )
|
||||
m_type = CV_MAKETYPE(CV_16U, CV_MAT_CN(m_type));
|
||||
|
||||
if( m_width > 0 && m_height > 0 && m_maxval > 0 && m_maxval < (1 << 16))
|
||||
{
|
||||
m_offset = m_strm.getPos();
|
||||
result = true;
|
||||
}
|
||||
}
|
||||
catch (const cv::Exception&)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
CV_LOG_ERROR(NULL, "PXM::readHeader(): unknown C++ exception");
|
||||
throw;
|
||||
}
|
||||
|
||||
if( !result )
|
||||
{
|
||||
m_offset = -1;
|
||||
m_width = m_height = -1;
|
||||
m_strm.close();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
bool PxMDecoder::readData( Mat& img )
|
||||
{
|
||||
bool color = img.channels() > 1;
|
||||
uchar* data = img.ptr();
|
||||
PaletteEntry palette[256];
|
||||
bool result = false;
|
||||
const int bit_depth = CV_ELEM_SIZE1(m_type)*8;
|
||||
const int src_pitch = divUp(m_width*m_bpp*(bit_depth/8), 8);
|
||||
int nch = CV_MAT_CN(m_type);
|
||||
int width3 = m_width*nch;
|
||||
|
||||
if( m_offset < 0 || !m_strm.isOpened())
|
||||
return false;
|
||||
|
||||
uchar gray_palette[256] = {0};
|
||||
|
||||
// create LUT for converting colors
|
||||
if( bit_depth == 8 )
|
||||
{
|
||||
CV_Assert(m_maxval < 256 && m_maxval > 0);
|
||||
|
||||
for (int i = 0; i <= m_maxval; i++)
|
||||
gray_palette[i] = (uchar)((i*255/m_maxval)^(m_bpp == 1 ? 255 : 0));
|
||||
|
||||
FillGrayPalette( palette, m_bpp==1 ? 1 : 8 , m_bpp == 1 );
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
m_strm.setPos( m_offset );
|
||||
|
||||
switch( m_bpp )
|
||||
{
|
||||
////////////////////////// 1 BPP /////////////////////////
|
||||
case 1:
|
||||
CV_Assert(CV_MAT_DEPTH(m_type) == CV_8U);
|
||||
if( !m_binary )
|
||||
{
|
||||
AutoBuffer<uchar> _src(m_width);
|
||||
uchar* src = _src.data();
|
||||
|
||||
for (int y = 0; y < m_height; y++, data += img.step)
|
||||
{
|
||||
for (int x = 0; x < m_width; x++)
|
||||
src[x] = ReadNumber(m_strm, 1) != 0;
|
||||
|
||||
if( color )
|
||||
FillColorRow8( data, src, m_width, palette );
|
||||
else
|
||||
FillGrayRow8( data, src, m_width, gray_palette );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
AutoBuffer<uchar> _src(src_pitch);
|
||||
uchar* src = _src.data();
|
||||
|
||||
for (int y = 0; y < m_height; y++, data += img.step)
|
||||
{
|
||||
m_strm.getBytes( src, src_pitch );
|
||||
|
||||
if( color )
|
||||
FillColorRow1( data, src, m_width, palette );
|
||||
else
|
||||
FillGrayRow1( data, src, m_width, gray_palette );
|
||||
}
|
||||
}
|
||||
result = true;
|
||||
break;
|
||||
|
||||
////////////////////////// 8 BPP /////////////////////////
|
||||
case 8:
|
||||
case 24:
|
||||
{
|
||||
AutoBuffer<uchar> _src(std::max<size_t>(width3*2, src_pitch));
|
||||
uchar* src = _src.data();
|
||||
|
||||
for (int y = 0; y < m_height; y++, data += img.step)
|
||||
{
|
||||
if( !m_binary )
|
||||
{
|
||||
for (int x = 0; x < width3; x++)
|
||||
{
|
||||
int code = ReadNumber(m_strm);
|
||||
if( (unsigned)code > (unsigned)m_maxval ) code = m_maxval;
|
||||
if( bit_depth == 8 )
|
||||
src[x] = gray_palette[code];
|
||||
else
|
||||
((ushort *)src)[x] = (ushort)code;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
m_strm.getBytes( src, src_pitch );
|
||||
if( bit_depth == 16 && !isBigEndian() )
|
||||
{
|
||||
for (int x = 0; x < width3; x++)
|
||||
{
|
||||
uchar v = src[x * 2];
|
||||
src[x * 2] = src[x * 2 + 1];
|
||||
src[x * 2 + 1] = v;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if( img.depth() == CV_8U && bit_depth == 16 )
|
||||
{
|
||||
for (int x = 0; x < width3; x++)
|
||||
{
|
||||
int v = ((ushort *)src)[x];
|
||||
src[x] = (uchar)(v >> 8);
|
||||
}
|
||||
}
|
||||
|
||||
if( m_bpp == 8 ) // image has one channel
|
||||
{
|
||||
if( color )
|
||||
{
|
||||
if( img.depth() == CV_8U ) {
|
||||
uchar *d = data, *s = src, *end = src + m_width;
|
||||
for( ; s < end; d += 3, s++)
|
||||
d[0] = d[1] = d[2] = *s;
|
||||
} else {
|
||||
ushort *d = (ushort *)data, *s = (ushort *)src, *end = ((ushort *)src) + m_width;
|
||||
for( ; s < end; s++, d += 3)
|
||||
d[0] = d[1] = d[2] = *s;
|
||||
}
|
||||
}
|
||||
else
|
||||
memcpy(data, src, img.elemSize1()*m_width);
|
||||
}
|
||||
else
|
||||
{
|
||||
if( color )
|
||||
{
|
||||
if (m_use_rgb)
|
||||
memcpy(data, src, m_width * CV_ELEM_SIZE(img.type()));
|
||||
else if( img.depth() == CV_8U )
|
||||
icvCvt_RGB2BGR_8u_C3R( src, 0, data, 0, Size(m_width,1) );
|
||||
else
|
||||
icvCvt_RGB2BGR_16u_C3R( (ushort *)src, 0, (ushort *)data, 0, Size(m_width,1) );
|
||||
}
|
||||
else if( img.depth() == CV_8U )
|
||||
icvCvt_BGR2Gray_8u_C3C1R( src, 0, data, 0, Size(m_width,1), 2 );
|
||||
else
|
||||
icvCvt_BGRA2Gray_16u_CnC1R( (ushort *)src, 0, (ushort *)data, 0, Size(m_width,1), 3, 2 );
|
||||
}
|
||||
}
|
||||
result = true;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
CV_Error(Error::StsError, "m_bpp is not supported");
|
||||
}
|
||||
}
|
||||
catch (const cv::Exception&)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
CV_LOG_ERROR(NULL, "PXM::readData(): unknown exception");
|
||||
throw;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
PxMEncoder::PxMEncoder(PxMMode mode) :
|
||||
mode_(mode)
|
||||
{
|
||||
switch (mode)
|
||||
{
|
||||
case PXM_TYPE_AUTO: m_description = "Portable image format - auto (*.pnm)"; break;
|
||||
case PXM_TYPE_PBM: m_description = "Portable image format - monochrome (*.pbm)"; break;
|
||||
case PXM_TYPE_PGM: m_description = "Portable image format - gray (*.pgm)"; break;
|
||||
case PXM_TYPE_PPM: m_description = "Portable image format - color (*.ppm)"; break;
|
||||
default:
|
||||
CV_Error(Error::StsInternal, "");
|
||||
}
|
||||
m_buf_supported = true;
|
||||
m_supported_encode_key = {IMWRITE_PXM_BINARY};
|
||||
}
|
||||
|
||||
PxMEncoder::~PxMEncoder()
|
||||
{
|
||||
}
|
||||
|
||||
bool PxMEncoder::isFormatSupported(int depth) const
|
||||
{
|
||||
if (mode_ == PXM_TYPE_PBM)
|
||||
return depth == CV_8U;
|
||||
return depth == CV_8U || depth == CV_16U;
|
||||
}
|
||||
|
||||
bool PxMEncoder::write(const Mat& img, const std::vector<int>& params)
|
||||
{
|
||||
bool isBinary = true;
|
||||
|
||||
int width = img.cols, height = img.rows;
|
||||
int _channels = img.channels(), depth = (int)img.elemSize1()*8;
|
||||
int channels = _channels > 1 ? 3 : 1;
|
||||
int fileStep = width*(int)img.elemSize();
|
||||
int x, y;
|
||||
|
||||
for( size_t i = 0; i < params.size(); i += 2 )
|
||||
{
|
||||
const int value = params[i+1];
|
||||
if( params[i] == IMWRITE_PXM_BINARY )
|
||||
{
|
||||
isBinary = value != 0;
|
||||
if((value != 0) && (value != 1)) {
|
||||
CV_LOG_WARNING(nullptr, cv::format("The value(%d) for IMWRITE_PXM_BINARY must be 0 or 1. It is fallbacked to 1", value));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int mode = mode_;
|
||||
if (mode == PXM_TYPE_AUTO)
|
||||
{
|
||||
mode = img.channels() == 1 ? PXM_TYPE_PGM : PXM_TYPE_PPM;
|
||||
}
|
||||
|
||||
if (mode == PXM_TYPE_PGM && img.channels() > 1)
|
||||
{
|
||||
CV_Error(Error::StsBadArg, "Portable bitmap(.pgm) expects gray image");
|
||||
}
|
||||
if (mode == PXM_TYPE_PPM && img.channels() != 3)
|
||||
{
|
||||
CV_Error(Error::StsBadArg, "Portable bitmap(.ppm) expects BGR image");
|
||||
}
|
||||
if (mode == PXM_TYPE_PBM && img.type() != CV_8UC1)
|
||||
{
|
||||
CV_Error(Error::StsBadArg, "For portable bitmap(.pbm) type must be CV_8UC1");
|
||||
}
|
||||
|
||||
WLByteStream strm;
|
||||
|
||||
if( m_buf )
|
||||
{
|
||||
if( !strm.open(*m_buf) )
|
||||
return false;
|
||||
int t = CV_MAKETYPE(img.depth(), channels);
|
||||
m_buf->reserve( alignSize(256 + (isBinary ? fileStep*height :
|
||||
((t == CV_8UC1 ? 4 : t == CV_8UC3 ? 4*3+2 :
|
||||
t == CV_16UC1 ? 6 : 6*3+2)*width+1)*height), 256));
|
||||
}
|
||||
else if( !strm.open(m_filename) )
|
||||
return false;
|
||||
|
||||
int lineLength;
|
||||
int bufferSize = 128; // buffer that should fit a header
|
||||
|
||||
if( isBinary )
|
||||
lineLength = width * (int)img.elemSize();
|
||||
else
|
||||
lineLength = (6 * channels + (channels > 1 ? 2 : 0)) * width + 32;
|
||||
|
||||
if( bufferSize < lineLength )
|
||||
bufferSize = lineLength;
|
||||
|
||||
AutoBuffer<char> _buffer(bufferSize);
|
||||
char* buffer = _buffer.data();
|
||||
|
||||
// write header;
|
||||
const int code = ((mode == PXM_TYPE_PBM) ? 1 : (mode == PXM_TYPE_PGM) ? 2 : 3)
|
||||
+ (isBinary ? 3 : 0);
|
||||
|
||||
int header_sz = snprintf(buffer, bufferSize, "P%c\n%d %d\n",
|
||||
(char)('0' + code), width, height);
|
||||
CV_Assert(header_sz > 0);
|
||||
if (mode != PXM_TYPE_PBM)
|
||||
{
|
||||
int sz = snprintf(&buffer[header_sz], bufferSize - header_sz, "%d\n", (1 << depth) - 1);
|
||||
CV_Assert(sz > 0);
|
||||
header_sz += sz;
|
||||
}
|
||||
|
||||
CHECK_WRITE(strm.putBytes(buffer, header_sz));
|
||||
|
||||
for( y = 0; y < height; y++ )
|
||||
{
|
||||
const uchar* const data = img.ptr(y);
|
||||
if( isBinary )
|
||||
{
|
||||
if (mode == PXM_TYPE_PBM)
|
||||
{
|
||||
char* ptr = buffer;
|
||||
int bcount = 7;
|
||||
char byte = 0;
|
||||
for (x = 0; x < width; x++)
|
||||
{
|
||||
if (bcount == 0)
|
||||
{
|
||||
if (data[x] == 0)
|
||||
byte = (byte) | 1;
|
||||
*ptr++ = byte;
|
||||
bcount = 7;
|
||||
byte = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (data[x] == 0)
|
||||
byte = (byte) | (1 << bcount);
|
||||
bcount--;
|
||||
}
|
||||
}
|
||||
if (bcount != 7)
|
||||
{
|
||||
*ptr++ = byte;
|
||||
}
|
||||
CHECK_WRITE(strm.putBytes(buffer, (int)(ptr - buffer)));
|
||||
continue;
|
||||
}
|
||||
|
||||
if( _channels == 3 )
|
||||
{
|
||||
if( depth == 8 )
|
||||
icvCvt_BGR2RGB_8u_C3R( (const uchar*)data, 0,
|
||||
(uchar*)buffer, 0, Size(width,1) );
|
||||
else
|
||||
icvCvt_BGR2RGB_16u_C3R( (const ushort*)data, 0,
|
||||
(ushort*)buffer, 0, Size(width,1) );
|
||||
}
|
||||
|
||||
// swap endianness if necessary
|
||||
if( depth == 16 && !isBigEndian() )
|
||||
{
|
||||
if( _channels == 1 )
|
||||
memcpy( buffer, data, fileStep );
|
||||
for( x = 0; x < width*channels*2; x += 2 )
|
||||
{
|
||||
uchar v = buffer[x];
|
||||
buffer[x] = buffer[x + 1];
|
||||
buffer[x + 1] = v;
|
||||
}
|
||||
}
|
||||
|
||||
CHECK_WRITE(strm.putBytes( (channels > 1 || depth > 8) ? buffer : (const char*)data, fileStep));
|
||||
}
|
||||
else
|
||||
{
|
||||
char* ptr = buffer;
|
||||
if (mode == PXM_TYPE_PBM)
|
||||
{
|
||||
CV_Assert(channels == 1);
|
||||
CV_Assert(depth == 8);
|
||||
for (x = 0; x < width; x++)
|
||||
{
|
||||
ptr[0] = data[x] ? '0' : '1';
|
||||
ptr += 1;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if( channels > 1 )
|
||||
{
|
||||
if( depth == 8 )
|
||||
{
|
||||
for( x = 0; x < width*channels; x += channels )
|
||||
{
|
||||
snprintf( ptr, bufferSize - (ptr - buffer), "% 4d", data[x + 2] );
|
||||
ptr += 4;
|
||||
snprintf( ptr, bufferSize - (ptr - buffer), "% 4d", data[x + 1] );
|
||||
ptr += 4;
|
||||
snprintf( ptr, bufferSize - (ptr - buffer), "% 4d", data[x] );
|
||||
ptr += 4;
|
||||
*ptr++ = ' ';
|
||||
*ptr++ = ' ';
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for( x = 0; x < width*channels; x += channels )
|
||||
{
|
||||
snprintf( ptr, bufferSize - (ptr - buffer), "% 6d", ((const ushort *)data)[x + 2] );
|
||||
ptr += 6;
|
||||
snprintf( ptr, bufferSize - (ptr - buffer), "% 6d", ((const ushort *)data)[x + 1] );
|
||||
ptr += 6;
|
||||
snprintf( ptr, bufferSize - (ptr - buffer), "% 6d", ((const ushort *)data)[x] );
|
||||
ptr += 6;
|
||||
*ptr++ = ' ';
|
||||
*ptr++ = ' ';
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if( depth == 8 )
|
||||
{
|
||||
for( x = 0; x < width; x++ )
|
||||
{
|
||||
snprintf( ptr, bufferSize - (ptr - buffer), "% 4d", data[x] );
|
||||
ptr += 4;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for( x = 0; x < width; x++ )
|
||||
{
|
||||
snprintf( ptr, bufferSize - (ptr - buffer), "% 6d", ((const ushort *)data)[x] );
|
||||
ptr += 6;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
*ptr++ = '\n';
|
||||
|
||||
CHECK_WRITE(strm.putBytes( buffer, (int)(ptr - buffer) ));
|
||||
}
|
||||
}
|
||||
|
||||
strm.close();
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif // HAVE_IMGCODEC_PXM
|
||||
@@ -0,0 +1,108 @@
|
||||
/*M///////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
|
||||
//
|
||||
// By downloading, copying, installing or using the software you agree to this license.
|
||||
// If you do not agree to this license, do not download, install,
|
||||
// copy or use the software.
|
||||
//
|
||||
//
|
||||
// License Agreement
|
||||
// For Open Source Computer Vision Library
|
||||
//
|
||||
// Copyright (C) 2000-2008, Intel Corporation, all rights reserved.
|
||||
// Copyright (C) 2009, Willow Garage Inc., all rights reserved.
|
||||
// Third party copyrights are property of their respective owners.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without modification,
|
||||
// are permitted provided that the following conditions are met:
|
||||
//
|
||||
// * Redistribution's of source code must retain the above copyright notice,
|
||||
// this list of conditions and the following disclaimer.
|
||||
//
|
||||
// * Redistribution's in binary form must reproduce the above copyright notice,
|
||||
// this list of conditions and the following disclaimer in the documentation
|
||||
// and/or other materials provided with the distribution.
|
||||
//
|
||||
// * The name of the copyright holders may not be used to endorse or promote products
|
||||
// derived from this software without specific prior written permission.
|
||||
//
|
||||
// This software is provided by the copyright holders and contributors "as is" and
|
||||
// any express or implied warranties, including, but not limited to, the implied
|
||||
// warranties of merchantability and fitness for a particular purpose are disclaimed.
|
||||
// In no event shall the Intel Corporation or contributors be liable for any direct,
|
||||
// indirect, incidental, special, exemplary, or consequential damages
|
||||
// (including, but not limited to, procurement of substitute goods or services;
|
||||
// loss of use, data, or profits; or business interruption) however caused
|
||||
// and on any theory of liability, whether in contract, strict liability,
|
||||
// or tort (including negligence or otherwise) arising in any way out of
|
||||
// the use of this software, even if advised of the possibility of such damage.
|
||||
//
|
||||
//M*/
|
||||
|
||||
#ifndef _GRFMT_PxM_H_
|
||||
#define _GRFMT_PxM_H_
|
||||
|
||||
#include "grfmt_base.hpp"
|
||||
#include "bitstrm.hpp"
|
||||
|
||||
#ifdef HAVE_IMGCODEC_PXM
|
||||
|
||||
namespace cv
|
||||
{
|
||||
|
||||
enum PxMMode
|
||||
{
|
||||
PXM_TYPE_AUTO = 0, // "auto"
|
||||
PXM_TYPE_PBM = 1, // monochrome format (single channel)
|
||||
PXM_TYPE_PGM = 2, // gray format (single channel)
|
||||
PXM_TYPE_PPM = 3 // color format
|
||||
};
|
||||
|
||||
class PxMDecoder CV_FINAL : public BaseImageDecoder
|
||||
{
|
||||
public:
|
||||
|
||||
PxMDecoder();
|
||||
virtual ~PxMDecoder() CV_OVERRIDE;
|
||||
|
||||
bool readData( Mat& img ) CV_OVERRIDE;
|
||||
bool readHeader() CV_OVERRIDE;
|
||||
void close();
|
||||
|
||||
size_t signatureLength() const CV_OVERRIDE;
|
||||
bool checkSignature( const String& signature ) const CV_OVERRIDE;
|
||||
ImageDecoder newDecoder() const CV_OVERRIDE;
|
||||
|
||||
protected:
|
||||
|
||||
RLByteStream m_strm;
|
||||
PaletteEntry m_palette[256];
|
||||
int m_bpp;
|
||||
int64_t m_offset;
|
||||
bool m_binary;
|
||||
int m_maxval;
|
||||
};
|
||||
|
||||
class PxMEncoder CV_FINAL : public BaseImageEncoder
|
||||
{
|
||||
public:
|
||||
PxMEncoder(PxMMode mode);
|
||||
virtual ~PxMEncoder() CV_OVERRIDE;
|
||||
|
||||
bool isFormatSupported( int depth ) const CV_OVERRIDE;
|
||||
bool write( const Mat& img, const std::vector<int>& params ) CV_OVERRIDE;
|
||||
|
||||
ImageEncoder newEncoder() const CV_OVERRIDE
|
||||
{
|
||||
return makePtr<PxMEncoder>(mode_);
|
||||
}
|
||||
|
||||
const PxMMode mode_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // HAVE_IMGCODEC_PXM
|
||||
|
||||
#endif/*_GRFMT_PxM_H_*/
|
||||
@@ -0,0 +1,828 @@
|
||||
// This file is part of OpenCV project.
|
||||
// It is subject to the license terms in the LICENSE file found in the top-level directory
|
||||
// of this distribution and at http://opencv.org/license.html.
|
||||
|
||||
#include "precomp.hpp"
|
||||
|
||||
#ifdef HAVE_SPNG
|
||||
|
||||
/****************************************************************************************\
|
||||
This part of the file implements PNG codec on base of libspng library,
|
||||
in particular, this code is based on example.c from libspng
|
||||
(see 3rdparty/libspng/LICENSE for copyright notice)
|
||||
\****************************************************************************************/
|
||||
|
||||
#ifndef _LFS64_LARGEFILE
|
||||
#define _LFS64_LARGEFILE 0
|
||||
#endif
|
||||
#ifndef _FILE_OFFSET_BITS
|
||||
#define _FILE_OFFSET_BITS 0
|
||||
#endif
|
||||
|
||||
#include <spng.h>
|
||||
#include <zlib.h>
|
||||
|
||||
#include "grfmt_spng.hpp"
|
||||
#include <opencv2/core/utils/logger.hpp>
|
||||
|
||||
/*
|
||||
* libspng does not support RGB -> Gray conversion. In order to decode colorful images as grayscale
|
||||
* we need conversion functions. In the previous png implementation(grfmt_png), the author was set
|
||||
* to particular values for rgb coefficients. OpenCV icvCvt_BGR2Gray function values does not match
|
||||
* with these values. (png_set_rgb_to_gray( png_ptr, 1, 0.299, 0.587 );) For this codec implementation,
|
||||
* slightly modified versions are implemented in the below of this page.
|
||||
*/
|
||||
void spngCvt_BGRA2Gray_8u_CnC1R(const uchar *bgr, int bgr_step,
|
||||
uchar *gray, int gray_step,
|
||||
cv::Size size, int ncn, int _swap_rb);
|
||||
|
||||
void spngCvt_BGRA2Gray_16u_CnC1R(const ushort *bgr, int bgr_step,
|
||||
ushort *gray, int gray_step,
|
||||
cv::Size size, int ncn, int _swap_rb);
|
||||
|
||||
void spngCvt_BGRA2Gray_16u28u_CnC1R(const ushort *bgr, int bgr_step,
|
||||
uchar *gray, int gray_step,
|
||||
cv::Size size, int ncn, int _swap_rb);
|
||||
|
||||
namespace cv
|
||||
{
|
||||
|
||||
/////////////////////// SPngDecoder ///////////////////
|
||||
|
||||
SPngDecoder::SPngDecoder()
|
||||
{
|
||||
m_signature = "\x89\x50\x4e\x47\xd\xa\x1a\xa";
|
||||
m_color_type = 0;
|
||||
m_ctx = 0;
|
||||
m_f = 0;
|
||||
m_buf_supported = true;
|
||||
m_buf_pos = 0;
|
||||
m_bit_depth = 0;
|
||||
}
|
||||
|
||||
SPngDecoder::~SPngDecoder()
|
||||
{
|
||||
close();
|
||||
}
|
||||
|
||||
ImageDecoder SPngDecoder::newDecoder() const
|
||||
{
|
||||
return makePtr<SPngDecoder>();
|
||||
}
|
||||
|
||||
void SPngDecoder::close()
|
||||
{
|
||||
if (m_f)
|
||||
{
|
||||
fclose(m_f);
|
||||
m_f = 0;
|
||||
}
|
||||
|
||||
if (m_ctx)
|
||||
{
|
||||
struct spng_ctx *ctx = (struct spng_ctx *)m_ctx;
|
||||
spng_ctx_free(ctx);
|
||||
m_ctx = 0;
|
||||
}
|
||||
}
|
||||
|
||||
int SPngDecoder::readDataFromBuf(void *sp_ctx, void *user, void *dst, size_t size)
|
||||
{
|
||||
/*
|
||||
* typedef int spng_read_fn(spng_ctx *ctx, void *user, void *dest, size_t length)
|
||||
* Type definition for callback passed to spng_set_png_stream() for decoders.
|
||||
* A read callback function should copy length bytes to dest and return 0 or SPNG_IO_EOF/SPNG_IO_ERROR on error.
|
||||
*/
|
||||
CV_UNUSED(sp_ctx);
|
||||
SPngDecoder *decoder = (SPngDecoder *)(user);
|
||||
CV_Assert(decoder);
|
||||
|
||||
const Mat &buf = decoder->m_buf;
|
||||
if (decoder->m_buf_pos + size > buf.cols * buf.rows * buf.elemSize())
|
||||
{
|
||||
return SPNG_IO_ERROR;
|
||||
}
|
||||
memcpy(dst, decoder->m_buf.ptr() + decoder->m_buf_pos, size);
|
||||
decoder->m_buf_pos += size;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool SPngDecoder::readHeader()
|
||||
{
|
||||
bool result = false;
|
||||
close();
|
||||
|
||||
spng_ctx *ctx = spng_ctx_new(SPNG_CTX_IGNORE_ADLER32);
|
||||
|
||||
if (!ctx)
|
||||
{
|
||||
spng_ctx_free(ctx);
|
||||
return false;
|
||||
}
|
||||
|
||||
m_ctx = ctx;
|
||||
|
||||
if (!m_buf.empty())
|
||||
spng_set_png_stream((struct spng_ctx *)m_ctx, (spng_rw_fn *)readDataFromBuf, this);
|
||||
else
|
||||
{
|
||||
m_f = fopen(m_filename.c_str(), "rb");
|
||||
if (m_f)
|
||||
{
|
||||
spng_set_png_file(ctx, m_f);
|
||||
}
|
||||
}
|
||||
|
||||
if (!m_buf.empty() || m_f)
|
||||
{
|
||||
struct spng_ihdr ihdr;
|
||||
|
||||
if (spng_get_ihdr(ctx, &ihdr) == SPNG_OK)
|
||||
{
|
||||
m_width = static_cast<int>(ihdr.width);
|
||||
m_height = static_cast<int>(ihdr.height);
|
||||
m_color_type = ihdr.color_type;
|
||||
m_bit_depth = ihdr.bit_depth;
|
||||
|
||||
int num_trans;
|
||||
switch (ihdr.color_type)
|
||||
{
|
||||
case SPNG_COLOR_TYPE_TRUECOLOR:
|
||||
case SPNG_COLOR_TYPE_INDEXED:
|
||||
struct spng_trns trns;
|
||||
num_trans = !spng_get_trns(ctx, &trns);
|
||||
if (num_trans > 0)
|
||||
m_type = CV_8UC4;
|
||||
else
|
||||
m_type = CV_8UC3;
|
||||
break;
|
||||
case SPNG_COLOR_TYPE_GRAYSCALE_ALPHA:
|
||||
case SPNG_COLOR_TYPE_TRUECOLOR_ALPHA:
|
||||
m_type = CV_8UC4;
|
||||
break;
|
||||
default:
|
||||
m_type = CV_8UC1;
|
||||
}
|
||||
if (ihdr.bit_depth == 16)
|
||||
m_type = CV_MAKETYPE(CV_16U, CV_MAT_CN(m_type));
|
||||
result = true;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool SPngDecoder::readData(Mat &img)
|
||||
{
|
||||
bool result = false;
|
||||
|
||||
if (m_ctx && m_width && m_height)
|
||||
{
|
||||
struct spng_ctx* png_ptr = (struct spng_ctx*)m_ctx;
|
||||
bool color = img.channels() > 1;
|
||||
int fmt = img.channels() == 4 ? m_bit_depth == 16 ? SPNG_FMT_RGBA16 : SPNG_FMT_RGBA8 : SPNG_FMT_PNG;
|
||||
int decode_flags = img.channels() == 4 ? SPNG_DECODE_TRNS : 0;
|
||||
|
||||
if (img.type() == CV_8UC3)
|
||||
{
|
||||
fmt = SPNG_FMT_RGB8;
|
||||
}
|
||||
else if (img.channels() == 1)
|
||||
{
|
||||
if (m_color_type == SPNG_COLOR_TYPE_GRAYSCALE && m_bit_depth <= 8)
|
||||
fmt = SPNG_FMT_G8;
|
||||
else
|
||||
fmt = img.depth() == CV_16U ? SPNG_FMT_RGBA16 : SPNG_FMT_RGB8;
|
||||
}
|
||||
|
||||
if (fmt == SPNG_FMT_PNG && m_bit_depth == 16 && m_color_type >= SPNG_COLOR_TYPE_GRAYSCALE_ALPHA)
|
||||
{
|
||||
Mat tmp(m_height, m_width, CV_16UC4);
|
||||
if (SPNG_OK != spng_decode_image(png_ptr, tmp.data, tmp.total() * tmp.elemSize(), SPNG_FMT_RGBA16, 0))
|
||||
return false;
|
||||
cvtColor(tmp, img, m_use_rgb ? COLOR_RGBA2RGB : COLOR_RGBA2BGR);
|
||||
return true;
|
||||
}
|
||||
|
||||
struct spng_ihdr ihdr;
|
||||
spng_get_ihdr(png_ptr, &ihdr);
|
||||
|
||||
size_t image_width, image_size = 0;
|
||||
int ret = spng_decoded_image_size(png_ptr, fmt, &image_size);
|
||||
|
||||
if (ret == SPNG_OK)
|
||||
{
|
||||
image_width = image_size / m_height;
|
||||
|
||||
if (!color && fmt == SPNG_FMT_RGB8 && m_bit_depth == 16 && (m_color_type == SPNG_COLOR_TYPE_TRUECOLOR || m_color_type == SPNG_COLOR_TYPE_TRUECOLOR_ALPHA))
|
||||
{
|
||||
Mat tmp(m_height, m_width, CV_16UC4);
|
||||
if (SPNG_OK != spng_decode_image(png_ptr, tmp.data, tmp.total() * tmp.elemSize(), SPNG_FMT_RGBA16, 0))
|
||||
return false;
|
||||
spngCvt_BGRA2Gray_16u28u_CnC1R(reinterpret_cast<const ushort*>(tmp.data), (int)tmp.step1(),
|
||||
img.data, (int)img.step1(), Size(m_width, m_height), 4, 2);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!color && ihdr.interlace_method && (fmt == SPNG_FMT_RGB8 || fmt == SPNG_FMT_RGBA16))
|
||||
{
|
||||
if (fmt == SPNG_FMT_RGBA16)
|
||||
{
|
||||
Mat tmp(m_height, m_width, CV_16UC4);
|
||||
if (SPNG_OK != spng_decode_image(png_ptr, tmp.data, tmp.total() * tmp.elemSize(), fmt, 0))
|
||||
return false;
|
||||
spngCvt_BGRA2Gray_16u_CnC1R(reinterpret_cast<const ushort*>(tmp.data), (int)tmp.step1(),
|
||||
reinterpret_cast<ushort*>(img.data), (int)img.step1(), Size(m_width, m_height), 4, 2);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
Mat tmp(m_height, m_width, CV_8UC3);
|
||||
if (SPNG_OK != spng_decode_image(png_ptr, tmp.data, image_size, fmt, 0))
|
||||
return false;
|
||||
spngCvt_BGRA2Gray_8u_CnC1R(tmp.data, (int)tmp.step1(), img.data, (int)img.step1(), Size(m_width, m_height), 3, 2);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (fmt == SPNG_FMT_PNG && img.elemSize() * m_width / 3 == image_width)
|
||||
{
|
||||
Mat tmp(m_height, m_width, CV_16U);
|
||||
if (SPNG_OK != spng_decode_image(png_ptr, tmp.data, image_size, SPNG_FMT_PNG, 0))
|
||||
return false;
|
||||
cvtColor(tmp, img, COLOR_GRAY2BGR);
|
||||
return true;
|
||||
}
|
||||
|
||||
ret = spng_decode_image(png_ptr, nullptr, 0, fmt, SPNG_DECODE_PROGRESSIVE | decode_flags);
|
||||
if (ret == SPNG_OK)
|
||||
{
|
||||
struct spng_row_info row_info{};
|
||||
|
||||
// If user wants to read image as grayscale(IMREAD_GRAYSCALE), but image format is not
|
||||
// decode image then convert to grayscale
|
||||
if (!color && (fmt == SPNG_FMT_RGB8 || fmt == SPNG_FMT_RGBA8 || fmt == SPNG_FMT_RGBA16))
|
||||
{
|
||||
AutoBuffer<unsigned char> buffer;
|
||||
buffer.allocate(image_width);
|
||||
if (fmt == SPNG_FMT_RGB8)
|
||||
{
|
||||
do
|
||||
{
|
||||
ret = spng_get_row_info(png_ptr, &row_info);
|
||||
if (ret)
|
||||
break;
|
||||
|
||||
ret = spng_decode_row(png_ptr, buffer.data(), image_width);
|
||||
spngCvt_BGRA2Gray_8u_CnC1R(buffer.data(), 0, img.data + row_info.row_num * img.step, 0, Size(m_width, 1), 3, 2);
|
||||
} while (ret == SPNG_OK);
|
||||
}
|
||||
else if (fmt == SPNG_FMT_RGBA8)
|
||||
{
|
||||
do
|
||||
{
|
||||
ret = spng_get_row_info(png_ptr, &row_info);
|
||||
if (ret)
|
||||
break;
|
||||
|
||||
ret = spng_decode_row(png_ptr, buffer.data(), image_width);
|
||||
spngCvt_BGRA2Gray_8u_CnC1R(buffer.data(), 0, img.data + row_info.row_num * img.step, 0, Size(m_width, 1), 4, 2);
|
||||
} while (ret == SPNG_OK);
|
||||
}
|
||||
else if (fmt == SPNG_FMT_RGBA16)
|
||||
{
|
||||
do
|
||||
{
|
||||
ret = spng_get_row_info(png_ptr, &row_info);
|
||||
if (ret)
|
||||
break;
|
||||
|
||||
ret = spng_decode_row(png_ptr, buffer.data(), image_width);
|
||||
spngCvt_BGRA2Gray_16u_CnC1R(
|
||||
reinterpret_cast<const ushort*>(buffer.data()), 0,
|
||||
reinterpret_cast<ushort*>(img.data + row_info.row_num * img.step),
|
||||
0, Size(m_width, 1), 4, 2);
|
||||
} while (ret == SPNG_OK);
|
||||
}
|
||||
}
|
||||
else if (color)
|
||||
{ // RGB -> BGR, convert row by row if png is non-interlaced, otherwise convert image as one
|
||||
int step = m_width * img.channels();
|
||||
AutoBuffer<uchar *> _buffer(m_height);
|
||||
uchar **buffer = _buffer.data();
|
||||
for (int y = 0; y < m_height; y++)
|
||||
{
|
||||
buffer[y] = img.data + y * img.step;
|
||||
}
|
||||
if (img.channels() == 4 && m_bit_depth == 16)
|
||||
{
|
||||
do
|
||||
{
|
||||
ret = spng_get_row_info(png_ptr, &row_info);
|
||||
if (ret)
|
||||
break;
|
||||
|
||||
ret = spng_decode_row(png_ptr, buffer[row_info.row_num], image_width);
|
||||
if (ihdr.interlace_method == 0 && !m_use_rgb)
|
||||
{
|
||||
icvCvt_RGBA2BGRA_16u_C4R(reinterpret_cast<const ushort*>(buffer[row_info.row_num]), 0,
|
||||
reinterpret_cast<ushort*>(buffer[row_info.row_num]), 0, Size(m_width, 1));
|
||||
}
|
||||
} while (ret == SPNG_OK);
|
||||
if (ihdr.interlace_method && !m_use_rgb)
|
||||
{
|
||||
icvCvt_RGBA2BGRA_16u_C4R(reinterpret_cast<const ushort *>(img.data), step * 2, reinterpret_cast<ushort *>(img.data), step * 2, Size(m_width, m_height));
|
||||
}
|
||||
}
|
||||
else if (img.channels() == 4)
|
||||
{
|
||||
do
|
||||
{
|
||||
ret = spng_get_row_info(png_ptr, &row_info);
|
||||
if (ret)
|
||||
break;
|
||||
|
||||
ret = spng_decode_row(png_ptr, buffer[row_info.row_num], image_width);
|
||||
if (ihdr.interlace_method == 0 && !m_use_rgb)
|
||||
{
|
||||
icvCvt_RGBA2BGRA_8u_C4R(buffer[row_info.row_num], 0, buffer[row_info.row_num], 0, Size(m_width, 1));
|
||||
}
|
||||
} while (ret == SPNG_OK);
|
||||
if (ihdr.interlace_method && !m_use_rgb)
|
||||
{
|
||||
icvCvt_RGBA2BGRA_8u_C4R(img.data, step, img.data, step, Size(m_width, m_height));
|
||||
}
|
||||
}
|
||||
else if (fmt == SPNG_FMT_PNG)
|
||||
{
|
||||
AutoBuffer<unsigned char> bufcn4;
|
||||
bufcn4.allocate(image_width);
|
||||
do
|
||||
{
|
||||
ret = spng_get_row_info(png_ptr, &row_info);
|
||||
if (ret)
|
||||
break;
|
||||
|
||||
ret = spng_decode_row(png_ptr, buffer[row_info.row_num], image_width);
|
||||
|
||||
if (ihdr.interlace_method == 0 && !m_use_rgb)
|
||||
{
|
||||
icvCvt_RGB2BGR_16u_C3R(reinterpret_cast<const ushort*>(buffer[row_info.row_num]), 0,
|
||||
reinterpret_cast<ushort*>(buffer[row_info.row_num]), 0, Size(m_width, 1));
|
||||
}
|
||||
} while (ret == SPNG_OK);
|
||||
if (ihdr.interlace_method && !m_use_rgb)
|
||||
{
|
||||
icvCvt_RGB2BGR_16u_C3R(reinterpret_cast<const ushort*>(img.data), step,
|
||||
reinterpret_cast<ushort*>(img.data), step, Size(m_width, m_height));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
do
|
||||
{
|
||||
ret = spng_get_row_info(png_ptr, &row_info);
|
||||
if (ret)
|
||||
break;
|
||||
|
||||
ret = spng_decode_row(png_ptr, buffer[row_info.row_num], image_width);
|
||||
if (ihdr.interlace_method == 0 && !m_use_rgb)
|
||||
{
|
||||
icvCvt_RGB2BGR_8u_C3R(buffer[row_info.row_num], 0, buffer[row_info.row_num], 0, Size(m_width, 1));
|
||||
}
|
||||
} while (ret == SPNG_OK);
|
||||
if (ihdr.interlace_method && !m_use_rgb)
|
||||
{
|
||||
icvCvt_RGB2BGR_8u_C3R(img.data, step, img.data, step, Size(m_width, m_height));
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
do
|
||||
{
|
||||
ret = spng_get_row_info(png_ptr, &row_info);
|
||||
if (ret)
|
||||
break;
|
||||
|
||||
ret = spng_decode_row(png_ptr, img.data + row_info.row_num * image_width, image_width);
|
||||
|
||||
} while (ret == SPNG_OK);
|
||||
}
|
||||
|
||||
if (ret == SPNG_EOI)
|
||||
{
|
||||
ret = spng_decode_chunks(png_ptr);
|
||||
if(ret == SPNG_OK) result = true;
|
||||
struct spng_exif exif_s{};
|
||||
ret = spng_get_exif(png_ptr, &exif_s);
|
||||
if (ret == SPNG_OK)
|
||||
{
|
||||
if (exif_s.data && exif_s.length > 0)
|
||||
{
|
||||
result = m_exif.parseExif((unsigned char *)exif_s.data, exif_s.length);
|
||||
}
|
||||
}
|
||||
|
||||
if (m_read_options)
|
||||
{
|
||||
uint32_t text_count;
|
||||
|
||||
// Retrieve all text chunks
|
||||
if (spng_get_text(png_ptr, NULL, &text_count) == SPNG_OK)
|
||||
{
|
||||
std::vector<spng_text> texts(text_count);
|
||||
spng_get_text(png_ptr, texts.data(), &text_count);
|
||||
|
||||
for (size_t i = 0; i < text_count; ++i)
|
||||
{
|
||||
char* key = texts[i].keyword;
|
||||
char* value = texts[i].text;
|
||||
size_t len = texts[i].length;
|
||||
|
||||
if (key && (!std::strcmp(key, "Raw profile type exif") || !std::strcmp(key, "Raw profile type APP1")))
|
||||
{
|
||||
m_exif.processRawProfile(value, len);
|
||||
}
|
||||
else if (key && !std::strcmp(key, "XML:com.adobe.xmp"))
|
||||
{
|
||||
auto& out = m_metadata[IMAGE_METADATA_XMP];
|
||||
out.insert(out.end(),
|
||||
value,
|
||||
value + len + 1); // include null terminator
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ICC Profile
|
||||
spng_iccp iccp_data;
|
||||
|
||||
if (spng_get_iccp(png_ptr, &iccp_data) == SPNG_OK && iccp_data.profile_len > 0)
|
||||
{
|
||||
auto& out = m_metadata[IMAGE_METADATA_ICCP];
|
||||
out.insert(out.end(),
|
||||
iccp_data.profile,
|
||||
iccp_data.profile + iccp_data.profile_len);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/////////////////////// SPngEncoder ///////////////////
|
||||
|
||||
SPngEncoder::SPngEncoder()
|
||||
{
|
||||
m_description = "Portable Network Graphics files (*.png)";
|
||||
m_buf_supported = true;
|
||||
m_support_metadata.assign((size_t)IMAGE_METADATA_MAX + 1, false);
|
||||
m_support_metadata[IMAGE_METADATA_EXIF] = true;
|
||||
m_support_metadata[IMAGE_METADATA_XMP] = true;
|
||||
m_support_metadata[IMAGE_METADATA_ICCP] = true;
|
||||
m_supported_encode_key = {IMWRITE_PNG_COMPRESSION, IMWRITE_PNG_STRATEGY, IMWRITE_PNG_BILEVEL, IMWRITE_PNG_FILTER, IMWRITE_PNG_ZLIBBUFFER_SIZE};
|
||||
}
|
||||
|
||||
SPngEncoder::~SPngEncoder()
|
||||
{
|
||||
}
|
||||
|
||||
bool SPngEncoder::isFormatSupported(int depth) const
|
||||
{
|
||||
return depth == CV_8U || depth == CV_16U;
|
||||
}
|
||||
|
||||
ImageEncoder SPngEncoder::newEncoder() const
|
||||
{
|
||||
return makePtr<SPngEncoder>();
|
||||
}
|
||||
|
||||
int SPngEncoder::writeDataToBuf(void *ctx, void *user, void *dst_src, size_t length)
|
||||
{
|
||||
CV_UNUSED(ctx);
|
||||
SPngEncoder *encoder = (SPngEncoder *)(user);
|
||||
CV_Assert(encoder && encoder->m_buf);
|
||||
size_t cursz = encoder->m_buf->size();
|
||||
encoder->m_buf->resize(cursz + length);
|
||||
memcpy(&(*encoder->m_buf)[cursz], dst_src, length);
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool SPngEncoder::write(const Mat &img, const std::vector<int> ¶ms)
|
||||
{
|
||||
spng_ctx *ctx = spng_ctx_new(SPNG_CTX_ENCODER);
|
||||
FILE *volatile f = 0;
|
||||
int width = img.cols, height = img.rows;
|
||||
int depth = img.depth(), channels = img.channels();
|
||||
volatile bool result = false;
|
||||
|
||||
if (depth != CV_8U && depth != CV_16U)
|
||||
return false;
|
||||
|
||||
if (ctx)
|
||||
{
|
||||
struct spng_ihdr ihdr = {};
|
||||
ihdr.height = height;
|
||||
ihdr.width = width;
|
||||
int compression_level = Z_BEST_SPEED;
|
||||
int compression_strategy = IMWRITE_PNG_STRATEGY_RLE; // Default strategy
|
||||
int filter = IMWRITE_PNG_FILTER_SUB; // Default filter
|
||||
bool isBilevel = false;
|
||||
bool set_compression_level = false;
|
||||
bool set_filter = false;
|
||||
|
||||
for (size_t i = 0; i < params.size(); i += 2)
|
||||
{
|
||||
const int value = params[i+1];
|
||||
if (params[i] == IMWRITE_PNG_COMPRESSION)
|
||||
{
|
||||
compression_strategy = IMWRITE_PNG_STRATEGY_DEFAULT; // Default strategy
|
||||
compression_level = MIN(MAX(value, 0), Z_BEST_COMPRESSION);
|
||||
if(value != compression_level) {
|
||||
CV_LOG_WARNING(nullptr, cv::format("The value(%d) for IMWRITE_PNG_COMPRESSION must be between 0 to 9. It is fallbacked to %d", value, compression_level));
|
||||
}
|
||||
set_compression_level = true;
|
||||
}
|
||||
if (params[i] == IMWRITE_PNG_STRATEGY)
|
||||
{
|
||||
switch(value) {
|
||||
case IMWRITE_PNG_STRATEGY_DEFAULT:
|
||||
case IMWRITE_PNG_STRATEGY_FILTERED:
|
||||
case IMWRITE_PNG_STRATEGY_HUFFMAN_ONLY:
|
||||
case IMWRITE_PNG_STRATEGY_RLE:
|
||||
case IMWRITE_PNG_STRATEGY_FIXED:
|
||||
compression_strategy = value;
|
||||
break;
|
||||
default:
|
||||
compression_strategy = IMWRITE_PNG_STRATEGY_RLE;
|
||||
CV_LOG_WARNING(nullptr, cv::format("The value(%d) for IMWRITE_PNG_STRATEGY must be one of ImwritePNGFlags. It is fallbacked to IMWRITE_PNG_STRATEGY_RLE", value));
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (params[i] == IMWRITE_PNG_BILEVEL)
|
||||
{
|
||||
isBilevel = value != 0;
|
||||
if((value != 0) && (value != 1)) {
|
||||
CV_LOG_WARNING(nullptr, cv::format("The value(%d) for IMWRITE_PNG_BILEVEL must be 0 or 1. It is fallbacked to 1", value ));
|
||||
}
|
||||
}
|
||||
if( params[i] == IMWRITE_PNG_FILTER )
|
||||
{
|
||||
switch(value) {
|
||||
case IMWRITE_PNG_FILTER_NONE:
|
||||
case IMWRITE_PNG_FILTER_SUB:
|
||||
case IMWRITE_PNG_FILTER_UP:
|
||||
case IMWRITE_PNG_FILTER_AVG:
|
||||
case IMWRITE_PNG_FILTER_PAETH:
|
||||
case IMWRITE_PNG_FAST_FILTERS:
|
||||
case IMWRITE_PNG_ALL_FILTERS:
|
||||
filter = value;
|
||||
break;
|
||||
default:
|
||||
filter = IMWRITE_PNG_FILTER_SUB;
|
||||
CV_LOG_WARNING(nullptr, cv::format("The value(%d) for IMWRITE_PNG_FILTER must be one of ImwritePNGFilterFlags. It is fallbacked to IMWRITE_PNG_FILTER_SUB", value ));
|
||||
break;
|
||||
}
|
||||
set_filter = true;
|
||||
}
|
||||
if( params[i] == IMWRITE_PNG_ZLIBBUFFER_SIZE )
|
||||
{
|
||||
// See https://libspng.org/docs/migrate-libpng/#miscellaneous-functions
|
||||
CV_LOG_WARNING(nullptr, "libspng does not support png_set_compression_buffer_size() which is required for IMWRITE_PNG_ZLIBBUFFER_SIZE");
|
||||
}
|
||||
}
|
||||
|
||||
ihdr.bit_depth = depth == CV_8U ? isBilevel ? 1 : 8 : 16;
|
||||
ihdr.color_type = (uint8_t)(channels == 1 ? SPNG_COLOR_TYPE_GRAYSCALE : channels == 3 ? SPNG_COLOR_TYPE_TRUECOLOR
|
||||
: SPNG_COLOR_TYPE_TRUECOLOR_ALPHA);
|
||||
ihdr.interlace_method = SPNG_INTERLACE_NONE;
|
||||
ihdr.filter_method = SPNG_FILTER_NONE;
|
||||
ihdr.compression_method = 0;
|
||||
spng_set_ihdr(ctx, &ihdr);
|
||||
|
||||
if (m_buf)
|
||||
{
|
||||
spng_set_png_stream(ctx, (spng_rw_fn *)writeDataToBuf, this);
|
||||
}
|
||||
else
|
||||
{
|
||||
f = fopen(m_filename.c_str(), "wb");
|
||||
if (f)
|
||||
spng_set_png_file(ctx, f);
|
||||
}
|
||||
|
||||
if (m_buf || f)
|
||||
{
|
||||
if (!set_compression_level || set_filter)
|
||||
spng_set_option(ctx, SPNG_FILTER_CHOICE, filter);
|
||||
spng_set_option(ctx, SPNG_IMG_COMPRESSION_LEVEL, compression_level);
|
||||
spng_set_option(ctx, SPNG_IMG_COMPRESSION_STRATEGY, compression_strategy);
|
||||
|
||||
if (!m_metadata.empty()) {
|
||||
std::vector<uchar>& exif = m_metadata[IMAGE_METADATA_EXIF];
|
||||
if (!exif.empty()) {
|
||||
spng_exif s_exif;
|
||||
s_exif.data = reinterpret_cast<char*>(exif.data());
|
||||
s_exif.length = exif.size();
|
||||
spng_set_exif(ctx, &s_exif);
|
||||
}
|
||||
|
||||
std::vector<uchar>& xmp = m_metadata[IMAGE_METADATA_XMP];
|
||||
if (!xmp.empty()) {
|
||||
spng_text text_chunk;
|
||||
strncpy(text_chunk.keyword, "XML:com.adobe.xmp", sizeof(text_chunk.keyword) - 1);
|
||||
text_chunk.keyword[sizeof(text_chunk.keyword) - 1] = '\0';
|
||||
text_chunk.type = SPNG_TEXT;
|
||||
text_chunk.text = reinterpret_cast<char*>(xmp.data());
|
||||
text_chunk.length = xmp.size();
|
||||
spng_set_text(ctx, &text_chunk, 1);
|
||||
}
|
||||
|
||||
std::vector<uchar>& iccp = m_metadata[IMAGE_METADATA_ICCP];
|
||||
if (!iccp.empty()) {
|
||||
spng_iccp s_iccp;
|
||||
strncpy(s_iccp.profile_name, "ICC Profile", sizeof(s_iccp.profile_name) - 1);
|
||||
s_iccp.profile_name[sizeof(s_iccp.profile_name) - 1] = '\0';
|
||||
s_iccp.profile_len = iccp.size();
|
||||
s_iccp.profile = reinterpret_cast<char*>(iccp.data());
|
||||
spng_set_iccp(ctx, &s_iccp);
|
||||
}
|
||||
}
|
||||
|
||||
int ret;
|
||||
spng_encode_chunks(ctx);
|
||||
ret = spng_encode_image(ctx, nullptr, 0, SPNG_FMT_PNG, SPNG_ENCODE_PROGRESSIVE);
|
||||
if (channels > 1)
|
||||
{
|
||||
int error = SPNG_OK;
|
||||
if (ret == SPNG_OK)
|
||||
{
|
||||
if (depth == CV_16U)
|
||||
{
|
||||
AutoBuffer<ushort *> buff2;
|
||||
buff2.allocate(height);
|
||||
for (int y = 0; y < height; y++)
|
||||
buff2[y] = reinterpret_cast<unsigned short *>(img.data + y * img.step);
|
||||
|
||||
AutoBuffer<ushort> _buffer;
|
||||
_buffer.allocate(width * channels * 2);
|
||||
for (int y = 0; y < height; y++)
|
||||
{
|
||||
if (channels == 3)
|
||||
{
|
||||
icvCvt_BGR2RGB_16u_C3R(buff2[y], 0,
|
||||
_buffer.data(), 0, Size(width, 1));
|
||||
}
|
||||
else if (channels == 4)
|
||||
{
|
||||
icvCvt_BGRA2RGBA_16u_C4R(buff2[y], 0,
|
||||
_buffer.data(), 0, Size(width, 1));
|
||||
}
|
||||
error = spng_encode_row(ctx, _buffer.data(), width * channels * 2);
|
||||
if (error)
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
AutoBuffer<uchar *> buff;
|
||||
buff.allocate(height);
|
||||
for (int y = 0; y < height; y++)
|
||||
buff[y] = img.data + y * img.step;
|
||||
|
||||
AutoBuffer<uchar> _buffer;
|
||||
_buffer.allocate(width * channels);
|
||||
for (int y = 0; y < height; y++)
|
||||
{
|
||||
if (channels == 3)
|
||||
{
|
||||
icvCvt_BGR2RGB_8u_C3R(buff[y], 0, _buffer.data(), 0, Size(width, 1));
|
||||
}
|
||||
else if (channels == 4)
|
||||
{
|
||||
icvCvt_BGRA2RGBA_8u_C4R(buff[y], 0, _buffer.data(), 0, Size(width, 1));
|
||||
}
|
||||
error = spng_encode_row(ctx, _buffer.data(), width * channels);
|
||||
if (error)
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (error == SPNG_EOI)
|
||||
{ // success
|
||||
spng_encode_chunks(ctx);
|
||||
ret = SPNG_OK;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
int error = SPNG_OK;
|
||||
for (int y = 0; y < height; y++)
|
||||
{
|
||||
error = spng_encode_row(ctx, img.data + y * img.step, width * channels * (depth == CV_16U ? 2 : 1));
|
||||
if (error)
|
||||
break;
|
||||
}
|
||||
if (error == SPNG_EOI)
|
||||
{ // success
|
||||
spng_encode_chunks(ctx);
|
||||
ret = SPNG_OK;
|
||||
}
|
||||
}
|
||||
if (ret == SPNG_OK)
|
||||
result = true;
|
||||
}
|
||||
}
|
||||
|
||||
spng_ctx_free(ctx);
|
||||
if (f)
|
||||
fclose((FILE *)f);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void spngCvt_BGRA2Gray_8u_CnC1R(const uchar *bgr, int bgr_step,
|
||||
uchar *gray, int gray_step,
|
||||
cv::Size size, int ncn, int _swap_rb)
|
||||
{
|
||||
int i;
|
||||
for (; size.height--; gray += gray_step)
|
||||
{
|
||||
int cBGR0 = 3737;
|
||||
int cBGR1 = 19234;
|
||||
int cBGR2 = 9797;
|
||||
|
||||
if (_swap_rb)
|
||||
std::swap(cBGR0, cBGR2);
|
||||
for (i = 0; i < size.width; i++, bgr += ncn)
|
||||
{
|
||||
if (bgr[0] != bgr[1] || bgr[0] != bgr[2])
|
||||
{
|
||||
gray[i] = (uchar)((cBGR0 * bgr[0] + cBGR1 * bgr[1] + cBGR2 * bgr[2]) >> 15);
|
||||
}
|
||||
else
|
||||
{
|
||||
gray[i] = bgr[0];
|
||||
}
|
||||
}
|
||||
|
||||
bgr += bgr_step - size.width * ncn;
|
||||
}
|
||||
}
|
||||
|
||||
void spngCvt_BGRA2Gray_16u_CnC1R(const ushort *bgr, int bgr_step,
|
||||
ushort *gray, int gray_step,
|
||||
cv::Size size, int ncn, int _swap_rb)
|
||||
{
|
||||
for (; size.height--; gray += gray_step)
|
||||
{
|
||||
int cBGR0 = 3737;
|
||||
int cBGR1 = 19234;
|
||||
int cBGR2 = 9797;
|
||||
|
||||
if (_swap_rb)
|
||||
std::swap(cBGR0, cBGR2);
|
||||
for (int i = 0; i < size.width; i++, bgr += ncn)
|
||||
{
|
||||
if (bgr[0] != bgr[1] || bgr[0] != bgr[2])
|
||||
{
|
||||
gray[i] = (ushort)((cBGR0 * bgr[0] + cBGR1 * bgr[1] + cBGR2 * bgr[2] + 16384) >> 15);
|
||||
}
|
||||
else
|
||||
{
|
||||
gray[i] = bgr[0];
|
||||
}
|
||||
}
|
||||
|
||||
bgr += bgr_step - size.width * ncn;
|
||||
}
|
||||
}
|
||||
|
||||
void spngCvt_BGRA2Gray_16u28u_CnC1R(const ushort *bgr, int bgr_step,
|
||||
uchar *gray, int gray_step,
|
||||
cv::Size size, int ncn, int _swap_rb)
|
||||
{
|
||||
int cBGR0 = 3737;
|
||||
int cBGR1 = 19234;
|
||||
int cBGR2 = 9797;
|
||||
if (_swap_rb)
|
||||
std::swap(cBGR0, cBGR2);
|
||||
|
||||
for (; size.height--; gray += gray_step)
|
||||
{
|
||||
for (int i = 0; i < size.width; i++, bgr += ncn)
|
||||
{
|
||||
gray[i] = static_cast<uchar>(((cBGR0 * bgr[0] + cBGR1 * bgr[1] + cBGR2 * bgr[2] + 16384) >> 15) >> 8);
|
||||
}
|
||||
|
||||
bgr += bgr_step - size.width * ncn;
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
/* End of file. */
|
||||
@@ -0,0 +1,60 @@
|
||||
// This file is part of OpenCV project.
|
||||
// It is subject to the license terms in the LICENSE file found in the top-level directory
|
||||
// of this distribution and at http://opencv.org/license.html.
|
||||
|
||||
#ifndef _GRFMT_SPNG_H_
|
||||
#define _GRFMT_SPNG_H_
|
||||
|
||||
#ifdef HAVE_SPNG
|
||||
|
||||
#include "grfmt_base.hpp"
|
||||
#include "bitstrm.hpp"
|
||||
|
||||
namespace cv
|
||||
{
|
||||
|
||||
class SPngDecoder CV_FINAL : public BaseImageDecoder
|
||||
{
|
||||
public:
|
||||
|
||||
SPngDecoder();
|
||||
virtual ~SPngDecoder();
|
||||
|
||||
bool readData( Mat& img ) CV_OVERRIDE;
|
||||
bool readHeader() CV_OVERRIDE;
|
||||
void close();
|
||||
|
||||
ImageDecoder newDecoder() const CV_OVERRIDE;
|
||||
|
||||
protected:
|
||||
|
||||
static int readDataFromBuf(void* sp_ctx, void *user, void* dst, size_t size);
|
||||
|
||||
int m_bit_depth;
|
||||
void* m_ctx;
|
||||
FILE* m_f;
|
||||
int m_color_type;
|
||||
size_t m_buf_pos;
|
||||
};
|
||||
|
||||
|
||||
class SPngEncoder CV_FINAL : public BaseImageEncoder
|
||||
{
|
||||
public:
|
||||
SPngEncoder();
|
||||
virtual ~SPngEncoder();
|
||||
|
||||
bool isFormatSupported( int depth ) const CV_OVERRIDE;
|
||||
bool write( const Mat& img, const std::vector<int>& params ) CV_OVERRIDE;
|
||||
|
||||
ImageEncoder newEncoder() const CV_OVERRIDE;
|
||||
|
||||
protected:
|
||||
static int writeDataToBuf(void *ctx, void *user, void *dst_src, size_t length);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#endif/*_GRFMT_PNG_H_*/
|
||||
@@ -0,0 +1,428 @@
|
||||
// This file is part of OpenCV project.
|
||||
// It is subject to the license terms in the LICENSE file found in the top-level
|
||||
// directory of this distribution and at http://opencv.org/license.html
|
||||
|
||||
#include "precomp.hpp"
|
||||
#include "grfmt_sunras.hpp"
|
||||
|
||||
#ifdef HAVE_IMGCODEC_SUNRASTER
|
||||
|
||||
namespace cv
|
||||
{
|
||||
|
||||
static const char* fmtSignSunRas = "\x59\xA6\x6A\x95";
|
||||
|
||||
/************************ Sun Raster reader *****************************/
|
||||
|
||||
SunRasterDecoder::SunRasterDecoder()
|
||||
{
|
||||
m_offset = -1;
|
||||
m_signature = fmtSignSunRas;
|
||||
m_bpp = 0;
|
||||
m_encoding = RAS_STANDARD;
|
||||
m_maptype = RMT_NONE;
|
||||
m_maplength = 0;
|
||||
m_buf_supported = true;
|
||||
}
|
||||
|
||||
|
||||
SunRasterDecoder::~SunRasterDecoder()
|
||||
{
|
||||
}
|
||||
|
||||
ImageDecoder SunRasterDecoder::newDecoder() const
|
||||
{
|
||||
return makePtr<SunRasterDecoder>();
|
||||
}
|
||||
|
||||
void SunRasterDecoder::close()
|
||||
{
|
||||
m_strm.close();
|
||||
}
|
||||
|
||||
|
||||
bool SunRasterDecoder::readHeader()
|
||||
{
|
||||
bool result = false;
|
||||
|
||||
if (!m_buf.empty())
|
||||
m_strm.open(m_buf);
|
||||
else
|
||||
m_strm.open(m_filename);
|
||||
|
||||
if( !m_strm.isOpened()) return false;
|
||||
|
||||
try
|
||||
{
|
||||
m_strm.skip( 4 );
|
||||
m_width = m_strm.getDWord();
|
||||
m_height = m_strm.getDWord();
|
||||
m_bpp = m_strm.getDWord();
|
||||
int palSize = (m_bpp > 0 && m_bpp <= 8) ? (3*(1 << m_bpp)) : 0;
|
||||
|
||||
m_strm.skip( 4 );
|
||||
// Read as plain integers first; validate before casting to enum types.
|
||||
// Casting an out-of-range integer directly to an enum with no fixed
|
||||
// underlying type is undefined behavior (C++11 §7.2/8). Reject invalid
|
||||
// values early so no downstream code ever touches an ill-formed enum.
|
||||
const int raw_encoding = (int)m_strm.getDWord();
|
||||
const int raw_maptype = (int)m_strm.getDWord();
|
||||
m_maplength = m_strm.getDWord();
|
||||
|
||||
if (raw_encoding < RAS_OLD || raw_encoding > RAS_FORMAT_RGB)
|
||||
return false;
|
||||
if (raw_maptype < RMT_NONE || raw_maptype > RMT_EQUAL_RGB)
|
||||
return false;
|
||||
|
||||
m_encoding = (SunRasType)raw_encoding;
|
||||
m_maptype = (SunRasMapType)raw_maptype;
|
||||
|
||||
if( m_width > 0 && m_height > 0 &&
|
||||
(m_bpp == 1 || m_bpp == 8 || m_bpp == 24 || m_bpp == 32) &&
|
||||
(m_encoding == RAS_OLD || m_encoding == RAS_STANDARD ||
|
||||
(m_type == RAS_BYTE_ENCODED && m_bpp == 8) || m_type == RAS_FORMAT_RGB) &&
|
||||
((m_maptype == RMT_NONE && m_maplength == 0) ||
|
||||
(m_maptype == RMT_EQUAL_RGB && m_maplength <= palSize && m_maplength > 0 && m_bpp <= 8)))
|
||||
{
|
||||
memset( m_palette, 0, sizeof(m_palette));
|
||||
|
||||
if( m_maplength != 0 )
|
||||
{
|
||||
uchar buffer[256*3];
|
||||
|
||||
if( m_strm.getBytes( buffer, m_maplength ) == m_maplength )
|
||||
{
|
||||
int i;
|
||||
palSize = m_maplength/3;
|
||||
|
||||
for( i = 0; i < palSize; i++ )
|
||||
{
|
||||
m_palette[i].b = buffer[i + 2*palSize];
|
||||
m_palette[i].g = buffer[i + palSize];
|
||||
m_palette[i].r = buffer[i];
|
||||
m_palette[i].a = 0;
|
||||
}
|
||||
|
||||
m_type = IsColorPalette( m_palette, m_bpp ) ? CV_8UC3 : CV_8UC1;
|
||||
m_offset = m_strm.getPos();
|
||||
|
||||
CV_Assert(m_offset == static_cast<int64_t>(32 + m_maplength));
|
||||
result = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
m_type = m_bpp > 8 ? CV_8UC3 : CV_8UC1;
|
||||
|
||||
if( CV_MAT_CN(m_type) == 1 )
|
||||
FillGrayPalette( m_palette, m_bpp );
|
||||
|
||||
m_offset = m_strm.getPos();
|
||||
|
||||
CV_Assert(m_offset == static_cast<int64_t>(32 + m_maplength));
|
||||
result = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch(...)
|
||||
{
|
||||
}
|
||||
|
||||
if( !result )
|
||||
{
|
||||
m_offset = -1;
|
||||
m_width = m_height = -1;
|
||||
m_strm.close();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
bool SunRasterDecoder::readData( Mat& img )
|
||||
{
|
||||
bool color = img.channels() > 1;
|
||||
uchar* data = img.ptr();
|
||||
size_t step = img.step;
|
||||
uchar gray_palette[256] = {0};
|
||||
bool result = false;
|
||||
int nch = color ? 3 : 1;
|
||||
|
||||
const RowPitchParams pitch_params = calculateRowPitch(m_width, m_bpp, 2, "SunRaster");
|
||||
const int src_pitch = pitch_params.src_pitch;
|
||||
const size_t bytes_per_row = pitch_params.bytes_per_row;
|
||||
const int width3 = calculateRowSize(m_width, nch, "SunRaster");
|
||||
int y;
|
||||
|
||||
if( m_offset < 0 || !m_strm.isOpened())
|
||||
return false;
|
||||
|
||||
AutoBuffer<uchar> _src(src_pitch + 32);
|
||||
uchar* src = _src.data();
|
||||
|
||||
if( !color && m_maptype == RMT_EQUAL_RGB )
|
||||
CvtPaletteToGray( m_palette, gray_palette, 1 << m_bpp );
|
||||
|
||||
try
|
||||
{
|
||||
m_strm.setPos( m_offset );
|
||||
|
||||
switch( m_bpp )
|
||||
{
|
||||
/************************* 1 BPP ************************/
|
||||
case 1:
|
||||
if( m_type != RAS_BYTE_ENCODED )
|
||||
{
|
||||
for( y = 0; y < m_height; y++, data += step )
|
||||
{
|
||||
m_strm.getBytes( src, src_pitch );
|
||||
if( color )
|
||||
FillColorRow1( data, src, m_width, m_palette );
|
||||
else
|
||||
FillGrayRow1( data, src, m_width, gray_palette );
|
||||
}
|
||||
result = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
uchar* line_end = src + bytes_per_row;
|
||||
uchar* tsrc = src;
|
||||
y = 0;
|
||||
|
||||
for(;;)
|
||||
{
|
||||
int max_count = (int)(line_end - tsrc);
|
||||
int code = 0, len = 0, len1 = 0;
|
||||
|
||||
do
|
||||
{
|
||||
code = m_strm.getByte();
|
||||
if( code == 0x80 )
|
||||
{
|
||||
len = m_strm.getByte();
|
||||
if( len != 0 ) break;
|
||||
}
|
||||
tsrc[len1] = (uchar)code;
|
||||
}
|
||||
while( ++len1 < max_count );
|
||||
|
||||
tsrc += len1;
|
||||
|
||||
if( len > 0 ) // encoded mode
|
||||
{
|
||||
++len;
|
||||
code = m_strm.getByte();
|
||||
if( len > line_end - tsrc )
|
||||
{
|
||||
CV_Error(Error::StsInternal, "");
|
||||
goto bad_decoding_1bpp;
|
||||
}
|
||||
|
||||
memset( tsrc, code, len );
|
||||
tsrc += len;
|
||||
}
|
||||
|
||||
if( tsrc >= line_end )
|
||||
{
|
||||
tsrc = src;
|
||||
if( color )
|
||||
FillColorRow1( data, src, m_width, m_palette );
|
||||
else
|
||||
FillGrayRow1( data, src, m_width, gray_palette );
|
||||
data += step;
|
||||
if( ++y >= m_height ) break;
|
||||
}
|
||||
}
|
||||
result = true;
|
||||
bad_decoding_1bpp:
|
||||
;
|
||||
}
|
||||
break;
|
||||
/************************* 8 BPP ************************/
|
||||
case 8:
|
||||
if( m_type != RAS_BYTE_ENCODED )
|
||||
{
|
||||
for( y = 0; y < m_height; y++, data += step )
|
||||
{
|
||||
m_strm.getBytes( src, src_pitch );
|
||||
if( color )
|
||||
FillColorRow8( data, src, m_width, m_palette );
|
||||
else
|
||||
FillGrayRow8( data, src, m_width, gray_palette );
|
||||
}
|
||||
result = true;
|
||||
}
|
||||
else // RLE-encoded
|
||||
{
|
||||
uchar* line_end = data + width3;
|
||||
y = 0;
|
||||
|
||||
for(;;)
|
||||
{
|
||||
int max_count = (int)(line_end - data);
|
||||
int code = 0, len = 0, len1;
|
||||
uchar* tsrc = src;
|
||||
|
||||
do
|
||||
{
|
||||
code = m_strm.getByte();
|
||||
if( code == 0x80 )
|
||||
{
|
||||
len = m_strm.getByte();
|
||||
if( len != 0 ) break;
|
||||
}
|
||||
*tsrc++ = (uchar)code;
|
||||
}
|
||||
while( (max_count -= nch) > 0 );
|
||||
|
||||
len1 = (int)(tsrc - src);
|
||||
|
||||
if( len1 > 0 )
|
||||
{
|
||||
if( color )
|
||||
FillColorRow8( data, src, len1, m_palette );
|
||||
else
|
||||
FillGrayRow8( data, src, len1, gray_palette );
|
||||
data += len1*nch;
|
||||
}
|
||||
|
||||
if( len > 0 ) // encoded mode
|
||||
{
|
||||
len = (len + 1)*nch;
|
||||
code = m_strm.getByte();
|
||||
|
||||
if( color )
|
||||
data = FillUniColor( data, line_end, validateToInt(step), width3,
|
||||
y, m_height, len,
|
||||
m_palette[code] );
|
||||
else
|
||||
data = FillUniGray( data, line_end, validateToInt(step), width3,
|
||||
y, m_height, len,
|
||||
gray_palette[code] );
|
||||
if( y >= m_height )
|
||||
break;
|
||||
}
|
||||
|
||||
if( data == line_end )
|
||||
{
|
||||
if( m_strm.getByte() != 0 )
|
||||
goto bad_decoding_end;
|
||||
line_end += step;
|
||||
data = line_end - width3;
|
||||
if( ++y >= m_height ) break;
|
||||
}
|
||||
}
|
||||
|
||||
result = true;
|
||||
bad_decoding_end:
|
||||
;
|
||||
}
|
||||
break;
|
||||
/************************* 24 BPP ************************/
|
||||
case 24:
|
||||
for( y = 0; y < m_height; y++, data += step )
|
||||
{
|
||||
m_strm.getBytes(src, src_pitch );
|
||||
|
||||
if( color )
|
||||
{
|
||||
if( m_type == RAS_FORMAT_RGB || m_use_rgb)
|
||||
icvCvt_RGB2BGR_8u_C3R(src, 0, data, 0, Size(m_width,1) );
|
||||
else
|
||||
memcpy(data, src, std::min(step, (size_t)src_pitch));
|
||||
}
|
||||
else
|
||||
{
|
||||
icvCvt_BGR2Gray_8u_C3C1R(src, 0, data, 0, Size(m_width,1),
|
||||
m_type == RAS_FORMAT_RGB ? 2 : 0 );
|
||||
}
|
||||
}
|
||||
result = true;
|
||||
break;
|
||||
/************************* 32 BPP ************************/
|
||||
case 32:
|
||||
for( y = 0; y < m_height; y++, data += step )
|
||||
{
|
||||
/* hack: a0 b0 g0 r0 a1 b1 g1 r1 ... are written to src + 3,
|
||||
so when we look at src + 4, we see b0 g0 r0 x b1 g1 g1 x ... */
|
||||
m_strm.getBytes( src + 3, src_pitch );
|
||||
|
||||
if( color )
|
||||
icvCvt_BGRA2BGR_8u_C4C3R( src + 4, 0, data, 0, Size(m_width,1),
|
||||
(m_type == RAS_FORMAT_RGB || m_use_rgb) ? 2 : 0 );
|
||||
else
|
||||
icvCvt_BGRA2Gray_8u_C4C1R( src + 4, 0, data, 0, Size(m_width,1),
|
||||
m_type == RAS_FORMAT_RGB ? 2 : 0 );
|
||||
}
|
||||
result = true;
|
||||
break;
|
||||
default:
|
||||
CV_Error(Error::StsInternal, "");
|
||||
}
|
||||
}
|
||||
catch( ... )
|
||||
{
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
SunRasterEncoder::SunRasterEncoder()
|
||||
{
|
||||
m_description = "Sun raster files (*.sr;*.ras)";
|
||||
m_buf_supported = true;
|
||||
}
|
||||
|
||||
|
||||
ImageEncoder SunRasterEncoder::newEncoder() const
|
||||
{
|
||||
return makePtr<SunRasterEncoder>();
|
||||
}
|
||||
|
||||
SunRasterEncoder::~SunRasterEncoder()
|
||||
{
|
||||
}
|
||||
|
||||
bool SunRasterEncoder::write( const Mat& img, const std::vector<int>& )
|
||||
{
|
||||
bool result = false;
|
||||
int y, width = img.cols, height = img.rows, channels = img.channels();
|
||||
int fileStep = (width*channels + 1) & -2;
|
||||
WMByteStream strm;
|
||||
|
||||
if (m_buf) {
|
||||
if (!strm.open(*m_buf)) {
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
m_buf->reserve(height * fileStep + 32);
|
||||
}
|
||||
}
|
||||
else
|
||||
strm.open(m_filename);
|
||||
|
||||
if( strm.isOpened() )
|
||||
{
|
||||
CHECK_WRITE(strm.putBytes( fmtSignSunRas, (int)strlen(fmtSignSunRas) ));
|
||||
CHECK_WRITE(strm.putDWord( width ));
|
||||
CHECK_WRITE(strm.putDWord( height ));
|
||||
CHECK_WRITE(strm.putDWord( channels*8 ));
|
||||
CHECK_WRITE(strm.putDWord( fileStep*height ));
|
||||
CHECK_WRITE(strm.putDWord( RAS_STANDARD ));
|
||||
CHECK_WRITE(strm.putDWord( RMT_NONE ));
|
||||
CHECK_WRITE(strm.putDWord( 0 ));
|
||||
|
||||
for( y = 0; y < height; y++ )
|
||||
CHECK_WRITE(strm.putBytes( img.ptr(y), fileStep ));
|
||||
|
||||
strm.close();
|
||||
result = true;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif // HAVE_IMGCODEC_SUNRASTER
|
||||
@@ -0,0 +1,109 @@
|
||||
/*M///////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
|
||||
//
|
||||
// By downloading, copying, installing or using the software you agree to this license.
|
||||
// If you do not agree to this license, do not download, install,
|
||||
// copy or use the software.
|
||||
//
|
||||
//
|
||||
// License Agreement
|
||||
// For Open Source Computer Vision Library
|
||||
//
|
||||
// Copyright (C) 2000-2008, Intel Corporation, all rights reserved.
|
||||
// Copyright (C) 2009, Willow Garage Inc., all rights reserved.
|
||||
// Third party copyrights are property of their respective owners.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without modification,
|
||||
// are permitted provided that the following conditions are met:
|
||||
//
|
||||
// * Redistribution's of source code must retain the above copyright notice,
|
||||
// this list of conditions and the following disclaimer.
|
||||
//
|
||||
// * Redistribution's in binary form must reproduce the above copyright notice,
|
||||
// this list of conditions and the following disclaimer in the documentation
|
||||
// and/or other materials provided with the distribution.
|
||||
//
|
||||
// * The name of the copyright holders may not be used to endorse or promote products
|
||||
// derived from this software without specific prior written permission.
|
||||
//
|
||||
// This software is provided by the copyright holders and contributors "as is" and
|
||||
// any express or implied warranties, including, but not limited to, the implied
|
||||
// warranties of merchantability and fitness for a particular purpose are disclaimed.
|
||||
// In no event shall the Intel Corporation or contributors be liable for any direct,
|
||||
// indirect, incidental, special, exemplary, or consequential damages
|
||||
// (including, but not limited to, procurement of substitute goods or services;
|
||||
// loss of use, data, or profits; or business interruption) however caused
|
||||
// and on any theory of liability, whether in contract, strict liability,
|
||||
// or tort (including negligence or otherwise) arising in any way out of
|
||||
// the use of this software, even if advised of the possibility of such damage.
|
||||
//
|
||||
//M*/
|
||||
|
||||
#ifndef _GRFMT_SUNRAS_H_
|
||||
#define _GRFMT_SUNRAS_H_
|
||||
|
||||
#include "grfmt_base.hpp"
|
||||
|
||||
#ifdef HAVE_IMGCODEC_SUNRASTER
|
||||
|
||||
namespace cv
|
||||
{
|
||||
|
||||
enum SunRasType
|
||||
{
|
||||
RAS_OLD = 0,
|
||||
RAS_STANDARD = 1,
|
||||
RAS_BYTE_ENCODED = 2, /* RLE encoded */
|
||||
RAS_FORMAT_RGB = 3 /* RGB instead of BGR */
|
||||
};
|
||||
|
||||
enum SunRasMapType
|
||||
{
|
||||
RMT_NONE = 0, /* direct color encoding */
|
||||
RMT_EQUAL_RGB = 1 /* paletted image */
|
||||
};
|
||||
|
||||
|
||||
// Sun Raster Reader
|
||||
class SunRasterDecoder CV_FINAL : public BaseImageDecoder
|
||||
{
|
||||
public:
|
||||
|
||||
SunRasterDecoder();
|
||||
virtual ~SunRasterDecoder() CV_OVERRIDE;
|
||||
|
||||
bool readData( Mat& img ) CV_OVERRIDE;
|
||||
bool readHeader() CV_OVERRIDE;
|
||||
void close();
|
||||
|
||||
ImageDecoder newDecoder() const CV_OVERRIDE;
|
||||
|
||||
protected:
|
||||
|
||||
RMByteStream m_strm;
|
||||
PaletteEntry m_palette[256];
|
||||
int m_bpp;
|
||||
int64_t m_offset;
|
||||
SunRasType m_encoding;
|
||||
SunRasMapType m_maptype;
|
||||
int m_maplength;
|
||||
};
|
||||
|
||||
|
||||
class SunRasterEncoder CV_FINAL : public BaseImageEncoder
|
||||
{
|
||||
public:
|
||||
SunRasterEncoder();
|
||||
virtual ~SunRasterEncoder() CV_OVERRIDE;
|
||||
|
||||
bool write( const Mat& img, const std::vector<int>& params ) CV_OVERRIDE;
|
||||
|
||||
ImageEncoder newEncoder() const CV_OVERRIDE;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // HAVE_IMGCODEC_SUNRASTER
|
||||
|
||||
#endif/*_GRFMT_SUNRAS_H_*/
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,148 @@
|
||||
/*M///////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
|
||||
//
|
||||
// By downloading, copying, installing or using the software you agree to this license.
|
||||
// If you do not agree to this license, do not download, install,
|
||||
// copy or use the software.
|
||||
//
|
||||
//
|
||||
// License Agreement
|
||||
// For Open Source Computer Vision Library
|
||||
//
|
||||
// Copyright (C) 2000-2008, Intel Corporation, all rights reserved.
|
||||
// Copyright (C) 2009, Willow Garage Inc., all rights reserved.
|
||||
// Third party copyrights are property of their respective owners.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without modification,
|
||||
// are permitted provided that the following conditions are met:
|
||||
//
|
||||
// * Redistribution's of source code must retain the above copyright notice,
|
||||
// this list of conditions and the following disclaimer.
|
||||
//
|
||||
// * Redistribution's in binary form must reproduce the above copyright notice,
|
||||
// this list of conditions and the following disclaimer in the documentation
|
||||
// and/or other materials provided with the distribution.
|
||||
//
|
||||
// * The name of the copyright holders may not be used to endorse or promote products
|
||||
// derived from this software without specific prior written permission.
|
||||
//
|
||||
// This software is provided by the copyright holders and contributors "as is" and
|
||||
// any express or implied warranties, including, but not limited to, the implied
|
||||
// warranties of merchantability and fitness for a particular purpose are disclaimed.
|
||||
// In no event shall the Intel Corporation or contributors be liable for any direct,
|
||||
// indirect, incidental, special, exemplary, or consequential damages
|
||||
// (including, but not limited to, procurement of substitute goods or services;
|
||||
// loss of use, data, or profits; or business interruption) however caused
|
||||
// and on any theory of liability, whether in contract, strict liability,
|
||||
// or tort (including negligence or otherwise) arising in any way out of
|
||||
// the use of this software, even if advised of the possibility of such damage.
|
||||
//
|
||||
//M*/
|
||||
|
||||
#ifndef _GRFMT_TIFF_H_
|
||||
#define _GRFMT_TIFF_H_
|
||||
|
||||
#include "grfmt_base.hpp"
|
||||
|
||||
#ifdef HAVE_TIFF
|
||||
|
||||
namespace cv
|
||||
{
|
||||
|
||||
// native simple TIFF codec
|
||||
enum TiffCompression
|
||||
{
|
||||
TIFF_UNCOMP = 1,
|
||||
TIFF_HUFFMAN = 2,
|
||||
TIFF_PACKBITS = 32773
|
||||
};
|
||||
|
||||
enum TiffByteOrder
|
||||
{
|
||||
TIFF_ORDER_II = 0x4949,
|
||||
TIFF_ORDER_MM = 0x4d4d
|
||||
};
|
||||
|
||||
|
||||
enum TiffTag
|
||||
{
|
||||
TIFF_TAG_WIDTH = 256,
|
||||
TIFF_TAG_HEIGHT = 257,
|
||||
TIFF_TAG_BITS_PER_SAMPLE = 258,
|
||||
TIFF_TAG_COMPRESSION = 259,
|
||||
TIFF_TAG_PHOTOMETRIC = 262,
|
||||
TIFF_TAG_STRIP_OFFSETS = 273,
|
||||
TIFF_TAG_STRIP_COUNTS = 279,
|
||||
TIFF_TAG_SAMPLES_PER_PIXEL = 277,
|
||||
TIFF_TAG_ROWS_PER_STRIP = 278,
|
||||
TIFF_TAG_PLANAR_CONFIG = 284,
|
||||
TIFF_TAG_COLOR_MAP = 320
|
||||
};
|
||||
|
||||
|
||||
enum TiffFieldType
|
||||
{
|
||||
TIFF_TYPE_BYTE = 1,
|
||||
TIFF_TYPE_SHORT = 3,
|
||||
TIFF_TYPE_LONG = 4
|
||||
};
|
||||
|
||||
|
||||
// libtiff based TIFF codec
|
||||
class TiffDecoder CV_FINAL : public BaseImageDecoder
|
||||
{
|
||||
public:
|
||||
TiffDecoder();
|
||||
virtual ~TiffDecoder() CV_OVERRIDE;
|
||||
|
||||
bool readHeader() CV_OVERRIDE;
|
||||
bool readData( Mat& img ) CV_OVERRIDE;
|
||||
void close();
|
||||
bool nextPage() CV_OVERRIDE;
|
||||
|
||||
size_t signatureLength() const CV_OVERRIDE;
|
||||
bool checkSignature( const String& signature ) const CV_OVERRIDE;
|
||||
ImageDecoder newDecoder() const CV_OVERRIDE;
|
||||
|
||||
protected:
|
||||
cv::Ptr<void> m_tif;
|
||||
int normalizeChannelsNumber(int channels) const;
|
||||
bool m_hdr;
|
||||
size_t m_buf_pos;
|
||||
|
||||
private:
|
||||
TiffDecoder(const TiffDecoder &); // copy disabled
|
||||
TiffDecoder& operator=(const TiffDecoder &); // assign disabled
|
||||
};
|
||||
|
||||
// ... and writer
|
||||
class TiffEncoder CV_FINAL : public BaseImageEncoder
|
||||
{
|
||||
public:
|
||||
TiffEncoder();
|
||||
virtual ~TiffEncoder() CV_OVERRIDE;
|
||||
|
||||
bool isFormatSupported( int depth ) const CV_OVERRIDE;
|
||||
|
||||
bool write( const Mat& img, const std::vector<int>& params ) CV_OVERRIDE;
|
||||
|
||||
bool writemulti(const std::vector<Mat>& img_vec, const std::vector<int>& params) CV_OVERRIDE;
|
||||
|
||||
ImageEncoder newEncoder() const CV_OVERRIDE;
|
||||
|
||||
protected:
|
||||
bool writeLibTiff( const std::vector<Mat>& img_vec, const std::vector<int>& params );
|
||||
bool write_32FC3_SGILOG(const Mat& img, void* tif);
|
||||
bool write_32F_SGILOG(const Mat& img, void* tif, int compression);
|
||||
|
||||
private:
|
||||
TiffEncoder(const TiffEncoder &); // copy disabled
|
||||
TiffEncoder& operator=(const TiffEncoder &); // assign disabled
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // HAVE_TIFF
|
||||
|
||||
#endif/*_GRFMT_TIFF_H_*/
|
||||
@@ -0,0 +1,717 @@
|
||||
/*M///////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
|
||||
//
|
||||
// By downloading, copying, installing or using the software you agree to this license.
|
||||
// If you do not agree to this license, do not download, install,
|
||||
// copy or use the software.
|
||||
//
|
||||
//
|
||||
// License Agreement
|
||||
// For Open Source Computer Vision Library
|
||||
//
|
||||
// Copyright (C) 2000-2008, Intel Corporation, all rights reserved.
|
||||
// Copyright (C) 2009, Willow Garage Inc., all rights reserved.
|
||||
// Third party copyrights are property of their respective owners.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without modification,
|
||||
// are permitted provided that the following conditions are met:
|
||||
//
|
||||
// * Redistribution's of source code must retain the above copyright notice,
|
||||
// this list of conditions and the following disclaimer.
|
||||
//
|
||||
// * Redistribution's in binary form must reproduce the above copyright notice,
|
||||
// this list of conditions and the following disclaimer in the documentation
|
||||
// and/or other materials provided with the distribution.
|
||||
//
|
||||
// * The name of the copyright holders may not be used to endorse or promote products
|
||||
// derived from this software without specific prior written permission.
|
||||
//
|
||||
// This software is provided by the copyright holders and contributors "as is" and
|
||||
// any express or implied warranties, including, but not limited to, the implied
|
||||
// warranties of merchantability and fitness for a particular purpose are disclaimed.
|
||||
// In no event shall the Intel Corporation or contributors be liable for any direct,
|
||||
// indirect, incidental, special, exemplary, or consequential damages
|
||||
// (including, but not limited to, procurement of substitute goods or services;
|
||||
// loss of use, data, or profits; or business interruption) however caused
|
||||
// and on any theory of liability, whether in contract, strict liability,
|
||||
// or tort (including negligence or otherwise) arising in any way out of
|
||||
// the use of this software, even if advised of the possibility of such damage.
|
||||
//
|
||||
//M*/
|
||||
|
||||
#ifdef HAVE_WEBP
|
||||
|
||||
#include "precomp.hpp"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <limits.h>
|
||||
#include "grfmt_webp.hpp"
|
||||
#include <opencv2/core/utils/logger.hpp>
|
||||
#include <opencv2/core/utils/configuration.private.hpp>
|
||||
#include <webp/decode.h>
|
||||
#include <webp/encode.h>
|
||||
#include <webp/demux.h>
|
||||
#include <webp/mux.h>
|
||||
|
||||
namespace cv
|
||||
{
|
||||
|
||||
// 64Mb limit to avoid memory DDOS
|
||||
static size_t param_maxFileSize = utils::getConfigurationParameterSizeT("OPENCV_IMGCODECS_WEBP_MAX_FILE_SIZE", 64*1024*1024);
|
||||
|
||||
static const size_t WEBP_HEADER_SIZE = 32;
|
||||
|
||||
WebPDecoder::WebPDecoder()
|
||||
{
|
||||
m_buf_supported = true;
|
||||
fs_size = 0;
|
||||
m_has_animation = false;
|
||||
m_previous_timestamp = 0;
|
||||
m_read_options = 1;
|
||||
}
|
||||
|
||||
WebPDecoder::~WebPDecoder() {}
|
||||
|
||||
void WebPDecoder::UniquePtrDeleter::operator()(WebPAnimDecoder* decoder) const
|
||||
{
|
||||
WebPAnimDecoderDelete(decoder);
|
||||
}
|
||||
|
||||
size_t WebPDecoder::signatureLength() const
|
||||
{
|
||||
return WEBP_HEADER_SIZE;
|
||||
}
|
||||
|
||||
bool WebPDecoder::checkSignature(const String & signature) const
|
||||
{
|
||||
bool ret = false;
|
||||
|
||||
if(signature.size() >= WEBP_HEADER_SIZE)
|
||||
{
|
||||
WebPBitstreamFeatures features;
|
||||
if(VP8_STATUS_OK == WebPGetFeatures((uint8_t *)signature.c_str(),
|
||||
WEBP_HEADER_SIZE, &features))
|
||||
{
|
||||
ret = true;
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
ImageDecoder WebPDecoder::newDecoder() const
|
||||
{
|
||||
return makePtr<WebPDecoder>();
|
||||
}
|
||||
|
||||
bool WebPDecoder::readHeader()
|
||||
{
|
||||
if (m_has_animation)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
uint8_t header[WEBP_HEADER_SIZE] = { 0 };
|
||||
if (m_buf.empty())
|
||||
{
|
||||
fs.open(m_filename.c_str(), std::ios::binary);
|
||||
fs.seekg(0, std::ios::end);
|
||||
fs_size = safeCastToSizeT(fs.tellg(), "File is too large");
|
||||
fs.seekg(0, std::ios::beg);
|
||||
CV_Assert(fs && "File stream error");
|
||||
CV_CheckGE(fs_size, WEBP_HEADER_SIZE, "File is too small");
|
||||
CV_CheckLE(fs_size, param_maxFileSize, "File is too large. Increase OPENCV_IMGCODECS_WEBP_MAX_FILE_SIZE parameter if you want to process large files");
|
||||
|
||||
fs.read((char*)header, sizeof(header));
|
||||
CV_Assert(fs && "Can't read WEBP_HEADER_SIZE bytes");
|
||||
}
|
||||
else
|
||||
{
|
||||
CV_CheckGE(m_buf.total(), WEBP_HEADER_SIZE, "Buffer is too small");
|
||||
memcpy(header, m_buf.ptr(), sizeof(header));
|
||||
data = m_buf;
|
||||
}
|
||||
|
||||
WebPBitstreamFeatures features;
|
||||
if (VP8_STATUS_OK < WebPGetFeatures(header, sizeof(header), &features)) return false;
|
||||
|
||||
m_has_animation = features.has_animation == 1;
|
||||
|
||||
if (m_has_animation)
|
||||
{
|
||||
if (m_buf.empty())
|
||||
{
|
||||
fs.seekg(0, std::ios::beg); CV_Assert(fs && "File stream error");
|
||||
data.create(1, validateToInt(fs_size), CV_8UC1);
|
||||
fs.read((char*)data.ptr(), fs_size);
|
||||
CV_Assert(fs && "Can't read file data");
|
||||
fs.close();
|
||||
}
|
||||
|
||||
CV_Assert(data.type() == CV_8UC1); CV_Assert(data.rows == 1);
|
||||
|
||||
WebPData webp_data;
|
||||
webp_data.bytes = (const uint8_t*)data.ptr();
|
||||
webp_data.size = data.total();
|
||||
|
||||
WebPAnimDecoderOptions dec_options;
|
||||
if (!WebPAnimDecoderOptionsInit(&dec_options))
|
||||
CV_Error(Error::StsInternal, "Failed to initialize animated WebP decoding options");
|
||||
|
||||
dec_options.color_mode = m_use_rgb ? MODE_RGBA : MODE_BGRA;
|
||||
anim_decoder.reset(WebPAnimDecoderNew(&webp_data, &dec_options));
|
||||
CV_Assert(anim_decoder.get() && "Error parsing image");
|
||||
|
||||
WebPAnimInfo anim_info;
|
||||
if (!WebPAnimDecoderGetInfo(anim_decoder.get(), &anim_info))
|
||||
CV_Error(Error::StsInternal, "Failed to get animated WebP information");
|
||||
m_animation.loop_count = anim_info.loop_count;
|
||||
|
||||
m_animation.bgcolor[0] = (anim_info.bgcolor >> 24) & 0xFF;
|
||||
m_animation.bgcolor[1] = (anim_info.bgcolor >> 16) & 0xFF;
|
||||
m_animation.bgcolor[2] = (anim_info.bgcolor >> 8) & 0xFF;
|
||||
m_animation.bgcolor[3] = anim_info.bgcolor & 0xFF;
|
||||
m_frame_count = anim_info.frame_count;
|
||||
}
|
||||
m_width = features.width;
|
||||
m_height = features.height;
|
||||
m_type = features.has_alpha ? CV_8UC4 : CV_8UC3;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WebPDecoder::readData(Mat &img)
|
||||
{
|
||||
CV_CheckGE(m_width, 0, ""); CV_CheckGE(m_height, 0, "");
|
||||
|
||||
CV_CheckEQ(img.cols, m_width, "");
|
||||
CV_CheckEQ(img.rows, m_height, "");
|
||||
|
||||
if (data.empty())
|
||||
{
|
||||
fs.seekg(0, std::ios::beg); CV_Assert(fs && "File stream error");
|
||||
data.create(1, validateToInt(fs_size), CV_8UC1);
|
||||
fs.read((char*)data.ptr(), fs_size);
|
||||
CV_Assert(fs && "Can't read file data");
|
||||
fs.close();
|
||||
}
|
||||
CV_Assert(data.type() == CV_8UC1); CV_Assert(data.rows == 1);
|
||||
|
||||
if (m_read_options) {
|
||||
WebPData webp_data;
|
||||
webp_data.bytes = (const uint8_t*)data.ptr();
|
||||
webp_data.size = data.total();
|
||||
|
||||
std::vector<uchar> metadata;
|
||||
WebPDemuxer* demux = WebPDemux(&webp_data);
|
||||
|
||||
if (demux)
|
||||
{
|
||||
WebPChunkIterator chunk_iter;
|
||||
|
||||
if (WebPDemuxGetChunk(demux, "EXIF", 1, &chunk_iter))
|
||||
{
|
||||
metadata = std::vector<uchar>(chunk_iter.chunk.bytes,
|
||||
chunk_iter.chunk.bytes + chunk_iter.chunk.size);
|
||||
WebPDemuxReleaseChunkIterator(&chunk_iter);
|
||||
m_exif.parseExif(metadata.data(), metadata.size());
|
||||
}
|
||||
|
||||
if (WebPDemuxGetChunk(demux, "ICCP", 1, &chunk_iter))
|
||||
{
|
||||
metadata = std::vector<uchar>(chunk_iter.chunk.bytes,
|
||||
chunk_iter.chunk.bytes + chunk_iter.chunk.size);
|
||||
WebPDemuxReleaseChunkIterator(&chunk_iter);
|
||||
m_metadata[IMAGE_METADATA_ICCP] = metadata;
|
||||
}
|
||||
|
||||
if (WebPDemuxGetChunk(demux, "XMP ", 1, &chunk_iter)) // note the space in "XMP "
|
||||
{
|
||||
metadata = std::vector<uchar>(chunk_iter.chunk.bytes,
|
||||
chunk_iter.chunk.bytes + chunk_iter.chunk.size);
|
||||
WebPDemuxReleaseChunkIterator(&chunk_iter);
|
||||
m_metadata[IMAGE_METADATA_XMP] = metadata;
|
||||
}
|
||||
|
||||
WebPDemuxDelete(demux);
|
||||
m_read_options = 0;
|
||||
}
|
||||
}
|
||||
|
||||
Mat read_img;
|
||||
CV_CheckType(img.type(), img.type() == CV_8UC1 || img.type() == CV_8UC3 || img.type() == CV_8UC4, "");
|
||||
if (img.type() != m_type || img.cols != m_width || img.rows != m_height)
|
||||
{
|
||||
read_img.create(m_height, m_width, m_type);
|
||||
}
|
||||
else
|
||||
{
|
||||
read_img = img; // copy header
|
||||
}
|
||||
|
||||
uchar* out_data = read_img.ptr();
|
||||
size_t out_data_size = read_img.dataend - out_data;
|
||||
|
||||
uchar* res_ptr = NULL;
|
||||
|
||||
if (m_has_animation)
|
||||
{
|
||||
uint8_t* buf;
|
||||
int timestamp;
|
||||
|
||||
if (!WebPAnimDecoderGetNext(anim_decoder.get(), &buf, ×tamp))
|
||||
CV_Error(Error::StsInternal, "Failed to decode animated WebP frame");
|
||||
Mat tmp(Size(m_width, m_height), CV_8UC4, buf);
|
||||
|
||||
if (img.type() == CV_8UC1)
|
||||
{
|
||||
cvtColor(tmp, img, COLOR_BGR2GRAY);
|
||||
}
|
||||
else
|
||||
if (img.type() == CV_8UC3)
|
||||
{
|
||||
cvtColor(tmp, img, COLOR_BGRA2BGR);
|
||||
}
|
||||
else
|
||||
tmp.copyTo(img);
|
||||
|
||||
m_animation.durations.push_back(timestamp - m_previous_timestamp);
|
||||
m_previous_timestamp = timestamp;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (m_type == CV_8UC3)
|
||||
{
|
||||
CV_CheckTypeEQ(read_img.type(), CV_8UC3, "");
|
||||
if (m_use_rgb)
|
||||
res_ptr = WebPDecodeRGBInto(data.ptr(), data.total(), out_data,
|
||||
(int)out_data_size, (int)read_img.step);
|
||||
else
|
||||
res_ptr = WebPDecodeBGRInto(data.ptr(), data.total(), out_data,
|
||||
(int)out_data_size, (int)read_img.step);
|
||||
}
|
||||
else if (m_type == CV_8UC4)
|
||||
{
|
||||
CV_CheckTypeEQ(read_img.type(), CV_8UC4, "");
|
||||
if (m_use_rgb)
|
||||
res_ptr = WebPDecodeRGBAInto(data.ptr(), data.total(), out_data,
|
||||
(int)out_data_size, (int)read_img.step);
|
||||
else
|
||||
res_ptr = WebPDecodeBGRAInto(data.ptr(), data.total(), out_data,
|
||||
(int)out_data_size, (int)read_img.step);
|
||||
}
|
||||
|
||||
if (res_ptr != out_data)
|
||||
return false;
|
||||
|
||||
if (read_img.data == img.data && img.type() == m_type)
|
||||
{
|
||||
// nothing
|
||||
}
|
||||
else if (img.type() == CV_8UC1)
|
||||
{
|
||||
cvtColor(read_img, img, COLOR_BGR2GRAY);
|
||||
}
|
||||
else if (img.type() == CV_8UC3 && m_type == CV_8UC4)
|
||||
{
|
||||
cvtColor(read_img, img, COLOR_BGRA2BGR);
|
||||
}
|
||||
else
|
||||
{
|
||||
CV_Error(Error::StsInternal, "");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WebPDecoder::nextPage()
|
||||
{
|
||||
// Prepare the next page, if any.
|
||||
return WebPAnimDecoderHasMoreFrames(anim_decoder.get()) > 0;
|
||||
}
|
||||
|
||||
WebPEncoder::WebPEncoder()
|
||||
{
|
||||
m_description = "WebP files (*.webp)";
|
||||
m_buf_supported = true;
|
||||
m_support_metadata.assign((size_t)IMAGE_METADATA_MAX + 1, false);
|
||||
m_support_metadata[IMAGE_METADATA_EXIF] = true;
|
||||
m_support_metadata[IMAGE_METADATA_XMP] = true;
|
||||
m_support_metadata[IMAGE_METADATA_ICCP] = true;
|
||||
m_supported_encode_key = {IMWRITE_WEBP_QUALITY, IMWRITE_WEBP_LOSSLESS_MODE};
|
||||
}
|
||||
|
||||
WebPEncoder::~WebPEncoder() { }
|
||||
|
||||
ImageEncoder WebPEncoder::newEncoder() const
|
||||
{
|
||||
return makePtr<WebPEncoder>();
|
||||
}
|
||||
|
||||
// Simple API style
|
||||
static size_t cvEncodeLosslessExactBGRA(const uint8_t* rgba, int width, int height, int stride, uint8_t** output)
|
||||
{
|
||||
WebPConfig config;
|
||||
WebPPicture pic;
|
||||
WebPMemoryWriter wrt;
|
||||
|
||||
// 6 is the default value for speed/compression balance in lossless mode.
|
||||
// It doesn't affect visual quality, only file size and encoding time.
|
||||
if (!WebPConfigInit(&config) || !WebPConfigLosslessPreset(&config, 6))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
config.exact = 1;
|
||||
|
||||
if (!WebPPictureInit(&pic))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
pic.width = width;
|
||||
pic.height = height;
|
||||
pic.use_argb = 1; // BGRA
|
||||
if (!WebPPictureImportBGRA(&pic, rgba, stride))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
WebPMemoryWriterInit(&wrt);
|
||||
pic.writer = WebPMemoryWrite;
|
||||
pic.custom_ptr = &wrt;
|
||||
|
||||
if (!WebPEncode(&config, &pic))
|
||||
{
|
||||
WebPMemoryWriterClear(&wrt);
|
||||
WebPPictureFree(&pic);
|
||||
return 0;
|
||||
}
|
||||
|
||||
*output = wrt.mem;
|
||||
size_t size = wrt.size;
|
||||
WebPPictureFree(&pic);
|
||||
return size;
|
||||
}
|
||||
|
||||
bool WebPEncoder::write(const Mat& img, const std::vector<int>& params)
|
||||
{
|
||||
CV_CheckDepthEQ(img.depth(), CV_8U, "WebP codec supports 8U images only");
|
||||
|
||||
const int width = img.cols, height = img.rows;
|
||||
|
||||
int lossless_mode = -1; // not specified
|
||||
float quality = 0.0f; // not specified
|
||||
|
||||
for(size_t i = 0; i < params.size(); i += 2)
|
||||
{
|
||||
const int value = params[i+1];
|
||||
if (params[i] == IMWRITE_WEBP_LOSSLESS_MODE)
|
||||
{
|
||||
switch(value)
|
||||
{
|
||||
case IMWRITE_WEBP_LOSSLESS_ON:
|
||||
case IMWRITE_WEBP_LOSSLESS_OFF:
|
||||
case IMWRITE_WEBP_LOSSLESS_PRESERVE_COLOR:
|
||||
lossless_mode = value;
|
||||
break;
|
||||
default:
|
||||
CV_LOG_WARNING(nullptr, cv::format("The value(%d) for IMWRITE_WEBP_LOSSLESS_MODE must be one of ImwriteWEBPLosslessMode. It is ignored", value));
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (params[i] == IMWRITE_WEBP_QUALITY)
|
||||
{
|
||||
if (value < 1)
|
||||
{
|
||||
CV_LOG_WARNING(nullptr, cv::format("The value(%d) for IMWRITE_WEBP_QUALITY must be between 1 to 100(lossy) or more(lossless). It is fallbacked to 1", value));
|
||||
quality = 1.0f;
|
||||
}
|
||||
else if (value > 100)
|
||||
{
|
||||
quality = 101.0f;
|
||||
}
|
||||
else // value is 1 to 100
|
||||
{
|
||||
quality = static_cast<float>(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
switch(lossless_mode)
|
||||
{
|
||||
case -1: // not specified by user
|
||||
case IMWRITE_WEBP_LOSSLESS_OFF:
|
||||
// Fallback to lossless if quality is not specified (-1.0f) or out of lossy range (>100.0f).
|
||||
// This maintains backward compatibility where WebP defaults to lossless.
|
||||
if ((quality < 1.0f) || (quality > 100.0f))
|
||||
{
|
||||
lossless_mode = IMWRITE_WEBP_LOSSLESS_ON;
|
||||
quality = 101.0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
lossless_mode = IMWRITE_WEBP_LOSSLESS_OFF;
|
||||
// Use specified quality for lossy compression.
|
||||
}
|
||||
break;
|
||||
|
||||
case IMWRITE_WEBP_LOSSLESS_ON:
|
||||
case IMWRITE_WEBP_LOSSLESS_PRESERVE_COLOR:
|
||||
// Force quality value to lossless range when explicit lossless mode is selected.
|
||||
quality = 101.0f;
|
||||
break;
|
||||
|
||||
default:
|
||||
CV_Error(Error::StsError, cv::format("Unexpected lossless_mode(%d)", lossless_mode));
|
||||
break;
|
||||
}
|
||||
|
||||
int channels = img.channels();
|
||||
CV_Check(channels, channels == 1 || channels == 3 || channels == 4, "");
|
||||
|
||||
const Mat *image = &img;
|
||||
Mat temp;
|
||||
|
||||
if (channels == 1)
|
||||
{
|
||||
cvtColor(*image, temp, COLOR_GRAY2BGR);
|
||||
image = &temp;
|
||||
channels = 3;
|
||||
}
|
||||
|
||||
uint8_t *encoder_out = NULL;
|
||||
size_t size = 0;
|
||||
if (channels == 3)
|
||||
{
|
||||
switch(lossless_mode)
|
||||
{
|
||||
case IMWRITE_WEBP_LOSSLESS_OFF:
|
||||
size = WebPEncodeBGR(image->ptr(), width, height, (int)image->step, quality, &encoder_out);
|
||||
break;
|
||||
case IMWRITE_WEBP_LOSSLESS_ON:
|
||||
case IMWRITE_WEBP_LOSSLESS_PRESERVE_COLOR:
|
||||
size = WebPEncodeLosslessBGR(image->ptr(), width, height, (int)image->step, &encoder_out);
|
||||
break;
|
||||
default:
|
||||
CV_Error(Error::StsError, cv::format("Unexcepted lossless_mode(%d)", lossless_mode));
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
CV_CheckEQ(channels, 4, "Unexpected channels is used");
|
||||
switch(lossless_mode)
|
||||
{
|
||||
case IMWRITE_WEBP_LOSSLESS_OFF:
|
||||
size = WebPEncodeBGRA(image->ptr(), width, height, (int)image->step, quality, &encoder_out);
|
||||
break;
|
||||
case IMWRITE_WEBP_LOSSLESS_ON:
|
||||
size = WebPEncodeLosslessBGRA(image->ptr(), width, height, (int)image->step, &encoder_out);
|
||||
break;
|
||||
case IMWRITE_WEBP_LOSSLESS_PRESERVE_COLOR:
|
||||
size = cvEncodeLosslessExactBGRA(image->ptr(), width, height, (int)image->step, &encoder_out);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (size == 0)
|
||||
{
|
||||
CV_LOG_ERROR(NULL, cv::format("WebP encoding failed with lossless_mode=%d", lossless_mode));
|
||||
return false;
|
||||
}
|
||||
|
||||
#if WEBP_DECODER_ABI_VERSION >= 0x0206
|
||||
Ptr<uint8_t> out_cleaner(encoder_out, WebPFree);
|
||||
#else
|
||||
Ptr<uint8_t> out_cleaner(encoder_out, free);
|
||||
#endif
|
||||
|
||||
uint8_t *out = encoder_out;
|
||||
uint8_t *muxer_out = nullptr;
|
||||
|
||||
if (!m_metadata.empty())
|
||||
{
|
||||
WebPData muxerData;
|
||||
|
||||
WebPMux* mux = WebPMuxNew();
|
||||
WebPData imageData = { out, size };
|
||||
WebPMuxSetImage(mux, &imageData, 0);
|
||||
|
||||
WebPData metadata;
|
||||
if (m_metadata[IMAGE_METADATA_EXIF].size() > 0)
|
||||
{
|
||||
metadata.bytes = m_metadata[IMAGE_METADATA_EXIF].data();
|
||||
metadata.size = m_metadata[IMAGE_METADATA_EXIF].size();
|
||||
WebPMuxSetChunk(mux, "EXIF", &metadata, 1);
|
||||
}
|
||||
if (m_metadata[IMAGE_METADATA_XMP].size() > 0)
|
||||
{
|
||||
metadata.bytes = m_metadata[IMAGE_METADATA_XMP].data();
|
||||
metadata.size = m_metadata[IMAGE_METADATA_XMP].size();
|
||||
WebPMuxSetChunk(mux, "XMP ", &metadata, 1);
|
||||
}
|
||||
|
||||
if (m_metadata[IMAGE_METADATA_ICCP].size() > 0)
|
||||
{
|
||||
metadata.bytes = m_metadata[IMAGE_METADATA_ICCP].data();
|
||||
metadata.size = m_metadata[IMAGE_METADATA_ICCP].size();
|
||||
WebPMuxSetChunk(mux, "ICCP", &metadata, 1);
|
||||
}
|
||||
|
||||
if (WebPMuxAssemble(mux, &muxerData) == WEBP_MUX_OK) {
|
||||
size = muxerData.size;
|
||||
muxer_out = const_cast<uint8_t*>(muxerData.bytes);
|
||||
out = muxer_out;
|
||||
WebPMuxDelete(mux);
|
||||
}
|
||||
else {
|
||||
WebPMuxDelete(mux);
|
||||
CV_Error(Error::StsError, "Failed to assemble WebP with EXIF");
|
||||
}
|
||||
}
|
||||
|
||||
#if WEBP_DECODER_ABI_VERSION >= 0x0206
|
||||
Ptr<const uint8_t> muxer_cleaner(muxer_out, WebPFree);
|
||||
#else
|
||||
Ptr<const uint8_t> muxer_cleaner(muxer_out, free);
|
||||
#endif
|
||||
|
||||
CV_Assert(size > 0);
|
||||
size_t bytes_written = 0;
|
||||
if (m_buf)
|
||||
{
|
||||
m_buf->resize(size);
|
||||
memcpy(&(*m_buf)[0], out, size);
|
||||
bytes_written = size;
|
||||
}
|
||||
else
|
||||
{
|
||||
FILE *fd = fopen(m_filename.c_str(), "wb");
|
||||
if (fd != NULL)
|
||||
{
|
||||
bytes_written = fwrite(out, sizeof(uint8_t), size, fd);
|
||||
if (size != bytes_written)
|
||||
{
|
||||
CV_LOG_ERROR(NULL, cv::format("Only %zu or %zu bytes are written\n",bytes_written, size));
|
||||
}
|
||||
fclose(fd); fd = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
return (size > 0) && (bytes_written == size);
|
||||
}
|
||||
|
||||
bool WebPEncoder::writeanimation(const Animation& animation, const std::vector<int>& params)
|
||||
{
|
||||
CV_CheckDepthEQ(animation.frames[0].depth(), CV_8U, "WebP codec supports only 8-bit unsigned images");
|
||||
int ok = 0;
|
||||
int timestamp = 0;
|
||||
const int width = animation.frames[0].cols, height = animation.frames[0].rows;
|
||||
|
||||
WebPAnimEncoderOptions anim_config;
|
||||
WebPConfig config;
|
||||
WebPPicture pic;
|
||||
WebPData webp_data;
|
||||
|
||||
WebPDataInit(&webp_data);
|
||||
if (!WebPAnimEncoderOptionsInit(&anim_config) ||
|
||||
!WebPConfigInit(&config) ||
|
||||
!WebPPictureInit(&pic)) {
|
||||
CV_LOG_ERROR(NULL, "Library version mismatch!\n");
|
||||
WebPDataClear(&webp_data);
|
||||
return false;
|
||||
}
|
||||
|
||||
int bgvalue = (static_cast<int>(animation.bgcolor[0]) & 0xFF) << 24 |
|
||||
(static_cast<int>(animation.bgcolor[1]) & 0xFF) << 16 |
|
||||
(static_cast<int>(animation.bgcolor[2]) & 0xFF) << 8 |
|
||||
(static_cast<int>(animation.bgcolor[3]) & 0xFF);
|
||||
|
||||
anim_config.anim_params.bgcolor = bgvalue;
|
||||
anim_config.anim_params.loop_count = animation.loop_count;
|
||||
anim_config.minimize_size = 0;
|
||||
|
||||
for(size_t i = 0; i < params.size(); i += 2)
|
||||
{
|
||||
const int value = params[i+1];
|
||||
if (params[i] == IMWRITE_WEBP_QUALITY)
|
||||
{
|
||||
config.lossless = 0; // false
|
||||
config.quality = static_cast<float>(value);
|
||||
if (config.quality < 1.0f)
|
||||
{
|
||||
config.quality = 1.0f;
|
||||
CV_LOG_WARNING(nullptr, cv::format("The value(%d) for IMWRITE_WEBP_QUALITY must be between 1 to 100(lossy) or more(lossless). It is fallbacked to 1", value));
|
||||
}
|
||||
if (config.quality > 100.0f)
|
||||
{
|
||||
config.quality = 100.0f;
|
||||
config.lossless = 1; // true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<WebPAnimEncoder, void (*)(WebPAnimEncoder*)> anim_encoder(
|
||||
WebPAnimEncoderNew(width, height, &anim_config), WebPAnimEncoderDelete);
|
||||
|
||||
pic.width = width;
|
||||
pic.height = height;
|
||||
pic.use_argb = 1;
|
||||
pic.argb_stride = width;
|
||||
|
||||
bool is_input_rgba = animation.frames[0].channels() == 4;
|
||||
Size canvas_size = Size(animation.frames[0].cols,animation.frames[0].rows);
|
||||
|
||||
for (size_t i = 0; i < animation.frames.size(); i++)
|
||||
{
|
||||
Mat argb;
|
||||
CV_Assert(canvas_size == Size(animation.frames[i].cols,animation.frames[i].rows));
|
||||
if (is_input_rgba)
|
||||
pic.argb = (uint32_t*)animation.frames[i].data;
|
||||
else
|
||||
{
|
||||
cvtColor(animation.frames[i], argb, COLOR_BGR2BGRA);
|
||||
pic.argb = (uint32_t*)argb.data;
|
||||
}
|
||||
ok = WebPAnimEncoderAdd(anim_encoder.get(), &pic, timestamp, &config);
|
||||
timestamp += animation.durations[i];
|
||||
}
|
||||
|
||||
// add a last fake frame to signal the last duration
|
||||
ok = ok & WebPAnimEncoderAdd(anim_encoder.get(), NULL, timestamp, NULL);
|
||||
ok = ok & WebPAnimEncoderAssemble(anim_encoder.get(), &webp_data);
|
||||
|
||||
size_t bytes_written = 0;
|
||||
if (ok)
|
||||
{
|
||||
if (m_buf)
|
||||
{
|
||||
m_buf->resize(webp_data.size);
|
||||
memcpy(&(*m_buf)[0], webp_data.bytes, webp_data.size);
|
||||
bytes_written = webp_data.size;
|
||||
}
|
||||
else
|
||||
{
|
||||
FILE* fd = fopen(m_filename.c_str(), "wb");
|
||||
if (fd != NULL)
|
||||
{
|
||||
bytes_written = fwrite(webp_data.bytes, sizeof(uint8_t), webp_data.size, fd);
|
||||
if (webp_data.size != bytes_written)
|
||||
{
|
||||
CV_LOG_ERROR(NULL, cv::format("Only %zu or %zu bytes are written\n",bytes_written, webp_data.size));
|
||||
}
|
||||
fclose(fd); fd = NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool status = (ok > 0) && (webp_data.size == bytes_written);
|
||||
|
||||
// free resources
|
||||
WebPDataClear(&webp_data);
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,102 @@
|
||||
/*M///////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
|
||||
//
|
||||
// By downloading, copying, installing or using the software you agree to this license.
|
||||
// If you do not agree to this license, do not download, install,
|
||||
// copy or use the software.
|
||||
//
|
||||
//
|
||||
// License Agreement
|
||||
// For Open Source Computer Vision Library
|
||||
//
|
||||
// Copyright (C) 2000-2008, Intel Corporation, all rights reserved.
|
||||
// Copyright (C) 2009, Willow Garage Inc., all rights reserved.
|
||||
// Third party copyrights are property of their respective owners.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without modification,
|
||||
// are permitted provided that the following conditions are met:
|
||||
//
|
||||
// * Redistribution's of source code must retain the above copyright notice,
|
||||
// this list of conditions and the following disclaimer.
|
||||
//
|
||||
// * Redistribution's in binary form must reproduce the above copyright notice,
|
||||
// this list of conditions and the following disclaimer in the documentation
|
||||
// and/or other materials provided with the distribution.
|
||||
//
|
||||
// * The name of the copyright holders may not be used to endorse or promote products
|
||||
// derived from this software without specific prior written permission.
|
||||
//
|
||||
// This software is provided by the copyright holders and contributors "as is" and
|
||||
// any express or implied warranties, including, but not limited to, the implied
|
||||
// warranties of merchantability and fitness for a particular purpose are disclaimed.
|
||||
// In no event shall the Intel Corporation or contributors be liable for any direct,
|
||||
// indirect, incidental, special, exemplary, or consequential damages
|
||||
// (including, but not limited to, procurement of substitute goods or services;
|
||||
// loss of use, data, or profits; or business interruption) however caused
|
||||
// and on any theory of liability, whether in contract, strict liability,
|
||||
// or tort (including negligence or otherwise) arising in any way out of
|
||||
// the use of this software, even if advised of the possibility of such damage.
|
||||
//
|
||||
//M*/
|
||||
|
||||
#ifndef _GRFMT_WEBP_H_
|
||||
#define _GRFMT_WEBP_H_
|
||||
|
||||
#include "grfmt_base.hpp"
|
||||
|
||||
#ifdef HAVE_WEBP
|
||||
|
||||
#include <fstream>
|
||||
|
||||
struct WebPAnimDecoder;
|
||||
|
||||
namespace cv
|
||||
{
|
||||
|
||||
class WebPDecoder CV_FINAL : public BaseImageDecoder
|
||||
{
|
||||
public:
|
||||
|
||||
WebPDecoder();
|
||||
~WebPDecoder() CV_OVERRIDE;
|
||||
|
||||
bool readData( Mat& img ) CV_OVERRIDE;
|
||||
bool readHeader() CV_OVERRIDE;
|
||||
bool nextPage() CV_OVERRIDE;
|
||||
|
||||
size_t signatureLength() const CV_OVERRIDE;
|
||||
bool checkSignature( const String& signature) const CV_OVERRIDE;
|
||||
|
||||
ImageDecoder newDecoder() const CV_OVERRIDE;
|
||||
|
||||
protected:
|
||||
struct UniquePtrDeleter {
|
||||
void operator()(WebPAnimDecoder* decoder) const;
|
||||
};
|
||||
|
||||
std::ifstream fs;
|
||||
size_t fs_size;
|
||||
Mat data;
|
||||
std::unique_ptr<WebPAnimDecoder, UniquePtrDeleter> anim_decoder;
|
||||
bool m_has_animation;
|
||||
int m_previous_timestamp;
|
||||
};
|
||||
|
||||
class WebPEncoder CV_FINAL : public BaseImageEncoder
|
||||
{
|
||||
public:
|
||||
WebPEncoder();
|
||||
~WebPEncoder() CV_OVERRIDE;
|
||||
|
||||
bool write(const Mat& img, const std::vector<int>& params) CV_OVERRIDE;
|
||||
bool writeanimation(const Animation& animation, const std::vector<int>& params) CV_OVERRIDE;
|
||||
|
||||
ImageEncoder newEncoder() const CV_OVERRIDE;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#endif /* _GRFMT_WEBP_H_ */
|
||||
@@ -0,0 +1,66 @@
|
||||
/*M///////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
|
||||
//
|
||||
// By downloading, copying, installing or using the software you agree to this license.
|
||||
// If you do not agree to this license, do not download, install,
|
||||
// copy or use the software.
|
||||
//
|
||||
//
|
||||
// Intel License Agreement
|
||||
// For Open Source Computer Vision Library
|
||||
//
|
||||
// Copyright (C) 2000, Intel Corporation, all rights reserved.
|
||||
// Third party copyrights are property of their respective owners.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without modification,
|
||||
// are permitted provided that the following conditions are met:
|
||||
//
|
||||
// * Redistribution's of source code must retain the above copyright notice,
|
||||
// this list of conditions and the following disclaimer.
|
||||
//
|
||||
// * Redistribution's in binary form must reproduce the above copyright notice,
|
||||
// this list of conditions and the following disclaimer in the documentation
|
||||
// and/or other materials provided with the distribution.
|
||||
//
|
||||
// * The name of Intel Corporation may not be used to endorse or promote products
|
||||
// derived from this software without specific prior written permission.
|
||||
//
|
||||
// This software is provided by the copyright holders and contributors "as is" and
|
||||
// any express or implied warranties, including, but not limited to, the implied
|
||||
// warranties of merchantability and fitness for a particular purpose are disclaimed.
|
||||
// In no event shall the Intel Corporation or contributors be liable for any direct,
|
||||
// indirect, incidental, special, exemplary, or consequential damages
|
||||
// (including, but not limited to, procurement of substitute goods or services;
|
||||
// loss of use, data, or profits; or business interruption) however caused
|
||||
// and on any theory of liability, whether in contract, strict liability,
|
||||
// or tort (including negligence or otherwise) arising in any way out of
|
||||
// the use of this software, even if advised of the possibility of such damage.
|
||||
//
|
||||
//M*/
|
||||
|
||||
#ifndef _GRFMTS_H_
|
||||
#define _GRFMTS_H_
|
||||
|
||||
#include "grfmt_base.hpp"
|
||||
#include "grfmt_avif.hpp"
|
||||
#include "grfmt_bmp.hpp"
|
||||
#include "grfmt_gif.hpp"
|
||||
#include "grfmt_sunras.hpp"
|
||||
#include "grfmt_jpeg.hpp"
|
||||
#include "grfmt_jpegxl.hpp"
|
||||
#include "grfmt_pxm.hpp"
|
||||
#include "grfmt_pfm.hpp"
|
||||
#include "grfmt_tiff.hpp"
|
||||
#include "grfmt_spng.hpp"
|
||||
#include "grfmt_png.hpp"
|
||||
#include "grfmt_jpeg2000.hpp"
|
||||
#include "grfmt_jpeg2000_openjpeg.hpp"
|
||||
#include "grfmt_exr.hpp"
|
||||
#include "grfmt_webp.hpp"
|
||||
#include "grfmt_hdr.hpp"
|
||||
#include "grfmt_gdal.hpp"
|
||||
#include "grfmt_gdcm.hpp"
|
||||
#include "grfmt_pam.hpp"
|
||||
|
||||
#endif/*_GRFMTS_H_*/
|
||||
@@ -0,0 +1,66 @@
|
||||
/*M///////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
|
||||
//
|
||||
// By downloading, copying, installing or using the software you agree to this license.
|
||||
// If you do not agree to this license, do not download, install,
|
||||
// copy or use the software.
|
||||
//
|
||||
//
|
||||
// License Agreement
|
||||
// For Open Source Computer Vision Library
|
||||
//
|
||||
// Copyright (C) 2000-2008, Intel Corporation, all rights reserved.
|
||||
// Copyright (C) 2009, Willow Garage Inc., all rights reserved.
|
||||
// Third party copyrights are property of their respective owners.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without modification,
|
||||
// are permitted provided that the following conditions are met:
|
||||
//
|
||||
// * Redistribution's of source code must retain the above copyright notice,
|
||||
// this list of conditions and the following disclaimer.
|
||||
//
|
||||
// * Redistribution's in binary form must reproduce the above copyright notice,
|
||||
// this list of conditions and the following disclaimer in the documentation
|
||||
// and/or other materials provided with the distribution.
|
||||
//
|
||||
// * The name of the copyright holders may not be used to endorse or promote products
|
||||
// derived from this software without specific prior written permission.
|
||||
//
|
||||
// This software is provided by the copyright holders and contributors "as is" and
|
||||
// any express or implied warranties, including, but not limited to, the implied
|
||||
// warranties of merchantability and fitness for a particular purpose are disclaimed.
|
||||
// In no event shall the Intel Corporation or contributors be liable for any direct,
|
||||
// indirect, incidental, special, exemplary, or consequential damages
|
||||
// (including, but not limited to, procurement of substitute goods or services;
|
||||
// loss of use, data, or profits; or business interruption) however caused
|
||||
// and on any theory of liability, whether in contract, strict liability,
|
||||
// or tort (including negligence or otherwise) arising in any way out of
|
||||
// the use of this software, even if advised of the possibility of such damage.
|
||||
//
|
||||
//M*/
|
||||
#include <TargetConditionals.h>
|
||||
#if (TARGET_OS_IOS || TARGET_OS_VISION) && !TARGET_OS_MACCATALYST
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#include "apple_conversions.h"
|
||||
|
||||
CV_EXPORTS UIImage* MatToUIImage(const cv::Mat& image);
|
||||
CV_EXPORTS void UIImageToMat(const UIImage* image, cv::Mat& m, bool alphaExist);
|
||||
|
||||
UIImage* MatToUIImage(const cv::Mat& image) {
|
||||
// Creating CGImage from cv::Mat
|
||||
CGImageRef imageRef = MatToCGImage(image);
|
||||
|
||||
// Getting UIImage from CGImage
|
||||
UIImage *uiImage = [UIImage imageWithCGImage:imageRef];
|
||||
CGImageRelease(imageRef);
|
||||
|
||||
return uiImage;
|
||||
}
|
||||
|
||||
void UIImageToMat(const UIImage* image, cv::Mat& m, bool alphaExist) {
|
||||
CGImageRef imageRef = image.CGImage;
|
||||
CGImageToMat(imageRef, m, alphaExist);
|
||||
}
|
||||
#endif
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,27 @@
|
||||
// This file is part of OpenCV project.
|
||||
// It is subject to the license terms in the LICENSE file found in the top-level directory
|
||||
// of this distribution and at http://opencv.org/license.html.
|
||||
|
||||
#include "apple_conversions.h"
|
||||
#import <AppKit/AppKit.h>
|
||||
|
||||
CV_EXPORTS NSImage* MatToNSImage(const cv::Mat& image);
|
||||
CV_EXPORTS void NSImageToMat(const NSImage* image, cv::Mat& m, bool alphaExist);
|
||||
|
||||
NSImage* MatToNSImage(const cv::Mat& image) {
|
||||
// Creating CGImage from cv::Mat
|
||||
CGImageRef imageRef = MatToCGImage(image);
|
||||
|
||||
// Getting NSImage from CGImage
|
||||
NSSize imageSize = NSMakeSize(CGImageGetWidth(imageRef), CGImageGetHeight(imageRef));
|
||||
NSImage *nsImage = [[NSImage alloc] initWithCGImage:imageRef size:imageSize];
|
||||
CGImageRelease(imageRef);
|
||||
|
||||
|
||||
return nsImage;
|
||||
}
|
||||
|
||||
void NSImageToMat(const NSImage* image, cv::Mat& m, bool alphaExist) {
|
||||
CGImageRef imageRef = [image CGImageForProposedRect:NULL context:NULL hints:NULL];
|
||||
CGImageToMat(imageRef, m, alphaExist);
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
/*M///////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
|
||||
//
|
||||
// By downloading, copying, installing or using the software you agree to this license.
|
||||
// If you do not agree to this license, do not download, install,
|
||||
// copy or use the software.
|
||||
//
|
||||
//
|
||||
// Intel License Agreement
|
||||
// For Open Source Computer Vision Library
|
||||
//
|
||||
// Copyright (C) 2000, Intel Corporation, all rights reserved.
|
||||
// Third party copyrights are property of their respective owners.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without modification,
|
||||
// are permitted provided that the following conditions are met:
|
||||
//
|
||||
// * Redistribution's of source code must retain the above copyright notice,
|
||||
// this list of conditions and the following disclaimer.
|
||||
//
|
||||
// * Redistribution's in binary form must reproduce the above copyright notice,
|
||||
// this list of conditions and the following disclaimer in the documentation
|
||||
// and/or other materials provided with the distribution.
|
||||
//
|
||||
// * The name of Intel Corporation may not be used to endorse or promote products
|
||||
// derived from this software without specific prior written permission.
|
||||
//
|
||||
// This software is provided by the copyright holders and contributors "as is" and
|
||||
// any express or implied warranties, including, but not limited to, the implied
|
||||
// warranties of merchantability and fitness for a particular purpose are disclaimed.
|
||||
// In no event shall the Intel Corporation or contributors be liable for any direct,
|
||||
// indirect, incidental, special, exemplary, or consequential damages
|
||||
// (including, but not limited to, procurement of substitute goods or services;
|
||||
// loss of use, data, or profits; or business interruption) however caused
|
||||
// and on any theory of liability, whether in contract, strict liability,
|
||||
// or tort (including negligence or otherwise) arising in any way out of
|
||||
// the use of this software, even if advised of the possibility of such damage.
|
||||
//
|
||||
//M*/
|
||||
|
||||
#ifndef __IMGCODECS_H_
|
||||
#define __IMGCODECS_H_
|
||||
|
||||
#include "opencv2/imgcodecs.hpp"
|
||||
#include "opencv2/core/utility.hpp"
|
||||
#include "opencv2/core/private.hpp"
|
||||
#include "opencv2/imgproc.hpp"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <limits.h>
|
||||
#include <ctype.h>
|
||||
|
||||
#if defined _WIN32 || defined WINCE
|
||||
#include <windows.h>
|
||||
#undef small
|
||||
#undef min
|
||||
#undef max
|
||||
#undef abs
|
||||
#endif
|
||||
|
||||
#define __BEGIN__ __CV_BEGIN__
|
||||
#define __END__ __CV_END__
|
||||
#define EXIT __CV_EXIT__
|
||||
|
||||
#endif /* __IMGCODECS_H_ */
|
||||
@@ -0,0 +1,477 @@
|
||||
/*M///////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
|
||||
//
|
||||
// By downloading, copying, installing or using the software you agree to this license.
|
||||
// If you do not agree to this license, do not download, install,
|
||||
// copy or use the software.
|
||||
//
|
||||
//
|
||||
// License Agreement
|
||||
// For Open Source Computer Vision Library
|
||||
//
|
||||
// Copyright (C) 2000-2008, Intel Corporation, all rights reserved.
|
||||
// Copyright (C) 2009, Willow Garage Inc., all rights reserved.
|
||||
// Third party copyrights are property of their respective owners.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without modification,
|
||||
// are permitted provided that the following conditions are met:
|
||||
//
|
||||
// * Redistribution's of source code must retain the above copyright notice,
|
||||
// this list of conditions and the following disclaimer.
|
||||
//
|
||||
// * Redistribution's in binary form must reproduce the above copyright notice,
|
||||
// this list of conditions and the following disclaimer in the documentation
|
||||
// and/or other materials provided with the distribution.
|
||||
//
|
||||
// * The name of the copyright holders may not be used to endorse or promote products
|
||||
// derived from this software without specific prior written permission.
|
||||
//
|
||||
// This software is provided by the copyright holders and contributors "as is" and
|
||||
// any express or implied warranties, including, but not limited to, the implied
|
||||
// warranties of merchantability and fitness for a particular purpose are disclaimed.
|
||||
// In no event shall the Intel Corporation or contributors be liable for any direct,
|
||||
// indirect, incidental, special, exemplary, or consequential damages
|
||||
// (including, but not limited to, procurement of substitute goods or services;
|
||||
// loss of use, data, or profits; or business interruption) however caused
|
||||
// and on any theory of liability, whether in contract, strict liability,
|
||||
// or tort (including negligence or otherwise) arising in any way out of
|
||||
// the use of this software, even if advised of the possibility of such damage.
|
||||
//
|
||||
//M*/
|
||||
|
||||
#include "precomp.hpp"
|
||||
#include "rgbe.hpp"
|
||||
#include <math.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
|
||||
// This file contains code to read and write four byte rgbe file format
|
||||
// developed by Greg Ward. It handles the conversions between rgbe and
|
||||
// pixels consisting of floats. The data is assumed to be an array of floats.
|
||||
// By default there are three floats per pixel in the order red, green, blue.
|
||||
// (RGBE_DATA_??? values control this.) Only the minimal header reading and
|
||||
// writing is implemented. Each routine does error checking and will return
|
||||
// a status value as defined below. This code is intended as a skeleton so
|
||||
// feel free to modify it to suit your needs.
|
||||
|
||||
// Some opencv specific changes have been added:
|
||||
// inline define specified, error handler uses CV_Error,
|
||||
// defines changed to work in bgr color space.
|
||||
//
|
||||
// posted to http://www.graphics.cornell.edu/~bjw/
|
||||
// written by Bruce Walter (bjw@graphics.cornell.edu) 5/26/95
|
||||
// based on code written by Greg Ward
|
||||
|
||||
#define INLINE inline
|
||||
|
||||
/* offsets to red, green, and blue components in a data (float) pixel */
|
||||
#define RGBE_DATA_RED 2
|
||||
#define RGBE_DATA_GREEN 1
|
||||
#define RGBE_DATA_BLUE 0
|
||||
/* number of floats per pixel */
|
||||
#define RGBE_DATA_SIZE 3
|
||||
|
||||
enum rgbe_error_codes {
|
||||
rgbe_read_error,
|
||||
rgbe_write_error,
|
||||
rgbe_format_error,
|
||||
rgbe_memory_error
|
||||
};
|
||||
|
||||
/* default error routine. change this to change error handling */
|
||||
static int rgbe_error(int rgbe_error_code, const char *msg)
|
||||
{
|
||||
switch (rgbe_error_code) {
|
||||
case rgbe_read_error:
|
||||
CV_Error(cv::Error::StsError, "RGBE read error");
|
||||
break;
|
||||
case rgbe_write_error:
|
||||
CV_Error(cv::Error::StsError, "RGBE write error");
|
||||
break;
|
||||
case rgbe_format_error:
|
||||
CV_Error(cv::Error::StsError, cv::String("RGBE bad file format: ") +
|
||||
cv::String(msg));
|
||||
break;
|
||||
default:
|
||||
case rgbe_memory_error:
|
||||
CV_Error(cv::Error::StsError, cv::String("RGBE error: \n") +
|
||||
cv::String(msg));
|
||||
}
|
||||
}
|
||||
|
||||
/* standard conversion from float pixels to rgbe pixels */
|
||||
/* note: you can remove the "inline"s if your compiler complains about it */
|
||||
static INLINE void
|
||||
float2rgbe(unsigned char rgbe[4], float red, float green, float blue)
|
||||
{
|
||||
float v;
|
||||
int e;
|
||||
|
||||
v = red;
|
||||
if (green > v) v = green;
|
||||
if (blue > v) v = blue;
|
||||
if (v < 1e-32) {
|
||||
rgbe[0] = rgbe[1] = rgbe[2] = rgbe[3] = 0;
|
||||
}
|
||||
else {
|
||||
v = static_cast<float>(frexp(v,&e) * 256.0/v);
|
||||
rgbe[0] = (unsigned char) (red * v);
|
||||
rgbe[1] = (unsigned char) (green * v);
|
||||
rgbe[2] = (unsigned char) (blue * v);
|
||||
rgbe[3] = (unsigned char) (e + 128);
|
||||
}
|
||||
}
|
||||
|
||||
/* standard conversion from rgbe to float pixels */
|
||||
/* note: Ward uses ldexp(col+0.5,exp-(128+8)). However we wanted pixels */
|
||||
/* in the range [0,1] to map back into the range [0,1]. */
|
||||
static INLINE void
|
||||
rgbe2float(float *red, float *green, float *blue, unsigned char rgbe[4])
|
||||
{
|
||||
float f;
|
||||
|
||||
if (rgbe[3]) { /*nonzero pixel*/
|
||||
f = static_cast<float>(ldexp(1.0,rgbe[3]-(int)(128+8)));
|
||||
*red = rgbe[0] * f;
|
||||
*green = rgbe[1] * f;
|
||||
*blue = rgbe[2] * f;
|
||||
}
|
||||
else
|
||||
*red = *green = *blue = 0.0;
|
||||
}
|
||||
|
||||
/* default minimal header. modify if you want more information in header */
|
||||
int RGBE_WriteHeader(FILE *fp, int width, int height, rgbe_header_info *info)
|
||||
{
|
||||
const char *programtype = "RADIANCE";
|
||||
|
||||
if (info && (info->valid & RGBE_VALID_PROGRAMTYPE))
|
||||
programtype = info->programtype;
|
||||
if (fprintf(fp,"#?%s\n",programtype) < 0)
|
||||
return rgbe_error(rgbe_write_error,NULL);
|
||||
/* The #? is to identify file type, the programtype is optional. */
|
||||
if (info && (info->valid & RGBE_VALID_GAMMA)) {
|
||||
if (fprintf(fp,"GAMMA=%g\n",info->gamma) < 0)
|
||||
return rgbe_error(rgbe_write_error,NULL);
|
||||
}
|
||||
if (info && (info->valid & RGBE_VALID_EXPOSURE)) {
|
||||
if (fprintf(fp,"EXPOSURE=%g\n",info->exposure) < 0)
|
||||
return rgbe_error(rgbe_write_error,NULL);
|
||||
}
|
||||
if (fprintf(fp,"FORMAT=32-bit_rle_rgbe\n\n") < 0)
|
||||
return rgbe_error(rgbe_write_error,NULL);
|
||||
if (fprintf(fp, "-Y %d +X %d\n", height, width) < 0)
|
||||
return rgbe_error(rgbe_write_error,NULL);
|
||||
return RGBE_RETURN_SUCCESS;
|
||||
}
|
||||
|
||||
/* minimal header reading. modify if you want to parse more information */
|
||||
int RGBE_ReadHeader(FILE *fp, int *width, int *height, rgbe_header_info *info)
|
||||
{
|
||||
char buf[128];
|
||||
float tempf;
|
||||
int i;
|
||||
|
||||
if (info) {
|
||||
info->valid = 0;
|
||||
info->programtype[0] = 0;
|
||||
info->gamma = info->exposure = 1.0;
|
||||
}
|
||||
|
||||
// 1. read first line
|
||||
if (fgets(buf,sizeof(buf)/sizeof(buf[0]),fp) == NULL)
|
||||
return rgbe_error(rgbe_read_error,NULL);
|
||||
if ((buf[0] != '#')||(buf[1] != '?')) {
|
||||
/* if you want to require the magic token then uncomment the next line */
|
||||
/*return rgbe_error(rgbe_format_error,"bad initial token"); */
|
||||
}
|
||||
else if (info) {
|
||||
info->valid |= RGBE_VALID_PROGRAMTYPE;
|
||||
for(i=0;i<static_cast<int>(sizeof(info->programtype)-1);i++) {
|
||||
if ((buf[i+2] == 0) || isspace(buf[i+2]))
|
||||
break;
|
||||
info->programtype[i] = buf[i+2];
|
||||
}
|
||||
info->programtype[i] = 0;
|
||||
}
|
||||
|
||||
// 2. reading other header lines
|
||||
bool hasFormat = false;
|
||||
for(;;) {
|
||||
if (fgets(buf,sizeof(buf)/sizeof(buf[0]),fp) == 0)
|
||||
return rgbe_error(rgbe_read_error,NULL);
|
||||
if (buf[0] == '\n') // end of the header
|
||||
break;
|
||||
else if (buf[0] == '#') // comment
|
||||
continue;
|
||||
else if (strcmp(buf,"FORMAT=32-bit_rle_rgbe\n") == 0)
|
||||
hasFormat = true;
|
||||
else if (info && (sscanf(buf,"GAMMA=%g",&tempf) == 1)) {
|
||||
info->gamma = tempf;
|
||||
info->valid |= RGBE_VALID_GAMMA;
|
||||
}
|
||||
else if (info && (sscanf(buf,"EXPOSURE=%g",&tempf) == 1)) {
|
||||
info->exposure = tempf;
|
||||
info->valid |= RGBE_VALID_EXPOSURE;
|
||||
}
|
||||
}
|
||||
if (strcmp(buf,"\n") != 0)
|
||||
return rgbe_error(rgbe_format_error,
|
||||
"missing blank line after FORMAT specifier");
|
||||
if (!hasFormat)
|
||||
return rgbe_error(rgbe_format_error, "missing FORMAT specifier");
|
||||
|
||||
// 3. reading resolution string
|
||||
if (fgets(buf,sizeof(buf)/sizeof(buf[0]),fp) == 0)
|
||||
return rgbe_error(rgbe_read_error,NULL);
|
||||
if (sscanf(buf,"-Y %d +X %d",height,width) < 2)
|
||||
return rgbe_error(rgbe_format_error,"missing image size specifier");
|
||||
return RGBE_RETURN_SUCCESS;
|
||||
}
|
||||
|
||||
/* simple write routine that does not use run length encoding */
|
||||
/* These routines can be made faster by allocating a larger buffer and
|
||||
fread-ing and fwrite-ing the data in larger chunks */
|
||||
int RGBE_WritePixels(FILE *fp, float *data, int numpixels)
|
||||
{
|
||||
unsigned char rgbe[4];
|
||||
|
||||
while (numpixels-- > 0) {
|
||||
float2rgbe(rgbe,data[RGBE_DATA_RED],
|
||||
data[RGBE_DATA_GREEN],data[RGBE_DATA_BLUE]);
|
||||
data += RGBE_DATA_SIZE;
|
||||
if (fwrite(rgbe, sizeof(rgbe), 1, fp) < 1)
|
||||
return rgbe_error(rgbe_write_error,NULL);
|
||||
}
|
||||
return RGBE_RETURN_SUCCESS;
|
||||
}
|
||||
|
||||
/* simple read routine. will not correctly handle run length encoding */
|
||||
int RGBE_ReadPixels(FILE *fp, float *data, int numpixels)
|
||||
{
|
||||
unsigned char rgbe[4];
|
||||
|
||||
while(numpixels-- > 0) {
|
||||
if (fread(rgbe, sizeof(rgbe), 1, fp) < 1)
|
||||
return rgbe_error(rgbe_read_error,NULL);
|
||||
rgbe2float(&data[RGBE_DATA_RED],&data[RGBE_DATA_GREEN],
|
||||
&data[RGBE_DATA_BLUE],rgbe);
|
||||
data += RGBE_DATA_SIZE;
|
||||
}
|
||||
return RGBE_RETURN_SUCCESS;
|
||||
}
|
||||
|
||||
/* The code below is only needed for the run-length encoded files. */
|
||||
/* Run length encoding adds considerable complexity but does */
|
||||
/* save some space. For each scanline, each channel (r,g,b,e) is */
|
||||
/* encoded separately for better compression. */
|
||||
|
||||
static int RGBE_WriteBytes_RLE(FILE *fp, unsigned char *data, int numbytes)
|
||||
{
|
||||
#define MINRUNLENGTH 4
|
||||
int cur, beg_run, run_count, old_run_count, nonrun_count;
|
||||
unsigned char buf[2];
|
||||
|
||||
cur = 0;
|
||||
while(cur < numbytes) {
|
||||
beg_run = cur;
|
||||
/* find next run of length at least 4 if one exists */
|
||||
run_count = old_run_count = 0;
|
||||
while((run_count < MINRUNLENGTH) && (beg_run < numbytes)) {
|
||||
beg_run += run_count;
|
||||
old_run_count = run_count;
|
||||
run_count = 1;
|
||||
while( (beg_run + run_count < numbytes) && (run_count < 127)
|
||||
&& (data[beg_run] == data[beg_run + run_count]))
|
||||
run_count++;
|
||||
}
|
||||
/* if data before next big run is a short run then write it as such */
|
||||
if ((old_run_count > 1)&&(old_run_count == beg_run - cur)) {
|
||||
buf[0] = static_cast<unsigned char>(128 + old_run_count); /*write short run*/
|
||||
buf[1] = data[cur];
|
||||
if (fwrite(buf,sizeof(buf[0])*2,1,fp) < 1)
|
||||
return rgbe_error(rgbe_write_error,NULL);
|
||||
cur = beg_run;
|
||||
}
|
||||
/* write out bytes until we reach the start of the next run */
|
||||
while(cur < beg_run) {
|
||||
nonrun_count = beg_run - cur;
|
||||
if (nonrun_count > 128)
|
||||
nonrun_count = 128;
|
||||
buf[0] = static_cast<unsigned char>(nonrun_count);
|
||||
|
||||
if (fwrite(buf,sizeof(buf[0]),1,fp) < 1)
|
||||
return rgbe_error(rgbe_write_error,NULL);
|
||||
|
||||
if (fwrite(&data[cur],sizeof(data[0])*nonrun_count,1,fp) < 1)
|
||||
return rgbe_error(rgbe_write_error,NULL);
|
||||
cur += nonrun_count;
|
||||
}
|
||||
/* write out next run if one was found */
|
||||
if (run_count >= MINRUNLENGTH) {
|
||||
buf[0] = static_cast<unsigned char>(128 + run_count);
|
||||
buf[1] = data[beg_run];
|
||||
|
||||
if (fwrite(buf,sizeof(buf[0])*2,1,fp) < 1)
|
||||
return rgbe_error(rgbe_write_error,NULL);
|
||||
cur += run_count;
|
||||
}
|
||||
}
|
||||
return RGBE_RETURN_SUCCESS;
|
||||
#undef MINRUNLENGTH
|
||||
}
|
||||
|
||||
int RGBE_WritePixels_RLE(FILE *fp, float *data, int scanline_width,
|
||||
int num_scanlines)
|
||||
{
|
||||
unsigned char rgbe[4];
|
||||
unsigned char *buffer;
|
||||
int i, err;
|
||||
|
||||
if ((scanline_width < 8)||(scanline_width > 0x7fff))
|
||||
/* run length encoding is not allowed so write flat*/
|
||||
return RGBE_WritePixels(fp,data,scanline_width*num_scanlines);
|
||||
|
||||
buffer = (unsigned char *)malloc(sizeof(unsigned char)*4*scanline_width);
|
||||
if (buffer == NULL)
|
||||
/* no buffer space so write flat */
|
||||
return RGBE_WritePixels(fp,data,scanline_width*num_scanlines);
|
||||
|
||||
while(num_scanlines-- > 0) {
|
||||
rgbe[0] = 2;
|
||||
rgbe[1] = 2;
|
||||
rgbe[2] = static_cast<unsigned char>(scanline_width >> 8);
|
||||
rgbe[3] = scanline_width & 0xFF;
|
||||
|
||||
if (fwrite(rgbe, sizeof(rgbe), 1, fp) < 1) {
|
||||
free(buffer);
|
||||
return rgbe_error(rgbe_write_error,NULL);
|
||||
}
|
||||
|
||||
for(i=0;i<scanline_width;i++) {
|
||||
float2rgbe(rgbe,data[RGBE_DATA_RED],
|
||||
data[RGBE_DATA_GREEN],data[RGBE_DATA_BLUE]);
|
||||
buffer[i] = rgbe[0];
|
||||
buffer[i+scanline_width] = rgbe[1];
|
||||
buffer[i+2*scanline_width] = rgbe[2];
|
||||
buffer[i+3*scanline_width] = rgbe[3];
|
||||
data += RGBE_DATA_SIZE;
|
||||
}
|
||||
|
||||
/* write out each of the four channels separately run length encoded */
|
||||
/* first red, then green, then blue, then exponent */
|
||||
for(i=0;i<4;i++)
|
||||
{
|
||||
if ((err = RGBE_WriteBytes_RLE(fp,&buffer[i*scanline_width],
|
||||
scanline_width)) != RGBE_RETURN_SUCCESS)
|
||||
{
|
||||
free(buffer);
|
||||
return err;
|
||||
}
|
||||
}
|
||||
}
|
||||
free(buffer);
|
||||
return RGBE_RETURN_SUCCESS;
|
||||
}
|
||||
|
||||
int RGBE_ReadPixels_RLE(FILE *fp, float *data, int scanline_width,
|
||||
int num_scanlines)
|
||||
{
|
||||
unsigned char rgbe[4], *scanline_buffer, *ptr, *ptr_end;
|
||||
int i, count;
|
||||
unsigned char buf[2];
|
||||
|
||||
if ((scanline_width < 8)||(scanline_width > 0x7fff))
|
||||
/* run length encoding is not allowed so read flat*/
|
||||
return RGBE_ReadPixels(fp,data,scanline_width*num_scanlines);
|
||||
|
||||
scanline_buffer = NULL;
|
||||
/* read in each successive scanline */
|
||||
while(num_scanlines > 0) {
|
||||
if (fread(rgbe,sizeof(rgbe),1,fp) < 1) {
|
||||
free(scanline_buffer);
|
||||
return rgbe_error(rgbe_read_error,NULL);
|
||||
}
|
||||
|
||||
if ((rgbe[0] != 2)||(rgbe[1] != 2)||(rgbe[2] & 0x80)) {
|
||||
/* this file is not run length encoded */
|
||||
rgbe2float(&data[RGBE_DATA_RED],&data[RGBE_DATA_GREEN],&data[RGBE_DATA_BLUE],rgbe);
|
||||
data += RGBE_DATA_SIZE;
|
||||
free(scanline_buffer);
|
||||
return RGBE_ReadPixels(fp,data,scanline_width*num_scanlines-1);
|
||||
}
|
||||
|
||||
if ((((int)rgbe[2])<<8 | rgbe[3]) != scanline_width) {
|
||||
free(scanline_buffer);
|
||||
return rgbe_error(rgbe_format_error,"wrong scanline width");
|
||||
}
|
||||
|
||||
if (scanline_buffer == NULL)
|
||||
scanline_buffer = (unsigned char *)malloc(sizeof(unsigned char)*4*scanline_width);
|
||||
|
||||
if (scanline_buffer == NULL)
|
||||
return rgbe_error(rgbe_memory_error,"unable to allocate buffer space");
|
||||
|
||||
ptr = &scanline_buffer[0];
|
||||
/* read each of the four channels for the scanline into the buffer */
|
||||
for(i=0;i<4;i++) {
|
||||
ptr_end = &scanline_buffer[(i+1)*scanline_width];
|
||||
|
||||
while(ptr < ptr_end)
|
||||
{
|
||||
if (fread(buf,sizeof(buf[0])*2,1,fp) < 1)
|
||||
{
|
||||
free(scanline_buffer);
|
||||
return rgbe_error(rgbe_read_error,NULL);
|
||||
}
|
||||
|
||||
if (buf[0] > 128)
|
||||
{
|
||||
/* a run of the same value */
|
||||
count = buf[0]-128;
|
||||
if ((count == 0)||(count > ptr_end - ptr)) {
|
||||
free(scanline_buffer);
|
||||
return rgbe_error(rgbe_format_error,"bad scanline data");
|
||||
}
|
||||
while(count-- > 0)
|
||||
*ptr++ = buf[1];
|
||||
}
|
||||
else
|
||||
{
|
||||
/* a non-run */
|
||||
count = buf[0];
|
||||
if ((count == 0)||(count > ptr_end - ptr))
|
||||
{
|
||||
free(scanline_buffer);
|
||||
return rgbe_error(rgbe_format_error,"bad scanline data");
|
||||
}
|
||||
|
||||
*ptr++ = buf[1];
|
||||
if (--count > 0) {
|
||||
if (fread(ptr,sizeof(*ptr)*count,1,fp) < 1)
|
||||
{
|
||||
free(scanline_buffer);
|
||||
return rgbe_error(rgbe_read_error,NULL);
|
||||
}
|
||||
ptr += count;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* now convert data from buffer into floats */
|
||||
for(i=0;i<scanline_width;i++) {
|
||||
rgbe[0] = scanline_buffer[i];
|
||||
rgbe[1] = scanline_buffer[i+scanline_width];
|
||||
rgbe[2] = scanline_buffer[i+2*scanline_width];
|
||||
rgbe[3] = scanline_buffer[i+3*scanline_width];
|
||||
rgbe2float(&data[RGBE_DATA_RED],&data[RGBE_DATA_GREEN],&data[RGBE_DATA_BLUE],rgbe);
|
||||
data += RGBE_DATA_SIZE;
|
||||
}
|
||||
num_scanlines--;
|
||||
}
|
||||
free(scanline_buffer);
|
||||
return RGBE_RETURN_SUCCESS;
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
/*M///////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
|
||||
//
|
||||
// By downloading, copying, installing or using the software you agree to this license.
|
||||
// If you do not agree to this license, do not download, install,
|
||||
// copy or use the software.
|
||||
//
|
||||
//
|
||||
// License Agreement
|
||||
// For Open Source Computer Vision Library
|
||||
//
|
||||
// Copyright (C) 2000-2008, Intel Corporation, all rights reserved.
|
||||
// Copyright (C) 2009, Willow Garage Inc., all rights reserved.
|
||||
// Third party copyrights are property of their respective owners.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without modification,
|
||||
// are permitted provided that the following conditions are met:
|
||||
//
|
||||
// * Redistribution's of source code must retain the above copyright notice,
|
||||
// this list of conditions and the following disclaimer.
|
||||
//
|
||||
// * Redistribution's in binary form must reproduce the above copyright notice,
|
||||
// this list of conditions and the following disclaimer in the documentation
|
||||
// and/or other materials provided with the distribution.
|
||||
//
|
||||
// * The name of the copyright holders may not be used to endorse or promote products
|
||||
// derived from this software without specific prior written permission.
|
||||
//
|
||||
// This software is provided by the copyright holders and contributors "as is" and
|
||||
// any express or implied warranties, including, but not limited to, the implied
|
||||
// warranties of merchantability and fitness for a particular purpose are disclaimed.
|
||||
// In no event shall the Intel Corporation or contributors be liable for any direct,
|
||||
// indirect, incidental, special, exemplary, or consequential damages
|
||||
// (including, but not limited to, procurement of substitute goods or services;
|
||||
// loss of use, data, or profits; or business interruption) however caused
|
||||
// and on any theory of liability, whether in contract, strict liability,
|
||||
// or tort (including negligence or otherwise) arising in any way out of
|
||||
// the use of this software, even if advised of the possibility of such damage.
|
||||
//
|
||||
//M*/
|
||||
|
||||
#ifndef _RGBE_HDR_H_
|
||||
#define _RGBE_HDR_H_
|
||||
|
||||
// posted to http://www.graphics.cornell.edu/~bjw/
|
||||
// written by Bruce Walter (bjw@graphics.cornell.edu) 5/26/95
|
||||
// based on code written by Greg Ward
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
typedef struct {
|
||||
int valid; /* indicate which fields are valid */
|
||||
char programtype[16]; /* listed at beginning of file to identify it
|
||||
* after "#?". defaults to "RGBE" */
|
||||
float gamma; /* image has already been gamma corrected with
|
||||
* given gamma. defaults to 1.0 (no correction) */
|
||||
float exposure; /* a value of 1.0 in an image corresponds to
|
||||
* <exposure> watts/steradian/m^2.
|
||||
* defaults to 1.0 */
|
||||
} rgbe_header_info;
|
||||
|
||||
/* flags indicating which fields in an rgbe_header_info are valid */
|
||||
#define RGBE_VALID_PROGRAMTYPE 0x01
|
||||
#define RGBE_VALID_GAMMA 0x02
|
||||
#define RGBE_VALID_EXPOSURE 0x04
|
||||
|
||||
/* return codes for rgbe routines */
|
||||
#define RGBE_RETURN_SUCCESS 0
|
||||
#define RGBE_RETURN_FAILURE -1
|
||||
|
||||
/* read or write headers */
|
||||
/* you may set rgbe_header_info to null if you want to */
|
||||
int RGBE_WriteHeader(FILE *fp, int width, int height, rgbe_header_info *info);
|
||||
int RGBE_ReadHeader(FILE *fp, int *width, int *height, rgbe_header_info *info);
|
||||
|
||||
/* read or write pixels */
|
||||
/* can read or write pixels in chunks of any size including single pixels*/
|
||||
int RGBE_WritePixels(FILE *fp, float *data, int numpixels);
|
||||
int RGBE_ReadPixels(FILE *fp, float *data, int numpixels);
|
||||
|
||||
/* read or write run length encoded files */
|
||||
/* must be called to read or write whole scanlines */
|
||||
int RGBE_WritePixels_RLE(FILE *fp, float *data, int scanline_width,
|
||||
int num_scanlines);
|
||||
int RGBE_ReadPixels_RLE(FILE *fp, float *data, int scanline_width,
|
||||
int num_scanlines);
|
||||
|
||||
#endif/*_RGBE_HDR_H_*/
|
||||
@@ -0,0 +1,659 @@
|
||||
/*M///////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
|
||||
//
|
||||
// By downloading, copying, installing or using the software you agree to this license.
|
||||
// If you do not agree to this license, do not download, install,
|
||||
// copy or use the software.
|
||||
//
|
||||
//
|
||||
// Intel License Agreement
|
||||
// For Open Source Computer Vision Library
|
||||
//
|
||||
// Copyright (C) 2000, Intel Corporation, all rights reserved.
|
||||
// Third party copyrights are property of their respective owners.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without modification,
|
||||
// are permitted provided that the following conditions are met:
|
||||
//
|
||||
// * Redistribution's of source code must retain the above copyright notice,
|
||||
// this list of conditions and the following disclaimer.
|
||||
//
|
||||
// * Redistribution's in binary form must reproduce the above copyright notice,
|
||||
// this list of conditions and the following disclaimer in the documentation
|
||||
// and/or other materials provided with the distribution.
|
||||
//
|
||||
// * The name of Intel Corporation may not be used to endorse or promote products
|
||||
// derived from this software without specific prior written permission.
|
||||
//
|
||||
// This software is provided by the copyright holders and contributors "as is" and
|
||||
// any express or implied warranties, including, but not limited to, the implied
|
||||
// warranties of merchantability and fitness for a particular purpose are disclaimed.
|
||||
// In no event shall the Intel Corporation or contributors be liable for any direct,
|
||||
// indirect, incidental, special, exemplary, or consequential damages
|
||||
// (including, but not limited to, procurement of substitute goods or services;
|
||||
// loss of use, data, or profits; or business interruption) however caused
|
||||
// and on any theory of liability, whether in contract, strict liability,
|
||||
// or tort (including negligence or otherwise) arising in any way out of
|
||||
// the use of this software, even if advised of the possibility of such damage.
|
||||
//
|
||||
//M*/
|
||||
|
||||
#include "precomp.hpp"
|
||||
#include "utils.hpp"
|
||||
|
||||
namespace cv {
|
||||
|
||||
int validateToInt(size_t sz)
|
||||
{
|
||||
int valueInt = (int)sz;
|
||||
CV_Assert((size_t)valueInt == sz);
|
||||
return valueInt;
|
||||
}
|
||||
|
||||
RowPitchParams calculateRowPitch(int width, int bpp, int alignment, const char* format_name)
|
||||
{
|
||||
CV_Assert(width > 0 && bpp > 0 && alignment > 0);
|
||||
CV_Assert((alignment & (alignment - 1)) == 0); // must be power of 2
|
||||
|
||||
const size_t bits_per_row = static_cast<size_t>(width) * static_cast<size_t>(bpp);
|
||||
const size_t bytes_per_row = (bits_per_row + 7) / 8;
|
||||
const size_t aligned_pitch = (bytes_per_row + alignment - 1) & ~static_cast<size_t>(alignment - 1);
|
||||
|
||||
if (aligned_pitch >= MAX_IMAGE_ROW_SIZE)
|
||||
CV_Error(cv::Error::StsOutOfRange,
|
||||
cv::format("%s: src_pitch exceeds maximum allowed size", format_name));
|
||||
|
||||
RowPitchParams result;
|
||||
result.src_pitch = validateToInt(aligned_pitch);
|
||||
result.bytes_per_row = bytes_per_row;
|
||||
return result;
|
||||
}
|
||||
|
||||
int calculateRowSize(int width, int nch, const char* format_name)
|
||||
{
|
||||
CV_Assert(width > 0 && nch > 0);
|
||||
|
||||
const size_t row_size = static_cast<size_t>(width) * static_cast<size_t>(nch);
|
||||
if (row_size >= MAX_IMAGE_ROW_SIZE)
|
||||
CV_Error(cv::Error::StsOutOfRange,
|
||||
cv::format("%s: row size exceeds maximum allowed size", format_name));
|
||||
|
||||
return validateToInt(row_size);
|
||||
}
|
||||
|
||||
#define SCALE 14
|
||||
#define cR (int)(0.299*(1 << SCALE) + 0.5)
|
||||
#define cG (int)(0.587*(1 << SCALE) + 0.5)
|
||||
#define cB ((1 << SCALE) - cR - cG)
|
||||
|
||||
void icvCvt_BGR2Gray_8u_C3C1R( const uchar* bgr, int bgr_step,
|
||||
uchar* gray, int gray_step,
|
||||
Size size, int _swap_rb )
|
||||
{
|
||||
int i;
|
||||
for( ; size.height--; gray += gray_step )
|
||||
{
|
||||
short cBGR0 = cB;
|
||||
short cBGR2 = cR;
|
||||
if (_swap_rb) std::swap(cBGR0, cBGR2);
|
||||
for( i = 0; i < size.width; i++, bgr += 3 )
|
||||
{
|
||||
int t = descale( bgr[0]*cBGR0 + bgr[1]*cG + bgr[2]*cBGR2, SCALE );
|
||||
gray[i] = (uchar)t;
|
||||
}
|
||||
|
||||
bgr += bgr_step - size.width*3;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void icvCvt_BGRA2Gray_16u_CnC1R( const ushort* bgr, int bgr_step,
|
||||
ushort* gray, int gray_step,
|
||||
Size size, int ncn, int _swap_rb )
|
||||
{
|
||||
int i;
|
||||
for( ; size.height--; gray += gray_step )
|
||||
{
|
||||
short cBGR0 = cB;
|
||||
short cBGR2 = cR;
|
||||
if (_swap_rb) std::swap(cBGR0, cBGR2);
|
||||
for( i = 0; i < size.width; i++, bgr += ncn )
|
||||
{
|
||||
int t = descale( bgr[0]*cBGR0 + bgr[1]*cG + bgr[2]*cBGR2, SCALE );
|
||||
gray[i] = (ushort)t;
|
||||
}
|
||||
|
||||
bgr += bgr_step - size.width*ncn;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void icvCvt_BGRA2Gray_8u_C4C1R( const uchar* bgra, int rgba_step,
|
||||
uchar* gray, int gray_step,
|
||||
Size size, int _swap_rb )
|
||||
{
|
||||
int i;
|
||||
for( ; size.height--; gray += gray_step )
|
||||
{
|
||||
short cBGR0 = cB;
|
||||
short cBGR2 = cR;
|
||||
if (_swap_rb) std::swap(cBGR0, cBGR2);
|
||||
for( i = 0; i < size.width; i++, bgra += 4 )
|
||||
{
|
||||
int t = descale( bgra[0]*cBGR0 + bgra[1]*cG + bgra[2]*cBGR2, SCALE );
|
||||
gray[i] = (uchar)t;
|
||||
}
|
||||
|
||||
bgra += rgba_step - size.width*4;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void icvCvt_Gray2BGR_8u_C1C3R( const uchar* gray, int gray_step,
|
||||
uchar* bgr, int bgr_step, Size size )
|
||||
{
|
||||
int i;
|
||||
for( ; size.height--; gray += gray_step )
|
||||
{
|
||||
for( i = 0; i < size.width; i++, bgr += 3 )
|
||||
{
|
||||
bgr[0] = bgr[1] = bgr[2] = gray[i];
|
||||
}
|
||||
bgr += bgr_step - size.width*3;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void icvCvt_Gray2BGR_16u_C1C3R( const ushort* gray, int gray_step,
|
||||
ushort* bgr, int bgr_step, Size size )
|
||||
{
|
||||
int i;
|
||||
for( ; size.height--; gray += gray_step/sizeof(gray[0]) )
|
||||
{
|
||||
for( i = 0; i < size.width; i++, bgr += 3 )
|
||||
{
|
||||
bgr[0] = bgr[1] = bgr[2] = gray[i];
|
||||
}
|
||||
bgr += bgr_step/sizeof(bgr[0]) - size.width*3;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void icvCvt_BGRA2BGR_8u_C4C3R( const uchar* bgra, int bgra_step,
|
||||
uchar* bgr, int bgr_step,
|
||||
Size size, int _swap_rb )
|
||||
{
|
||||
int i;
|
||||
int swap_rb = _swap_rb ? 2 : 0;
|
||||
for( ; size.height--; )
|
||||
{
|
||||
for( i = 0; i < size.width; i++, bgr += 3, bgra += 4 )
|
||||
{
|
||||
uchar t0 = bgra[swap_rb], t1 = bgra[1];
|
||||
bgr[0] = t0; bgr[1] = t1;
|
||||
t0 = bgra[swap_rb^2]; bgr[2] = t0;
|
||||
}
|
||||
bgr += bgr_step - size.width*3;
|
||||
bgra += bgra_step - size.width*4;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void icvCvt_BGRA2BGR_16u_C4C3R( const ushort* bgra, int bgra_step,
|
||||
ushort* bgr, int bgr_step,
|
||||
Size size, int _swap_rb )
|
||||
{
|
||||
int i;
|
||||
int swap_rb = _swap_rb ? 2 : 0;
|
||||
for( ; size.height--; )
|
||||
{
|
||||
for( i = 0; i < size.width; i++, bgr += 3, bgra += 4 )
|
||||
{
|
||||
ushort t0 = bgra[swap_rb], t1 = bgra[1];
|
||||
bgr[0] = t0; bgr[1] = t1;
|
||||
t0 = bgra[swap_rb^2]; bgr[2] = t0;
|
||||
}
|
||||
bgr += bgr_step/sizeof(bgr[0]) - size.width*3;
|
||||
bgra += bgra_step/sizeof(bgra[0]) - size.width*4;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void icvCvt_BGRA2RGBA_8u_C4R( const uchar* bgra, int bgra_step,
|
||||
uchar* rgba, int rgba_step, Size size )
|
||||
{
|
||||
int i;
|
||||
for( ; size.height--; )
|
||||
{
|
||||
for( i = 0; i < size.width; i++, bgra += 4, rgba += 4 )
|
||||
{
|
||||
uchar t0 = bgra[0], t1 = bgra[1];
|
||||
uchar t2 = bgra[2], t3 = bgra[3];
|
||||
rgba[0] = t2; rgba[1] = t1;
|
||||
rgba[2] = t0; rgba[3] = t3;
|
||||
}
|
||||
bgra += bgra_step - size.width*4;
|
||||
rgba += rgba_step - size.width*4;
|
||||
}
|
||||
}
|
||||
|
||||
void icvCvt_BGRA2RGBA_16u_C4R( const ushort* bgra, int bgra_step,
|
||||
ushort* rgba, int rgba_step, Size size )
|
||||
{
|
||||
int i;
|
||||
for( ; size.height--; )
|
||||
{
|
||||
for( i = 0; i < size.width; i++, bgra += 4, rgba += 4 )
|
||||
{
|
||||
ushort t0 = bgra[0], t1 = bgra[1];
|
||||
ushort t2 = bgra[2], t3 = bgra[3];
|
||||
|
||||
rgba[0] = t2; rgba[1] = t1;
|
||||
rgba[2] = t0; rgba[3] = t3;
|
||||
}
|
||||
bgra += bgra_step/sizeof(bgra[0]) - size.width*4;
|
||||
rgba += rgba_step/sizeof(rgba[0]) - size.width*4;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void icvCvt_BGR2RGB_8u_C3R( const uchar* bgr, int bgr_step,
|
||||
uchar* rgb, int rgb_step, Size size )
|
||||
{
|
||||
int i;
|
||||
for( ; size.height--; )
|
||||
{
|
||||
for( i = 0; i < size.width; i++, bgr += 3, rgb += 3 )
|
||||
{
|
||||
uchar t0 = bgr[0], t1 = bgr[1], t2 = bgr[2];
|
||||
rgb[2] = t0; rgb[1] = t1; rgb[0] = t2;
|
||||
}
|
||||
bgr += bgr_step - size.width*3;
|
||||
rgb += rgb_step - size.width*3;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void icvCvt_BGR2RGB_16u_C3R( const ushort* bgr, int bgr_step,
|
||||
ushort* rgb, int rgb_step, Size size )
|
||||
{
|
||||
int i;
|
||||
for( ; size.height--; )
|
||||
{
|
||||
for( i = 0; i < size.width; i++, bgr += 3, rgb += 3 )
|
||||
{
|
||||
ushort t0 = bgr[0], t1 = bgr[1], t2 = bgr[2];
|
||||
rgb[2] = t0; rgb[1] = t1; rgb[0] = t2;
|
||||
}
|
||||
bgr += bgr_step - size.width*3;
|
||||
rgb += rgb_step - size.width*3;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
typedef unsigned short ushort;
|
||||
|
||||
void icvCvt_BGR5552Gray_8u_C2C1R( const uchar* bgr555, int bgr555_step,
|
||||
uchar* gray, int gray_step, Size size )
|
||||
{
|
||||
int i;
|
||||
for( ; size.height--; gray += gray_step, bgr555 += bgr555_step )
|
||||
{
|
||||
for( i = 0; i < size.width; i++ )
|
||||
{
|
||||
int t = descale( ((((ushort*)bgr555)[i] << 3) & 0xf8)*cB +
|
||||
((((ushort*)bgr555)[i] >> 2) & 0xf8)*cG +
|
||||
((((ushort*)bgr555)[i] >> 7) & 0xf8)*cR, SCALE );
|
||||
gray[i] = (uchar)t;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void icvCvt_BGR5652Gray_8u_C2C1R( const uchar* bgr565, int bgr565_step,
|
||||
uchar* gray, int gray_step, Size size )
|
||||
{
|
||||
int i;
|
||||
for( ; size.height--; gray += gray_step, bgr565 += bgr565_step )
|
||||
{
|
||||
for( i = 0; i < size.width; i++ )
|
||||
{
|
||||
int t = descale( ((((ushort*)bgr565)[i] << 3) & 0xf8)*cB +
|
||||
((((ushort*)bgr565)[i] >> 3) & 0xfc)*cG +
|
||||
((((ushort*)bgr565)[i] >> 8) & 0xf8)*cR, SCALE );
|
||||
gray[i] = (uchar)t;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void icvCvt_BGR5552BGR_8u_C2C3R( const uchar* bgr555, int bgr555_step,
|
||||
uchar* bgr, int bgr_step, Size size )
|
||||
{
|
||||
int i;
|
||||
for( ; size.height--; bgr555 += bgr555_step )
|
||||
{
|
||||
for( i = 0; i < size.width; i++, bgr += 3 )
|
||||
{
|
||||
int t0 = (((ushort*)bgr555)[i] << 3) & 0xf8;
|
||||
int t1 = (((ushort*)bgr555)[i] >> 2) & 0xf8;
|
||||
int t2 = (((ushort*)bgr555)[i] >> 7) & 0xf8;
|
||||
bgr[0] = (uchar)t0; bgr[1] = (uchar)t1; bgr[2] = (uchar)t2;
|
||||
}
|
||||
bgr += bgr_step - size.width*3;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void icvCvt_BGR5652BGR_8u_C2C3R( const uchar* bgr565, int bgr565_step,
|
||||
uchar* bgr, int bgr_step, Size size )
|
||||
{
|
||||
int i;
|
||||
for( ; size.height--; bgr565 += bgr565_step )
|
||||
{
|
||||
for( i = 0; i < size.width; i++, bgr += 3 )
|
||||
{
|
||||
int t0 = (((ushort*)bgr565)[i] << 3) & 0xf8;
|
||||
int t1 = (((ushort*)bgr565)[i] >> 3) & 0xfc;
|
||||
int t2 = (((ushort*)bgr565)[i] >> 8) & 0xf8;
|
||||
bgr[0] = (uchar)t0; bgr[1] = (uchar)t1; bgr[2] = (uchar)t2;
|
||||
}
|
||||
bgr += bgr_step - size.width*3;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void icvCvt_CMYK2BGR_8u_C4C3R( const uchar* cmyk, int cmyk_step,
|
||||
uchar* bgr, int bgr_step, Size size )
|
||||
{
|
||||
int i;
|
||||
for( ; size.height--; )
|
||||
{
|
||||
for( i = 0; i < size.width; i++, bgr += 3, cmyk += 4 )
|
||||
{
|
||||
int c = cmyk[0], m = cmyk[1], y = cmyk[2], k = cmyk[3];
|
||||
c = k - ((255 - c)*k>>8);
|
||||
m = k - ((255 - m)*k>>8);
|
||||
y = k - ((255 - y)*k>>8);
|
||||
bgr[2] = (uchar)c; bgr[1] = (uchar)m; bgr[0] = (uchar)y;
|
||||
}
|
||||
bgr += bgr_step - size.width*3;
|
||||
cmyk += cmyk_step - size.width*4;
|
||||
}
|
||||
}
|
||||
|
||||
void icvCvt_CMYK2RGB_8u_C4C3R( const uchar* cmyk, int cmyk_step,
|
||||
uchar* rgb, int rgb_step, Size size )
|
||||
{
|
||||
int i;
|
||||
for( ; size.height--; )
|
||||
{
|
||||
for( i = 0; i < size.width; i++, rgb += 3, cmyk += 4 )
|
||||
{
|
||||
int c = cmyk[0], m = cmyk[1], y = cmyk[2], k = cmyk[3];
|
||||
c = k - ((255 - c)*k>>8);
|
||||
m = k - ((255 - m)*k>>8);
|
||||
y = k - ((255 - y)*k>>8);
|
||||
rgb[0] = (uchar)c; rgb[1] = (uchar)m; rgb[2] = (uchar)y;
|
||||
}
|
||||
rgb += rgb_step - size.width*3;
|
||||
cmyk += cmyk_step - size.width*4;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void icvCvt_CMYK2Gray_8u_C4C1R( const uchar* cmyk, int cmyk_step,
|
||||
uchar* gray, int gray_step, Size size )
|
||||
{
|
||||
int i;
|
||||
for( ; size.height--; )
|
||||
{
|
||||
for( i = 0; i < size.width; i++, cmyk += 4 )
|
||||
{
|
||||
int c = cmyk[0], m = cmyk[1], y = cmyk[2], k = cmyk[3];
|
||||
c = k - ((255 - c)*k>>8);
|
||||
m = k - ((255 - m)*k>>8);
|
||||
y = k - ((255 - y)*k>>8);
|
||||
int t = descale( y*cB + m*cG + c*cR, SCALE );
|
||||
gray[i] = (uchar)t;
|
||||
}
|
||||
gray += gray_step;
|
||||
cmyk += cmyk_step - size.width*4;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void CvtPaletteToGray( const PaletteEntry* palette, uchar* grayPalette, int entries )
|
||||
{
|
||||
int i;
|
||||
for( i = 0; i < entries; i++ )
|
||||
{
|
||||
icvCvt_BGR2Gray_8u_C3C1R( (uchar*)(palette + i), 0, grayPalette + i, 0, Size(1,1) );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void FillGrayPalette( PaletteEntry* palette, int bpp, bool negative )
|
||||
{
|
||||
int i, length = 1 << bpp;
|
||||
int xor_mask = negative ? 255 : 0;
|
||||
|
||||
for( i = 0; i < length; i++ )
|
||||
{
|
||||
int val = (i * 255/(length - 1)) ^ xor_mask;
|
||||
palette[i].b = palette[i].g = palette[i].r = (uchar)val;
|
||||
palette[i].a = 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool IsColorPalette( PaletteEntry* palette, int bpp )
|
||||
{
|
||||
int i, length = 1 << bpp;
|
||||
|
||||
for( i = 0; i < length; i++ )
|
||||
{
|
||||
if( palette[i].b != palette[i].g ||
|
||||
palette[i].b != palette[i].r )
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
uchar* FillUniColor( uchar* data, uchar*& line_end,
|
||||
int step, int width3,
|
||||
int& y, int height,
|
||||
ptrdiff_t count3, PaletteEntry clr )
|
||||
{
|
||||
do
|
||||
{
|
||||
uchar* end = data + count3;
|
||||
|
||||
if( end > line_end )
|
||||
end = line_end;
|
||||
|
||||
count3 -= end - data;
|
||||
|
||||
for( ; data < end; data += 3 )
|
||||
{
|
||||
WRITE_PIX( data, clr );
|
||||
}
|
||||
|
||||
if( data >= line_end )
|
||||
{
|
||||
line_end += step;
|
||||
data = line_end - width3;
|
||||
if( ++y >= height ) break;
|
||||
}
|
||||
}
|
||||
while( count3 > 0 );
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
|
||||
uchar* FillUniGray( uchar* data, uchar*& line_end,
|
||||
int step, int width,
|
||||
int& y, int height,
|
||||
ptrdiff_t count, uchar clr )
|
||||
{
|
||||
do
|
||||
{
|
||||
uchar* end = data + count;
|
||||
|
||||
if( end > line_end )
|
||||
end = line_end;
|
||||
|
||||
count -= end - data;
|
||||
|
||||
for( ; data < end; data++ )
|
||||
{
|
||||
*data = clr;
|
||||
}
|
||||
|
||||
if( data >= line_end )
|
||||
{
|
||||
line_end += step;
|
||||
data = line_end - width;
|
||||
if( ++y >= height ) break;
|
||||
}
|
||||
}
|
||||
while( count > 0 );
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
|
||||
uchar* FillColorRow8( uchar* data, uchar* indices, int len, PaletteEntry* palette )
|
||||
{
|
||||
uchar* end = data + len*3;
|
||||
while( (data += 3) < end )
|
||||
{
|
||||
*((PaletteEntry*)(data-3)) = palette[*indices++];
|
||||
}
|
||||
PaletteEntry clr = palette[indices[0]];
|
||||
WRITE_PIX( data - 3, clr );
|
||||
return data;
|
||||
}
|
||||
|
||||
|
||||
uchar* FillGrayRow8( uchar* data, uchar* indices, int len, uchar* palette )
|
||||
{
|
||||
int i;
|
||||
for( i = 0; i < len; i++ )
|
||||
{
|
||||
data[i] = palette[indices[i]];
|
||||
}
|
||||
return data + len;
|
||||
}
|
||||
|
||||
|
||||
uchar* FillColorRow4( uchar* data, uchar* indices, int len, PaletteEntry* palette )
|
||||
{
|
||||
uchar* end = data + len*3;
|
||||
|
||||
while( (data += 6) < end )
|
||||
{
|
||||
int idx = *indices++;
|
||||
*((PaletteEntry*)(data-6)) = palette[idx >> 4];
|
||||
*((PaletteEntry*)(data-3)) = palette[idx & 15];
|
||||
}
|
||||
|
||||
int idx = indices[0];
|
||||
PaletteEntry clr = palette[idx >> 4];
|
||||
WRITE_PIX( data - 6, clr );
|
||||
|
||||
if( data == end )
|
||||
{
|
||||
clr = palette[idx & 15];
|
||||
WRITE_PIX( data - 3, clr );
|
||||
}
|
||||
return end;
|
||||
}
|
||||
|
||||
|
||||
uchar* FillGrayRow4( uchar* data, uchar* indices, int len, uchar* palette )
|
||||
{
|
||||
uchar* end = data + len;
|
||||
while( (data += 2) < end )
|
||||
{
|
||||
int idx = *indices++;
|
||||
data[-2] = palette[idx >> 4];
|
||||
data[-1] = palette[idx & 15];
|
||||
}
|
||||
|
||||
int idx = indices[0];
|
||||
uchar clr = palette[idx >> 4];
|
||||
data[-2] = clr;
|
||||
|
||||
if( data == end )
|
||||
{
|
||||
clr = palette[idx & 15];
|
||||
data[-1] = clr;
|
||||
}
|
||||
return end;
|
||||
}
|
||||
|
||||
|
||||
uchar* FillColorRow1( uchar* data, uchar* indices, int len, PaletteEntry* palette )
|
||||
{
|
||||
uchar* end = data + len*3;
|
||||
|
||||
const PaletteEntry p0 = palette[0], p1 = palette[1];
|
||||
|
||||
while( (data += 24) < end )
|
||||
{
|
||||
int idx = *indices++;
|
||||
*((PaletteEntry*)(data - 24)) = (idx & 128) ? p1 : p0;
|
||||
*((PaletteEntry*)(data - 21)) = (idx & 64) ? p1 : p0;
|
||||
*((PaletteEntry*)(data - 18)) = (idx & 32) ? p1 : p0;
|
||||
*((PaletteEntry*)(data - 15)) = (idx & 16) ? p1 : p0;
|
||||
*((PaletteEntry*)(data - 12)) = (idx & 8) ? p1 : p0;
|
||||
*((PaletteEntry*)(data - 9)) = (idx & 4) ? p1 : p0;
|
||||
*((PaletteEntry*)(data - 6)) = (idx & 2) ? p1 : p0;
|
||||
*((PaletteEntry*)(data - 3)) = (idx & 1) ? p1 : p0;
|
||||
}
|
||||
|
||||
int idx = indices[0];
|
||||
for( data -= 24; data < end; data += 3, idx += idx )
|
||||
{
|
||||
const PaletteEntry clr = (idx & 128) ? p1 : p0;
|
||||
WRITE_PIX( data, clr );
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
|
||||
uchar* FillGrayRow1( uchar* data, uchar* indices, int len, uchar* palette )
|
||||
{
|
||||
uchar* end = data + len;
|
||||
|
||||
const uchar p0 = palette[0], p1 = palette[1];
|
||||
|
||||
while( (data += 8) < end )
|
||||
{
|
||||
int idx = *indices++;
|
||||
*((uchar*)(data - 8)) = (idx & 128) ? p1 : p0;
|
||||
*((uchar*)(data - 7)) = (idx & 64) ? p1 : p0;
|
||||
*((uchar*)(data - 6)) = (idx & 32) ? p1 : p0;
|
||||
*((uchar*)(data - 5)) = (idx & 16) ? p1 : p0;
|
||||
*((uchar*)(data - 4)) = (idx & 8) ? p1 : p0;
|
||||
*((uchar*)(data - 3)) = (idx & 4) ? p1 : p0;
|
||||
*((uchar*)(data - 2)) = (idx & 2) ? p1 : p0;
|
||||
*((uchar*)(data - 1)) = (idx & 1) ? p1 : p0;
|
||||
}
|
||||
|
||||
int idx = indices[0];
|
||||
for( data -= 8; data < end; data++, idx += idx )
|
||||
{
|
||||
data[0] = (idx & 128) ? p1 : p0;
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
@@ -0,0 +1,159 @@
|
||||
/*M///////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
|
||||
//
|
||||
// By downloading, copying, installing or using the software you agree to this license.
|
||||
// If you do not agree to this license, do not download, install,
|
||||
// copy or use the software.
|
||||
//
|
||||
//
|
||||
// Intel License Agreement
|
||||
// For Open Source Computer Vision Library
|
||||
//
|
||||
// Copyright (C) 2000, Intel Corporation, all rights reserved.
|
||||
// Third party copyrights are property of their respective owners.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without modification,
|
||||
// are permitted provided that the following conditions are met:
|
||||
//
|
||||
// * Redistribution's of source code must retain the above copyright notice,
|
||||
// this list of conditions and the following disclaimer.
|
||||
//
|
||||
// * Redistribution's in binary form must reproduce the above copyright notice,
|
||||
// this list of conditions and the following disclaimer in the documentation
|
||||
// and/or other materials provided with the distribution.
|
||||
//
|
||||
// * The name of Intel Corporation may not be used to endorse or promote products
|
||||
// derived from this software without specific prior written permission.
|
||||
//
|
||||
// This software is provided by the copyright holders and contributors "as is" and
|
||||
// any express or implied warranties, including, but not limited to, the implied
|
||||
// warranties of merchantability and fitness for a particular purpose are disclaimed.
|
||||
// In no event shall the Intel Corporation or contributors be liable for any direct,
|
||||
// indirect, incidental, special, exemplary, or consequential damages
|
||||
// (including, but not limited to, procurement of substitute goods or services;
|
||||
// loss of use, data, or profits; or business interruption) however caused
|
||||
// and on any theory of liability, whether in contract, strict liability,
|
||||
// or tort (including negligence or otherwise) arising in any way out of
|
||||
// the use of this software, even if advised of the possibility of such damage.
|
||||
//
|
||||
//M*/
|
||||
|
||||
#ifndef _UTILS_H_
|
||||
#define _UTILS_H_
|
||||
|
||||
namespace cv {
|
||||
|
||||
int validateToInt(size_t step);
|
||||
|
||||
template <typename _Tp> static inline
|
||||
size_t safeCastToSizeT(const _Tp v_origin, const char* msg)
|
||||
{
|
||||
const size_t value_cast = (size_t)v_origin;
|
||||
if ((_Tp)value_cast != v_origin)
|
||||
CV_Error(cv::Error::StsError, msg ? msg : "Can't cast value into size_t");
|
||||
return value_cast;
|
||||
}
|
||||
|
||||
struct PaletteEntry
|
||||
{
|
||||
unsigned char b, g, r, a;
|
||||
};
|
||||
|
||||
#define WRITE_PIX( ptr, clr ) \
|
||||
(((uchar*)(ptr))[0] = (clr).b, \
|
||||
((uchar*)(ptr))[1] = (clr).g, \
|
||||
((uchar*)(ptr))[2] = (clr).r)
|
||||
|
||||
#define descale(x,n) (((x) + (1 << ((n)-1))) >> (n))
|
||||
#define saturate(x) (uchar)(((x) & ~255) == 0 ? (x) : ~((x)>>31))
|
||||
|
||||
void icvCvt_BGR2Gray_8u_C3C1R( const uchar* bgr, int bgr_step,
|
||||
uchar* gray, int gray_step,
|
||||
Size size, int swap_rb=0 );
|
||||
void icvCvt_BGRA2Gray_8u_C4C1R( const uchar* bgra, int bgra_step,
|
||||
uchar* gray, int gray_step,
|
||||
Size size, int swap_rb=0 );
|
||||
void icvCvt_BGRA2Gray_16u_CnC1R( const ushort* bgra, int bgra_step,
|
||||
ushort* gray, int gray_step,
|
||||
Size size, int ncn, int swap_rb=0 );
|
||||
|
||||
void icvCvt_Gray2BGR_8u_C1C3R( const uchar* gray, int gray_step,
|
||||
uchar* bgr, int bgr_step, Size size );
|
||||
void icvCvt_Gray2BGR_16u_C1C3R( const ushort* gray, int gray_step,
|
||||
ushort* bgr, int bgr_step, Size size );
|
||||
|
||||
void icvCvt_BGRA2BGR_8u_C4C3R( const uchar* bgra, int bgra_step,
|
||||
uchar* bgr, int bgr_step,
|
||||
Size size, int swap_rb=0 );
|
||||
void icvCvt_BGRA2BGR_16u_C4C3R( const ushort* bgra, int bgra_step,
|
||||
ushort* bgr, int bgr_step,
|
||||
Size size, int _swap_rb );
|
||||
|
||||
void icvCvt_BGR2RGB_8u_C3R( const uchar* bgr, int bgr_step,
|
||||
uchar* rgb, int rgb_step, Size size );
|
||||
#define icvCvt_RGB2BGR_8u_C3R icvCvt_BGR2RGB_8u_C3R
|
||||
void icvCvt_BGR2RGB_16u_C3R( const ushort* bgr, int bgr_step,
|
||||
ushort* rgb, int rgb_step, Size size );
|
||||
#define icvCvt_RGB2BGR_16u_C3R icvCvt_BGR2RGB_16u_C3R
|
||||
|
||||
void icvCvt_BGRA2RGBA_8u_C4R( const uchar* bgra, int bgra_step,
|
||||
uchar* rgba, int rgba_step, Size size );
|
||||
#define icvCvt_RGBA2BGRA_8u_C4R icvCvt_BGRA2RGBA_8u_C4R
|
||||
|
||||
void icvCvt_BGRA2RGBA_16u_C4R( const ushort* bgra, int bgra_step,
|
||||
ushort* rgba, int rgba_step, Size size );
|
||||
#define icvCvt_RGBA2BGRA_16u_C4R icvCvt_BGRA2RGBA_16u_C4R
|
||||
|
||||
void icvCvt_BGR5552Gray_8u_C2C1R( const uchar* bgr555, int bgr555_step,
|
||||
uchar* gray, int gray_step, Size size );
|
||||
void icvCvt_BGR5652Gray_8u_C2C1R( const uchar* bgr565, int bgr565_step,
|
||||
uchar* gray, int gray_step, Size size );
|
||||
void icvCvt_BGR5552BGR_8u_C2C3R( const uchar* bgr555, int bgr555_step,
|
||||
uchar* bgr, int bgr_step, Size size );
|
||||
void icvCvt_BGR5652BGR_8u_C2C3R( const uchar* bgr565, int bgr565_step,
|
||||
uchar* bgr, int bgr_step, Size size );
|
||||
void icvCvt_CMYK2BGR_8u_C4C3R( const uchar* cmyk, int cmyk_step,
|
||||
uchar* bgr, int bgr_step, Size size );
|
||||
void icvCvt_CMYK2RGB_8u_C4C3R( const uchar* cmyk, int cmyk_step,
|
||||
uchar* rgb, int rgb_step, Size size );
|
||||
void icvCvt_CMYK2Gray_8u_C4C1R( const uchar* ycck, int ycck_step,
|
||||
uchar* gray, int gray_step, Size size );
|
||||
|
||||
void FillGrayPalette( PaletteEntry* palette, int bpp, bool negative = false );
|
||||
bool IsColorPalette( PaletteEntry* palette, int bpp );
|
||||
void CvtPaletteToGray( const PaletteEntry* palette, uchar* grayPalette, int entries );
|
||||
uchar* FillUniColor( uchar* data, uchar*& line_end, int step, int width3,
|
||||
int& y, int height, ptrdiff_t count3, PaletteEntry clr );
|
||||
uchar* FillUniGray( uchar* data, uchar*& line_end, int step, int width3,
|
||||
int& y, int height, ptrdiff_t count3, uchar clr );
|
||||
|
||||
uchar* FillColorRow8( uchar* data, uchar* indices, int len, PaletteEntry* palette );
|
||||
uchar* FillGrayRow8( uchar* data, uchar* indices, int len, uchar* palette );
|
||||
uchar* FillColorRow4( uchar* data, uchar* indices, int len, PaletteEntry* palette );
|
||||
uchar* FillGrayRow4( uchar* data, uchar* indices, int len, uchar* palette );
|
||||
uchar* FillColorRow1( uchar* data, uchar* indices, int len, PaletteEntry* palette );
|
||||
uchar* FillGrayRow1( uchar* data, uchar* indices, int len, uchar* palette );
|
||||
|
||||
static const size_t MAX_IMAGE_ROW_SIZE = static_cast<size_t>(1) << 28; // 256 MB
|
||||
|
||||
struct RowPitchParams {
|
||||
int src_pitch;
|
||||
size_t bytes_per_row;
|
||||
};
|
||||
|
||||
RowPitchParams calculateRowPitch(int width, int bpp, int alignment, const char* format_name);
|
||||
int calculateRowSize(int width, int nch, const char* format_name);
|
||||
|
||||
CV_INLINE bool isBigEndian( void )
|
||||
{
|
||||
#ifdef WORDS_BIGENDIAN
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
#endif/*_UTILS_H_*/
|
||||
@@ -0,0 +1,744 @@
|
||||
// This file is part of OpenCV project.
|
||||
// It is subject to the license terms in the LICENSE file found in the top-level directory
|
||||
// of this distribution and at http://opencv.org/license.html.
|
||||
|
||||
#include "test_precomp.hpp"
|
||||
#include "test_common.hpp"
|
||||
|
||||
namespace opencv_test { namespace {
|
||||
|
||||
static bool fillFrames(Animation& animation, bool hasAlpha, int n = 14)
|
||||
{
|
||||
// Set the path to the test image directory and filename for loading.
|
||||
const string root = cvtest::TS::ptr()->get_data_path();
|
||||
const string filename = root + "pngsuite/tp1n3p08.png";
|
||||
|
||||
EXPECT_TRUE(imreadanimation(filename, animation));
|
||||
EXPECT_EQ(1000, animation.durations.back());
|
||||
|
||||
if (!hasAlpha)
|
||||
cvtColor(animation.frames[0], animation.frames[0], COLOR_BGRA2BGR);
|
||||
|
||||
animation.loop_count = 0xffff; // 0xffff is the maximum value to set.
|
||||
|
||||
// Add the first frame with a duration value of 400 milliseconds.
|
||||
int duration = 80;
|
||||
animation.durations[0] = duration * 5;
|
||||
Mat image = animation.frames[0].clone();
|
||||
putText(animation.frames[0], "0", Point(5, 28), FONT_HERSHEY_SIMPLEX, .5, Scalar(100, 255, 0, 255), 2);
|
||||
|
||||
// Define a region of interest (ROI)
|
||||
Rect roi(2, 16, 26, 16);
|
||||
|
||||
// Modify the ROI in n iterations to simulate slight changes in animation frames.
|
||||
for (int i = 1; i < n; i++)
|
||||
{
|
||||
roi.x++;
|
||||
roi.width -= 2;
|
||||
RNG rng = theRNG();
|
||||
for (int x = roi.x; x < roi.x + roi.width; x++)
|
||||
for (int y = roi.y; y < roi.y + roi.height; y++)
|
||||
{
|
||||
if (hasAlpha)
|
||||
{
|
||||
Vec4b& pixel = image.at<Vec4b>(y, x);
|
||||
if (pixel[3] > 0)
|
||||
{
|
||||
if (pixel[0] > 10) pixel[0] -= (uchar)rng.uniform(2, 5);
|
||||
if (pixel[1] > 10) pixel[1] -= (uchar)rng.uniform(2, 5);
|
||||
if (pixel[2] > 10) pixel[2] -= (uchar)rng.uniform(2, 5);
|
||||
pixel[3] -= (uchar)rng.uniform(2, 5);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Vec3b& pixel = image.at<Vec3b>(y, x);
|
||||
if (pixel[0] > 50) pixel[0] -= (uchar)rng.uniform(2, 5);
|
||||
if (pixel[1] > 50) pixel[1] -= (uchar)rng.uniform(2, 5);
|
||||
if (pixel[2] > 50) pixel[2] -= (uchar)rng.uniform(2, 5);
|
||||
}
|
||||
}
|
||||
|
||||
// Update the duration and add the modified frame to the animation.
|
||||
duration += rng.uniform(2, 10); // Increase duration with random value (to be sure different duration values saved correctly).
|
||||
animation.frames.push_back(image.clone());
|
||||
putText(animation.frames[i], format("%d", i), Point(5, 28), FONT_HERSHEY_SIMPLEX, .5, Scalar(100, 255, 0, 255), 2);
|
||||
animation.durations.push_back(duration);
|
||||
}
|
||||
|
||||
// Add two identical frames with the same duration.
|
||||
if (animation.frames.size() > 1 && animation.frames.size() < 20)
|
||||
{
|
||||
animation.durations.push_back(++duration);
|
||||
animation.frames.push_back(animation.frames.back());
|
||||
animation.durations.push_back(++duration);
|
||||
animation.frames.push_back(animation.frames.back());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#ifdef HAVE_IMGCODEC_GIF
|
||||
|
||||
TEST(Imgcodecs_Gif, imwriteanimation_rgba)
|
||||
{
|
||||
Animation s_animation, l_animation;
|
||||
EXPECT_TRUE(fillFrames(s_animation, true));
|
||||
s_animation.bgcolor = Scalar(0, 0, 0, 0); // TO DO not implemented yet.
|
||||
|
||||
// Create a temporary output filename for saving the animation.
|
||||
string output = cv::tempfile(".gif");
|
||||
|
||||
// Write the animation to a .webp file and verify success.
|
||||
EXPECT_TRUE(imwriteanimation(output, s_animation));
|
||||
|
||||
// Read the animation back and compare with the original.
|
||||
EXPECT_TRUE(imreadanimation(output, l_animation));
|
||||
|
||||
size_t expected_frame_count = s_animation.frames.size();
|
||||
|
||||
// Verify that the number of frames matches the expected count.
|
||||
EXPECT_EQ(expected_frame_count, imcount(output));
|
||||
EXPECT_EQ(expected_frame_count, l_animation.frames.size());
|
||||
|
||||
// Check that the background color and loop count match between saved and loaded animations.
|
||||
EXPECT_EQ(l_animation.bgcolor, s_animation.bgcolor); // written as BGRA order
|
||||
EXPECT_EQ(l_animation.loop_count, s_animation.loop_count);
|
||||
|
||||
// Verify that the durations of frames match.
|
||||
for (size_t i = 0; i < l_animation.frames.size() - 1; i++)
|
||||
EXPECT_EQ(cvRound(s_animation.durations[i] / 10), cvRound(l_animation.durations[i] / 10));
|
||||
|
||||
EXPECT_TRUE(imreadanimation(output, l_animation, 5, 3));
|
||||
EXPECT_EQ(expected_frame_count + 3, l_animation.frames.size());
|
||||
EXPECT_EQ(l_animation.frames.size(), l_animation.durations.size());
|
||||
EXPECT_EQ(0, cvtest::norm(l_animation.frames[5], l_animation.frames[16], NORM_INF));
|
||||
EXPECT_EQ(0, cvtest::norm(l_animation.frames[6], l_animation.frames[17], NORM_INF));
|
||||
EXPECT_EQ(0, cvtest::norm(l_animation.frames[7], l_animation.frames[18], NORM_INF));
|
||||
|
||||
// Verify whether the imread function successfully loads the first frame
|
||||
Mat frame = imread(output, IMREAD_UNCHANGED);
|
||||
EXPECT_EQ(0, cvtest::norm(l_animation.frames[0], frame, NORM_INF));
|
||||
|
||||
std::vector<uchar> buf;
|
||||
readFileBytes(output, buf);
|
||||
vector<Mat> webp_frames;
|
||||
|
||||
EXPECT_TRUE(imdecodemulti(buf, IMREAD_UNCHANGED, webp_frames));
|
||||
EXPECT_EQ(expected_frame_count, webp_frames.size());
|
||||
|
||||
// Clean up by removing the temporary file.
|
||||
EXPECT_EQ(0, remove(output.c_str()));
|
||||
}
|
||||
|
||||
#endif // HAVE_IMGCODEC_GIF
|
||||
|
||||
#ifdef HAVE_WEBP
|
||||
|
||||
TEST(Imgcodecs_WebP, imwriteanimation_rgba)
|
||||
{
|
||||
Animation s_animation, l_animation;
|
||||
EXPECT_TRUE(fillFrames(s_animation, true));
|
||||
s_animation.bgcolor = Scalar(50, 100, 150, 128); // different values for test purpose.
|
||||
|
||||
// Create a temporary output filename for saving the animation.
|
||||
string output = cv::tempfile(".webp");
|
||||
|
||||
// Write the animation to a .webp file and verify success.
|
||||
EXPECT_TRUE(imwriteanimation(output, s_animation));
|
||||
|
||||
// Read the animation back and compare with the original.
|
||||
EXPECT_TRUE(imreadanimation(output, l_animation));
|
||||
|
||||
// Since the last frames are identical, WebP optimizes by storing only one of them,
|
||||
// and the duration value for the last frame is handled by libwebp.
|
||||
size_t expected_frame_count = s_animation.frames.size() - 2;
|
||||
|
||||
// Verify that the number of frames matches the expected count.
|
||||
EXPECT_EQ(expected_frame_count, imcount(output));
|
||||
EXPECT_EQ(expected_frame_count, l_animation.frames.size());
|
||||
|
||||
// Check that the background color and loop count match between saved and loaded animations.
|
||||
EXPECT_EQ(l_animation.bgcolor, s_animation.bgcolor); // written as BGRA order
|
||||
EXPECT_EQ(l_animation.loop_count, s_animation.loop_count);
|
||||
|
||||
// Verify that the durations of frames match.
|
||||
for (size_t i = 0; i < l_animation.frames.size() - 1; i++)
|
||||
EXPECT_EQ(s_animation.durations[i], l_animation.durations[i]);
|
||||
|
||||
EXPECT_TRUE(imreadanimation(output, l_animation, 5, 3));
|
||||
EXPECT_EQ(expected_frame_count + 3, l_animation.frames.size());
|
||||
EXPECT_EQ(l_animation.frames.size(), l_animation.durations.size());
|
||||
EXPECT_EQ(0, cvtest::norm(l_animation.frames[5], l_animation.frames[14], NORM_INF));
|
||||
EXPECT_EQ(0, cvtest::norm(l_animation.frames[6], l_animation.frames[15], NORM_INF));
|
||||
EXPECT_EQ(0, cvtest::norm(l_animation.frames[7], l_animation.frames[16], NORM_INF));
|
||||
|
||||
// Verify whether the imread function successfully loads the first frame
|
||||
Mat frame = imread(output, IMREAD_UNCHANGED);
|
||||
EXPECT_EQ(0, cvtest::norm(l_animation.frames[0], frame, NORM_INF));
|
||||
|
||||
std::vector<uchar> buf;
|
||||
readFileBytes(output, buf);
|
||||
vector<Mat> webp_frames;
|
||||
|
||||
EXPECT_TRUE(imdecodemulti(buf, IMREAD_UNCHANGED, webp_frames));
|
||||
EXPECT_EQ(expected_frame_count, webp_frames.size());
|
||||
|
||||
// Clean up by removing the temporary file.
|
||||
EXPECT_EQ(0, remove(output.c_str()));
|
||||
}
|
||||
|
||||
TEST(Imgcodecs_WebP, imwriteanimation_rgb)
|
||||
{
|
||||
Animation s_animation, l_animation;
|
||||
EXPECT_TRUE(fillFrames(s_animation, false));
|
||||
|
||||
// Create a temporary output filename for saving the animation.
|
||||
string output = cv::tempfile(".webp");
|
||||
|
||||
// Write the animation to a .webp file and verify success.
|
||||
EXPECT_TRUE(imwriteanimation(output, s_animation));
|
||||
|
||||
// Read the animation back and compare with the original.
|
||||
EXPECT_TRUE(imreadanimation(output, l_animation));
|
||||
|
||||
// Since the last frames are identical, WebP optimizes by storing only one of them,
|
||||
// and the duration value for the last frame is handled by libwebp.
|
||||
size_t expected_frame_count = s_animation.frames.size() - 2;
|
||||
|
||||
// Verify that the number of frames matches the expected count.
|
||||
EXPECT_EQ(expected_frame_count, imcount(output));
|
||||
EXPECT_EQ(expected_frame_count, l_animation.frames.size());
|
||||
|
||||
// Verify that the durations of frames match.
|
||||
for (size_t i = 0; i < l_animation.frames.size() - 1; i++)
|
||||
EXPECT_EQ(s_animation.durations[i], l_animation.durations[i]);
|
||||
|
||||
EXPECT_TRUE(imreadanimation(output, l_animation, 5, 3));
|
||||
EXPECT_EQ(expected_frame_count + 3, l_animation.frames.size());
|
||||
EXPECT_EQ(l_animation.frames.size(), l_animation.durations.size());
|
||||
EXPECT_TRUE(cvtest::norm(l_animation.frames[5], l_animation.frames[14], NORM_INF) == 0);
|
||||
EXPECT_TRUE(cvtest::norm(l_animation.frames[6], l_animation.frames[15], NORM_INF) == 0);
|
||||
EXPECT_TRUE(cvtest::norm(l_animation.frames[7], l_animation.frames[16], NORM_INF) == 0);
|
||||
|
||||
// Verify whether the imread function successfully loads the first frame
|
||||
Mat frame = imread(output, IMREAD_COLOR);
|
||||
EXPECT_TRUE(cvtest::norm(l_animation.frames[0], frame, NORM_INF) == 0);
|
||||
|
||||
std::vector<uchar> buf;
|
||||
readFileBytes(output, buf);
|
||||
|
||||
vector<Mat> webp_frames;
|
||||
EXPECT_TRUE(imdecodemulti(buf, IMREAD_UNCHANGED, webp_frames));
|
||||
EXPECT_EQ(expected_frame_count,webp_frames.size());
|
||||
|
||||
// Clean up by removing the temporary file.
|
||||
EXPECT_EQ(0, remove(output.c_str()));
|
||||
}
|
||||
|
||||
TEST(Imgcodecs_WebP, imwritemulti_rgba)
|
||||
{
|
||||
Animation s_animation;
|
||||
EXPECT_TRUE(fillFrames(s_animation, true));
|
||||
|
||||
string output = cv::tempfile(".webp");
|
||||
ASSERT_TRUE(imwrite(output, s_animation.frames));
|
||||
vector<Mat> read_frames;
|
||||
ASSERT_TRUE(imreadmulti(output, read_frames, IMREAD_UNCHANGED));
|
||||
EXPECT_EQ(s_animation.frames.size() - 2, read_frames.size());
|
||||
EXPECT_EQ(4, s_animation.frames[0].channels());
|
||||
EXPECT_EQ(0, remove(output.c_str()));
|
||||
}
|
||||
|
||||
TEST(Imgcodecs_WebP, imwritemulti_rgb)
|
||||
{
|
||||
Animation s_animation;
|
||||
EXPECT_TRUE(fillFrames(s_animation, false));
|
||||
|
||||
string output = cv::tempfile(".webp");
|
||||
ASSERT_TRUE(imwrite(output, s_animation.frames));
|
||||
vector<Mat> read_frames;
|
||||
ASSERT_TRUE(imreadmulti(output, read_frames));
|
||||
EXPECT_EQ(s_animation.frames.size() - 2, read_frames.size());
|
||||
EXPECT_EQ(0, remove(output.c_str()));
|
||||
}
|
||||
|
||||
TEST(Imgcodecs_WebP, imencode_rgba)
|
||||
{
|
||||
Animation s_animation;
|
||||
EXPECT_TRUE(fillFrames(s_animation, true, 3));
|
||||
|
||||
std::vector<uchar> buf;
|
||||
vector<Mat> apng_frames;
|
||||
|
||||
// Test encoding and decoding the images in memory (without saving to disk).
|
||||
EXPECT_TRUE(imencode(".webp", s_animation.frames, buf));
|
||||
EXPECT_TRUE(imdecodemulti(buf, IMREAD_UNCHANGED, apng_frames));
|
||||
EXPECT_EQ(s_animation.frames.size() - 2, apng_frames.size());
|
||||
}
|
||||
|
||||
#endif // HAVE_WEBP
|
||||
|
||||
#ifdef HAVE_PNG
|
||||
|
||||
TEST(Imgcodecs_APNG, imwriteanimation_rgba)
|
||||
{
|
||||
Animation s_animation, l_animation;
|
||||
EXPECT_TRUE(fillFrames(s_animation, true));
|
||||
|
||||
// Create a temporary output filename for saving the animation.
|
||||
string output = cv::tempfile(".png");
|
||||
|
||||
// Write the animation to a .png file and verify success.
|
||||
EXPECT_TRUE(imwriteanimation(output, s_animation));
|
||||
|
||||
// Read the animation back and compare with the original.
|
||||
EXPECT_TRUE(imreadanimation(output, l_animation));
|
||||
|
||||
size_t expected_frame_count = s_animation.frames.size() - 2;
|
||||
|
||||
// Verify that the number of frames matches the expected count.
|
||||
EXPECT_EQ(expected_frame_count, imcount(output));
|
||||
EXPECT_EQ(expected_frame_count, l_animation.frames.size());
|
||||
|
||||
for (size_t i = 0; i < l_animation.frames.size() - 1; i++)
|
||||
{
|
||||
EXPECT_EQ(s_animation.durations[i], l_animation.durations[i]);
|
||||
EXPECT_EQ(0, cvtest::norm(s_animation.frames[i], l_animation.frames[i], NORM_INF));
|
||||
}
|
||||
|
||||
EXPECT_TRUE(imreadanimation(output, l_animation, 5, 3));
|
||||
EXPECT_EQ(expected_frame_count + 3, l_animation.frames.size());
|
||||
EXPECT_EQ(l_animation.frames.size(), l_animation.durations.size());
|
||||
EXPECT_EQ(0, cvtest::norm(l_animation.frames[5], l_animation.frames[14], NORM_INF));
|
||||
EXPECT_EQ(0, cvtest::norm(l_animation.frames[6], l_animation.frames[15], NORM_INF));
|
||||
EXPECT_EQ(0, cvtest::norm(l_animation.frames[7], l_animation.frames[16], NORM_INF));
|
||||
|
||||
// Verify whether the imread function successfully loads the first frame
|
||||
Mat frame = imread(output, IMREAD_UNCHANGED);
|
||||
EXPECT_EQ(0, cvtest::norm(l_animation.frames[0], frame, NORM_INF));
|
||||
|
||||
std::vector<uchar> buf;
|
||||
readFileBytes(output, buf);
|
||||
vector<Mat> apng_frames;
|
||||
|
||||
EXPECT_TRUE(imdecodemulti(buf, IMREAD_UNCHANGED, apng_frames));
|
||||
EXPECT_EQ(expected_frame_count, apng_frames.size());
|
||||
|
||||
apng_frames.clear();
|
||||
// Test saving the animation frames as individual still images.
|
||||
EXPECT_TRUE(imwrite(output, s_animation.frames));
|
||||
|
||||
// Read back the still images into a vector of Mats.
|
||||
EXPECT_TRUE(imreadmulti(output, apng_frames));
|
||||
|
||||
// Expect all frames written as multi-page image
|
||||
EXPECT_EQ(expected_frame_count, apng_frames.size());
|
||||
|
||||
// Clean up by removing the temporary file.
|
||||
EXPECT_EQ(0, remove(output.c_str()));
|
||||
}
|
||||
|
||||
TEST(Imgcodecs_APNG, imwriteanimation_rgba16u)
|
||||
{
|
||||
Animation s_animation, l_animation;
|
||||
EXPECT_TRUE(fillFrames(s_animation, true));
|
||||
|
||||
for (size_t i = 0; i < s_animation.frames.size(); i++)
|
||||
{
|
||||
s_animation.frames[i].convertTo(s_animation.frames[i], CV_16U, 255);
|
||||
}
|
||||
// Create a temporary output filename for saving the animation.
|
||||
string output = cv::tempfile(".png");
|
||||
|
||||
// Write the animation to a .png file and verify success.
|
||||
EXPECT_TRUE(imwriteanimation(output, s_animation));
|
||||
|
||||
// Read the animation back and compare with the original.
|
||||
EXPECT_TRUE(imreadanimation(output, l_animation));
|
||||
|
||||
size_t expected_frame_count = s_animation.frames.size() - 2;
|
||||
|
||||
// Verify that the number of frames matches the expected count.
|
||||
EXPECT_EQ(expected_frame_count, imcount(output));
|
||||
EXPECT_EQ(expected_frame_count, l_animation.frames.size());
|
||||
|
||||
std::vector<uchar> buf;
|
||||
readFileBytes(output, buf);
|
||||
vector<Mat> apng_frames;
|
||||
|
||||
EXPECT_TRUE(imdecodemulti(buf, IMREAD_UNCHANGED, apng_frames));
|
||||
EXPECT_EQ(expected_frame_count, apng_frames.size());
|
||||
|
||||
apng_frames.clear();
|
||||
// Test saving the animation frames as individual still images.
|
||||
EXPECT_TRUE(imwrite(output, s_animation.frames));
|
||||
|
||||
// Read back the still images into a vector of Mats.
|
||||
EXPECT_TRUE(imreadmulti(output, apng_frames));
|
||||
|
||||
// Expect all frames written as multi-page image
|
||||
EXPECT_EQ(expected_frame_count, apng_frames.size());
|
||||
|
||||
// Clean up by removing the temporary file.
|
||||
EXPECT_EQ(0, remove(output.c_str()));
|
||||
}
|
||||
|
||||
TEST(Imgcodecs_APNG, imwriteanimation_rgb)
|
||||
{
|
||||
Animation s_animation, l_animation;
|
||||
EXPECT_TRUE(fillFrames(s_animation, false));
|
||||
|
||||
string output = cv::tempfile(".png");
|
||||
|
||||
// Write the animation to a .png file and verify success.
|
||||
EXPECT_TRUE(imwriteanimation(output, s_animation));
|
||||
|
||||
// Read the animation back and compare with the original.
|
||||
EXPECT_TRUE(imreadanimation(output, l_animation));
|
||||
EXPECT_EQ(l_animation.frames.size(), s_animation.frames.size() - 2);
|
||||
for (size_t i = 0; i < l_animation.frames.size() - 1; i++)
|
||||
{
|
||||
EXPECT_EQ(0, cvtest::norm(s_animation.frames[i], l_animation.frames[i], NORM_INF));
|
||||
}
|
||||
EXPECT_EQ(0, remove(output.c_str()));
|
||||
}
|
||||
|
||||
TEST(Imgcodecs_APNG, imwriteanimation_gray)
|
||||
{
|
||||
Animation s_animation, l_animation;
|
||||
EXPECT_TRUE(fillFrames(s_animation, false));
|
||||
|
||||
for (size_t i = 0; i < s_animation.frames.size(); i++)
|
||||
{
|
||||
cvtColor(s_animation.frames[i], s_animation.frames[i], COLOR_BGR2GRAY);
|
||||
}
|
||||
|
||||
s_animation.bgcolor = Scalar(50, 100, 150);
|
||||
string output = cv::tempfile(".png");
|
||||
// Write the animation to a .png file and verify success.
|
||||
EXPECT_TRUE(imwriteanimation(output, s_animation));
|
||||
|
||||
// Read the animation back and compare with the original.
|
||||
EXPECT_TRUE(imreadanimation(output, l_animation));
|
||||
|
||||
EXPECT_EQ(Scalar(), l_animation.bgcolor);
|
||||
size_t expected_frame_count = s_animation.frames.size() - 2;
|
||||
|
||||
// Verify that the number of frames matches the expected count.
|
||||
EXPECT_EQ(expected_frame_count, imcount(output));
|
||||
EXPECT_EQ(expected_frame_count, l_animation.frames.size());
|
||||
|
||||
EXPECT_EQ(0, remove(output.c_str()));
|
||||
|
||||
for (size_t i = 0; i < l_animation.frames.size(); i++)
|
||||
{
|
||||
EXPECT_EQ(0, cvtest::norm(s_animation.frames[i], l_animation.frames[i], NORM_INF));
|
||||
}
|
||||
}
|
||||
|
||||
TEST(Imgcodecs_APNG, imwritemulti_rgba)
|
||||
{
|
||||
Animation s_animation;
|
||||
EXPECT_TRUE(fillFrames(s_animation, true));
|
||||
|
||||
string output = cv::tempfile(".png");
|
||||
EXPECT_EQ(true, imwrite(output, s_animation.frames));
|
||||
vector<Mat> read_frames;
|
||||
EXPECT_EQ(true, imreadmulti(output, read_frames, IMREAD_UNCHANGED));
|
||||
EXPECT_EQ(read_frames.size(), s_animation.frames.size() - 2);
|
||||
EXPECT_EQ(imcount(output), read_frames.size());
|
||||
EXPECT_EQ(0, remove(output.c_str()));
|
||||
}
|
||||
|
||||
TEST(Imgcodecs_APNG, imwritemulti_rgb)
|
||||
{
|
||||
Animation s_animation;
|
||||
EXPECT_TRUE(fillFrames(s_animation, false));
|
||||
|
||||
string output = cv::tempfile(".png");
|
||||
ASSERT_TRUE(imwrite(output, s_animation.frames));
|
||||
vector<Mat> read_frames;
|
||||
ASSERT_TRUE(imreadmulti(output, read_frames));
|
||||
EXPECT_EQ(read_frames.size(), s_animation.frames.size() - 2);
|
||||
EXPECT_EQ(0, remove(output.c_str()));
|
||||
|
||||
for (size_t i = 0; i < read_frames.size(); i++)
|
||||
{
|
||||
EXPECT_EQ(0, cvtest::norm(s_animation.frames[i], read_frames[i], NORM_INF));
|
||||
}
|
||||
}
|
||||
|
||||
TEST(Imgcodecs_APNG, imwritemulti_gray)
|
||||
{
|
||||
Animation s_animation;
|
||||
EXPECT_TRUE(fillFrames(s_animation, false));
|
||||
|
||||
for (size_t i = 0; i < s_animation.frames.size(); i++)
|
||||
{
|
||||
cvtColor(s_animation.frames[i], s_animation.frames[i], COLOR_BGR2GRAY);
|
||||
}
|
||||
|
||||
string output = cv::tempfile(".png");
|
||||
EXPECT_TRUE(imwrite(output, s_animation.frames));
|
||||
vector<Mat> read_frames;
|
||||
EXPECT_TRUE(imreadmulti(output, read_frames));
|
||||
EXPECT_EQ(1, read_frames[0].channels());
|
||||
read_frames.clear();
|
||||
EXPECT_TRUE(imreadmulti(output, read_frames, IMREAD_UNCHANGED));
|
||||
EXPECT_EQ(1, read_frames[0].channels());
|
||||
read_frames.clear();
|
||||
EXPECT_TRUE(imreadmulti(output, read_frames, IMREAD_COLOR));
|
||||
EXPECT_EQ(3, read_frames[0].channels());
|
||||
read_frames.clear();
|
||||
EXPECT_TRUE(imreadmulti(output, read_frames, IMREAD_GRAYSCALE));
|
||||
EXPECT_EQ(0, remove(output.c_str()));
|
||||
|
||||
for (size_t i = 0; i < read_frames.size(); i++)
|
||||
{
|
||||
EXPECT_EQ(0, cvtest::norm(s_animation.frames[i], read_frames[i], NORM_INF));
|
||||
}
|
||||
}
|
||||
|
||||
TEST(Imgcodecs_APNG, imwriteanimation_bgcolor)
|
||||
{
|
||||
Animation s_animation, l_animation;
|
||||
EXPECT_TRUE(fillFrames(s_animation, true, 2));
|
||||
s_animation.bgcolor = Scalar(50, 100, 150); // will be written in bKGD chunk as RGB.
|
||||
|
||||
// Create a temporary output filename for saving the animation.
|
||||
string output = cv::tempfile(".png");
|
||||
|
||||
// Write the animation to a .png file and verify success.
|
||||
EXPECT_TRUE(imwriteanimation(output, s_animation));
|
||||
|
||||
// Read the animation back and compare with the original.
|
||||
EXPECT_TRUE(imreadanimation(output, l_animation));
|
||||
|
||||
// Check that the background color match between saved and loaded animations.
|
||||
EXPECT_EQ(l_animation.bgcolor, s_animation.bgcolor);
|
||||
EXPECT_EQ(0, remove(output.c_str()));
|
||||
|
||||
EXPECT_TRUE(fillFrames(s_animation, true, 2));
|
||||
s_animation.bgcolor = Scalar();
|
||||
|
||||
output = cv::tempfile(".png");
|
||||
EXPECT_TRUE(imwriteanimation(output, s_animation));
|
||||
EXPECT_TRUE(imreadanimation(output, l_animation));
|
||||
EXPECT_EQ(l_animation.bgcolor, s_animation.bgcolor);
|
||||
|
||||
EXPECT_EQ(0, remove(output.c_str()));
|
||||
}
|
||||
|
||||
TEST(Imgcodecs_APNG, imencode_rgba)
|
||||
{
|
||||
Animation s_animation;
|
||||
EXPECT_TRUE(fillFrames(s_animation, true, 3));
|
||||
|
||||
std::vector<uchar> buf;
|
||||
vector<Mat> read_frames;
|
||||
// Test encoding and decoding the images in memory (without saving to disk).
|
||||
EXPECT_TRUE(imencode(".png", s_animation.frames, buf));
|
||||
EXPECT_TRUE(imdecodemulti(buf, IMREAD_UNCHANGED, read_frames));
|
||||
EXPECT_EQ(read_frames.size(), s_animation.frames.size() - 2);
|
||||
}
|
||||
|
||||
typedef testing::TestWithParam<string> Imgcodecs_ImageCollection;
|
||||
|
||||
const string exts_multi[] = {
|
||||
#ifdef HAVE_AVIF
|
||||
".avif",
|
||||
#endif
|
||||
#ifdef HAVE_IMGCODEC_GIF
|
||||
".gif",
|
||||
#endif
|
||||
".png",
|
||||
#ifdef HAVE_TIFF
|
||||
".tiff",
|
||||
#endif
|
||||
#ifdef HAVE_WEBP
|
||||
".webp",
|
||||
#endif
|
||||
};
|
||||
|
||||
TEST_P(Imgcodecs_ImageCollection, animations)
|
||||
{
|
||||
Animation s_animation;
|
||||
EXPECT_TRUE(fillFrames(s_animation, false));
|
||||
|
||||
string output = cv::tempfile(GetParam().c_str());
|
||||
ASSERT_TRUE(imwritemulti(output, s_animation.frames));
|
||||
vector<Mat> read_frames;
|
||||
ASSERT_TRUE(imreadmulti(output, read_frames, IMREAD_UNCHANGED));
|
||||
|
||||
{
|
||||
ImageCollection collection(output, IMREAD_UNCHANGED);
|
||||
EXPECT_EQ(read_frames.size(), collection.size());
|
||||
int i = 0;
|
||||
for (auto&& frame : collection)
|
||||
{
|
||||
EXPECT_EQ(0, cvtest::norm(frame, read_frames[i], NORM_INF));
|
||||
++i;
|
||||
}
|
||||
}
|
||||
EXPECT_EQ(0, remove(output.c_str()));
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(/**/,
|
||||
Imgcodecs_ImageCollection,
|
||||
testing::ValuesIn(exts_multi));
|
||||
|
||||
TEST(Imgcodecs_APNG, imdecode_animation)
|
||||
{
|
||||
Animation gt_animation, mem_animation;
|
||||
// Set the path to the test image directory and filename for loading.
|
||||
const string root = cvtest::TS::ptr()->get_data_path();
|
||||
const string filename = root + "pngsuite/tp1n3p08.png";
|
||||
|
||||
EXPECT_TRUE(imreadanimation(filename, gt_animation));
|
||||
EXPECT_EQ(1000, gt_animation.durations.back());
|
||||
|
||||
std::vector<unsigned char> buf;
|
||||
readFileBytes(filename, buf);
|
||||
EXPECT_TRUE(imdecodeanimation(buf, mem_animation));
|
||||
|
||||
EXPECT_EQ(mem_animation.frames.size(), gt_animation.frames.size());
|
||||
EXPECT_EQ(mem_animation.bgcolor, gt_animation.bgcolor);
|
||||
EXPECT_EQ(mem_animation.loop_count, gt_animation.loop_count);
|
||||
for (size_t i = 0; i < gt_animation.frames.size(); i++)
|
||||
{
|
||||
EXPECT_EQ(0, cvtest::norm(mem_animation.frames[i], gt_animation.frames[i], NORM_INF));
|
||||
EXPECT_EQ(mem_animation.durations[i], gt_animation.durations[i]);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(Imgcodecs_APNG, imencode_animation)
|
||||
{
|
||||
Animation gt_animation, mem_animation;
|
||||
// Set the path to the test image directory and filename for loading.
|
||||
const string root = cvtest::TS::ptr()->get_data_path();
|
||||
const string filename = root + "pngsuite/tp1n3p08.png";
|
||||
|
||||
EXPECT_TRUE(imreadanimation(filename, gt_animation));
|
||||
EXPECT_EQ(1000, gt_animation.durations.back());
|
||||
|
||||
std::vector<unsigned char> buf;
|
||||
EXPECT_TRUE(imencodeanimation(".png", gt_animation, buf));
|
||||
EXPECT_TRUE(imdecodeanimation(buf, mem_animation));
|
||||
|
||||
EXPECT_EQ(mem_animation.frames.size(), gt_animation.frames.size());
|
||||
EXPECT_EQ(mem_animation.bgcolor, gt_animation.bgcolor);
|
||||
EXPECT_EQ(mem_animation.loop_count, gt_animation.loop_count);
|
||||
for (size_t i = 0; i < gt_animation.frames.size(); i++)
|
||||
{
|
||||
EXPECT_EQ(0, cvtest::norm(mem_animation.frames[i], gt_animation.frames[i], NORM_INF));
|
||||
EXPECT_EQ(mem_animation.durations[i], gt_animation.durations[i]);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(Imgcodecs_APNG, animation_has_hidden_frame)
|
||||
{
|
||||
// Set the path to the test image directory and filename for loading.
|
||||
const string root = cvtest::TS::ptr()->get_data_path();
|
||||
const string filename = root + "readwrite/033.png";
|
||||
Animation animation1, animation2, animation3;
|
||||
|
||||
imreadanimation(filename, animation1);
|
||||
|
||||
EXPECT_FALSE(animation1.still_image.empty());
|
||||
EXPECT_EQ((size_t)2, animation1.frames.size());
|
||||
|
||||
std::vector<unsigned char> buf;
|
||||
EXPECT_TRUE(imencodeanimation(".png", animation1, buf));
|
||||
EXPECT_TRUE(imdecodeanimation(buf, animation2));
|
||||
|
||||
EXPECT_FALSE(animation2.still_image.empty());
|
||||
EXPECT_EQ(animation1.frames.size(), animation2.frames.size());
|
||||
|
||||
animation1.frames.erase(animation1.frames.begin());
|
||||
animation1.durations.erase(animation1.durations.begin());
|
||||
EXPECT_TRUE(imencodeanimation(".png", animation1, buf));
|
||||
EXPECT_TRUE(imdecodeanimation(buf, animation3));
|
||||
|
||||
EXPECT_FALSE(animation1.still_image.empty());
|
||||
EXPECT_TRUE(animation3.still_image.empty());
|
||||
EXPECT_EQ((size_t)1, animation3.frames.size());
|
||||
}
|
||||
|
||||
TEST(Imgcodecs_APNG, animation_imread_preview)
|
||||
{
|
||||
// Set the path to the test image directory and filename for loading.
|
||||
const string root = cvtest::TS::ptr()->get_data_path();
|
||||
const string filename = root + "readwrite/034.png";
|
||||
cv::Mat imread_result;
|
||||
cv::imread(filename, imread_result, cv::IMREAD_UNCHANGED);
|
||||
EXPECT_FALSE(imread_result.empty());
|
||||
|
||||
Animation animation;
|
||||
ASSERT_TRUE(imreadanimation(filename, animation));
|
||||
EXPECT_FALSE(animation.still_image.empty());
|
||||
EXPECT_EQ((size_t)2, animation.frames.size());
|
||||
|
||||
EXPECT_EQ(0, cv::norm(animation.still_image, imread_result, cv::NORM_INF));
|
||||
}
|
||||
|
||||
#endif // HAVE_PNG
|
||||
|
||||
#if defined(HAVE_PNG) || defined(HAVE_SPNG)
|
||||
|
||||
TEST(Imgcodecs_APNG, imread_animation_16u)
|
||||
{
|
||||
// Set the path to the test image directory and filename for loading.
|
||||
const string root = cvtest::TS::ptr()->get_data_path();
|
||||
const string filename = root + "readwrite/033.png";
|
||||
|
||||
Mat img = imread(filename, IMREAD_UNCHANGED);
|
||||
ASSERT_FALSE(img.empty());
|
||||
EXPECT_TRUE(img.type() == CV_16UC4);
|
||||
EXPECT_EQ(0, img.at<ushort>(0, 0));
|
||||
EXPECT_EQ(0, img.at<ushort>(0, 1));
|
||||
EXPECT_EQ(65280, img.at<ushort>(0, 2));
|
||||
EXPECT_EQ(65535, img.at<ushort>(0, 3));
|
||||
|
||||
img = imread(filename, IMREAD_GRAYSCALE);
|
||||
ASSERT_FALSE(img.empty());
|
||||
EXPECT_TRUE(img.type() == CV_8UC1);
|
||||
EXPECT_EQ(76, img.at<uchar>(0, 0));
|
||||
|
||||
img = imread(filename, IMREAD_COLOR);
|
||||
ASSERT_FALSE(img.empty());
|
||||
EXPECT_TRUE(img.type() == CV_8UC3);
|
||||
EXPECT_EQ(0, img.at<uchar>(0, 0));
|
||||
EXPECT_EQ(0, img.at<uchar>(0, 1));
|
||||
EXPECT_EQ(255, img.at<uchar>(0, 2));
|
||||
|
||||
img = imread(filename, IMREAD_COLOR_RGB);
|
||||
ASSERT_FALSE(img.empty());
|
||||
EXPECT_TRUE(img.type() == CV_8UC3);
|
||||
EXPECT_EQ(255, img.at<uchar>(0, 0));
|
||||
EXPECT_EQ(0, img.at<uchar>(0, 1));
|
||||
EXPECT_EQ(0, img.at<uchar>(0, 2));
|
||||
|
||||
img = imread(filename, IMREAD_ANYDEPTH);
|
||||
ASSERT_FALSE(img.empty());
|
||||
EXPECT_TRUE(img.type() == CV_16UC1);
|
||||
EXPECT_EQ(19517, img.at<ushort>(0, 0));
|
||||
|
||||
img = imread(filename, IMREAD_COLOR | IMREAD_ANYDEPTH);
|
||||
ASSERT_FALSE(img.empty());
|
||||
EXPECT_TRUE(img.type() == CV_16UC3);
|
||||
EXPECT_EQ(0, img.at<ushort>(0, 0));
|
||||
EXPECT_EQ(0, img.at<ushort>(0, 1));
|
||||
EXPECT_EQ(65280, img.at<ushort>(0, 2));
|
||||
|
||||
img = imread(filename, IMREAD_COLOR_RGB | IMREAD_ANYDEPTH);
|
||||
ASSERT_FALSE(img.empty());
|
||||
EXPECT_TRUE(img.type() == CV_16UC3);
|
||||
EXPECT_EQ(65280, img.at<ushort>(0, 0));
|
||||
EXPECT_EQ(0, img.at<ushort>(0, 1));
|
||||
EXPECT_EQ(0, img.at<ushort>(0, 2));
|
||||
}
|
||||
|
||||
#endif // HAVE_PNG || HAVE_SPNG
|
||||
|
||||
}} // namespace
|
||||
@@ -0,0 +1,310 @@
|
||||
// This file is part of OpenCV project.
|
||||
// It is subject to the license terms in the LICENSE file found in the top-level
|
||||
// directory of this distribution and at http://opencv.org/license.html
|
||||
|
||||
#include <cstdint>
|
||||
#include <fstream>
|
||||
|
||||
#include "test_precomp.hpp"
|
||||
|
||||
#ifdef HAVE_AVIF
|
||||
|
||||
namespace opencv_test {
|
||||
namespace {
|
||||
|
||||
class Imgcodecs_Avif_RoundTripSuite
|
||||
: public testing::TestWithParam<std::tuple<int, int, int, ImreadModes>> {
|
||||
protected:
|
||||
static cv::Mat modifyImage(const cv::Mat& img_original, int channels,
|
||||
int bit_depth) {
|
||||
cv::Mat img;
|
||||
if (channels == 1) {
|
||||
cv::cvtColor(img_original, img, cv::COLOR_BGR2GRAY);
|
||||
} else if (channels == 4) {
|
||||
std::vector<cv::Mat> imgs;
|
||||
cv::split(img_original, imgs);
|
||||
imgs.push_back(cv::Mat(imgs[0]));
|
||||
imgs[imgs.size() - 1] = cv::Scalar::all(128);
|
||||
cv::merge(imgs, img);
|
||||
} else {
|
||||
img = img_original.clone();
|
||||
}
|
||||
|
||||
cv::Mat img_final = img;
|
||||
// Convert image to CV_16U for some bit depths.
|
||||
if (bit_depth > 8) img.convertTo(img_final, CV_16U, 1 << (bit_depth - 8));
|
||||
|
||||
return img_final;
|
||||
}
|
||||
|
||||
void SetUp() {
|
||||
bit_depth_ = std::get<0>(GetParam());
|
||||
channels_ = std::get<1>(GetParam());
|
||||
quality_ = std::get<2>(GetParam());
|
||||
imread_mode_ = std::get<3>(GetParam());
|
||||
encoding_params_ = {cv::IMWRITE_AVIF_QUALITY, quality_,
|
||||
cv::IMWRITE_AVIF_DEPTH, bit_depth_};
|
||||
}
|
||||
|
||||
bool IsBitDepthValid() const {
|
||||
return (bit_depth_ == 8 || bit_depth_ == 10 || bit_depth_ == 12);
|
||||
}
|
||||
|
||||
// Makes sure images are close enough after encode/decode roundtrip.
|
||||
void ValidateRead(const cv::Mat& img_original, const cv::Mat& img) const {
|
||||
EXPECT_EQ(img_original.size(), img.size());
|
||||
if (imread_mode_ == IMREAD_UNCHANGED) {
|
||||
ASSERT_EQ(img_original.type(), img.type());
|
||||
// Lossless.
|
||||
if (quality_ == 100) {
|
||||
EXPECT_EQ(0, cvtest::norm(img, img_original, NORM_INF));
|
||||
} else {
|
||||
const float norm = cvtest::norm(img, img_original, NORM_L2) /
|
||||
img.channels() / img.cols / img.rows /
|
||||
(1 << (bit_depth_ - 8));
|
||||
if (quality_ == 50) {
|
||||
EXPECT_LE(norm, 10);
|
||||
} else if (quality_ == 0) {
|
||||
EXPECT_LE(norm, 13);
|
||||
} else {
|
||||
EXPECT_FALSE(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
int bit_depth_;
|
||||
int channels_;
|
||||
int quality_;
|
||||
int imread_mode_;
|
||||
std::vector<int> encoding_params_;
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
class Imgcodecs_Avif_Image_RoundTripSuite
|
||||
: public Imgcodecs_Avif_RoundTripSuite {
|
||||
public:
|
||||
const cv::Mat& get_img_original() {
|
||||
const Key key = {channels_, (bit_depth_ < 8) ? 8 : bit_depth_};
|
||||
return imgs_[key];
|
||||
}
|
||||
|
||||
// Prepare the original image modified for different number of channels and
|
||||
// bit depth.
|
||||
static void SetUpTestCase() {
|
||||
const string root = cvtest::TS::ptr()->get_data_path();
|
||||
const string filename = root + "../cv/shared/lena.png";
|
||||
const cv::Mat img_original = cv::imread(filename);
|
||||
cv::Mat img_resized;
|
||||
cv::resize(img_original, img_resized, cv::Size(kWidth, kHeight), 0, 0);
|
||||
for (int channels : {1, 3, 4}) {
|
||||
for (int bit_depth : {8, 10, 12}) {
|
||||
const Key key{channels, bit_depth};
|
||||
imgs_[key] = modifyImage(img_resized, channels, bit_depth);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static const int kWidth;
|
||||
static const int kHeight;
|
||||
|
||||
private:
|
||||
typedef std::tuple<int, int> Key;
|
||||
static std::map<Key, cv::Mat> imgs_;
|
||||
};
|
||||
std::map<std::tuple<int, int>, cv::Mat>
|
||||
Imgcodecs_Avif_Image_RoundTripSuite::imgs_;
|
||||
const int Imgcodecs_Avif_Image_RoundTripSuite::kWidth = 51;
|
||||
const int Imgcodecs_Avif_Image_RoundTripSuite::kHeight = 31;
|
||||
|
||||
class Imgcodecs_Avif_Image_WriteReadSuite
|
||||
: public Imgcodecs_Avif_Image_RoundTripSuite {};
|
||||
|
||||
TEST_P(Imgcodecs_Avif_Image_WriteReadSuite, imwrite_imread) {
|
||||
const cv::Mat& img_original = get_img_original();
|
||||
ASSERT_FALSE(img_original.empty());
|
||||
|
||||
// Encode.
|
||||
const string output = cv::tempfile(".avif");
|
||||
EXPECT_NO_THROW(cv::imwrite(output, img_original, encoding_params_));
|
||||
|
||||
// Read from file.
|
||||
const cv::Mat img = cv::imread(output, imread_mode_);
|
||||
|
||||
ValidateRead(img_original, img);
|
||||
|
||||
EXPECT_EQ(0, remove(output.c_str()));
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(
|
||||
Imgcodecs_AVIF, Imgcodecs_Avif_Image_WriteReadSuite,
|
||||
::testing::Combine(::testing::ValuesIn({6, 8, 10, 12}),
|
||||
::testing::ValuesIn({1, 3, 4}),
|
||||
::testing::ValuesIn({0, 50, 100}),
|
||||
::testing::ValuesIn({IMREAD_UNCHANGED, IMREAD_GRAYSCALE,
|
||||
IMREAD_COLOR, IMREAD_COLOR_RGB})));
|
||||
|
||||
class Imgcodecs_Avif_Image_EncodeDecodeSuite
|
||||
: public Imgcodecs_Avif_Image_RoundTripSuite {};
|
||||
|
||||
TEST_P(Imgcodecs_Avif_Image_EncodeDecodeSuite, imencode_imdecode) {
|
||||
const cv::Mat& img_original = get_img_original();
|
||||
ASSERT_FALSE(img_original.empty());
|
||||
|
||||
// Encode.
|
||||
std::vector<unsigned char> buf;
|
||||
bool result = true;
|
||||
EXPECT_NO_THROW(
|
||||
result = cv::imencode(".avif", img_original, buf, encoding_params_););
|
||||
EXPECT_TRUE(result);
|
||||
|
||||
// Read back.
|
||||
const cv::Mat img = cv::imdecode(buf, imread_mode_);
|
||||
|
||||
ValidateRead(img_original, img);
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(
|
||||
Imgcodecs_AVIF, Imgcodecs_Avif_Image_EncodeDecodeSuite,
|
||||
::testing::Combine(::testing::ValuesIn({6, 8, 10, 12}),
|
||||
::testing::ValuesIn({1, 3, 4}),
|
||||
::testing::ValuesIn({0, 50, 100}),
|
||||
::testing::ValuesIn({IMREAD_UNCHANGED, IMREAD_GRAYSCALE,
|
||||
IMREAD_COLOR, IMREAD_COLOR_RGB})));
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
class Imgcodecs_Avif_Animation_RoundTripSuite
|
||||
: public Imgcodecs_Avif_RoundTripSuite {
|
||||
public:
|
||||
const std::vector<cv::Mat>& get_anim_original() {
|
||||
const Key key = {channels_, bit_depth_};
|
||||
return anims_[key];
|
||||
}
|
||||
|
||||
// Prepare the original image modified for different number of channels and
|
||||
// bit depth.
|
||||
static void SetUpTestCase() {
|
||||
const string root = cvtest::TS::ptr()->get_data_path();
|
||||
const string filename = root + "../cv/shared/lena.png";
|
||||
const cv::Mat img_original = cv::imread(filename);
|
||||
cv::Mat img_resized;
|
||||
cv::resize(img_original, img_resized, cv::Size(kWidth, kHeight), 0, 0);
|
||||
for (int channels : {1, 3, 4}) {
|
||||
for (int bit_depth : {8, 10, 12}) {
|
||||
const Key key{channels, bit_depth};
|
||||
const cv::Mat img = modifyImage(img_resized, channels, bit_depth);
|
||||
cv::Mat img2, img3;
|
||||
cv::flip(img, img2, 0);
|
||||
cv::flip(img, img3, -1);
|
||||
anims_[key] = {img, img2, img3};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ValidateRead(const std::vector<cv::Mat>& anim_original,
|
||||
const std::vector<cv::Mat>& anim) const {
|
||||
ASSERT_EQ(anim_original.size(), anim.size());
|
||||
for (size_t i = 0; i < anim.size(); ++i) {
|
||||
Imgcodecs_Avif_RoundTripSuite::ValidateRead(anim_original[i], anim[i]);
|
||||
}
|
||||
}
|
||||
|
||||
static const int kWidth;
|
||||
static const int kHeight;
|
||||
|
||||
private:
|
||||
typedef std::tuple<int, int> Key;
|
||||
static std::map<Key, std::vector<cv::Mat>> anims_;
|
||||
};
|
||||
std::map<std::tuple<int, int>, std::vector<cv::Mat>>
|
||||
Imgcodecs_Avif_Animation_RoundTripSuite::anims_;
|
||||
const int Imgcodecs_Avif_Animation_RoundTripSuite::kWidth = 5;
|
||||
const int Imgcodecs_Avif_Animation_RoundTripSuite::kHeight = 5;
|
||||
|
||||
class Imgcodecs_Avif_Animation_WriteReadSuite
|
||||
: public Imgcodecs_Avif_Animation_RoundTripSuite {};
|
||||
|
||||
TEST_P(Imgcodecs_Avif_Animation_WriteReadSuite, encode_decode) {
|
||||
const std::vector<cv::Mat>& anim_original = get_anim_original();
|
||||
ASSERT_FALSE(anim_original.empty());
|
||||
|
||||
// Encode.
|
||||
const string output = cv::tempfile(".avif");
|
||||
if (!IsBitDepthValid()) {
|
||||
EXPECT_THROW(cv::imwritemulti(output, anim_original, encoding_params_),
|
||||
cv::Exception);
|
||||
EXPECT_NE(0, remove(output.c_str()));
|
||||
return;
|
||||
}
|
||||
EXPECT_NO_THROW(cv::imwritemulti(output, anim_original, encoding_params_));
|
||||
EXPECT_EQ(anim_original.size(), imcount(output));
|
||||
|
||||
// Read from file.
|
||||
std::vector<cv::Mat> anim;
|
||||
ASSERT_TRUE(cv::imreadmulti(output, anim, imread_mode_));
|
||||
|
||||
ValidateRead(anim_original, anim);
|
||||
|
||||
EXPECT_EQ(0, remove(output.c_str()));
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(
|
||||
Imgcodecs_AVIF, Imgcodecs_Avif_Animation_WriteReadSuite,
|
||||
::testing::Combine(::testing::ValuesIn({8, 10, 12}),
|
||||
::testing::ValuesIn({1, 3}), ::testing::ValuesIn({50}),
|
||||
::testing::ValuesIn({IMREAD_UNCHANGED, IMREAD_GRAYSCALE,
|
||||
IMREAD_COLOR, IMREAD_COLOR_RGB})));
|
||||
class Imgcodecs_Avif_Animation_WriteDecodeSuite
|
||||
: public Imgcodecs_Avif_Animation_RoundTripSuite {};
|
||||
|
||||
TEST_P(Imgcodecs_Avif_Animation_WriteDecodeSuite, encode_decode) {
|
||||
const std::vector<cv::Mat>& anim_original = get_anim_original();
|
||||
ASSERT_FALSE(anim_original.empty());
|
||||
|
||||
// Encode.
|
||||
const string output = cv::tempfile(".avif");
|
||||
if (!IsBitDepthValid()) {
|
||||
EXPECT_THROW(cv::imwritemulti(output, anim_original, encoding_params_),
|
||||
cv::Exception);
|
||||
EXPECT_NE(0, remove(output.c_str()));
|
||||
return;
|
||||
}
|
||||
EXPECT_NO_THROW(cv::imwritemulti(output, anim_original, encoding_params_));
|
||||
|
||||
// Put file into buffer and read from buffer.
|
||||
std::ifstream file(output, std::ios::binary | std::ios::ate);
|
||||
std::streamsize size = file.tellg();
|
||||
file.seekg(0, std::ios::beg);
|
||||
std::vector<unsigned char> buf(size);
|
||||
EXPECT_TRUE(file.read(reinterpret_cast<char*>(buf.data()), size));
|
||||
file.close();
|
||||
std::vector<cv::Mat> anim;
|
||||
ASSERT_TRUE(cv::imdecodemulti(buf, imread_mode_, anim));
|
||||
|
||||
ValidateRead(anim_original, anim);
|
||||
|
||||
if (imread_mode_ == IMREAD_UNCHANGED) {
|
||||
ImageCollection collection(output, IMREAD_UNCHANGED);
|
||||
anim.clear();
|
||||
for (auto&& i : collection)
|
||||
anim.push_back(i);
|
||||
ValidateRead(anim_original, anim);
|
||||
}
|
||||
|
||||
EXPECT_EQ(0, remove(output.c_str()));
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(
|
||||
Imgcodecs_AVIF, Imgcodecs_Avif_Animation_WriteDecodeSuite,
|
||||
::testing::Combine(::testing::ValuesIn({8, 10, 12}),
|
||||
::testing::ValuesIn({1, 3}), ::testing::ValuesIn({50}),
|
||||
::testing::ValuesIn({IMREAD_UNCHANGED, IMREAD_GRAYSCALE,
|
||||
IMREAD_COLOR, IMREAD_COLOR_RGB})));
|
||||
|
||||
} // namespace
|
||||
} // namespace opencv_test
|
||||
|
||||
#endif // HAVE_AVIF
|
||||
@@ -0,0 +1,61 @@
|
||||
// This file is part of OpenCV project.
|
||||
// It is subject to the license terms in the LICENSE file found in the top-level directory
|
||||
// of this distribution and at http://opencv.org/license.html
|
||||
#include "test_precomp.hpp"
|
||||
#include "test_common.hpp"
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace opencv_test { namespace {
|
||||
|
||||
// See https://github.com/opencv/opencv/issues/27789
|
||||
// See https://github.com/opencv/opencv/issues/23233
|
||||
TEST(Imgcodecs_BMP, encode_decode_over1GB_regression27789)
|
||||
{
|
||||
applyTestTag( CV_TEST_TAG_MEMORY_2GB, CV_TEST_TAG_LONG );
|
||||
|
||||
// Create large Mat over 1GB
|
||||
// 20000 px * 18000 px * 24 bpp(3ch) = 1,080,000,000 bytes
|
||||
// 1 GiB = 1,073,741,824 bytes
|
||||
cv::Mat src(20000, 18000, CV_8UC3, cv::Scalar(0,0,0));
|
||||
|
||||
// Encode large BMP file.
|
||||
std::vector<uint8_t> buf;
|
||||
bool ret = false;
|
||||
ASSERT_NO_THROW(ret = cv::imencode(".bmp", src, buf, {}));
|
||||
ASSERT_TRUE(ret);
|
||||
|
||||
src.release(); // To reduce usage memory, it is needed.
|
||||
|
||||
// Decode large BMP file.
|
||||
cv::Mat dst;
|
||||
ASSERT_NO_THROW(dst = cv::imdecode(buf, cv::IMREAD_COLOR));
|
||||
ASSERT_FALSE(dst.empty());
|
||||
}
|
||||
|
||||
TEST(Imgcodecs_BMP, write_read_over1GB_regression27789)
|
||||
{
|
||||
// tag CV_TEST_TAG_VERYLONG applied to skip on CI. The test writes ~1GB file.
|
||||
applyTestTag( CV_TEST_TAG_MEMORY_2GB, CV_TEST_TAG_VERYLONG );
|
||||
string bmpFilename = cv::tempfile(".bmp"); // To remove it, test must use EXPECT_* instead of ASSERT_*.
|
||||
|
||||
// Create large Mat over 1GB
|
||||
// 20000 px * 18000 px * 24 bpp(3ch) = 1,080,000,000 bytes
|
||||
// 1 GiB = 1,073,741,824 bytes
|
||||
cv::Mat src(20000, 18000, CV_8UC3, cv::Scalar(0,0,0));
|
||||
|
||||
// Write large BMP file.
|
||||
bool ret = false;
|
||||
EXPECT_NO_THROW(ret = cv::imwrite(bmpFilename, src, {}));
|
||||
EXPECT_TRUE(ret);
|
||||
|
||||
// Read large BMP file.
|
||||
cv::Mat dst;
|
||||
EXPECT_NO_THROW(dst = cv::imread(bmpFilename, cv::IMREAD_COLOR));
|
||||
EXPECT_FALSE(dst.empty());
|
||||
|
||||
remove(bmpFilename.c_str());
|
||||
}
|
||||
|
||||
|
||||
}} // namespace
|
||||
@@ -0,0 +1,68 @@
|
||||
// This file is part of OpenCV project.
|
||||
// It is subject to the license terms in the LICENSE file found in the top-level directory
|
||||
// of this distribution and at http://opencv.org/license.html
|
||||
#include "test_precomp.hpp"
|
||||
#include "test_common.hpp"
|
||||
|
||||
namespace opencv_test {
|
||||
|
||||
static
|
||||
Mat generateTestImageBGR_()
|
||||
{
|
||||
Size sz(640, 480);
|
||||
Mat result(sz, CV_8UC3, Scalar::all(0));
|
||||
|
||||
const string fname = cvtest::findDataFile("../cv/shared/baboon.png");
|
||||
Mat image = imread(fname, IMREAD_COLOR);
|
||||
CV_Assert(!image.empty());
|
||||
CV_CheckEQ(image.size(), Size(512, 512), "");
|
||||
Rect roi((640-512) / 2, 0, 512, 480);
|
||||
image(Rect(0, 0, 512, 480)).copyTo(result(roi));
|
||||
result(Rect(0, 0, 5, 5)).setTo(Scalar(0, 0, 255)); // R
|
||||
result(Rect(5, 0, 5, 5)).setTo(Scalar(0, 255, 0)); // G
|
||||
result(Rect(10, 0, 5, 5)).setTo(Scalar(255, 0, 0)); // B
|
||||
result(Rect(0, 5, 5, 5)).setTo(Scalar(128, 128, 128)); // gray
|
||||
//imshow("test_image", result); waitKey();
|
||||
return result;
|
||||
}
|
||||
Mat generateTestImageBGR()
|
||||
{
|
||||
static Mat image = generateTestImageBGR_(); // initialize once
|
||||
CV_Assert(!image.empty());
|
||||
return image;
|
||||
}
|
||||
|
||||
static
|
||||
Mat generateTestImageGrayscale_()
|
||||
{
|
||||
Mat imageBGR = generateTestImageBGR();
|
||||
CV_Assert(!imageBGR.empty());
|
||||
|
||||
Mat result;
|
||||
cvtColor(imageBGR, result, COLOR_BGR2GRAY);
|
||||
return result;
|
||||
}
|
||||
Mat generateTestImageGrayscale()
|
||||
{
|
||||
static Mat image = generateTestImageGrayscale_(); // initialize once
|
||||
return image;
|
||||
}
|
||||
|
||||
void readFileBytes(const std::string& fname, std::vector<unsigned char>& buf)
|
||||
{
|
||||
FILE * wfile = fopen(fname.c_str(), "rb");
|
||||
if (wfile != NULL)
|
||||
{
|
||||
fseek(wfile, 0, SEEK_END);
|
||||
size_t wfile_size = ftell(wfile);
|
||||
fseek(wfile, 0, SEEK_SET);
|
||||
|
||||
buf.resize(wfile_size);
|
||||
size_t data_size = fread(&buf[0], 1, wfile_size, wfile);
|
||||
fclose(wfile);
|
||||
|
||||
EXPECT_EQ(data_size, wfile_size);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
@@ -0,0 +1,16 @@
|
||||
// This file is part of OpenCV project.
|
||||
// It is subject to the license terms in the LICENSE file found in the top-level directory
|
||||
// of this distribution and at http://opencv.org/license.html
|
||||
|
||||
#ifndef OPENCV_TEST_IMGCODECS_COMMON_HPP
|
||||
#define OPENCV_TEST_IMGCODECS_COMMON_HPP
|
||||
|
||||
namespace opencv_test {
|
||||
|
||||
Mat generateTestImageBGR();
|
||||
Mat generateTestImageGrayscale();
|
||||
void readFileBytes(const std::string& fname, std::vector<unsigned char>& buf);
|
||||
|
||||
} // namespace
|
||||
|
||||
#endif // OPENCV_TEST_IMGCODECS_COMMON_HPP
|
||||
@@ -0,0 +1,711 @@
|
||||
// This file is part of OpenCV project.
|
||||
// It is subject to the license terms in the LICENSE file found in the top-level
|
||||
// directory of this distribution and at http://opencv.org/license.html
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "test_precomp.hpp"
|
||||
|
||||
namespace opencv_test { namespace {
|
||||
|
||||
static Mat makeCirclesImage(Size size, int type, int nbits)
|
||||
{
|
||||
Mat img(size, type);
|
||||
img.setTo(Scalar::all(0));
|
||||
RNG& rng = theRNG();
|
||||
int maxval = (int)(1 << nbits);
|
||||
for (int i = 0; i < 100; i++) {
|
||||
int x = rng.uniform(0, img.cols);
|
||||
int y = rng.uniform(0, img.rows);
|
||||
int radius = rng.uniform(5, std::min(img.cols, img.rows) / 5);
|
||||
int b = rng.uniform(0, maxval);
|
||||
int g = rng.uniform(0, maxval);
|
||||
int r = rng.uniform(0, maxval);
|
||||
circle(img, Point(x, y), radius, Scalar(b, g, r), -1, LINE_AA);
|
||||
}
|
||||
return img;
|
||||
}
|
||||
|
||||
static std::vector<uchar> getSampleExifData() {
|
||||
return {
|
||||
'M', 'M', 0, '*', 0, 0, 0, 8, 0, 10, 1, 0, 0, 4, 0, 0, 0, 1, 0, 0, 5,
|
||||
0, 1, 1, 0, 4, 0, 0, 0, 1, 0, 0, 2, 208, 1, 2, 0, 3, 0, 0, 0, 1,
|
||||
0, 10, 0, 0, 1, 18, 0, 3, 0, 0, 0, 1, 0, 1, 0, 0, 1, 14, 0, 2, 0, 0,
|
||||
0, '"', 0, 0, 0, 176, 1, '1', 0, 2, 0, 0, 0, 7, 0, 0, 0, 210, 1, 26,
|
||||
0, 5, 0, 0, 0, 1, 0, 0, 0, 218, 1, 27, 0, 5, 0, 0, 0, 1, 0, 0, 0,
|
||||
226, 1, '(', 0, 3, 0, 0, 0, 1, 0, 2, 0, 0, 135, 'i', 0, 4, 0, 0, 0,
|
||||
1, 0, 0, 0, 134, 0, 0, 0, 0, 0, 3, 144, 0, 0, 7, 0, 0, 0, 4, '0', '2',
|
||||
'2', '1', 160, 2, 0, 4, 0, 0, 0, 1, 0, 0, 5, 0, 160, 3, 0, 4, 0, 0,
|
||||
0, 1, 0, 0, 2, 208, 0, 0, 0, 0, 'S', 'a', 'm', 'p', 'l', 'e', ' ', '1', '0',
|
||||
'-', 'b', 'i', 't', ' ', 'i', 'm', 'a', 'g', 'e', ' ', 'w', 'i', 't', 'h', ' ',
|
||||
'm', 'e', 't', 'a', 'd', 'a', 't', 'a', 0, 'O', 'p', 'e', 'n', 'C', 'V', 0, 0,
|
||||
0, 0, 0, 'H', 0, 0, 0, 1, 0, 0, 0, 'H', 0, 0, 0, 1
|
||||
};
|
||||
}
|
||||
|
||||
static std::vector<uchar> getSampleXmpData() {
|
||||
return {
|
||||
'<','x',':','x','m','p','m','e','t','a',' ','x','m','l','n','s',':','x','=',
|
||||
'"','a','d','o','b','e',':','x','m','p','"','>',
|
||||
'<','x','m','p',':','C','r','e','a','t','o','r','T','o','o','l','>',
|
||||
'O','p','e','n','C','V',
|
||||
'<','/','x','m','p',':','C','r','e','a','t','o','r','T','o','o','l','>',
|
||||
'<','/','x',':','x','m','p','m','e','t','a','>',0
|
||||
};
|
||||
}
|
||||
|
||||
// Returns a Minimal ICC profile data (Generated with help from ChatGPT)
|
||||
static std::vector<uchar> getSampleIccpData() {
|
||||
std::vector<uchar> iccp_data(192, 0);
|
||||
|
||||
iccp_data[3] = 192; // Profile size: 192 bytes
|
||||
|
||||
iccp_data[12] = 'm';
|
||||
iccp_data[13] = 'n';
|
||||
iccp_data[14] = 't';
|
||||
iccp_data[15] = 'r';
|
||||
|
||||
iccp_data[16] = 'R';
|
||||
iccp_data[17] = 'G';
|
||||
iccp_data[18] = 'B';
|
||||
iccp_data[19] = ' ';
|
||||
|
||||
iccp_data[20] = 'X';
|
||||
iccp_data[21] = 'Y';
|
||||
iccp_data[22] = 'Z';
|
||||
iccp_data[23] = ' ';
|
||||
|
||||
// File signature 'acsp' at offset 36 (0x24)
|
||||
iccp_data[36] = 'a';
|
||||
iccp_data[37] = 'c';
|
||||
iccp_data[38] = 's';
|
||||
iccp_data[39] = 'p';
|
||||
|
||||
// Illuminant D50 at offset 68 (0x44), example values:
|
||||
iccp_data[68] = 0x00;
|
||||
iccp_data[69] = 0x00;
|
||||
iccp_data[70] = 0xF6;
|
||||
iccp_data[71] = 0xD6; // 0.9642
|
||||
iccp_data[72] = 0x00;
|
||||
iccp_data[73] = 0x01;
|
||||
iccp_data[74] = 0x00;
|
||||
iccp_data[75] = 0x00; // 1.0
|
||||
iccp_data[76] = 0x00;
|
||||
iccp_data[77] = 0x00;
|
||||
iccp_data[78] = 0xD3;
|
||||
iccp_data[79] = 0x2D; // 0.8249
|
||||
|
||||
// Tag count at offset 128 (0x80) = 1
|
||||
iccp_data[131] = 1;
|
||||
|
||||
// Tag record at offset 132 (0x84): signature 'desc', offset 128, size 64
|
||||
iccp_data[132] = 'd';
|
||||
iccp_data[133] = 'e';
|
||||
iccp_data[134] = 's';
|
||||
iccp_data[135] = 'c';
|
||||
|
||||
iccp_data[139] = 128; // offset
|
||||
|
||||
iccp_data[143] = 64; // size
|
||||
|
||||
// Tag data 'desc' at offset 128 (start of tag data)
|
||||
// Set type 'desc' etc. here, for simplicity fill zeros
|
||||
|
||||
iccp_data[144] = 'd';
|
||||
iccp_data[145] = 'e';
|
||||
iccp_data[146] = 's';
|
||||
iccp_data[147] = 'c';
|
||||
|
||||
// ASCII string length at offset 156
|
||||
iccp_data[156] = 20; // length
|
||||
|
||||
// ASCII string "Minimal ICC Profile" starting at offset 160
|
||||
iccp_data[160] = 'M';
|
||||
iccp_data[161] = 'i';
|
||||
iccp_data[162] = 'n';
|
||||
iccp_data[163] = 'i';
|
||||
iccp_data[164] = 'm';
|
||||
iccp_data[165] = 'a';
|
||||
iccp_data[166] = 'l';
|
||||
iccp_data[167] = ' ';
|
||||
iccp_data[168] = 'I';
|
||||
iccp_data[169] = 'C';
|
||||
iccp_data[170] = 'C';
|
||||
iccp_data[171] = ' ';
|
||||
iccp_data[172] = 'P';
|
||||
iccp_data[173] = 'r';
|
||||
iccp_data[174] = 'o';
|
||||
iccp_data[175] = 'f';
|
||||
iccp_data[176] = 'i';
|
||||
iccp_data[177] = 'l';
|
||||
iccp_data[178] = 'e';
|
||||
|
||||
return iccp_data;
|
||||
}
|
||||
|
||||
#ifdef OPENCV_IMGCODECS_PNG_WITH_cICP
|
||||
static std::vector<uchar> getSampleCicpData() {
|
||||
return {
|
||||
9, // BT.2020 / BT.2100
|
||||
16, // SMPTE ST 2084 (PQ)
|
||||
0, // Identity (RGB)
|
||||
1, // Full Range
|
||||
};
|
||||
}
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Test to check whether the EXIF orientation tag was processed successfully or not.
|
||||
* The test uses a set of 8 images named testExifOrientation_{1 to 8}.(extension).
|
||||
* Each test image is a 10x10 square, divided into four smaller sub-squares:
|
||||
* (R corresponds to Red, G to Green, B to Blue, W to White)
|
||||
* --------- ---------
|
||||
* | R | G | | G | R |
|
||||
* |-------| - (tag 1) |-------| - (tag 2)
|
||||
* | B | W | | W | B |
|
||||
* --------- ---------
|
||||
*
|
||||
* --------- ---------
|
||||
* | W | B | | B | W |
|
||||
* |-------| - (tag 3) |-------| - (tag 4)
|
||||
* | G | R | | R | G |
|
||||
* --------- ---------
|
||||
*
|
||||
* --------- ---------
|
||||
* | R | B | | G | W |
|
||||
* |-------| - (tag 5) |-------| - (tag 6)
|
||||
* | G | W | | R | B |
|
||||
* --------- ---------
|
||||
*
|
||||
* --------- ---------
|
||||
* | W | G | | B | R |
|
||||
* |-------| - (tag 7) |-------| - (tag 8)
|
||||
* | B | R | | W | G |
|
||||
* --------- ---------
|
||||
*
|
||||
*
|
||||
* Each image contains an EXIF field with an orientation tag (0x112).
|
||||
* After reading each image and applying the orientation tag,
|
||||
* the resulting image should be:
|
||||
* ---------
|
||||
* | R | G |
|
||||
* |-------|
|
||||
* | B | W |
|
||||
* ---------
|
||||
*
|
||||
* Note:
|
||||
* The flags parameter of the imread function is set as IMREAD_COLOR | IMREAD_ANYCOLOR | IMREAD_ANYDEPTH.
|
||||
* Using this combination is an undocumented trick to load images similarly to the IMREAD_UNCHANGED flag,
|
||||
* preserving the alpha channel (if present) while also applying the orientation.
|
||||
*/
|
||||
|
||||
typedef testing::TestWithParam<string> Exif;
|
||||
|
||||
TEST_P(Exif, exif_orientation)
|
||||
{
|
||||
const string root = cvtest::TS::ptr()->get_data_path();
|
||||
const string filename = root + GetParam();
|
||||
const int colorThresholdHigh = 250;
|
||||
const int colorThresholdLow = 5;
|
||||
|
||||
// Refer to the note in the explanation above.
|
||||
Mat m_img = imread(filename, IMREAD_COLOR | IMREAD_ANYCOLOR | IMREAD_ANYDEPTH);
|
||||
ASSERT_FALSE(m_img.empty());
|
||||
|
||||
if (m_img.channels() == 3)
|
||||
{
|
||||
Vec3b vec;
|
||||
|
||||
//Checking the first quadrant (with supposed red)
|
||||
vec = m_img.at<Vec3b>(2, 2); //some point inside the square
|
||||
EXPECT_LE(vec.val[0], colorThresholdLow);
|
||||
EXPECT_LE(vec.val[1], colorThresholdLow);
|
||||
EXPECT_GE(vec.val[2], colorThresholdHigh);
|
||||
|
||||
//Checking the second quadrant (with supposed green)
|
||||
vec = m_img.at<Vec3b>(2, 7); //some point inside the square
|
||||
EXPECT_LE(vec.val[0], colorThresholdLow);
|
||||
EXPECT_GE(vec.val[1], colorThresholdHigh);
|
||||
EXPECT_LE(vec.val[2], colorThresholdLow);
|
||||
|
||||
//Checking the third quadrant (with supposed blue)
|
||||
vec = m_img.at<Vec3b>(7, 2); //some point inside the square
|
||||
EXPECT_GE(vec.val[0], colorThresholdHigh);
|
||||
EXPECT_LE(vec.val[1], colorThresholdLow);
|
||||
EXPECT_LE(vec.val[2], colorThresholdLow);
|
||||
}
|
||||
else
|
||||
{
|
||||
Vec4b vec;
|
||||
|
||||
//Checking the first quadrant (with supposed red)
|
||||
vec = m_img.at<Vec4b>(2, 2); //some point inside the square
|
||||
EXPECT_LE(vec.val[0], colorThresholdLow);
|
||||
EXPECT_LE(vec.val[1], colorThresholdLow);
|
||||
EXPECT_GE(vec.val[2], colorThresholdHigh);
|
||||
|
||||
//Checking the second quadrant (with supposed green)
|
||||
vec = m_img.at<Vec4b>(2, 7); //some point inside the square
|
||||
EXPECT_LE(vec.val[0], colorThresholdLow);
|
||||
EXPECT_GE(vec.val[1], colorThresholdHigh);
|
||||
EXPECT_LE(vec.val[2], colorThresholdLow);
|
||||
|
||||
//Checking the third quadrant (with supposed blue)
|
||||
vec = m_img.at<Vec4b>(7, 2); //some point inside the square
|
||||
EXPECT_GE(vec.val[0], colorThresholdHigh);
|
||||
EXPECT_LE(vec.val[1], colorThresholdLow);
|
||||
EXPECT_LE(vec.val[2], colorThresholdLow);
|
||||
}
|
||||
}
|
||||
|
||||
const std::vector<std::string> exif_files
|
||||
{
|
||||
#ifdef HAVE_JPEG
|
||||
"readwrite/testExifOrientation_1.jpg",
|
||||
"readwrite/testExifOrientation_2.jpg",
|
||||
"readwrite/testExifOrientation_3.jpg",
|
||||
"readwrite/testExifOrientation_4.jpg",
|
||||
"readwrite/testExifOrientation_5.jpg",
|
||||
"readwrite/testExifOrientation_6.jpg",
|
||||
"readwrite/testExifOrientation_7.jpg",
|
||||
"readwrite/testExifOrientation_8.jpg",
|
||||
#endif
|
||||
#ifdef OPENCV_IMGCODECS_PNG_WITH_EXIF
|
||||
"readwrite/testExifOrientation_1.png",
|
||||
"readwrite/testExifOrientation_2.png",
|
||||
"readwrite/testExifOrientation_3.png",
|
||||
"readwrite/testExifOrientation_4.png",
|
||||
"readwrite/testExifOrientation_5.png",
|
||||
"readwrite/testExifOrientation_6.png",
|
||||
"readwrite/testExifOrientation_7.png",
|
||||
"readwrite/testExifOrientation_8.png",
|
||||
#endif
|
||||
#ifdef HAVE_AVIF
|
||||
"readwrite/testExifOrientation_1.avif",
|
||||
"readwrite/testExifOrientation_2.avif",
|
||||
"readwrite/testExifOrientation_3.avif",
|
||||
"readwrite/testExifOrientation_4.avif",
|
||||
"readwrite/testExifOrientation_5.avif",
|
||||
"readwrite/testExifOrientation_6.avif",
|
||||
"readwrite/testExifOrientation_7.avif",
|
||||
"readwrite/testExifOrientation_8.avif",
|
||||
#endif
|
||||
#ifdef HAVE_WEBP
|
||||
"readwrite/testExifOrientation_1.webp",
|
||||
"readwrite/testExifOrientation_2.webp",
|
||||
"readwrite/testExifOrientation_3.webp",
|
||||
"readwrite/testExifOrientation_4.webp",
|
||||
"readwrite/testExifOrientation_5.webp",
|
||||
"readwrite/testExifOrientation_6.webp",
|
||||
"readwrite/testExifOrientation_7.webp",
|
||||
"readwrite/testExifOrientation_8.webp",
|
||||
#endif
|
||||
};
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(Imgcodecs, Exif,
|
||||
testing::ValuesIn(exif_files));
|
||||
|
||||
#ifdef HAVE_AVIF
|
||||
typedef testing::TestWithParam<int> MatChannels;
|
||||
|
||||
TEST_P(MatChannels, Imgcodecs_Avif_ReadWriteWithExif)
|
||||
{
|
||||
int avif_nbits = 10;
|
||||
int avif_speed = 10;
|
||||
int avif_quality = 85;
|
||||
int imgdepth = avif_nbits > 8 ? CV_16U : CV_8U;
|
||||
int imgtype = CV_MAKETYPE(imgdepth, GetParam());
|
||||
const string outputname = cv::tempfile(".avif");
|
||||
Mat img = makeCirclesImage(Size(1280, 720), imgtype, avif_nbits);
|
||||
|
||||
std::vector<int> metadata_types = {IMAGE_METADATA_EXIF};
|
||||
std::vector<std::vector<uchar>> metadata = {
|
||||
getSampleExifData() };
|
||||
|
||||
std::vector<int> write_params = {
|
||||
IMWRITE_AVIF_DEPTH, avif_nbits,
|
||||
IMWRITE_AVIF_SPEED, avif_speed,
|
||||
IMWRITE_AVIF_QUALITY, avif_quality
|
||||
};
|
||||
|
||||
imwriteWithMetadata(outputname, img, metadata_types, metadata, write_params);
|
||||
std::vector<uchar> compressed;
|
||||
imencodeWithMetadata(outputname, img, metadata_types, metadata, compressed, write_params);
|
||||
|
||||
std::vector<int> read_metadata_types, read_metadata_types2;
|
||||
std::vector<std::vector<uchar> > read_metadata, read_metadata2;
|
||||
Mat img2 = imreadWithMetadata(outputname, read_metadata_types, read_metadata, IMREAD_UNCHANGED);
|
||||
Mat img3 = imdecodeWithMetadata(compressed, read_metadata_types2, read_metadata2, IMREAD_UNCHANGED);
|
||||
EXPECT_EQ(img2.cols, img.cols);
|
||||
EXPECT_EQ(img2.rows, img.rows);
|
||||
EXPECT_EQ(img2.type(), imgtype);
|
||||
EXPECT_EQ(read_metadata_types, read_metadata_types2);
|
||||
ASSERT_GE(read_metadata_types.size(), 1u);
|
||||
EXPECT_EQ(read_metadata, read_metadata2);
|
||||
EXPECT_EQ(read_metadata_types[0], IMAGE_METADATA_EXIF);
|
||||
EXPECT_EQ(read_metadata_types.size(), read_metadata.size());
|
||||
EXPECT_EQ(read_metadata[0], metadata[0]);
|
||||
EXPECT_EQ(cv::norm(img2, img3, NORM_INF), 0.);
|
||||
double mse = cv::norm(img, img2, NORM_L2SQR)/(img.rows*img.cols);
|
||||
EXPECT_LT(mse, 1500);
|
||||
remove(outputname.c_str());
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(Imgcodecs, MatChannels,
|
||||
testing::Values(1,3,4));
|
||||
#endif // HAVE_AVIF
|
||||
|
||||
#ifdef HAVE_WEBP
|
||||
TEST(Imgcodecs_WebP, Read_Write_With_Exif_Xmp_Iccp)
|
||||
{
|
||||
int imgtype = CV_MAKETYPE(CV_8U, 3);
|
||||
const std::string outputname = cv::tempfile(".webp");
|
||||
cv::Mat img = makeCirclesImage(cv::Size(160, 120), imgtype, 8);
|
||||
|
||||
std::vector<int> metadata_types = {IMAGE_METADATA_EXIF, IMAGE_METADATA_XMP, IMAGE_METADATA_ICCP};
|
||||
std::vector<std::vector<uchar>> metadata = {
|
||||
getSampleExifData(),
|
||||
getSampleXmpData(),
|
||||
getSampleIccpData()
|
||||
};
|
||||
|
||||
int webp_quality = 101; // 101 is lossless compression
|
||||
std::vector<int> write_params = {IMWRITE_WEBP_QUALITY, webp_quality};
|
||||
|
||||
imwriteWithMetadata(outputname, img, metadata_types, metadata, write_params);
|
||||
|
||||
std::vector<uchar> compressed;
|
||||
imencodeWithMetadata(outputname, img, metadata_types, metadata, compressed, write_params);
|
||||
|
||||
std::vector<int> read_metadata_types, read_metadata_types2;
|
||||
std::vector<std::vector<uchar>> read_metadata, read_metadata2;
|
||||
|
||||
cv::Mat img2 = imreadWithMetadata(outputname, read_metadata_types, read_metadata, cv::IMREAD_UNCHANGED);
|
||||
cv::Mat img3 = imdecodeWithMetadata(compressed, read_metadata_types2, read_metadata2, cv::IMREAD_UNCHANGED);
|
||||
|
||||
EXPECT_EQ(img2.cols, img.cols);
|
||||
EXPECT_EQ(img2.rows, img.rows);
|
||||
EXPECT_EQ(img2.type(), imgtype);
|
||||
|
||||
EXPECT_EQ(read_metadata_types, read_metadata_types2);
|
||||
EXPECT_EQ(read_metadata_types.size(), 3u);
|
||||
|
||||
EXPECT_EQ(read_metadata, read_metadata2);
|
||||
EXPECT_EQ(read_metadata, metadata);
|
||||
|
||||
EXPECT_EQ(cv::norm(img2, img3, cv::NORM_INF), 0.0);
|
||||
|
||||
double mse = cv::norm(img, img2, cv::NORM_L2SQR) / (img.rows * img.cols);
|
||||
EXPECT_EQ(mse, 0);
|
||||
|
||||
remove(outputname.c_str());
|
||||
}
|
||||
#endif // HAVE_WEBP
|
||||
|
||||
TEST(Imgcodecs_Jpeg, Read_Write_With_Exif)
|
||||
{
|
||||
int jpeg_quality = 95;
|
||||
int imgtype = CV_MAKETYPE(CV_8U, 3);
|
||||
const string outputname = cv::tempfile(".jpeg");
|
||||
Mat img = makeCirclesImage(Size(1280, 720), imgtype, 8);
|
||||
|
||||
std::vector<int> metadata_types = {IMAGE_METADATA_EXIF};
|
||||
std::vector<std::vector<uchar>> metadata = {
|
||||
getSampleExifData() };
|
||||
|
||||
std::vector<int> write_params = {
|
||||
IMWRITE_JPEG_QUALITY, jpeg_quality
|
||||
};
|
||||
|
||||
imwriteWithMetadata(outputname, img, metadata_types, metadata, write_params);
|
||||
std::vector<uchar> compressed;
|
||||
imencodeWithMetadata(outputname, img, metadata_types, metadata, compressed, write_params);
|
||||
|
||||
std::vector<int> read_metadata_types, read_metadata_types2;
|
||||
std::vector<std::vector<uchar> > read_metadata, read_metadata2;
|
||||
Mat img2 = imreadWithMetadata(outputname, read_metadata_types, read_metadata, IMREAD_UNCHANGED);
|
||||
Mat img3 = imdecodeWithMetadata(compressed, read_metadata_types2, read_metadata2, IMREAD_UNCHANGED);
|
||||
EXPECT_EQ(img2.cols, img.cols);
|
||||
EXPECT_EQ(img2.rows, img.rows);
|
||||
EXPECT_EQ(img2.type(), imgtype);
|
||||
EXPECT_EQ(read_metadata_types, read_metadata_types2);
|
||||
EXPECT_GE(read_metadata_types.size(), 1u);
|
||||
EXPECT_EQ(read_metadata, read_metadata2);
|
||||
EXPECT_EQ(read_metadata_types[0], IMAGE_METADATA_EXIF);
|
||||
EXPECT_EQ(read_metadata_types.size(), read_metadata.size());
|
||||
EXPECT_EQ(read_metadata[0], metadata[0]);
|
||||
EXPECT_EQ(cv::norm(img2, img3, NORM_INF), 0.);
|
||||
double mse = cv::norm(img, img2, NORM_L2SQR)/(img.rows*img.cols);
|
||||
EXPECT_LT(mse, 80);
|
||||
remove(outputname.c_str());
|
||||
}
|
||||
|
||||
TEST(Imgcodecs_Png, Read_Write_With_Exif)
|
||||
{
|
||||
int png_compression = 3;
|
||||
int imgtype = CV_MAKETYPE(CV_8U, 3);
|
||||
const string outputname = cv::tempfile(".png");
|
||||
Mat img = makeCirclesImage(Size(160, 120), imgtype, 8);
|
||||
|
||||
std::vector<int> metadata_types = {IMAGE_METADATA_EXIF};
|
||||
std::vector<std::vector<uchar>> metadata = {
|
||||
getSampleExifData() };
|
||||
|
||||
std::vector<int> write_params = {
|
||||
IMWRITE_PNG_COMPRESSION, png_compression
|
||||
};
|
||||
|
||||
imwriteWithMetadata(outputname, img, metadata_types, metadata, write_params);
|
||||
std::vector<uchar> compressed;
|
||||
imencodeWithMetadata(outputname, img, metadata_types, metadata, compressed, write_params);
|
||||
|
||||
std::vector<int> read_metadata_types, read_metadata_types2;
|
||||
std::vector<std::vector<uchar> > read_metadata, read_metadata2;
|
||||
Mat img2 = imreadWithMetadata(outputname, read_metadata_types, read_metadata, IMREAD_UNCHANGED);
|
||||
Mat img3 = imdecodeWithMetadata(compressed, read_metadata_types2, read_metadata2, IMREAD_UNCHANGED);
|
||||
EXPECT_EQ(img2.cols, img.cols);
|
||||
EXPECT_EQ(img2.rows, img.rows);
|
||||
EXPECT_EQ(img2.type(), imgtype);
|
||||
EXPECT_EQ(read_metadata_types, read_metadata_types2);
|
||||
|
||||
#ifdef OPENCV_IMGCODECS_PNG_WITH_EXIF
|
||||
ASSERT_GE(read_metadata_types.size(), 1u);
|
||||
EXPECT_EQ(read_metadata, read_metadata2);
|
||||
EXPECT_EQ(read_metadata_types[0], IMAGE_METADATA_EXIF);
|
||||
EXPECT_EQ(read_metadata_types.size(), read_metadata.size());
|
||||
EXPECT_EQ(read_metadata[0], metadata[0]);
|
||||
#else
|
||||
ASSERT_GE(read_metadata_types.size(), 0u);
|
||||
#endif
|
||||
EXPECT_EQ(cv::norm(img2, img3, NORM_INF), 0.);
|
||||
double mse = cv::norm(img, img2, NORM_L2SQR)/(img.rows*img.cols);
|
||||
EXPECT_EQ(mse, 0); // png is lossless
|
||||
remove(outputname.c_str());
|
||||
}
|
||||
|
||||
#ifdef OPENCV_IMGCODECS_PNG_WITH_cICP
|
||||
TEST(Imgcodecs_Png, Read_Write_With_Exif_Xmp_Iccp_cICP)
|
||||
#else
|
||||
TEST(Imgcodecs_Png, Read_Write_With_Exif_Xmp_Iccp)
|
||||
#endif
|
||||
{
|
||||
int png_compression = 3;
|
||||
int imgtype = CV_MAKETYPE(CV_8U, 3);
|
||||
const string outputname = cv::tempfile(".png");
|
||||
Mat img = makeCirclesImage(Size(160, 120), imgtype, 8);
|
||||
|
||||
std::vector<int> metadata_types = { IMAGE_METADATA_EXIF, IMAGE_METADATA_XMP, IMAGE_METADATA_ICCP };
|
||||
std::vector<std::vector<uchar>> metadata = {
|
||||
getSampleExifData(),
|
||||
getSampleXmpData(),
|
||||
getSampleIccpData(),
|
||||
};
|
||||
|
||||
#ifdef OPENCV_IMGCODECS_PNG_WITH_cICP
|
||||
metadata_types.push_back(IMAGE_METADATA_CICP);
|
||||
metadata.push_back(getSampleCicpData());
|
||||
#endif
|
||||
|
||||
std::vector<int> write_params = {
|
||||
IMWRITE_PNG_COMPRESSION, png_compression
|
||||
};
|
||||
|
||||
imwriteWithMetadata(outputname, img, metadata_types, metadata, write_params);
|
||||
std::vector<uchar> compressed;
|
||||
imencodeWithMetadata(outputname, img, metadata_types, metadata, compressed, write_params);
|
||||
|
||||
std::vector<int> read_metadata_types, read_metadata_types2;
|
||||
std::vector<std::vector<uchar> > read_metadata, read_metadata2;
|
||||
Mat img2 = imreadWithMetadata(outputname, read_metadata_types, read_metadata, IMREAD_UNCHANGED);
|
||||
Mat img3 = imdecodeWithMetadata(compressed, read_metadata_types2, read_metadata2, IMREAD_UNCHANGED);
|
||||
EXPECT_EQ(img2.cols, img.cols);
|
||||
EXPECT_EQ(img2.rows, img.rows);
|
||||
EXPECT_EQ(img2.type(), imgtype);
|
||||
|
||||
#ifdef OPENCV_IMGCODECS_PNG_WITH_EXIF
|
||||
EXPECT_EQ(metadata_types, read_metadata_types);
|
||||
EXPECT_EQ(read_metadata_types, read_metadata_types2);
|
||||
EXPECT_EQ(metadata, read_metadata);
|
||||
#else
|
||||
ASSERT_GE(read_metadata_types.size(), 2u);
|
||||
EXPECT_EQ(read_metadata_types[0], IMAGE_METADATA_XMP);
|
||||
EXPECT_EQ(read_metadata_types[1], IMAGE_METADATA_ICCP);
|
||||
|
||||
ASSERT_GE(read_metadata_types2.size(), 2u);
|
||||
EXPECT_EQ(read_metadata_types2[0], IMAGE_METADATA_XMP);
|
||||
EXPECT_EQ(read_metadata_types2[1], IMAGE_METADATA_ICCP);
|
||||
|
||||
ASSERT_GE(read_metadata.size(), 2u);
|
||||
EXPECT_EQ(metadata[1], read_metadata[0]);
|
||||
EXPECT_EQ(metadata[2], read_metadata[1]);
|
||||
#endif
|
||||
remove(outputname.c_str());
|
||||
}
|
||||
|
||||
TEST(Imgcodecs_Png, Read_Exif_From_Text)
|
||||
{
|
||||
const string root = cvtest::TS::ptr()->get_data_path();
|
||||
const string filename = root + "../perf/320x260.png";
|
||||
const string dst_file = cv::tempfile(".png");
|
||||
|
||||
std::vector<uchar> exif_data =
|
||||
{ 'M' , 'M' , 0, '*' , 0, 0, 0, 8, 0, 4, 1,
|
||||
26, 0, 5, 0, 0, 0, 1, 0, 0, 0, 62, 1, 27, 0, 5, 0, 0, 0, 1, 0, 0, 0,
|
||||
70, 1, 40, 0, 3, 0, 0, 0, 1, 0, 2, 0, 0, 1, 49, 0, 2, 0, 0, 0, 18, 0,
|
||||
0, 0, 78, 0, 0, 0, 0, 0, 0, 0, 96, 0, 0, 0, 1, 0, 0, 0, 96, 0, 0, 0,
|
||||
1, 80, 97, 105, 110, 116, 46, 78, 69, 84, 32, 118, 51, 46, 53, 46, 49, 48, 0
|
||||
};
|
||||
|
||||
std::vector<int> read_metadata_types;
|
||||
std::vector<std::vector<uchar> > read_metadata;
|
||||
Mat img = imreadWithMetadata(filename, read_metadata_types, read_metadata, IMREAD_GRAYSCALE);
|
||||
|
||||
std::vector<int> metadata_types = { IMAGE_METADATA_EXIF };
|
||||
EXPECT_EQ(read_metadata_types, metadata_types);
|
||||
EXPECT_EQ(read_metadata[0], exif_data);
|
||||
}
|
||||
|
||||
static uint32_t pngCrc32(const uchar* data, size_t len)
|
||||
{
|
||||
uint32_t crc = 0xFFFFFFFFu;
|
||||
for (size_t i = 0; i < len; i++)
|
||||
{
|
||||
crc ^= data[i];
|
||||
for (int k = 0; k < 8; k++)
|
||||
crc = (crc & 1u) ? ((crc >> 1) ^ 0xEDB88320u) : (crc >> 1);
|
||||
}
|
||||
return crc ^ 0xFFFFFFFFu;
|
||||
}
|
||||
|
||||
static void pngAppendBE32(std::vector<uchar>& v, uint32_t x)
|
||||
{
|
||||
v.push_back((uchar)(x >> 24)); v.push_back((uchar)(x >> 16));
|
||||
v.push_back((uchar)(x >> 8)); v.push_back((uchar)x);
|
||||
}
|
||||
|
||||
// Regression: a PNG "Raw profile type exif" text chunk whose declared length is
|
||||
// far larger than the payload it carries must be rejected (no multi-GB
|
||||
// speculative allocation / no out-of-bounds read in ExifReader::processRawProfile)
|
||||
// while the image itself still decodes.
|
||||
TEST(Imgcodecs_Png, Read_Exif_From_Text_oversized_length_rejected)
|
||||
{
|
||||
Mat img(8, 8, CV_8UC3, Scalar(10, 20, 30));
|
||||
std::vector<uchar> png;
|
||||
ASSERT_TRUE(imencode(".png", img, png));
|
||||
ASSERT_GT(png.size(), 33u); // 8-byte signature + 25-byte IHDR chunk
|
||||
|
||||
const std::string keyword = "Raw profile type exif";
|
||||
const std::string profile = "\nexif\n999999999\n41414141\n"; // 9e8 declared, tiny payload
|
||||
std::vector<uchar> data(keyword.begin(), keyword.end());
|
||||
data.push_back(0); // keyword / text separator
|
||||
data.insert(data.end(), profile.begin(), profile.end());
|
||||
|
||||
std::vector<uchar> chunk;
|
||||
pngAppendBE32(chunk, (uint32_t)data.size());
|
||||
const char type[4] = { 't', 'E', 'X', 't' };
|
||||
chunk.insert(chunk.end(), type, type + 4);
|
||||
chunk.insert(chunk.end(), data.begin(), data.end());
|
||||
std::vector<uchar> crc_input(type, type + 4);
|
||||
crc_input.insert(crc_input.end(), data.begin(), data.end());
|
||||
pngAppendBE32(chunk, pngCrc32(crc_input.data(), crc_input.size()));
|
||||
|
||||
// splice the tEXt chunk right after IHDR (valid placement for ancillary chunks)
|
||||
png.insert(png.begin() + 33, chunk.begin(), chunk.end());
|
||||
|
||||
std::vector<int> metadata_types;
|
||||
std::vector<std::vector<uchar> > metadata;
|
||||
Mat decoded;
|
||||
ASSERT_NO_THROW(decoded = imdecodeWithMetadata(png, metadata_types, metadata, IMREAD_COLOR));
|
||||
ASSERT_FALSE(decoded.empty());
|
||||
EXPECT_EQ(decoded.rows, 8);
|
||||
EXPECT_EQ(decoded.cols, 8);
|
||||
// the malformed profile must not produce EXIF metadata
|
||||
for (size_t i = 0; i < metadata_types.size(); i++)
|
||||
EXPECT_NE(metadata_types[i], IMAGE_METADATA_EXIF);
|
||||
}
|
||||
|
||||
static size_t locateString(const uchar* exif, size_t exif_size, const std::string& pattern)
|
||||
{
|
||||
size_t plen = pattern.size();
|
||||
for (size_t i = 0; i + plen <= exif_size; i++) {
|
||||
if (exif[i] == pattern[0] && memcmp(&exif[i], pattern.c_str(), plen) == 0)
|
||||
return i;
|
||||
}
|
||||
return 0xFFFFFFFFu;
|
||||
}
|
||||
|
||||
typedef std::tuple<std::string, size_t, std::string, size_t, size_t, size_t> ReadExif_Sanity_Params;
|
||||
typedef testing::TestWithParam<ReadExif_Sanity_Params> ReadExif_Sanity;
|
||||
|
||||
TEST_P(ReadExif_Sanity, Check)
|
||||
{
|
||||
std::string filename = get<0>(GetParam());
|
||||
size_t exif_size = get<1>(GetParam());
|
||||
std::string pattern = get<2>(GetParam());
|
||||
size_t ploc = get<3>(GetParam());
|
||||
size_t expected_xmp_size = get<4>(GetParam());
|
||||
size_t expected_iccp_size = get<5>(GetParam());
|
||||
|
||||
const string root = cvtest::TS::ptr()->get_data_path();
|
||||
filename = root + filename;
|
||||
|
||||
std::vector<int> metadata_types, metadata_types2;
|
||||
std::vector<std::vector<uchar> > metadata, metadata2;
|
||||
Mat img = imreadWithMetadata(filename, metadata_types, metadata);
|
||||
|
||||
std::vector<uchar> compressed;
|
||||
imencodeWithMetadata(".jpg", img, metadata_types, metadata, compressed);
|
||||
img = imdecodeWithMetadata(compressed, metadata_types2, metadata2);
|
||||
|
||||
EXPECT_EQ(metadata_types, metadata_types2);
|
||||
EXPECT_EQ(metadata, metadata2);
|
||||
|
||||
EXPECT_EQ(img.type(), CV_8UC3);
|
||||
ASSERT_GE(metadata_types.size(), 1u);
|
||||
EXPECT_EQ(metadata_types.size(), metadata.size());
|
||||
const Mat exif = Mat(metadata[IMAGE_METADATA_EXIF]);
|
||||
EXPECT_EQ(exif.type(), CV_8U);
|
||||
EXPECT_EQ(exif.total(), exif_size);
|
||||
ASSERT_GE(exif_size, 26u); // minimal exif should take at least 26 bytes
|
||||
// (the header + IDF0 with at least 1 entry).
|
||||
EXPECT_TRUE(exif.data[0] == 'I' || exif.data[0] == 'M');
|
||||
EXPECT_EQ(exif.data[0], exif.data[1]);
|
||||
EXPECT_EQ(locateString(exif.data, exif_size, pattern), ploc);
|
||||
|
||||
if (metadata_types.size() > IMAGE_METADATA_XMP)
|
||||
{
|
||||
const Mat xmp = Mat(metadata[IMAGE_METADATA_XMP]);
|
||||
EXPECT_EQ(xmp.type(), CV_8U);
|
||||
EXPECT_GT(xmp.total(), 0u);
|
||||
size_t xmp_size = xmp.total() * xmp.elemSize();
|
||||
EXPECT_EQ(expected_xmp_size, xmp_size);
|
||||
}
|
||||
|
||||
if (metadata_types.size() > IMAGE_METADATA_ICCP)
|
||||
{
|
||||
const Mat iccp = Mat(metadata[IMAGE_METADATA_ICCP]);
|
||||
EXPECT_EQ(iccp.type(), CV_8U);
|
||||
EXPECT_GT(iccp.total(), 0u);
|
||||
size_t iccp_size = iccp.total() * iccp.elemSize();
|
||||
EXPECT_EQ(expected_iccp_size, iccp_size);
|
||||
}
|
||||
}
|
||||
|
||||
static const std::vector<ReadExif_Sanity_Params> exif_sanity_params
|
||||
{
|
||||
#ifdef HAVE_JPEG
|
||||
ReadExif_Sanity_Params("readwrite/testExifOrientation_3.jpg", 916, "Photoshop", 120, 3597, 940),
|
||||
#endif
|
||||
#ifdef OPENCV_IMGCODECS_PNG_WITH_EXIF
|
||||
ReadExif_Sanity_Params("readwrite/testExifOrientation_5.png", 112, "ExifTool", 102, 505, 0),
|
||||
#endif
|
||||
#ifdef HAVE_AVIF
|
||||
ReadExif_Sanity_Params("readwrite/testExifOrientation_7.avif", 913, "Photoshop", 120, 3597, 940),
|
||||
#endif
|
||||
};
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(Imgcodecs, ReadExif_Sanity,
|
||||
testing::ValuesIn(exif_sanity_params));
|
||||
|
||||
}}
|
||||
@@ -0,0 +1,375 @@
|
||||
// This file is part of OpenCV project.
|
||||
// It is subject to the license terms in the LICENSE file found in the top-level directory
|
||||
// of this distribution and at http://opencv.org/license.html
|
||||
|
||||
//#define GENERATE_DATA
|
||||
|
||||
namespace opencv_test { namespace {
|
||||
|
||||
size_t getFileSize(const string& filename)
|
||||
{
|
||||
std::ifstream ifs(filename.c_str(), std::ios::in | std::ios::binary);
|
||||
if (ifs.is_open())
|
||||
{
|
||||
ifs.seekg(0, std::ios::end);
|
||||
return (size_t)ifs.tellg();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
TEST(Imgcodecs_EXR, readWrite_32FC1)
|
||||
{ // Y channels
|
||||
const string root = cvtest::TS::ptr()->get_data_path();
|
||||
const string filenameInput = root + "readwrite/test32FC1.exr";
|
||||
const string filenameOutput = cv::tempfile(".exr");
|
||||
#ifndef GENERATE_DATA
|
||||
const Mat img = cv::imread(filenameInput, IMREAD_UNCHANGED);
|
||||
#else
|
||||
const Size sz(64, 32);
|
||||
Mat img(sz, CV_32FC1, Scalar(0.5, 0.1, 1));
|
||||
img(Rect(10, 5, sz.width - 30, sz.height - 20)).setTo(Scalar(1, 0, 0));
|
||||
ASSERT_TRUE(cv::imwrite(filenameInput, img));
|
||||
#endif
|
||||
ASSERT_FALSE(img.empty());
|
||||
ASSERT_EQ(CV_32FC1,img.type());
|
||||
|
||||
ASSERT_TRUE(cv::imwrite(filenameOutput, img));
|
||||
// Check generated file size to ensure that it's compressed with proper options
|
||||
ASSERT_LE(396u, getFileSize(filenameOutput)); // OpenEXR 2
|
||||
ASSERT_LE( getFileSize(filenameOutput), 440u); // OpenEXR 3.2+
|
||||
const Mat img2 = cv::imread(filenameOutput, IMREAD_UNCHANGED);
|
||||
ASSERT_EQ(img2.type(), img.type());
|
||||
ASSERT_EQ(img2.size(), img.size());
|
||||
EXPECT_LE(cvtest::norm(img, img2, NORM_INF | NORM_RELATIVE), 1e-3);
|
||||
EXPECT_EQ(0, remove(filenameOutput.c_str()));
|
||||
}
|
||||
|
||||
TEST(Imgcodecs_EXR, readWrite_32FC3)
|
||||
{ // RGB channels
|
||||
const string root = cvtest::TS::ptr()->get_data_path();
|
||||
const string filenameInput = root + "readwrite/test32FC3.exr";
|
||||
const string filenameOutput = cv::tempfile(".exr");
|
||||
#ifndef GENERATE_DATA
|
||||
const Mat img = cv::imread(filenameInput, IMREAD_UNCHANGED);
|
||||
#else
|
||||
const Size sz(64, 32);
|
||||
Mat img(sz, CV_32FC3, Scalar(0.5, 0.1, 1));
|
||||
img(Rect(10, 5, sz.width - 30, sz.height - 20)).setTo(Scalar(1, 0, 0));
|
||||
ASSERT_TRUE(cv::imwrite(filenameInput, img));
|
||||
#endif
|
||||
ASSERT_FALSE(img.empty());
|
||||
ASSERT_EQ(CV_32FC3, img.type());
|
||||
|
||||
ASSERT_TRUE(cv::imwrite(filenameOutput, img));
|
||||
const Mat img2 = cv::imread(filenameOutput, IMREAD_UNCHANGED);
|
||||
ASSERT_EQ(img2.type(), img.type());
|
||||
ASSERT_EQ(img2.size(), img.size());
|
||||
EXPECT_LE(cvtest::norm(img, img2, NORM_INF | NORM_RELATIVE), 1e-3);
|
||||
EXPECT_EQ(0, remove(filenameOutput.c_str()));
|
||||
}
|
||||
|
||||
TEST(Imgcodecs_EXR, readWrite_32FC7)
|
||||
{ // 0-6 channels (multispectral)
|
||||
const string root = cvtest::TS::ptr()->get_data_path();
|
||||
const string filenameInput = root + "readwrite/test32FC7.exr";
|
||||
const string filenameOutput = cv::tempfile(".exr");
|
||||
#ifndef GENERATE_DATA
|
||||
const Mat img = cv::imread(filenameInput, IMREAD_UNCHANGED);
|
||||
#else
|
||||
const Size sz(3, 5);
|
||||
Mat img(sz, CV_32FC7);
|
||||
img.at<cv::Vec<float, 7>>(0, 0)[0] = 101.125;
|
||||
img.at<cv::Vec<float, 7>>(2, 1)[3] = 203.500;
|
||||
img.at<cv::Vec<float, 7>>(4, 2)[6] = 305.875;
|
||||
ASSERT_TRUE(cv::imwrite(filenameInput, img));
|
||||
#endif
|
||||
ASSERT_FALSE(img.empty());
|
||||
ASSERT_EQ(CV_MAKETYPE(CV_32F, 7), img.type());
|
||||
|
||||
ASSERT_TRUE(cv::imwrite(filenameOutput, img));
|
||||
const Mat img2 = cv::imread(filenameOutput, IMREAD_UNCHANGED);
|
||||
EXPECT_EQ(img2.type(), img.type());
|
||||
EXPECT_EQ(img2.size(), img.size());
|
||||
EXPECT_LE(cvtest::norm(img, img2, NORM_INF | NORM_RELATIVE), 1e-3);
|
||||
EXPECT_EQ(0, remove(filenameOutput.c_str()));
|
||||
const Mat img3 = cv::imread(filenameInput, IMREAD_GRAYSCALE);
|
||||
ASSERT_TRUE(img3.empty());
|
||||
const Mat img4 = cv::imread(filenameInput, IMREAD_COLOR);
|
||||
ASSERT_TRUE(img4.empty());
|
||||
}
|
||||
|
||||
|
||||
TEST(Imgcodecs_EXR, readWrite_32FC1_half)
|
||||
{
|
||||
const string root = cvtest::TS::ptr()->get_data_path();
|
||||
const string filenameInput = root + "readwrite/test32FC1_half.exr";
|
||||
const string filenameOutput = cv::tempfile(".exr");
|
||||
|
||||
std::vector<int> params;
|
||||
params.push_back(IMWRITE_EXR_TYPE);
|
||||
params.push_back(IMWRITE_EXR_TYPE_HALF);
|
||||
|
||||
#ifndef GENERATE_DATA
|
||||
const Mat img = cv::imread(filenameInput, IMREAD_UNCHANGED);
|
||||
#else
|
||||
const Size sz(64, 32);
|
||||
Mat img(sz, CV_32FC1, Scalar(0.5, 0.1, 1));
|
||||
img(Rect(10, 5, sz.width - 30, sz.height - 20)).setTo(Scalar(1, 0, 0));
|
||||
ASSERT_TRUE(cv::imwrite(filenameInput, img, params));
|
||||
#endif
|
||||
ASSERT_FALSE(img.empty());
|
||||
ASSERT_EQ(CV_32FC1,img.type());
|
||||
|
||||
ASSERT_TRUE(cv::imwrite(filenameOutput, img, params));
|
||||
const Mat img2 = cv::imread(filenameOutput, IMREAD_UNCHANGED);
|
||||
ASSERT_EQ(img2.type(), img.type());
|
||||
ASSERT_EQ(img2.size(), img.size());
|
||||
EXPECT_LE(cvtest::norm(img, img2, NORM_INF | NORM_RELATIVE), 1e-3);
|
||||
EXPECT_EQ(0, remove(filenameOutput.c_str()));
|
||||
}
|
||||
|
||||
TEST(Imgcodecs_EXR, readWrite_32FC3_half)
|
||||
{
|
||||
const string root = cvtest::TS::ptr()->get_data_path();
|
||||
const string filenameInput = root + "readwrite/test32FC3_half.exr";
|
||||
const string filenameOutput = cv::tempfile(".exr");
|
||||
|
||||
std::vector<int> params;
|
||||
params.push_back(IMWRITE_EXR_TYPE);
|
||||
params.push_back(IMWRITE_EXR_TYPE_HALF);
|
||||
|
||||
#ifndef GENERATE_DATA
|
||||
const Mat img = cv::imread(filenameInput, IMREAD_UNCHANGED);
|
||||
#else
|
||||
const Size sz(64, 32);
|
||||
Mat img(sz, CV_32FC3, Scalar(0.5, 0.1, 1));
|
||||
img(Rect(10, 5, sz.width - 30, sz.height - 20)).setTo(Scalar(1, 0, 0));
|
||||
ASSERT_TRUE(cv::imwrite(filenameInput, img, params));
|
||||
#endif
|
||||
ASSERT_FALSE(img.empty());
|
||||
ASSERT_EQ(CV_32FC3, img.type());
|
||||
|
||||
ASSERT_TRUE(cv::imwrite(filenameOutput, img, params));
|
||||
const Mat img2 = cv::imread(filenameOutput, IMREAD_UNCHANGED);
|
||||
ASSERT_EQ(img2.type(), img.type());
|
||||
ASSERT_EQ(img2.size(), img.size());
|
||||
EXPECT_LE(cvtest::norm(img, img2, NORM_INF | NORM_RELATIVE), 1e-3);
|
||||
EXPECT_EQ(0, remove(filenameOutput.c_str()));
|
||||
}
|
||||
|
||||
TEST(Imgcodecs_EXR, readWrite_32FC1_PIZ)
|
||||
{
|
||||
const string root = cvtest::TS::ptr()->get_data_path();
|
||||
const string filenameInput = root + "readwrite/test32FC1.exr";
|
||||
const string filenameOutput = cv::tempfile(".exr");
|
||||
|
||||
|
||||
const Mat img = cv::imread(filenameInput, IMREAD_UNCHANGED);
|
||||
ASSERT_FALSE(img.empty());
|
||||
ASSERT_EQ(CV_32FC1, img.type());
|
||||
|
||||
std::vector<int> params;
|
||||
params.push_back(IMWRITE_EXR_COMPRESSION);
|
||||
params.push_back(IMWRITE_EXR_COMPRESSION_PIZ);
|
||||
ASSERT_TRUE(cv::imwrite(filenameOutput, img, params));
|
||||
// Check generated file size to ensure that it's compressed with proper options
|
||||
ASSERT_EQ(849u, getFileSize(filenameOutput));
|
||||
const Mat img2 = cv::imread(filenameOutput, IMREAD_UNCHANGED);
|
||||
ASSERT_EQ(img2.type(), img.type());
|
||||
ASSERT_EQ(img2.size(), img.size());
|
||||
EXPECT_LE(cvtest::norm(img, img2, NORM_INF | NORM_RELATIVE), 1e-3);
|
||||
EXPECT_EQ(0, remove(filenameOutput.c_str()));
|
||||
}
|
||||
|
||||
// Note: YC to GRAYSCALE (IMREAD_GRAYSCALE | IMREAD_ANYDEPTH)
|
||||
// outputs a black image,
|
||||
// as does Y to RGB (IMREAD_COLOR | IMREAD_ANYDEPTH).
|
||||
// This behavior predates adding EXR alpha support issue
|
||||
// 16115.
|
||||
|
||||
TEST(Imgcodecs_EXR, read_YA_ignore_alpha)
|
||||
{
|
||||
const string root = cvtest::TS::ptr()->get_data_path();
|
||||
const string filenameInput = root + "readwrite/test_YA.exr";
|
||||
|
||||
const Mat img = cv::imread(filenameInput, IMREAD_GRAYSCALE | IMREAD_ANYDEPTH);
|
||||
|
||||
ASSERT_FALSE(img.empty());
|
||||
ASSERT_EQ(CV_32FC1, img.type());
|
||||
|
||||
// Writing Y covered by test 32FC1
|
||||
}
|
||||
|
||||
TEST(Imgcodecs_EXR, read_YA_unchanged)
|
||||
{
|
||||
const string root = cvtest::TS::ptr()->get_data_path();
|
||||
const string filenameInput = root + "readwrite/test_YA.exr";
|
||||
|
||||
const Mat img = cv::imread(filenameInput, IMREAD_UNCHANGED);
|
||||
|
||||
ASSERT_FALSE(img.empty());
|
||||
ASSERT_EQ(CV_32FC2, img.type());
|
||||
|
||||
// Cannot test writing, 2 channel writing not supported by loadsave
|
||||
}
|
||||
|
||||
TEST(Imgcodecs_EXR, read_YC_changeDepth)
|
||||
{
|
||||
const string root = cvtest::TS::ptr()->get_data_path();
|
||||
const string filenameInput = root + "readwrite/test_YRYBY.exr";
|
||||
|
||||
const Mat img = cv::imread(filenameInput, IMREAD_COLOR);
|
||||
|
||||
ASSERT_FALSE(img.empty());
|
||||
ASSERT_EQ(CV_8UC3, img.type());
|
||||
|
||||
const Mat img_rgb = cv::imread(filenameInput, IMREAD_COLOR_RGB);
|
||||
|
||||
ASSERT_FALSE(img_rgb.empty());
|
||||
ASSERT_EQ(CV_8UC3, img_rgb.type());
|
||||
|
||||
cvtColor(img_rgb, img_rgb, COLOR_RGB2BGR);
|
||||
|
||||
// See https://github.com/opencv/opencv/issues/26705
|
||||
// If ALGO_HINT_ACCURATE is set, norm should be 0.
|
||||
// If ALGO_HINT_APPROX is set, norm should be 1(or 0).
|
||||
EXPECT_LE(cvtest::norm(img, img_rgb, NORM_INF),
|
||||
(cv::getDefaultAlgorithmHint() == ALGO_HINT_ACCURATE)?0:1);
|
||||
|
||||
// Cannot test writing, EXR encoder doesn't support 8U depth
|
||||
}
|
||||
|
||||
TEST(Imgcodecs_EXR, readwrite_YCA_ignore_alpha)
|
||||
{
|
||||
const string root = cvtest::TS::ptr()->get_data_path();
|
||||
const string filenameInput = root + "readwrite/test_YRYBYA.exr";
|
||||
const string filenameOutput = cv::tempfile(".exr");
|
||||
|
||||
const Mat img = cv::imread(filenameInput, IMREAD_COLOR | IMREAD_ANYDEPTH);
|
||||
|
||||
ASSERT_FALSE(img.empty());
|
||||
ASSERT_EQ(CV_32FC3, img.type());
|
||||
|
||||
ASSERT_TRUE(cv::imwrite(filenameOutput, img));
|
||||
const Mat img2 = cv::imread(filenameOutput, IMREAD_UNCHANGED);
|
||||
ASSERT_EQ(img2.type(), img.type());
|
||||
ASSERT_EQ(img2.size(), img.size());
|
||||
EXPECT_LE(cvtest::norm(img, img2, NORM_INF | NORM_RELATIVE), 1e-3);
|
||||
EXPECT_EQ(0, remove(filenameOutput.c_str()));
|
||||
}
|
||||
|
||||
TEST(Imgcodecs_EXR, read_YC_unchanged)
|
||||
{
|
||||
const string root = cvtest::TS::ptr()->get_data_path();
|
||||
const string filenameInput = root + "readwrite/test_YRYBY.exr";
|
||||
|
||||
const Mat img = cv::imread(filenameInput, IMREAD_UNCHANGED);
|
||||
|
||||
ASSERT_FALSE(img.empty());
|
||||
ASSERT_EQ(CV_32FC3, img.type());
|
||||
|
||||
// Writing YC covered by test readwrite_YCA_ignore_alpha
|
||||
}
|
||||
|
||||
TEST(Imgcodecs_EXR, readwrite_YCA_unchanged)
|
||||
{
|
||||
const string root = cvtest::TS::ptr()->get_data_path();
|
||||
const string filenameInput = root + "readwrite/test_YRYBYA.exr";
|
||||
const string filenameOutput = cv::tempfile(".exr");
|
||||
|
||||
const Mat img = cv::imread(filenameInput, IMREAD_UNCHANGED);
|
||||
|
||||
ASSERT_FALSE(img.empty());
|
||||
ASSERT_EQ(CV_32FC4, img.type());
|
||||
|
||||
ASSERT_TRUE(cv::imwrite(filenameOutput, img));
|
||||
const Mat img2 = cv::imread(filenameOutput, IMREAD_UNCHANGED);
|
||||
ASSERT_EQ(img2.type(), img.type());
|
||||
ASSERT_EQ(img2.size(), img.size());
|
||||
EXPECT_LE(cvtest::norm(img, img2, NORM_INF | NORM_RELATIVE), 1e-3);
|
||||
EXPECT_EQ(0, remove(filenameOutput.c_str()));
|
||||
}
|
||||
|
||||
TEST(Imgcodecs_EXR, readwrite_RGBA_togreyscale)
|
||||
{
|
||||
const string root = cvtest::TS::ptr()->get_data_path();
|
||||
const string filenameInput = root + "readwrite/test_GeneratedRGBA.exr";
|
||||
const string filenameOutput = cv::tempfile(".exr");
|
||||
|
||||
const Mat img = cv::imread(filenameInput, IMREAD_GRAYSCALE | IMREAD_ANYDEPTH);
|
||||
|
||||
ASSERT_FALSE(img.empty());
|
||||
ASSERT_EQ(CV_32FC1, img.type());
|
||||
|
||||
ASSERT_TRUE(cv::imwrite(filenameOutput, img));
|
||||
const Mat img2 = cv::imread(filenameOutput, IMREAD_UNCHANGED);
|
||||
ASSERT_EQ(img2.type(), img.type());
|
||||
ASSERT_EQ(img2.size(), img.size());
|
||||
EXPECT_LE(cvtest::norm(img, img2, NORM_INF | NORM_RELATIVE), 1e-3);
|
||||
EXPECT_EQ(0, remove(filenameOutput.c_str()));
|
||||
}
|
||||
|
||||
TEST(Imgcodecs_EXR, read_RGBA_ignore_alpha)
|
||||
{
|
||||
const string root = cvtest::TS::ptr()->get_data_path();
|
||||
const string filenameInput = root + "readwrite/test_GeneratedRGBA.exr";
|
||||
|
||||
const Mat img = cv::imread(filenameInput, IMREAD_COLOR | IMREAD_ANYDEPTH);
|
||||
|
||||
ASSERT_FALSE(img.empty());
|
||||
ASSERT_EQ(CV_32FC3, img.type());
|
||||
|
||||
// Writing RGB covered by test 32FC3
|
||||
}
|
||||
|
||||
TEST(Imgcodecs_EXR, read_RGBA_unchanged)
|
||||
{
|
||||
const string root = cvtest::TS::ptr()->get_data_path();
|
||||
const string filenameInput = root + "readwrite/test_GeneratedRGBA.exr";
|
||||
const string filenameOutput = cv::tempfile(".exr");
|
||||
|
||||
#ifndef GENERATE_DATA
|
||||
const Mat img = cv::imread(filenameInput, IMREAD_UNCHANGED);
|
||||
#else
|
||||
const Size sz(64, 32);
|
||||
Mat img(sz, CV_32FC4, Scalar(0.5, 0.1, 1, 1));
|
||||
img(Rect(10, 5, sz.width - 30, sz.height - 20)).setTo(Scalar(1, 0, 0, 1));
|
||||
img(Rect(10, 20, sz.width - 30, sz.height - 20)).setTo(Scalar(1, 1, 0, 0));
|
||||
ASSERT_TRUE(cv::imwrite(filenameInput, img));
|
||||
#endif
|
||||
|
||||
ASSERT_FALSE(img.empty());
|
||||
ASSERT_EQ(CV_32FC4, img.type());
|
||||
|
||||
ASSERT_TRUE(cv::imwrite(filenameOutput, img));
|
||||
const Mat img2 = cv::imread(filenameOutput, IMREAD_UNCHANGED);
|
||||
ASSERT_EQ(img2.type(), img.type());
|
||||
ASSERT_EQ(img2.size(), img.size());
|
||||
EXPECT_LE(cvtest::norm(img, img2, NORM_INF | NORM_RELATIVE), 1e-3);
|
||||
EXPECT_EQ(0, remove(filenameOutput.c_str()));
|
||||
}
|
||||
|
||||
// See https://github.com/opencv/opencv/pull/26211
|
||||
// ( related with https://github.com/opencv/opencv/issues/26207 )
|
||||
TEST(Imgcodecs_EXR, imencode_regression_26207_extra)
|
||||
{
|
||||
// CV_8U is not supported depth for EXR Encoder.
|
||||
const cv::Mat src(100, 100, CV_8UC1, cv::Scalar::all(0));
|
||||
std::vector<uchar> buf;
|
||||
bool ret = false;
|
||||
EXPECT_ANY_THROW(ret = imencode(".exr", src, buf));
|
||||
EXPECT_FALSE(ret);
|
||||
}
|
||||
TEST(Imgcodecs_EXR, imwrite_regression_26207_extra)
|
||||
{
|
||||
// CV_8U is not supported depth for EXR Encoder.
|
||||
const cv::Mat src(100, 100, CV_8UC1, cv::Scalar::all(0));
|
||||
const string filename = cv::tempfile(".exr");
|
||||
bool ret = false;
|
||||
EXPECT_ANY_THROW(ret = imwrite(filename, src));
|
||||
EXPECT_FALSE(ret);
|
||||
remove(filename.c_str());
|
||||
}
|
||||
|
||||
|
||||
}} // namespace
|
||||
@@ -0,0 +1,41 @@
|
||||
// This file is part of OpenCV project.
|
||||
// It is subject to the license terms in the LICENSE file found in the top-level directory
|
||||
// of this distribution and at http://opencv.org/license.html
|
||||
#include "test_precomp.hpp"
|
||||
#include "test_common.hpp"
|
||||
|
||||
namespace opencv_test { namespace {
|
||||
|
||||
#ifdef HAVE_GDAL
|
||||
|
||||
static void test_gdal_read(const string filename, bool required = true) {
|
||||
const string path = cvtest::findDataFile(filename);
|
||||
Mat img;
|
||||
ASSERT_NO_THROW(img = imread(path, cv::IMREAD_LOAD_GDAL | cv::IMREAD_ANYDEPTH | cv::IMREAD_ANYCOLOR));
|
||||
if(!required && img.empty())
|
||||
{
|
||||
throw SkipTestException("GDAL is built wihout required back-end support");
|
||||
}
|
||||
ASSERT_FALSE(img.empty());
|
||||
EXPECT_EQ(3, img.cols);
|
||||
EXPECT_EQ(5, img.rows);
|
||||
EXPECT_EQ(CV_MAKETYPE(CV_32F, 7), img.type());
|
||||
EXPECT_EQ(101.125, (img.at<Vec<float, 7>>(0, 0)[0]));
|
||||
EXPECT_EQ(203.500, (img.at<Vec<float, 7>>(2, 1)[3]));
|
||||
EXPECT_EQ(305.875, (img.at<Vec<float, 7>>(4, 2)[6]));
|
||||
}
|
||||
|
||||
TEST(Imgcodecs_gdal, read_envi)
|
||||
{
|
||||
test_gdal_read("../cv/gdal/envi_test.raw");
|
||||
}
|
||||
|
||||
TEST(Imgcodecs_gdal, read_fits)
|
||||
{
|
||||
// .fit test is optional because GDAL may be built wihtout CFITSIO library support
|
||||
test_gdal_read("../cv/gdal/fits_test.fit", false);
|
||||
}
|
||||
|
||||
#endif // HAVE_GDAL
|
||||
|
||||
}} // namespace
|
||||
@@ -0,0 +1,520 @@
|
||||
// This file is part of OpenCV project.
|
||||
// It is subject to the license terms in the LICENSE file found in the top-level directory
|
||||
// of this distribution and at http://opencv.org/license.html
|
||||
#include "test_precomp.hpp"
|
||||
|
||||
#ifdef HAVE_IMGCODEC_GIF
|
||||
|
||||
namespace opencv_test { namespace {
|
||||
|
||||
const string gifsuite_files_multi[]={
|
||||
"basi3p01",
|
||||
"basi3p02",
|
||||
"basi3p04",
|
||||
"basn3p01",
|
||||
"basn3p02",
|
||||
"basn3p04",
|
||||
"ccwn3p08",
|
||||
"ch1n3p04",
|
||||
"cs3n3p08",
|
||||
"cs5n3p08",
|
||||
"cs8n3p08",
|
||||
"g03n3p04",
|
||||
"g04n3p04",
|
||||
"g05n3p04",
|
||||
"g07n3p04",
|
||||
"g10n3p04",
|
||||
"g25n3p04",
|
||||
"s32i3p04",
|
||||
"s32n3p04",
|
||||
"tp0n3p08",
|
||||
};
|
||||
|
||||
const string gifsuite_files_read_single[] = {
|
||||
"basi3p01",
|
||||
"basi3p02",
|
||||
"basi3p04",
|
||||
"basn3p01",
|
||||
"basn3p02",
|
||||
"basn3p04",
|
||||
"ccwn3p08",
|
||||
"cdfn2c08",
|
||||
"cdhn2c08",
|
||||
"cdsn2c08",
|
||||
"cdun2c08",
|
||||
"ch1n3p04",
|
||||
"cs3n3p08",
|
||||
"cs5n2c08",
|
||||
"cs5n3p08",
|
||||
"cs8n2c08",
|
||||
"cs8n3p08",
|
||||
"exif2c08",
|
||||
"g03n2c08",
|
||||
"g03n3p04",
|
||||
"g04n2c08",
|
||||
"g04n3p04",
|
||||
"g05n2c08",
|
||||
"g05n3p04",
|
||||
"g07n2c08",
|
||||
"g07n3p04",
|
||||
"g10n2c08",
|
||||
"g10n3p04"
|
||||
};
|
||||
|
||||
const string gifsuite_files_read_write_suite[]={
|
||||
"g25n2c08",
|
||||
"g25n3p04",
|
||||
"s01i3p01",
|
||||
"s01n3p01",
|
||||
"s02i3p01",
|
||||
"s02n3p01",
|
||||
"s03i3p01",
|
||||
"s03n3p01",
|
||||
"s04i3p01",
|
||||
"s04n3p01",
|
||||
"s05i3p02",
|
||||
"s05n3p02",
|
||||
"s06i3p02",
|
||||
"s06n3p02",
|
||||
"s07i3p02",
|
||||
"s07n3p02",
|
||||
"s08i3p02",
|
||||
"s08n3p02",
|
||||
"s09i3p02",
|
||||
"s09n3p02",
|
||||
"s32i3p04",
|
||||
"s32n3p04",
|
||||
"s33i3p04",
|
||||
"s33n3p04",
|
||||
"s34i3p04",
|
||||
"s34n3p04",
|
||||
"s35i3p04",
|
||||
"s35n3p04",
|
||||
"s36i3p04",
|
||||
"s36n3p04",
|
||||
"s37i3p04",
|
||||
"s37n3p04",
|
||||
"s38i3p04",
|
||||
"s38n3p04",
|
||||
"s39i3p04",
|
||||
"s39n3p04",
|
||||
"s40i3p04",
|
||||
"s40n3p04",
|
||||
"tp0n3p08",
|
||||
};
|
||||
|
||||
const std::pair<string,int> gifsuite_files_bgra[]={
|
||||
make_pair("gif_bgra1",53287),
|
||||
make_pair("gif_bgra2",52651),
|
||||
make_pair("gif_bgra3",54809),
|
||||
make_pair("gif_bgra4",57562),
|
||||
make_pair("gif_bgra5",56733),
|
||||
make_pair("gif_bgra6",52110),
|
||||
};
|
||||
|
||||
TEST(Imgcodecs_Gif, read_gif_multi)
|
||||
{
|
||||
const string root = cvtest::TS::ptr()->get_data_path();
|
||||
const string filename = root + "gifsuite/gif_multi.gif";
|
||||
vector<cv::Mat> img_vec_8UC4;
|
||||
ASSERT_NO_THROW(cv::imreadmulti(filename, img_vec_8UC4,0,20,IMREAD_UNCHANGED));
|
||||
EXPECT_EQ(img_vec_8UC4.size(), imcount(filename));
|
||||
vector<cv::Mat> img_vec_8UC3;
|
||||
for(const auto & i : img_vec_8UC4){
|
||||
cv::Mat img_tmp;
|
||||
cvtColor(i,img_tmp,COLOR_BGRA2BGR);
|
||||
img_vec_8UC3.push_back(img_tmp);
|
||||
}
|
||||
const long unsigned int expected_size=20;
|
||||
EXPECT_EQ(img_vec_8UC3.size(),expected_size);
|
||||
for(long unsigned int i=0;i<img_vec_8UC3.size();i++){
|
||||
cv::Mat img=img_vec_8UC3[i];
|
||||
const string png_filename = root + "pngsuite/" + gifsuite_files_multi[i] + ".png";
|
||||
cv::Mat img_png;
|
||||
ASSERT_NO_THROW(img_png = imread(png_filename,IMREAD_UNCHANGED));
|
||||
ASSERT_FALSE(img_png.empty());
|
||||
EXPECT_PRED_FORMAT2(cvtest::MatComparator(0, 0), img, img_png);
|
||||
}
|
||||
}
|
||||
|
||||
typedef testing::TestWithParam<string> Imgcodecs_Gif_GifSuite_SingleFrame;
|
||||
|
||||
TEST_P(Imgcodecs_Gif_GifSuite_SingleFrame, read_gif_single)
|
||||
{
|
||||
const string root = cvtest::TS::ptr()->get_data_path();
|
||||
const string filename = root + "gifsuite/" + GetParam() + ".gif";
|
||||
const string png_filename=root + "pngsuite/" + GetParam() + ".png";
|
||||
const long unsigned int expected_size = 1;
|
||||
|
||||
EXPECT_EQ(expected_size, imcount(filename));
|
||||
cv::Mat img_8UC4;
|
||||
ASSERT_NO_THROW(img_8UC4 = cv::imread(filename, IMREAD_UNCHANGED));
|
||||
ASSERT_FALSE(img_8UC4.empty());
|
||||
cv::Mat img_8UC3;
|
||||
ASSERT_NO_THROW(cvtColor(img_8UC4, img_8UC3, COLOR_BGRA2BGR));
|
||||
cv::Mat img_png;
|
||||
ASSERT_NO_THROW(img_png = cv::imread(png_filename, IMREAD_UNCHANGED));
|
||||
ASSERT_FALSE(img_png.empty());
|
||||
EXPECT_PRED_FORMAT2(cvtest::MatComparator(0, 0), img_8UC3, img_png);
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(/*nothing*/, Imgcodecs_Gif_GifSuite_SingleFrame,
|
||||
testing::ValuesIn(gifsuite_files_read_single));
|
||||
|
||||
TEST(Imgcodecs_Gif, read_gif_big){
|
||||
const string root = cvtest::TS::ptr()->get_data_path();
|
||||
const string gif_filename = root + "gifsuite/gif_big.gif";
|
||||
const string png_filename = root + "gifsuite/gif_big.png";
|
||||
cv::Mat img_8UC4;
|
||||
ASSERT_NO_THROW(img_8UC4 = imread(gif_filename, IMREAD_UNCHANGED));
|
||||
ASSERT_FALSE(img_8UC4.empty());
|
||||
cv::Mat img_8UC3;
|
||||
const int expected_col=1303;
|
||||
const int expected_row=1391;
|
||||
EXPECT_EQ(expected_col, img_8UC4.cols);
|
||||
EXPECT_EQ(expected_row, img_8UC4.rows);
|
||||
ASSERT_NO_THROW(cvtColor(img_8UC4, img_8UC3,COLOR_BGRA2BGR));
|
||||
EXPECT_EQ(expected_col, img_8UC3.cols);
|
||||
EXPECT_EQ(expected_row, img_8UC3.rows);
|
||||
cv::Mat img_png;
|
||||
ASSERT_NO_THROW(img_png=imread(png_filename, IMREAD_UNCHANGED));
|
||||
ASSERT_FALSE(img_png.empty());
|
||||
cv::Mat img_png_8UC3;
|
||||
ASSERT_NO_THROW(cvtColor(img_png,img_png_8UC3, COLOR_BGRA2BGR));
|
||||
EXPECT_EQ(img_8UC3.size, img_png_8UC3.size);
|
||||
EXPECT_PRED_FORMAT2(cvtest::MatComparator(0, 0), img_8UC3, img_png_8UC3);
|
||||
}
|
||||
|
||||
typedef testing::TestWithParam<std::pair<string,int>> Imgcodecs_Gif_GifSuite_SingleFrame_BGRA;
|
||||
|
||||
TEST_P(Imgcodecs_Gif_GifSuite_SingleFrame_BGRA, read_gif_single_bgra){
|
||||
const string root = cvtest::TS::ptr()->get_data_path();
|
||||
const string gif_filename = root + "gifsuite/" + GetParam().first + ".gif";
|
||||
const string png_filename = root + "gifsuite/" + GetParam().first + ".png";
|
||||
cv::Mat gif_img;
|
||||
cv::Mat png_img;
|
||||
ASSERT_NO_THROW(gif_img = cv::imread(gif_filename, IMREAD_UNCHANGED));
|
||||
ASSERT_FALSE(gif_img.empty());
|
||||
ASSERT_NO_THROW(png_img = cv::imread(png_filename, IMREAD_UNCHANGED));
|
||||
ASSERT_FALSE(png_img.empty());
|
||||
EXPECT_PRED_FORMAT2(cvtest::MatComparator(0, 0), gif_img, png_img);
|
||||
int transparent_count = 0;
|
||||
for(int i=0; i<gif_img.rows; i++){
|
||||
for(int j=0; j<gif_img.cols; j++){
|
||||
cv::Vec4b pixel1 = gif_img.at<cv::Vec4b>(i,j);
|
||||
if((int)(pixel1[3]) == 0){
|
||||
transparent_count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
EXPECT_EQ(transparent_count,GetParam().second);
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(/*nothing*/, Imgcodecs_Gif_GifSuite_SingleFrame_BGRA ,
|
||||
testing::ValuesIn(gifsuite_files_bgra));
|
||||
|
||||
TEST(Imgcodecs_Gif,read_gif_multi_bgra){
|
||||
const string root = cvtest::TS::ptr()->get_data_path();
|
||||
const string gif_filename = root + "gifsuite/gif_multi_bgra.gif";
|
||||
vector<cv::Mat> img_vec;
|
||||
ASSERT_NO_THROW(cv::imreadmulti(gif_filename, img_vec, IMREAD_UNCHANGED));
|
||||
EXPECT_EQ(imcount(gif_filename), img_vec.size());
|
||||
const int fixed_transparent_count = 53211;
|
||||
for(auto & frame_count : img_vec){
|
||||
int transparent_count=0;
|
||||
for(int i=0; i<frame_count.rows; i++){
|
||||
for(int j=0; j<frame_count.cols; j++){
|
||||
cv::Vec4b pixel1 = frame_count.at<cv::Vec4b>(i,j);
|
||||
if((int)(pixel1[3]) == 0){
|
||||
transparent_count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
EXPECT_EQ(fixed_transparent_count,transparent_count);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(Imgcodecs_Gif, read_gif_special){
|
||||
const string root = cvtest::TS::ptr()->get_data_path();
|
||||
const string gif_filename1 = root + "gifsuite/special1.gif";
|
||||
const string png_filename1 = root + "gifsuite/special1.png";
|
||||
const string gif_filename2 = root + "gifsuite/special2.gif";
|
||||
const string png_filename2 = root + "gifsuite/special2.png";
|
||||
cv::Mat gif_img1;
|
||||
ASSERT_NO_THROW(gif_img1 = cv::imread(gif_filename1,IMREAD_COLOR));
|
||||
ASSERT_FALSE(gif_img1.empty());
|
||||
cv::Mat png_img1;
|
||||
ASSERT_NO_THROW(png_img1 = cv::imread(png_filename1,IMREAD_COLOR));
|
||||
ASSERT_FALSE(png_img1.empty());
|
||||
EXPECT_PRED_FORMAT2(cvtest::MatComparator(0, 0), gif_img1, png_img1);
|
||||
cv::Mat gif_img2;
|
||||
ASSERT_NO_THROW(gif_img2 = cv::imread(gif_filename2,IMREAD_COLOR));
|
||||
ASSERT_FALSE(gif_img2.empty());
|
||||
cv::Mat png_img2;
|
||||
ASSERT_NO_THROW(png_img2 = cv::imread(png_filename2,IMREAD_COLOR));
|
||||
ASSERT_FALSE(png_img2.empty());
|
||||
EXPECT_PRED_FORMAT2(cvtest::MatComparator(0, 0), gif_img2, png_img2);
|
||||
}
|
||||
|
||||
TEST(Imgcodecs_Gif,write_gif_flags){
|
||||
vector<uchar> buff;
|
||||
const int expected_rows=611;
|
||||
const int expected_cols=293;
|
||||
Mat img_gt = Mat::ones(expected_rows, expected_cols, CV_8UC3);
|
||||
const vector<int> param = { IMWRITE_GIF_QUALITY, IMWRITE_GIF_FAST_NO_DITHER, IMWRITE_GIF_DITHER, 3};
|
||||
bool ret = false;
|
||||
EXPECT_NO_THROW(ret = imencode(".gif", img_gt, buff, param));
|
||||
EXPECT_TRUE(ret);
|
||||
Mat img;
|
||||
EXPECT_NO_THROW(img = imdecode(buff, IMREAD_COLOR));
|
||||
EXPECT_FALSE(img.empty());
|
||||
EXPECT_EQ(img.cols, expected_cols);
|
||||
EXPECT_EQ(img.rows, expected_rows);
|
||||
EXPECT_PRED_FORMAT2(cvtest::MatComparator(0, 0), img, img_gt);
|
||||
}
|
||||
|
||||
TEST(Imgcodecs_Gif, write_gif_big) {
|
||||
const string root = cvtest::TS::ptr()->get_data_path();
|
||||
const string png_filename = root + "gifsuite/gif_big.png";
|
||||
const string gif_filename = cv::tempfile(".gif");
|
||||
cv::Mat img;
|
||||
ASSERT_NO_THROW(img = cv::imread(png_filename, IMREAD_UNCHANGED));
|
||||
ASSERT_FALSE(img.empty());
|
||||
EXPECT_EQ(1303, img.cols);
|
||||
EXPECT_EQ(1391, img.rows);
|
||||
ASSERT_NO_THROW(imwrite(gif_filename, img));
|
||||
cv::Mat img_gif;
|
||||
ASSERT_NO_THROW(img_gif = cv::imread(gif_filename, IMREAD_UNCHANGED));
|
||||
ASSERT_FALSE(img_gif.empty());
|
||||
EXPECT_EQ(1303, img_gif.cols);
|
||||
EXPECT_EQ(1391, img_gif.rows);
|
||||
EXPECT_EQ(0, remove(gif_filename.c_str()));
|
||||
}
|
||||
|
||||
typedef testing::TestWithParam<string> Imgcodecs_Gif_GifSuite_Read_Write_Suite;
|
||||
|
||||
TEST_P(Imgcodecs_Gif_GifSuite_Read_Write_Suite ,read_gif_single)
|
||||
{
|
||||
const string root = cvtest::TS::ptr()->get_data_path();
|
||||
const string png_filename = root + "pngsuite/"+GetParam()+".png";
|
||||
const string gif_filename = cv::tempfile(".gif");
|
||||
cv::Mat img;
|
||||
ASSERT_NO_THROW(img = cv::imread(png_filename, IMREAD_UNCHANGED));
|
||||
ASSERT_FALSE(img.empty());
|
||||
vector<int> param;
|
||||
param.push_back(IMWRITE_GIF_QUALITY);
|
||||
param.push_back(8);
|
||||
param.push_back(IMWRITE_GIF_DITHER);
|
||||
param.push_back(3);
|
||||
ASSERT_NO_THROW(imwrite(gif_filename, img, param));
|
||||
cv::Mat img_gif;
|
||||
ASSERT_NO_THROW(img_gif = cv::imread(gif_filename, IMREAD_UNCHANGED));
|
||||
ASSERT_FALSE(img_gif.empty());
|
||||
cv::Mat img_8UC3;
|
||||
ASSERT_NO_THROW(cv::cvtColor(img_gif, img_8UC3, COLOR_BGRA2BGR));
|
||||
EXPECT_PRED_FORMAT2(cvtest::MatComparator(29, 0), img, img_8UC3);
|
||||
EXPECT_EQ(0, remove(gif_filename.c_str()));
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(/*nothing*/, Imgcodecs_Gif_GifSuite_Read_Write_Suite ,
|
||||
testing::ValuesIn(gifsuite_files_read_write_suite));
|
||||
|
||||
TEST(Imgcodecs_Gif, write_gif_multi) {
|
||||
const string root = cvtest::TS::ptr()->get_data_path();
|
||||
const string gif_filename = cv::tempfile(".gif");
|
||||
vector<cv::Mat> img_vec;
|
||||
for (long unsigned int i = 0; i < 20; i++) {
|
||||
const string png_filename = root + "pngsuite/" + gifsuite_files_multi[i] + ".png";
|
||||
cv::Mat img;
|
||||
ASSERT_NO_THROW(img = cv::imread(png_filename, IMREAD_UNCHANGED));
|
||||
ASSERT_FALSE(img.empty());
|
||||
img_vec.push_back(img);
|
||||
}
|
||||
vector<int> param;
|
||||
param.push_back(IMWRITE_GIF_QUALITY);
|
||||
param.push_back(8);
|
||||
param.push_back(IMWRITE_GIF_DITHER);
|
||||
param.push_back(3);
|
||||
ASSERT_NO_THROW(cv::imwritemulti(gif_filename, img_vec, param));
|
||||
vector<cv::Mat> img_vec_gif;
|
||||
ASSERT_NO_THROW(cv::imreadmulti(gif_filename, img_vec_gif));
|
||||
EXPECT_EQ(img_vec.size(), img_vec_gif.size());
|
||||
for (long unsigned int i = 0; i < img_vec.size(); i++) {
|
||||
cv::Mat img_8UC3;
|
||||
ASSERT_NO_THROW(cv::cvtColor(img_vec_gif[i], img_8UC3, COLOR_BGRA2BGR));
|
||||
EXPECT_PRED_FORMAT2(cvtest::MatComparator(29, 0), img_vec[i], img_8UC3);
|
||||
}
|
||||
EXPECT_EQ(0, remove(gif_filename.c_str()));
|
||||
}
|
||||
|
||||
TEST(Imgcodecs_Gif, encode_IMREAD_GRAYSCALE) {
|
||||
cv::Mat src;
|
||||
cv::Mat decoded;
|
||||
vector<uint8_t> buf;
|
||||
vector<int> param;
|
||||
bool ret = false;
|
||||
|
||||
src = cv::Mat(240,240,CV_8UC3,cv::Scalar(128,64,32));
|
||||
EXPECT_NO_THROW(ret = imencode(".gif", src, buf, param));
|
||||
EXPECT_TRUE(ret);
|
||||
EXPECT_NO_THROW(decoded = imdecode(buf, cv::IMREAD_GRAYSCALE));
|
||||
EXPECT_FALSE(decoded.empty());
|
||||
EXPECT_EQ(decoded.channels(), 1);
|
||||
}
|
||||
|
||||
// See https://github.com/opencv/opencv/issues/26924
|
||||
TEST(Imgcodecs_Gif, decode_disposal_method)
|
||||
{
|
||||
const string root = cvtest::TS::ptr()->get_data_path();
|
||||
const string filename = root + "gifsuite/disposalMethod.gif";
|
||||
cv::Animation anim;
|
||||
bool ret = false;
|
||||
EXPECT_NO_THROW(ret = imreadanimation(filename, anim, cv::IMREAD_UNCHANGED));
|
||||
EXPECT_TRUE(ret);
|
||||
|
||||
/* [Detail of this test]
|
||||
* disposalMethod.gif has 5 frames to draw 8x8 rectangles with each color index, offsets and disposal method.
|
||||
* frame 1 draws {1, ... ,1} rectangle at (1,1) with DisposalMethod = 0.
|
||||
* frame 2 draws {2, ... ,2} rectangle at (2,2) with DisposalMethod = 3.
|
||||
* frame 3 draws {3, ... ,3} rectangle at (3,3) with DisposalMethod = 1.
|
||||
* frame 4 draws {4, ... ,4} rectangle at (4,4) with DisposalMethod = 2.
|
||||
* frame 5 draws {5, ... ,5} rectangle at (5,5) with DisposalMethod = 1.
|
||||
*
|
||||
* To convenience to test, color[N] in the color table has RGB(32*N, some, some).
|
||||
* color[0] = RGB(0,0,0) (background color).
|
||||
* color[1] = RGB(32,0,0)
|
||||
* color[2] = RGB(64,0,255)
|
||||
* color[3] = RGB(96,255,0)
|
||||
* color[4] = RGB(128,128,128)
|
||||
* color[5] = RGB(160,255,255)
|
||||
*/
|
||||
const int refIds[5][6] =
|
||||
{// { 0, 0, 0, 0, 0, 0} 0 is background color.
|
||||
{ 0, 1, 1, 1, 1, 1}, // 1 is to be not disposed.
|
||||
{ 0, 1, 2, 2, 2, 2}, // 2 is to be restored to previous.
|
||||
{ 0, 1, 1, 3, 3, 3}, // 3 is to be left in place.
|
||||
{ 0, 1, 1, 3, 4, 4}, // 4 is to be restored to the background color.
|
||||
{ 0, 1, 1, 3, 0, 5}, // 5 is to be left in place.
|
||||
};
|
||||
|
||||
for(int i = 0 ; i < 5; i++)
|
||||
{
|
||||
cv::Mat frame = anim.frames[i];
|
||||
EXPECT_FALSE(frame.empty());
|
||||
EXPECT_EQ(frame.type(), CV_8UC4);
|
||||
for(int j = 0; j < 6; j ++ )
|
||||
{
|
||||
const cv::Scalar p = frame.at<Vec4b>(j,j);
|
||||
EXPECT_EQ( p[2], refIds[i][j] * 32 ) << " i = " << i << " j = " << j << " pixels = " << p;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// See https://github.com/opencv/opencv/issues/26970
|
||||
typedef testing::TestWithParam<int> Imgcodecs_Gif_loop_count;
|
||||
TEST_P(Imgcodecs_Gif_loop_count, imwriteanimation)
|
||||
{
|
||||
const string gif_filename = cv::tempfile(".gif");
|
||||
|
||||
int loopCount = GetParam();
|
||||
cv::Animation anim(loopCount);
|
||||
|
||||
vector<cv::Mat> src;
|
||||
for(int n = 1; n <= 5 ; n ++ )
|
||||
{
|
||||
cv::Mat frame(64, 64, CV_8UC3, cv::Scalar::all(0));
|
||||
cv::putText(frame, cv::format("%d", n), cv::Point(0,64), cv::FONT_HERSHEY_PLAIN, 4.0, cv::Scalar::all(255));
|
||||
anim.frames.push_back(frame);
|
||||
anim.durations.push_back(1000 /* ms */);
|
||||
}
|
||||
|
||||
bool ret = false;
|
||||
#if 0
|
||||
// To output gif image for test.
|
||||
EXPECT_NO_THROW(ret = imwriteanimation(cv::format("gif_loop-%d.gif", loopCount), anim));
|
||||
EXPECT_TRUE(ret);
|
||||
#endif
|
||||
EXPECT_NO_THROW(ret = imwriteanimation(gif_filename, anim));
|
||||
EXPECT_TRUE(ret);
|
||||
|
||||
// Read raw GIF data.
|
||||
std::ifstream ifs(gif_filename);
|
||||
std::stringstream ss;
|
||||
ss << ifs.rdbuf();
|
||||
string tmp = ss.str();
|
||||
std::vector<uint8_t> buf(tmp.begin(), tmp.end());
|
||||
|
||||
std::vector<uint8_t> netscape = {0x21, 0xFF, 0x0B, 'N','E','T','S','C','A','P','E','2','.','0'};
|
||||
auto pos = std::search(buf.begin(), buf.end(), netscape.begin(), netscape.end());
|
||||
if(loopCount == 1) {
|
||||
EXPECT_EQ(pos, buf.end()) << "Netscape Application Block should not be included if Animation.loop_count == 1";
|
||||
} else {
|
||||
EXPECT_NE(pos, buf.end()) << "Netscape Application Block should be included if Animation.loop_count != 1";
|
||||
}
|
||||
|
||||
remove(gif_filename.c_str());
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(/*nothing*/,
|
||||
Imgcodecs_Gif_loop_count,
|
||||
testing::Values(
|
||||
-1,
|
||||
0, // Default, loop-forever
|
||||
1,
|
||||
2,
|
||||
65534,
|
||||
65535, // Maximum Limit
|
||||
65536
|
||||
)
|
||||
);
|
||||
|
||||
typedef testing::TestWithParam<int> Imgcodecs_Gif_duration;
|
||||
TEST_P(Imgcodecs_Gif_duration, imwriteanimation)
|
||||
{
|
||||
const string gif_filename = cv::tempfile(".gif");
|
||||
|
||||
cv::Animation anim;
|
||||
|
||||
int duration = GetParam();
|
||||
vector<cv::Mat> src;
|
||||
for(int n = 1; n <= 5 ; n ++ )
|
||||
{
|
||||
cv::Mat frame(64, 64, CV_8UC3, cv::Scalar::all(0));
|
||||
cv::putText(frame, cv::format("%d", n), cv::Point(0,64), cv::FONT_HERSHEY_PLAIN, 4.0, cv::Scalar::all(255));
|
||||
anim.frames.push_back(frame);
|
||||
anim.durations.push_back(duration /* ms */);
|
||||
}
|
||||
|
||||
bool ret = false;
|
||||
#if 0
|
||||
// To output gif image for test.
|
||||
EXPECT_NO_THROW(ret = imwriteanimation(cv::format("gif_duration-%d.gif", duration), anim));
|
||||
EXPECT_EQ(ret, ( (0 <= duration) && (duration <= 655350) ) );
|
||||
#endif
|
||||
EXPECT_NO_THROW(ret = imwriteanimation(gif_filename, anim));
|
||||
EXPECT_EQ(ret, ( (0 <= duration) && (duration <= 655350) ) );
|
||||
|
||||
remove(gif_filename.c_str());
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(/*nothing*/,
|
||||
Imgcodecs_Gif_duration,
|
||||
testing::Values(
|
||||
-1, // Unsupported
|
||||
0, // Undefined Behaviour
|
||||
1,
|
||||
9,
|
||||
10,
|
||||
50,
|
||||
100, // 10 FPS
|
||||
1000, // 1 FPS
|
||||
655340,
|
||||
655350, // Maximum Limit
|
||||
655360 // Unsupported
|
||||
)
|
||||
);
|
||||
|
||||
}//opencv_test
|
||||
}//namespace
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,655 @@
|
||||
/*M///////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
|
||||
//
|
||||
// By downloading, copying, installing or using the software you agree to this license.
|
||||
// If you do not agree to this license, do not download, install,
|
||||
// copy or use the software.
|
||||
//
|
||||
//
|
||||
// License Agreement
|
||||
// For Open Source Computer Vision Library
|
||||
//
|
||||
// Copyright (C) 2000-2008, Intel Corporation, all rights reserved.
|
||||
// Copyright (C) 2009, Willow Garage Inc., all rights reserved.
|
||||
// Third party copyrights are property of their respective owners.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without modification,
|
||||
// are permitted provided that the following conditions are met:
|
||||
//
|
||||
// * Redistribution's of source code must retain the above copyright notice,
|
||||
// this list of conditions and the following disclaimer.
|
||||
//
|
||||
// * Redistribution's in binary form must reproduce the above copyright notice,
|
||||
// this list of conditions and the following disclaimer in the documentation
|
||||
// and/or other materials provided with the distribution.
|
||||
//
|
||||
// * The name of the copyright holders may not be used to endorse or promote products
|
||||
// derived from this software without specific prior written permission.
|
||||
//
|
||||
// This software is provided by the copyright holders and contributors "as is" and
|
||||
// any express or implied warranties, including, but not limited to, the implied
|
||||
// warranties of merchantability and fitness for a particular purpose are disclaimed.
|
||||
// In no event shall the Intel Corporation or contributors be liable for any direct,
|
||||
// indirect, incidental, special, exemplary, or consequential damages
|
||||
// (including, but not limited to, procurement of substitute goods or services;
|
||||
// loss of use, data, or profits; or business interruption) however caused
|
||||
// and on any theory of liability, whether in contract, strict liability,
|
||||
// or tort (including negligence or otherwise) arising in any way out of
|
||||
// the use of this software, even if advised of the possibility of such damage.
|
||||
//
|
||||
//M*/
|
||||
|
||||
#include "test_precomp.hpp"
|
||||
|
||||
namespace opencv_test { namespace {
|
||||
|
||||
typedef tuple<string, int> File_Mode;
|
||||
typedef testing::TestWithParam<File_Mode> Imgcodecs_FileMode;
|
||||
|
||||
TEST_P(Imgcodecs_FileMode, regression)
|
||||
{
|
||||
const string root = cvtest::TS::ptr()->get_data_path();
|
||||
const string filename = root + get<0>(GetParam());
|
||||
const int mode = get<1>(GetParam());
|
||||
|
||||
const Mat single = imread(filename, mode);
|
||||
ASSERT_FALSE(single.empty());
|
||||
|
||||
vector<Mat> pages;
|
||||
ASSERT_TRUE(imreadmulti(filename, pages, mode));
|
||||
ASSERT_FALSE(pages.empty());
|
||||
const Mat page = pages[0];
|
||||
ASSERT_FALSE(page.empty());
|
||||
|
||||
EXPECT_EQ(page.channels(), single.channels());
|
||||
EXPECT_EQ(page.depth(), single.depth());
|
||||
EXPECT_EQ(page.size().height, single.size().height);
|
||||
EXPECT_EQ(page.size().width, single.size().width);
|
||||
EXPECT_PRED_FORMAT2(cvtest::MatComparator(0, 0), page, single);
|
||||
}
|
||||
|
||||
const string all_images[] =
|
||||
{
|
||||
#if (defined(HAVE_JASPER) && defined(OPENCV_IMGCODECS_ENABLE_JASPER_TESTS)) \
|
||||
|| defined(HAVE_OPENJPEG)
|
||||
"readwrite/Rome.jp2",
|
||||
"readwrite/Bretagne2.jp2",
|
||||
"readwrite/Bretagne2.jp2",
|
||||
"readwrite/Grey.jp2",
|
||||
"readwrite/Grey.jp2",
|
||||
"readwrite/balloon.j2c",
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_GDCM
|
||||
"readwrite/int16-mono1.dcm",
|
||||
"readwrite/uint8-mono2.dcm",
|
||||
"readwrite/uint16-mono2.dcm",
|
||||
"readwrite/uint8-rgb.dcm",
|
||||
#endif
|
||||
#if defined(HAVE_PNG) || defined(HAVE_SPNG)
|
||||
"readwrite/color_palette_alpha.png",
|
||||
#endif
|
||||
#ifdef HAVE_TIFF
|
||||
"readwrite/multipage.tif",
|
||||
#endif
|
||||
"readwrite/ordinary.bmp",
|
||||
"readwrite/rle8.bmp",
|
||||
#ifdef HAVE_JPEG
|
||||
"readwrite/test_1_c1.jpg",
|
||||
#endif
|
||||
#ifdef HAVE_IMGCODEC_HDR
|
||||
"readwrite/rle.hdr"
|
||||
#endif
|
||||
};
|
||||
|
||||
const int basic_modes[] =
|
||||
{
|
||||
IMREAD_UNCHANGED,
|
||||
IMREAD_GRAYSCALE,
|
||||
IMREAD_COLOR,
|
||||
IMREAD_COLOR_RGB,
|
||||
IMREAD_ANYDEPTH,
|
||||
IMREAD_ANYCOLOR
|
||||
};
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(All, Imgcodecs_FileMode,
|
||||
testing::Combine(
|
||||
testing::ValuesIn(all_images),
|
||||
testing::ValuesIn(basic_modes)));
|
||||
|
||||
// GDAL does not support "hdr", "dcm" and has problems with JPEG2000 files (jp2, j2c)
|
||||
struct notForGDAL {
|
||||
bool operator()(const string &name) const {
|
||||
const string &ext = name.substr(name.size() - 3, 3);
|
||||
return ext == "hdr" || ext == "dcm" || ext == "jp2" || ext == "j2c" ||
|
||||
name.find("rle8.bmp") != std::string::npos;
|
||||
}
|
||||
};
|
||||
|
||||
inline vector<string> gdal_images()
|
||||
{
|
||||
vector<string> res;
|
||||
std::back_insert_iterator< vector<string> > it(res);
|
||||
std::remove_copy_if(all_images, all_images + sizeof(all_images)/sizeof(all_images[0]), it, notForGDAL());
|
||||
return res;
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(GDAL, Imgcodecs_FileMode,
|
||||
testing::Combine(
|
||||
testing::ValuesIn(gdal_images()),
|
||||
testing::Values(IMREAD_LOAD_GDAL)));
|
||||
|
||||
//==================================================================================================
|
||||
|
||||
typedef tuple<string, Size> Ext_Size;
|
||||
typedef testing::TestWithParam<Ext_Size> Imgcodecs_ExtSize;
|
||||
|
||||
TEST_P(Imgcodecs_ExtSize, write_imageseq)
|
||||
{
|
||||
const string ext = get<0>(GetParam());
|
||||
const Size size = get<1>(GetParam());
|
||||
const Point2i center = Point2i(size.width / 2, size.height / 2);
|
||||
const int radius = std::min(size.height, size.width / 4);
|
||||
|
||||
for (int cn = 1; cn <= 4; cn++)
|
||||
{
|
||||
SCOPED_TRACE(format("channels %d", cn));
|
||||
std::vector<int> parameters;
|
||||
if (cn == 2)
|
||||
continue;
|
||||
if (cn == 4 && ext != ".tiff")
|
||||
continue;
|
||||
if (cn > 1 && (ext == ".pbm" || ext == ".pgm"))
|
||||
continue;
|
||||
if (cn != 3 && ext == ".ppm")
|
||||
continue;
|
||||
if (cn == 1 && ext == ".gif")
|
||||
continue;
|
||||
if (cn == 1 && ext == ".webp")
|
||||
continue;
|
||||
string filename = cv::tempfile(format("%d%s", cn, ext.c_str()).c_str());
|
||||
|
||||
Mat img_gt(size, CV_MAKETYPE(CV_8U, cn), Scalar::all(0));
|
||||
circle(img_gt, center, radius, Scalar::all(255));
|
||||
|
||||
#if 1
|
||||
if (ext == ".pbm" || ext == ".pgm" || ext == ".ppm")
|
||||
{
|
||||
parameters.push_back(IMWRITE_PXM_BINARY);
|
||||
parameters.push_back(0);
|
||||
}
|
||||
#endif
|
||||
ASSERT_TRUE(imwrite(filename, img_gt, parameters));
|
||||
Mat img = imread(filename, IMREAD_UNCHANGED);
|
||||
ASSERT_FALSE(img.empty());
|
||||
EXPECT_EQ(img_gt.size(), img.size());
|
||||
EXPECT_EQ(img_gt.channels(), img.channels());
|
||||
if (ext == ".pfm") {
|
||||
EXPECT_EQ(img_gt.depth(), CV_8U);
|
||||
EXPECT_EQ(img.depth(), CV_32F);
|
||||
} else {
|
||||
EXPECT_EQ(img_gt.depth(), img.depth());
|
||||
}
|
||||
EXPECT_EQ(cn, img.channels());
|
||||
|
||||
|
||||
if (ext == ".jpg")
|
||||
{
|
||||
// JPEG format does not provide 100% accuracy
|
||||
// using fuzzy image comparison
|
||||
double n = cvtest::norm(img, img_gt, NORM_L1);
|
||||
double expected = 0.07 * img.size().area();
|
||||
EXPECT_LT(n, expected);
|
||||
EXPECT_PRED_FORMAT2(cvtest::MatComparator(10, 0), img, img_gt);
|
||||
}
|
||||
else if (ext == ".pfm")
|
||||
{
|
||||
img_gt.convertTo(img_gt, CV_MAKETYPE(CV_32F, img.channels()));
|
||||
double n = cvtest::norm(img, img_gt, NORM_L2);
|
||||
EXPECT_LT(n, 1.);
|
||||
EXPECT_PRED_FORMAT2(cvtest::MatComparator(0, 0), img, img_gt);
|
||||
}
|
||||
else if (ext == ".gif")
|
||||
{
|
||||
// GIF encoder will reduce the number of colors to 256.
|
||||
// It is hard to compare image comparison by pixel unit.
|
||||
double n = cvtest::norm(img, img_gt, NORM_L1);
|
||||
double expected = 0.03 * img.size().area();
|
||||
EXPECT_LT(n, expected);
|
||||
}
|
||||
else
|
||||
{
|
||||
double n = cvtest::norm(img, img_gt, NORM_L2);
|
||||
EXPECT_LT(n, 1.);
|
||||
EXPECT_PRED_FORMAT2(cvtest::MatComparator(0, 0), img, img_gt);
|
||||
}
|
||||
|
||||
#if 0
|
||||
imshow("loaded", img);
|
||||
waitKey(0);
|
||||
#else
|
||||
EXPECT_EQ(0, remove(filename.c_str()));
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
const string all_exts[] =
|
||||
{
|
||||
#if defined(HAVE_PNG) || defined(HAVE_SPNG)
|
||||
".png",
|
||||
#endif
|
||||
#ifdef HAVE_TIFF
|
||||
".tiff",
|
||||
#endif
|
||||
#ifdef HAVE_JPEG
|
||||
".jpg",
|
||||
#endif
|
||||
".bmp",
|
||||
#ifdef HAVE_IMGCODEC_PXM
|
||||
".pam",
|
||||
".ppm",
|
||||
".pgm",
|
||||
".pbm",
|
||||
".pnm",
|
||||
#endif
|
||||
#ifdef HAVE_IMGCODEC_PFM
|
||||
".pfm",
|
||||
#endif
|
||||
#ifdef HAVE_IMGCODEC_GIF
|
||||
".gif",
|
||||
#endif
|
||||
#ifdef HAVE_WEBP
|
||||
".webp",
|
||||
#endif
|
||||
};
|
||||
|
||||
vector<Size> all_sizes()
|
||||
{
|
||||
vector<Size> res;
|
||||
for (int k = 1; k <= 5; ++k)
|
||||
res.push_back(Size(640 * k, 480 * k));
|
||||
return res;
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(All, Imgcodecs_ExtSize,
|
||||
testing::Combine(
|
||||
testing::ValuesIn(all_exts),
|
||||
testing::ValuesIn(all_sizes())));
|
||||
|
||||
#ifdef HAVE_IMGCODEC_PXM
|
||||
typedef testing::TestWithParam<bool> Imgcodecs_pbm;
|
||||
TEST_P(Imgcodecs_pbm, write_read)
|
||||
{
|
||||
bool binary = GetParam();
|
||||
const String ext = "pbm";
|
||||
const string full_name = cv::tempfile(ext.c_str());
|
||||
|
||||
Size size(640, 480);
|
||||
const Point2i center = Point2i(size.width / 2, size.height / 2);
|
||||
const int radius = std::min(size.height, size.width / 4);
|
||||
Mat image(size, CV_8UC1, Scalar::all(0));
|
||||
circle(image, center, radius, Scalar::all(255));
|
||||
|
||||
vector<int> pbm_params;
|
||||
pbm_params.push_back(IMWRITE_PXM_BINARY);
|
||||
pbm_params.push_back(binary);
|
||||
|
||||
imwrite( full_name, image, pbm_params );
|
||||
Mat loaded = imread(full_name, IMREAD_UNCHANGED);
|
||||
ASSERT_FALSE(loaded.empty());
|
||||
|
||||
EXPECT_EQ(0, cvtest::norm(loaded, image, NORM_INF));
|
||||
|
||||
FILE *f = fopen(full_name.c_str(), "rb");
|
||||
ASSERT_TRUE(f != NULL);
|
||||
ASSERT_EQ('P', getc(f));
|
||||
ASSERT_EQ('1' + (binary ? 3 : 0), getc(f));
|
||||
fclose(f);
|
||||
EXPECT_EQ(0, remove(full_name.c_str()));
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(All, Imgcodecs_pbm, testing::Bool());
|
||||
#endif
|
||||
|
||||
// See https://github.com/opencv/opencv/issues/27557
|
||||
typedef testing::TestWithParam<string> Imgcodecs_invalid_key;
|
||||
|
||||
TEST_P(Imgcodecs_invalid_key, encode_regression27557)
|
||||
{
|
||||
const string ext = GetParam();
|
||||
const int matType = ((ext == ".pbm") || (ext == ".pgm"))? CV_8UC1 : CV_8UC3;
|
||||
Mat src(100, 100, matType, Scalar(0, 255, 0));
|
||||
std::vector<uchar> buf;
|
||||
bool status = false;
|
||||
EXPECT_NO_THROW(status = imencode(ext, src, buf, { -1, -1 }));
|
||||
EXPECT_TRUE(status);
|
||||
}
|
||||
|
||||
TEST_P(Imgcodecs_invalid_key, write_regression27557)
|
||||
{
|
||||
const string ext = GetParam();
|
||||
string fname = tempfile(ext.c_str());
|
||||
|
||||
const int matType = ((ext == ".pbm") || (ext == ".pgm"))? CV_8UC1 : CV_8UC3;
|
||||
Mat src(100, 100, matType, Scalar(0, 255, 0));
|
||||
std::vector<uchar> buf;
|
||||
bool status = false;
|
||||
EXPECT_NO_THROW(status = imwrite(fname, src, { -1, -1 }));
|
||||
EXPECT_TRUE(status);
|
||||
remove(fname.c_str());
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(All, Imgcodecs_invalid_key, testing::ValuesIn(all_exts));
|
||||
|
||||
//==================================================================================================
|
||||
|
||||
TEST(Imgcodecs_Bmp, read_rle8)
|
||||
{
|
||||
const string root = cvtest::TS::ptr()->get_data_path();
|
||||
Mat rle = imread(root + "readwrite/rle8.bmp");
|
||||
ASSERT_FALSE(rle.empty());
|
||||
Mat ord = imread(root + "readwrite/ordinary.bmp");
|
||||
ASSERT_FALSE(ord.empty());
|
||||
EXPECT_LE(cvtest::norm(rle, ord, NORM_L2), 1.e-10);
|
||||
EXPECT_PRED_FORMAT2(cvtest::MatComparator(0, 0), rle, ord);
|
||||
}
|
||||
|
||||
TEST(Imgcodecs_Bmp, read_32bit_rgb)
|
||||
{
|
||||
const string root = cvtest::TS::ptr()->get_data_path();
|
||||
const string filenameInput = root + "readwrite/test_32bit_rgb.bmp";
|
||||
|
||||
Mat img;
|
||||
ASSERT_NO_THROW(img = cv::imread(filenameInput));
|
||||
ASSERT_FALSE(img.empty());
|
||||
ASSERT_EQ(CV_8UC3, img.type());
|
||||
|
||||
ASSERT_NO_THROW(img = cv::imread(filenameInput, IMREAD_UNCHANGED));
|
||||
ASSERT_FALSE(img.empty());
|
||||
ASSERT_EQ(CV_8UC3, img.type());
|
||||
|
||||
ASSERT_NO_THROW(img = cv::imread(filenameInput, IMREAD_COLOR | IMREAD_ANYCOLOR | IMREAD_ANYDEPTH));
|
||||
ASSERT_FALSE(img.empty());
|
||||
ASSERT_EQ(CV_8UC3, img.type());
|
||||
}
|
||||
|
||||
TEST(Imgcodecs_Bmp, rgba_bit_mask)
|
||||
{
|
||||
const string root = cvtest::TS::ptr()->get_data_path();
|
||||
const string filenameInput = root + "readwrite/test_rgba_mask.bmp";
|
||||
|
||||
const Mat img = cv::imread(filenameInput, IMREAD_UNCHANGED);
|
||||
ASSERT_FALSE(img.empty());
|
||||
ASSERT_EQ(CV_8UC4, img.type());
|
||||
|
||||
const uchar* data = img.ptr();
|
||||
ASSERT_EQ(data[3], 255);
|
||||
}
|
||||
|
||||
TEST(Imgcodecs_Bmp, read_32bit_xrgb)
|
||||
{
|
||||
const string root = cvtest::TS::ptr()->get_data_path();
|
||||
const string filenameInput = root + "readwrite/test_32bit_xrgb.bmp";
|
||||
|
||||
const Mat img = cv::imread(filenameInput, IMREAD_UNCHANGED);
|
||||
ASSERT_FALSE(img.empty());
|
||||
ASSERT_EQ(CV_8UC4, img.type());
|
||||
|
||||
const uchar* data = img.ptr();
|
||||
ASSERT_EQ(data[3], 255);
|
||||
}
|
||||
|
||||
TEST(Imgcodecs_Bmp, rgba_scale)
|
||||
{
|
||||
const string root = cvtest::TS::ptr()->get_data_path();
|
||||
const string filenameInput = root + "readwrite/test_rgba_scale.bmp";
|
||||
|
||||
Mat img = cv::imread(filenameInput, IMREAD_UNCHANGED);
|
||||
ASSERT_FALSE(img.empty());
|
||||
ASSERT_EQ(CV_8UC4, img.type());
|
||||
|
||||
uchar* data = img.ptr();
|
||||
ASSERT_EQ(data[0], 255);
|
||||
ASSERT_EQ(data[1], 255);
|
||||
ASSERT_EQ(data[2], 255);
|
||||
ASSERT_EQ(data[3], 255);
|
||||
|
||||
img = cv::imread(filenameInput, IMREAD_COLOR);
|
||||
ASSERT_FALSE(img.empty());
|
||||
ASSERT_EQ(CV_8UC3, img.type());
|
||||
|
||||
img = cv::imread(filenameInput, IMREAD_COLOR_RGB);
|
||||
ASSERT_FALSE(img.empty());
|
||||
ASSERT_EQ(CV_8UC3, img.type());
|
||||
|
||||
data = img.ptr();
|
||||
ASSERT_EQ(data[0], 255);
|
||||
ASSERT_EQ(data[1], 255);
|
||||
ASSERT_EQ(data[2], 255);
|
||||
|
||||
img = cv::imread(filenameInput, IMREAD_GRAYSCALE);
|
||||
ASSERT_FALSE(img.empty());
|
||||
ASSERT_EQ(CV_8UC1, img.type());
|
||||
|
||||
data = img.ptr();
|
||||
ASSERT_EQ(data[0], 255);
|
||||
}
|
||||
|
||||
typedef testing::TestWithParam<ImwriteBMPCompressionFlags> Imgcodecs_bmp_compress;
|
||||
TEST_P(Imgcodecs_bmp_compress, rgba32bpp)
|
||||
{
|
||||
const ImwriteBMPCompressionFlags comp = GetParam();
|
||||
|
||||
RNG rng = theRNG();
|
||||
Mat src(256, 256, CV_8UC4);
|
||||
rng.fill(src, RNG::UNIFORM, Scalar(0,0,0,0), Scalar(255,255,255,255));
|
||||
|
||||
vector<uint8_t> buf;
|
||||
bool ret = false;
|
||||
ASSERT_NO_THROW(ret = cv::imencode(".bmp", src, buf, {IMWRITE_BMP_COMPRESSION, static_cast<int>(comp)}));
|
||||
ASSERT_TRUE(ret);
|
||||
|
||||
ASSERT_EQ(buf[0x0e], comp == IMWRITE_BMP_COMPRESSION_RGB ? 40 : 124 ); // the size of header
|
||||
ASSERT_EQ(buf[0x0f], 0);
|
||||
ASSERT_EQ(buf[0x1c], 32); // the number of bits per pixel = 32
|
||||
ASSERT_EQ(buf[0x1d], 0);
|
||||
ASSERT_EQ(buf[0x1e], static_cast<int>(comp)); // the compression method
|
||||
ASSERT_EQ(buf[0x1f], 0);
|
||||
ASSERT_EQ(buf[0x20], 0);
|
||||
ASSERT_EQ(buf[0x21], 0);
|
||||
|
||||
Mat dst;
|
||||
ASSERT_NO_THROW(dst = cv::imdecode(buf, IMREAD_UNCHANGED));
|
||||
ASSERT_FALSE(dst.empty());
|
||||
|
||||
if(comp == IMWRITE_BMP_COMPRESSION_RGB)
|
||||
{
|
||||
// If BI_RGB is used, output BMP file stores RGB image.
|
||||
ASSERT_EQ(CV_8UC3, dst.type());
|
||||
Mat srcBGR;
|
||||
cv::cvtColor(src, srcBGR, cv::COLOR_BGRA2BGR);
|
||||
EXPECT_PRED_FORMAT2(cvtest::MatComparator(0, 0), srcBGR, dst);
|
||||
}
|
||||
else
|
||||
{
|
||||
// If BI_BITFIELDS is used, output BMP file stores RGBA image.
|
||||
ASSERT_EQ(CV_8UC4, dst.type());
|
||||
EXPECT_PRED_FORMAT2(cvtest::MatComparator(0, 0), src, dst);
|
||||
}
|
||||
|
||||
}
|
||||
INSTANTIATE_TEST_CASE_P(All,
|
||||
Imgcodecs_bmp_compress,
|
||||
testing::Values(
|
||||
IMWRITE_BMP_COMPRESSION_RGB,
|
||||
IMWRITE_BMP_COMPRESSION_BITFIELDS));
|
||||
|
||||
#ifdef HAVE_IMGCODEC_HDR
|
||||
TEST(Imgcodecs_Hdr, regression)
|
||||
{
|
||||
string folder = string(cvtest::TS::ptr()->get_data_path()) + "/readwrite/";
|
||||
string name_rle = folder + "rle.hdr";
|
||||
string name_no_rle = folder + "no_rle.hdr";
|
||||
Mat img_rle = imread(name_rle, -1);
|
||||
ASSERT_FALSE(img_rle.empty()) << "Could not open " << name_rle;
|
||||
Mat img_no_rle = imread(name_no_rle, -1);
|
||||
ASSERT_FALSE(img_no_rle.empty()) << "Could not open " << name_no_rle;
|
||||
|
||||
EXPECT_EQ(cvtest::norm(img_rle, img_no_rle, NORM_INF), 0.0);
|
||||
|
||||
string tmp_file_name = tempfile(".hdr");
|
||||
vector<int> param(2);
|
||||
param[0] = IMWRITE_HDR_COMPRESSION;
|
||||
for(int i = 0; i < 2; i++) {
|
||||
param[1] = i;
|
||||
imwrite(tmp_file_name, img_rle, param);
|
||||
Mat written_img = imread(tmp_file_name, -1);
|
||||
EXPECT_EQ(cvtest::norm(written_img, img_rle, NORM_INF), 0.0);
|
||||
}
|
||||
remove(tmp_file_name.c_str());
|
||||
}
|
||||
|
||||
TEST(Imgcodecs_Hdr, regression_imencode)
|
||||
{
|
||||
string folder = string(cvtest::TS::ptr()->get_data_path()) + "/readwrite/";
|
||||
string name = folder + "rle.hdr";
|
||||
Mat img_ref = imread(name, -1);
|
||||
ASSERT_FALSE(img_ref.empty()) << "Could not open " << name;
|
||||
|
||||
vector<int> params(2);
|
||||
params[0] = IMWRITE_HDR_COMPRESSION;
|
||||
{
|
||||
vector<uchar> buf;
|
||||
params[1] = IMWRITE_HDR_COMPRESSION_NONE;
|
||||
imencode(".hdr", img_ref, buf, params);
|
||||
Mat img = imdecode(buf, -1);
|
||||
EXPECT_EQ(cvtest::norm(img_ref, img, NORM_INF), 0.0);
|
||||
}
|
||||
{
|
||||
vector<uchar> buf;
|
||||
params[1] = IMWRITE_HDR_COMPRESSION_RLE;
|
||||
imencode(".hdr", img_ref, buf, params);
|
||||
Mat img = imdecode(buf, -1);
|
||||
EXPECT_EQ(cvtest::norm(img_ref, img, NORM_INF), 0.0);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_IMGCODEC_PXM
|
||||
TEST(Imgcodecs_Pam, read_write)
|
||||
{
|
||||
string folder = string(cvtest::TS::ptr()->get_data_path()) + "readwrite/";
|
||||
string filepath = folder + "lena.pam";
|
||||
|
||||
cv::Mat img = cv::imread(filepath);
|
||||
ASSERT_FALSE(img.empty());
|
||||
|
||||
std::vector<int> params;
|
||||
params.push_back(IMWRITE_PAM_TUPLETYPE);
|
||||
params.push_back(IMWRITE_PAM_FORMAT_RGB);
|
||||
|
||||
string writefile = cv::tempfile(".pam");
|
||||
EXPECT_NO_THROW(cv::imwrite(writefile, img, params));
|
||||
cv::Mat reread = cv::imread(writefile);
|
||||
|
||||
string writefile_no_param = cv::tempfile(".pam");
|
||||
EXPECT_NO_THROW(cv::imwrite(writefile_no_param, img));
|
||||
cv::Mat reread_no_param = cv::imread(writefile_no_param);
|
||||
|
||||
EXPECT_EQ(0, cvtest::norm(reread, reread_no_param, NORM_INF));
|
||||
EXPECT_EQ(0, cvtest::norm(img, reread, NORM_INF));
|
||||
|
||||
remove(writefile.c_str());
|
||||
remove(writefile_no_param.c_str());
|
||||
}
|
||||
|
||||
// Regression test: a 2-channel (GRAYSCALE_ALPHA) PAM decoded as single channel
|
||||
// used to overflow the output row in basic_conversion() (3 bytes written per
|
||||
// source pixel into a 1-channel row). Verify it decodes safely and correctly.
|
||||
TEST(Imgcodecs_Pam, decode_graya_as_gray)
|
||||
{
|
||||
const int width = 9, height = 3; // odd width to expose off-by-row overflow
|
||||
std::string header = cv::format(
|
||||
"P7\nWIDTH %d\nHEIGHT %d\nDEPTH 2\nMAXVAL 255\n"
|
||||
"TUPLTYPE GRAYSCALE_ALPHA\nENDHDR\n", width, height);
|
||||
|
||||
std::vector<uchar> buf(header.begin(), header.end());
|
||||
Mat gray_ref(height, width, CV_8UC1);
|
||||
for (int y = 0; y < height; y++)
|
||||
for (int x = 0; x < width; x++)
|
||||
{
|
||||
uchar gray = (uchar)((y * width + x) * 7 + 1);
|
||||
uchar alpha = (uchar)(255 - gray);
|
||||
gray_ref.at<uchar>(y, x) = gray;
|
||||
buf.push_back(gray); // channel 0: gray
|
||||
buf.push_back(alpha); // channel 1: alpha (must be ignored)
|
||||
}
|
||||
|
||||
Mat decoded;
|
||||
ASSERT_NO_THROW(decoded = imdecode(buf, IMREAD_GRAYSCALE));
|
||||
ASSERT_FALSE(decoded.empty());
|
||||
EXPECT_EQ(width, decoded.cols);
|
||||
EXPECT_EQ(height, decoded.rows);
|
||||
EXPECT_EQ(1, decoded.channels());
|
||||
EXPECT_EQ(0, cvtest::norm(gray_ref, decoded, NORM_INF));
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_IMGCODEC_PFM
|
||||
TEST(Imgcodecs_Pfm, read_write)
|
||||
{
|
||||
Mat img = imread(findDataFile("readwrite/lena.pam"));
|
||||
ASSERT_FALSE(img.empty());
|
||||
img.convertTo(img, CV_32F, 1/255.0f);
|
||||
|
||||
std::vector<int> params;
|
||||
string writefile = cv::tempfile(".pfm");
|
||||
EXPECT_NO_THROW(cv::imwrite(writefile, img, params));
|
||||
cv::Mat reread = cv::imread(writefile, IMREAD_UNCHANGED);
|
||||
|
||||
string writefile_no_param = cv::tempfile(".pfm");
|
||||
EXPECT_NO_THROW(cv::imwrite(writefile_no_param, img));
|
||||
cv::Mat reread_no_param = cv::imread(writefile_no_param, IMREAD_UNCHANGED);
|
||||
|
||||
EXPECT_EQ(0, cvtest::norm(reread, reread_no_param, NORM_INF));
|
||||
EXPECT_EQ(0, cvtest::norm(img, reread, NORM_INF));
|
||||
|
||||
EXPECT_EQ(0, remove(writefile.c_str()));
|
||||
EXPECT_EQ(0, remove(writefile_no_param.c_str()));
|
||||
}
|
||||
#endif
|
||||
|
||||
TEST(Imgcodecs, write_parameter_type)
|
||||
{
|
||||
cv::Mat m(10, 10, CV_8UC1, cv::Scalar::all(0));
|
||||
cv::Mat1b m_type = cv::Mat1b::zeros(10, 10);
|
||||
string tmp_file = cv::tempfile(".bmp");
|
||||
EXPECT_NO_THROW(cv::imwrite(tmp_file, cv::Mat(m * 2))) << "* Failed with cv::Mat";
|
||||
EXPECT_NO_THROW(cv::imwrite(tmp_file, m * 2)) << "* Failed with cv::MatExpr";
|
||||
EXPECT_NO_THROW(cv::imwrite(tmp_file, m_type)) << "* Failed with cv::Mat_";
|
||||
EXPECT_NO_THROW(cv::imwrite(tmp_file, m_type * 2)) << "* Failed with cv::MatExpr(Mat_)";
|
||||
cv::Matx<uchar, 10, 10> matx;
|
||||
EXPECT_NO_THROW(cv::imwrite(tmp_file, matx)) << "* Failed with cv::Matx";
|
||||
EXPECT_EQ(0, remove(tmp_file.c_str()));
|
||||
}
|
||||
|
||||
TEST(Imgcodecs, imdecode_user_buffer)
|
||||
{
|
||||
cv::Mat encoded = cv::Mat::zeros(1, 1024, CV_8UC1);
|
||||
cv::Mat user_buffer(1, 1024, CV_8UC1);
|
||||
cv::Mat result = cv::imdecode(encoded, IMREAD_ANYCOLOR, &user_buffer);
|
||||
EXPECT_TRUE(result.empty());
|
||||
// the function does not release user-provided buffer
|
||||
EXPECT_FALSE(user_buffer.empty());
|
||||
|
||||
result = cv::imdecode(encoded, IMREAD_ANYCOLOR);
|
||||
EXPECT_TRUE(result.empty());
|
||||
}
|
||||
|
||||
}} // namespace
|
||||
|
||||
#if defined(HAVE_OPENEXR) && defined(OPENCV_IMGCODECS_ENABLE_OPENEXR_TESTS)
|
||||
#include "test_exr.impl.hpp"
|
||||
#endif
|
||||
@@ -0,0 +1,294 @@
|
||||
// This file is part of OpenCV project.
|
||||
// It is subject to the license terms in the LICENSE file found in the top-level directory
|
||||
// of this distribution and at http://opencv.org/license.html
|
||||
#include "test_precomp.hpp"
|
||||
|
||||
namespace opencv_test { namespace {
|
||||
|
||||
#ifdef HAVE_JPEG
|
||||
|
||||
extern "C" {
|
||||
#include "jpeglib.h"
|
||||
}
|
||||
|
||||
TEST(Imgcodecs_Jpeg, encode_empty)
|
||||
{
|
||||
cv::Mat img;
|
||||
std::vector<uchar> jpegImg;
|
||||
ASSERT_THROW(cv::imencode(".jpg", img, jpegImg), cv::Exception);
|
||||
}
|
||||
|
||||
TEST(Imgcodecs_Jpeg, encode_decode_progressive_jpeg)
|
||||
{
|
||||
cvtest::TS& ts = *cvtest::TS::ptr();
|
||||
string input = string(ts.get_data_path()) + "../cv/shared/lena.png";
|
||||
cv::Mat img = cv::imread(input);
|
||||
ASSERT_FALSE(img.empty());
|
||||
|
||||
std::vector<int> params;
|
||||
params.push_back(IMWRITE_JPEG_PROGRESSIVE);
|
||||
params.push_back(1);
|
||||
|
||||
string output_progressive = cv::tempfile(".jpg");
|
||||
EXPECT_NO_THROW(cv::imwrite(output_progressive, img, params));
|
||||
cv::Mat img_jpg_progressive = cv::imread(output_progressive);
|
||||
|
||||
string output_normal = cv::tempfile(".jpg");
|
||||
EXPECT_NO_THROW(cv::imwrite(output_normal, img));
|
||||
cv::Mat img_jpg_normal = cv::imread(output_normal);
|
||||
|
||||
EXPECT_EQ(0, cvtest::norm(img_jpg_progressive, img_jpg_normal, NORM_INF));
|
||||
|
||||
EXPECT_EQ(0, remove(output_progressive.c_str()));
|
||||
EXPECT_EQ(0, remove(output_normal.c_str()));
|
||||
}
|
||||
|
||||
TEST(Imgcodecs_Jpeg, encode_decode_optimize_jpeg)
|
||||
{
|
||||
cvtest::TS& ts = *cvtest::TS::ptr();
|
||||
string input = string(ts.get_data_path()) + "../cv/shared/lena.png";
|
||||
cv::Mat img = cv::imread(input);
|
||||
ASSERT_FALSE(img.empty());
|
||||
|
||||
std::vector<int> params;
|
||||
params.push_back(IMWRITE_JPEG_OPTIMIZE);
|
||||
params.push_back(1);
|
||||
|
||||
string output_optimized = cv::tempfile(".jpg");
|
||||
EXPECT_NO_THROW(cv::imwrite(output_optimized, img, params));
|
||||
cv::Mat img_jpg_optimized = cv::imread(output_optimized);
|
||||
|
||||
string output_normal = cv::tempfile(".jpg");
|
||||
EXPECT_NO_THROW(cv::imwrite(output_normal, img));
|
||||
cv::Mat img_jpg_normal = cv::imread(output_normal);
|
||||
|
||||
EXPECT_EQ(0, cvtest::norm(img_jpg_optimized, img_jpg_normal, NORM_INF));
|
||||
|
||||
EXPECT_EQ(0, remove(output_optimized.c_str()));
|
||||
EXPECT_EQ(0, remove(output_normal.c_str()));
|
||||
}
|
||||
|
||||
TEST(Imgcodecs_Jpeg, encode_decode_rst_jpeg)
|
||||
{
|
||||
cvtest::TS& ts = *cvtest::TS::ptr();
|
||||
string input = string(ts.get_data_path()) + "../cv/shared/lena.png";
|
||||
cv::Mat img = cv::imread(input);
|
||||
ASSERT_FALSE(img.empty());
|
||||
|
||||
std::vector<int> params;
|
||||
params.push_back(IMWRITE_JPEG_RST_INTERVAL);
|
||||
params.push_back(1);
|
||||
|
||||
string output_rst = cv::tempfile(".jpg");
|
||||
EXPECT_NO_THROW(cv::imwrite(output_rst, img, params));
|
||||
cv::Mat img_jpg_rst = cv::imread(output_rst);
|
||||
|
||||
string output_normal = cv::tempfile(".jpg");
|
||||
EXPECT_NO_THROW(cv::imwrite(output_normal, img));
|
||||
cv::Mat img_jpg_normal = cv::imread(output_normal);
|
||||
|
||||
EXPECT_EQ(0, cvtest::norm(img_jpg_rst, img_jpg_normal, NORM_INF));
|
||||
|
||||
EXPECT_EQ(0, remove(output_rst.c_str()));
|
||||
EXPECT_EQ(0, remove(output_normal.c_str()));
|
||||
}
|
||||
|
||||
// See https://github.com/opencv/opencv/issues/25274
|
||||
typedef testing::TestWithParam<int> Imgcodecs_Jpeg_decode_cmyk;
|
||||
TEST_P(Imgcodecs_Jpeg_decode_cmyk, regression25274)
|
||||
{
|
||||
const int imread_flag = GetParam();
|
||||
|
||||
/*
|
||||
* "test_1_c4.jpg" is CMYK-JPEG.
|
||||
* $ convert test_1_c3.jpg -colorspace CMYK test_1_c4.jpg
|
||||
* $ identify test_1_c4.jpg
|
||||
* test_1_c4.jpg JPEG 480x640 480x640+0+0 8-bit CMYK 11240B 0.000u 0:00.000
|
||||
*/
|
||||
|
||||
cvtest::TS& ts = *cvtest::TS::ptr();
|
||||
|
||||
string rgb_filename = string(ts.get_data_path()) + "readwrite/test_1_c3.jpg";
|
||||
cv::Mat rgb_img = cv::imread(rgb_filename, imread_flag);
|
||||
ASSERT_FALSE(rgb_img.empty());
|
||||
|
||||
string cmyk_filename = string(ts.get_data_path()) + "readwrite/test_1_c4.jpg";
|
||||
cv::Mat cmyk_img = cv::imread(cmyk_filename, imread_flag);
|
||||
ASSERT_FALSE(cmyk_img.empty());
|
||||
|
||||
EXPECT_EQ(rgb_img.size(), cmyk_img.size());
|
||||
EXPECT_EQ(rgb_img.type(), cmyk_img.type());
|
||||
|
||||
// Jpeg is lossy compression.
|
||||
// There may be small differences in decoding results by environments.
|
||||
// -> 255 * 1% = 2.55 .
|
||||
EXPECT_LE(cvtest::norm(rgb_img, cmyk_img, NORM_INF), 3); // norm() <= 3
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P( /* nothing */,
|
||||
Imgcodecs_Jpeg_decode_cmyk,
|
||||
testing::Values(cv::IMREAD_COLOR,
|
||||
cv::IMREAD_COLOR_RGB,
|
||||
cv::IMREAD_GRAYSCALE,
|
||||
cv::IMREAD_ANYCOLOR));
|
||||
|
||||
//==================================================================================================
|
||||
|
||||
static const uint32_t default_sampling_factor = static_cast<uint32_t>(0x221111);
|
||||
|
||||
static uint32_t test_jpeg_subsampling( const Mat src, const vector<int> param )
|
||||
{
|
||||
vector<uint8_t> jpeg;
|
||||
|
||||
if ( cv::imencode(".jpg", src, jpeg, param ) == false )
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
if ( src.channels() != 3 )
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Find SOF Marker(FFC0)
|
||||
int sof_offset = 0; // not found.
|
||||
int jpeg_size = static_cast<int>( jpeg.size() );
|
||||
for ( int i = 0 ; i < jpeg_size - 1; i++ )
|
||||
{
|
||||
if ( (jpeg[i] == 0xff ) && ( jpeg[i+1] == 0xC0 ) )
|
||||
{
|
||||
sof_offset = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ( sof_offset == 0 )
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Extract Subsampling Factor from SOF.
|
||||
return ( jpeg[sof_offset + 0x0A + 3 * 0 + 1] << 16 ) +
|
||||
( jpeg[sof_offset + 0x0A + 3 * 1 + 1] << 8 ) +
|
||||
( jpeg[sof_offset + 0x0A + 3 * 2 + 1] ) ;
|
||||
}
|
||||
|
||||
TEST(Imgcodecs_Jpeg, encode_subsamplingfactor_default)
|
||||
{
|
||||
vector<int> param;
|
||||
Mat src( 48, 64, CV_8UC3, cv::Scalar::all(0) );
|
||||
EXPECT_EQ( default_sampling_factor, test_jpeg_subsampling(src, param) );
|
||||
}
|
||||
|
||||
TEST(Imgcodecs_Jpeg, encode_subsamplingfactor_usersetting_valid)
|
||||
{
|
||||
Mat src( 48, 64, CV_8UC3, cv::Scalar::all(0) );
|
||||
const uint32_t sampling_factor_list[] = {
|
||||
IMWRITE_JPEG_SAMPLING_FACTOR_411,
|
||||
IMWRITE_JPEG_SAMPLING_FACTOR_420,
|
||||
IMWRITE_JPEG_SAMPLING_FACTOR_422,
|
||||
IMWRITE_JPEG_SAMPLING_FACTOR_440,
|
||||
IMWRITE_JPEG_SAMPLING_FACTOR_444,
|
||||
};
|
||||
const int sampling_factor_list_num = 5;
|
||||
|
||||
for ( int i = 0 ; i < sampling_factor_list_num; i ++ )
|
||||
{
|
||||
vector<int> param;
|
||||
param.push_back( IMWRITE_JPEG_SAMPLING_FACTOR );
|
||||
param.push_back( sampling_factor_list[i] );
|
||||
EXPECT_EQ( sampling_factor_list[i], test_jpeg_subsampling(src, param) );
|
||||
}
|
||||
}
|
||||
|
||||
TEST(Imgcodecs_Jpeg, encode_subsamplingfactor_usersetting_invalid)
|
||||
{
|
||||
Mat src( 48, 64, CV_8UC3, cv::Scalar::all(0) );
|
||||
const uint32_t sampling_factor_list[] = { // Invalid list
|
||||
0x111112,
|
||||
0x000000,
|
||||
0x001111,
|
||||
0xFF1111,
|
||||
0x141111, // 1x4,1x1,1x1 - unknown
|
||||
0x241111, // 2x4,1x1,1x1 - unknown
|
||||
0x421111, // 4x2,1x1,1x1 - unknown
|
||||
0x441111, // 4x4,1x1,1x1 - 410(libjpeg cannot handle it)
|
||||
};
|
||||
const int sampling_factor_list_num = 8;
|
||||
|
||||
for ( int i = 0 ; i < sampling_factor_list_num; i ++ )
|
||||
{
|
||||
vector<int> param;
|
||||
param.push_back( IMWRITE_JPEG_SAMPLING_FACTOR );
|
||||
param.push_back( sampling_factor_list[i] );
|
||||
EXPECT_EQ( default_sampling_factor, test_jpeg_subsampling(src, param) );
|
||||
}
|
||||
}
|
||||
|
||||
//==================================================================================================
|
||||
// See https://github.com/opencv/opencv/issues/25646
|
||||
typedef testing::TestWithParam<std::tuple<int, int>> Imgcodecs_Jpeg_encode_withLumaChromaQuality;
|
||||
|
||||
TEST_P(Imgcodecs_Jpeg_encode_withLumaChromaQuality, basic)
|
||||
{
|
||||
const int luma = get<0>(GetParam());
|
||||
const int chroma = get<1>(GetParam());
|
||||
|
||||
cvtest::TS& ts = *cvtest::TS::ptr();
|
||||
string fname = string(ts.get_data_path()) + "../cv/shared/lena.png";
|
||||
|
||||
cv::Mat src = imread(fname, cv::IMREAD_COLOR);
|
||||
ASSERT_FALSE(src.empty());
|
||||
|
||||
// Add imread RGB test
|
||||
cv::Mat src_rgb = imread(fname, cv::IMREAD_COLOR_RGB);
|
||||
ASSERT_FALSE(src_rgb.empty());
|
||||
|
||||
cvtColor(src_rgb, src_rgb, COLOR_RGB2BGR);
|
||||
EXPECT_TRUE(cvtest::norm(src, src_rgb, NORM_INF) == 0);
|
||||
|
||||
std::vector<uint8_t> jpegNormal;
|
||||
ASSERT_NO_THROW(cv::imencode(".jpg", src, jpegNormal));
|
||||
|
||||
std::vector<int> param;
|
||||
param.push_back(IMWRITE_JPEG_LUMA_QUALITY);
|
||||
param.push_back(luma);
|
||||
param.push_back(IMWRITE_JPEG_CHROMA_QUALITY);
|
||||
param.push_back(chroma);
|
||||
|
||||
std::vector<uint8_t> jpegCustom;
|
||||
ASSERT_NO_THROW(cv::imencode(".jpg", src, jpegCustom, param));
|
||||
|
||||
#if JPEG_LIB_VERSION >= 70
|
||||
// For jpeg7+, we can support IMWRITE_JPEG_LUMA_QUALITY and IMWRITE_JPEG_CHROMA_QUALITY.
|
||||
if( (luma == 95 /* Default Luma Quality */ ) && ( chroma == 95 /* Default Chroma Quality */))
|
||||
{
|
||||
EXPECT_EQ(jpegNormal, jpegCustom);
|
||||
}
|
||||
else
|
||||
{
|
||||
EXPECT_NE(jpegNormal, jpegCustom);
|
||||
}
|
||||
#else
|
||||
// For jpeg6-, we cannot support IMWRITE_JPEG_LUMA/CHROMA_QUALITY because jpeg_default_qtables() is missing.
|
||||
// - IMWRITE_JPEG_LUMA_QUALITY updates internal parameter of IMWRITE_JPEG_QUALITY.
|
||||
// - IMWRITE_JPEG_CHROMA_QUALITY updates nothing.
|
||||
if( luma == 95 /* Default Jpeg Quality */ )
|
||||
{
|
||||
EXPECT_EQ(jpegNormal, jpegCustom);
|
||||
}
|
||||
else
|
||||
{
|
||||
EXPECT_NE(jpegNormal, jpegCustom);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P( /* nothing */,
|
||||
Imgcodecs_Jpeg_encode_withLumaChromaQuality,
|
||||
testing::Combine(
|
||||
testing::Values(70, 95, 100), // IMWRITE_JPEG_LUMA_QUALITY
|
||||
testing::Values(70, 95, 100) )); // IMWRITE_JPEG_CHROMA_QUALITY
|
||||
|
||||
#endif // HAVE_JPEG
|
||||
|
||||
}} // namespace
|
||||
@@ -0,0 +1,364 @@
|
||||
// This file is part of OpenCV project.
|
||||
// It is subject to the license terms in the LICENSE file found in the top-level
|
||||
// directory of this distribution and at http://opencv.org/license.html
|
||||
#include "test_precomp.hpp"
|
||||
|
||||
namespace opencv_test { namespace {
|
||||
|
||||
#ifdef HAVE_JPEGXL
|
||||
|
||||
#include <jxl/version.h> // For JPEGXL_MAJOR_VERSION and JPEGXL_MINOR_VERSION
|
||||
|
||||
typedef tuple<perf::MatType, int> MatType_and_Distance;
|
||||
typedef testing::TestWithParam<MatType_and_Distance> Imgcodecs_JpegXL_MatType;
|
||||
|
||||
TEST_P(Imgcodecs_JpegXL_MatType, write_read)
|
||||
{
|
||||
const int matType = get<0>(GetParam());
|
||||
const int distanceParam = get<1>(GetParam());
|
||||
|
||||
cv::Scalar col;
|
||||
// Jpeg XL supports lossy and lossless compressions.
|
||||
// Lossy compression may be small differences in decoding results by environments.
|
||||
double th;
|
||||
|
||||
switch( CV_MAT_DEPTH(matType) )
|
||||
{
|
||||
case CV_16U:
|
||||
col = cv::Scalar(124 * 256, 76 * 256, 42 * 256, 192 * 256 );
|
||||
th = 656; // = 65535 / 100;
|
||||
break;
|
||||
case CV_32F:
|
||||
col = cv::Scalar(0.486, 0.298, 0.165, 0.75);
|
||||
th = 1.0 / 100.0;
|
||||
break;
|
||||
default:
|
||||
case CV_8U:
|
||||
col = cv::Scalar(124, 76, 42, 192);
|
||||
th = 3; // = 255 / 100 (1%);
|
||||
break;
|
||||
}
|
||||
|
||||
// If increasing distanceParam, threshold should be increased.
|
||||
th *= (distanceParam >= 25) ? 5 : (distanceParam > 2) ? 3 : distanceParam;
|
||||
|
||||
bool ret = false;
|
||||
string tmp_fname = cv::tempfile(".jxl");
|
||||
Mat img_org(320, 480, matType, col);
|
||||
vector<int> param;
|
||||
param.push_back(IMWRITE_JPEGXL_DISTANCE);
|
||||
param.push_back(distanceParam);
|
||||
EXPECT_NO_THROW(ret = imwrite(tmp_fname, img_org, param));
|
||||
EXPECT_TRUE(ret);
|
||||
Mat img_decoded;
|
||||
EXPECT_NO_THROW(img_decoded = imread(tmp_fname, IMREAD_UNCHANGED));
|
||||
EXPECT_FALSE(img_decoded.empty());
|
||||
|
||||
EXPECT_LE(cvtest::norm(img_org, img_decoded, NORM_INF), th);
|
||||
|
||||
EXPECT_EQ(0, remove(tmp_fname.c_str()));
|
||||
}
|
||||
|
||||
TEST_P(Imgcodecs_JpegXL_MatType, encode_decode)
|
||||
{
|
||||
const int matType = get<0>(GetParam());
|
||||
const int distanceParam = get<1>(GetParam());
|
||||
|
||||
cv::Scalar col;
|
||||
// Jpeg XL supports lossy and lossless compressions.
|
||||
// Lossy compression may be small differences in decoding results by environments.
|
||||
double th;
|
||||
|
||||
// If alpha=0, libjxl modify color channels(BGR). So do not set it.
|
||||
switch( CV_MAT_DEPTH(matType) )
|
||||
{
|
||||
case CV_16U:
|
||||
col = cv::Scalar(124 * 256, 76 * 256, 42 * 256, 192 * 256 );
|
||||
th = 656; // = 65535 / 100;
|
||||
break;
|
||||
case CV_32F:
|
||||
col = cv::Scalar(0.486, 0.298, 0.165, 0.75);
|
||||
th = 1.0 / 100.0;
|
||||
break;
|
||||
default:
|
||||
case CV_8U:
|
||||
col = cv::Scalar(124, 76, 42, 192);
|
||||
th = 3; // = 255 / 100 (1%);
|
||||
break;
|
||||
}
|
||||
|
||||
// If increasing distanceParam, threshold should be increased.
|
||||
th *= (distanceParam >= 25) ? 5 : (distanceParam > 2) ? 3 : distanceParam;
|
||||
|
||||
bool ret = false;
|
||||
vector<uchar> buff;
|
||||
Mat img_org(320, 480, matType, col);
|
||||
vector<int> param;
|
||||
param.push_back(IMWRITE_JPEGXL_DISTANCE);
|
||||
param.push_back(distanceParam);
|
||||
EXPECT_NO_THROW(ret = imencode(".jxl", img_org, buff, param));
|
||||
EXPECT_TRUE(ret);
|
||||
Mat img_decoded;
|
||||
EXPECT_NO_THROW(img_decoded = imdecode(buff, IMREAD_UNCHANGED));
|
||||
EXPECT_FALSE(img_decoded.empty());
|
||||
|
||||
EXPECT_LE(cvtest::norm(img_org, img_decoded, NORM_INF), th);
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(
|
||||
/**/,
|
||||
Imgcodecs_JpegXL_MatType,
|
||||
testing::Combine(
|
||||
testing::Values(
|
||||
CV_8UC1, CV_8UC3, CV_8UC4,
|
||||
CV_16UC1, CV_16UC3, CV_16UC4,
|
||||
CV_32FC1, CV_32FC3, CV_32FC4
|
||||
),
|
||||
testing::Values( // Distance
|
||||
0, // Lossless
|
||||
1, // Default
|
||||
3, // Recomended Lossy Max
|
||||
25 // Specification Max
|
||||
)
|
||||
) );
|
||||
|
||||
|
||||
typedef tuple<int, int> Effort_and_Decoding_speed;
|
||||
typedef testing::TestWithParam<Effort_and_Decoding_speed> Imgcodecs_JpegXL_Effort_DecodingSpeed;
|
||||
|
||||
TEST_P(Imgcodecs_JpegXL_Effort_DecodingSpeed, encode_decode)
|
||||
{
|
||||
const int effort = get<0>(GetParam());
|
||||
const int speed = get<1>(GetParam());
|
||||
|
||||
cv::Scalar col = cv::Scalar(124,76,42);
|
||||
// Jpeg XL supports lossy and lossless compression.
|
||||
// Lossy compression may be small differences in decoding results by environments.
|
||||
double th = 3; // = 255 / 100 (1%);
|
||||
|
||||
bool ret = false;
|
||||
vector<uchar> buff;
|
||||
Mat img_org(320, 480, CV_8UC3, col);
|
||||
vector<int> param;
|
||||
param.push_back(IMWRITE_JPEGXL_EFFORT);
|
||||
param.push_back(effort);
|
||||
param.push_back(IMWRITE_JPEGXL_DECODING_SPEED);
|
||||
param.push_back(speed);
|
||||
EXPECT_NO_THROW(ret = imencode(".jxl", img_org, buff, param));
|
||||
EXPECT_TRUE(ret);
|
||||
Mat img_decoded;
|
||||
EXPECT_NO_THROW(img_decoded = imdecode(buff, IMREAD_UNCHANGED));
|
||||
EXPECT_FALSE(img_decoded.empty());
|
||||
|
||||
EXPECT_LE(cvtest::norm(img_org, img_decoded, NORM_INF), th);
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(
|
||||
/**/,
|
||||
Imgcodecs_JpegXL_Effort_DecodingSpeed,
|
||||
testing::Combine(
|
||||
testing::Values( // Effort
|
||||
1, // fastest
|
||||
7, // default
|
||||
9 // slowest
|
||||
),
|
||||
testing::Values( // Decoding Speed
|
||||
0, // default, slowest, and best quality/density
|
||||
2,
|
||||
4 // fastest, at the cost of some qulity/density
|
||||
)
|
||||
) );
|
||||
|
||||
TEST(Imgcodecs_JpegXL, encode_from_uncontinued_image)
|
||||
{
|
||||
cv::Mat src(100, 100, CV_8UC1, Scalar(40,50,10));
|
||||
cv::Mat roi = src(cv::Rect(10,20,30,50));
|
||||
EXPECT_FALSE(roi.isContinuous()); // uncontinued image
|
||||
|
||||
vector<uint8_t> buff;
|
||||
vector<int> param;
|
||||
bool ret = false;
|
||||
EXPECT_NO_THROW(ret = cv::imencode(".jxl", roi, buff, param));
|
||||
EXPECT_TRUE(ret);
|
||||
}
|
||||
|
||||
// See https://github.com/opencv/opencv/issues/26767
|
||||
|
||||
typedef tuple<perf::MatType, ImreadModes> MatType_and_ImreadFlag;
|
||||
typedef testing::TestWithParam<MatType_and_ImreadFlag> Imgcodecs_JpegXL_MatType_ImreadFlag;
|
||||
|
||||
TEST_P(Imgcodecs_JpegXL_MatType_ImreadFlag, all_imreadFlags)
|
||||
{
|
||||
string tmp_fname = cv::tempfile(".jxl");
|
||||
const int matType = get<0>(GetParam());
|
||||
const int imreadFlag = get<1>(GetParam());
|
||||
|
||||
Mat img(240, 320, matType);
|
||||
randu(img, Scalar(0, 0, 0, 255), Scalar(255, 255, 255, 255));
|
||||
|
||||
vector<int> param;
|
||||
param.push_back(IMWRITE_JPEGXL_DISTANCE);
|
||||
param.push_back(0 /* Lossless */);
|
||||
EXPECT_NO_THROW(imwrite(tmp_fname, img, param));
|
||||
|
||||
Mat img_decoded;
|
||||
EXPECT_NO_THROW(img_decoded = imread(tmp_fname, imreadFlag));
|
||||
EXPECT_FALSE(img_decoded.empty());
|
||||
|
||||
switch( imreadFlag )
|
||||
{
|
||||
case IMREAD_UNCHANGED:
|
||||
EXPECT_EQ( img.type(), img_decoded.type() );
|
||||
break;
|
||||
case IMREAD_GRAYSCALE:
|
||||
EXPECT_EQ( img_decoded.depth(), CV_8U );
|
||||
EXPECT_EQ( img_decoded.channels(), 1 );
|
||||
break;
|
||||
case IMREAD_COLOR:
|
||||
case IMREAD_COLOR_RGB:
|
||||
EXPECT_EQ( img_decoded.depth(), CV_8U );
|
||||
EXPECT_EQ( img_decoded.channels(), 3 );
|
||||
break;
|
||||
case IMREAD_ANYDEPTH:
|
||||
EXPECT_EQ( img_decoded.depth(), img.depth() );
|
||||
EXPECT_EQ( img_decoded.channels(), 1 );
|
||||
break;
|
||||
case IMREAD_ANYCOLOR:
|
||||
EXPECT_EQ( img_decoded.depth(), CV_8U ) ;
|
||||
EXPECT_EQ( img_decoded.channels(), img.channels() == 1 ? 1 : 3 ); // Alpha channel will be dropped.
|
||||
break;
|
||||
}
|
||||
remove(tmp_fname.c_str());
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(
|
||||
/**/,
|
||||
Imgcodecs_JpegXL_MatType_ImreadFlag,
|
||||
testing::Combine(
|
||||
testing::Values(
|
||||
CV_8UC1, CV_8UC3, CV_8UC4,
|
||||
CV_16UC1, CV_16UC3, CV_16UC4,
|
||||
CV_32FC1, CV_32FC3, CV_32FC4
|
||||
),
|
||||
testing::Values(
|
||||
IMREAD_UNCHANGED,
|
||||
IMREAD_GRAYSCALE,
|
||||
IMREAD_COLOR,
|
||||
IMREAD_COLOR_RGB,
|
||||
IMREAD_ANYDEPTH,
|
||||
IMREAD_ANYCOLOR
|
||||
)
|
||||
) );
|
||||
|
||||
TEST(Imgcodecs_JpegXL, imdecode_truncated_stream)
|
||||
{
|
||||
cv::Mat src(100, 100, CV_8UC1, Scalar(40,50,10));
|
||||
vector<uint8_t> buff;
|
||||
vector<int> param;
|
||||
|
||||
bool ret = false;
|
||||
EXPECT_NO_THROW(ret = cv::imencode(".jxl", src, buff, param));
|
||||
EXPECT_TRUE(ret);
|
||||
|
||||
// Try to decode non-truncated image.
|
||||
cv::Mat decoded;
|
||||
EXPECT_NO_THROW(decoded = cv::imdecode(buff, cv::IMREAD_COLOR));
|
||||
EXPECT_FALSE(decoded.empty());
|
||||
|
||||
// Try to decode truncated image.
|
||||
buff.resize(buff.size() - 1 );
|
||||
EXPECT_NO_THROW(decoded = cv::imdecode(buff, cv::IMREAD_COLOR));
|
||||
EXPECT_TRUE(decoded.empty());
|
||||
}
|
||||
|
||||
TEST(Imgcodecs_JpegXL, imread_truncated_stream)
|
||||
{
|
||||
string tmp_fname = cv::tempfile(".jxl");
|
||||
cv::Mat src(100, 100, CV_8UC1, Scalar(40,50,10));
|
||||
vector<uint8_t> buff;
|
||||
vector<int> param;
|
||||
|
||||
bool ret = false;
|
||||
EXPECT_NO_THROW(ret = cv::imencode(".jxl", src, buff, param));
|
||||
EXPECT_TRUE(ret);
|
||||
|
||||
// Try to decode non-truncated image.
|
||||
FILE *fp = nullptr;
|
||||
|
||||
fp = fopen(tmp_fname.c_str(), "wb");
|
||||
EXPECT_TRUE(fp != nullptr);
|
||||
fwrite(&buff[0], sizeof(uint8_t), buff.size(), fp);
|
||||
fclose(fp);
|
||||
|
||||
cv::Mat decoded;
|
||||
EXPECT_NO_THROW(decoded = cv::imread(tmp_fname, cv::IMREAD_COLOR));
|
||||
EXPECT_FALSE(decoded.empty());
|
||||
|
||||
// Try to decode truncated image.
|
||||
fp = fopen(tmp_fname.c_str(), "wb");
|
||||
EXPECT_TRUE(fp != nullptr);
|
||||
fwrite(&buff[0], sizeof(uint8_t), buff.size() - 1, fp);
|
||||
fclose(fp);
|
||||
|
||||
EXPECT_NO_THROW(decoded = cv::imread(tmp_fname, cv::IMREAD_COLOR));
|
||||
EXPECT_TRUE(decoded.empty());
|
||||
|
||||
// Delete temporary file
|
||||
remove(tmp_fname.c_str());
|
||||
}
|
||||
|
||||
// See https://github.com/opencv/opencv/issues/27382
|
||||
TEST(Imgcodecs_JpegXL, imencode_regression27382)
|
||||
{
|
||||
cv::Mat image(1024, 1024, CV_16U);
|
||||
cv::RNG rng(1024);
|
||||
rng.fill(image, cv::RNG::NORMAL, 0, 65535);
|
||||
|
||||
std::vector<unsigned char> buffer;
|
||||
std::vector<int> params = {cv::IMWRITE_JPEGXL_DISTANCE, 0}; // lossless
|
||||
|
||||
EXPECT_NO_THROW(cv::imencode(".jxl", image, buffer, params));
|
||||
|
||||
cv::Mat decoded;
|
||||
EXPECT_NO_THROW(decoded = cv::imdecode(buffer, cv::IMREAD_UNCHANGED));
|
||||
EXPECT_FALSE(decoded.empty());
|
||||
|
||||
cv::Mat diff;
|
||||
cv::absdiff(image, decoded, diff);
|
||||
double max_diff = 0.0;
|
||||
cv::minMaxLoc(diff, nullptr, &max_diff);
|
||||
EXPECT_EQ(max_diff, 0 );
|
||||
}
|
||||
|
||||
TEST(Imgcodecs_JpegXL, imencode_regression27382_2)
|
||||
{
|
||||
cv::Mat image(1024, 1024, CV_16U);
|
||||
cv::RNG rng(1024);
|
||||
rng.fill(image, cv::RNG::NORMAL, 0, 65535);
|
||||
|
||||
std::vector<unsigned char> buffer;
|
||||
std::vector<int> params = {cv::IMWRITE_JPEGXL_QUALITY, 100}; // lossless
|
||||
|
||||
EXPECT_NO_THROW(cv::imencode(".jxl", image, buffer, params));
|
||||
|
||||
cv::Mat decoded;
|
||||
EXPECT_NO_THROW(decoded = cv::imdecode(buffer, cv::IMREAD_UNCHANGED));
|
||||
EXPECT_FALSE(decoded.empty());
|
||||
|
||||
cv::Mat diff;
|
||||
cv::absdiff(image, decoded, diff);
|
||||
double max_diff = 0.0;
|
||||
cv::minMaxLoc(diff, nullptr, &max_diff);
|
||||
#if JPEGXL_MAJOR_VERSION > 0 || JPEGXL_MINOR_VERSION >= 10
|
||||
// Quality parameter is supported with libjxl v0.10.0 or later
|
||||
EXPECT_EQ(max_diff, 0); // Lossless
|
||||
#else
|
||||
EXPECT_NE(max_diff, 0); // Lossy
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
#endif // HAVE_JPEGXL
|
||||
|
||||
} // namespace
|
||||
} // namespace opencv_test
|
||||
@@ -0,0 +1,10 @@
|
||||
// This file is part of OpenCV project.
|
||||
// It is subject to the license terms in the LICENSE file found in the top-level directory
|
||||
// of this distribution and at http://opencv.org/license.html
|
||||
#include "test_precomp.hpp"
|
||||
|
||||
#if defined(HAVE_HPX)
|
||||
#include <hpx/hpx_main.hpp>
|
||||
#endif
|
||||
|
||||
CV_TEST_MAIN("highgui")
|
||||
@@ -0,0 +1,648 @@
|
||||
// This file is part of OpenCV project.
|
||||
// It is subject to the license terms in the LICENSE file found in the top-level directory
|
||||
// of this distribution and at http://opencv.org/license.html
|
||||
#include "test_precomp.hpp"
|
||||
#include "test_common.hpp"
|
||||
|
||||
namespace opencv_test { namespace {
|
||||
|
||||
#if defined(HAVE_PNG) || defined(HAVE_SPNG)
|
||||
|
||||
// See https://github.com/opencv/opencv/pull/28615
|
||||
// Precision differences in 16-bit grayscale conversion between old and modern libpng versions
|
||||
#define OPENCV_IMGCODECS_PNG_EPS_DEFAULT (4)
|
||||
#ifndef OPENCV_IMGCODECS_PNG_EPS_16BIT_GRAY
|
||||
#define OPENCV_IMGCODECS_PNG_EPS_16BIT_GRAY (OPENCV_IMGCODECS_PNG_EPS_DEFAULT)
|
||||
#endif
|
||||
|
||||
TEST(Imgcodecs_Png, write_big)
|
||||
{
|
||||
const string root = cvtest::TS::ptr()->get_data_path();
|
||||
const string filename = root + "readwrite/read.png";
|
||||
Mat img;
|
||||
ASSERT_NO_THROW(img = imread(filename));
|
||||
ASSERT_FALSE(img.empty());
|
||||
EXPECT_EQ(13043, img.cols);
|
||||
EXPECT_EQ(13917, img.rows);
|
||||
|
||||
vector<uchar> buff;
|
||||
bool status = false;
|
||||
ASSERT_NO_THROW(status = imencode(".png", img, buff, { IMWRITE_PNG_ZLIBBUFFER_SIZE, 1024*1024 }));
|
||||
ASSERT_TRUE(status);
|
||||
#ifdef HAVE_PNG
|
||||
EXPECT_EQ((size_t)816219, buff.size());
|
||||
#else
|
||||
EXPECT_EQ((size_t)817407, buff.size());
|
||||
#endif
|
||||
}
|
||||
|
||||
TEST(Imgcodecs_Png, encode)
|
||||
{
|
||||
vector<uchar> buff;
|
||||
Mat img_gt = Mat::zeros(1000, 1000, CV_8U);
|
||||
vector<int> param;
|
||||
param.push_back(IMWRITE_PNG_COMPRESSION);
|
||||
param.push_back(3); //default(3) 0-9.
|
||||
bool status = false;
|
||||
EXPECT_NO_THROW(status = imencode(".png", img_gt, buff, param));
|
||||
ASSERT_TRUE(status);
|
||||
Mat img;
|
||||
EXPECT_NO_THROW(img = imdecode(buff, IMREAD_ANYDEPTH)); // hang
|
||||
EXPECT_FALSE(img.empty());
|
||||
EXPECT_PRED_FORMAT2(cvtest::MatComparator(0, 0), img, img_gt);
|
||||
}
|
||||
|
||||
TEST(Imgcodecs_Png, regression_ImreadVSCvtColor)
|
||||
{
|
||||
const string root = cvtest::TS::ptr()->get_data_path();
|
||||
const string imgName = root + "../cv/shared/lena.png";
|
||||
Mat original_image = imread(imgName);
|
||||
Mat gray_by_codec = imread(imgName, IMREAD_GRAYSCALE);
|
||||
Mat gray_by_cvt;
|
||||
cvtColor(original_image, gray_by_cvt, COLOR_BGR2GRAY);
|
||||
|
||||
Mat diff;
|
||||
absdiff(gray_by_codec, gray_by_cvt, diff);
|
||||
EXPECT_LT(cvtest::mean(diff)[0], 1.);
|
||||
EXPECT_PRED_FORMAT2(cvtest::MatComparator(10, 0), gray_by_codec, gray_by_cvt);
|
||||
}
|
||||
|
||||
// Test OpenCV issue 3075 is solved
|
||||
TEST(Imgcodecs_Png, read_color_palette_with_alpha)
|
||||
{
|
||||
const string root = cvtest::TS::ptr()->get_data_path();
|
||||
Mat img;
|
||||
|
||||
// First Test : Read PNG with alpha, imread flag -1
|
||||
img = imread(root + "readwrite/color_palette_alpha.png", IMREAD_UNCHANGED);
|
||||
ASSERT_FALSE(img.empty());
|
||||
ASSERT_TRUE(img.channels() == 4);
|
||||
|
||||
// pixel is red in BGRA
|
||||
EXPECT_EQ(img.at<Vec4b>(0, 0), Vec4b(0, 0, 255, 255));
|
||||
EXPECT_EQ(img.at<Vec4b>(0, 1), Vec4b(0, 0, 255, 255));
|
||||
|
||||
// Second Test : Read PNG without alpha, imread flag -1
|
||||
img = imread(root + "readwrite/color_palette_no_alpha.png", IMREAD_UNCHANGED);
|
||||
ASSERT_FALSE(img.empty());
|
||||
ASSERT_TRUE(img.channels() == 3);
|
||||
|
||||
// pixel is red in BGR
|
||||
EXPECT_EQ(img.at<Vec3b>(0, 0), Vec3b(0, 0, 255));
|
||||
EXPECT_EQ(img.at<Vec3b>(0, 1), Vec3b(0, 0, 255));
|
||||
|
||||
// Third Test : Read PNG with alpha, imread flag 1
|
||||
img = imread(root + "readwrite/color_palette_alpha.png", IMREAD_COLOR);
|
||||
ASSERT_FALSE(img.empty());
|
||||
ASSERT_TRUE(img.channels() == 3);
|
||||
|
||||
// pixel is red in BGR
|
||||
EXPECT_EQ(img.at<Vec3b>(0, 0), Vec3b(0, 0, 255));
|
||||
EXPECT_EQ(img.at<Vec3b>(0, 1), Vec3b(0, 0, 255));
|
||||
|
||||
img = imread(root + "readwrite/color_palette_alpha.png", IMREAD_COLOR_RGB);
|
||||
ASSERT_FALSE(img.empty());
|
||||
ASSERT_TRUE(img.channels() == 3);
|
||||
|
||||
// pixel is red in RGB
|
||||
EXPECT_EQ(img.at<Vec3b>(0, 0), Vec3b(255, 0, 0));
|
||||
EXPECT_EQ(img.at<Vec3b>(0, 1), Vec3b(255, 0, 0));
|
||||
|
||||
// Fourth Test : Read PNG without alpha, imread flag 1
|
||||
img = imread(root + "readwrite/color_palette_no_alpha.png", IMREAD_COLOR);
|
||||
ASSERT_FALSE(img.empty());
|
||||
ASSERT_TRUE(img.channels() == 3);
|
||||
|
||||
// pixel is red in BGR
|
||||
EXPECT_EQ(img.at<Vec3b>(0, 0), Vec3b(0, 0, 255));
|
||||
EXPECT_EQ(img.at<Vec3b>(0, 1), Vec3b(0, 0, 255));
|
||||
|
||||
img = imread(root + "readwrite/color_palette_no_alpha.png", IMREAD_COLOR_RGB);
|
||||
ASSERT_FALSE(img.empty());
|
||||
ASSERT_TRUE(img.channels() == 3);
|
||||
|
||||
// pixel is red in RGB
|
||||
EXPECT_EQ(img.at<Vec3b>(0, 0), Vec3b(255, 0, 0));
|
||||
EXPECT_EQ(img.at<Vec3b>(0, 1), Vec3b(255, 0, 0));
|
||||
}
|
||||
|
||||
// IHDR shall be first.
|
||||
// See https://github.com/opencv/opencv/issues/27295
|
||||
TEST(Imgcodecs_Png, decode_regression27295)
|
||||
{
|
||||
vector<uchar> buff;
|
||||
Mat src = Mat::zeros(240, 180, CV_8UC3);
|
||||
vector<int> param;
|
||||
EXPECT_NO_THROW(imencode(".png", src, buff, param));
|
||||
|
||||
Mat img;
|
||||
|
||||
// If IHDR chunk found as the first chunk, output shall not be empty.
|
||||
// 8 means PNG signature length.
|
||||
// 4 means length field(uint32_t).
|
||||
EXPECT_EQ(buff[8+4+0], 'I');
|
||||
EXPECT_EQ(buff[8+4+1], 'H');
|
||||
EXPECT_EQ(buff[8+4+2], 'D');
|
||||
EXPECT_EQ(buff[8+4+3], 'R');
|
||||
EXPECT_NO_THROW(img = imdecode(buff, IMREAD_COLOR));
|
||||
EXPECT_FALSE(img.empty());
|
||||
|
||||
// If Non-IHDR chunk found as the first chunk, output shall be empty.
|
||||
buff[8+4+0] = 'i'; // Not 'I'
|
||||
buff[8+4+1] = 'H';
|
||||
buff[8+4+2] = 'D';
|
||||
buff[8+4+3] = 'R';
|
||||
EXPECT_NO_THROW(img = imdecode(buff, IMREAD_COLOR));
|
||||
EXPECT_TRUE(img.empty());
|
||||
|
||||
// If CgBI chunk (Apple private) found as the first chunk, output shall be empty with special message.
|
||||
buff[8+4+0] = 'C';
|
||||
buff[8+4+1] = 'g';
|
||||
buff[8+4+2] = 'B';
|
||||
buff[8+4+3] = 'I';
|
||||
EXPECT_NO_THROW(img = imdecode(buff, IMREAD_COLOR));
|
||||
EXPECT_TRUE(img.empty());
|
||||
}
|
||||
|
||||
// The program must not crash even when decoding a corrupted APNG image.
|
||||
// See https://github.com/opencv/opencv/issues/27744
|
||||
#if defined(HAVE_PNG) // APNG is supported only with using libpng
|
||||
TEST(Imgcodecs_Png, decode_regression27744)
|
||||
{
|
||||
// Create APNG stream
|
||||
Animation anim;
|
||||
for(size_t i = 0 ; i < 3 ; i++) {
|
||||
Mat frame(120, 120, CV_8UC3, Scalar(0,0,0));
|
||||
putText(frame, cv::format("%d", static_cast<int>(i)), Point(5, 28), FONT_HERSHEY_SIMPLEX, .5, Scalar(100, 255, 0, 255), 2);
|
||||
anim.frames.push_back(frame);
|
||||
anim.durations.push_back(30);
|
||||
}
|
||||
bool ret = false;
|
||||
vector<uchar> buff;
|
||||
EXPECT_NO_THROW(ret = imencodeanimation(".png", anim, buff));
|
||||
ASSERT_TRUE(ret) << "imencodeanimation() returns false";
|
||||
|
||||
// Find IDAT chunk
|
||||
const vector<uchar> IDAT = {'I', 'D', 'A', 'T' };
|
||||
std::vector<uchar>::iterator it = std::search(buff.begin(), buff.end(), IDAT.begin(), IDAT.end());
|
||||
ASSERT_FALSE(it == buff.end()) << "IDAT chunk not found";
|
||||
|
||||
// Determine the range to test
|
||||
// APNG stream contains as { len0, len1, len2, len3, 'I', 'D', 'A' 'T', ... }
|
||||
size_t idx = std::distance(buff.begin(), it); // 'I' position
|
||||
size_t len = (buff[idx-4] << 24) + (buff[idx-3] << 16) +
|
||||
(buff[idx-2] << 8) + (buff[idx-1]); // IDAT chunk length
|
||||
idx = idx + 4; // Move to IDAT body
|
||||
|
||||
// Test
|
||||
for(size_t i = 0; i < len; i++, idx++) {
|
||||
vector<uint8_t> work = buff;
|
||||
work[idx] = static_cast<uint8_t>((static_cast<uint32_t>(work[idx]) + 1) & 0xff);
|
||||
|
||||
Mat dst;
|
||||
EXPECT_NO_THROW(dst = imdecode(work, cv::IMREAD_COLOR));
|
||||
if(dst.empty()) {
|
||||
// libpng detects some error, but the program is not crashed. Test is passed.
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
typedef testing::TestWithParam<string> Imgcodecs_Png_PngSuite;
|
||||
|
||||
// Parameterized test for decoding PNG files from the PNGSuite test set
|
||||
TEST_P(Imgcodecs_Png_PngSuite, decode)
|
||||
{
|
||||
// Construct full paths for the PNG image and corresponding ground truth XML file
|
||||
const string root = cvtest::TS::ptr()->get_data_path();
|
||||
const string filename = root + "pngsuite/" + GetParam() + ".png";
|
||||
const string xml_filename = root + "pngsuite/" + GetParam() + ".xml";
|
||||
|
||||
// Load the XML file containing the ground truth data
|
||||
FileStorage fs(xml_filename, FileStorage::READ);
|
||||
ASSERT_TRUE(fs.isOpened()); // Ensure the file was opened successfully
|
||||
|
||||
// Load the image using IMREAD_UNCHANGED to preserve original format
|
||||
Mat src = imread(filename, IMREAD_UNCHANGED);
|
||||
ASSERT_FALSE(src.empty()); // Ensure the image was loaded successfully
|
||||
|
||||
// Load the ground truth matrix from XML
|
||||
Mat gt;
|
||||
fs.getFirstTopLevelNode() >> gt;
|
||||
|
||||
// Compare the image loaded with IMREAD_UNCHANGED to the ground truth
|
||||
EXPECT_PRED_FORMAT2(cvtest::MatComparator(0, 0), src, gt);
|
||||
|
||||
// Declare matrices for ground truth in different imread flag combinations
|
||||
Mat gt_0, gt_1, gt_2, gt_3, gt_256, gt_258;
|
||||
|
||||
// Handle grayscale 8-bit and 16-bit images
|
||||
if (gt.channels() == 1)
|
||||
{
|
||||
gt.copyTo(gt_2); // For IMREAD_ANYDEPTH
|
||||
if (gt.depth() == CV_16U)
|
||||
gt_2.convertTo(gt_0, CV_8U, 1. / 256);
|
||||
else
|
||||
gt_0 = gt_2; // For IMREAD_GRAYSCALE
|
||||
|
||||
cvtColor(gt_2, gt_3, COLOR_GRAY2BGR); // For IMREAD_COLOR | IMREAD_ANYDEPTH
|
||||
|
||||
if (gt.depth() == CV_16U)
|
||||
gt_3.convertTo(gt_1, CV_8U, 1. / 256);
|
||||
else
|
||||
gt_1 = gt_3; // For IMREAD_COLOR
|
||||
|
||||
gt_256 = gt_1; // For IMREAD_COLOR_RGB
|
||||
gt_258 = gt_3; // For IMREAD_COLOR_RGB | IMREAD_ANYDEPTH
|
||||
}
|
||||
|
||||
// Handle color images (3 or 4 channels) with 8-bit and 16-bit depth
|
||||
if (gt.channels() > 1)
|
||||
{
|
||||
// Convert to grayscale
|
||||
cvtColor(gt, gt_2, COLOR_BGRA2GRAY);
|
||||
if (gt.depth() == CV_16U)
|
||||
gt_2.convertTo(gt_0, CV_8U, 1. / 256);
|
||||
else
|
||||
gt_0 = gt_2;
|
||||
|
||||
// Convert to 3-channel BGR
|
||||
if (gt.channels() == 3)
|
||||
gt.copyTo(gt_3);
|
||||
else
|
||||
cvtColor(gt, gt_3, COLOR_BGRA2BGR);
|
||||
|
||||
if (gt.depth() == CV_16U)
|
||||
gt_3.convertTo(gt_1, CV_8U, 1. / 256);
|
||||
else
|
||||
gt_1 = gt_3;
|
||||
|
||||
// Convert to RGB for IMREAD_COLOR_RGB variants
|
||||
cvtColor(gt_1, gt_256, COLOR_BGR2RGB);
|
||||
cvtColor(gt_3, gt_258, COLOR_BGR2RGB);
|
||||
}
|
||||
|
||||
const double epsGrayAnydepth = ((gt.depth() == CV_16U) && (gt.channels() > 1)) ? OPENCV_IMGCODECS_PNG_EPS_16BIT_GRAY: OPENCV_IMGCODECS_PNG_EPS_DEFAULT;
|
||||
|
||||
// Perform comparisons with different imread flags
|
||||
EXPECT_PRED_FORMAT2(cvtest::MatComparator(1, 0), imread(filename, IMREAD_GRAYSCALE), gt_0);
|
||||
EXPECT_PRED_FORMAT2(cvtest::MatComparator(1, 0), imread(filename, IMREAD_COLOR), gt_1);
|
||||
EXPECT_PRED_FORMAT2(cvtest::MatComparator(epsGrayAnydepth, 0), imread(filename, IMREAD_ANYDEPTH), gt_2); // IMREAD_GRAYSCALE is used.
|
||||
EXPECT_PRED_FORMAT2(cvtest::MatComparator(0, 0), imread(filename, IMREAD_COLOR | IMREAD_ANYDEPTH), gt_3);
|
||||
EXPECT_PRED_FORMAT2(cvtest::MatComparator(1, 0), imread(filename, IMREAD_COLOR_RGB), gt_256);
|
||||
EXPECT_PRED_FORMAT2(cvtest::MatComparator(0, 0), imread(filename, IMREAD_COLOR_RGB | IMREAD_ANYDEPTH), gt_258);
|
||||
|
||||
// Uncomment this block to write out the decoded images for visual/manual inspection
|
||||
// or for regenerating expected ground truth PNGs (for example, after changing decoder logic).
|
||||
#if 0
|
||||
imwrite(filename + "_0.png", imread(filename, IMREAD_GRAYSCALE));
|
||||
imwrite(filename + "_1.png", imread(filename, IMREAD_COLOR));
|
||||
imwrite(filename + "_2.png", imread(filename, IMREAD_ANYDEPTH));
|
||||
imwrite(filename + "_3.png", imread(filename, IMREAD_COLOR | IMREAD_ANYDEPTH));
|
||||
imwrite(filename + "_256.png", imread(filename, IMREAD_COLOR_RGB));
|
||||
imwrite(filename + "_258.png", imread(filename, IMREAD_COLOR_RGB | IMREAD_ANYDEPTH));
|
||||
#endif
|
||||
|
||||
// Uncomment this block to verify that saved images (from above) load identically
|
||||
// when read back with IMREAD_UNCHANGED. Helps ensure write-read symmetry.
|
||||
#if 0
|
||||
EXPECT_PRED_FORMAT2(cvtest::MatComparator(0, 0), imread(filename, IMREAD_GRAYSCALE), imread(filename + "_0.png", IMREAD_UNCHANGED));
|
||||
EXPECT_PRED_FORMAT2(cvtest::MatComparator(0, 0), imread(filename, IMREAD_COLOR), imread(filename + "_1.png", IMREAD_UNCHANGED));
|
||||
EXPECT_PRED_FORMAT2(cvtest::MatComparator(0, 0), imread(filename, IMREAD_ANYDEPTH), imread(filename + "_2.png", IMREAD_UNCHANGED));
|
||||
EXPECT_PRED_FORMAT2(cvtest::MatComparator(0, 0), imread(filename, IMREAD_COLOR | IMREAD_ANYDEPTH), imread(filename + "_3.png", IMREAD_UNCHANGED));
|
||||
EXPECT_PRED_FORMAT2(cvtest::MatComparator(0, 0), imread(filename, IMREAD_COLOR_RGB), imread(filename + "_256.png", IMREAD_UNCHANGED));
|
||||
EXPECT_PRED_FORMAT2(cvtest::MatComparator(0, 0), imread(filename, IMREAD_COLOR_RGB | IMREAD_ANYDEPTH), imread(filename + "_258.png", IMREAD_UNCHANGED));
|
||||
#endif
|
||||
}
|
||||
|
||||
const string pngsuite_files[] =
|
||||
{
|
||||
"basi0g01",
|
||||
"basi0g02",
|
||||
"basi0g04",
|
||||
"basi0g08",
|
||||
"basi0g16",
|
||||
"basi2c08",
|
||||
"basi2c16",
|
||||
"basi3p01",
|
||||
"basi3p02",
|
||||
"basi3p04",
|
||||
"basi3p08",
|
||||
"basi4a08",
|
||||
"basi4a16",
|
||||
"basi6a08",
|
||||
"basi6a16",
|
||||
"basn0g01",
|
||||
"basn0g02",
|
||||
"basn0g04",
|
||||
"basn0g08",
|
||||
"basn0g16",
|
||||
"basn2c08",
|
||||
"basn2c16",
|
||||
"basn3p01",
|
||||
"basn3p02",
|
||||
"basn3p04",
|
||||
"basn3p08",
|
||||
"basn4a08",
|
||||
"basn4a16",
|
||||
"basn6a08",
|
||||
"basn6a16",
|
||||
"bgai4a08",
|
||||
"bgai4a16",
|
||||
"bgan6a08",
|
||||
"bgan6a16",
|
||||
"bgbn4a08",
|
||||
"bggn4a16",
|
||||
"bgwn6a08",
|
||||
"bgyn6a16",
|
||||
"ccwn2c08",
|
||||
"ccwn3p08",
|
||||
"cdfn2c08",
|
||||
"cdhn2c08",
|
||||
"cdsn2c08",
|
||||
"cdun2c08",
|
||||
"ch1n3p04",
|
||||
"ch2n3p08",
|
||||
"cm0n0g04",
|
||||
"cm7n0g04",
|
||||
"cm9n0g04",
|
||||
"cs3n2c16",
|
||||
"cs3n3p08",
|
||||
"cs5n2c08",
|
||||
"cs5n3p08",
|
||||
"cs8n2c08",
|
||||
"cs8n3p08",
|
||||
"ct0n0g04",
|
||||
"ct1n0g04",
|
||||
"cten0g04",
|
||||
"ctfn0g04",
|
||||
"ctgn0g04",
|
||||
"cthn0g04",
|
||||
"ctjn0g04",
|
||||
"ctzn0g04",
|
||||
"exif2c08",
|
||||
"f00n0g08",
|
||||
"f00n2c08",
|
||||
"f01n0g08",
|
||||
"f01n2c08",
|
||||
"f02n0g08",
|
||||
"f02n2c08",
|
||||
"f03n0g08",
|
||||
"f03n2c08",
|
||||
"f04n0g08",
|
||||
"f04n2c08",
|
||||
"f99n0g04",
|
||||
"g03n0g16",
|
||||
"g04n0g16",
|
||||
"g05n0g16",
|
||||
"g07n0g16",
|
||||
"g10n0g16",
|
||||
"g10n2c08",
|
||||
"g10n3p04",
|
||||
"g25n0g16",
|
||||
"oi1n0g16",
|
||||
"oi1n2c16",
|
||||
"oi2n0g16",
|
||||
"oi2n2c16",
|
||||
"oi4n0g16",
|
||||
"oi4n2c16",
|
||||
"oi9n0g16",
|
||||
"oi9n2c16",
|
||||
"pp0n2c16",
|
||||
"pp0n6a08",
|
||||
"ps1n0g08",
|
||||
"ps1n2c16",
|
||||
"ps2n0g08",
|
||||
"ps2n2c16",
|
||||
"s01i3p01",
|
||||
"s01n3p01",
|
||||
"s02i3p01",
|
||||
"s02n3p01",
|
||||
"s03i3p01",
|
||||
"s03n3p01",
|
||||
"s04i3p01",
|
||||
"s04n3p01",
|
||||
"s05i3p02",
|
||||
"s05n3p02",
|
||||
"s06i3p02",
|
||||
"s06n3p02",
|
||||
"s07i3p02",
|
||||
"s07n3p02",
|
||||
"s08i3p02",
|
||||
"s08n3p02",
|
||||
"s09i3p02",
|
||||
"s09n3p02",
|
||||
"s32i3p04",
|
||||
"s32n3p04",
|
||||
"s33i3p04",
|
||||
"s33n3p04",
|
||||
"s34i3p04",
|
||||
"s34n3p04",
|
||||
"s35i3p04",
|
||||
"s35n3p04",
|
||||
"s36i3p04",
|
||||
"s36n3p04",
|
||||
"s37i3p04",
|
||||
"s37n3p04",
|
||||
"s38i3p04",
|
||||
"s38n3p04",
|
||||
"s39i3p04",
|
||||
"s39n3p04",
|
||||
"s40i3p04",
|
||||
"s40n3p04",
|
||||
"tbbn0g04",
|
||||
"tbbn2c16",
|
||||
"tbbn3p08",
|
||||
"tbgn2c16",
|
||||
"tbgn3p08",
|
||||
"tbrn2c08",
|
||||
"tbwn0g16",
|
||||
"tbwn3p08",
|
||||
"tbyn3p08",
|
||||
"tm3n3p02",
|
||||
"tp0n0g08",
|
||||
"tp0n2c08",
|
||||
"tp0n3p08",
|
||||
"tp1n3p08",
|
||||
"z00n2c08",
|
||||
"z03n2c08",
|
||||
"z06n2c08",
|
||||
"z09n2c08",
|
||||
};
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(/*nothing*/, Imgcodecs_Png_PngSuite,
|
||||
testing::ValuesIn(pngsuite_files));
|
||||
|
||||
typedef testing::TestWithParam<string> Imgcodecs_Png_PngSuite_Gamma;
|
||||
|
||||
// Parameterized test for decoding PNG files from the PNGSuite test set
|
||||
TEST_P(Imgcodecs_Png_PngSuite_Gamma, decode)
|
||||
{
|
||||
// Construct full paths for the PNG image and corresponding ground truth XML file
|
||||
const string root = cvtest::TS::ptr()->get_data_path();
|
||||
const string filename = root + "pngsuite/" + GetParam() + ".png";
|
||||
const string xml_filename = root + "pngsuite/" + GetParam() + ".xml";
|
||||
|
||||
// Load the XML file containing the ground truth data
|
||||
FileStorage fs(xml_filename, FileStorage::READ);
|
||||
ASSERT_TRUE(fs.isOpened()); // Ensure the file was opened successfully
|
||||
|
||||
// Load the image using IMREAD_UNCHANGED to preserve original format
|
||||
Mat src = imread(filename, IMREAD_UNCHANGED);
|
||||
ASSERT_FALSE(src.empty()); // Ensure the image was loaded successfully
|
||||
|
||||
// Load the ground truth matrix from XML
|
||||
Mat gt;
|
||||
fs.getFirstTopLevelNode() >> gt;
|
||||
|
||||
// Compare the image loaded with IMREAD_UNCHANGED to the ground truth
|
||||
EXPECT_PRED_FORMAT2(cvtest::MatComparator(0, 0), src, gt);
|
||||
}
|
||||
|
||||
const string pngsuite_files_gamma[] =
|
||||
{
|
||||
"g03n2c08",
|
||||
"g03n3p04",
|
||||
"g04n2c08",
|
||||
"g04n3p04",
|
||||
"g05n2c08",
|
||||
"g05n3p04",
|
||||
"g07n2c08",
|
||||
"g07n3p04",
|
||||
"g25n2c08",
|
||||
"g25n3p04"
|
||||
};
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(/*nothing*/, Imgcodecs_Png_PngSuite_Gamma,
|
||||
testing::ValuesIn(pngsuite_files_gamma));
|
||||
|
||||
typedef testing::TestWithParam<string> Imgcodecs_Png_PngSuite_Corrupted;
|
||||
|
||||
TEST_P(Imgcodecs_Png_PngSuite_Corrupted, decode)
|
||||
{
|
||||
const string root = cvtest::TS::ptr()->get_data_path();
|
||||
const string filename = root + "pngsuite/" + GetParam() + ".png";
|
||||
|
||||
Mat src = imread(filename, IMREAD_UNCHANGED);
|
||||
|
||||
// Corrupted files should not be read
|
||||
EXPECT_TRUE(src.empty());
|
||||
}
|
||||
|
||||
const string pngsuite_files_corrupted[] = {
|
||||
"xc1n0g08",
|
||||
"xc9n2c08",
|
||||
"xcrn0g04",
|
||||
"xcsn0g01",
|
||||
"xd0n2c08",
|
||||
"xd3n2c08",
|
||||
"xd9n2c08",
|
||||
"xdtn0g01",
|
||||
"xhdn0g08",
|
||||
"xlfn0g04",
|
||||
"xs1n0g01",
|
||||
"xs2n0g01",
|
||||
"xs4n0g01",
|
||||
"xs7n0g01",
|
||||
};
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(/*nothing*/, Imgcodecs_Png_PngSuite_Corrupted,
|
||||
testing::ValuesIn(pngsuite_files_corrupted));
|
||||
|
||||
CV_ENUM(PNGStrategy, IMWRITE_PNG_STRATEGY_DEFAULT, IMWRITE_PNG_STRATEGY_FILTERED, IMWRITE_PNG_STRATEGY_HUFFMAN_ONLY, IMWRITE_PNG_STRATEGY_RLE, IMWRITE_PNG_STRATEGY_FIXED);
|
||||
CV_ENUM(PNGFilters, IMWRITE_PNG_FILTER_NONE, IMWRITE_PNG_FILTER_SUB, IMWRITE_PNG_FILTER_UP, IMWRITE_PNG_FILTER_AVG, IMWRITE_PNG_FILTER_PAETH, IMWRITE_PNG_FAST_FILTERS, IMWRITE_PNG_ALL_FILTERS);
|
||||
|
||||
typedef testing::TestWithParam<testing::tuple<string, PNGStrategy, PNGFilters, int>> Imgcodecs_Png_Encode;
|
||||
|
||||
TEST_P(Imgcodecs_Png_Encode, params)
|
||||
{
|
||||
const string root = cvtest::TS::ptr()->get_data_path();
|
||||
const string filename = root + "pngsuite/" + get<0>(GetParam());
|
||||
|
||||
const int strategy = get<1>(GetParam());
|
||||
const int filter = get<2>(GetParam());
|
||||
const int compression_level = get<3>(GetParam());
|
||||
|
||||
std::vector<uchar> file_buf;
|
||||
readFileBytes(filename, file_buf);
|
||||
Mat src = imdecode(file_buf, IMREAD_UNCHANGED);
|
||||
EXPECT_FALSE(src.empty()) << "Cannot decode test image " << filename;
|
||||
|
||||
vector<uchar> buf;
|
||||
imencode(".png", src, buf, { IMWRITE_PNG_COMPRESSION, compression_level, IMWRITE_PNG_STRATEGY, strategy, IMWRITE_PNG_FILTER, filter });
|
||||
EXPECT_EQ(buf.size(), file_buf.size());
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(/**/,
|
||||
Imgcodecs_Png_Encode,
|
||||
testing::Values(
|
||||
make_tuple("f00n0g08.png", IMWRITE_PNG_STRATEGY_DEFAULT, IMWRITE_PNG_FILTER_NONE, 6),
|
||||
make_tuple("f00n2c08.png", IMWRITE_PNG_STRATEGY_DEFAULT, IMWRITE_PNG_FILTER_NONE, 6),
|
||||
make_tuple("f01n0g08.png", IMWRITE_PNG_STRATEGY_FILTERED, IMWRITE_PNG_FILTER_SUB, 6),
|
||||
make_tuple("f01n2c08.png", IMWRITE_PNG_STRATEGY_FILTERED, IMWRITE_PNG_FILTER_SUB, 6),
|
||||
make_tuple("f02n0g08.png", IMWRITE_PNG_STRATEGY_FILTERED, IMWRITE_PNG_FILTER_UP, 6),
|
||||
make_tuple("f02n2c08.png", IMWRITE_PNG_STRATEGY_FILTERED, IMWRITE_PNG_FILTER_UP, 6),
|
||||
make_tuple("f03n0g08.png", IMWRITE_PNG_STRATEGY_FILTERED, IMWRITE_PNG_FILTER_AVG, 6),
|
||||
make_tuple("f03n2c08.png", IMWRITE_PNG_STRATEGY_FILTERED, IMWRITE_PNG_FILTER_AVG, 6),
|
||||
make_tuple("f04n0g08.png", IMWRITE_PNG_STRATEGY_FILTERED, IMWRITE_PNG_FILTER_PAETH, 6),
|
||||
make_tuple("f04n2c08.png", IMWRITE_PNG_STRATEGY_FILTERED, IMWRITE_PNG_FILTER_PAETH, 6),
|
||||
make_tuple("z03n2c08.png", IMWRITE_PNG_STRATEGY_FILTERED, IMWRITE_PNG_ALL_FILTERS, 3),
|
||||
make_tuple("z06n2c08.png", IMWRITE_PNG_STRATEGY_FILTERED, IMWRITE_PNG_ALL_FILTERS, 6),
|
||||
make_tuple("z09n2c08.png", IMWRITE_PNG_STRATEGY_FILTERED, IMWRITE_PNG_ALL_FILTERS, 9)));
|
||||
|
||||
typedef testing::TestWithParam<testing::tuple<string, int, size_t>> Imgcodecs_Png_ImwriteFlags;
|
||||
|
||||
TEST_P(Imgcodecs_Png_ImwriteFlags, compression_level)
|
||||
{
|
||||
const string root = cvtest::TS::ptr()->get_data_path();
|
||||
const string filename = root + get<0>(GetParam());
|
||||
|
||||
const int compression_level = get<1>(GetParam());
|
||||
const size_t compression_level_output_size = get<2>(GetParam());
|
||||
|
||||
Mat src = imread(filename, IMREAD_UNCHANGED);
|
||||
EXPECT_FALSE(src.empty()) << "Cannot read test image " << filename;
|
||||
|
||||
vector<uchar> buf;
|
||||
imencode(".png", src, buf, { IMWRITE_PNG_COMPRESSION, compression_level });
|
||||
EXPECT_EQ(buf.size(), compression_level_output_size);
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(/**/,
|
||||
Imgcodecs_Png_ImwriteFlags,
|
||||
testing::Values(
|
||||
make_tuple("../perf/512x512.png", 0, 788279),
|
||||
make_tuple("../perf/512x512.png", 1, 179503),
|
||||
make_tuple("../perf/512x512.png", 2, 176007),
|
||||
make_tuple("../perf/512x512.png", 3, 170497),
|
||||
make_tuple("../perf/512x512.png", 4, 163357),
|
||||
make_tuple("../perf/512x512.png", 5, 159190),
|
||||
make_tuple("../perf/512x512.png", 6, 156621),
|
||||
make_tuple("../perf/512x512.png", 7, 155696),
|
||||
make_tuple("../perf/512x512.png", 8, 153708),
|
||||
make_tuple("../perf/512x512.png", 9, 152181)));
|
||||
|
||||
// See https://github.com/opencv/opencv/issues/27614
|
||||
typedef testing::TestWithParam<int> Imgcodecs_Png_ZLIBBUFFER_SIZE;
|
||||
TEST_P(Imgcodecs_Png_ZLIBBUFFER_SIZE, encode_regression_27614)
|
||||
{
|
||||
Mat img(320,240,CV_8UC3,cv::Scalar(64,76,43));
|
||||
vector<uint8_t> buff;
|
||||
bool status = false;
|
||||
ASSERT_NO_THROW(status = imencode(".png", img, buff, { IMWRITE_PNG_ZLIBBUFFER_SIZE, GetParam() }));
|
||||
ASSERT_TRUE(status);
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(/*nothing*/, Imgcodecs_Png_ZLIBBUFFER_SIZE,
|
||||
testing::Values(5,
|
||||
6, // Minimum limit
|
||||
8192, // Default value
|
||||
131072, // 128 KiB
|
||||
262144, // 256 KiB
|
||||
1048576, // Maximum limit
|
||||
1048577));
|
||||
|
||||
#endif // HAVE_PNG
|
||||
|
||||
}} // namespace
|
||||
@@ -0,0 +1,102 @@
|
||||
// This file is part of OpenCV project.
|
||||
// It is subject to the license terms in the LICENSE file found in the top-level directory
|
||||
// of this distribution and at http://opencv.org/license.html
|
||||
#ifndef __OPENCV_TEST_PRECOMP_HPP__
|
||||
#define __OPENCV_TEST_PRECOMP_HPP__
|
||||
|
||||
#include "opencv2/ts.hpp"
|
||||
#include "opencv2/imgcodecs.hpp"
|
||||
|
||||
namespace cv {
|
||||
|
||||
static inline
|
||||
void PrintTo(const ImreadModes& val, std::ostream* os)
|
||||
{
|
||||
int v = val;
|
||||
if (v == IMREAD_UNCHANGED && (v & IMREAD_IGNORE_ORIENTATION) != 0)
|
||||
{
|
||||
CV_Assert(IMREAD_UNCHANGED == -1);
|
||||
*os << "IMREAD_UNCHANGED";
|
||||
return;
|
||||
}
|
||||
if ((v & IMREAD_COLOR) != 0)
|
||||
{
|
||||
CV_Assert(IMREAD_COLOR == 1);
|
||||
v &= ~IMREAD_COLOR;
|
||||
*os << "IMREAD_COLOR" << (v == 0 ? "" : " | ");
|
||||
}
|
||||
else if ((v & IMREAD_COLOR_RGB) != 0)
|
||||
{
|
||||
CV_Assert(IMREAD_COLOR_RGB == 256);
|
||||
v &= ~IMREAD_COLOR_RGB;
|
||||
*os << "IMREAD_COLOR_RGB" << (v == 0 ? "" : " | ");
|
||||
}
|
||||
else if ((v & IMREAD_ANYCOLOR) != 0)
|
||||
{
|
||||
// Do nothing
|
||||
}
|
||||
else
|
||||
{
|
||||
CV_Assert(IMREAD_GRAYSCALE == 0);
|
||||
*os << "IMREAD_GRAYSCALE" << (v == 0 ? "" : " | ");
|
||||
}
|
||||
if ((v & IMREAD_ANYDEPTH) != 0)
|
||||
{
|
||||
v &= ~IMREAD_ANYDEPTH;
|
||||
*os << "IMREAD_ANYDEPTH" << (v == 0 ? "" : " | ");
|
||||
}
|
||||
if ((v & IMREAD_ANYCOLOR) != 0)
|
||||
{
|
||||
v &= ~IMREAD_ANYCOLOR;
|
||||
*os << "IMREAD_ANYCOLOR" << (v == 0 ? "" : " | ");
|
||||
}
|
||||
if ((v & IMREAD_LOAD_GDAL) != 0)
|
||||
{
|
||||
v &= ~IMREAD_LOAD_GDAL;
|
||||
*os << "IMREAD_LOAD_GDAL" << (v == 0 ? "" : " | ");
|
||||
}
|
||||
if ((v & IMREAD_IGNORE_ORIENTATION) != 0)
|
||||
{
|
||||
v &= ~IMREAD_IGNORE_ORIENTATION;
|
||||
*os << "IMREAD_IGNORE_ORIENTATION" << (v == 0 ? "" : " | ");
|
||||
}
|
||||
switch (v)
|
||||
{
|
||||
case IMREAD_UNCHANGED: return;
|
||||
case IMREAD_GRAYSCALE: return;
|
||||
case IMREAD_COLOR: return;
|
||||
case IMREAD_ANYDEPTH: return;
|
||||
case IMREAD_ANYCOLOR: return;
|
||||
case IMREAD_LOAD_GDAL: return;
|
||||
case IMREAD_REDUCED_GRAYSCALE_2: // fallthru
|
||||
case IMREAD_REDUCED_COLOR_2: *os << "REDUCED_2"; return;
|
||||
case IMREAD_REDUCED_GRAYSCALE_4: // fallthru
|
||||
case IMREAD_REDUCED_COLOR_4: *os << "REDUCED_4"; return;
|
||||
case IMREAD_REDUCED_GRAYSCALE_8: // fallthru
|
||||
case IMREAD_REDUCED_COLOR_8: *os << "REDUCED_8"; return;
|
||||
case IMREAD_IGNORE_ORIENTATION: return;
|
||||
case IMREAD_COLOR_RGB: return;
|
||||
} // don't use "default:" to emit compiler warnings
|
||||
*os << "IMREAD_UNKNOWN(" << (int)v << ")";
|
||||
}
|
||||
|
||||
static inline
|
||||
void PrintTo(const ImwriteBMPCompressionFlags& val, std::ostream* os)
|
||||
{
|
||||
switch(val)
|
||||
{
|
||||
case IMWRITE_BMP_COMPRESSION_RGB:
|
||||
*os << "IMWRITE_BMP_COMPRESSION_RGB";
|
||||
break;
|
||||
case IMWRITE_BMP_COMPRESSION_BITFIELDS:
|
||||
*os << "IMWRITE_BMP_COMPRESSION_BITFIELDS";
|
||||
break;
|
||||
default:
|
||||
*os << "IMWRITE_BMP_COMPRESSION_UNKNOWN(" << (int)val << ")";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,631 @@
|
||||
// This file is part of OpenCV project.
|
||||
// It is subject to the license terms in the LICENSE file found in the top-level directory
|
||||
// of this distribution and at http://opencv.org/license.html
|
||||
#include "test_precomp.hpp"
|
||||
#include "test_common.hpp"
|
||||
|
||||
namespace opencv_test { namespace {
|
||||
|
||||
/* < <file_name, image_size>, <imread mode, scale> > */
|
||||
typedef tuple< tuple<string, Size>, tuple<ImreadModes, int> > Imgcodecs_Resize_t;
|
||||
|
||||
typedef testing::TestWithParam< Imgcodecs_Resize_t > Imgcodecs_Resize;
|
||||
|
||||
/* resize_flag_and_dims = <imread_flag, scale>*/
|
||||
const tuple <ImreadModes, int> resize_flag_and_dims[] =
|
||||
{
|
||||
make_tuple(IMREAD_UNCHANGED, 1),
|
||||
make_tuple(IMREAD_REDUCED_GRAYSCALE_2, 2),
|
||||
make_tuple(IMREAD_REDUCED_GRAYSCALE_4, 4),
|
||||
make_tuple(IMREAD_REDUCED_GRAYSCALE_8, 8),
|
||||
make_tuple(IMREAD_REDUCED_COLOR_2, 2),
|
||||
make_tuple(IMREAD_REDUCED_COLOR_4, 4),
|
||||
make_tuple(IMREAD_REDUCED_COLOR_8, 8)
|
||||
};
|
||||
|
||||
const tuple<string, Size> images[] =
|
||||
{
|
||||
#ifdef HAVE_JPEG
|
||||
make_tuple<string, Size>("../cv/imgproc/stuff.jpg", Size(640, 480)),
|
||||
#endif
|
||||
#if defined(HAVE_PNG) || defined(HAVE_SPNG)
|
||||
make_tuple<string, Size>("../cv/shared/pic1.png", Size(400, 300)),
|
||||
#endif
|
||||
make_tuple<string, Size>("../highgui/readwrite/ordinary.bmp", Size(480, 272)),
|
||||
};
|
||||
|
||||
TEST_P(Imgcodecs_Resize, imread_reduce_flags)
|
||||
{
|
||||
const string file_name = findDataFile(get<0>(get<0>(GetParam())));
|
||||
const Size imageSize = get<1>(get<0>(GetParam()));
|
||||
|
||||
const int imread_flag = get<0>(get<1>(GetParam()));
|
||||
const int scale = get<1>(get<1>(GetParam()));
|
||||
|
||||
const int cols = imageSize.width / scale;
|
||||
const int rows = imageSize.height / scale;
|
||||
{
|
||||
Mat img = imread(file_name, imread_flag);
|
||||
ASSERT_FALSE(img.empty());
|
||||
EXPECT_EQ(cols, img.cols);
|
||||
EXPECT_EQ(rows, img.rows);
|
||||
}
|
||||
}
|
||||
|
||||
//==================================================================================================
|
||||
|
||||
TEST_P(Imgcodecs_Resize, imdecode_reduce_flags)
|
||||
{
|
||||
const string file_name = findDataFile(get<0>(get<0>(GetParam())));
|
||||
const Size imageSize = get<1>(get<0>(GetParam()));
|
||||
|
||||
const int imread_flag = get<0>(get<1>(GetParam()));
|
||||
const int scale = get<1>(get<1>(GetParam()));
|
||||
|
||||
const int cols = imageSize.width / scale;
|
||||
const int rows = imageSize.height / scale;
|
||||
|
||||
const std::ios::openmode mode = std::ios::in | std::ios::binary;
|
||||
std::ifstream ifs(file_name.c_str(), mode);
|
||||
ASSERT_TRUE(ifs.is_open());
|
||||
|
||||
ifs.seekg(0, std::ios::end);
|
||||
const size_t sz = static_cast<size_t>(ifs.tellg());
|
||||
ifs.seekg(0, std::ios::beg);
|
||||
|
||||
std::vector<char> content(sz);
|
||||
ifs.read((char*)content.data(), sz);
|
||||
ASSERT_FALSE(ifs.fail());
|
||||
|
||||
{
|
||||
Mat img = imdecode(Mat(content), imread_flag);
|
||||
ASSERT_FALSE(img.empty());
|
||||
EXPECT_EQ(cols, img.cols);
|
||||
EXPECT_EQ(rows, img.rows);
|
||||
}
|
||||
}
|
||||
|
||||
//==================================================================================================
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(/*nothing*/, Imgcodecs_Resize,
|
||||
testing::Combine(
|
||||
testing::ValuesIn(images),
|
||||
testing::ValuesIn(resize_flag_and_dims)
|
||||
)
|
||||
);
|
||||
|
||||
//==================================================================================================
|
||||
|
||||
TEST(Imgcodecs_Image, read_write_bmp)
|
||||
{
|
||||
const size_t IMAGE_COUNT = 10;
|
||||
const double thresDbell = 32;
|
||||
|
||||
for (size_t i = 0; i < IMAGE_COUNT; ++i)
|
||||
{
|
||||
stringstream s; s << i;
|
||||
const string digit = s.str();
|
||||
const string src_name = TS::ptr()->get_data_path() + "../python/images/QCIF_0" + digit + ".bmp";
|
||||
const string dst_name = cv::tempfile((digit + ".bmp").c_str());
|
||||
Mat image = imread(src_name);
|
||||
ASSERT_FALSE(image.empty());
|
||||
|
||||
resize(image, image, Size(968, 757), 0.0, 0.0, INTER_CUBIC);
|
||||
imwrite(dst_name, image);
|
||||
Mat loaded = imread(dst_name);
|
||||
ASSERT_FALSE(loaded.empty());
|
||||
|
||||
double psnr = cvtest::PSNR(loaded, image);
|
||||
EXPECT_GT(psnr, thresDbell);
|
||||
|
||||
vector<uchar> from_file;
|
||||
|
||||
FILE *f = fopen(dst_name.c_str(), "rb");
|
||||
fseek(f, 0, SEEK_END);
|
||||
long len = ftell(f);
|
||||
from_file.resize((size_t)len);
|
||||
fseek(f, 0, SEEK_SET);
|
||||
from_file.resize(fread(&from_file[0], 1, from_file.size(), f));
|
||||
fclose(f);
|
||||
|
||||
vector<uchar> buf;
|
||||
imencode(".bmp", image, buf);
|
||||
ASSERT_EQ(buf, from_file);
|
||||
|
||||
Mat buf_loaded = imdecode(Mat(buf), 1);
|
||||
ASSERT_FALSE(buf_loaded.empty());
|
||||
|
||||
psnr = cvtest::PSNR(buf_loaded, image);
|
||||
EXPECT_GT(psnr, thresDbell);
|
||||
|
||||
EXPECT_EQ(0, remove(dst_name.c_str()));
|
||||
}
|
||||
}
|
||||
|
||||
//==================================================================================================
|
||||
|
||||
typedef string Ext;
|
||||
typedef testing::TestWithParam<Ext> Imgcodecs_Image;
|
||||
|
||||
const string exts[] = {
|
||||
#if defined(HAVE_PNG) || defined(HAVE_SPNG)
|
||||
"png",
|
||||
#endif
|
||||
#ifdef HAVE_TIFF
|
||||
"tiff",
|
||||
#endif
|
||||
#ifdef HAVE_JPEG
|
||||
"jpg",
|
||||
#endif
|
||||
#ifdef HAVE_JPEGXL
|
||||
"jxl",
|
||||
#endif
|
||||
#if (defined(HAVE_JASPER) && defined(OPENCV_IMGCODECS_ENABLE_JASPER_TESTS)) \
|
||||
|| defined(HAVE_OPENJPEG)
|
||||
"jp2",
|
||||
#endif
|
||||
#if 0 /*defined HAVE_OPENEXR && !defined __APPLE__*/
|
||||
"exr",
|
||||
#endif
|
||||
"bmp",
|
||||
#ifdef HAVE_IMGCODEC_PXM
|
||||
"ppm",
|
||||
#endif
|
||||
#ifdef HAVE_IMGCODEC_SUNRASTER
|
||||
"ras",
|
||||
#endif
|
||||
};
|
||||
|
||||
static
|
||||
void test_image_io(const Mat& image, const std::string& fname, const std::string& ext, int imreadFlag, double psnrThreshold)
|
||||
{
|
||||
vector<uchar> buf;
|
||||
ASSERT_NO_THROW(imencode("." + ext, image, buf));
|
||||
|
||||
ASSERT_NO_THROW(imwrite(fname, image));
|
||||
|
||||
FILE *f = fopen(fname.c_str(), "rb");
|
||||
fseek(f, 0, SEEK_END);
|
||||
long len = ftell(f);
|
||||
cout << "File size: " << len << " bytes" << endl;
|
||||
EXPECT_GT(len, 1024) << "File is small. Test or implementation is broken";
|
||||
fseek(f, 0, SEEK_SET);
|
||||
vector<uchar> file_buf((size_t)len);
|
||||
EXPECT_EQ(len, (long)fread(&file_buf[0], 1, (size_t)len, f));
|
||||
fclose(f); f = NULL;
|
||||
|
||||
EXPECT_EQ(buf, file_buf) << "imwrite() / imencode() calls must provide the same output (bit-exact)";
|
||||
|
||||
Mat buf_loaded = imdecode(Mat(buf), imreadFlag);
|
||||
EXPECT_FALSE(buf_loaded.empty());
|
||||
|
||||
if (imreadFlag & IMREAD_COLOR_RGB && imreadFlag != -1)
|
||||
{
|
||||
cvtColor(buf_loaded, buf_loaded, COLOR_RGB2BGR);
|
||||
}
|
||||
|
||||
Mat loaded = imread(fname, imreadFlag);
|
||||
EXPECT_FALSE(loaded.empty());
|
||||
|
||||
if (imreadFlag & IMREAD_COLOR_RGB && imreadFlag != -1)
|
||||
{
|
||||
cvtColor(loaded, loaded, COLOR_RGB2BGR);
|
||||
}
|
||||
|
||||
EXPECT_EQ(0, cv::norm(loaded, buf_loaded, NORM_INF)) << "imread() and imdecode() calls must provide the same result (bit-exact)";
|
||||
|
||||
double psnr = cvtest::PSNR(loaded, image);
|
||||
EXPECT_GT(psnr, psnrThreshold);
|
||||
|
||||
// not necessary due bitexact check above
|
||||
//double buf_psnr = cvtest::PSNR(buf_loaded, image);
|
||||
//EXPECT_GT(buf_psnr, psnrThreshold);
|
||||
|
||||
#if 0 // debug
|
||||
if (psnr <= psnrThreshold /*|| buf_psnr <= thresDbell*/)
|
||||
{
|
||||
cout << "File: " << fname << endl;
|
||||
imshow("origin", image);
|
||||
imshow("imread", loaded);
|
||||
imshow("imdecode", buf_loaded);
|
||||
waitKey();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
TEST_P(Imgcodecs_Image, read_write_BGR)
|
||||
{
|
||||
const string ext = this->GetParam();
|
||||
const string fname = cv::tempfile(ext.c_str());
|
||||
|
||||
double psnrThreshold = 100;
|
||||
if (ext == "jpg")
|
||||
psnrThreshold = 32;
|
||||
if (ext == "jxl")
|
||||
psnrThreshold = 30;
|
||||
#if defined(HAVE_JASPER)
|
||||
if (ext == "jp2")
|
||||
psnrThreshold = 95;
|
||||
#elif defined(HAVE_OPENJPEG)
|
||||
if (ext == "jp2")
|
||||
psnrThreshold = 35;
|
||||
#endif
|
||||
|
||||
Mat image = generateTestImageBGR();
|
||||
EXPECT_NO_THROW(test_image_io(image, fname, ext, IMREAD_COLOR, psnrThreshold));
|
||||
EXPECT_NO_THROW(test_image_io(image, fname, ext, IMREAD_COLOR_RGB, psnrThreshold));
|
||||
|
||||
EXPECT_EQ(0, remove(fname.c_str()));
|
||||
}
|
||||
|
||||
TEST_P(Imgcodecs_Image, read_write_GRAYSCALE)
|
||||
{
|
||||
const string ext = this->GetParam();
|
||||
|
||||
if (false
|
||||
|| ext == "ppm" // grayscale is not implemented
|
||||
|| ext == "ras" // broken (black result)
|
||||
)
|
||||
throw SkipTestException("GRAYSCALE mode is not supported");
|
||||
|
||||
const string fname = cv::tempfile(ext.c_str());
|
||||
|
||||
double psnrThreshold = 100;
|
||||
if (ext == "jpg")
|
||||
psnrThreshold = 40;
|
||||
if (ext == "jxl")
|
||||
psnrThreshold = 40;
|
||||
#if defined(HAVE_JASPER)
|
||||
if (ext == "jp2")
|
||||
psnrThreshold = 70;
|
||||
#elif defined(HAVE_OPENJPEG)
|
||||
if (ext == "jp2")
|
||||
psnrThreshold = 35;
|
||||
#endif
|
||||
|
||||
Mat image = generateTestImageGrayscale();
|
||||
EXPECT_NO_THROW(test_image_io(image, fname, ext, IMREAD_GRAYSCALE, psnrThreshold));
|
||||
|
||||
EXPECT_EQ(0, remove(fname.c_str()));
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(imgcodecs, Imgcodecs_Image, testing::ValuesIn(exts));
|
||||
|
||||
TEST(Imgcodecs_Image, regression_9376)
|
||||
{
|
||||
String path = findDataFile("readwrite/regression_9376.bmp");
|
||||
Mat m = imread(path);
|
||||
ASSERT_FALSE(m.empty());
|
||||
EXPECT_EQ(32, m.cols);
|
||||
EXPECT_EQ(32, m.rows);
|
||||
}
|
||||
|
||||
TEST(Imgcodecs_Image, imread_overload)
|
||||
{
|
||||
const string root = cvtest::TS::ptr()->get_data_path();
|
||||
const string imgName = findDataFile("../highgui/readwrite/ordinary.bmp");
|
||||
|
||||
Mat ref = imread(imgName);
|
||||
ASSERT_FALSE(ref.empty());
|
||||
{
|
||||
Mat img(ref.size(), ref.type(), Scalar::all(0)); // existing image
|
||||
void * ptr = img.data;
|
||||
imread(imgName, img);
|
||||
ASSERT_FALSE(img.empty());
|
||||
EXPECT_EQ(cv::norm(ref, img, NORM_INF), 0);
|
||||
EXPECT_EQ(img.data, ptr); // no reallocation
|
||||
}
|
||||
{
|
||||
Mat img; // empty image
|
||||
imread(imgName, img);
|
||||
ASSERT_FALSE(img.empty());
|
||||
EXPECT_EQ(cv::norm(ref, img, NORM_INF), 0);
|
||||
}
|
||||
{
|
||||
UMat img; // empty UMat
|
||||
imread(imgName, img);
|
||||
ASSERT_FALSE(img.empty());
|
||||
EXPECT_EQ(cv::norm(ref, img, NORM_INF), 0);
|
||||
}
|
||||
}
|
||||
|
||||
//==================================================================================================
|
||||
|
||||
TEST(Imgcodecs_Image, write_umat)
|
||||
{
|
||||
const string src_name = TS::ptr()->get_data_path() + "../python/images/baboon.bmp";
|
||||
const string dst_name = cv::tempfile(".bmp");
|
||||
|
||||
Mat image1 = imread(src_name);
|
||||
ASSERT_FALSE(image1.empty());
|
||||
|
||||
UMat image1_umat = image1.getUMat(ACCESS_RW);
|
||||
|
||||
imwrite(dst_name, image1_umat);
|
||||
|
||||
Mat image2 = imread(dst_name);
|
||||
ASSERT_FALSE(image2.empty());
|
||||
|
||||
EXPECT_PRED_FORMAT2(cvtest::MatComparator(0, 0), image1, image2);
|
||||
EXPECT_EQ(0, remove(dst_name.c_str()));
|
||||
}
|
||||
|
||||
#ifdef HAVE_TIFF
|
||||
TEST(Imgcodecs_Image, multipage_collection_size)
|
||||
{
|
||||
const string root = cvtest::TS::ptr()->get_data_path();
|
||||
const string filename = root + "readwrite/multipage.tif";
|
||||
|
||||
ImageCollection collection(filename, IMREAD_ANYCOLOR);
|
||||
EXPECT_EQ((std::size_t)6, collection.size());
|
||||
}
|
||||
|
||||
TEST(Imgcodecs_Image, multipage_collection_read_pages_iterator)
|
||||
{
|
||||
const string root = cvtest::TS::ptr()->get_data_path();
|
||||
const string filename = root + "readwrite/multipage.tif";
|
||||
const string page_files[] = {
|
||||
root + "readwrite/multipage_p1.tif",
|
||||
root + "readwrite/multipage_p2.tif",
|
||||
root + "readwrite/multipage_p3.tif",
|
||||
root + "readwrite/multipage_p4.tif",
|
||||
root + "readwrite/multipage_p5.tif",
|
||||
root + "readwrite/multipage_p6.tif"
|
||||
};
|
||||
|
||||
ImageCollection collection(filename, IMREAD_ANYCOLOR);
|
||||
|
||||
auto collectionBegin = collection.begin();
|
||||
for(size_t i = 0; i < collection.size(); ++i, ++collectionBegin)
|
||||
{
|
||||
double diff = cv::norm(collectionBegin.operator*(), imread(page_files[i]), NORM_INF);
|
||||
EXPECT_EQ(0., diff);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(Imgcodecs_Image, multipage_collection_two_iterator)
|
||||
{
|
||||
const string root = cvtest::TS::ptr()->get_data_path();
|
||||
const string filename = root + "readwrite/multipage.tif";
|
||||
const string page_files[] = {
|
||||
root + "readwrite/multipage_p1.tif",
|
||||
root + "readwrite/multipage_p2.tif",
|
||||
root + "readwrite/multipage_p3.tif",
|
||||
root + "readwrite/multipage_p4.tif",
|
||||
root + "readwrite/multipage_p5.tif",
|
||||
root + "readwrite/multipage_p6.tif"
|
||||
};
|
||||
|
||||
ImageCollection collection(filename, IMREAD_ANYCOLOR);
|
||||
auto firstIter = collection.begin();
|
||||
auto secondIter = collection.begin();
|
||||
|
||||
// Decode all odd pages then decode even pages -> 1, 0, 3, 2 ...
|
||||
firstIter++;
|
||||
for(size_t i = 1; i < collection.size(); i += 2, ++firstIter, ++firstIter, ++secondIter, ++secondIter) {
|
||||
Mat mat = *firstIter;
|
||||
double diff = cv::norm(mat, imread(page_files[i]), NORM_INF);
|
||||
EXPECT_EQ(0., diff);
|
||||
Mat evenMat = *secondIter;
|
||||
diff = cv::norm(evenMat, imread(page_files[i-1]), NORM_INF);
|
||||
EXPECT_EQ(0., diff);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(Imgcodecs_Image, multipage_collection_operator_plusplus)
|
||||
{
|
||||
const string root = cvtest::TS::ptr()->get_data_path();
|
||||
const string filename = root + "readwrite/multipage.tif";
|
||||
|
||||
// operator++ test
|
||||
ImageCollection collection(filename, IMREAD_ANYCOLOR);
|
||||
auto firstIter = collection.begin();
|
||||
auto secondIter = firstIter++;
|
||||
|
||||
// firstIter points to second page, secondIter points to first page
|
||||
double diff = cv::norm(*firstIter, *secondIter, NORM_INF);
|
||||
EXPECT_NE(diff, 0.);
|
||||
}
|
||||
|
||||
TEST(Imgcodecs_Image, multipage_collection_backward_decoding)
|
||||
{
|
||||
const string root = cvtest::TS::ptr()->get_data_path();
|
||||
const string filename = root + "readwrite/multipage.tif";
|
||||
const string page_files[] = {
|
||||
root + "readwrite/multipage_p1.tif",
|
||||
root + "readwrite/multipage_p2.tif",
|
||||
root + "readwrite/multipage_p3.tif",
|
||||
root + "readwrite/multipage_p4.tif",
|
||||
root + "readwrite/multipage_p5.tif",
|
||||
root + "readwrite/multipage_p6.tif"
|
||||
};
|
||||
|
||||
ImageCollection collection(filename, IMREAD_ANYCOLOR);
|
||||
EXPECT_EQ((size_t)6, collection.size());
|
||||
|
||||
// backward decoding -> 5,4,3,2,1,0
|
||||
for(int i = (int)collection.size() - 1; i >= 0; --i)
|
||||
{
|
||||
cv::Mat ithPage = imread(page_files[i]);
|
||||
EXPECT_FALSE(ithPage.empty());
|
||||
double diff = cv::norm(collection[i], ithPage, NORM_INF);
|
||||
EXPECT_EQ(diff, 0.);
|
||||
}
|
||||
|
||||
for(int i = 0; i < (int)collection.size(); ++i)
|
||||
{
|
||||
collection.releaseCache(i);
|
||||
}
|
||||
|
||||
double diff = cv::norm(collection[2], imread(page_files[2]), NORM_INF);
|
||||
EXPECT_EQ(diff, 0.);
|
||||
}
|
||||
|
||||
TEST(ImgCodecs, multipage_collection_decoding_range_based_for_loop_test)
|
||||
{
|
||||
const string root = cvtest::TS::ptr()->get_data_path();
|
||||
const string filename = root + "readwrite/multipage.tif";
|
||||
const string page_files[] = {
|
||||
root + "readwrite/multipage_p1.tif",
|
||||
root + "readwrite/multipage_p2.tif",
|
||||
root + "readwrite/multipage_p3.tif",
|
||||
root + "readwrite/multipage_p4.tif",
|
||||
root + "readwrite/multipage_p5.tif",
|
||||
root + "readwrite/multipage_p6.tif"
|
||||
};
|
||||
|
||||
ImageCollection collection(filename, IMREAD_ANYCOLOR);
|
||||
|
||||
size_t index = 0;
|
||||
for(auto &i: collection)
|
||||
{
|
||||
cv::Mat ithPage = imread(page_files[index]);
|
||||
EXPECT_FALSE(ithPage.empty());
|
||||
double diff = cv::norm(i, ithPage, NORM_INF);
|
||||
EXPECT_EQ(0., diff);
|
||||
++index;
|
||||
}
|
||||
EXPECT_EQ(index, collection.size());
|
||||
|
||||
index = 0;
|
||||
for(auto &&i: collection)
|
||||
{
|
||||
cv::Mat ithPage = imread(page_files[index]);
|
||||
EXPECT_FALSE(ithPage.empty());
|
||||
double diff = cv::norm(i, ithPage, NORM_INF);
|
||||
EXPECT_EQ(0., diff);
|
||||
++index;
|
||||
}
|
||||
EXPECT_EQ(index, collection.size());
|
||||
}
|
||||
|
||||
TEST(ImgCodecs, multipage_collection_two_iterator_operatorpp)
|
||||
{
|
||||
const string root = cvtest::TS::ptr()->get_data_path();
|
||||
const string filename = root + "readwrite/multipage.tif";
|
||||
|
||||
ImageCollection imcol(filename, IMREAD_ANYCOLOR);
|
||||
|
||||
auto it0 = imcol.begin(), it1 = it0, it2 = it0;
|
||||
vector<Mat> img(6);
|
||||
for (int i = 0; i < 6; i++) {
|
||||
img[i] = *it0;
|
||||
it0->release();
|
||||
++it0;
|
||||
}
|
||||
|
||||
for (int i = 0; i < 3; i++) {
|
||||
++it2;
|
||||
}
|
||||
|
||||
for (int i = 0; i < 3; i++) {
|
||||
auto img2 = *it2;
|
||||
auto img1 = *it1;
|
||||
++it2;
|
||||
++it1;
|
||||
EXPECT_TRUE(cv::norm(img2, img[i+3], NORM_INF) == 0);
|
||||
EXPECT_TRUE(cv::norm(img1, img[i], NORM_INF) == 0);
|
||||
}
|
||||
}
|
||||
|
||||
// See https://github.com/opencv/opencv/issues/26207
|
||||
TEST(Imgcodecs, imencodemulti_regression_26207)
|
||||
{
|
||||
vector<Mat> imgs;
|
||||
const cv::Mat img(100, 100, CV_8UC1, cv::Scalar::all(0));
|
||||
imgs.push_back(img);
|
||||
std::vector<uchar> buf;
|
||||
bool ret = false;
|
||||
|
||||
// Encode single image
|
||||
EXPECT_NO_THROW(ret = imencode(".tiff", img, buf));
|
||||
EXPECT_TRUE(ret);
|
||||
EXPECT_NO_THROW(ret = imencode(".tiff", imgs, buf));
|
||||
EXPECT_TRUE(ret);
|
||||
EXPECT_NO_THROW(ret = imencodemulti(".tiff", imgs, buf));
|
||||
EXPECT_TRUE(ret);
|
||||
|
||||
// Encode multiple images
|
||||
imgs.push_back(img.clone());
|
||||
EXPECT_NO_THROW(ret = imencode(".tiff", imgs, buf));
|
||||
EXPECT_TRUE(ret);
|
||||
EXPECT_NO_THROW(ret = imencodemulti(".tiff", imgs, buf));
|
||||
EXPECT_TRUE(ret);
|
||||
|
||||
// Count stored images from buffer.
|
||||
// imcount() doesn't support buffer, so encoded buffer outputs to file temporary.
|
||||
const size_t len = buf.size();
|
||||
const string filename = cv::tempfile(".tiff");
|
||||
FILE *f = fopen(filename.c_str(), "wb");
|
||||
EXPECT_NE(f, nullptr);
|
||||
EXPECT_EQ(len, fwrite(&buf[0], 1, len, f));
|
||||
fclose(f);
|
||||
|
||||
EXPECT_EQ(2, (int)imcount(filename));
|
||||
EXPECT_EQ(0, remove(filename.c_str()));
|
||||
}
|
||||
#endif
|
||||
|
||||
// See https://github.com/opencv/opencv/pull/26211
|
||||
// ( related with https://github.com/opencv/opencv/issues/26207 )
|
||||
TEST(Imgcodecs, imencode_regression_26207_extra)
|
||||
{
|
||||
// CV_32F is not supported depth for BMP Encoder.
|
||||
// Encoded buffer contains CV_8U image which is fallbacked.
|
||||
const cv::Mat src(100, 100, CV_32FC1, cv::Scalar::all(0));
|
||||
std::vector<uchar> buf;
|
||||
bool ret = false;
|
||||
EXPECT_NO_THROW(ret = imencode(".bmp", src, buf));
|
||||
EXPECT_TRUE(ret);
|
||||
|
||||
cv::Mat dst;
|
||||
EXPECT_NO_THROW(dst = imdecode(buf, IMREAD_GRAYSCALE));
|
||||
EXPECT_FALSE(dst.empty());
|
||||
EXPECT_EQ(CV_8UC1, dst.type());
|
||||
}
|
||||
TEST(Imgcodecs, imwrite_regression_26207_extra)
|
||||
{
|
||||
// CV_32F is not supported depth for BMP Encoder.
|
||||
// Encoded buffer contains CV_8U image which is fallbacked.
|
||||
const cv::Mat src(100, 100, CV_32FC1, cv::Scalar::all(0));
|
||||
const string filename = cv::tempfile(".bmp");
|
||||
bool ret = false;
|
||||
EXPECT_NO_THROW(ret = imwrite(filename, src));
|
||||
EXPECT_TRUE(ret);
|
||||
|
||||
cv::Mat dst;
|
||||
EXPECT_NO_THROW(dst = imread(filename, IMREAD_GRAYSCALE));
|
||||
EXPECT_FALSE(dst.empty());
|
||||
EXPECT_EQ(CV_8UC1, dst.type());
|
||||
EXPECT_EQ(0, remove(filename.c_str()));
|
||||
}
|
||||
|
||||
TEST(Imgcodecs_Params, imwrite_regression_22752)
|
||||
{
|
||||
const Mat img(16, 16, CV_8UC3, cv::Scalar::all(0));
|
||||
vector<int> params;
|
||||
params.push_back(IMWRITE_JPEG_QUALITY);
|
||||
// params.push_back(100)); // Forget it.
|
||||
EXPECT_ANY_THROW(cv::imwrite("test.jpg", img, params)); // parameters size or missing JPEG codec
|
||||
}
|
||||
|
||||
TEST(Imgcodecs_Params, imencode_regression_22752)
|
||||
{
|
||||
const Mat img(16, 16, CV_8UC3, cv::Scalar::all(0));
|
||||
vector<int> params;
|
||||
params.push_back(IMWRITE_JPEG_QUALITY);
|
||||
// params.push_back(100)); // Forget it.
|
||||
vector<uchar> buf;
|
||||
EXPECT_ANY_THROW(cv::imencode("test.jpg", img, buf, params)); // parameters size or missing JPEG codec
|
||||
}
|
||||
|
||||
TEST(Imgcodecs, decode_over2GB)
|
||||
{
|
||||
applyTestTag(CV_TEST_TAG_MEMORY_6GB);
|
||||
// empty buffer more than 2GB size
|
||||
std::vector<uint8_t> buf(size_t(INT_MAX) + 4096);
|
||||
cv::Mat dst;
|
||||
EXPECT_THROW(dst = cv::imdecode(buf, cv::IMREAD_COLOR), cv::Exception);
|
||||
}
|
||||
|
||||
}} // namespace
|
||||
@@ -0,0 +1,138 @@
|
||||
// This file is part of OpenCV project.
|
||||
// It is subject to the license terms in the LICENSE file found in the top-level directory
|
||||
// of this distribution and at http://opencv.org/license.html
|
||||
|
||||
// Regression tests for the Sun Raster decoder (grfmt_sunras.cpp).
|
||||
// These tests guard against:
|
||||
// - UBSan "load of invalid enum value" in SunRasterDecoder::readHeader()
|
||||
// (see https://github.com/opencv/opencv/issues/29150)
|
||||
// - Out-of-range SunRasType / SunRasMapType values causing UB via unchecked C-cast
|
||||
|
||||
#include "test_precomp.hpp"
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace opencv_test { namespace {
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Helper: build a minimal Sun Raster byte buffer with caller-supplied fields.
|
||||
// Sun Raster header layout (all fields big-endian, 32-bit):
|
||||
// [0] magic = 0x59a66a95
|
||||
// [4] width
|
||||
// [8] height
|
||||
// [12] depth (bits-per-pixel)
|
||||
// [16] length (image data length, may be 0 for RAS_OLD)
|
||||
// [20] ras_type (SunRasType)
|
||||
// [24] maptype (SunRasMapType)
|
||||
// [28] maplength
|
||||
// ---------------------------------------------------------------------------
|
||||
static std::vector<uint8_t> makeSunRasHeader(
|
||||
uint32_t width, uint32_t height, uint32_t depth,
|
||||
uint32_t length, uint32_t ras_type, uint32_t maptype, uint32_t maplength)
|
||||
{
|
||||
std::vector<uint8_t> buf(32);
|
||||
auto put32 = [&](size_t off, uint32_t v) {
|
||||
buf[off+0] = (v >> 24) & 0xff;
|
||||
buf[off+1] = (v >> 16) & 0xff;
|
||||
buf[off+2] = (v >> 8) & 0xff;
|
||||
buf[off+3] = (v ) & 0xff;
|
||||
};
|
||||
put32( 0, 0x59a66a95u); // magic
|
||||
put32( 4, width);
|
||||
put32( 8, height);
|
||||
put32(12, depth);
|
||||
put32(16, length);
|
||||
put32(20, ras_type);
|
||||
put32(24, maptype);
|
||||
put32(28, maplength);
|
||||
return buf;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Crash / UBSan regression — issue #29150
|
||||
// Feeding an invalid maptype value (34077 / 0x851d) must NOT trigger UB;
|
||||
// imdecode must return an empty Mat gracefully.
|
||||
// ---------------------------------------------------------------------------
|
||||
TEST(Imgcodecs_SunRaster, invalid_maptype_returns_empty_29150)
|
||||
{
|
||||
// Crafted header from the original bug report:
|
||||
// magic=0x59a66a95, width=0x10101, height=0x10000, depth=1, length=0x1000000
|
||||
// ras_type=0 (RAS_OLD, valid), maptype=0x851d (34077, INVALID)
|
||||
const std::vector<uint8_t> image_data = {
|
||||
0x59,0xa6,0x6a,0x95, 0x01,0x01,0x00,0x00,
|
||||
0x00,0x01,0x00,0x00, 0x00,0x00,0x00,0x01,
|
||||
0x01,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x85,0x1d, 0xae,0x5b,0x8d,0xd5,
|
||||
0x9c,0x25,0x22,0x41, 0x51,0x92,0x13,0x14,0x33
|
||||
};
|
||||
|
||||
cv::Mat result;
|
||||
ASSERT_NO_THROW(result = cv::imdecode(image_data, cv::IMREAD_REDUCED_GRAYSCALE_2));
|
||||
EXPECT_TRUE(result.empty()) << "imdecode must return empty Mat for invalid maptype";
|
||||
}
|
||||
|
||||
// Invalid ras_type (value well outside [0,3]) must be rejected cleanly.
|
||||
TEST(Imgcodecs_SunRaster, invalid_rastype_returns_empty)
|
||||
{
|
||||
auto buf = makeSunRasHeader(/*w*/8, /*h*/8, /*depth*/8,
|
||||
/*len*/0, /*ras_type*/0xFFFF, /*maptype*/0, /*mapllen*/0);
|
||||
cv::Mat result;
|
||||
ASSERT_NO_THROW(result = cv::imdecode(buf, cv::IMREAD_GRAYSCALE));
|
||||
EXPECT_TRUE(result.empty()) << "imdecode must return empty Mat for invalid ras_type";
|
||||
}
|
||||
|
||||
// maptype = 2 is outside [0,1] (only RMT_NONE=0, RMT_EQUAL_RGB=1 are defined).
|
||||
TEST(Imgcodecs_SunRaster, maptype_2_returns_empty)
|
||||
{
|
||||
auto buf = makeSunRasHeader(8, 8, 8, 0, /*ras_type*/0, /*maptype*/2, 0);
|
||||
cv::Mat result;
|
||||
ASSERT_NO_THROW(result = cv::imdecode(buf, cv::IMREAD_GRAYSCALE));
|
||||
EXPECT_TRUE(result.empty()) << "imdecode must return empty Mat for maptype=2";
|
||||
}
|
||||
|
||||
// maptype = UINT32_MAX is also invalid.
|
||||
TEST(Imgcodecs_SunRaster, maptype_max_returns_empty)
|
||||
{
|
||||
auto buf = makeSunRasHeader(8, 8, 8, 0, 0, 0xFFFFFFFFu, 0);
|
||||
cv::Mat result;
|
||||
ASSERT_NO_THROW(result = cv::imdecode(buf, cv::IMREAD_GRAYSCALE));
|
||||
EXPECT_TRUE(result.empty()) << "imdecode must return empty Mat for maptype=UINT_MAX";
|
||||
}
|
||||
|
||||
// A truncated buffer (shorter than the 32-byte header) must not crash.
|
||||
TEST(Imgcodecs_SunRaster, truncated_header_returns_empty)
|
||||
{
|
||||
// Only 16 bytes — header read will run out of data.
|
||||
const std::vector<uint8_t> buf = {
|
||||
0x59,0xa6,0x6a,0x95, 0x00,0x00,0x00,0x08,
|
||||
0x00,0x00,0x00,0x08, 0x00,0x00,0x00,0x08
|
||||
};
|
||||
cv::Mat result;
|
||||
ASSERT_NO_THROW(result = cv::imdecode(buf, cv::IMREAD_GRAYSCALE));
|
||||
EXPECT_TRUE(result.empty()) << "imdecode must return empty Mat for truncated header";
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Sanity: a well-formed 8-bpp grayscale Sun Raster (RMT_NONE) still decodes.
|
||||
// We build the full header + pixel data manually so the test has no file I/O.
|
||||
// ---------------------------------------------------------------------------
|
||||
TEST(Imgcodecs_SunRaster, valid_8bpp_grayscale_decodes)
|
||||
{
|
||||
const int W = 4, H = 4;
|
||||
// RAS_OLD(0), maptype=RMT_NONE(0), maplength=0
|
||||
auto buf = makeSunRasHeader(W, H, 8, W*H, 0, 0, 0);
|
||||
|
||||
// Sun Raster rows are padded to 16-bit boundary.
|
||||
// W=4: row_bytes = 4 (already even), no padding needed.
|
||||
for (int i = 0; i < W * H; ++i)
|
||||
buf.push_back(static_cast<uint8_t>(i * 16)); // arbitrary gray values
|
||||
|
||||
cv::Mat result;
|
||||
ASSERT_NO_THROW(result = cv::imdecode(buf, cv::IMREAD_GRAYSCALE));
|
||||
EXPECT_FALSE(result.empty()) << "imdecode must succeed for a valid 8-bpp Sun Raster";
|
||||
EXPECT_EQ(result.cols, W);
|
||||
EXPECT_EQ(result.rows, H);
|
||||
EXPECT_EQ(result.type(), CV_8UC1);
|
||||
}
|
||||
|
||||
}} // namespace opencv_test
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,267 @@
|
||||
// This file is part of OpenCV project.
|
||||
// It is subject to the license terms in the LICENSE file found in the top-level directory
|
||||
// of this distribution and at http://opencv.org/license.html.
|
||||
|
||||
#include "test_precomp.hpp"
|
||||
|
||||
namespace opencv_test { namespace {
|
||||
|
||||
#ifdef HAVE_WEBP
|
||||
|
||||
static void readFileBytes(const std::string& fname, std::vector<unsigned char>& buf)
|
||||
{
|
||||
FILE * wfile = fopen(fname.c_str(), "rb");
|
||||
if (wfile != NULL)
|
||||
{
|
||||
fseek(wfile, 0, SEEK_END);
|
||||
size_t wfile_size = ftell(wfile);
|
||||
fseek(wfile, 0, SEEK_SET);
|
||||
|
||||
buf.resize(wfile_size);
|
||||
|
||||
size_t data_size = fread(&buf[0], 1, wfile_size, wfile);
|
||||
|
||||
if(wfile)
|
||||
{
|
||||
fclose(wfile);
|
||||
}
|
||||
|
||||
EXPECT_EQ(data_size, wfile_size);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(Imgcodecs_WebP, encode_decode_lossless_webp)
|
||||
{
|
||||
const string root = cvtest::TS::ptr()->get_data_path();
|
||||
string filename = root + "../cv/shared/lena.png";
|
||||
cv::Mat img = cv::imread(filename);
|
||||
ASSERT_FALSE(img.empty());
|
||||
|
||||
string output = cv::tempfile(".webp");
|
||||
EXPECT_NO_THROW(cv::imwrite(output, img)); // lossless
|
||||
|
||||
cv::Mat img_webp = cv::imread(output);
|
||||
|
||||
std::vector<unsigned char> buf;
|
||||
readFileBytes(output, buf);
|
||||
EXPECT_EQ(0, remove(output.c_str()));
|
||||
|
||||
cv::Mat decode = cv::imdecode(buf, IMREAD_COLOR);
|
||||
ASSERT_FALSE(decode.empty());
|
||||
EXPECT_TRUE(cvtest::norm(decode, img_webp, NORM_INF) == 0);
|
||||
|
||||
cv::Mat decode_rgb = cv::imdecode(buf, IMREAD_COLOR_RGB);
|
||||
ASSERT_FALSE(decode_rgb.empty());
|
||||
|
||||
cvtColor(decode_rgb, decode_rgb, COLOR_RGB2BGR);
|
||||
EXPECT_TRUE(cvtest::norm(decode_rgb, img_webp, NORM_INF) == 0);
|
||||
|
||||
ASSERT_FALSE(img_webp.empty());
|
||||
|
||||
EXPECT_TRUE(cvtest::norm(img, img_webp, NORM_INF) == 0);
|
||||
}
|
||||
|
||||
TEST(Imgcodecs_WebP, encode_decode_lossy_webp)
|
||||
{
|
||||
const string root = cvtest::TS::ptr()->get_data_path();
|
||||
std::string input = root + "../cv/shared/lena.png";
|
||||
cv::Mat img = cv::imread(input);
|
||||
ASSERT_FALSE(img.empty());
|
||||
|
||||
for(int q = 100; q>=0; q-=20)
|
||||
{
|
||||
std::vector<int> params;
|
||||
params.push_back(IMWRITE_WEBP_QUALITY);
|
||||
params.push_back(MAX(q,1));
|
||||
string output = cv::tempfile(".webp");
|
||||
|
||||
EXPECT_NO_THROW(cv::imwrite(output, img, params));
|
||||
cv::Mat img_webp = cv::imread(output);
|
||||
EXPECT_EQ(0, remove(output.c_str()));
|
||||
EXPECT_FALSE(img_webp.empty());
|
||||
EXPECT_EQ(3, img_webp.channels());
|
||||
EXPECT_EQ(512, img_webp.cols);
|
||||
EXPECT_EQ(512, img_webp.rows);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(Imgcodecs_WebP, encode_decode_with_alpha_webp)
|
||||
{
|
||||
const string root = cvtest::TS::ptr()->get_data_path();
|
||||
std::string input = root + "../cv/shared/lena.png";
|
||||
cv::Mat img = cv::imread(input);
|
||||
ASSERT_FALSE(img.empty());
|
||||
|
||||
std::vector<cv::Mat> imgs;
|
||||
cv::split(img, imgs);
|
||||
imgs.push_back(cv::Mat(imgs[0]));
|
||||
imgs[imgs.size() - 1] = cv::Scalar::all(128);
|
||||
cv::merge(imgs, img);
|
||||
|
||||
string output = cv::tempfile(".webp");
|
||||
|
||||
EXPECT_NO_THROW(cv::imwrite(output, img));
|
||||
cv::Mat img_webp = cv::imread(output, IMREAD_UNCHANGED);
|
||||
cv::Mat img_webp_bgr = cv::imread(output); // IMREAD_COLOR by default
|
||||
EXPECT_EQ(0, remove(output.c_str()));
|
||||
EXPECT_FALSE(img_webp.empty());
|
||||
EXPECT_EQ(4, img_webp.channels());
|
||||
EXPECT_EQ(512, img_webp.cols);
|
||||
EXPECT_EQ(512, img_webp.rows);
|
||||
EXPECT_FALSE(img_webp_bgr.empty());
|
||||
EXPECT_EQ(3, img_webp_bgr.channels());
|
||||
EXPECT_EQ(512, img_webp_bgr.cols);
|
||||
EXPECT_EQ(512, img_webp_bgr.rows);
|
||||
}
|
||||
|
||||
|
||||
// See https://github.com/opencv/opencv/issues/28503
|
||||
TEST(Imgcodecs_WebP, encode_decode_LOSSLESS_MODE)
|
||||
{
|
||||
cv::Mat img(cv::Size(64,4), CV_8UC4, cv::Scalar(124,64,67,0) );
|
||||
for(int ix = 0; ix < img.size().width; ix++)
|
||||
{
|
||||
img.at<Vec4b>(0, ix)[3] = 0; // Transpacency pixel
|
||||
img.at<Vec4b>(1, ix)[3] = 1;
|
||||
img.at<Vec4b>(2, ix)[3] = 254;
|
||||
img.at<Vec4b>(3, ix)[3] = 255;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> work;
|
||||
EXPECT_NO_THROW(cv::imencode(".webp", img, work, {IMWRITE_WEBP_LOSSLESS_MODE, IMWRITE_WEBP_LOSSLESS_ON}));
|
||||
cv::Mat img_ON = cv::imdecode(work, IMREAD_UNCHANGED);
|
||||
EXPECT_NO_THROW(cv::imencode(".webp", img, work, {IMWRITE_WEBP_LOSSLESS_MODE, IMWRITE_WEBP_LOSSLESS_PRESERVE_COLOR}));
|
||||
cv::Mat img_PRESERVE_COLOR = cv::imdecode(work, IMREAD_UNCHANGED);
|
||||
|
||||
for(int ix = 0; ix < img.size().width; ix++)
|
||||
{
|
||||
EXPECT_EQ(img_ON.at<Vec4b>(0, ix), Vec4b(0, 0, 0, 0)); // LOSSLESS_ON -> COLOR will be optimized/dropped.
|
||||
EXPECT_EQ(img_PRESERVE_COLOR.at<Vec4b>(0, ix), Vec4b(124,64,67,0)); // PRESERVE_COLOR
|
||||
|
||||
EXPECT_EQ(img_ON.at<Vec4b>(1, ix), img_PRESERVE_COLOR.at<Vec4b>(1, ix) );
|
||||
EXPECT_EQ(img_ON.at<Vec4b>(2, ix), img_PRESERVE_COLOR.at<Vec4b>(2, ix) );
|
||||
EXPECT_EQ(img_ON.at<Vec4b>(3, ix), img_PRESERVE_COLOR.at<Vec4b>(3, ix) );
|
||||
}
|
||||
}
|
||||
|
||||
// Expected result categories for WebP encoding tests.
|
||||
enum ImencodeLosslessResult {
|
||||
LOSSY, // Expect lossy compression (pixel differences allowed)
|
||||
LOSSLESS, // Expect standard lossless compression (pixel values match)
|
||||
EXACT // Expect exact lossless (preserving RGB values of transparent pixels)
|
||||
};
|
||||
|
||||
typedef std::tuple<int, int, ImencodeLosslessResult> WebPModePriorityParams;
|
||||
class Imgcodecs_WebP_Mode_Priority : public testing::TestWithParam<WebPModePriorityParams> {};
|
||||
|
||||
TEST_P(Imgcodecs_WebP_Mode_Priority, encode_webp_mode_priority)
|
||||
{
|
||||
const int mode = std::get<0>(GetParam());
|
||||
const int quality = std::get<1>(GetParam());
|
||||
const ImencodeLosslessResult expected = std::get<2>(GetParam());
|
||||
|
||||
// Generate a 100x100 RGBA test image.
|
||||
// Set a transparent pixel with specific color (Blue) to verify EXACT mode.
|
||||
Mat src(100, 100, CV_8UC4, Scalar(255, 255, 255, 255));
|
||||
src.at<Vec4b>(0, 0) = Vec4b(255, 0, 0, 0); // Transparent Blue (B:255, G:0, R:0, A:0)
|
||||
|
||||
// Build the imwrite parameter vector dynamically.
|
||||
std::vector<int> params;
|
||||
if (mode != -1) {
|
||||
params.push_back(IMWRITE_WEBP_LOSSLESS_MODE);
|
||||
params.push_back(mode);
|
||||
}
|
||||
if (quality != -1) {
|
||||
params.push_back(IMWRITE_WEBP_QUALITY);
|
||||
params.push_back(quality);
|
||||
}
|
||||
|
||||
// Encode to memory and decode back.
|
||||
std::vector<uchar> buf;
|
||||
ASSERT_TRUE(imencode(".webp", src, buf, params));
|
||||
Mat dst = imdecode(buf, IMREAD_UNCHANGED);
|
||||
ASSERT_FALSE(dst.empty());
|
||||
|
||||
// Validation logic
|
||||
if (expected == LOSSY) {
|
||||
// We expect some differences in lossy mode
|
||||
double diff = cv::norm(src, dst, NORM_INF);
|
||||
EXPECT_GT(diff, 0) << "Should be lossy (Quality: " << quality << ")";
|
||||
}
|
||||
else if (expected == LOSSLESS) {
|
||||
// Standard lossless: we allow the library to modify RGB values
|
||||
// of fully transparent pixels (A=0) to improve compression ratio.
|
||||
// Thus, we compare only visible pixels or check with a slightly relaxed condition.
|
||||
|
||||
// Option A: If you want to allow RGB changes on A=0:
|
||||
// We can't use cv::norm directly if A=0 pixels are modified.
|
||||
// Let's check if they are identical except for the transparent pixel.
|
||||
Mat diff;
|
||||
absdiff(src, dst, diff);
|
||||
Scalar total_diff = sum(diff);
|
||||
// If only the (0,0) pixel changed from (255,0,0,0) to (0,0,0,0),
|
||||
// total_diff will be 255.
|
||||
EXPECT_LE(total_diff[0] + total_diff[1] + total_diff[2], 255)
|
||||
<< "Standard lossless should not have significant pixel differences";
|
||||
EXPECT_EQ(src.at<Vec4b>(0,0)[3], dst.at<Vec4b>(0,0)[3]) << "Alpha must be preserved";
|
||||
}
|
||||
else if (expected == EXACT) {
|
||||
// Exact lossless: Every single bit must match, including transparent pixels.
|
||||
double diff = cv::norm(src, dst, NORM_INF);
|
||||
EXPECT_EQ(0, diff) << "Exact mode must preserve all pixel values perfectly";
|
||||
EXPECT_EQ(src.at<Vec4b>(0, 0), dst.at<Vec4b>(0, 0))
|
||||
<< "RGB values of transparent pixels must be preserved in EXACT mode";
|
||||
}
|
||||
else {
|
||||
FAIL() << "Unknown expectation type";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to generate human-readable test names in gtest output.
|
||||
*/
|
||||
static std::string getModeStr(int m) {
|
||||
if (m == -1) return "OMIT";
|
||||
if (m == IMWRITE_WEBP_LOSSLESS_OFF) return "OFF";
|
||||
if (m == IMWRITE_WEBP_LOSSLESS_ON) return "ON";
|
||||
if (m == IMWRITE_WEBP_LOSSLESS_PRESERVE_COLOR) return "PRESERVE";
|
||||
return "UNKNOWN";
|
||||
}
|
||||
|
||||
static std::string getExpectStr(ImencodeLosslessResult r) {
|
||||
return (r == LOSSY) ? "LOSSY" : (r == EXACT) ? "EXACT" : "LOSSLESS";
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(Imgcodecs, Imgcodecs_WebP_Mode_Priority,
|
||||
testing::Values(
|
||||
// Default (OMIT mode) cases
|
||||
WebPModePriorityParams(-1, -1, LOSSLESS),
|
||||
WebPModePriorityParams(-1, 80, LOSSY),
|
||||
WebPModePriorityParams(-1, 101, LOSSLESS),
|
||||
|
||||
// LOSSLESS_OFF (Explicitly off)
|
||||
WebPModePriorityParams(IMWRITE_WEBP_LOSSLESS_OFF, -1, LOSSLESS),
|
||||
WebPModePriorityParams(IMWRITE_WEBP_LOSSLESS_OFF, 80, LOSSY),
|
||||
WebPModePriorityParams(IMWRITE_WEBP_LOSSLESS_OFF, 101, LOSSLESS),
|
||||
|
||||
// LOSSLESS_ON (Force lossless)
|
||||
WebPModePriorityParams(IMWRITE_WEBP_LOSSLESS_ON, -1, LOSSLESS),
|
||||
WebPModePriorityParams(IMWRITE_WEBP_LOSSLESS_ON, 80, LOSSLESS),
|
||||
WebPModePriorityParams(IMWRITE_WEBP_LOSSLESS_ON, 101, LOSSLESS),
|
||||
|
||||
// PRESERVE_COLOR (Exact lossless)
|
||||
WebPModePriorityParams(IMWRITE_WEBP_LOSSLESS_PRESERVE_COLOR, -1, EXACT),
|
||||
WebPModePriorityParams(IMWRITE_WEBP_LOSSLESS_PRESERVE_COLOR, 80, EXACT),
|
||||
WebPModePriorityParams(IMWRITE_WEBP_LOSSLESS_PRESERVE_COLOR, 101, EXACT)
|
||||
),
|
||||
[](const testing::TestParamInfo<WebPModePriorityParams>& info_) {
|
||||
std::string mode = getModeStr(std::get<0>(info_.param));
|
||||
int q = std::get<1>(info_.param);
|
||||
std::string q_str = (q == -1) ? "omit" : std::to_string(q);
|
||||
return mode + "_q" + q_str + "_" + getExpectStr(std::get<2>(info_.param));
|
||||
}
|
||||
);
|
||||
|
||||
#endif // HAVE_WEBP
|
||||
|
||||
}} // namespace
|
||||
Reference in New Issue
Block a user