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 [%" B_PRIx32 "]\n", strerror(err), 338 err)); 339 return err; 340 } 341 /* Check if we're configured to write ASCII format file. */ 342 bool out_ascii = false; 343 if (outType == PPM_TYPE) { 344 out_ascii = g_settings.write_ascii; 345 if (ioExtension != NULL) { 346 ioExtension->FindBool("ppm /ascii", &out_ascii); 347 } 348 } 349 err = B_OK; 350 /* Figure out which color space to convert to */ 351 color_space out_space; 352 int out_rowbytes; 353 g_settings_lock.Lock(); 354 if (outType == PPM_TYPE) { 355 out_space = B_RGB24_BIG; 356 out_rowbytes = 3*width; 357 } 358 else { /* When outputting to B_TRANSLATOR_BITMAP, follow user's wishes. */ 359 #if defined(_PR3_COMPATIBLE_) /* R4 headers? */ 360 if (!ioExtension || ioExtension->FindInt32(B_TRANSLATOR_EXT_BITMAP_COLOR_SPACE, (int32*)&out_space) || 361 #else 362 if (!ioExtension || ioExtension->FindInt32("bits/space", (int32*)&out_space) || 363 #endif 364 (out_space == B_NO_COLOR_SPACE)) { 365 if (g_settings.out_space == B_NO_COLOR_SPACE) { 366 switch (space) { /* The 24-bit versions are pretty silly, use 32 instead. */ 367 case B_RGB24: 368 case B_RGB24_BIG: 369 out_space = B_RGB32; 370 break; 371 default: 372 /* use whatever is there */ 373 out_space = space; 374 break; 375 } 376 } 377 else { 378 out_space = g_settings.out_space; 379 } 380 } 381 out_rowbytes = calc_rowbytes(out_space, width); 382 } 383 g_settings_lock.Unlock(); 384 /* Write file header */ 385 if (outType == PPM_TYPE) { 386 dprintf(("PPMTranslator: write PPM\n")); 387 /* begin PPM header */ 388 const char * magic; 389 if (out_ascii) 390 magic = "P3\n"; 391 else 392 magic = "P6\n"; 393 err = outDestination->Write(magic, strlen(magic)); 394 if (err == (long)strlen(magic)) err = 0; 395 //comment = NULL; 396 const char* fsComment; 397 #if defined(_PR3_COMPATIBLE_) /* R4 headers? */ 398 if ((ioExtension != NULL) && !ioExtension->FindString(B_TRANSLATOR_EXT_COMMENT, &fsComment)) { 399 #else 400 if ((ioExtension != NULL) && !ioExtension->FindString("/comment", &fsComment)) { 401 #endif 402 err = write_comment(fsComment, outDestination); 403 } 404 if (err == B_OK) { 405 char data[40]; 406 sprintf(data, "%d %d %d\n", width, height, max); 407 err = outDestination->Write(data, strlen(data)); 408 if (err == (long)strlen(data)) err = 0; 409 } 410 /* header done */ 411 } 412 else { 413 dprintf(("PPMTranslator: write B_TRANSLATOR_BITMAP\n")); 414 /* B_TRANSLATOR_BITMAP header */ 415 TranslatorBitmap hdr; 416 hdr.magic = B_HOST_TO_BENDIAN_INT32(B_TRANSLATOR_BITMAP); 417 hdr.bounds.left = B_HOST_TO_BENDIAN_FLOAT(0); 418 hdr.bounds.top = B_HOST_TO_BENDIAN_FLOAT(0); 419 hdr.bounds.right = B_HOST_TO_BENDIAN_FLOAT(width-1); 420 hdr.bounds.bottom = B_HOST_TO_BENDIAN_FLOAT(height-1); 421 hdr.rowBytes = B_HOST_TO_BENDIAN_INT32(out_rowbytes); 422 hdr.colors = (color_space)B_HOST_TO_BENDIAN_INT32(out_space); 423 hdr.dataSize = B_HOST_TO_BENDIAN_INT32(out_rowbytes*height); 424 dprintf(("rowBytes is %d, width %d, out_space %x, space %x\n", out_rowbytes, width, out_space, space)); 425 err = outDestination->Write(&hdr, sizeof(hdr)); 426 dprintf(("PPMTranslator: Write() returns %" B_PRIx32 "\n", err)); 427 #if DEBUG 428 { 429 BBitmap * map = new BBitmap(BRect(0,0,width-1,height-1), out_space); 430 printf("map rb = %" B_PRId32 "\n", map->BytesPerRow()); 431 delete map; 432 } 433 #endif 434 if (err == sizeof(hdr)) err = 0; 435 /* header done */ 436 } 437 if (err != B_OK) { 438 return err > 0 ? B_IO_ERROR : err; 439 } 440 /* Write data. Luckily, PPM and B_TRANSLATOR_BITMAP both scan from left to right, top to bottom. */ 441 return copy_data(inSource, outDestination, rowbytes, out_rowbytes, height, max, ascii, out_ascii, space, out_space); 442 } 443 444 445 class PPMView : 446 public BView 447 { 448 public: 449 PPMView( 450 const char * name, 451 uint32 flags) : 452 BView(name, flags) 453 { 454 SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR)); 455 SetLowColor(ViewColor()); 456 457 mTitle = new BStringView("title", 458 B_TRANSLATE("PPM image translator")); 459 mTitle->SetFont(be_bold_font); 460 461 char detail[100]; 462 int ver = static_cast<int>(translatorVersion); 463 sprintf(detail, B_TRANSLATE("Version %d.%d.%d %s"), ver >> 8, 464 ((ver >> 4) & 0xf), 465 (ver & 0xf), __DATE__); 466 mDetail = new BStringView("detail", detail); 467 468 mBasedOn = new BStringView("basedOn", 469 B_TRANSLATE("Based on PPMTranslator sample code")); 470 471 mCopyright = new BStringView("copyright", 472 B_TRANSLATE("Sample code copyright 1999, Be Incorporated")); 473 474 mMenu = new BPopUpMenu("Color Space"); 475 mMenu->AddItem(new BMenuItem(B_TRANSLATE("None"), 476 CSMessage(B_NO_COLOR_SPACE))); 477 mMenu->AddItem(new BMenuItem(B_TRANSLATE("RGB 8:8:8 32 bits"), 478 CSMessage(B_RGB32))); 479 mMenu->AddItem(new BMenuItem(B_TRANSLATE("RGBA 8:8:8:8 32 " 480 "bits"), CSMessage(B_RGBA32))); 481 mMenu->AddItem(new BMenuItem(B_TRANSLATE("RGB 5:5:5 16 bits"), 482 CSMessage(B_RGB15))); 483 mMenu->AddItem(new BMenuItem(B_TRANSLATE("RGBA 5:5:5:1 16 " 484 "bits"), CSMessage(B_RGBA15))); 485 mMenu->AddItem(new BMenuItem(B_TRANSLATE("RGB 5:6:5 16 bits"), 486 CSMessage(B_RGB16))); 487 mMenu->AddItem(new BMenuItem(B_TRANSLATE("System palette 8 " 488 "bits"), CSMessage(B_CMAP8))); 489 mMenu->AddSeparatorItem(); 490 mMenu->AddItem(new BMenuItem(B_TRANSLATE("Grayscale 8 bits"), 491 CSMessage(B_GRAY8))); 492 mMenu->AddItem(new BMenuItem(B_TRANSLATE("Bitmap 1 bit"), 493 CSMessage(B_GRAY1))); 494 mMenu->AddItem(new BMenuItem(B_TRANSLATE("CMY 8:8:8 32 bits"), 495 CSMessage(B_CMY32))); 496 mMenu->AddItem(new BMenuItem(B_TRANSLATE("CMYA 8:8:8:8 32 " 497 "bits"), CSMessage(B_CMYA32))); 498 mMenu->AddItem(new BMenuItem(B_TRANSLATE("CMYK 8:8:8:8 32 " 499 "bits"), CSMessage(B_CMYK32))); 500 mMenu->AddSeparatorItem(); 501 mMenu->AddItem(new BMenuItem(B_TRANSLATE("RGB 8:8:8 32 bits " 502 "big-endian"), CSMessage(B_RGB32_BIG))); 503 mMenu->AddItem(new BMenuItem(B_TRANSLATE("RGBA 8:8:8:8 32 " 504 "bits big-endian"), CSMessage(B_RGBA32_BIG))); 505 mMenu->AddItem(new BMenuItem(B_TRANSLATE("RGB 5:5:5 16 bits " 506 "big-endian"), CSMessage(B_RGB15_BIG))); 507 mMenu->AddItem(new BMenuItem(B_TRANSLATE("RGBA 5:5:5:1 16 " 508 "bits big-endian"), CSMessage(B_RGBA15_BIG))); 509 mMenu->AddItem(new BMenuItem(B_TRANSLATE("RGB 5:6:5 16 bits " 510 "big-endian"), CSMessage(B_RGB16))); 511 mField = new BMenuField(B_TRANSLATE("Input Color Space"), 512 mMenu); 513 mField->SetViewColor(ViewColor()); 514 SelectColorSpace(g_settings.out_space); 515 BMessage * msg = new BMessage(CHANGE_ASCII); 516 mAscii = new BCheckBox(B_TRANSLATE("Write ASCII"), msg); 517 if (g_settings.write_ascii) 518 mAscii->SetValue(1); 519 mAscii->SetViewColor(ViewColor()); 520 521 // Build the layout 522 BLayoutBuilder::Group<>(this, B_VERTICAL, 7) 523 .SetInsets(5) 524 .Add(mTitle) 525 .Add(mDetail) 526 .AddGlue() 527 .Add(mBasedOn) 528 .Add(mCopyright) 529 .AddGlue() 530 .AddGrid(10, 10) 531 .Add(mField->CreateLabelLayoutItem(), 0, 0) 532 .Add(mField->CreateMenuBarLayoutItem(), 1, 0) 533 .Add(mAscii, 0, 1) 534 .End() 535 .AddGlue(); 536 537 BFont font; 538 GetFont(&font); 539 SetExplicitPreferredSize(BSize((font.Size() * 350)/12, (font.Size() * 200)/12)); 540 } 541 ~PPMView() 542 { 543 /* nothing here */ 544 } 545 546 enum { 547 SET_COLOR_SPACE = 'ppm=', 548 CHANGE_ASCII 549 }; 550 551 virtual void MessageReceived( 552 BMessage * message) 553 { 554 if (message->what == SET_COLOR_SPACE) { 555 SetSettings(message); 556 } 557 else if (message->what == CHANGE_ASCII) { 558 BMessage msg; 559 msg.AddBool("ppm /ascii", mAscii->Value()); 560 SetSettings(&msg); 561 } 562 else { 563 BView::MessageReceived(message); 564 } 565 } 566 virtual void AllAttached() 567 { 568 BView::AllAttached(); 569 BMessenger msgr(this); 570 /* Tell all menu items we're the man. */ 571 for (int ix=0; ix<mMenu->CountItems(); ix++) { 572 BMenuItem * i = mMenu->ItemAt(ix); 573 if (i) { 574 i->SetTarget(msgr); 575 } 576 } 577 mAscii->SetTarget(msgr); 578 } 579 580 void SetSettings( 581 BMessage * message) 582 { 583 g_settings_lock.Lock(); 584 color_space space; 585 if (!message->FindInt32("space", (int32*)&space)) { 586 g_settings.out_space = space; 587 SelectColorSpace(space); 588 g_settings.settings_touched = true; 589 } 590 bool ascii; 591 if (!message->FindBool("ppm /ascii", &ascii)) { 592 g_settings.write_ascii = ascii; 593 g_settings.settings_touched = true; 594 } 595 g_settings_lock.Unlock(); 596 } 597 598 private: 599 BStringView * mTitle; 600 BStringView * mDetail; 601 BStringView * mBasedOn; 602 BStringView * mCopyright; 603 BPopUpMenu * mMenu; 604 BMenuField * mField; 605 BCheckBox * mAscii; 606 607 BMessage * CSMessage( 608 color_space space) 609 { 610 BMessage * ret = new BMessage(SET_COLOR_SPACE); 611 ret->AddInt32("space", space); 612 return ret; 613 } 614 615 void SelectColorSpace( 616 color_space space) 617 { 618 for (int ix=0; ix<mMenu->CountItems(); ix++) { 619 int32 s; 620 BMenuItem * i = mMenu->ItemAt(ix); 621 if (i) { 622 BMessage * m = i->Message(); 623 if (m && !m->FindInt32("space", &s) && (s == space)) { 624 mMenu->Superitem()->SetLabel(i->Label()); 625 break; 626 } 627 } 628 } 629 } 630 }; 631 632 633 /* The view will get resized to what the parent thinks is */ 634 /* reasonable. However, it will still receive MouseDowns etc. */ 635 /* Your view should change settings in the translator immediately, */ 636 /* taking care not to change parameters for a translation that is */ 637 /* currently running. Typically, you'll have a global struct for */ 638 /* settings that is atomically copied into the translator function */ 639 /* as a local when translation starts. */ 640 /* Store your settings wherever you feel like it. */ 641 642 status_t 643 MakeConfig( /* optional */ 644 BMessage * ioExtension, /* can be NULL */ 645 BView * * outView, 646 BRect * outExtent) 647 { 648 PPMView * v = new PPMView(B_TRANSLATE("PPMTranslator Settings"), 649 B_WILL_DRAW); 650 *outView = v; 651 v->ResizeTo(v->ExplicitPreferredSize());; 652 *outExtent = v->Bounds(); 653 if (ioExtension) { 654 v->SetSettings(ioExtension); 655 } 656 return B_OK; 657 } 658 659 660 /* Copy your current settings to a BMessage that may be passed */ 661 /* to BTranslators::Translate at some later time when the user wants to */ 662 /* use whatever settings you're using right now. */ 663 664 status_t 665 GetConfigMessage( /* optional */ 666 BMessage * ioExtension) 667 { 668 status_t err = B_OK; 669 #if defined(_PR3_COMPATIBLE_) 670 const char * name = B_TRANSLATOR_EXT_BITMAP_COLOR_SPACE; 671 #else 672 const char * name = B_TRANSLATE_MARK("bits/space"); 673 #endif 674 g_settings_lock.Lock(); 675 (void)ioExtension->RemoveName(name); 676 err = ioExtension->AddInt32(name, g_settings.out_space); 677 g_settings_lock.Unlock(); 678 return err; 679 } 680 681 682 683 684 685 status_t 686 read_ppm_header( 687 BDataIO * inSource, 688 int * width, 689 int * rowbytes, 690 int * height, 691 int * max, 692 bool * ascii, 693 color_space * space, 694 bool * is_ppm, 695 char ** comment) 696 { 697 /* check for PPM magic number */ 698 char ch[2]; 699 if (inSource->Read(ch, 2) != 2) { 700 return B_NO_TRANSLATOR; 701 } 702 /* look for magic number */ 703 if (ch[0] != 'P') { 704 /* B_TRANSLATOR_BITMAP magic? */ 705 if (ch[0] == 'b' && ch[1] == 'i') { 706 *is_ppm = false; 707 return read_bits_header(inSource, 2, width, rowbytes, height, max, ascii, space); 708 } 709 return B_NO_TRANSLATOR; 710 } 711 *is_ppm = true; 712 if (ch[1] == '6') { 713 *ascii = false; 714 } 715 else if (ch[1] == '3') { 716 *ascii = true; 717 } 718 else { 719 return B_NO_TRANSLATOR; 720 } 721 // status_t err = B_NO_TRANSLATOR; 722 enum scan_state { 723 scan_width, 724 scan_height, 725 scan_max, 726 scan_white 727 } state = scan_width; 728 int * scan = NULL; 729 bool in_comment = false; 730 *space = B_RGB24_BIG; 731 /* The description of PPM is slightly ambiguous as far as comments */ 732 /* go. We choose to allow comments anywhere, in the spirit of laxness. */ 733 /* See http://www.dcs.ed.ac.uk/~mxr/gfx/2d/PPM.txt */ 734 int comlen = 0; 735 int comptr = 0; 736 while (inSource->Read(ch, 1) == 1) { 737 if (in_comment && (ch[0] != 10) && (ch[0] != 13)) { 738 if (comment) { /* collect comment(s) into comment string */ 739 if (comptr >= comlen-1) { 740 char * n = (char *)realloc(*comment, comlen+100); 741 if (!n) { 742 free(*comment); 743 *comment = NULL; 744 } 745 *comment = n; 746 comlen += 100; 747 } 748 (*comment)[comptr++] = ch[0]; 749 (*comment)[comptr] = 0; 750 } 751 continue; 752 } 753 in_comment = false; 754 if (ch[0] == '#') { 755 in_comment = true; 756 continue; 757 } 758 /* once we're done with whitespace after max, we're done with header */ 759 if (isdigit(ch[0])) { 760 if (!scan) { /* first digit for this value */ 761 switch(state) { 762 case scan_width: 763 scan = width; 764 break; 765 case scan_height: 766 *rowbytes = *width*3; 767 scan = height; 768 break; 769 case scan_max: 770 scan = max; 771 break; 772 default: 773 return B_OK; /* done with header, all OK */ 774 } 775 *scan = 0; 776 } 777 *scan = (*scan)*10 + (ch[0]-'0'); 778 } 779 else if (isspace(ch[0])) { 780 if (scan) { /* are we done with one value? */ 781 scan = NULL; 782 state = (enum scan_state)(state+1); 783 } 784 if (state == scan_white) { /* we only ever read one whitespace, since we skip space */ 785 return B_OK; /* when reading ASCII, and there is a single whitespace after max in raw mode */ 786 } 787 } 788 else { 789 if (state != scan_white) { 790 return B_NO_TRANSLATOR; 791 } 792 return B_OK; /* header done */ 793 } 794 } 795 return B_NO_TRANSLATOR; 796 } 797 798 799 800 status_t 801 read_bits_header( 802 BDataIO * io, 803 int skipped, 804 int * width, 805 int * rowbytes, 806 int * height, 807 int * max, 808 bool * ascii, 809 color_space * space) 810 { 811 /* read the rest of a possible B_TRANSLATOR_BITMAP header */ 812 if (skipped < 0 || skipped > 4) return B_NO_TRANSLATOR; 813 int rd = sizeof(TranslatorBitmap)-skipped; 814 TranslatorBitmap hdr; 815 /* pre-initialize magic because we might have skipped part of it already */ 816 hdr.magic = B_HOST_TO_BENDIAN_INT32(B_TRANSLATOR_BITMAP); 817 char * ptr = (char *)&hdr; 818 if (io->Read(ptr+skipped, rd) != rd) { 819 return B_NO_TRANSLATOR; 820 } 821 /* swap header values */ 822 hdr.magic = B_BENDIAN_TO_HOST_INT32(hdr.magic); 823 hdr.bounds.left = B_BENDIAN_TO_HOST_FLOAT(hdr.bounds.left); 824 hdr.bounds.right = B_BENDIAN_TO_HOST_FLOAT(hdr.bounds.right); 825 hdr.bounds.top = B_BENDIAN_TO_HOST_FLOAT(hdr.bounds.top); 826 hdr.bounds.bottom = B_BENDIAN_TO_HOST_FLOAT(hdr.bounds.bottom); 827 hdr.rowBytes = B_BENDIAN_TO_HOST_INT32(hdr.rowBytes); 828 hdr.colors = (color_space)B_BENDIAN_TO_HOST_INT32(hdr.colors); 829 hdr.dataSize = B_BENDIAN_TO_HOST_INT32(hdr.dataSize); 830 /* sanity checking */ 831 if (hdr.magic != B_TRANSLATOR_BITMAP) { 832 return B_NO_TRANSLATOR; 833 } 834 if (hdr.colors & 0xffff0000) { /* according to <GraphicsDefs.h> this is a reasonable check. */ 835 return B_NO_TRANSLATOR; 836 } 837 if (hdr.rowBytes * (hdr.bounds.Height()+1) > hdr.dataSize) { 838 return B_NO_TRANSLATOR; 839 } 840 /* return information about the data in the stream */ 841 *width = (int)hdr.bounds.Width()+1; 842 *rowbytes = hdr.rowBytes; 843 *height = (int)hdr.bounds.Height()+1; 844 *max = 255; 845 *ascii = false; 846 *space = hdr.colors; 847 return B_OK; 848 } 849 850 851 /* String may contain newlines, after which we need to insert extra hash signs. */ 852 status_t 853 write_comment( 854 const char * str, 855 BDataIO * io) 856 { 857 const char * end = str+strlen(str); 858 const char * ptr = str; 859 status_t err = B_OK; 860 /* write each line as it's found */ 861 while ((ptr < end) && !err) { 862 if ((*ptr == 10) || (*ptr == 13)) { 863 err = io->Write("# ", 2); 864 if (err == 2) { 865 err = io->Write(str, ptr-str); 866 if (err == ptr-str) { 867 if (io->Write("\n", 1) == 1) { 868 err = 0; 869 } 870 } 871 } 872 str = ptr+1; 873 } 874 ptr++; 875 } 876 /* write the last data, if any, as a line */ 877 if (ptr > str) { 878 err = io->Write("# ", 2); 879 if (err == 2) { 880 err = io->Write(str, ptr-str); 881 if (err == ptr-str) { 882 if (io->Write("\n", 1) == 1) { 883 err = 0; 884 } 885 } 886 } 887 } 888 if (err > 0) { 889 err = B_IO_ERROR; 890 } 891 return err; 892 } 893 894 895 static status_t 896 read_ascii_line( 897 BDataIO * in, 898 int max, 899 unsigned char * data, 900 int rowbytes) 901 { 902 char ch; 903 status_t err; 904 // int nread = 0; 905 bool comment = false; 906 int val = 0; 907 bool dig = false; 908 while ((err = in->Read(&ch, 1)) == 1) { 909 if (comment) { 910 if ((ch == '\n') || (ch == '\r')) { 911 comment = false; 912 } 913 } 914 if (isdigit(ch)) { 915 dig = true; 916 val = val * 10 + (ch - '0'); 917 } 918 else { 919 if (dig) { 920 *(data++) = val*255/max; 921 val = 0; 922 rowbytes--; 923 dig = false; 924 } 925 if (ch == '#') { 926 comment = true; 927 continue; 928 } 929 } 930 if (rowbytes < 1) { 931 break; 932 } 933 } 934 if (dig) { 935 *(data++) = val*255/max; 936 val = 0; 937 rowbytes--; 938 dig = false; 939 } 940 if (rowbytes < 1) { 941 return B_OK; 942 } 943 return B_IO_ERROR; 944 } 945 946 947 static status_t 948 write_ascii_line( 949 BDataIO * out, 950 unsigned char * data, 951 int rowbytes) 952 { 953 char buffer[20]; 954 int linelen = 0; 955 while (rowbytes > 2) { 956 sprintf(buffer, "%d %d %d ", data[0], data[1], data[2]); 957 rowbytes -= 3; 958 int l = strlen(buffer); 959 if (l + linelen > 70) { 960 out->Write("\n", 1); 961 linelen = 0; 962 } 963 if (out->Write(buffer, l) != l) { 964 return B_IO_ERROR; 965 } 966 linelen += l; 967 data += 3; 968 } 969 out->Write("\n", 1); /* terminate each scanline */ 970 return B_OK; 971 } 972 973 974 static unsigned char * 975 make_scale_data( 976 int max) 977 { 978 unsigned char * ptr = (unsigned char *)malloc(max); 979 for (int ix=0; ix<max; ix++) { 980 ptr[ix] = ix*255/max; 981 } 982 return ptr; 983 } 984 985 986 static void 987 scale_data( 988 unsigned char * scale, 989 unsigned char * data, 990 int bytes) 991 { 992 for (int ix=0; ix<bytes; ix++) { 993 data[ix] = scale[data[ix]]; 994 } 995 } 996 997 998 status_t 999 copy_data( 1000 BDataIO * in, 1001 BDataIO * out, 1002 int rowbytes, 1003 int out_rowbytes, 1004 int height, 1005 int max, 1006 bool in_ascii, 1007 bool out_ascii, 1008 color_space in_space, 1009 color_space out_space) 1010 { 1011 dprintf(("copy_data(%x, %x, %x, %x, %x, %x)\n", rowbytes, out_rowbytes, height, max, in_space, out_space)); 1012 /* We read/write one scanline at a time. */ 1013 unsigned char * data = (unsigned char *)malloc(rowbytes); 1014 unsigned char * out_data = (unsigned char *)malloc(out_rowbytes); 1015 if (data == NULL || out_data == NULL) { 1016 free(data); 1017 free(out_data); 1018 return B_NO_MEMORY; 1019 } 1020 unsigned char * scale = NULL; 1021 if (max != 255) { 1022 scale = make_scale_data(max); 1023 } 1024 status_t err = B_OK; 1025 /* There is no data format conversion, so we can just copy data. */ 1026 while ((height-- > 0) && !err) { 1027 if (in_ascii) { 1028 err = read_ascii_line(in, max, data, rowbytes); 1029 } 1030 else { 1031 err = in->Read(data, rowbytes); 1032 if (err == rowbytes) { 1033 err = B_OK; 1034 } 1035 if (scale) { /* for reading PPM that is smaller than 8 bit */ 1036 scale_data(scale, data, rowbytes); 1037 } 1038 } 1039 if (err == B_OK) { 1040 unsigned char * wbuf = data; 1041 if (in_space != out_space) { 1042 err = convert_space(in_space, out_space, data, rowbytes, out_data); 1043 wbuf = out_data; 1044 } 1045 if (!err && out_ascii) { 1046 err = write_ascii_line(out, wbuf, out_rowbytes); 1047 } 1048 else if (!err) { 1049 err = out->Write(wbuf, out_rowbytes); 1050 if (err == out_rowbytes) { 1051 err = B_OK; 1052 } 1053 } 1054 } 1055 } 1056 free(data); 1057 free(out_data); 1058 free(scale); 1059 return err > 0 ? B_IO_ERROR : err; 1060 } 1061 1062 1063