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