BPicture: Fix archive constructor.
[haiku.git] / src / add-ons / translators / ppm / PPMTranslator.cpp
blobb81f384f6f76ae452b7ce2a832bc207987c70fab
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 */
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. */
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>
28 #include <ctype.h>
29 #include <string.h>
30 #include <stdlib.h>
31 #include <stdio.h>
32 #include <syslog.h>
34 #include "colorspace.h"
36 #undef B_TRANSLATION_CONTEXT
37 #define B_TRANSLATION_CONTEXT "PPMTranslator"
39 #if DEBUG
40 #define dprintf(x) printf x
41 #else
42 #define dprintf(x)
43 #endif
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
53 #define PPM_TRANSLATOR_VERSION 0x100
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
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 '
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) */
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) */
82 /* Translators that don't export outputFormats */
83 /* will not be considered by files looking for */
84 /* specific output formats. */
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;
94 static BLocker g_settings_lock("PPM settings lock");
95 static ppm_settings g_settings;
97 BPoint get_window_origin();
98 void set_window_origin(BPoint pos);
99 BPoint get_window_origin()
101 BPoint ret;
102 g_settings_lock.Lock();
103 ret = g_settings.window_pos;
104 g_settings_lock.Unlock();
105 return ret;
108 void set_window_origin(BPoint pos)
110 g_settings_lock.Lock();
111 g_settings.window_pos = pos;
112 g_settings.settings_touched = true;
113 g_settings_lock.Unlock();
117 class PrefsLoader {
118 public:
119 PrefsLoader(
120 const char * str)
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");
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;
145 /* remember: line ends with \n, so printf()s don't have to */
146 ptr = line;
147 while (isspace(*ptr)) {
148 ptr++;
150 if (*ptr == '#' || !*ptr) { /* comment or blank */
151 continue;
153 if (sscanf(ptr, "%31[a-zA-Z_0-9] =", name) != 1) {
154 syslog(LOG_ERR, "unknown PPMTranslator "
155 "settings line: %s", line);
157 else {
158 if (!strcmp(name, "color_space")) {
159 while (*ptr != '=') {
160 ptr++;
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);
169 else if (!strcmp(name, "window_pos")) {
170 while (*ptr != '=') {
171 ptr++;
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);
181 else if (!strcmp(name, "write_ascii")) {
182 while (*ptr != '=') {
183 ptr++;
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);
191 else {
192 g_settings.write_ascii = ascii;
195 else {
196 syslog(LOG_ERR,
197 "unknown PPMTranslator setting: %s", line);
201 fclose(f);
204 ~PrefsLoader()
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");
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);
229 PrefsLoader g_prefs_loader("PPMTranslator_Settings");
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);
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 */
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)
253 dprintf(("PPMTranslator: Identify()\n"));
254 /* Silence compiler warnings. */
255 inFormat = inFormat;
256 ioExtension = ioExtension;
258 /* Check that requested format is something we can deal with. */
259 if (outType == 0) {
260 outType = B_TRANSLATOR_BITMAP;
262 if (outType != B_TRANSLATOR_BITMAP && outType != PPM_TYPE) {
263 return B_NO_TRANSLATOR;
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;
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");
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 */
291 return B_OK;
295 /* Return B_NO_TRANSLATOR if not handling the output format */
296 /* If outputFormats exists, will only be called for those formats */
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)
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;
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;
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
334 free(comment);
336 if (err < B_OK) {
337 dprintf(("read_ppm_header() error %s [%" B_PRIx32 "]\n", strerror(err),
338 err));
339 return err;
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);
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;
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;
377 else {
378 out_space = g_settings.out_space;
381 out_rowbytes = calc_rowbytes(out_space, width);
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);
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;
410 /* header done */
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
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;
433 #endif
434 if (err == sizeof(hdr)) err = 0;
435 /* header done */
437 if (err != B_OK) {
438 return err > 0 ? B_IO_ERROR : err;
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);
445 class PPMView :
446 public BView
448 public:
449 PPMView(
450 const char * name,
451 uint32 flags) :
452 BView(name, flags)
454 SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR));
456 fTitle = new BStringView("title",
457 B_TRANSLATE("PPM image translator"));
458 fTitle->SetFont(be_bold_font);
460 char detail[100];
461 int ver = static_cast<int>(translatorVersion);
462 sprintf(detail, B_TRANSLATE("Version %d.%d.%d, %s"), ver >> 8,
463 ((ver >> 4) & 0xf),
464 (ver & 0xf), __DATE__);
465 fDetail = new BStringView("detail", detail);
467 fBasedOn = new BStringView("basedOn",
468 B_TRANSLATE("Based on PPMTranslator sample code"));
470 fCopyright = new BStringView("copyright",
471 B_TRANSLATE("Sample code copyright 1999, Be Incorporated"));
473 fMenu = new BPopUpMenu("Color Space");
474 fMenu->AddItem(new BMenuItem(B_TRANSLATE("None"),
475 CSMessage(B_NO_COLOR_SPACE)));
476 fMenu->AddItem(new BMenuItem(B_TRANSLATE("RGB 8:8:8 32 bits"),
477 CSMessage(B_RGB32)));
478 fMenu->AddItem(new BMenuItem(B_TRANSLATE("RGBA 8:8:8:8 32 "
479 "bits"), CSMessage(B_RGBA32)));
480 fMenu->AddItem(new BMenuItem(B_TRANSLATE("RGB 5:5:5 16 bits"),
481 CSMessage(B_RGB15)));
482 fMenu->AddItem(new BMenuItem(B_TRANSLATE("RGBA 5:5:5:1 16 "
483 "bits"), CSMessage(B_RGBA15)));
484 fMenu->AddItem(new BMenuItem(B_TRANSLATE("RGB 5:6:5 16 bits"),
485 CSMessage(B_RGB16)));
486 fMenu->AddItem(new BMenuItem(B_TRANSLATE("System palette 8 "
487 "bits"), CSMessage(B_CMAP8)));
488 fMenu->AddSeparatorItem();
489 fMenu->AddItem(new BMenuItem(B_TRANSLATE("Grayscale 8 bits"),
490 CSMessage(B_GRAY8)));
491 fMenu->AddItem(new BMenuItem(B_TRANSLATE("Bitmap 1 bit"),
492 CSMessage(B_GRAY1)));
493 fMenu->AddItem(new BMenuItem(B_TRANSLATE("CMY 8:8:8 32 bits"),
494 CSMessage(B_CMY32)));
495 fMenu->AddItem(new BMenuItem(B_TRANSLATE("CMYA 8:8:8:8 32 "
496 "bits"), CSMessage(B_CMYA32)));
497 fMenu->AddItem(new BMenuItem(B_TRANSLATE("CMYK 8:8:8:8 32 "
498 "bits"), CSMessage(B_CMYK32)));
499 fMenu->AddSeparatorItem();
500 fMenu->AddItem(new BMenuItem(B_TRANSLATE("RGB 8:8:8 32 bits "
501 "big-endian"), CSMessage(B_RGB32_BIG)));
502 fMenu->AddItem(new BMenuItem(B_TRANSLATE("RGBA 8:8:8:8 32 "
503 "bits big-endian"), CSMessage(B_RGBA32_BIG)));
504 fMenu->AddItem(new BMenuItem(B_TRANSLATE("RGB 5:5:5 16 bits "
505 "big-endian"), CSMessage(B_RGB15_BIG)));
506 fMenu->AddItem(new BMenuItem(B_TRANSLATE("RGBA 5:5:5:1 16 "
507 "bits big-endian"), CSMessage(B_RGBA15_BIG)));
508 fMenu->AddItem(new BMenuItem(B_TRANSLATE("RGB 5:6:5 16 bits "
509 "big-endian"), CSMessage(B_RGB16)));
510 fField = new BMenuField(B_TRANSLATE("Input color space:"),
511 fMenu);
512 fField->SetViewColor(ViewColor());
513 SelectColorSpace(g_settings.out_space);
514 BMessage * msg = new BMessage(CHANGE_ASCII);
515 fAscii = new BCheckBox(B_TRANSLATE("Write ASCII"), msg);
516 if (g_settings.write_ascii)
517 fAscii->SetValue(1);
518 fAscii->SetViewColor(ViewColor());
520 // Build the layout
521 BLayoutBuilder::Group<>(this, B_VERTICAL, 0)
522 .SetInsets(B_USE_DEFAULT_SPACING)
523 .Add(fTitle)
524 .Add(fDetail)
525 .AddGlue()
526 .AddGroup(B_HORIZONTAL)
527 .Add(fField)
528 .AddGlue()
529 .End()
530 .Add(fAscii)
531 .AddGlue()
532 .Add(fBasedOn)
533 .Add(fCopyright);
535 BFont font;
536 GetFont(&font);
537 SetExplicitPreferredSize(BSize((font.Size() * 350)/12,
538 (font.Size() * 200)/12));
540 ~PPMView()
542 /* nothing here */
545 enum {
546 SET_COLOR_SPACE = 'ppm=',
547 CHANGE_ASCII
550 virtual void MessageReceived(
551 BMessage * message)
553 if (message->what == SET_COLOR_SPACE) {
554 SetSettings(message);
556 else if (message->what == CHANGE_ASCII) {
557 BMessage msg;
558 msg.AddBool("ppm /ascii", fAscii->Value());
559 SetSettings(&msg);
561 else {
562 BView::MessageReceived(message);
565 virtual void AllAttached()
567 BView::AllAttached();
568 BMessenger msgr(this);
569 /* Tell all menu items we're the man. */
570 for (int ix=0; ix<fMenu->CountItems(); ix++) {
571 BMenuItem * i = fMenu->ItemAt(ix);
572 if (i) {
573 i->SetTarget(msgr);
576 fAscii->SetTarget(msgr);
579 void SetSettings(
580 BMessage * message)
582 g_settings_lock.Lock();
583 color_space space;
584 if (!message->FindInt32("space", (int32*)&space)) {
585 g_settings.out_space = space;
586 SelectColorSpace(space);
587 g_settings.settings_touched = true;
589 bool ascii;
590 if (!message->FindBool("ppm /ascii", &ascii)) {
591 g_settings.write_ascii = ascii;
592 g_settings.settings_touched = true;
594 g_settings_lock.Unlock();
597 private:
598 BStringView * fTitle;
599 BStringView * fDetail;
600 BStringView * fBasedOn;
601 BStringView * fCopyright;
602 BPopUpMenu * fMenu;
603 BMenuField * fField;
604 BCheckBox * fAscii;
606 BMessage * CSMessage(
607 color_space space)
609 BMessage * ret = new BMessage(SET_COLOR_SPACE);
610 ret->AddInt32("space", space);
611 return ret;
614 void SelectColorSpace(
615 color_space space)
617 for (int ix=0; ix<fMenu->CountItems(); ix++) {
618 int32 s;
619 BMenuItem * i = fMenu->ItemAt(ix);
620 if (i) {
621 BMessage * m = i->Message();
622 if (m && !m->FindInt32("space", &s) && (s == space)) {
623 fMenu->Superitem()->SetLabel(i->Label());
624 break;
632 /* The view will get resized to what the parent thinks is */
633 /* reasonable. However, it will still receive MouseDowns etc. */
634 /* Your view should change settings in the translator immediately, */
635 /* taking care not to change parameters for a translation that is */
636 /* currently running. Typically, you'll have a global struct for */
637 /* settings that is atomically copied into the translator function */
638 /* as a local when translation starts. */
639 /* Store your settings wherever you feel like it. */
641 status_t
642 MakeConfig( /* optional */
643 BMessage * ioExtension, /* can be NULL */
644 BView * * outView,
645 BRect * outExtent)
647 PPMView * v = new PPMView(B_TRANSLATE("PPMTranslator Settings"),
648 B_WILL_DRAW);
649 *outView = v;
650 v->ResizeTo(v->ExplicitPreferredSize());;
651 *outExtent = v->Bounds();
652 if (ioExtension) {
653 v->SetSettings(ioExtension);
655 return B_OK;
659 /* Copy your current settings to a BMessage that may be passed */
660 /* to BTranslators::Translate at some later time when the user wants to */
661 /* use whatever settings you're using right now. */
663 status_t
664 GetConfigMessage( /* optional */
665 BMessage * ioExtension)
667 status_t err = B_OK;
668 #if defined(_PR3_COMPATIBLE_)
669 const char * name = B_TRANSLATOR_EXT_BITMAP_COLOR_SPACE;
670 #else
671 const char * name = B_TRANSLATE_MARK("bits/space");
672 #endif
673 g_settings_lock.Lock();
674 (void)ioExtension->RemoveName(name);
675 err = ioExtension->AddInt32(name, g_settings.out_space);
676 g_settings_lock.Unlock();
677 return err;
684 status_t
685 read_ppm_header(
686 BDataIO * inSource,
687 int * width,
688 int * rowbytes,
689 int * height,
690 int * max,
691 bool * ascii,
692 color_space * space,
693 bool * is_ppm,
694 char ** comment)
696 /* check for PPM magic number */
697 char ch[2];
698 if (inSource->Read(ch, 2) != 2) {
699 return B_NO_TRANSLATOR;
701 /* look for magic number */
702 if (ch[0] != 'P') {
703 /* B_TRANSLATOR_BITMAP magic? */
704 if (ch[0] == 'b' && ch[1] == 'i') {
705 *is_ppm = false;
706 return read_bits_header(inSource, 2, width, rowbytes, height, max, ascii, space);
708 return B_NO_TRANSLATOR;
710 *is_ppm = true;
711 if (ch[1] == '6') {
712 *ascii = false;
714 else if (ch[1] == '3') {
715 *ascii = true;
717 else {
718 return B_NO_TRANSLATOR;
720 // status_t err = B_NO_TRANSLATOR;
721 enum scan_state {
722 scan_width,
723 scan_height,
724 scan_max,
725 scan_white
726 } state = scan_width;
727 int * scan = NULL;
728 bool in_comment = false;
729 *space = B_RGB24_BIG;
730 /* The description of PPM is slightly ambiguous as far as comments */
731 /* go. We choose to allow comments anywhere, in the spirit of laxness. */
732 /* See http://www.dcs.ed.ac.uk/~mxr/gfx/2d/PPM.txt */
733 int comlen = 0;
734 int comptr = 0;
735 while (inSource->Read(ch, 1) == 1) {
736 if (in_comment && (ch[0] != 10) && (ch[0] != 13)) {
737 if (comment) { /* collect comment(s) into comment string */
738 if (comptr >= comlen-1) {
739 char * n = (char *)realloc(*comment, comlen+100);
740 if (!n) {
741 free(*comment);
742 *comment = NULL;
744 *comment = n;
745 comlen += 100;
747 (*comment)[comptr++] = ch[0];
748 (*comment)[comptr] = 0;
750 continue;
752 in_comment = false;
753 if (ch[0] == '#') {
754 in_comment = true;
755 continue;
757 /* once we're done with whitespace after max, we're done with header */
758 if (isdigit(ch[0])) {
759 if (!scan) { /* first digit for this value */
760 switch(state) {
761 case scan_width:
762 scan = width;
763 break;
764 case scan_height:
765 *rowbytes = *width*3;
766 scan = height;
767 break;
768 case scan_max:
769 scan = max;
770 break;
771 default:
772 return B_OK; /* done with header, all OK */
774 *scan = 0;
776 *scan = (*scan)*10 + (ch[0]-'0');
778 else if (isspace(ch[0])) {
779 if (scan) { /* are we done with one value? */
780 scan = NULL;
781 state = (enum scan_state)(state+1);
783 if (state == scan_white) { /* we only ever read one whitespace, since we skip space */
784 return B_OK; /* when reading ASCII, and there is a single whitespace after max in raw mode */
787 else {
788 if (state != scan_white) {
789 return B_NO_TRANSLATOR;
791 return B_OK; /* header done */
794 return B_NO_TRANSLATOR;
799 status_t
800 read_bits_header(
801 BDataIO * io,
802 int skipped,
803 int * width,
804 int * rowbytes,
805 int * height,
806 int * max,
807 bool * ascii,
808 color_space * space)
810 /* read the rest of a possible B_TRANSLATOR_BITMAP header */
811 if (skipped < 0 || skipped > 4) return B_NO_TRANSLATOR;
812 int rd = sizeof(TranslatorBitmap)-skipped;
813 TranslatorBitmap hdr;
814 /* pre-initialize magic because we might have skipped part of it already */
815 hdr.magic = B_HOST_TO_BENDIAN_INT32(B_TRANSLATOR_BITMAP);
816 char * ptr = (char *)&hdr;
817 if (io->Read(ptr+skipped, rd) != rd) {
818 return B_NO_TRANSLATOR;
820 /* swap header values */
821 hdr.magic = B_BENDIAN_TO_HOST_INT32(hdr.magic);
822 hdr.bounds.left = B_BENDIAN_TO_HOST_FLOAT(hdr.bounds.left);
823 hdr.bounds.right = B_BENDIAN_TO_HOST_FLOAT(hdr.bounds.right);
824 hdr.bounds.top = B_BENDIAN_TO_HOST_FLOAT(hdr.bounds.top);
825 hdr.bounds.bottom = B_BENDIAN_TO_HOST_FLOAT(hdr.bounds.bottom);
826 hdr.rowBytes = B_BENDIAN_TO_HOST_INT32(hdr.rowBytes);
827 hdr.colors = (color_space)B_BENDIAN_TO_HOST_INT32(hdr.colors);
828 hdr.dataSize = B_BENDIAN_TO_HOST_INT32(hdr.dataSize);
829 /* sanity checking */
830 if (hdr.magic != B_TRANSLATOR_BITMAP) {
831 return B_NO_TRANSLATOR;
833 if (hdr.colors & 0xffff0000) { /* according to <GraphicsDefs.h> this is a reasonable check. */
834 return B_NO_TRANSLATOR;
836 if (hdr.rowBytes * (hdr.bounds.Height()+1) > hdr.dataSize) {
837 return B_NO_TRANSLATOR;
839 /* return information about the data in the stream */
840 *width = (int)hdr.bounds.Width()+1;
841 *rowbytes = hdr.rowBytes;
842 *height = (int)hdr.bounds.Height()+1;
843 *max = 255;
844 *ascii = false;
845 *space = hdr.colors;
846 return B_OK;
850 /* String may contain newlines, after which we need to insert extra hash signs. */
851 status_t
852 write_comment(
853 const char * str,
854 BDataIO * io)
856 const char * end = str+strlen(str);
857 const char * ptr = str;
858 status_t err = B_OK;
859 /* write each line as it's found */
860 while ((ptr < end) && !err) {
861 if ((*ptr == 10) || (*ptr == 13)) {
862 err = io->Write("# ", 2);
863 if (err == 2) {
864 err = io->Write(str, ptr-str);
865 if (err == ptr-str) {
866 if (io->Write("\n", 1) == 1) {
867 err = 0;
871 str = ptr+1;
873 ptr++;
875 /* write the last data, if any, as a line */
876 if (ptr > str) {
877 err = io->Write("# ", 2);
878 if (err == 2) {
879 err = io->Write(str, ptr-str);
880 if (err == ptr-str) {
881 if (io->Write("\n", 1) == 1) {
882 err = 0;
887 if (err > 0) {
888 err = B_IO_ERROR;
890 return err;
894 static status_t
895 read_ascii_line(
896 BDataIO * in,
897 int max,
898 unsigned char * data,
899 int rowbytes)
901 char ch;
902 status_t err;
903 // int nread = 0;
904 bool comment = false;
905 int val = 0;
906 bool dig = false;
907 while ((err = in->Read(&ch, 1)) == 1) {
908 if (comment) {
909 if ((ch == '\n') || (ch == '\r')) {
910 comment = false;
913 if (isdigit(ch)) {
914 dig = true;
915 val = val * 10 + (ch - '0');
917 else {
918 if (dig) {
919 *(data++) = val*255/max;
920 val = 0;
921 rowbytes--;
922 dig = false;
924 if (ch == '#') {
925 comment = true;
926 continue;
929 if (rowbytes < 1) {
930 break;
933 if (dig) {
934 *(data++) = val*255/max;
935 val = 0;
936 rowbytes--;
937 dig = false;
939 if (rowbytes < 1) {
940 return B_OK;
942 return B_IO_ERROR;
946 static status_t
947 write_ascii_line(
948 BDataIO * out,
949 unsigned char * data,
950 int rowbytes)
952 char buffer[20];
953 int linelen = 0;
954 while (rowbytes > 2) {
955 sprintf(buffer, "%d %d %d ", data[0], data[1], data[2]);
956 rowbytes -= 3;
957 int l = strlen(buffer);
958 if (l + linelen > 70) {
959 out->Write("\n", 1);
960 linelen = 0;
962 if (out->Write(buffer, l) != l) {
963 return B_IO_ERROR;
965 linelen += l;
966 data += 3;
968 out->Write("\n", 1); /* terminate each scanline */
969 return B_OK;
973 static unsigned char *
974 make_scale_data(
975 int max)
977 unsigned char * ptr = (unsigned char *)malloc(max);
978 for (int ix=0; ix<max; ix++) {
979 ptr[ix] = ix*255/max;
981 return ptr;
985 static void
986 scale_data(
987 unsigned char * scale,
988 unsigned char * data,
989 int bytes)
991 for (int ix=0; ix<bytes; ix++) {
992 data[ix] = scale[data[ix]];
997 status_t
998 copy_data(
999 BDataIO * in,
1000 BDataIO * out,
1001 int rowbytes,
1002 int out_rowbytes,
1003 int height,
1004 int max,
1005 bool in_ascii,
1006 bool out_ascii,
1007 color_space in_space,
1008 color_space out_space)
1010 dprintf(("copy_data(%x, %x, %x, %x, %x, %x)\n", rowbytes, out_rowbytes, height, max, in_space, out_space));
1011 /* We read/write one scanline at a time. */
1012 unsigned char * data = (unsigned char *)malloc(rowbytes);
1013 unsigned char * out_data = (unsigned char *)malloc(out_rowbytes);
1014 if (data == NULL || out_data == NULL) {
1015 free(data);
1016 free(out_data);
1017 return B_NO_MEMORY;
1019 unsigned char * scale = NULL;
1020 if (max != 255) {
1021 scale = make_scale_data(max);
1023 status_t err = B_OK;
1024 /* There is no data format conversion, so we can just copy data. */
1025 while ((height-- > 0) && !err) {
1026 if (in_ascii) {
1027 err = read_ascii_line(in, max, data, rowbytes);
1029 else {
1030 err = in->Read(data, rowbytes);
1031 if (err == rowbytes) {
1032 err = B_OK;
1034 if (scale) { /* for reading PPM that is smaller than 8 bit */
1035 scale_data(scale, data, rowbytes);
1038 if (err == B_OK) {
1039 unsigned char * wbuf = data;
1040 if (in_space != out_space) {
1041 err = convert_space(in_space, out_space, data, rowbytes, out_data);
1042 wbuf = out_data;
1044 if (!err && out_ascii) {
1045 err = write_ascii_line(out, wbuf, out_rowbytes);
1047 else if (!err) {
1048 err = out->Write(wbuf, out_rowbytes);
1049 if (err == out_rowbytes) {
1050 err = B_OK;
1055 free(data);
1056 free(out_data);
1057 free(scale);
1058 return err > 0 ? B_IO_ERROR : err;