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