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