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