1 // ----------------------------------------------------------------------------
2 // picture.cxx rgb picture viewer
3 //
4 // Copyright (C) 2006-2008
5 //		Dave Freese, W1HKJ
6 // Copyright (C) 2008-2009
7 //		Stelios Bounanos, M0GLD
8 // Copyright (C) 2010
9 //		Remi Chateauneu, F4ECW
10 //
11 // This file is part of fldigi.
12 //
13 // fldigi is free software; you can redistribute it and/or modify
14 // it under the terms of the GNU General Public License as published by
15 // the Free Software Foundation; either version 3 of the License, or
16 // (at your option) any later version.
17 //
18 // Fldigi is distributed in the hope that it will be useful,
19 // but WITHOUT ANY WARRANTY; without even the implied warranty of
20 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
21 // GNU General Public License for more details.
22 //
23 // You should have received a copy of the GNU General Public License
24 // along with this program.  If not, see <http://www.gnu.org/licenses/>.
25 // ----------------------------------------------------------------------------
26 
27 #include <zlib.h>
28 #include <config.h>
29 
30 #ifdef __MINGW32__
31 #  include "compat.h"
32 #endif
33 
34 #include <string>
35 #include <sstream>
36 #include <cmath>
37 #include <cstdlib>
38 #include <cstdio>
39 #include <cstring>
40 #include <algorithm>
41 
42 #include <time.h>
43 #include <unistd.h>
44 #include <sys/stat.h>
45 #include <sys/types.h>
46 
47 #include <FL/Fl.H>
48 #include <FL/fl_draw.H>
49 
50 //#include <zlib.h>
51 #include <png.h>
52 
53 #include "fl_digi.h"
54 #include "trx.h"
55 #include "picture.h"
56 #include "debug.h"
57 #include "timeops.h"
58 
59 using namespace std;
60 
picture(int X,int Y,int W,int H,int bg_col)61 picture::picture (int X, int Y, int W, int H, int bg_col) :
62 	Fl_Widget (X, Y, W, H)
63 {
64 	width = W;
65 	height = H;
66 	bufsize = W * H * depth;
67 	numcol = 0;
68 	slantdir = 0;
69 	vidbuf = new unsigned char[bufsize];
70 	background = bg_col ;
71 	memset( vidbuf, background, bufsize );
72 	zoom = 0 ;
73 	binary = false ;
74 	binary_threshold = 128 ;
75 	slantcorr = true;
76 	cbFunc = NULL;
77 }
78 
~picture()79 picture::~picture()
80 {
81 	if (vidbuf) delete [] vidbuf;
82 }
83 
video(unsigned char const * data,int len)84 void picture::video(unsigned char const *data, int len )
85 {
86 	if (len > bufsize) return;
87 	memcpy( vidbuf, data, len );
88 	redraw();
89 }
90 
pixel(int pos)91 unsigned char picture::pixel(int pos)
92 {
93 	if (pos < 0 || pos >= bufsize) return 0;
94 	return vidbuf[pos];
95 }
96 
clear()97 void picture::clear()
98 {
99 	memset(vidbuf, background, bufsize);
100 	redraw();
101 }
102 
103 
resize_zoom(int x,int y,int w,int h)104 void picture::resize_zoom(int x, int y, int w, int h)
105 {
106 	if( zoom < 0 ) {
107 		int stride = -zoom + 1 ;
108 		Fl_Widget::resize(x,y,w/stride,h/stride);
109 	} else if( zoom > 0 ) {
110 		int stride = zoom + 1 ;
111 		Fl_Widget::resize(x,y,w*stride,h*stride);
112 	} else {
113 		Fl_Widget::resize(x,y,w,h);
114 	}
115 	redraw();
116 }
117 
resize(int x,int y,int w,int h)118 void picture::resize(int x, int y, int w, int h)
119 {
120 	if (w != width || h != height) {
121 		width = w;
122 		height = h;
123 		delete [] vidbuf;
124 		bufsize = depth * w * h;
125 		vidbuf = new unsigned char[bufsize];
126 		memset( vidbuf, background, bufsize );
127 	}
128 	resize_zoom(x,y,w,h);
129 }
130 
131 /// No data destruction. Used when the received image grows more than expected.
132 /// Beware that this is not protected by a mutex.
resize_height(int new_height,bool clear_img)133 void picture::resize_height(int new_height, bool clear_img)
134 {
135 	int new_bufsize = width * new_height * depth;
136 
137 	/// If the allocation fails, std::bad_alloc is thrown.
138 	unsigned char * new_vidbuf = new unsigned char[new_bufsize];
139 	if( clear_img )
140 	{
141 		/// Sets to zero the complete image.
142 		memset( new_vidbuf, background, new_bufsize );
143 	}
144 	else
145 	{
146 		if( new_height <= height )
147 		{
148 			memcpy( new_vidbuf, vidbuf, new_bufsize );
149 		}
150 		else
151 		{
152 			memcpy( new_vidbuf, vidbuf, bufsize );
153 			memset( new_vidbuf + bufsize, background, new_bufsize - bufsize );
154 		}
155 	}
156 	delete [] vidbuf ;
157 	vidbuf = new_vidbuf ;
158 	bufsize = new_bufsize ;
159 	height = new_height;
160 	resize_zoom( x(), y(), width, height );
161 	redraw();
162 }
163 
stretch(double the_ratio)164 void picture::stretch(double the_ratio)
165 {
166 
167 	/// We do not change the width but the height
168 	int new_height = height * the_ratio + 0.5 ;
169 	int new_bufsize = width * new_height * depth;
170 
171 	/// If the allocation fails, std::bad_alloc is thrown.
172 	unsigned char * new_vidbuf = new unsigned char[new_bufsize];
173 
174 	/// No interpolation, it takes the nearest pixel.
175 	for( int ix_out = 0 ; ix_out < new_bufsize ; ix_out += depth ) {
176 		int ix_in = 0.5 + ( double )ix_out * the_ratio ;
177 		switch( ix_in % depth ) {
178 			case 1 : --ix_in ; break ;
179 			case 2 : ++ix_in ; break ;
180 			default: ;
181 		}
182 
183 		if( ix_in >= bufsize ) {
184 			/// Grey value as a filler to indicate the end. For debugging.
185 			memset( new_vidbuf + ix_out, 128, new_bufsize - ix_out );
186 			break ;
187 		}
188 		for( int i = 0; i < depth ; ++i )
189 		{
190 			new_vidbuf[ ix_out + i ] = vidbuf[ ix_in + i ];
191 		}
192 	};
193 
194 	delete [] vidbuf ;
195 	vidbuf = new_vidbuf ;
196 	bufsize = new_bufsize ;
197 	height = new_height;
198 	resize_zoom( x(), y(), width, height );
199 	redraw();
200 }
201 
202 /// Change the horizontal center of the image by shifting the pixels.
203 // Beware that it is not protected by a mutex
shift_horizontal_center(int horizontal_shift)204 void picture::shift_horizontal_center(int horizontal_shift)
205 {
206 	/// This is a number of pixels.
207 	int shift = horizontal_shift * depth;
208 	if( shift < -bufsize ) {
209 		shift = -bufsize ;
210 	}
211 
212 	if( horizontal_shift > 0 ) {
213 		/// Here we lose a couple of pixels at the end of the buffer
214 		/// if there is not a line enough. It should not be a lot.
215 		int tmp, n;
216 		memmove( vidbuf + shift, vidbuf, bufsize - shift );
217 		memcpy(vidbuf, vidbuf + width*depth, shift);
218 		for (int row = 0; row < height; row ++) {
219 			for (int col = 0; col < shift; col++) {
220 				n = (row * width + col) * depth;
221 				tmp = vidbuf[n];
222 				vidbuf[n] = vidbuf[n+2];
223 				vidbuf[n+2] = tmp;
224 			}
225 		}
226 //		memset( vidbuf, background, horizontal_shift );
227 	} else {
228 //		shift *= -1;
229 		/// Here, it is not necessary to reduce the buffer's size.
230 //		memmove( vidbuf, vidbuf + shift, bufsize - shift );
231 //		memcpy( vidbuf + bufsize - shift - 1,
232 //				vidbuf + bufsize - width * depth - 1, shift);
233 //		memset( vidbuf + bufsize + horizontal_shift, background, -horizontal_shift );
234 	}
235 
236 	redraw();
237 }
238 
239 /// Shift the center by 1 rgb value
240 // not protected by a mutex
shift_center(int dir)241 void picture::shift_center(int dir)
242 {
243 	if( dir > 0 ) {
244 		memmove( vidbuf + 1, vidbuf, bufsize - 1 );
245 		vidbuf[0] = 0;
246 	} else {
247 		memmove( vidbuf, vidbuf + 1, bufsize - 1 );
248 		vidbuf[bufsize-1] = 0;
249 	}
250 	redraw();
251 }
252 
253 /// rotate rgb pixel ordering in the image to the right of
254 /// and including grp pixels from the left
255 // not protected by a mutex
rotate()256 void picture::rotate()
257 {
258 	unsigned char tmp;
259 	int n;
260 	for (int row = 0; row < height; row++) {
261 		for (int col = 0; col < width; col++) {
262 			n = (row * width + col) * depth;
263 			tmp = vidbuf[n];
264 			vidbuf[n] = vidbuf[n+1];
265 			vidbuf[n+1] = vidbuf[n+2];
266 			vidbuf[n+2] = tmp;
267 		}
268 	}
269 	redraw();
270 }
271 
set_zoom(int the_zoom)272 void picture::set_zoom( int the_zoom )
273 {
274 	zoom = the_zoom ;
275 	/// The size of the displayed bitmap is changed.
276 	resize_zoom( x(), y(), width, height );
277 }
278 
279 // in 	data 	                     user data passed to function
280 // in 	x_screen,y_screen,wid_screen position and width of scan line in image
281 // out 	buf 	                     buffer for generated image data.
282 // Must copy wid_screen pixels from scanline y_screen, starting at pixel x_screen to this buffer.
draw_cb(void * data,int x_screen,int y_screen,int wid_screen,uchar * __restrict__ buf)283 void picture::draw_cb( void *data, int x_screen, int y_screen, int wid_screen, uchar * __restrict__ buf)
284 {
285 	const picture * __restrict__ ptr_pic = ( const picture * ) data ;
286 	const int img_width = ptr_pic->width ;
287 	const unsigned char * __restrict__ in_ptr = ptr_pic->vidbuf ;
288 
289 	/// One pixel out of (zoom+1)
290 	if( ptr_pic->zoom < 0 ) {
291 		const int stride = -ptr_pic->zoom + 1 ;
292 		const int in_offset = ( img_width * y_screen + x_screen ) * stride ;
293 		int dpth_in_offset = depth * in_offset ;
294 
295 		if(ptr_pic->binary) {
296 			for( int ix_w = 0, max_w = wid_screen * depth; ix_w < max_w ; ix_w += depth ) {
297 				buf[ ix_w     ] = ptr_pic->pix2bin(in_ptr[ dpth_in_offset     ]);
298 				buf[ ix_w + 1 ] = ptr_pic->pix2bin(in_ptr[ dpth_in_offset + 1 ]);
299 				buf[ ix_w + 2 ] = ptr_pic->pix2bin(in_ptr[ dpth_in_offset + 2 ]);
300 				dpth_in_offset += depth * stride ;
301 			}
302 		}
303 		else {
304 			for( int ix_w = 0, max_w = wid_screen * depth; ix_w < max_w ; ix_w += depth ) {
305 				buf[ ix_w     ] = in_ptr[ dpth_in_offset     ];
306 				buf[ ix_w + 1 ] = in_ptr[ dpth_in_offset + 1 ];
307 				buf[ ix_w + 2 ] = in_ptr[ dpth_in_offset + 2 ];
308 				dpth_in_offset += depth * stride ;
309 			}
310 		}
311 		return ;
312 	}
313 
314 	/// Reads each input pixel (-zoom+1) times.
315 	if( ptr_pic->zoom > 0 ) {
316 		const int stride = ptr_pic->zoom + 1 ;
317 		const int in_offset = img_width * ( y_screen / stride ) + x_screen / stride ;
318 #ifndef NDEBUG
319 		if( y_screen / stride >= ptr_pic->h() )
320 		{
321 			LOG_ERROR(
322 				"Overflow2 y_screen=%d h=%d y_screen*stride=%d height=%d stride=%d\n",
323 				y_screen,
324 				ptr_pic->h(),
325 				(y_screen/stride),
326 				ptr_pic->height,
327 				stride );
328 			return ;
329 		}
330 #endif
331 		if(ptr_pic->binary) {
332                 	for( int ix_w = 0, max_w = wid_screen * depth, dpth_in_offset = depth * in_offset ; ix_w < max_w ; )
333 			{
334 				unsigned char in_dpth_in_offset_0 = ptr_pic->pix2bin(in_ptr[ dpth_in_offset     ]);
335 				unsigned char in_dpth_in_offset_1 = ptr_pic->pix2bin(in_ptr[ dpth_in_offset + 1 ]);
336 				unsigned char in_dpth_in_offset_2 = ptr_pic->pix2bin(in_ptr[ dpth_in_offset + 2 ]);
337 
338 				// Stride is less than 4 or 5.
339 				for( int j= 0; j < stride; j++, ix_w += depth )
340 				{
341 					buf[ ix_w     ] = in_dpth_in_offset_0;
342 					buf[ ix_w + 1 ] = in_dpth_in_offset_1;
343 					buf[ ix_w + 2 ] = in_dpth_in_offset_2;
344 				}
345 				dpth_in_offset += depth ;
346 			}
347 		} else {
348                 	for( int ix_w = 0, max_w = wid_screen * depth, dpth_in_offset = depth * in_offset ; ix_w < max_w ; )
349 			{
350 				unsigned char in_dpth_in_offset_0 = in_ptr[ dpth_in_offset     ];
351 				unsigned char in_dpth_in_offset_1 = in_ptr[ dpth_in_offset + 1 ];
352 				unsigned char in_dpth_in_offset_2 = in_ptr[ dpth_in_offset + 2 ];
353 
354 				// Stride is less than 4 or 5.
355 				for( int j= 0; j < stride; j++, ix_w += depth )
356 				{
357 					buf[ ix_w     ] = in_dpth_in_offset_0;
358 					buf[ ix_w + 1 ] = in_dpth_in_offset_1;
359 					buf[ ix_w + 2 ] = in_dpth_in_offset_2;
360 				}
361 				dpth_in_offset += depth ;
362 			}
363 		}
364 
365 		return ;
366 	}
367 
368 	// zoom == 0, stride=1
369 	const int in_offset = img_width * y_screen + x_screen ;
370 	if(ptr_pic->binary) {
371 		int dpth_in_offset = depth * in_offset ;
372 		for( int ix_w = 0, max_w = wid_screen * depth; ix_w < max_w ; ix_w += depth ) {
373 			buf[ ix_w     ] = ptr_pic->pix2bin(in_ptr[ dpth_in_offset     ]);
374 			buf[ ix_w + 1 ] = ptr_pic->pix2bin(in_ptr[ dpth_in_offset + 1 ]);
375 			buf[ ix_w + 2 ] = ptr_pic->pix2bin(in_ptr[ dpth_in_offset + 2 ]);
376 			dpth_in_offset += depth ;
377 		}
378 	} else {
379 		abort(); // This should never be called, see optimization in picture::draw().
380 	}
381 } // picture::draw_cb
382 
draw()383 void picture::draw()
384 {
385 	if( ( zoom == 0 ) && ( binary == false ) ) {
386 		/// No scaling, this is faster.
387 		fl_draw_image( vidbuf, x(), y(), w(), h() );
388 	} else {
389 		fl_draw_image( draw_cb, this, x(), y(), w(), h() );
390 	}
391 }
392 
slant_undo()393 void picture::slant_undo()
394 {
395 	int row, col;
396 	unsigned char temp[width * depth];
397 	if (height == 0 || width == 0 || slantdir == 0) return;
398 	if (slantdir == -1) { // undo from left
399 		for (row = 0; row < height; row++) {
400 			col = numcol * row / (height - 1);
401 			if (col > 0) {
402 				memmove(	temp,
403 							&vidbuf[(row * width + width - col) * depth],
404 							(width - col) * depth );
405 				memmove(	&vidbuf[(row * width + col)*depth],
406 							&vidbuf[row * width *depth],
407 							(width - col) * depth );
408 				memmove(	&vidbuf[row * width * depth],
409 							temp,
410 							col * depth );
411  			}
412 		}
413 	} else if (slantdir == 1) { // undo from right
414 		for (row = 0; row < height; row++) {
415 			col = numcol * row / (height - 1);
416 			if (col > 0) {
417 				memmove(	temp,
418 							&vidbuf[row * width * depth],
419 							col * depth );
420 				memmove(	&vidbuf[row * width * depth],
421 							&vidbuf[(row * width + col) * depth],
422 							(width - col) * depth );
423 				memmove(	&vidbuf[(row * width + width - col) * depth],
424 							temp,
425 							col *depth );
426 			}
427 		}
428 	}
429 	slantdir = 0;
430 	redraw();
431 }
432 
slant_corr(int x,int y)433 void picture::slant_corr(int x, int y)
434 {
435 	int row, col;
436 	unsigned char temp[width * depth];
437 	if (height == 0 || width == 0) return;
438 	if (x > width / 2) { // unwrap from right
439 		numcol = (width - x) * height / y;
440 		if (numcol > width / 2) numcol = width / 2;
441 		for (row = 0; row < height; row++) {
442 			col = numcol * row / (height - 1);
443 			if (col > 0) {
444 				memmove(	temp,
445 							&vidbuf[(row * width + width - col) * depth],
446 							(width - col) * depth );
447 				memmove(	&vidbuf[(row * width + col)*depth],
448 							&vidbuf[row * width *depth],
449 							(width - col) * depth );
450 				memmove(	&vidbuf[row * width * depth],
451 							temp,
452 							col * depth );
453  			}
454 		}
455 		slantdir = 1;
456 	} else { // unwrap from left
457 		numcol = x * height / y;
458 		if (numcol > width / 2) numcol = width / 2;
459 		for (row = 0; row < height; row++) {
460 			col = numcol * row / (height - 1);
461 			if (col > 0) {
462 				memmove(	temp,
463 							&vidbuf[row * width * depth],
464 							col * depth );
465 				memmove(	&vidbuf[row * width * depth],
466 							&vidbuf[(row * width + col) * depth],
467 							(width - col) * depth );
468 				memmove(	&vidbuf[(row * width + width - col) * depth],
469 							temp,
470 							col *depth );
471 			}
472 		}
473 		slantdir = -1;
474 	}
475 	redraw();
476 }
477 
slant(int dir)478 void picture::slant(int dir)
479 {
480 }
481 
handle(int event)482 int picture::handle(int event)
483 {
484 	if (Fl::event_inside( this )) {
485 		if (event == FL_RELEASE) {
486 			if (!slantcorr) {
487 				do_callback();
488 				return 1;
489 			}
490 			int xpos = Fl::event_x() - x();
491 			int ypos = Fl::event_y() - y();
492 			int evb = Fl::event_button();
493 			if (evb == 1)
494 				slant_corr(xpos, ypos);
495 			else if (evb == 3)
496 				slant_undo();
497 			LOG_DEBUG("#2 %d, %d", xpos, ypos);
498 			return 1;
499 		}
500 		return 1;
501 	}
502 	return 0;
503 }
504 
open_file(const char * name,const char * suffix)505 static FILE* open_file(const char* name, const char* suffix)
506 {
507 	FILE* fp;
508 
509 	size_t flen = strlen(name);
510 	if (name[flen - 1] == '/') {
511 		// if the name ends in a slash we will generate
512 		// a timestamped name in the following  format:
513 		const char t[] = "pic_YYYY-MM-DD_HHMMSSz";
514 
515 		size_t newlen = flen + sizeof(t);
516 		if (suffix)
517 			newlen += 5;
518 		char* newfn = new char[newlen];
519 		memcpy(newfn, name, flen);
520 
521 		time_t time_sec = time(0);
522 		struct tm ztime;
523 		(void)gmtime_r(&time_sec, &ztime);
524 
525 		size_t sz;
526 		if ((sz = strftime(newfn + flen, newlen - flen, "pic_%Y-%m-%d_%H%M%Sz", &ztime)) > 0) {
527 			strncpy(newfn + flen + sz, suffix, newlen - flen - sz);
528 			newfn[newlen - 1] = '\0';
529 			mkdir(name, 0777);
530 			fp = fl_fopen(newfn, "wb");
531 		}
532 		else
533 			fp = NULL;
534 		delete [] newfn;
535 	}
536 	else {
537 		fp = fl_fopen(name, "rb");
538 		if (fp) {
539 			fclose(fp);
540 			const int n = 5; // rename existing image files to keep up to 5 old versions
541 			ostringstream oldfn, newfn;
542 			std::streampos p;
543 
544 			oldfn << name << '.';
545 			newfn << name << '.';
546 			p = oldfn.tellp();
547 
548 			for (int i = n - 1; i > 0; i--) {
549 				oldfn.seekp(p);
550 				newfn.seekp(p);
551 				oldfn << i;
552 				newfn << i + 1;
553 				remove(newfn.str().c_str());
554 				rename(oldfn.str().c_str(), newfn.str().c_str());
555 			}
556 			remove(oldfn.str().c_str());
557 			rename(name, oldfn.str().c_str());
558 		}
559 		fp = fl_fopen(name, "wb");
560 	}
561 	return fp;
562 }
563 
avg_pix(const unsigned char * vidbuf)564 static inline unsigned char avg_pix( const unsigned char * vidbuf )
565 {
566 	return ( vidbuf[ 0 ] + vidbuf[ 1 ] + vidbuf[ 2 ] ) / picture::depth ;
567 }
568 
save_png(const char * filename,bool monochrome,const char * extra_comments)569 int picture::save_png(const char* filename, bool monochrome, const char *extra_comments)
570 {
571 	FILE* fp;
572 	if ((fp = open_file(filename, ".png")) == NULL)
573 		return -1;
574 
575 	// set up the png structures
576 	png_structp png;
577 	png_infop info;
578 	if ((png = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL)) == NULL) {
579 		fclose(fp);
580 		return -1;
581 	}
582 	/* png_set_compression_level() shall set the compression level to "level".
583 	 * The valid values for "level" range from [0,9], corresponding directly
584 	 * to compression levels for zlib. The value 0 implies no compression
585 	 * and 9 implies maximal compression. Note: Tests have shown that zlib
586 	 * compression levels 3-6 usually perform as well as level 9 for PNG images,
587 	 * and do considerably fewer calculations. */
588 	png_set_compression_level(png, Z_BEST_COMPRESSION);
589 
590 	if ((info = png_create_info_struct(png)) == NULL) {
591 		png_destroy_write_struct(&png, NULL);
592 		fclose(fp);
593 		return -1;
594 	}
595 	if (setjmp(png_jmpbuf(png))) {
596 		png_destroy_write_struct(&png, &info);
597 		fclose(fp);
598 		return -1;
599 	}
600 
601 	// use an stdio stream
602 	png_init_io(png, fp);
603 
604 	// set png header
605 	int color_type = monochrome ? PNG_COLOR_TYPE_GRAY : PNG_COLOR_TYPE_RGB ;
606 	/// Color images must take eight bits per pixel.
607 	const int bit_depth = ( monochrome && binary ) ? 1 : 8 ;
608 	png_set_IHDR(png, info, width, height, bit_depth,
609 		     color_type, PNG_INTERLACE_NONE,
610 		     PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
611 
612 	// write text comments
613 	struct tm tm;
614 	time_t t = time(NULL);
615 	gmtime_r(&t, &tm);
616 	char z[20 + 1];
617 	strftime(z, sizeof(z), "%Y-%m-%dT%H:%M:%SZ", &tm);
618 	z[sizeof(z) - 1] = '\0';
619 
620 	ostringstream comment;
621 	comment << "Program: " PACKAGE_STRING << '\n'
622 		<< "Received: " << z << '\n'
623 		<< "Modem: " << mode_info[active_modem->get_mode()].name << '\n'
624 		<< "Frequency: " << inpFreq->value() << '\n';
625 	if( extra_comments ) {
626 		comment << extra_comments ;
627 	}
628 	if (inpCall->size())
629 		comment << "Log call: " << inpCall->value() << '\n';
630 
631 	// set text
632 	png_text text;
633 	text.key = strdup("Comment");
634 	text.text = strdup(comment.str().c_str());
635 	text.compression = PNG_TEXT_COMPRESSION_NONE;
636 	png_set_text(png, info, &text, 1);
637 
638 	// write header
639 	png_write_info(png, info);
640 
641 	// Extra check for debugging.
642 	if( height * width * depth != bufsize ) {
643 		LOG_ERROR("Buffer inconsistency h=%d w=%d b=%d", height, width, bufsize );
644 	}
645 
646 	// write image
647 	if(monochrome)
648 	{
649 		unsigned char tmp_row[width];
650 		png_bytep row;
651 		for (int i = 0; i < height; i++) {
652 			int row_offset = i * width * depth ;
653 			if( binary )
654 			{
655 				unsigned char accumPix = 0 ;
656 				int j_offset = 0 ;
657 				for(int j = 0 ; j < width; ++j )
658 				{
659 					int col_offset = row_offset + j * depth ;
660 					unsigned char tmpChr = avg_pix( vidbuf + col_offset );
661 					tmpChr = pix2bin(tmpChr) ? 1 : 0 ;
662 					j_offset = j & 0x07 ;
663 					tmpChr = tmpChr << ( 7 - j_offset );
664 					accumPix |= tmpChr ;
665 
666 					if( j_offset == 7 ) {
667 						tmp_row[ j >> 3 ] = accumPix ;
668 						accumPix = 0 ;
669 					}
670 				}
671 				if( j_offset != 7 ) {
672 					tmp_row[ width >> 3 ] = accumPix ;
673 				}
674 			} else {
675 				for(int j = 0 ; j < width; ++j )
676 				{
677 					int col_offset = row_offset + j * depth ;
678 					tmp_row[j] = avg_pix( vidbuf + col_offset );
679 				}
680 			}
681 			row = tmp_row;
682 			png_write_rows(png, &row, 1);
683 		}
684 	}
685 	else
686 	{
687 		png_bytep row;
688 		for (int i = 0; i < height; i++) {
689 			row = &vidbuf[i * width * depth];
690 			png_write_rows(png, &row, 1);
691 		}
692 	}
693 	png_write_end(png, info);
694 
695 	// clean up
696 	free(text.key);
697 	free(text.text);
698 	png_destroy_write_struct(&png, &info);
699 
700 	fflush(fp);
701 	fsync(fileno(fp));
702 
703 	fclose(fp);
704 
705 	return 0;
706 }
707 
restore(int row,int margin)708 bool picture::restore( int row, int margin )
709 {
710 	if( ( row <= noise_height_margin ) || ( row >= height ) ) return true;
711 
712 	unsigned char * line_ante = vidbuf + (row - noise_height_margin) * width * depth;
713 	// Copy the new calculated value (at previous call) to all channels.
714 	// TODO: Do that when switching off noise removal, otherwise a couple of colored pixels are left.
715 	for( int col = margin ; col < width - margin; ++col )
716 	{
717 		int offset = col * depth ;
718 		line_ante[ offset ] = line_ante[ offset + 2 ] = line_ante[ offset + 1 ];
719 	}
720 	return false ;
721 }
722 
erosion(int row)723 void picture::erosion( int row )
724 {
725 	static const size_t margin_one = 1 ;
726 	if( restore( row, margin_one ) ) return ;
727 
728 	const unsigned char * line_prev = vidbuf + (row - noise_height_margin + 1) * width * depth;
729 	      unsigned char * line_curr = vidbuf + (row - noise_height_margin + 2) * width * depth;
730 	const unsigned char * line_next = vidbuf + (row - noise_height_margin + 3) * width * depth;
731 
732 	for( size_t col = margin_one ; col < width - margin_one; ++col )
733 	{
734 		unsigned char new_pix = 255 ;
735 		new_pix = std::min( new_pix, line_prev[ depth * ( col - 1 ) ] );
736 		new_pix = std::min( new_pix, line_prev[ depth * ( col     ) ] );
737 		new_pix = std::min( new_pix, line_prev[ depth * ( col + 1 ) ] );
738 		new_pix = std::min( new_pix, line_curr[ depth * ( col - 1 ) ] );
739 		new_pix = std::min( new_pix, line_curr[ depth * ( col     ) ] );
740 		new_pix = std::min( new_pix, line_curr[ depth * ( col + 1 ) ] );
741 		new_pix = std::min( new_pix, line_next[ depth * ( col - 1 ) ] );
742 		new_pix = std::min( new_pix, line_next[ depth * ( col     ) ] );
743 		new_pix = std::min( new_pix, line_next[ depth * ( col + 1 ) ] );
744 
745 		/// Use this channel as a buffer. Beware that if we change the slant,
746 		// this component might not be restored
747 		// because the line position changed. Not a big problem.
748 		// We might forbid slanting when de-noising.
749 		line_curr[ col * depth + 1 ] = new_pix;
750 	}
751 }
752 
dilatation(int row)753 void picture::dilatation( int row )
754 {
755 	static const size_t margin_one = 1 ;
756 	if( restore( row, margin_one ) ) return ;
757 
758 	const unsigned char * line_prev = vidbuf + (row - noise_height_margin + 1) * width * depth;
759 	      unsigned char * line_curr = vidbuf + (row - noise_height_margin + 2) * width * depth;
760 	const unsigned char * line_next = vidbuf + (row - noise_height_margin + 3) * width * depth;
761 
762 	for( size_t col = margin_one ; col < width - margin_one; ++col )
763 	{
764 		unsigned char new_pix = 0 ;
765 		new_pix = std::max( new_pix, line_prev[ depth * ( col - 1 ) ] );
766 		new_pix = std::max( new_pix, line_prev[ depth * ( col     ) ] );
767 		new_pix = std::max( new_pix, line_prev[ depth * ( col + 1 ) ] );
768 		new_pix = std::max( new_pix, line_curr[ depth * ( col - 1 ) ] );
769 		new_pix = std::max( new_pix, line_curr[ depth * ( col     ) ] );
770 		new_pix = std::max( new_pix, line_curr[ depth * ( col + 1 ) ] );
771 		new_pix = std::max( new_pix, line_next[ depth * ( col - 1 ) ] );
772 		new_pix = std::max( new_pix, line_next[ depth * ( col     ) ] );
773 		new_pix = std::max( new_pix, line_next[ depth * ( col + 1 ) ] );
774 
775 		/// Use this channel as a buffer. Beware that if we change the slant,
776 		// this component might not be restored
777 		// because the line position changed. Not a big problem.
778 		// We might forbid slanting when de-noising.
779 		line_curr[ col * depth + 1 ] = new_pix;
780 	}
781 }
782 
remove_noise(int row,int half_len,int noise_margin)783 void picture::remove_noise( int row, int half_len, int noise_margin )
784 {
785 	if( restore( row, half_len ) ) return ;
786 
787 	const unsigned char * line_prev = vidbuf + (row - noise_height_margin + 1) * width * depth;
788 	      unsigned char * line_curr = vidbuf + (row - noise_height_margin + 2) * width * depth;
789 	const unsigned char * line_next = vidbuf + (row - noise_height_margin + 3) * width * depth;
790 
791 	const int nb_neighbours = ( 2 * ( 2 * half_len + 1 ) );
792 	int medians[nb_neighbours];
793 
794 	/// Takes into account the first component only.
795 	for( int col = half_len ; col < width - half_len; ++col )
796 	{
797 		int curr_pix = line_curr[ col * depth ];
798 		assert( ( curr_pix >= 0 ) && ( curr_pix <= 255 ) );
799 
800 		int pix_min = 255, pix_max = 0;
801 		for( int subcol = col - half_len, tmp, nghb_i = 0 ; subcol <= col + half_len ; ++subcol )
802 		{
803 			int offset = subcol * depth ;
804 			tmp = line_prev[ offset ];
805 			if( tmp < pix_min ) pix_min = tmp ;
806 			else if( tmp > pix_max ) pix_max = tmp ;
807 			medians[nghb_i++] = tmp;
808 
809 			tmp = line_next[ offset ];
810 			if( tmp < pix_min ) pix_min = tmp ;
811 			else if( tmp > pix_max ) pix_max = tmp ;
812 			medians[nghb_i++] = tmp;
813 		}
814 
815 		// Maybe the pixel is between min and max.
816 		int thres_min = pix_min - noise_margin;
817 		if(thres_min < 0) thres_min = 0;
818 		assert(thres_min <= pix_min);
819 
820 		int thres_max = pix_max + noise_margin;
821 		if(thres_max > 255 ) thres_max = 255;
822 		if(thres_max < pix_max) abort();
823 
824 		if( ( curr_pix >= thres_min ) && ( curr_pix <= thres_max ) ) continue ;
825 		assert( ( pix_max >= 0 ) && ( pix_max <= 255 ) );
826 
827 		std::sort( medians, medians + nb_neighbours );
828 		int new_pix = medians[ nb_neighbours / 2 ];
829 		assert( new_pix >= 0 );
830 
831 		/// Use this channel as a buffer. Beware that if we change the slant,
832 		// this component might not be restored
833 		// because the line position changed. Not a big problem.
834 		// We might forbid slanting when de-noising.
835 		line_curr[ col * depth + 1 ] = new_pix;
836 	}
837 }
838 
handle(int event)839 int picbox::handle(int event)
840 {
841 	if (!Fl::event_inside(this))
842 		return 0;
843 
844 	switch (event) {
845 	case FL_DND_ENTER: case FL_DND_LEAVE:
846 	case FL_DND_DRAG: case FL_DND_RELEASE:
847 		return 1;
848 	case FL_PASTE:
849 		break;
850 	default:
851 		return Fl_Box::handle(event);
852 	}
853 
854 	// handle FL_PASTE
855 	string text = Fl::event_text();
856 // from dnd event "file:///home/dave/Photos/dave.jpeg"
857 	string::size_type p;
858 	if ((p = text.find("file://")) != string::npos)
859 		text.erase(0, p + strlen("file://"));
860 	if ((p = text.find('\r')) != string::npos)
861 		text.erase(p);
862 	if ((p = text.find('\n')) != string::npos)
863 		text.erase(p);
864 
865 	struct stat st;
866 	if (stat(text.c_str(), &st) == -1 || !S_ISREG(st.st_mode))
867 		return 0;
868 	extern void load_image(const char*);
869 	load_image(text.c_str());
870 
871 	return 1;
872 }
873