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