xref: /haiku/src/add-ons/translators/ppm/PPMTranslator.cpp (revision 1294543de9ac0eff000eaea1b18368c36435d08e)
1 /*
2 	Copyright 1999, Be Incorporated.   All Rights Reserved.
3 	This file may be used under the terms of the Be Sample Code License.
4 */
5 
6 /*	Parse the ASCII and raw versions of PPM.	*/
7 /*	Note that the parsing of ASCII is very inefficient, because BFile	*/
8 /*	does not buffer data. We should wrap a buffering thing around	*/
9 /*	the input or output when they are in ASCII mode.				*/
10 
11 #include <TranslatorAddOn.h>
12 #include <TranslationKit.h>
13 #include <ByteOrder.h>
14 #include <Message.h>
15 #include <Screen.h>
16 #include <Locker.h>
17 #include <FindDirectory.h>
18 #include <Path.h>
19 #include <PopUpMenu.h>
20 #include <MenuField.h>
21 #include <MenuItem.h>
22 #include <CheckBox.h>
23 #include <Bitmap.h>
24 #include <StringView.h>
25 #include <GridLayoutBuilder.h>
26 #include <GroupLayout.h>
27 #include <GroupLayoutBuilder.h>
28 #include <SpaceLayoutItem.h>
29 
30 #include <ctype.h>
31 #include <string.h>
32 #include <stdlib.h>
33 #include <stdio.h>
34 
35 #include "colorspace.h"
36 
37 
38 #if DEBUG
39  #define dprintf(x) printf x
40 #else
41  #define dprintf(x)
42 #endif
43 
44 
45 #if !defined(_PR3_COMPATIBLE_)	/* R4 headers? Else we need to define these constants. */
46  #define B_CMY24 ((color_space)0xC001) /* C[7:0]  M[7:0]  Y[7:0]                       No gray removal done            */
47  #define B_CMY32 ((color_space)0xC002) /* C[7:0]  M[7:0]  Y[7:0]  X[7:0]       No gray removal done            */
48  #define B_CMYA32 ((color_space)0xE002) /* C[7:0]  M[7:0]  Y[7:0]  A[7:0]       No gray removal done            */
49  #define B_CMYK32 ((color_space)0xC003) /* C[7:0]  M[7:0]  Y[7:0]  K[7:0]                                       */
50 #endif
51 
52 #define PPM_TRANSLATOR_VERSION 0x100
53 
54 /* These three data items are exported by every translator. */
55 char translatorName[] = "PPM images";
56 char translatorInfo[] = "PPM image translator v1.0.0, " __DATE__;
57 int32 translatorVersion = PPM_TRANSLATOR_VERSION;
58 	// Revision: lowest 4 bits
59 	// Minor: next 4 bits
60 	// Major: highest 24 bits
61 
62 /*	Be reserves all codes with non-lowecase letters in them.	*/
63 /*	Luckily, there is already a reserved code for PPM. If you	*/
64 /*	make up your own for a new type, use lower-case letters.	*/
65 #define PPM_TYPE 'PPM '
66 
67 
68 /* These two data arrays are a really good idea to export from Translators, but not required. */
69 translation_format inputFormats[] = {
70 	{	B_TRANSLATOR_BITMAP, B_TRANSLATOR_BITMAP, 0.4, 0.8, "image/x-be-bitmap", "Be Bitmap Format (PPMTranslator)" },
71 	{	PPM_TYPE, B_TRANSLATOR_BITMAP, 0.3, 0.8, "image/x-portable-pixmap", "PPM image" },
72 	{	0, 0, 0, 0, "\0", "\0" }
73 };		/*	optional (else Identify is always called)	*/
74 
75 translation_format outputFormats[] = {
76 	{	B_TRANSLATOR_BITMAP, B_TRANSLATOR_BITMAP, 0.4, 0.8, "image/x-be-bitmap", "Be Bitmap Format (PPMTranslator)" },
77 	{	PPM_TYPE, B_TRANSLATOR_BITMAP, 0.3, 0.8, "image/x-portable-pixmap", "PPM image" },
78 	{	0, 0, 0, 0, "\0", "\0" }
79 };	/*	optional (else Translate is called anyway)	*/
80 
81 /*	Translators that don't export outputFormats 	*/
82 /*	will not be considered by files looking for 	*/
83 /*	specific output formats.	*/
84 
85 
86 /*	We keep our settings in a global struct, and wrap a lock around them.	*/
87 struct ppm_settings {
88 	color_space		out_space;
89 	BPoint				window_pos;
90 	bool				write_ascii;
91 	bool				settings_touched;
92 };
93 BLocker g_settings_lock("PPM settings lock");
94 ppm_settings g_settings;
95 
96 BPoint get_window_origin();
97 void set_window_origin(BPoint pos);
98 BPoint get_window_origin()
99 {
100 	BPoint ret;
101 	g_settings_lock.Lock();
102 	ret = g_settings.window_pos;
103 	g_settings_lock.Unlock();
104 	return ret;
105 }
106 
107 void set_window_origin(BPoint pos)
108 {
109 	g_settings_lock.Lock();
110 	g_settings.window_pos = pos;
111 	g_settings.settings_touched = true;
112 	g_settings_lock.Unlock();
113 }
114 
115 
116 class PrefsLoader {
117 public:
118 		PrefsLoader(
119 				const char * str)
120 			{
121 				dprintf(("PPMTranslator: PrefsLoader()\n"));
122 				/* defaults */
123 				g_settings.out_space = B_NO_COLOR_SPACE;
124 				g_settings.window_pos = B_ORIGIN;
125 				g_settings.write_ascii = false;
126 				g_settings.settings_touched = false;
127 				BPath path;
128 				if (find_directory(B_USER_SETTINGS_DIRECTORY, &path)) {
129 					path.SetTo("/tmp");
130 				}
131 				path.Append(str);
132 				FILE * f = fopen(path.Path(), "r");
133 				/*	parse text settings file -- this should be a library...	*/
134 				if (f) {
135 					char line[1024];
136 					char name[32];
137 					char * ptr;
138 					while (true) {
139 						line[0] = 0;
140 						fgets(line, 1024, f);
141 						if (!line[0]) {
142 							break;
143 						}
144 						/* remember: line ends with \n, so printf()s don't have to */
145 						ptr = line;
146 						while (isspace(*ptr)) {
147 							ptr++;
148 						}
149 						if (*ptr == '#' || !*ptr) {	/* comment or blank */
150 							continue;
151 						}
152 						if (sscanf(ptr, "%31[a-zA-Z_0-9] =", name) != 1) {
153 							fprintf(stderr, "unknown PPMTranslator settings line: %s", line);
154 						}
155 						else {
156 							if (!strcmp(name, "color_space")) {
157 								while (*ptr != '=') {
158 									ptr++;
159 								}
160 								ptr++;
161 								if (sscanf(ptr, "%d", (int*)&g_settings.out_space) != 1) {
162 									fprintf(stderr, "illegal color space in PPMTranslator settings: %s", ptr);
163 								}
164 							}
165 							else if (!strcmp(name, "window_pos")) {
166 								while (*ptr != '=') {
167 									ptr++;
168 								}
169 								ptr++;
170 								if (sscanf(ptr, "%f,%f", &g_settings.window_pos.x, &g_settings.window_pos.y) != 2) {
171 									fprintf(stderr, "illegal window position in PPMTranslator settings: %s", ptr);
172 								}
173 							}
174 							else if (!strcmp(name, "write_ascii")) {
175 								while (*ptr != '=') {
176 									ptr++;
177 								}
178 								ptr++;
179 								int ascii = g_settings.write_ascii;
180 								if (sscanf(ptr, "%d", &ascii) != 1) {
181 									fprintf(stderr, "illegal write_ascii value in PPMTranslator settings: %s", ptr);
182 								}
183 								else {
184 									g_settings.write_ascii = ascii;
185 								}
186 							}
187 							else {
188 								fprintf(stderr, "unknown PPMTranslator setting: %s", line);
189 							}
190 						}
191 					}
192 					fclose(f);
193 				}
194 			}
195 		~PrefsLoader()
196 			{
197 				/*	No need writing settings if there aren't any	*/
198 				if (g_settings.settings_touched) {
199 					BPath path;
200 					if (find_directory(B_USER_SETTINGS_DIRECTORY, &path)) {
201 						path.SetTo("/tmp");
202 					}
203 					path.Append("PPMTranslator_Settings");
204 					FILE * f = fopen(path.Path(), "w");
205 					if (f) {
206 						fprintf(f, "# PPMTranslator settings version %d.%d.%d\n",
207 							static_cast<int>(translatorVersion >> 8),
208 							static_cast<int>((translatorVersion >> 4) & 0xf),
209 							static_cast<int>(translatorVersion & 0xf));
210 						fprintf(f, "color_space = %d\n", g_settings.out_space);
211 						fprintf(f, "window_pos = %g,%g\n", g_settings.window_pos.x, g_settings.window_pos.y);
212 						fprintf(f, "write_ascii = %d\n", g_settings.write_ascii ? 1 : 0);
213 						fclose(f);
214 					}
215 				}
216 			}
217 };
218 
219 PrefsLoader g_prefs_loader("PPMTranslator_Settings");
220 
221 /*	Some prototypes for functions we use.	*/
222 status_t read_ppm_header(BDataIO * io, int * width, int * rowbytes, int * height,
223 	int * max, bool * ascii, color_space * space, bool * is_ppm, char ** comment);
224 status_t read_bits_header(BDataIO * io, int skipped, int * width, int * rowbytes,
225 	int * height, int * max, bool * ascii, color_space * space);
226 status_t write_comment(const char * str, BDataIO * io);
227 status_t copy_data(BDataIO * in, BDataIO * out, int rowbytes, int out_rowbytes,
228 	int height, int max, bool in_ascii, bool out_ascii, color_space in_space,
229 	color_space out_space);
230 
231 	/*	Return B_NO_TRANSLATOR if not handling this data.	*/
232 	/*	Even if inputFormats exists, may be called for data without hints	*/
233 	/*	If outType is not 0, must be able to export in wanted format	*/
234 
235 status_t
236 Identify(	/*	required	*/
237 	BPositionIO * inSource,
238 	const translation_format * inFormat,	/*	can beNULL	*/
239 	BMessage * ioExtension,	/*	can be NULL	*/
240 	translator_info * outInfo,
241 	uint32 outType)
242 {
243 	dprintf(("PPMTranslator: Identify()\n"));
244 	/* Silence compiler warnings. */
245 	inFormat = inFormat;
246 	ioExtension = ioExtension;
247 
248 	/* Check that requested format is something we can deal with. */
249 	if (outType == 0) {
250 		outType = B_TRANSLATOR_BITMAP;
251 	}
252 	if (outType != B_TRANSLATOR_BITMAP && outType != PPM_TYPE) {
253 		return B_NO_TRANSLATOR;
254 	}
255 
256 	/* Check header. */
257 	int width, rowbytes, height, max;
258 	bool ascii, is_ppm;
259 	color_space space;
260 	status_t err = read_ppm_header(inSource, &width, &rowbytes, &height, &max, &ascii, &space, &is_ppm, NULL);
261 	if (err != B_OK) {
262 		return err;
263 	}
264 	/* Stuff info into info struct -- Translation Kit will do "translator" for us. */
265 	outInfo->group = B_TRANSLATOR_BITMAP;
266 	if (is_ppm) {
267 		outInfo->type = PPM_TYPE;
268 		outInfo->quality = 0.3;		/* no alpha, etc */
269 		outInfo->capability = 0.8;	/* we're pretty good at PPM reading, though */
270 		strcpy(outInfo->name, "PPM image");
271 		strcpy(outInfo->MIME, "image/x-portable-pixmap");
272 	}
273 	else {
274 		outInfo->type = B_TRANSLATOR_BITMAP;
275 		outInfo->quality = 0.4;		/* B_TRANSLATOR_BITMAP can do alpha, at least */
276 		outInfo->capability = 0.8;	/* and we might not know many variations thereof */
277 		strcpy(outInfo->name, "Be Bitmap Format (PPMTranslator)");
278 		strcpy(outInfo->MIME, "image/x-be-bitmap");	/* this is the MIME type of B_TRANSLATOR_BITMAP */
279 	}
280 	return B_OK;
281 }
282 
283 
284 	/*	Return B_NO_TRANSLATOR if not handling the output format	*/
285 	/*	If outputFormats exists, will only be called for those formats	*/
286 
287 status_t
288 Translate(	/*	required	*/
289 	BPositionIO * inSource,
290 	const translator_info * /* inInfo*/ ,	/* silence compiler warning */
291 	BMessage * ioExtension,	/*	can be NULL	*/
292 	uint32 outType,
293 	BPositionIO * outDestination)
294 {
295 	dprintf(("PPMTranslator: Translate()\n"));
296 	inSource->Seek(0, SEEK_SET);	/* paranoia */
297 //	inInfo = inInfo;	/* silence compiler warning */
298 	/* Check what we're being asked to produce. */
299 	if (!outType) {
300 		outType = B_TRANSLATOR_BITMAP;
301 	}
302 	dprintf(("PPMTranslator: outType is '%c%c%c%c'\n", char(outType>>24), char(outType>>16), char(outType>>8), char(outType)));
303 	if (outType != B_TRANSLATOR_BITMAP && outType != PPM_TYPE) {
304 		return B_NO_TRANSLATOR;
305 	}
306 
307 	/* Figure out what we've been given (again). */
308 	int width, rowbytes, height, max;
309 	bool ascii, is_ppm;
310 	color_space space;
311 	/* Read_ppm_header() will always return with stream at beginning of data */
312 	/* for both B_TRANSLATOR_BITMAP and PPM_TYPE, and indicate what it is. */
313 	char * comment = NULL;
314 	status_t err = read_ppm_header(inSource, &width, &rowbytes, &height, &max, &ascii, &space, &is_ppm, &comment);
315 	if (comment != NULL) {
316 		if (ioExtension != NULL) {
317 #if defined(_PR3_COMPATIBLE_)	/* R4 headers? */
318 			ioExtension->AddString(B_TRANSLATOR_EXT_COMMENT, comment);
319 #else
320 			ioExtension->AddString("/comment", comment);
321 #endif
322 		}
323 		free(comment);
324 	}
325 	if (err < B_OK) {
326 		dprintf(("read_ppm_header() error %s [%lx]\n", strerror(err), err));
327 		return err;
328 	}
329 	/* Check if we're configured to write ASCII format file. */
330 	bool out_ascii = false;
331 	if (outType == PPM_TYPE) {
332 		out_ascii = g_settings.write_ascii;
333 		if (ioExtension != NULL) {
334 			ioExtension->FindBool("ppm /ascii", &out_ascii);
335 		}
336 	}
337 	err = B_OK;
338 	/* Figure out which color space to convert to */
339 	color_space out_space;
340 	int out_rowbytes;
341 	g_settings_lock.Lock();
342 	if (outType == PPM_TYPE) {
343 		out_space = B_RGB24_BIG;
344 		out_rowbytes = 3*width;
345 	}
346 	else {	/*	When outputting to B_TRANSLATOR_BITMAP, follow user's wishes.	*/
347 #if defined(_PR3_COMPATIBLE_)	/* R4 headers? */
348 		if (!ioExtension || ioExtension->FindInt32(B_TRANSLATOR_EXT_BITMAP_COLOR_SPACE, (int32*)&out_space) ||
349 #else
350 		if (!ioExtension || ioExtension->FindInt32("bits/space", (int32*)&out_space) ||
351 #endif
352 			(out_space == B_NO_COLOR_SPACE)) {
353 			if (g_settings.out_space == B_NO_COLOR_SPACE) {
354 				switch (space) {	/*	The 24-bit versions are pretty silly, use 32 instead.	*/
355 				case B_RGB24:
356 				case B_RGB24_BIG:
357 					out_space = B_RGB32;
358 					break;
359 				default:
360 					/* use whatever is there */
361 					out_space = space;
362 					break;
363 				}
364 			}
365 			else {
366 				out_space = g_settings.out_space;
367 			}
368 		}
369 		out_rowbytes = calc_rowbytes(out_space, width);
370 	}
371 	g_settings_lock.Unlock();
372 	/* Write file header */
373 	if (outType == PPM_TYPE) {
374 		dprintf(("PPMTranslator: write PPM\n"));
375 		/* begin PPM header */
376 		const char * magic;
377 		if (out_ascii)
378 			magic = "P3\n";
379 		else
380 			magic = "P6\n";
381 		err = outDestination->Write(magic, strlen(magic));
382 		if (err == (long)strlen(magic)) err = 0;
383 		//comment = NULL;
384 		const char* fsComment;
385 #if defined(_PR3_COMPATIBLE_)	/* R4 headers? */
386 		if ((ioExtension != NULL) && !ioExtension->FindString(B_TRANSLATOR_EXT_COMMENT, &fsComment)) {
387 #else
388 		if ((ioExtension != NULL) && !ioExtension->FindString("/comment", &fsComment)) {
389 #endif
390 			err = write_comment(fsComment, outDestination);
391 		}
392 		if (err == B_OK) {
393 			char data[40];
394 			sprintf(data, "%d %d %d\n", width, height, max);
395 			err = outDestination->Write(data, strlen(data));
396 			if (err == (long)strlen(data)) err = 0;
397 		}
398 		/* header done */
399 	}
400 	else {
401 		dprintf(("PPMTranslator: write B_TRANSLATOR_BITMAP\n"));
402 		/* B_TRANSLATOR_BITMAP header */
403 		TranslatorBitmap hdr;
404 		hdr.magic = B_HOST_TO_BENDIAN_INT32(B_TRANSLATOR_BITMAP);
405 		hdr.bounds.left = B_HOST_TO_BENDIAN_FLOAT(0);
406 		hdr.bounds.top = B_HOST_TO_BENDIAN_FLOAT(0);
407 		hdr.bounds.right = B_HOST_TO_BENDIAN_FLOAT(width-1);
408 		hdr.bounds.bottom = B_HOST_TO_BENDIAN_FLOAT(height-1);
409 		hdr.rowBytes = B_HOST_TO_BENDIAN_INT32(out_rowbytes);
410 		hdr.colors = (color_space)B_HOST_TO_BENDIAN_INT32(out_space);
411 		hdr.dataSize = B_HOST_TO_BENDIAN_INT32(out_rowbytes*height);
412 		dprintf(("rowBytes is %d, width %d, out_space %x, space %x\n", out_rowbytes, width, out_space, space));
413 		err = outDestination->Write(&hdr, sizeof(hdr));
414 		dprintf(("PPMTranslator: Write() returns %lx\n", err));
415 #if DEBUG
416 		{
417 			BBitmap * map = new BBitmap(BRect(0,0,width-1,height-1), out_space);
418 			printf("map rb = %ld\n", map->BytesPerRow());
419 			delete map;
420 		}
421 #endif
422 		if (err == sizeof(hdr)) err = 0;
423 		/* header done */
424 	}
425 	if (err != B_OK) {
426 		return err > 0 ? B_IO_ERROR : err;
427 	}
428 	/* Write data. Luckily, PPM and B_TRANSLATOR_BITMAP both scan from left to right, top to bottom. */
429 	return copy_data(inSource, outDestination, rowbytes, out_rowbytes, height, max, ascii, out_ascii, space, out_space);
430 }
431 
432 
433 class PPMView :
434 	public BView
435 {
436 public:
437 		PPMView(
438 				const char * name,
439 				uint32 flags) :
440 			BView(name, flags)
441 			{
442 				SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR));
443 				SetLowColor(ViewColor());
444 
445 				mTitle = new BStringView("title", "PPM Image Translator");
446 				mTitle->SetFont(be_bold_font);
447 
448 				char detail[100];
449 				int ver = static_cast<int>(translatorVersion);
450 				sprintf(detail, "Version %d.%d.%d %s", ver >> 8, ((ver >> 4) & 0xf),
451 					(ver & 0xf), __DATE__);
452 				mDetail = new BStringView("detail", detail);
453 
454 				mBasedOn = new BStringView("basedOn",
455 					"Based on PPMTranslator sample code");
456 
457 				mCopyright = new BStringView("copyright",
458 					"Sample code copyright 1999, Be Incorporated");
459 
460 				mMenu = new BPopUpMenu("Color Space");
461 				mMenu->AddItem(new BMenuItem("None", CSMessage(B_NO_COLOR_SPACE)));
462 				mMenu->AddItem(new BMenuItem("RGB 8:8:8 32 bits", CSMessage(B_RGB32)));
463 				mMenu->AddItem(new BMenuItem("RGBA 8:8:8:8 32 bits", CSMessage(B_RGBA32)));
464 				mMenu->AddItem(new BMenuItem("RGB 5:5:5 16 bits", CSMessage(B_RGB15)));
465 				mMenu->AddItem(new BMenuItem("RGBA 5:5:5:1 16 bits", CSMessage(B_RGBA15)));
466 				mMenu->AddItem(new BMenuItem("RGB 5:6:5 16 bits", CSMessage(B_RGB16)));
467 				mMenu->AddItem(new BMenuItem("System palette 8 bits", CSMessage(B_CMAP8)));
468 				mMenu->AddSeparatorItem();
469 				mMenu->AddItem(new BMenuItem("Grayscale 8 bits", CSMessage(B_GRAY8)));
470 				mMenu->AddItem(new BMenuItem("Bitmap 1 bit", CSMessage(B_GRAY1)));
471 				mMenu->AddItem(new BMenuItem("CMY 8:8:8 32 bits", CSMessage(B_CMY32)));
472 				mMenu->AddItem(new BMenuItem("CMYA 8:8:8:8 32 bits", CSMessage(B_CMYA32)));
473 				mMenu->AddItem(new BMenuItem("CMYK 8:8:8:8 32 bits", CSMessage(B_CMYK32)));
474 				mMenu->AddSeparatorItem();
475 				mMenu->AddItem(new BMenuItem("RGB 8:8:8 32 bits big-endian", CSMessage(B_RGB32_BIG)));
476 				mMenu->AddItem(new BMenuItem("RGBA 8:8:8:8 32 bits big-endian", CSMessage(B_RGBA32_BIG)));
477 				mMenu->AddItem(new BMenuItem("RGB 5:5:5 16 bits big-endian", CSMessage(B_RGB15_BIG)));
478 				mMenu->AddItem(new BMenuItem("RGBA 5:5:5:1 16 bits big-endian", CSMessage(B_RGBA15_BIG)));
479 				mMenu->AddItem(new BMenuItem("RGB 5:6:5 16 bits big-endian", CSMessage(B_RGB16)));
480  				mField = new BMenuField("Input Color Space", mMenu, NULL);
481  				mField->SetViewColor(ViewColor());
482  				SelectColorSpace(g_settings.out_space);
483  				BMessage * msg = new BMessage(CHANGE_ASCII);
484  				mAscii = new BCheckBox("Write ASCII", msg);
485  				if (g_settings.write_ascii)
486  					mAscii->SetValue(1);
487  				mAscii->SetViewColor(ViewColor());
488 
489  				// Build the layout
490  				SetLayout(new BGroupLayout(B_HORIZONTAL));
491 
492  				AddChild(BGroupLayoutBuilder(B_VERTICAL, 7)
493  					.Add(mTitle)
494  					.Add(mDetail)
495  					.AddGlue()
496  					.Add(mBasedOn)
497  					.Add(mCopyright)
498  					.AddGlue()
499  					.Add(BGridLayoutBuilder(10, 10)
500  						.Add(mField->CreateLabelLayoutItem(), 0, 0)
501  						.Add(mField->CreateMenuBarLayoutItem(), 1, 0)
502  						.Add(mAscii, 0, 1)
503  					)
504  					.AddGlue()
505  					.SetInsets(5, 5, 5, 5)
506  				);
507 
508  				BFont font;
509  				GetFont(&font);
510  				SetExplicitPreferredSize(BSize((font.Size() * 350)/12, (font.Size() * 200)/12));
511 			}
512 		~PPMView()
513 			{
514 				/* nothing here */
515 			}
516 
517 		enum {
518 			SET_COLOR_SPACE = 'ppm=',
519 			CHANGE_ASCII
520 		};
521 
522 virtual	void MessageReceived(
523 				BMessage * message)
524 			{
525 				if (message->what == SET_COLOR_SPACE) {
526 					SetSettings(message);
527 				}
528 				else if (message->what == CHANGE_ASCII) {
529 					BMessage msg;
530 					msg.AddBool("ppm /ascii", mAscii->Value());
531 					SetSettings(&msg);
532 				}
533 				else {
534 					BView::MessageReceived(message);
535 				}
536 			}
537 virtual	void AllAttached()
538 			{
539 				BView::AllAttached();
540 				BMessenger msgr(this);
541 				/*	Tell all menu items we're the man.	*/
542 				for (int ix=0; ix<mMenu->CountItems(); ix++) {
543 					BMenuItem * i = mMenu->ItemAt(ix);
544 					if (i) {
545 						i->SetTarget(msgr);
546 					}
547 				}
548 				mAscii->SetTarget(msgr);
549 			}
550 
551 		void SetSettings(
552 				BMessage * message)
553 			{
554 				g_settings_lock.Lock();
555 				color_space space;
556 				if (!message->FindInt32("space", (int32*)&space)) {
557 					g_settings.out_space = space;
558 					SelectColorSpace(space);
559 					g_settings.settings_touched = true;
560 				}
561 				bool ascii;
562 				if (!message->FindBool("ppm /ascii", &ascii)) {
563 					g_settings.write_ascii = ascii;
564 					g_settings.settings_touched = true;
565 				}
566 				g_settings_lock.Unlock();
567 			}
568 
569 private:
570 		BStringView * mTitle;
571 		BStringView * mDetail;
572 		BStringView * mBasedOn;
573 		BStringView * mCopyright;
574 		BPopUpMenu * mMenu;
575 		BMenuField * mField;
576 		BCheckBox * mAscii;
577 
578 		BMessage * CSMessage(
579 				color_space space)
580 			{
581 				BMessage * ret = new BMessage(SET_COLOR_SPACE);
582 				ret->AddInt32("space", space);
583 				return ret;
584 			}
585 
586 		void SelectColorSpace(
587 				color_space space)
588 			{
589 				for (int ix=0; ix<mMenu->CountItems(); ix++) {
590 					int32 s;
591 					BMenuItem * i = mMenu->ItemAt(ix);
592 					if (i) {
593 						BMessage * m = i->Message();
594 						if (m && !m->FindInt32("space", &s) && (s == space)) {
595 							mMenu->Superitem()->SetLabel(i->Label());
596 							break;
597 						}
598 					}
599 				}
600 			}
601 };
602 
603 
604 	/*	The view will get resized to what the parent thinks is 	*/
605 	/*	reasonable. However, it will still receive MouseDowns etc.	*/
606 	/*	Your view should change settings in the translator immediately, 	*/
607 	/*	taking care not to change parameters for a translation that is 	*/
608 	/*	currently running. Typically, you'll have a global struct for 	*/
609 	/*	settings that is atomically copied into the translator function 	*/
610 	/*	as a local when translation starts.	*/
611 	/*	Store your settings wherever you feel like it.	*/
612 
613 status_t
614 MakeConfig(	/*	optional	*/
615 	BMessage * ioExtension,	/*	can be NULL	*/
616 	BView * * outView,
617 	BRect * outExtent)
618 {
619 	PPMView * v = new PPMView("PPMTranslator Settings", B_WILL_DRAW);
620 	*outView = v;
621 	v->ResizeTo(v->ExplicitPreferredSize());;
622 	*outExtent = v->Bounds();
623 	if (ioExtension) {
624 		v->SetSettings(ioExtension);
625 	}
626 	return B_OK;
627 }
628 
629 
630 	/*	Copy your current settings to a BMessage that may be passed 	*/
631 	/*	to BTranslators::Translate at some later time when the user wants to 	*/
632 	/*	use whatever settings you're using right now.	*/
633 
634 status_t
635 GetConfigMessage(	/*	optional	*/
636 	BMessage * ioExtension)
637 {
638 	status_t err = B_OK;
639 #if defined(_PR3_COMPATIBLE_)
640 	const char * name = B_TRANSLATOR_EXT_BITMAP_COLOR_SPACE;
641 #else
642 	const char * name = "bits/space";
643 #endif
644 	g_settings_lock.Lock();
645 	(void)ioExtension->RemoveName(name);
646 	err = ioExtension->AddInt32(name, g_settings.out_space);
647 	g_settings_lock.Unlock();
648 	return err;
649 }
650 
651 
652 
653 
654 
655 status_t
656 read_ppm_header(
657 	BDataIO * inSource,
658 	int * width,
659 	int * rowbytes,
660 	int * height,
661 	int * max,
662 	bool * ascii,
663 	color_space * space,
664 	bool * is_ppm,
665 	char ** comment)
666 {
667 	/* check for PPM magic number */
668 	char ch[2];
669 	if (inSource->Read(ch, 2) != 2) {
670 		return B_NO_TRANSLATOR;
671 	}
672 	/* look for magic number */
673 	if (ch[0] != 'P') {
674 		/* B_TRANSLATOR_BITMAP magic? */
675 		if (ch[0] == 'b' && ch[1] == 'i') {
676 			*is_ppm = false;
677 			return read_bits_header(inSource, 2, width, rowbytes, height, max, ascii, space);
678 		}
679 		return B_NO_TRANSLATOR;
680 	}
681 	*is_ppm = true;
682 	if (ch[1] == '6') {
683 		*ascii = false;
684 	}
685 	else if (ch[1] == '3') {
686 		*ascii = true;
687 	}
688 	else {
689 		return B_NO_TRANSLATOR;
690 	}
691 	// status_t err = B_NO_TRANSLATOR;
692 	enum scan_state {
693 		scan_width,
694 		scan_height,
695 		scan_max,
696 		scan_white
697 	} state = scan_width;
698 	int * scan = NULL;
699 	bool in_comment = false;
700 	*space = B_RGB24_BIG;
701 	/* The description of PPM is slightly ambiguous as far as comments */
702 	/* go. We choose to allow comments anywhere, in the spirit of laxness. */
703 	/* See http://www.dcs.ed.ac.uk/~mxr/gfx/2d/PPM.txt */
704 	int comlen = 0;
705 	int comptr = 0;
706 	while (inSource->Read(ch, 1) == 1) {
707 		if (in_comment && (ch[0] != 10) && (ch[0] != 13)) {
708 			if (comment) {	/* collect comment(s) into comment string */
709 				if (comptr >= comlen-1) {
710 					char * n = (char *)realloc(*comment, comlen+100);
711 					if (!n) {
712 						free(*comment);
713 						*comment = NULL;
714 					}
715 					*comment = n;
716 					comlen += 100;
717 				}
718 				(*comment)[comptr++] = ch[0];
719 				(*comment)[comptr] = 0;
720 			}
721 			continue;
722 		}
723 		in_comment = false;
724 		if (ch[0] == '#') {
725 			in_comment = true;
726 			continue;
727 		}
728 		/* once we're done with whitespace after max, we're done with header */
729 		if (isdigit(ch[0])) {
730 			if (!scan) {	/* first digit for this value */
731 				switch(state) {
732 				case scan_width:
733 					scan = width;
734 					break;
735 				case scan_height:
736 					*rowbytes = *width*3;
737 					scan = height;
738 					break;
739 				case scan_max:
740 					scan = max;
741 					break;
742 				default:
743 					return B_OK;	/* done with header, all OK */
744 				}
745 				*scan = 0;
746 			}
747 			*scan = (*scan)*10 + (ch[0]-'0');
748 		}
749 		else if (isspace(ch[0])) {
750 			if (scan) {	/* are we done with one value? */
751 				scan = NULL;
752 				state = (enum scan_state)(state+1);
753 			}
754 			if (state == scan_white) {	/* we only ever read one whitespace, since we skip space */
755 				return B_OK;	/* when reading ASCII, and there is a single whitespace after max in raw mode */
756 			}
757 		}
758 		else {
759 			if (state != scan_white) {
760 				return B_NO_TRANSLATOR;
761 			}
762 			return B_OK;	/* header done */
763 		}
764 	}
765 	return B_NO_TRANSLATOR;
766 }
767 
768 
769 
770 status_t
771 read_bits_header(
772 	BDataIO * io,
773 	int skipped,
774 	int * width,
775 	int * rowbytes,
776 	int * height,
777 	int * max,
778 	bool * ascii,
779 	color_space * space)
780 {
781 	/* read the rest of a possible B_TRANSLATOR_BITMAP header */
782 	if (skipped < 0 || skipped > 4) return B_NO_TRANSLATOR;
783 	int rd = sizeof(TranslatorBitmap)-skipped;
784 	TranslatorBitmap hdr;
785 	/* pre-initialize magic because we might have skipped part of it already */
786 	hdr.magic = B_HOST_TO_BENDIAN_INT32(B_TRANSLATOR_BITMAP);
787 	char * ptr = (char *)&hdr;
788 	if (io->Read(ptr+skipped, rd) != rd) {
789 		return B_NO_TRANSLATOR;
790 	}
791 	/* swap header values */
792 	hdr.magic = B_BENDIAN_TO_HOST_INT32(hdr.magic);
793 	hdr.bounds.left = B_BENDIAN_TO_HOST_FLOAT(hdr.bounds.left);
794 	hdr.bounds.right = B_BENDIAN_TO_HOST_FLOAT(hdr.bounds.right);
795 	hdr.bounds.top = B_BENDIAN_TO_HOST_FLOAT(hdr.bounds.top);
796 	hdr.bounds.bottom = B_BENDIAN_TO_HOST_FLOAT(hdr.bounds.bottom);
797 	hdr.rowBytes = B_BENDIAN_TO_HOST_INT32(hdr.rowBytes);
798 	hdr.colors = (color_space)B_BENDIAN_TO_HOST_INT32(hdr.colors);
799 	hdr.dataSize = B_BENDIAN_TO_HOST_INT32(hdr.dataSize);
800 	/* sanity checking */
801 	if (hdr.magic != B_TRANSLATOR_BITMAP) {
802 		return B_NO_TRANSLATOR;
803 	}
804 	if (hdr.colors & 0xffff0000) {	/* according to <GraphicsDefs.h> this is a reasonable check. */
805 		return B_NO_TRANSLATOR;
806 	}
807 	if (hdr.rowBytes * (hdr.bounds.Height()+1) > hdr.dataSize) {
808 		return B_NO_TRANSLATOR;
809 	}
810 	/* return information about the data in the stream */
811 	*width = (int)hdr.bounds.Width()+1;
812 	*rowbytes = hdr.rowBytes;
813 	*height = (int)hdr.bounds.Height()+1;
814 	*max = 255;
815 	*ascii = false;
816 	*space = hdr.colors;
817 	return B_OK;
818 }
819 
820 
821 /*	String may contain newlines, after which we need to insert extra hash signs. */
822 status_t
823 write_comment(
824 	const char * str,
825 	BDataIO * io)
826 {
827 	const char * end = str+strlen(str);
828 	const char * ptr = str;
829 	status_t err = B_OK;
830 	/* write each line as it's found */
831 	while ((ptr < end) && !err) {
832 		if ((*ptr == 10) || (*ptr == 13)) {
833 			err = io->Write("# ", 2);
834 			if (err == 2) {
835 				err = io->Write(str, ptr-str);
836 				if (err == ptr-str) {
837 					if (io->Write("\n", 1) == 1) {
838 						err = 0;
839 					}
840 				}
841 			}
842 			str = ptr+1;
843 		}
844 		ptr++;
845 	}
846 	/* write the last data, if any, as a line */
847 	if (ptr > str) {
848 		err = io->Write("# ", 2);
849 		if (err == 2) {
850 			err = io->Write(str, ptr-str);
851 			if (err == ptr-str) {
852 				if (io->Write("\n", 1) == 1) {
853 					err = 0;
854 				}
855 			}
856 		}
857 	}
858 	if (err > 0) {
859 		err = B_IO_ERROR;
860 	}
861 	return err;
862 }
863 
864 
865 static status_t
866 read_ascii_line(
867 	BDataIO * in,
868 	int max,
869 	unsigned char * data,
870 	int rowbytes)
871 {
872 	char ch;
873 	status_t err;
874 	// int nread = 0;
875 	bool comment = false;
876 	int val = 0;
877 	bool dig = false;
878 	while ((err = in->Read(&ch, 1)) == 1) {
879 		if (comment) {
880 			if ((ch == '\n') || (ch == '\r')) {
881 				comment = false;
882 			}
883 		}
884 		if (isdigit(ch)) {
885 			dig = true;
886 			val = val * 10 + (ch - '0');
887 		}
888 		else {
889 			if (dig) {
890 				*(data++) = val*255/max;
891 				val = 0;
892 				rowbytes--;
893 				dig = false;
894 			}
895 			if (ch == '#') {
896 				comment = true;
897 				continue;
898 			}
899 		}
900 		if (rowbytes < 1) {
901 			break;
902 		}
903 	}
904 	if (dig) {
905 		*(data++) = val*255/max;
906 		val = 0;
907 		rowbytes--;
908 		dig = false;
909 	}
910 	if (rowbytes < 1) {
911 		return B_OK;
912 	}
913 	return B_IO_ERROR;
914 }
915 
916 
917 static status_t
918 write_ascii_line(
919 	BDataIO * out,
920 	unsigned char * data,
921 	int rowbytes)
922 {
923 	char buffer[20];
924 	int linelen = 0;
925 	while (rowbytes > 2) {
926 		sprintf(buffer, "%d %d %d ", data[0], data[1], data[2]);
927 		rowbytes -= 3;
928 		int l = strlen(buffer);
929 		if (l + linelen > 70) {
930 			out->Write("\n", 1);
931 			linelen = 0;
932 		}
933 		if (out->Write(buffer, l) != l) {
934 			return B_IO_ERROR;
935 		}
936 		linelen += l;
937 		data += 3;
938 	}
939 	out->Write("\n", 1);	/* terminate each scanline */
940 	return B_OK;
941 }
942 
943 
944 static unsigned char *
945 make_scale_data(
946 	int max)
947 {
948 	unsigned char * ptr = (unsigned char *)malloc(max);
949 	for (int ix=0; ix<max; ix++) {
950 		ptr[ix] = ix*255/max;
951 	}
952 	return ptr;
953 }
954 
955 
956 static void
957 scale_data(
958 	unsigned char * scale,
959 	unsigned char * data,
960 	int bytes)
961 {
962 	for (int ix=0; ix<bytes; ix++) {
963 		data[ix] = scale[data[ix]];
964 	}
965 }
966 
967 
968 status_t
969 copy_data(
970 	BDataIO * in,
971 	BDataIO * out,
972 	int rowbytes,
973 	int out_rowbytes,
974 	int height,
975 	int max,
976 	bool in_ascii,
977 	bool out_ascii,
978 	color_space in_space,
979 	color_space out_space)
980 {
981 	dprintf(("copy_data(%x, %x, %x, %x, %x, %x)\n", rowbytes, out_rowbytes, height, max, in_space, out_space));
982 	/*	We read/write one scanline at a time.	*/
983 	unsigned char * data = (unsigned char *)malloc(rowbytes);
984 	unsigned char * out_data = (unsigned char *)malloc(out_rowbytes);
985 	if (data == NULL || out_data == NULL) {
986 		free(data);
987 		free(out_data);
988 		return B_NO_MEMORY;
989 	}
990 	unsigned char * scale = NULL;
991 	if (max != 255) {
992 		scale = make_scale_data(max);
993 	}
994 	status_t err = B_OK;
995 	/*	There is no data format conversion, so we can just copy data.	*/
996 	while ((height-- > 0) && !err) {
997 		if (in_ascii) {
998 			err = read_ascii_line(in, max, data, rowbytes);
999 		}
1000 		else {
1001 			err = in->Read(data, rowbytes);
1002 			if (err == rowbytes) {
1003 				err = B_OK;
1004 			}
1005 			if (scale) {	/* for reading PPM that is smaller than 8 bit */
1006 				scale_data(scale, data, rowbytes);
1007 			}
1008 		}
1009 		if (err == B_OK) {
1010 			unsigned char * wbuf = data;
1011 			if (in_space != out_space) {
1012 				err = convert_space(in_space, out_space, data, rowbytes, out_data);
1013 				wbuf = out_data;
1014 			}
1015 			if (!err && out_ascii) {
1016 				err = write_ascii_line(out, wbuf, out_rowbytes);
1017 			}
1018 			else if (!err) {
1019 				err = out->Write(wbuf, out_rowbytes);
1020 				if (err == out_rowbytes) {
1021 					err = B_OK;
1022 				}
1023 			}
1024 		}
1025 	}
1026 	free(data);
1027 	free(out_data);
1028 	free(scale);
1029 	return err > 0 ? B_IO_ERROR : err;
1030 }
1031 
1032 
1033