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