Commit 5aac9090 authored by Vinay Sharma's avatar Vinay Sharma Committed by Alexander Alekhin

Merge pull request #10352 from vinay0410:write_pbm

* added write as pbm

* add tests for pbm

* imgcodecs: PBM support

- drop additional PBM parameters
- write: fix P1/P4 mode (no maxval 255 value after width/height)
- write: invert values for P1/P4
- write: P1: compact ASCII mode (no spaces)
- simplify pbm test
- drop .pxm extension (http://netpbm.sourceforge.net/doc/ doesn't know such extension)
parent 592f8d8c
...@@ -374,31 +374,33 @@ bool PxMDecoder::readData( Mat& img ) ...@@ -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; m_buf_supported = true;
} }
PxMEncoder::~PxMEncoder() PxMEncoder::~PxMEncoder()
{ {
} }
bool PxMEncoder::isFormatSupported(int depth) const
ImageEncoder PxMEncoder::newEncoder() const
{
return makePtr<PxMEncoder>();
}
bool PxMEncoder::isFormatSupported( int depth ) const
{ {
if (mode_ == PXM_TYPE_PBM)
return depth == CV_8U;
return depth == CV_8U || depth == CV_16U; return depth == CV_8U || depth == CV_16U;
} }
bool PxMEncoder::write(const Mat& img, const std::vector<int>& params)
bool PxMEncoder::write( const Mat& img, const std::vector<int>& params )
{ {
bool isBinary = true; bool isBinary = true;
...@@ -409,8 +411,29 @@ bool PxMEncoder::write( const Mat& img, const std::vector<int>& params ) ...@@ -409,8 +411,29 @@ bool PxMEncoder::write( const Mat& img, const std::vector<int>& params )
int x, y; int x, y;
for( size_t i = 0; i < params.size(); i += 2 ) 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; 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; WLByteStream strm;
...@@ -441,18 +464,58 @@ bool PxMEncoder::write( const Mat& img, const std::vector<int>& params ) ...@@ -441,18 +464,58 @@ bool PxMEncoder::write( const Mat& img, const std::vector<int>& params )
char* buffer = _buffer; char* buffer = _buffer;
// write header; // write header;
sprintf( buffer, "P%c\n# Generated by OpenCV %s\n%d %d\n%d\n", const int code = ((mode == PXM_TYPE_PBM) ? 1 : (mode == PXM_TYPE_PGM) ? 2 : 3)
'2' + (channels > 1 ? 1 : 0) + (isBinary ? 3 : 0), + (isBinary ? 3 : 0);
CV_VERSION, const char* comment = "# Generated by OpenCV " CV_VERSION "\n";
width, height, (1 << depth) - 1 );
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, (int)strlen(buffer) ); strm.putBytes(buffer, header_sz);
for( y = 0; y < height; y++ ) for( y = 0; y < height; y++ )
{ {
const uchar* const data = img.ptr(y); const uchar* const data = img.ptr(y);
if( isBinary ) 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( _channels == 3 )
{ {
if( depth == 8 ) if( depth == 8 )
...@@ -475,59 +538,72 @@ bool PxMEncoder::write( const Mat& img, const std::vector<int>& params ) ...@@ -475,59 +538,72 @@ bool PxMEncoder::write( const Mat& img, const std::vector<int>& params )
buffer[x + 1] = v; 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 else
{ {
char* ptr = buffer; char* ptr = buffer;
if (mode == PXM_TYPE_PBM)
if( channels > 1 )
{ {
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 ) ptr[0] = data[x] ? '0' : '1';
{ ptr += 1;
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 else
{ {
if( depth == 8 ) if( channels > 1 )
{ {
for( x = 0; x < width; x++ ) if( depth == 8 )
{
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
{ {
sprintf( ptr, "% 4d", data[x] ); for( x = 0; x < width*channels; x += channels )
ptr += 4; {
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 else
{ {
for( x = 0; x < width; x++ ) if( depth == 8 )
{ {
sprintf( ptr, "% 6d", ((const ushort *)data)[x] ); for( x = 0; x < width; x++ )
ptr += 6; {
sprintf( ptr, "% 4d", data[x] );
ptr += 4;
}
}
else
{
for( x = 0; x < width; x++ )
{
sprintf( ptr, "% 6d", ((const ushort *)data)[x] );
ptr += 6;
}
} }
} }
} }
......
...@@ -49,6 +49,14 @@ ...@@ -49,6 +49,14 @@
namespace cv 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 class PxMDecoder : public BaseImageDecoder
{ {
public: public:
...@@ -74,17 +82,21 @@ protected: ...@@ -74,17 +82,21 @@ protected:
int m_maxval; int m_maxval;
}; };
class PxMEncoder : public BaseImageEncoder class PxMEncoder : public BaseImageEncoder
{ {
public: public:
PxMEncoder(); PxMEncoder(PxMMode mode);
virtual ~PxMEncoder(); virtual ~PxMEncoder();
bool isFormatSupported( int depth ) const; bool isFormatSupported( int depth ) const;
bool write( const Mat& img, const std::vector<int>& params ); bool write( const Mat& img, const std::vector<int>& params );
ImageEncoder newEncoder() const; ImageEncoder newEncoder() const
{
return makePtr<PxMEncoder>(mode_);
}
const PxMMode mode_;
}; };
} }
......
...@@ -144,7 +144,10 @@ struct ImageCodecInitializer ...@@ -144,7 +144,10 @@ struct ImageCodecInitializer
decoders.push_back( makePtr<SunRasterDecoder>() ); decoders.push_back( makePtr<SunRasterDecoder>() );
encoders.push_back( makePtr<SunRasterEncoder>() ); encoders.push_back( makePtr<SunRasterEncoder>() );
decoders.push_back( makePtr<PxMDecoder>() ); decoders.push_back( makePtr<PxMDecoder>() );
encoders.push_back( makePtr<PxMEncoder>() ); encoders.push_back( makePtr<PxMEncoder>(PXM_TYPE_AUTO) );
encoders.push_back( makePtr<PxMEncoder>(PXM_TYPE_PBM) );
encoders.push_back( makePtr<PxMEncoder>(PXM_TYPE_PGM) );
encoders.push_back( makePtr<PxMEncoder>(PXM_TYPE_PPM) );
#ifdef HAVE_TIFF #ifdef HAVE_TIFF
decoders.push_back( makePtr<TiffDecoder>() ); decoders.push_back( makePtr<TiffDecoder>() );
encoders.push_back( makePtr<TiffEncoder>() ); encoders.push_back( makePtr<TiffEncoder>() );
......
...@@ -144,16 +144,27 @@ TEST_P(Imgcodecs_ExtSize, write_imageseq) ...@@ -144,16 +144,27 @@ TEST_P(Imgcodecs_ExtSize, write_imageseq)
for (int cn = 1; cn <= 4; cn++) for (int cn = 1; cn <= 4; cn++)
{ {
SCOPED_TRACE(format("channels %d", cn)); SCOPED_TRACE(format("channels %d", cn));
std::vector<int> parameters;
if (cn == 2) if (cn == 2)
continue; continue;
if (cn == 4 && ext != ".tiff") if (cn == 4 && ext != ".tiff")
continue; 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()); 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)); Mat img_gt(size, CV_MAKETYPE(CV_8U, cn), Scalar::all(0));
circle(img_gt, center, radius, Scalar::all(255)); 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); Mat img = imread(filename, IMREAD_UNCHANGED);
ASSERT_FALSE(img.empty()); ASSERT_FALSE(img.empty());
EXPECT_EQ(img.size(), img.size()); EXPECT_EQ(img.size(), img.size());
...@@ -175,7 +186,13 @@ TEST_P(Imgcodecs_ExtSize, write_imageseq) ...@@ -175,7 +186,13 @@ TEST_P(Imgcodecs_ExtSize, write_imageseq)
EXPECT_LT(n, 1.); EXPECT_LT(n, 1.);
EXPECT_PRED_FORMAT2(cvtest::MatComparator(0, 0), img, img_gt); 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())); EXPECT_EQ(0, remove(filename.c_str()));
#endif
} }
} }
...@@ -191,8 +208,11 @@ const string all_exts[] = ...@@ -191,8 +208,11 @@ const string all_exts[] =
".jpg", ".jpg",
#endif #endif
".bmp", ".bmp",
".pam",
".ppm",
".pgm", ".pgm",
".pam" ".pbm",
".pnm"
}; };
vector<Size> all_sizes() vector<Size> all_sizes()
...@@ -208,6 +228,40 @@ INSTANTIATE_TEST_CASE_P(All, Imgcodecs_ExtSize, ...@@ -208,6 +228,40 @@ INSTANTIATE_TEST_CASE_P(All, Imgcodecs_ExtSize,
testing::ValuesIn(all_exts), testing::ValuesIn(all_exts),
testing::ValuesIn(all_sizes()))); testing::ValuesIn(all_sizes())));
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());
//================================================================================================== //==================================================================================================
TEST(Imgcodecs_Bmp, read_rle8) TEST(Imgcodecs_Bmp, read_rle8)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment