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