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