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. */
47char translatorName[] = "PPM images";
48char translatorInfo[] = "PPM image translator v1.0.0, " __DATE__;
49int32 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. */
62translation_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
70translation_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.	*/
84struct ppm_settings {
85	color_space out_space;
86	BPoint window_pos;
87	bool write_ascii;
88	bool settings_touched;
89};
90static BLocker g_settings_lock("PPM settings lock");
91static ppm_settings g_settings;
92
93BPoint get_window_origin();
94void set_window_origin(BPoint pos);
95BPoint
96get_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
105void
106set_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
115class PrefsLoader
116{
117public:
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
224PrefsLoader g_prefs_loader("PPMTranslator_Settings");
225
226/*	Some prototypes for functions we use.	*/
227status_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);
229status_t read_bits_header(BDataIO* io, int skipped, int* width, int* rowbytes,
230	int* height, int* max, bool* ascii, color_space* space);
231status_t write_comment(const char* str, BDataIO* io);
232status_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	*/
240status_t
241Identify(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	*/
291status_t
292Translate(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
427class PPMView : public BView
428{
429public:
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	~PPMView() { /* nothing here */ }
521
522	enum { SET_COLOR_SPACE = 'ppm=', CHANGE_ASCII };
523
524	virtual void MessageReceived(BMessage* message)
525	{
526		if (message->what == SET_COLOR_SPACE) {
527			SetSettings(message);
528		} else if (message->what == CHANGE_ASCII) {
529			BMessage msg;
530			msg.AddBool("ppm /ascii", fAscii->Value());
531			SetSettings(&msg);
532		} else {
533			BView::MessageReceived(message);
534		}
535	}
536	virtual void AllAttached()
537	{
538		BView::AllAttached();
539		BMessenger msgr(this);
540		/*	Tell all menu items we're the man.	*/
541		for (int ix = 0; ix < fMenu->CountItems(); ix++) {
542			BMenuItem* i = fMenu->ItemAt(ix);
543			if (i)
544				i->SetTarget(msgr);
545		}
546		fAscii->SetTarget(msgr);
547	}
548
549	void SetSettings(BMessage* message)
550	{
551		g_settings_lock.Lock();
552		color_space space;
553		if (!message->FindInt32("space", (int32*) &space)) {
554			g_settings.out_space = space;
555			SelectColorSpace(space);
556			g_settings.settings_touched = true;
557		}
558		bool ascii;
559		if (!message->FindBool("ppm /ascii", &ascii)) {
560			g_settings.write_ascii = ascii;
561			g_settings.settings_touched = true;
562		}
563		g_settings_lock.Unlock();
564	}
565
566private:
567	BStringView* fTitle;
568	BStringView* fDetail;
569	BStringView* fBasedOn;
570	BStringView* fCopyright;
571	BPopUpMenu* fMenu;
572	BMenuField* fField;
573	BCheckBox* fAscii;
574
575	BMessage* CSMessage(color_space space)
576	{
577		BMessage* ret = new BMessage(SET_COLOR_SPACE);
578		ret->AddInt32("space", space);
579		return ret;
580	}
581
582	void SelectColorSpace(color_space space)
583	{
584		for (int ix = 0; ix < fMenu->CountItems(); ix++) {
585			int32 s;
586			BMenuItem* i = fMenu->ItemAt(ix);
587			if (i) {
588				BMessage* m = i->Message();
589				if (m && !m->FindInt32("space", &s) && (s == space)) {
590					fMenu->Superitem()->SetLabel(i->Label());
591					break;
592				}
593			}
594		}
595	}
596};
597
598
599/*	The view will get resized to what the parent thinks is 	*/
600/*	reasonable. However, it will still receive MouseDowns etc.	*/
601/*	Your view should change settings in the translator immediately, 	*/
602/*	taking care not to change parameters for a translation that is 	*/
603/*	currently running. Typically, you'll have a global struct for 	*/
604/*	settings that is atomically copied into the translator function 	*/
605/*	as a local when translation starts.	*/
606/*	Store your settings wherever you feel like it.	*/
607status_t
608MakeConfig(BMessage* ioExtension, BView** outView, BRect* outExtent)
609{
610	PPMView* v
611		= new PPMView(B_TRANSLATE("PPMTranslator Settings"), B_WILL_DRAW);
612	*outView = v;
613	v->ResizeTo(v->ExplicitPreferredSize());
614	;
615	*outExtent = v->Bounds();
616	if (ioExtension)
617		v->SetSettings(ioExtension);
618	return B_OK;
619}
620
621
622/*	Copy your current settings to a BMessage that may be passed 	*/
623/*	to BTranslators::Translate at some later time when the user wants to 	*/
624/*	use whatever settings you're using right now.	*/
625status_t
626GetConfigMessage(BMessage* ioExtension)
627{
628	status_t err = B_OK;
629	const char* name = B_TRANSLATOR_EXT_BITMAP_COLOR_SPACE;
630	g_settings_lock.Lock();
631	(void) ioExtension->RemoveName(name);
632	err = ioExtension->AddInt32(name, g_settings.out_space);
633	g_settings_lock.Unlock();
634	return err;
635}
636
637
638status_t
639read_ppm_header(BDataIO* inSource, int* width, int* rowbytes, int* height,
640	int* max, bool* ascii, color_space* space, bool* is_ppm, char** comment)
641{
642	/* check for PPM magic number */
643	char ch[2];
644	bool monochrome = false;
645	bool greyscale = false;
646	if (inSource->Read(ch, 2) != 2)
647		return B_NO_TRANSLATOR;
648	/* look for magic number */
649	if (ch[0] != 'P') {
650		/* B_TRANSLATOR_BITMAP magic? */
651		if (ch[0] == 'b' && ch[1] == 'i') {
652			*is_ppm = false;
653			return read_bits_header(
654				inSource, 2, width, rowbytes, height, max, ascii, space);
655		}
656		return B_NO_TRANSLATOR;
657	}
658	*is_ppm = true;
659	if (ch[1] == '6' || ch[1] == '5' || ch[1] == '4') {
660		*ascii = false;
661	} else if (ch[1] == '3' || ch[1] == '2' || ch[1] == '1') {
662		*ascii = true;
663	} else {
664		return B_NO_TRANSLATOR;
665	}
666
667	if (ch[1] == '4' || ch[1] == '1')
668		monochrome = true;
669	else if (ch[1] == '5' || ch[1] == '2')
670		greyscale = true;
671
672	// status_t err = B_NO_TRANSLATOR;
673	enum scan_state {
674		scan_width,
675		scan_height,
676		scan_max,
677		scan_white
678	} state
679		= scan_width;
680	int* scan = NULL;
681	bool in_comment = false;
682	if (monochrome)
683		*space = B_GRAY1;
684	else if (greyscale)
685		*space = B_GRAY8;
686	else
687		*space = B_RGB24_BIG;
688	/* The description of PPM is slightly ambiguous as far as comments */
689	/* go. We choose to allow comments anywhere, in the spirit of laxness. */
690	/* See http://www.dcs.ed.ac.uk/~mxr/gfx/2d/PPM.txt */
691	int comlen = 0;
692	int comptr = 0;
693	while (inSource->Read(ch, 1) == 1) {
694		if (in_comment && (ch[0] != 10) && (ch[0] != 13)) {
695			if (comment) { /* collect comment(s) into comment string */
696				if (comptr >= comlen - 1) {
697					char* n = (char*) realloc(*comment, comlen + 100);
698					if (!n) {
699						free(*comment);
700						*comment = NULL;
701					}
702					*comment = n;
703					comlen += 100;
704				}
705				(*comment)[comptr++] = ch[0];
706				(*comment)[comptr] = 0;
707			}
708			continue;
709		}
710		in_comment = false;
711		if (ch[0] == '#') {
712			in_comment = true;
713			continue;
714		}
715		/* once we're done with whitespace after max, we're done with header */
716		if (isdigit(ch[0])) {
717			if (!scan) { /* first digit for this value */
718				switch (state) {
719					case scan_width:
720						scan = width;
721						break;
722					case scan_height:
723						if (monochrome)
724							*rowbytes = (*width + 7) / 8;
725						else if (greyscale)
726							*rowbytes = *width;
727						else
728							*rowbytes = *width * 3;
729						scan = height;
730						break;
731					case scan_max:
732						scan = max;
733						break;
734					default:
735						return B_OK; /* done with header, all OK */
736				}
737				*scan = 0;
738			}
739			*scan = (*scan) * 10 + (ch[0] - '0');
740		} else if (isspace(ch[0])) {
741			if (scan) { /* are we done with one value? */
742				scan = NULL;
743				state = (enum scan_state)(state + 1);
744
745				/* in monochrome ppm, there is no max in the file, so we
746				 * skip that step. */
747				if ((state == scan_max) && monochrome) {
748					state = (enum scan_state)(state + 1);
749					*max = 1;
750				}
751			}
752
753			if (state == scan_white) { /* we only ever read one whitespace,
754										  since we skip space */
755				return B_OK; /* when reading ASCII, and there is a single
756								whitespace after max in raw mode */
757			}
758		} else {
759			if (state != scan_white)
760				return B_NO_TRANSLATOR;
761			return B_OK; /* header done */
762		}
763	}
764	return B_NO_TRANSLATOR;
765}
766
767
768status_t
769read_bits_header(BDataIO* io, int skipped, int* width, int* rowbytes,
770	int* height, int* max, bool* ascii, color_space* space)
771{
772	/* read the rest of a possible B_TRANSLATOR_BITMAP header */
773	if (skipped < 0 || skipped > 4)
774		return B_NO_TRANSLATOR;
775	int rd = sizeof(TranslatorBitmap) - skipped;
776	TranslatorBitmap hdr;
777	/* pre-initialize magic because we might have skipped part of it already */
778	hdr.magic = B_HOST_TO_BENDIAN_INT32(B_TRANSLATOR_BITMAP);
779	char* ptr = (char*) &hdr;
780	if (io->Read(ptr + skipped, rd) != rd)
781		return B_NO_TRANSLATOR;
782	/* swap header values */
783	hdr.magic = B_BENDIAN_TO_HOST_INT32(hdr.magic);
784	hdr.bounds.left = B_BENDIAN_TO_HOST_FLOAT(hdr.bounds.left);
785	hdr.bounds.right = B_BENDIAN_TO_HOST_FLOAT(hdr.bounds.right);
786	hdr.bounds.top = B_BENDIAN_TO_HOST_FLOAT(hdr.bounds.top);
787	hdr.bounds.bottom = B_BENDIAN_TO_HOST_FLOAT(hdr.bounds.bottom);
788	hdr.rowBytes = B_BENDIAN_TO_HOST_INT32(hdr.rowBytes);
789	hdr.colors = (color_space) B_BENDIAN_TO_HOST_INT32(hdr.colors);
790	hdr.dataSize = B_BENDIAN_TO_HOST_INT32(hdr.dataSize);
791	/* sanity checking */
792	if (hdr.magic != B_TRANSLATOR_BITMAP)
793		return B_NO_TRANSLATOR;
794	if (hdr.colors & 0xffff0000) { /* according to <GraphicsDefs.h> this is a
795									  reasonable check. */
796		return B_NO_TRANSLATOR;
797	}
798	if (hdr.rowBytes * (hdr.bounds.Height() + 1) > hdr.dataSize)
799		return B_NO_TRANSLATOR;
800	/* return information about the data in the stream */
801	*width = (int) hdr.bounds.Width() + 1;
802	*rowbytes = hdr.rowBytes;
803	*height = (int) hdr.bounds.Height() + 1;
804	*max = 255;
805	*ascii = false;
806	*space = hdr.colors;
807	return B_OK;
808}
809
810
811/*	String may contain newlines, after which we need to insert extra hash signs.
812 */
813status_t
814write_comment(const char* str, BDataIO* io)
815{
816	const char* end = str + strlen(str);
817	const char* ptr = str;
818	status_t err = B_OK;
819	/* write each line as it's found */
820	while ((ptr < end) && !err) {
821		if ((*ptr == 10) || (*ptr == 13)) {
822			err = io->Write("# ", 2);
823			if (err == 2) {
824				err = io->Write(str, ptr - str);
825				if (err == ptr - str) {
826					if (io->Write("\n", 1) == 1)
827						err = 0;
828				}
829			}
830			str = ptr + 1;
831		}
832		ptr++;
833	}
834	/* write the last data, if any, as a line */
835	if (ptr > str) {
836		err = io->Write("# ", 2);
837		if (err == 2) {
838			err = io->Write(str, ptr - str);
839			if (err == ptr - str) {
840				if (io->Write("\n", 1) == 1)
841					err = 0;
842			}
843		}
844	}
845	if (err > 0)
846		err = B_IO_ERROR;
847	return err;
848}
849
850
851static status_t
852read_ascii_line(BDataIO* in, int max, unsigned char* data, int rowbytes)
853{
854	char ch;
855	status_t err;
856	// int nread = 0;
857	bool comment = false;
858	int val = 0;
859	bool dig = false;
860	while ((err = in->Read(&ch, 1)) == 1) {
861		if (comment) {
862			if ((ch == '\n') || (ch == '\r'))
863				comment = false;
864		}
865		if (isdigit(ch)) {
866			dig = true;
867			val = val * 10 + (ch - '0');
868		} else {
869			if (dig) {
870				*(data++) = val * 255 / max;
871				val = 0;
872				rowbytes--;
873				dig = false;
874			}
875			if (ch == '#') {
876				comment = true;
877				continue;
878			}
879		}
880		if (rowbytes < 1)
881			break;
882	}
883	if (dig) {
884		*(data++) = val * 255 / max;
885		val = 0;
886		rowbytes--;
887		dig = false;
888	}
889	if (rowbytes < 1)
890		return B_OK;
891	return B_IO_ERROR;
892}
893
894
895static status_t
896write_ascii_line(BDataIO* out, unsigned char* data, int rowbytes)
897{
898	char buffer[20];
899	int linelen = 0;
900	while (rowbytes > 2) {
901		sprintf(buffer, "%d %d %d ", data[0], data[1], data[2]);
902		rowbytes -= 3;
903		int l = strlen(buffer);
904		if (l + linelen > 70) {
905			out->Write("\n", 1);
906			linelen = 0;
907		}
908		if (out->Write(buffer, l) != l)
909			return B_IO_ERROR;
910		linelen += l;
911		data += 3;
912	}
913	out->Write("\n", 1); /* terminate each scanline */
914	return B_OK;
915}
916
917
918static unsigned char*
919make_scale_data(int max)
920{
921	unsigned char* ptr = (unsigned char*) malloc(max);
922	for (int ix = 0; ix < max; ix++)
923		ptr[ix] = ix * 255 / max;
924	return ptr;
925}
926
927
928static void
929scale_data(unsigned char* scale, unsigned char* data, int bytes)
930{
931	for (int ix = 0; ix < bytes; ix++)
932		data[ix] = scale[data[ix]];
933}
934
935
936status_t
937copy_data(BDataIO* in, BDataIO* out, int rowbytes, int out_rowbytes, int height,
938	int max, bool in_ascii, bool out_ascii, color_space in_space,
939	color_space out_space)
940{
941	dprintf(("copy_data(%d, %d, %d, %d, %x, %x)\n", rowbytes, out_rowbytes,
942		height, max, in_space, out_space));
943
944	// We will be reading 1 char at a time, so use a buffer
945	BBufferedDataIO inBuffer(*in, 65536, false);
946
947	/*	We read/write one scanline at a time.	*/
948	unsigned char* data = (unsigned char*) malloc(rowbytes);
949	unsigned char* out_data = (unsigned char*) malloc(out_rowbytes);
950	if (data == NULL || out_data == NULL) {
951		free(data);
952		free(out_data);
953		return B_NO_MEMORY;
954	}
955	unsigned char* scale = NULL;
956	if (max != 255 && in_space != B_GRAY1)
957		scale = make_scale_data(max);
958	status_t err = B_OK;
959	/*	There is no data format conversion, so we can just copy data.	*/
960	while ((height-- > 0) && !err) {
961		if (in_ascii) {
962			err = read_ascii_line(&inBuffer, max, data, rowbytes);
963		} else {
964			err = inBuffer.Read(data, rowbytes);
965			if (err == rowbytes)
966				err = B_OK;
967			if (scale) /* for reading PPM that is smaller than 8 bit */
968				scale_data(scale, data, rowbytes);
969		}
970		if (err == B_OK) {
971			unsigned char* wbuf = data;
972			if (in_space != out_space) {
973				err = convert_space(
974					in_space, out_space, data, rowbytes, out_data);
975				wbuf = out_data;
976			}
977			if (!err && out_ascii) {
978				err = write_ascii_line(out, wbuf, out_rowbytes);
979			} else if (!err) {
980				err = out->Write(wbuf, out_rowbytes);
981				if (err == out_rowbytes)
982					err = B_OK;
983			}
984		}
985	}
986	free(data);
987	free(out_data);
988	free(scale);
989	return err > 0 ? B_IO_ERROR : err;
990}
991