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
get_window_origin()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
set_window_origin(BPoint pos)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:
PrefsLoader(const char * str)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
~PrefsLoader()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
Identify(BPositionIO * inSource,const translation_format * inFormat,BMessage * ioExtension,translator_info * outInfo,uint32 outType)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
Translate(BPositionIO * inSource,const translator_info *,BMessage * ioExtension,uint32 outType,BPositionIO * outDestination)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:
PPMView(const char * name,uint32 flags)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 }
~PPMView()520 ~PPMView() { /* nothing here */ }
521
522 enum { SET_COLOR_SPACE = 'ppm=', CHANGE_ASCII };
523
MessageReceived(BMessage * message)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 }
AllAttached()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
SetSettings(BMessage * message)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
566 private:
567 BStringView* fTitle;
568 BStringView* fDetail;
569 BStringView* fBasedOn;
570 BStringView* fCopyright;
571 BPopUpMenu* fMenu;
572 BMenuField* fField;
573 BCheckBox* fAscii;
574
CSMessage(color_space space)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
SelectColorSpace(color_space space)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. */
607 status_t
MakeConfig(BMessage * ioExtension,BView ** outView,BRect * outExtent)608 MakeConfig(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. */
625 status_t
GetConfigMessage(BMessage * ioExtension)626 GetConfigMessage(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
638 status_t
read_ppm_header(BDataIO * inSource,int * width,int * rowbytes,int * height,int * max,bool * ascii,color_space * space,bool * is_ppm,char ** comment)639 read_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
768 status_t
read_bits_header(BDataIO * io,int skipped,int * width,int * rowbytes,int * height,int * max,bool * ascii,color_space * space)769 read_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 */
813 status_t
write_comment(const char * str,BDataIO * io)814 write_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
851 static status_t
read_ascii_line(BDataIO * in,int max,unsigned char * data,int rowbytes)852 read_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
895 static status_t
write_ascii_line(BDataIO * out,unsigned char * data,int rowbytes)896 write_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
918 static unsigned char*
make_scale_data(int max)919 make_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
928 static void
scale_data(unsigned char * scale,unsigned char * data,int bytes)929 scale_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
936 status_t
copy_data(BDataIO * in,BDataIO * out,int rowbytes,int out_rowbytes,int height,int max,bool in_ascii,bool out_ascii,color_space in_space,color_space out_space)937 copy_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