chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 12:06:04 +08:00
commit 86c9b1c39f
7743 changed files with 3316339 additions and 0 deletions
+216
View File
@@ -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
+60
View 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
#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
+10
View File
@@ -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)
+90
View File
@@ -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
+10
View File
@@ -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
+28
View File
@@ -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
+100
View File
@@ -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);
}
+581
View File
@@ -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;
}
}
+150
View File
@@ -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_*/
+643
View File
@@ -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
+238
View File
@@ -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_ */
+437
View File
@@ -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> &params) {
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
+56
View File
@@ -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_*/
+248
View File
@@ -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> &params) {
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. */
+305
View 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_*/
+740
View File
@@ -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;
}
}
+112
View File
@@ -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_*/
+937
View File
@@ -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. */
+127
View 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_*/
+585
View File
@@ -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 */
+166
View File
@@ -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__*/
+197
View File
@@ -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
+70
View File
@@ -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
+195
View File
@@ -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
+216
View File
@@ -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
+86
View File
@@ -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_*/
+949
View File
@@ -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. */
+113
View 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_*/
+651
View File
@@ -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. */
+95
View 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(&parameters);
return parameters;
}
opj_cparameters setupEncoderParameters(const std::vector<int>& params)
{
opj_cparameters parameters;
opj_set_default_encoder_parameters(&parameters);
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(), &parameters))
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(), &parameters, 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_*/
+670
View File
@@ -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. */
+68
View 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_*/
+787
View File
@@ -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
+103
View File
@@ -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_ */
+260
View File
@@ -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
+57
View File
@@ -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
+244
View File
@@ -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_*/
+630
View File
@@ -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
+108
View File
@@ -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_*/
+828
View File
@@ -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> &params)
{
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. */
+60
View 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_*/
+428
View File
@@ -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
+109
View File
@@ -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
+148
View File
@@ -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_*/
+717
View File
@@ -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, &timestamp))
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
+102
View File
@@ -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_ */
+66
View File
@@ -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_*/
+66
View File
@@ -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);
}
+68
View File
@@ -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_ */
+477
View File
@@ -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;
}
+89
View File
@@ -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_*/
+659
View File
@@ -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
+159
View File
@@ -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_*/
+744
View File
@@ -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
+310
View File
@@ -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
+61
View File
@@ -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
+68
View 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
#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
+16
View File
@@ -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
+711
View File
@@ -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));
}}
+375
View File
@@ -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
+41
View File
@@ -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
+520
View File
@@ -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
+655
View File
@@ -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
+294
View File
@@ -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
+364
View File
@@ -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
+10
View File
@@ -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")
+648
View File
@@ -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
+102
View File
@@ -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
+631
View File
@@ -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
+138
View File
@@ -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
+267
View File
@@ -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