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