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 )
//////////////////////////////////////////////////////////////////////////////////////////
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<PxMEncoder>();
}
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<int>& params )
bool PxMEncoder::write(const Mat& img, const std::vector<int>& params)
{
bool isBinary = true;
......@@ -409,8 +411,29 @@ bool PxMEncoder::write( const Mat& img, const std::vector<int>& 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<int>& 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";
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++ )
{
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<int>& 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 )
{
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] );
ptr += 4;
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;
}
}
}
}
......
......@@ -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<int>& params );
ImageEncoder newEncoder() const;
ImageEncoder newEncoder() const
{
return makePtr<PxMEncoder>(mode_);
}
const PxMMode mode_;
};
}
......
......@@ -144,7 +144,10 @@ struct ImageCodecInitializer
decoders.push_back( makePtr<SunRasterDecoder>() );
encoders.push_back( makePtr<SunRasterEncoder>() );
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
decoders.push_back( makePtr<TiffDecoder>() );
encoders.push_back( makePtr<TiffEncoder>() );
......
......@@ -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<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;
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<Size> all_sizes()
......@@ -208,6 +228,40 @@ INSTANTIATE_TEST_CASE_P(All, Imgcodecs_ExtSize,
testing::ValuesIn(all_exts),
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)
......
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