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