diff --git a/modules/imgcodecs/src/grfmt_pxm.cpp b/modules/imgcodecs/src/grfmt_pxm.cpp index 2e20d7814f..a7da43902f 100644 --- a/modules/imgcodecs/src/grfmt_pxm.cpp +++ b/modules/imgcodecs/src/grfmt_pxm.cpp @@ -374,31 +374,33 @@ bool PxMDecoder::readData( Mat& img ) ////////////////////////////////////////////////////////////////////////////////////////// -PxMEncoder::PxMEncoder() +PxMEncoder::PxMEncoder(PxMMode mode) : + mode_(mode) { - m_description = "Portable image format (*.pbm;*.pgm;*.ppm;*.pxm;*.pnm)"; + 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; } - PxMEncoder::~PxMEncoder() { } - -ImageEncoder PxMEncoder::newEncoder() const -{ - return makePtr(); -} - - -bool PxMEncoder::isFormatSupported( int depth ) const +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& params ) +bool PxMEncoder::write(const Mat& img, const std::vector& params) { bool isBinary = true; @@ -409,8 +411,29 @@ bool PxMEncoder::write( const Mat& img, const std::vector& params ) int x, y; for( size_t i = 0; i < params.size(); i += 2 ) - if( params[i] == CV_IMWRITE_PXM_BINARY ) + { + if( params[i] == IMWRITE_PXM_BINARY ) isBinary = params[i+1] != 0; + } + + 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; @@ -441,18 +464,58 @@ bool PxMEncoder::write( const Mat& img, const std::vector& params ) char* buffer = _buffer; // write header; - sprintf( buffer, "P%c\n# Generated by OpenCV %s\n%d %d\n%d\n", - '2' + (channels > 1 ? 1 : 0) + (isBinary ? 3 : 0), - CV_VERSION, - width, height, (1 << depth) - 1 ); + const int code = ((mode == PXM_TYPE_PBM) ? 1 : (mode == PXM_TYPE_PGM) ? 2 : 3) + + (isBinary ? 3 : 0); + const char* comment = "# Generated by OpenCV " CV_VERSION "\n"; - strm.putBytes( buffer, (int)strlen(buffer) ); + int header_sz = sprintf(buffer, "P%c\n%s%d %d\n", + (char)('0' + code), comment, + width, height); + CV_Assert(header_sz > 0); + if (mode != PXM_TYPE_PBM) + { + int sz = sprintf(&buffer[header_sz], "%d\n", (1 << depth) - 1); + CV_Assert(sz > 0); + header_sz += sz; + } + + 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; + } + strm.putBytes(buffer, (int)(ptr - buffer)); + continue; + } + if( _channels == 3 ) { if( depth == 8 ) @@ -475,59 +538,72 @@ bool PxMEncoder::write( const Mat& img, const std::vector& params ) buffer[x + 1] = v; } } - strm.putBytes( (channels > 1 || depth > 8) ? buffer : (const char*)data, fileStep ); + + strm.putBytes( (channels > 1 || depth > 8) ? buffer : (const char*)data, fileStep); } else { char* ptr = buffer; - - if( channels > 1 ) + if (mode == PXM_TYPE_PBM) { - if( depth == 8 ) + CV_Assert(channels == 1); + CV_Assert(depth == 8); + for (x = 0; x < width; x++) { - for( x = 0; x < width*channels; x += channels ) - { - sprintf( ptr, "% 4d", data[x + 2] ); - ptr += 4; - sprintf( ptr, "% 4d", data[x + 1] ); - ptr += 4; - sprintf( ptr, "% 4d", data[x] ); - ptr += 4; - *ptr++ = ' '; - *ptr++ = ' '; - } - } - else - { - for( x = 0; x < width*channels; x += channels ) - { - sprintf( ptr, "% 6d", ((const ushort *)data)[x + 2] ); - ptr += 6; - sprintf( ptr, "% 6d", ((const ushort *)data)[x + 1] ); - ptr += 6; - sprintf( ptr, "% 6d", ((const ushort *)data)[x] ); - ptr += 6; - *ptr++ = ' '; - *ptr++ = ' '; - } + ptr[0] = data[x] ? '0' : '1'; + ptr += 1; } } else { - if( depth == 8 ) + if( channels > 1 ) { - for( x = 0; x < width; x++ ) + if( depth == 8 ) { - sprintf( ptr, "% 4d", data[x] ); - ptr += 4; + for( x = 0; x < width*channels; x += channels ) + { + sprintf( ptr, "% 4d", data[x + 2] ); + ptr += 4; + sprintf( ptr, "% 4d", data[x + 1] ); + ptr += 4; + sprintf( ptr, "% 4d", data[x] ); + ptr += 4; + *ptr++ = ' '; + *ptr++ = ' '; + } + } + else + { + for( x = 0; x < width*channels; x += channels ) + { + sprintf( ptr, "% 6d", ((const ushort *)data)[x + 2] ); + ptr += 6; + sprintf( ptr, "% 6d", ((const ushort *)data)[x + 1] ); + ptr += 6; + sprintf( ptr, "% 6d", ((const ushort *)data)[x] ); + ptr += 6; + *ptr++ = ' '; + *ptr++ = ' '; + } } } else { - for( x = 0; x < width; x++ ) + if( depth == 8 ) { - sprintf( ptr, "% 6d", ((const ushort *)data)[x] ); - ptr += 6; + for( x = 0; x < width; x++ ) + { + sprintf( ptr, "% 4d", data[x] ); + ptr += 4; + } + } + else + { + for( x = 0; x < width; x++ ) + { + sprintf( ptr, "% 6d", ((const ushort *)data)[x] ); + ptr += 6; + } } } } diff --git a/modules/imgcodecs/src/grfmt_pxm.hpp b/modules/imgcodecs/src/grfmt_pxm.hpp index 5460e11002..3c7100b7d8 100644 --- a/modules/imgcodecs/src/grfmt_pxm.hpp +++ b/modules/imgcodecs/src/grfmt_pxm.hpp @@ -49,6 +49,14 @@ 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 : public BaseImageDecoder { public: @@ -74,17 +82,21 @@ protected: int m_maxval; }; - class PxMEncoder : public BaseImageEncoder { public: - PxMEncoder(); + PxMEncoder(PxMMode mode); virtual ~PxMEncoder(); bool isFormatSupported( int depth ) const; bool write( const Mat& img, const std::vector& params ); - ImageEncoder newEncoder() const; + ImageEncoder newEncoder() const + { + return makePtr(mode_); + } + + const PxMMode mode_; }; } diff --git a/modules/imgcodecs/src/loadsave.cpp b/modules/imgcodecs/src/loadsave.cpp index 74c94a4da4..b6b3c7e5ec 100644 --- a/modules/imgcodecs/src/loadsave.cpp +++ b/modules/imgcodecs/src/loadsave.cpp @@ -144,7 +144,10 @@ struct ImageCodecInitializer decoders.push_back( makePtr() ); encoders.push_back( makePtr() ); decoders.push_back( makePtr() ); - encoders.push_back( makePtr() ); + encoders.push_back( makePtr(PXM_TYPE_AUTO) ); + encoders.push_back( makePtr(PXM_TYPE_PBM) ); + encoders.push_back( makePtr(PXM_TYPE_PGM) ); + encoders.push_back( makePtr(PXM_TYPE_PPM) ); #ifdef HAVE_TIFF decoders.push_back( makePtr() ); encoders.push_back( makePtr() ); diff --git a/modules/imgcodecs/test/test_grfmt.cpp b/modules/imgcodecs/test/test_grfmt.cpp index 74b72c3b3e..828daa46b3 100644 --- a/modules/imgcodecs/test/test_grfmt.cpp +++ b/modules/imgcodecs/test/test_grfmt.cpp @@ -144,16 +144,27 @@ TEST_P(Imgcodecs_ExtSize, write_imageseq) for (int cn = 1; cn <= 4; cn++) { SCOPED_TRACE(format("channels %d", cn)); + std::vector 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; 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)); - ASSERT_TRUE(imwrite(filename, img_gt)); - +#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.size(), img.size()); @@ -175,7 +186,13 @@ TEST_P(Imgcodecs_ExtSize, write_imageseq) EXPECT_LT(n, 1.); EXPECT_PRED_FORMAT2(cvtest::MatComparator(0, 0), img, img_gt); } +#if 0 + std::cout << filename << std::endl; + imshow("loaded", img); + waitKey(0); +#else EXPECT_EQ(0, remove(filename.c_str())); +#endif } } @@ -191,8 +208,11 @@ const string all_exts[] = ".jpg", #endif ".bmp", + ".pam", + ".ppm", ".pgm", - ".pam" + ".pbm", + ".pnm" }; vector all_sizes() @@ -208,6 +228,40 @@ INSTANTIATE_TEST_CASE_P(All, Imgcodecs_ExtSize, testing::ValuesIn(all_exts), testing::ValuesIn(all_sizes()))); +typedef testing::TestWithParam 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 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()); + + //================================================================================================== TEST(Imgcodecs_Bmp, read_rle8)