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