1/*
2 Copyright 2005-2007 Adobe Systems Incorporated
3
4 Use, modification and distribution are subject to the Boost Software License,
5 Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at
6 http://www.boost.org/LICENSE_1_0.txt).
7
8 See http://opensource.adobe.com/gil for most recent version including documentation.
9*/
10// image_test.cpp :
11//
12
13#ifdef _MSC_VER
14//#pragma warning(disable : 4244) // conversion from 'gil::image<V,Alloc>::coord_t' to 'int', possible loss of data (visual studio compiler doesn't realize that the two types are the same)
15#pragma warning(disable : 4503) // decorated name length exceeded, name was truncated
16#endif
17
18#include <string>
19#include <vector>
20#include <ios>
21#include <iostream>
22#include <fstream>
23#include <map>
24#include <boost/lambda/lambda.hpp>
25#include <boost/lambda/bind.hpp>
26#include <boost/mpl/vector.hpp>
27#include <boost/gil/extension/dynamic_image/dynamic_image_all.hpp>
28#include <boost/crc.hpp>
29
30using namespace boost::gil;
31using namespace std;
32using namespace boost;
33
34extern rgb8c_planar_view_t sample_view;
35void error_if(bool condition);
36
37
38// When BOOST_GIL_GENERATE_REFERENCE_DATA is defined, the reference data is generated and saved.
39// When it is undefined, regression tests are checked against it
40//#define BOOST_GIL_GENERATE_REFERENCE_DATA
41
42////////////////////////////////////////////////////
43///
44/// Some algorithms to use in testing
45///
46////////////////////////////////////////////////////
47
48template <typename GrayView, typename R>
49void gray_image_hist(const GrayView& img_view, R& hist) {
50// for_each_pixel(img_view,++lambda::var(hist)[lambda::_1]);
51 for (typename GrayView::iterator it=img_view.begin(); it!=img_view.end(); ++it)
52 ++hist[*it];
53}
54
55template <typename V, typename R>
56void get_hist(const V& img_view, R& hist) {
57 gray_image_hist(color_converted_view<gray8_pixel_t>(img_view), hist);
58}
59
60// testing custom color conversion
61template <typename C1, typename C2>
62struct my_color_converter_impl : public default_color_converter_impl<C1,C2> {};
63template <typename C1>
64struct my_color_converter_impl<C1,gray_t> {
65 template <typename P1, typename P2>
66 void operator()(const P1& src, P2& dst) const {
67 default_color_converter_impl<C1,gray_t>()(src,dst);
68 get_color(dst,gray_color_t())=channel_invert(get_color(dst,gray_color_t()));
69 }
70};
71
72struct my_color_converter {
73 template <typename SrcP,typename DstP>
74 void operator()(const SrcP& src,DstP& dst) const {
75 typedef typename color_space_type<SrcP>::type src_cs_t;
76 typedef typename color_space_type<DstP>::type dst_cs_t;
77 my_color_converter_impl<src_cs_t,dst_cs_t>()(src,dst);
78 }
79};
80
81// Models a Unary Function
82template <typename P> // Models PixelValueConcept
83struct mandelbrot_fn {
84 typedef point2<std::ptrdiff_t> point_t;
85
86 typedef mandelbrot_fn const_t;
87 typedef P value_type;
88 typedef value_type reference;
89 typedef value_type const_reference;
90 typedef point_t argument_type;
91 typedef reference result_type;
92 BOOST_STATIC_CONSTANT(bool, is_mutable=false);
93
94 value_type _in_color,_out_color;
95 point_t _img_size;
96 static const int MAX_ITER=100; // max number of iterations
97
98 mandelbrot_fn() {}
99 mandelbrot_fn(const point_t& sz, const value_type& in_color, const value_type& out_color) : _in_color(in_color), _out_color(out_color), _img_size(sz) {}
100
101 result_type operator()(const point_t& p) const {
102 // normalize the coords to (-2..1, -1.5..1.5)
103 // (actually make y -1.0..2 so it is asymmetric, so we can verify some view factory methods)
104 double t=get_num_iter(p: point2<double>(p.x/(double)_img_size.x*3-2, p.y/(double)_img_size.y*3-1.0f));//1.5f));
105 t=pow(x: t,y: 0.2);
106
107 value_type ret;
108 for (int k=0; k<num_channels<P>::value; ++k)
109 ret[k]=(typename channel_type<value_type>::type)(_in_color[k]*t + _out_color[k]*(1-t));
110 return ret;
111 }
112
113private:
114 double get_num_iter(const point2<double>& p) const {
115 point2<double> Z(0,0);
116 for (int i=0; i<MAX_ITER; ++i) {
117 Z = point2<double>(Z.x*Z.x - Z.y*Z.y + p.x, 2*Z.x*Z.y + p.y);
118 if (Z.x*Z.x + Z.y*Z.y > 4)
119 return i/(double)MAX_ITER;
120 }
121 return 0;
122 }
123};
124
125template <typename T>
126void x_gradient(const T& src, const gray8s_view_t& dst) {
127 for (int y=0; y<src.height(); ++y) {
128 typename T::x_iterator src_it = src.row_begin(y);
129 gray8s_view_t::x_iterator dst_it = dst.row_begin(y);
130
131 for (int x=1; x<src.width()-1; ++x)
132 dst_it[x] = (src_it[x+1] - src_it[x-1]) / 2;
133 }
134}
135
136// A quick test whether a view is homogeneous
137
138template <typename Pixel>
139struct pixel_is_homogeneous : public mpl::true_ {};
140
141template <typename P, typename C, typename L>
142struct pixel_is_homogeneous<packed_pixel<P,C,L> > : public mpl::false_ {};
143
144template <typename View>
145struct view_is_homogeneous : public pixel_is_homogeneous<typename View::value_type> {};
146
147
148////////////////////////////////////////////////////
149///
150/// Tests image view transformations and algorithms
151///
152////////////////////////////////////////////////////
153class image_test {
154public:
155 virtual void initialize() {}
156 virtual void finalize() {}
157 virtual ~image_test() {}
158
159 void run();
160protected:
161 virtual void check_view_impl(const rgb8c_view_t& view, const string& name)=0;
162 template <typename View>
163 void check_view(const View& img_view, const string& name) {
164 rgb8_image_t rgb_img(img_view.dimensions());
165 copy_and_convert_pixels(img_view,view(img&: rgb_img));
166 check_view_impl(view: const_view(img: rgb_img), name);
167 }
168private:
169 template <typename Img> void basic_test(const string& prefix);
170 template <typename View> void view_transformations_test(const View& img_view, const string& prefix);
171 template <typename View> void homogeneous_view_transformations_test(const View& img_view, const string& prefix, mpl::true_);
172 template <typename View> void homogeneous_view_transformations_test(const View& img_view, const string& prefix, mpl::false_) {}
173 template <typename View> void histogram_test(const View& img_view, const string& prefix);
174 void virtual_view_test();
175 void packed_image_test();
176 void dynamic_image_test();
177 template <typename Img> void image_all_test(const string& prefix);
178};
179
180
181// testing image iterators, clone, fill, locators, color convert
182template <typename Img>
183void image_test::basic_test(const string& prefix) {
184 typedef typename Img::view_t View;
185
186 // make a 20x20 image
187 Img img(typename View::point_t(20,20));
188 const View& img_view=view(img);
189
190 // fill it with red
191 rgb8_pixel_t red8(255,0,0), green8(0,255,0), blue8(0,0,255), white8(255,255,255);
192 typename View::value_type red,green,blue,white;
193 color_convert(red8,red);
194 default_color_converter()(red8,red);
195 red=color_convert_deref_fn<rgb8_ref_t,typename Img::view_t::value_type>()(red8);
196
197 color_convert(green8,green);
198 color_convert(blue8,blue);
199 color_convert(white8,white);
200 fill(img_view.begin(),img_view.end(),red);
201
202 color_convert(red8,img_view[0]);
203
204 // pointer to first pixel of second row
205 typename View::reference rt=img_view.at(0,0)[img_view.width()];
206 typename View::x_iterator ptr=&rt;
207 typename View::reference rt2=*(img_view.at(0,0)+img_view.width());
208 typename View::x_iterator ptr2=&rt2;
209 error_if(ptr!=ptr2);
210 error_if(img_view.x_at(0,0)+10!=10+img_view.x_at(0,0));
211
212 // draw a blue line along the diagonal
213 typename View::xy_locator loc=img_view.xy_at(0,img_view.height()-1);
214 for (int y=0; y<img_view.height(); ++y) {
215 *loc=blue;
216 ++loc.x();
217 loc.y()--;
218 }
219
220 // draw a green dotted line along the main diagonal with step of 3
221 loc=img_view.xy_at(img_view.width()-1,img_view.height()-1);
222 while (loc.x()>=img_view.x_at(0,0)) {
223 *loc=green;
224 loc-=typename View::point_t(3,3);
225 }
226
227 // Clone and make every red pixel white
228 Img imgWhite(img);
229 for (typename View::iterator it=view(imgWhite).end(); (it-1)!=view(imgWhite).begin(); --it) {
230 if (*(it-1)==red)
231 *(it-1)=white;
232 }
233
234 check_view(img_view,prefix+"red_x");
235 check_view(view(imgWhite),prefix+"white_x");
236}
237
238template <typename View>
239void image_test::histogram_test(const View& img_view, const string& prefix) {
240// vector<int> histogram(255,0);
241// get_hist(cropped,histogram.begin());
242 unsigned char histogram[256];
243 fill(first: histogram,last: histogram+256,value: 0);
244 get_hist(img_view,histogram);
245 gray8c_view_t hist_view=interleaved_view(width: 256,height: 1,pixels: (const gray8_pixel_t*)histogram,rowsize_in_bytes: 256);
246 check_view(img_view: hist_view,name: prefix+"histogram");
247}
248
249
250template <typename View>
251void image_test::view_transformations_test(const View& img_view, const string& prefix) {
252 check_view(img_view,prefix+"original");
253
254 check_view(subimage_view(img_view, iround(img_view.dimensions()/4), iround(img_view.dimensions()/2)),prefix+"cropped");
255 check_view(color_converted_view<gray8_pixel_t>(img_view),prefix+"gray8");
256 check_view(color_converted_view<gray8_pixel_t>(img_view,my_color_converter()),prefix+"my_gray8");
257 check_view(transposed_view(img_view),prefix+"transpose");
258 check_view(rotated180_view(img_view),prefix+"rot180");
259 check_view(rotated90cw_view(img_view),prefix+"90cw");
260 check_view(rotated90ccw_view(img_view),prefix+"90ccw");
261 check_view(flipped_up_down_view(img_view),prefix+"flipped_ud");
262 check_view(flipped_left_right_view(img_view),prefix+"flipped_lr");
263 check_view(subsampled_view(img_view,typename View::point_t(2,1)),prefix+"subsampled");
264 check_view(kth_channel_view<0>(img_view),prefix+"0th_k_channel");
265 homogeneous_view_transformations_test(img_view, prefix, view_is_homogeneous<View>());
266}
267
268template <typename View>
269void image_test::homogeneous_view_transformations_test(const View& img_view, const string& prefix, mpl::true_) {
270 check_view(nth_channel_view(img_view,0),prefix+"0th_n_channel");
271}
272
273
274void image_test::virtual_view_test() {
275 typedef mandelbrot_fn<rgb8_pixel_t> deref_t;
276 typedef deref_t::point_t point_t;
277 typedef virtual_2d_locator<deref_t,false> locator_t;
278 typedef image_view<locator_t> my_virt_view_t;
279
280 boost::function_requires<PixelLocatorConcept<locator_t> >();
281 gil_function_requires<StepIteratorConcept<locator_t::x_iterator> >();
282
283 point_t dims(200,200);
284 my_virt_view_t mandel(dims, locator_t(point_t(0,0), point_t(1,1), deref_t(dims, rgb8_pixel_t(255,0,255), rgb8_pixel_t(0,255,0))));
285
286 gray8s_image_t img(dims);
287 fill_pixels(img_view: view(img),val: 0); // our x_gradient algorithm doesn't change the first & last column, so make sure they are 0
288 x_gradient(src: color_converted_view<gray8_pixel_t>(src: mandel), dst: view(img));
289 check_view(img_view: color_converted_view<gray8_pixel_t>(src: const_view(img)), name: "mandelLuminosityGradient");
290
291 view_transformations_test(img_view: mandel,prefix: "virtual_");
292 histogram_test(img_view: mandel,prefix: "virtual_");
293}
294
295// Test alignment and packed images
296void image_test::packed_image_test() {
297 typedef bit_aligned_image3_type<1,3,1, bgr_layout_t>::type bgr131_image_t;
298 typedef bgr131_image_t::value_type bgr131_pixel_t;
299 bgr131_pixel_t fill_val(1,3,1);
300
301 bgr131_image_t bgr131_img(3,10);
302 fill_pixels(img_view: view(img&: bgr131_img), val: fill_val);
303
304 bgr131_image_t bgr131a_img(3,10,1);
305 copy_pixels(src: const_view(img: bgr131_img), dst: view(img&: bgr131a_img));
306
307 bgr131_image_t bgr131b_img(3,10,4);
308 copy_pixels(src: const_view(img: bgr131_img), dst: view(img&: bgr131b_img));
309
310 error_if(condition: bgr131_img!=bgr131a_img || bgr131a_img!=bgr131b_img);
311}
312
313void image_test::dynamic_image_test() {
314 typedef any_image<mpl::vector<gray8_image_t, bgr8_image_t, argb8_image_t,
315 rgb8_image_t, rgb8_planar_image_t> > any_image_t;
316 rgb8_planar_image_t img(sample_view.dimensions());
317 copy_pixels(src: sample_view, dst: view(img));
318 any_image_t any_img=any_image_t(img);
319
320 check_view(img_view: view(anyImage&: any_img), name: "dynamic_");
321 check_view(img_view: flipped_left_right_view(src: view(anyImage&: any_img)), name: "dynamic_fliplr");
322 check_view(img_view: flipped_up_down_view(src: view(anyImage&: any_img)), name: "dynamic_flipud");
323
324 any_image_t::view_t subimageView=subimage_view(src: view(anyImage&: any_img),xMin: 0,yMin: 0,width: 10,height: 15);
325
326 check_view(img_view: subimageView, name: "dynamic_subimage");
327 check_view(img_view: subsampled_view(src: rotated180_view(src: view(anyImage&: any_img)), xStep: 2,yStep: 1), name: "dynamic_subimage_subsampled180rot");
328}
329
330template <typename Img>
331void image_test::image_all_test(const string& prefix) {
332 basic_test<Img>(prefix+"basic_");
333
334 Img img;
335 img.recreate(sample_view.dimensions());
336 copy_and_convert_pixels(sample_view,view(img));
337
338 view_transformations_test(view(img), prefix+"views_");
339
340 histogram_test(const_view(img),prefix+"histogram_");
341}
342
343void image_test::run() {
344 initialize();
345
346 image_all_test<bgr8_image_t>(prefix: "bgr8_");
347 image_all_test<rgb8_image_t>(prefix: "rgb8_");
348 image_all_test<rgb8_planar_image_t>(prefix: "planarrgb8_");
349 image_all_test<gray8_image_t>(prefix: "gray8_");
350
351 typedef const bit_aligned_pixel_reference<boost::uint8_t, mpl::vector3_c<int,1,2,1>, bgr_layout_t, true> bgr121_ref_t;
352 typedef image<bgr121_ref_t,false> bgr121_image_t;
353 image_all_test<bgr121_image_t>(prefix: "bgr121_");
354
355 // TODO: Remove?
356 view_transformations_test(img_view: subsampled_view(src: sample_view,step: point2<ptrdiff_t>(1,2)),prefix: "subsampled_");
357 view_transformations_test(img_view: color_converted_view<gray8_pixel_t>(src: sample_view),prefix: "color_converted_");
358
359 virtual_view_test();
360 packed_image_test();
361 dynamic_image_test();
362
363 finalize();
364}
365
366
367
368////////////////////////////////////////////////////
369///
370/// Performs or generates image tests using checksums
371///
372////////////////////////////////////////////////////
373
374class checksum_image_mgr : public image_test {
375protected:
376 typedef map<string,boost::crc_32_type::value_type> crc_map_t;
377 crc_map_t _crc_map;
378};
379
380////////////////////////////////////////////////////
381///
382/// Performs image tests by comparing image pixel checksums against a reference
383///
384////////////////////////////////////////////////////
385
386class checksum_image_test : public checksum_image_mgr {
387public:
388 checksum_image_test(const char* filename) : _filename(filename) {}
389private:
390 const char* _filename;
391 virtual void initialize();
392 virtual void check_view_impl(const rgb8c_view_t& v, const string& name);
393};
394
395// Load the checksums from the reference file and create the start image
396void checksum_image_test::initialize() {
397 string crc_name;
398 boost::crc_32_type::value_type crc_result;
399 fstream checksum_ref(_filename,ios::in);
400 while (true) {
401 checksum_ref >> crc_name >> std::hex >> crc_result;
402 if(checksum_ref.fail()) break;
403 _crc_map[crc_name]=crc_result;
404 }
405 checksum_ref.close();
406}
407
408// Create a checksum for the given view and compare it with the reference checksum. Throw exception if different
409void checksum_image_test::check_view_impl(const rgb8c_view_t& img_view, const string& name) {
410 boost::crc_32_type checksum_acumulator;
411 checksum_acumulator.process_bytes(buffer: img_view.row_begin(y: 0),byte_count: img_view.size()*3);
412
413 cerr << "Checking checksum for " << name << endl;
414 if (checksum_acumulator.checksum()!=_crc_map[name]) {
415 cerr << "Checksum error in "<<name<<"\n";
416 error_if(condition: true);
417 }
418}
419
420////////////////////////////////////////////////////
421///
422/// Generates a set of reference checksums to compare against
423///
424////////////////////////////////////////////////////
425
426class checksum_image_generate : public checksum_image_mgr {
427public:
428 checksum_image_generate(const char* filename) : _filename(filename) {}
429private:
430 const char* _filename;
431 virtual void check_view_impl(const rgb8c_view_t& img_view, const string& name);
432 virtual void finalize();
433};
434
435// Add the checksum of the given view to the map of checksums
436void checksum_image_generate::check_view_impl(const rgb8c_view_t& img_view, const string& name) {
437 boost::crc_32_type result;
438 result.process_bytes(buffer: img_view.row_begin(y: 0),byte_count: img_view.size()*3);
439 cerr << "Generating checksum for " << name << endl;
440 _crc_map[name] = result.checksum();
441}
442
443// Save the checksums into the reference file
444void checksum_image_generate::finalize() {
445 fstream checksum_ref(_filename,ios::out);
446 for (crc_map_t::const_iterator it=_crc_map.begin(); it!=_crc_map.end(); ++it) {
447 checksum_ref << it->first << " " << std::hex << it->second << "\r\n";
448 }
449 checksum_ref.close();
450}
451
452
453////////////////////////////////////////////////////
454///
455/// Performs or generates image tests using image I/O
456///
457////////////////////////////////////////////////////
458
459extern const string in_dir;
460extern const string out_dir;
461extern const string ref_dir;
462
463const string in_dir=""; // directory of source images
464const string out_dir=in_dir+"image-out/"; // directory where to write output
465const string ref_dir=in_dir+"image-ref/"; // reference directory to compare written with actual output
466
467#ifndef BOOST_GIL_NO_IO
468
469#include <boost/gil/extension/io/jpeg_io.hpp>
470
471class file_image_mgr : public image_test {};
472
473class file_image_test : public file_image_mgr {
474public:
475 file_image_test(const char*) {}
476protected:
477 void check_view_impl(const boost::gil::rgb8c_view_t& img_view,const string& name) {
478 jpeg_write_view(out_dir+name+".jpg",img_view);
479 rgb8_image_t img1, img2;
480 jpeg_read_and_convert_image(out_dir+name+".jpg",img1);
481 cerr << "Testing "<<name<<"\n";
482
483 jpeg_read_and_convert_image(ref_dir+name+".jpg",img2);
484 if (img1!=img2) {
485 cerr << "Error with "<<name<<"\n";
486 error_if(true);
487 }
488 }
489};
490
491class file_image_generate : public file_image_mgr {
492public:
493 file_image_generate(const char*) {}
494protected:
495 void check_view_impl(const boost::gil::rgb8c_view_t& img_view,const string& name) {
496 jpeg_write_view(ref_dir+name+".jpg",img_view);
497 cerr << "Writing "<<name<<"\n";
498 }
499};
500#endif
501
502
503
504
505
506
507
508
509void static_checks() {
510 gil_function_requires<ImageConcept<rgb8_image_t> >();
511
512 BOOST_STATIC_ASSERT(view_is_basic<rgb8_step_view_t>::value);
513 BOOST_STATIC_ASSERT(view_is_basic<cmyk8c_planar_step_view_t>::value);
514 BOOST_STATIC_ASSERT(view_is_basic<rgb8_planar_view_t>::value);
515
516 BOOST_STATIC_ASSERT(view_is_step_in_x<rgb8_step_view_t>::value);
517 BOOST_STATIC_ASSERT(view_is_step_in_x<cmyk8c_planar_step_view_t>::value);
518 BOOST_STATIC_ASSERT(!view_is_step_in_x<rgb8_planar_view_t>::value);
519
520 BOOST_STATIC_ASSERT(!is_planar<rgb8_step_view_t>::value);
521 BOOST_STATIC_ASSERT(is_planar<cmyk8c_planar_step_view_t>::value);
522 BOOST_STATIC_ASSERT(is_planar<rgb8_planar_view_t>::value);
523
524 BOOST_STATIC_ASSERT(view_is_mutable<rgb8_step_view_t>::value);
525 BOOST_STATIC_ASSERT(!view_is_mutable<cmyk8c_planar_step_view_t>::value);
526 BOOST_STATIC_ASSERT(view_is_mutable<rgb8_planar_view_t>::value);
527
528 BOOST_STATIC_ASSERT((boost::is_same<derived_view_type<cmyk8c_planar_step_view_t>::type, cmyk8c_planar_step_view_t>::value));
529 BOOST_STATIC_ASSERT((boost::is_same<derived_view_type<cmyk8c_planar_step_view_t, bits16, rgb_layout_t>::type, rgb16c_planar_step_view_t>::value));
530 BOOST_STATIC_ASSERT((boost::is_same<derived_view_type<cmyk8c_planar_step_view_t, use_default, rgb_layout_t, mpl::false_, use_default, mpl::false_>::type, rgb8c_step_view_t>::value));
531
532 // test view get raw data (mostly compile-time test)
533 {
534 rgb8_image_t rgb8(100,100);
535 unsigned char* data=interleaved_view_get_raw_data(view: view(img&: rgb8));
536 const unsigned char* cdata=interleaved_view_get_raw_data(view: const_view(img: rgb8));
537 error_if(condition: data!=cdata);
538 }
539
540 {
541 rgb16s_planar_image_t rgb8(100,100);
542 short* data=planar_view_get_raw_data(view: view(img&: rgb8),plane_index: 1);
543 const short* cdata=planar_view_get_raw_data(view: const_view(img: rgb8),plane_index: 1);
544 error_if(condition: data!=cdata);
545 }
546}
547
548#ifdef BOOST_GIL_NO_IO
549typedef checksum_image_test image_test_t;
550typedef checksum_image_generate image_generate_t;
551#else
552typedef file_image_test image_test_t;
553typedef file_image_generate image_generate_t;
554#endif
555
556#ifdef BOOST_GIL_GENERATE_REFERENCE_DATA
557typedef image_generate_t image_mgr_t;
558#else
559typedef image_test_t image_mgr_t;
560#endif
561
562
563void test_image(const char* ref_checksum) {
564 image_mgr_t mgr(ref_checksum);
565
566 mgr.run();
567 static_checks();
568}
569
570int main(int argc, char* argv[]) {
571
572 const char* local_name = "gil_reference_checksums.txt";
573 const char* name_from_status = "../libs/gil/test/gil_reference_checksums.txt";
574
575 std::ifstream file_is_there(local_name);
576 if (file_is_there) {
577 test_image(ref_checksum: local_name);
578 } else {
579 std::ifstream file_is_there(name_from_status);
580 if (file_is_there)
581 test_image(ref_checksum: name_from_status);
582 else {
583 std::cerr << "Unable to open gil_reference_checksums.txt"<<std::endl;
584 return 1;
585 }
586 }
587
588 return 0;
589}
590
591

source code of boost/libs/gil/test/image.cpp