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 ~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 566 private: 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. */ 607 status_t 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 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 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 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 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 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 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* 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 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 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