TGASharpLib.cs (220069B)
1 // 2 // Copyright 2020 Electronic Arts Inc. 3 // 4 // The Command & Conquer Map Editor and corresponding source code is free 5 // software: you can redistribute it and/or modify it under the terms of 6 // the GNU General Public License as published by the Free Software Foundation, 7 // either version 3 of the License, or (at your option) any later version. 8 9 // The Command & Conquer Map Editor and corresponding source code is distributed 10 // in the hope that it will be useful, but with permitted additional restrictions 11 // under Section 7 of the GPL. See the GNU General Public License in LICENSE.TXT 12 // distributed with this program. You should have received a copy of the 13 // GNU General Public License along with permitted additional restrictions 14 // with this program. If not, see https://github.com/electronicarts/CnC_Remastered_Collection 15 /* MIT License 16 Copyright (c) 2017 TGASharpLib 17 18 Permission is hereby granted, free of charge, to any person obtaining a copy 19 of this software and associated documentation files (the "Software"), to deal 20 in the Software without restriction, including without limitation the rights 21 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 22 copies of the Software, and to permit persons to whom the Software is 23 furnished to do so, subject to the following conditions: 24 25 The above copyright notice and this permission notice shall be included in all 26 copies or substantial portions of the Software. 27 28 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 29 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 30 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 31 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 32 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 33 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 34 SOFTWARE. 35 */ 36 37 using System; 38 using System.Collections.Generic; 39 using System.Text; 40 using System.Drawing; 41 using System.Drawing.Imaging; 42 using System.IO; 43 using System.Runtime.InteropServices; 44 45 namespace TGASharpLib 46 { 47 #region Enums 48 /// <summary> 49 /// <para>The first 128 Color Map Type codes are reserved for use by Truevision, 50 /// while the second set of 128 Color Map Type codes(128 to 255) may be used for 51 /// developer applications.</para> 52 /// True-Color images do not normally make use of the color map field, but some current 53 /// applications store palette information or developer-defined information in this field. 54 /// It is best to check Field 3, Image Type, to make sure you have a file which can use the 55 /// data stored in the Color Map Field. 56 /// Otherwise ignore the information. When saving or creating files for True-Color 57 /// images do not use this field and set it to Zero to ensure compatibility. Please refer 58 /// to the Developer Area specification for methods of storing developer defined information. 59 /// </summary> 60 public enum TgaColorMapType : byte 61 { 62 NoColorMap = 0, 63 ColorMap = 1, 64 Truevision_2, 65 Truevision_3, 66 Truevision_4, 67 Truevision_5, 68 Truevision_6, 69 Truevision_7, 70 Truevision_8, 71 Truevision_9, 72 Truevision_10, 73 Truevision_11, 74 Truevision_12, 75 Truevision_13, 76 Truevision_14, 77 Truevision_15, 78 Truevision_16, 79 Truevision_17, 80 Truevision_18, 81 Truevision_19, 82 Truevision_20, 83 Truevision_21, 84 Truevision_22, 85 Truevision_23, 86 Truevision_24, 87 Truevision_25, 88 Truevision_26, 89 Truevision_27, 90 Truevision_28, 91 Truevision_29, 92 Truevision_30, 93 Truevision_31, 94 Truevision_32, 95 Truevision_33, 96 Truevision_34, 97 Truevision_35, 98 Truevision_36, 99 Truevision_37, 100 Truevision_38, 101 Truevision_39, 102 Truevision_40, 103 Truevision_41, 104 Truevision_42, 105 Truevision_43, 106 Truevision_44, 107 Truevision_45, 108 Truevision_46, 109 Truevision_47, 110 Truevision_48, 111 Truevision_49, 112 Truevision_50, 113 Truevision_51, 114 Truevision_52, 115 Truevision_53, 116 Truevision_54, 117 Truevision_55, 118 Truevision_56, 119 Truevision_57, 120 Truevision_58, 121 Truevision_59, 122 Truevision_60, 123 Truevision_61, 124 Truevision_62, 125 Truevision_63, 126 Truevision_64, 127 Truevision_65, 128 Truevision_66, 129 Truevision_67, 130 Truevision_68, 131 Truevision_69, 132 Truevision_70, 133 Truevision_71, 134 Truevision_72, 135 Truevision_73, 136 Truevision_74, 137 Truevision_75, 138 Truevision_76, 139 Truevision_77, 140 Truevision_78, 141 Truevision_79, 142 Truevision_80, 143 Truevision_81, 144 Truevision_82, 145 Truevision_83, 146 Truevision_84, 147 Truevision_85, 148 Truevision_86, 149 Truevision_87, 150 Truevision_88, 151 Truevision_89, 152 Truevision_90, 153 Truevision_91, 154 Truevision_92, 155 Truevision_93, 156 Truevision_94, 157 Truevision_95, 158 Truevision_96, 159 Truevision_97, 160 Truevision_98, 161 Truevision_99, 162 Truevision_100, 163 Truevision_101, 164 Truevision_102, 165 Truevision_103, 166 Truevision_104, 167 Truevision_105, 168 Truevision_106, 169 Truevision_107, 170 Truevision_108, 171 Truevision_109, 172 Truevision_110, 173 Truevision_111, 174 Truevision_112, 175 Truevision_113, 176 Truevision_114, 177 Truevision_115, 178 Truevision_116, 179 Truevision_117, 180 Truevision_118, 181 Truevision_119, 182 Truevision_120, 183 Truevision_121, 184 Truevision_122, 185 Truevision_123, 186 Truevision_124, 187 Truevision_125, 188 Truevision_126, 189 Truevision_127, 190 Other_128, 191 Other_129, 192 Other_130, 193 Other_131, 194 Other_132, 195 Other_133, 196 Other_134, 197 Other_135, 198 Other_136, 199 Other_137, 200 Other_138, 201 Other_139, 202 Other_140, 203 Other_141, 204 Other_142, 205 Other_143, 206 Other_144, 207 Other_145, 208 Other_146, 209 Other_147, 210 Other_148, 211 Other_149, 212 Other_150, 213 Other_151, 214 Other_152, 215 Other_153, 216 Other_154, 217 Other_155, 218 Other_156, 219 Other_157, 220 Other_158, 221 Other_159, 222 Other_160, 223 Other_161, 224 Other_162, 225 Other_163, 226 Other_164, 227 Other_165, 228 Other_166, 229 Other_167, 230 Other_168, 231 Other_169, 232 Other_170, 233 Other_171, 234 Other_172, 235 Other_173, 236 Other_174, 237 Other_175, 238 Other_176, 239 Other_177, 240 Other_178, 241 Other_179, 242 Other_180, 243 Other_181, 244 Other_182, 245 Other_183, 246 Other_184, 247 Other_185, 248 Other_186, 249 Other_187, 250 Other_188, 251 Other_189, 252 Other_190, 253 Other_191, 254 Other_192, 255 Other_193, 256 Other_194, 257 Other_195, 258 Other_196, 259 Other_197, 260 Other_198, 261 Other_199, 262 Other_200, 263 Other_201, 264 Other_202, 265 Other_203, 266 Other_204, 267 Other_205, 268 Other_206, 269 Other_207, 270 Other_208, 271 Other_209, 272 Other_210, 273 Other_211, 274 Other_212, 275 Other_213, 276 Other_214, 277 Other_215, 278 Other_216, 279 Other_217, 280 Other_218, 281 Other_219, 282 Other_220, 283 Other_221, 284 Other_222, 285 Other_223, 286 Other_224, 287 Other_225, 288 Other_226, 289 Other_227, 290 Other_228, 291 Other_229, 292 Other_230, 293 Other_231, 294 Other_232, 295 Other_233, 296 Other_234, 297 Other_235, 298 Other_236, 299 Other_237, 300 Other_238, 301 Other_239, 302 Other_240, 303 Other_241, 304 Other_242, 305 Other_243, 306 Other_244, 307 Other_245, 308 Other_246, 309 Other_247, 310 Other_248, 311 Other_249, 312 Other_250, 313 Other_251, 314 Other_252, 315 Other_253, 316 Other_254, 317 Other_255 318 } 319 320 /// <summary> 321 /// Establishes the number of bits per entry. Typically 15, 16, 24 or 32-bit values are used. 322 /// <para>When working with VDA or VDA/D cards it is preferred that you select 16 bits(5 bits 323 /// per primary with 1 bit to select interrupt control) and set the 16th bit to 0 so that the 324 /// interrupt bit is disabled. Even if this field is set to 15 bits(5 bits per primary) you 325 /// must still parse the color map data 16 bits at a time and ignore the 16th bit.</para> 326 /// <para>When working with a TARGA M8 card you would select 24 bits (8 bits per primary) 327 /// since the color map is defined as 256 entries of 24 bit color values.</para> 328 /// When working with a TrueVista card(ATVista or NuVista) you would select 24-bit(8 bits per 329 /// primary) or 32-bit(8 bits per primary including Alpha channel) depending on your 330 /// application’s use of look-up tables. It is suggested that when working with 16-bit and 331 /// 32-bit color images, you store them as True-Color images and do not use the color map 332 /// field to store look-up tables. Please refer to the TGA Extensions for fields better suited 333 /// to storing look-up table information. 334 /// </summary> 335 public enum TgaColorMapEntrySize : byte 336 { 337 Other = 0, 338 X1R5G5B5 = 15, 339 A1R5G5B5 = 16, 340 R8G8B8 = 24, 341 A8R8G8B8 = 32 342 } 343 344 /// <summary> 345 /// Truevision has currently defined seven image types: 346 /// <para>0 - No Image Data Included;</para> 347 /// <para>1 - Uncompressed, Color-mapped Image;</para> 348 /// <para>2 - Uncompressed, True-color Image;</para> 349 /// <para>3 - Uncompressed, Black-and-white Image;</para> 350 /// <para>9 - Run-length encoded, Color-mapped Image;</para> 351 /// <para>10 - Run-length encoded, True-color Image;</para> 352 /// <para>11 - Run-length encoded, Black-and-white Image.</para> 353 /// Image Data Type codes 0 to 127 are reserved for use by Truevision for general applications. 354 /// Image Data Type codes 128 to 255 may be used for developer applications. 355 /// </summary> 356 public enum TgaImageType : byte 357 { 358 NoImageData = 0, 359 Uncompressed_ColorMapped = 1, 360 Uncompressed_TrueColor, 361 Uncompressed_BlackWhite, 362 _Truevision_4, 363 _Truevision_5, 364 _Truevision_6, 365 _Truevision_7, 366 _Truevision_8, 367 RLE_ColorMapped = 9, 368 RLE_TrueColor, 369 RLE_BlackWhite, 370 _Truevision_12, 371 _Truevision_13, 372 _Truevision_14, 373 _Truevision_15, 374 _Truevision_16, 375 _Truevision_17, 376 _Truevision_18, 377 _Truevision_19, 378 _Truevision_20, 379 _Truevision_21, 380 _Truevision_22, 381 _Truevision_23, 382 _Truevision_24, 383 _Truevision_25, 384 _Truevision_26, 385 _Truevision_27, 386 _Truevision_28, 387 _Truevision_29, 388 _Truevision_30, 389 _Truevision_31, 390 _Truevision_32, 391 _Truevision_33, 392 _Truevision_34, 393 _Truevision_35, 394 _Truevision_36, 395 _Truevision_37, 396 _Truevision_38, 397 _Truevision_39, 398 _Truevision_40, 399 _Truevision_41, 400 _Truevision_42, 401 _Truevision_43, 402 _Truevision_44, 403 _Truevision_45, 404 _Truevision_46, 405 _Truevision_47, 406 _Truevision_48, 407 _Truevision_49, 408 _Truevision_50, 409 _Truevision_51, 410 _Truevision_52, 411 _Truevision_53, 412 _Truevision_54, 413 _Truevision_55, 414 _Truevision_56, 415 _Truevision_57, 416 _Truevision_58, 417 _Truevision_59, 418 _Truevision_60, 419 _Truevision_61, 420 _Truevision_62, 421 _Truevision_63, 422 _Truevision_64, 423 _Truevision_65, 424 _Truevision_66, 425 _Truevision_67, 426 _Truevision_68, 427 _Truevision_69, 428 _Truevision_70, 429 _Truevision_71, 430 _Truevision_72, 431 _Truevision_73, 432 _Truevision_74, 433 _Truevision_75, 434 _Truevision_76, 435 _Truevision_77, 436 _Truevision_78, 437 _Truevision_79, 438 _Truevision_80, 439 _Truevision_81, 440 _Truevision_82, 441 _Truevision_83, 442 _Truevision_84, 443 _Truevision_85, 444 _Truevision_86, 445 _Truevision_87, 446 _Truevision_88, 447 _Truevision_89, 448 _Truevision_90, 449 _Truevision_91, 450 _Truevision_92, 451 _Truevision_93, 452 _Truevision_94, 453 _Truevision_95, 454 _Truevision_96, 455 _Truevision_97, 456 _Truevision_98, 457 _Truevision_99, 458 _Truevision_100, 459 _Truevision_101, 460 _Truevision_102, 461 _Truevision_103, 462 _Truevision_104, 463 _Truevision_105, 464 _Truevision_106, 465 _Truevision_107, 466 _Truevision_108, 467 _Truevision_109, 468 _Truevision_110, 469 _Truevision_111, 470 _Truevision_112, 471 _Truevision_113, 472 _Truevision_114, 473 _Truevision_115, 474 _Truevision_116, 475 _Truevision_117, 476 _Truevision_118, 477 _Truevision_119, 478 _Truevision_120, 479 _Truevision_121, 480 _Truevision_122, 481 _Truevision_123, 482 _Truevision_124, 483 _Truevision_125, 484 _Truevision_126, 485 _Truevision_127, 486 _Other_128, 487 _Other_129, 488 _Other_130, 489 _Other_131, 490 _Other_132, 491 _Other_133, 492 _Other_134, 493 _Other_135, 494 _Other_136, 495 _Other_137, 496 _Other_138, 497 _Other_139, 498 _Other_140, 499 _Other_141, 500 _Other_142, 501 _Other_143, 502 _Other_144, 503 _Other_145, 504 _Other_146, 505 _Other_147, 506 _Other_148, 507 _Other_149, 508 _Other_150, 509 _Other_151, 510 _Other_152, 511 _Other_153, 512 _Other_154, 513 _Other_155, 514 _Other_156, 515 _Other_157, 516 _Other_158, 517 _Other_159, 518 _Other_160, 519 _Other_161, 520 _Other_162, 521 _Other_163, 522 _Other_164, 523 _Other_165, 524 _Other_166, 525 _Other_167, 526 _Other_168, 527 _Other_169, 528 _Other_170, 529 _Other_171, 530 _Other_172, 531 _Other_173, 532 _Other_174, 533 _Other_175, 534 _Other_176, 535 _Other_177, 536 _Other_178, 537 _Other_179, 538 _Other_180, 539 _Other_181, 540 _Other_182, 541 _Other_183, 542 _Other_184, 543 _Other_185, 544 _Other_186, 545 _Other_187, 546 _Other_188, 547 _Other_189, 548 _Other_190, 549 _Other_191, 550 _Other_192, 551 _Other_193, 552 _Other_194, 553 _Other_195, 554 _Other_196, 555 _Other_197, 556 _Other_198, 557 _Other_199, 558 _Other_200, 559 _Other_201, 560 _Other_202, 561 _Other_203, 562 _Other_204, 563 _Other_205, 564 _Other_206, 565 _Other_207, 566 _Other_208, 567 _Other_209, 568 _Other_210, 569 _Other_211, 570 _Other_212, 571 _Other_213, 572 _Other_214, 573 _Other_215, 574 _Other_216, 575 _Other_217, 576 _Other_218, 577 _Other_219, 578 _Other_220, 579 _Other_221, 580 _Other_222, 581 _Other_223, 582 _Other_224, 583 _Other_225, 584 _Other_226, 585 _Other_227, 586 _Other_228, 587 _Other_229, 588 _Other_230, 589 _Other_231, 590 _Other_232, 591 _Other_233, 592 _Other_234, 593 _Other_235, 594 _Other_236, 595 _Other_237, 596 _Other_238, 597 _Other_239, 598 _Other_240, 599 _Other_241, 600 _Other_242, 601 _Other_243, 602 _Other_244, 603 _Other_245, 604 _Other_246, 605 _Other_247, 606 _Other_248, 607 _Other_249, 608 _Other_250, 609 _Other_251, 610 _Other_252, 611 _Other_253, 612 _Other_254, 613 _Other_255 614 } 615 616 /// <summary> 617 /// Number of bits per pixel. This number includes the Attribute or Alpha channel bits. 618 /// Common values are 8, 16, 24 and 32 but other pixel depths could be used. 619 /// </summary> 620 public enum TgaPixelDepth : byte 621 { 622 Other = 0, 623 Bpp8 = 8, 624 Bpp16 = 16, 625 Bpp24 = 24, 626 Bpp32 = 32 627 } 628 629 /// <summary> 630 /// Used to indicate the order in which pixel data is transferred from the file to the screen. 631 /// (Bit 4 (bit 0 in enum) is for left-to-right ordering and bit 5 (bit 1 in enum) is for 632 /// topto-bottom ordering as shown below.) 633 /// </summary> 634 public enum TgaImgOrigin : byte 635 { 636 BottomLeft = 0, 637 BottomRight, 638 TopLeft, 639 TopRight 640 } 641 642 /// <summary> 643 /// Contains a value which specifies the type of Alpha channel 644 /// data contained in the file. Value Meaning: 645 /// <para>0: no Alpha data included (bits 3-0 of field 5.6 should also be set to zero)</para> 646 /// <para>1: undefined data in the Alpha field, can be ignored</para> 647 /// <para>2: undefined data in the Alpha field, but should be retained</para> 648 /// <para>3: useful Alpha channel data is present</para> 649 /// <para>4: pre-multiplied Alpha(see description below)</para> 650 /// <para>5 -127: RESERVED</para> 651 /// <para>128-255: Un-assigned</para> 652 /// <para>Pre-multiplied Alpha Example: Suppose the Alpha channel data is being used to specify the 653 /// opacity of each pixel(for use when the image is overlayed on another image), where 0 indicates 654 /// that the pixel is completely transparent and a value of 1 indicates that the pixel is 655 /// completely opaque(assume all component values have been normalized).</para> 656 /// <para>A quadruple(a, r, g, b) of( 0.5, 1, 0, 0) would indicate that the pixel is pure red with a 657 /// transparency of one-half. For numerous reasons(including image compositing) is is better to 658 /// pre-multiply the individual color components with the value in the Alpha channel.</para> 659 /// A pre-multiplication of the above would produce a quadruple(0.5, 0.5, 0, 0). 660 /// A value of 3 in the Attributes Type Field(field 23) would indicate that the color components 661 /// of the pixel have already been scaled by the value in the Alpha channel. 662 /// </summary> 663 public enum TgaAttrType : byte 664 { 665 NoAlpha = 0, 666 UndefinedAlphaCanBeIgnored, 667 UndefinedAlphaButShouldBeRetained, 668 UsefulAlpha, 669 PreMultipliedAlpha, 670 _Reserved_5, 671 _Reserved_6, 672 _Reserved_7, 673 _Reserved_8, 674 _Reserved_9, 675 _Reserved_10, 676 _Reserved_11, 677 _Reserved_12, 678 _Reserved_13, 679 _Reserved_14, 680 _Reserved_15, 681 _Reserved_16, 682 _Reserved_17, 683 _Reserved_18, 684 _Reserved_19, 685 _Reserved_20, 686 _Reserved_21, 687 _Reserved_22, 688 _Reserved_23, 689 _Reserved_24, 690 _Reserved_25, 691 _Reserved_26, 692 _Reserved_27, 693 _Reserved_28, 694 _Reserved_29, 695 _Reserved_30, 696 _Reserved_31, 697 _Reserved_32, 698 _Reserved_33, 699 _Reserved_34, 700 _Reserved_35, 701 _Reserved_36, 702 _Reserved_37, 703 _Reserved_38, 704 _Reserved_39, 705 _Reserved_40, 706 _Reserved_41, 707 _Reserved_42, 708 _Reserved_43, 709 _Reserved_44, 710 _Reserved_45, 711 _Reserved_46, 712 _Reserved_47, 713 _Reserved_48, 714 _Reserved_49, 715 _Reserved_50, 716 _Reserved_51, 717 _Reserved_52, 718 _Reserved_53, 719 _Reserved_54, 720 _Reserved_55, 721 _Reserved_56, 722 _Reserved_57, 723 _Reserved_58, 724 _Reserved_59, 725 _Reserved_60, 726 _Reserved_61, 727 _Reserved_62, 728 _Reserved_63, 729 _Reserved_64, 730 _Reserved_65, 731 _Reserved_66, 732 _Reserved_67, 733 _Reserved_68, 734 _Reserved_69, 735 _Reserved_70, 736 _Reserved_71, 737 _Reserved_72, 738 _Reserved_73, 739 _Reserved_74, 740 _Reserved_75, 741 _Reserved_76, 742 _Reserved_77, 743 _Reserved_78, 744 _Reserved_79, 745 _Reserved_80, 746 _Reserved_81, 747 _Reserved_82, 748 _Reserved_83, 749 _Reserved_84, 750 _Reserved_85, 751 _Reserved_86, 752 _Reserved_87, 753 _Reserved_88, 754 _Reserved_89, 755 _Reserved_90, 756 _Reserved_91, 757 _Reserved_92, 758 _Reserved_93, 759 _Reserved_94, 760 _Reserved_95, 761 _Reserved_96, 762 _Reserved_97, 763 _Reserved_98, 764 _Reserved_99, 765 _Reserved_100, 766 _Reserved_101, 767 _Reserved_102, 768 _Reserved_103, 769 _Reserved_104, 770 _Reserved_105, 771 _Reserved_106, 772 _Reserved_107, 773 _Reserved_108, 774 _Reserved_109, 775 _Reserved_110, 776 _Reserved_111, 777 _Reserved_112, 778 _Reserved_113, 779 _Reserved_114, 780 _Reserved_115, 781 _Reserved_116, 782 _Reserved_117, 783 _Reserved_118, 784 _Reserved_119, 785 _Reserved_120, 786 _Reserved_121, 787 _Reserved_122, 788 _Reserved_123, 789 _Reserved_124, 790 _Reserved_125, 791 _Reserved_126, 792 _Reserved_127, 793 _UnAssigned_128, 794 _UnAssigned_129, 795 _UnAssigned_130, 796 _UnAssigned_131, 797 _UnAssigned_132, 798 _UnAssigned_133, 799 _UnAssigned_134, 800 _UnAssigned_135, 801 _UnAssigned_136, 802 _UnAssigned_137, 803 _UnAssigned_138, 804 _UnAssigned_139, 805 _UnAssigned_140, 806 _UnAssigned_141, 807 _UnAssigned_142, 808 _UnAssigned_143, 809 _UnAssigned_144, 810 _UnAssigned_145, 811 _UnAssigned_146, 812 _UnAssigned_147, 813 _UnAssigned_148, 814 _UnAssigned_149, 815 _UnAssigned_150, 816 _UnAssigned_151, 817 _UnAssigned_152, 818 _UnAssigned_153, 819 _UnAssigned_154, 820 _UnAssigned_155, 821 _UnAssigned_156, 822 _UnAssigned_157, 823 _UnAssigned_158, 824 _UnAssigned_159, 825 _UnAssigned_160, 826 _UnAssigned_161, 827 _UnAssigned_162, 828 _UnAssigned_163, 829 _UnAssigned_164, 830 _UnAssigned_165, 831 _UnAssigned_166, 832 _UnAssigned_167, 833 _UnAssigned_168, 834 _UnAssigned_169, 835 _UnAssigned_170, 836 _UnAssigned_171, 837 _UnAssigned_172, 838 _UnAssigned_173, 839 _UnAssigned_174, 840 _UnAssigned_175, 841 _UnAssigned_176, 842 _UnAssigned_177, 843 _UnAssigned_178, 844 _UnAssigned_179, 845 _UnAssigned_180, 846 _UnAssigned_181, 847 _UnAssigned_182, 848 _UnAssigned_183, 849 _UnAssigned_184, 850 _UnAssigned_185, 851 _UnAssigned_186, 852 _UnAssigned_187, 853 _UnAssigned_188, 854 _UnAssigned_189, 855 _UnAssigned_190, 856 _UnAssigned_191, 857 _UnAssigned_192, 858 _UnAssigned_193, 859 _UnAssigned_194, 860 _UnAssigned_195, 861 _UnAssigned_196, 862 _UnAssigned_197, 863 _UnAssigned_198, 864 _UnAssigned_199, 865 _UnAssigned_200, 866 _UnAssigned_201, 867 _UnAssigned_202, 868 _UnAssigned_203, 869 _UnAssigned_204, 870 _UnAssigned_205, 871 _UnAssigned_206, 872 _UnAssigned_207, 873 _UnAssigned_208, 874 _UnAssigned_209, 875 _UnAssigned_210, 876 _UnAssigned_211, 877 _UnAssigned_212, 878 _UnAssigned_213, 879 _UnAssigned_214, 880 _UnAssigned_215, 881 _UnAssigned_216, 882 _UnAssigned_217, 883 _UnAssigned_218, 884 _UnAssigned_219, 885 _UnAssigned_220, 886 _UnAssigned_221, 887 _UnAssigned_222, 888 _UnAssigned_223, 889 _UnAssigned_224, 890 _UnAssigned_225, 891 _UnAssigned_226, 892 _UnAssigned_227, 893 _UnAssigned_228, 894 _UnAssigned_229, 895 _UnAssigned_230, 896 _UnAssigned_231, 897 _UnAssigned_232, 898 _UnAssigned_233, 899 _UnAssigned_234, 900 _UnAssigned_235, 901 _UnAssigned_236, 902 _UnAssigned_237, 903 _UnAssigned_238, 904 _UnAssigned_239, 905 _UnAssigned_240, 906 _UnAssigned_241, 907 _UnAssigned_242, 908 _UnAssigned_243, 909 _UnAssigned_244, 910 _UnAssigned_245, 911 _UnAssigned_246, 912 _UnAssigned_247, 913 _UnAssigned_248, 914 _UnAssigned_249, 915 _UnAssigned_250, 916 _UnAssigned_251, 917 _UnAssigned_252, 918 _UnAssigned_253, 919 _UnAssigned_254, 920 _UnAssigned_255 921 } 922 #endregion 923 924 #region Classes 925 public class TgaColorKey : ICloneable 926 { 927 byte a = 0; 928 byte r = 0; 929 byte g = 0; 930 byte b = 0; 931 932 public TgaColorKey() 933 { 934 } 935 936 /// <summary> 937 /// Make <see cref="TgaColorKey"/> from ARGB bytes. 938 /// </summary> 939 /// <param name="A">Alpha value.</param> 940 /// <param name="R">Red value.</param> 941 /// <param name="G">Green value.</param> 942 /// <param name="B">Blue value.</param> 943 public TgaColorKey(byte A, byte R, byte G, byte B) 944 { 945 a = A; 946 r = R; 947 g = G; 948 b = B; 949 } 950 951 /// <summary> 952 /// Make <see cref="TgaColorKey"/> from ARGB bytes. 953 /// </summary> 954 /// <param name="Bytes">Array of bytes(byte[4]).</param> 955 public TgaColorKey(byte[] Bytes) 956 { 957 if (Bytes == null) 958 throw new ArgumentNullException(nameof(Bytes) + " = null!"); 959 if (Bytes.Length != Size) 960 throw new ArgumentOutOfRangeException(nameof(Bytes.Length) + " must be equal " + Size + "!"); 961 962 Color color = Color.FromArgb(BitConverter.ToInt32(Bytes, 0)); 963 a = color.A; 964 r = color.R; 965 g = color.G; 966 b = color.B; 967 } 968 969 /// <summary> 970 /// Make <see cref="TgaColorKey"/> from <see cref="int"/>. 971 /// </summary> 972 /// <param name="ARGB">32bit ARGB integer color value.</param> 973 public TgaColorKey(int ARGB) 974 { 975 Color ColorARGB = Color.FromArgb(ARGB); 976 a = ColorARGB.A; 977 r = ColorARGB.R; 978 g = ColorARGB.G; 979 b = ColorARGB.B; 980 } 981 982 /// <summary> 983 /// Make <see cref="TgaColorKey"/> from <see cref="Color"/>. 984 /// </summary> 985 /// <param name="color">GDI+ <see cref="Color"/> value.</param> 986 public TgaColorKey(Color color) 987 { 988 a = color.A; 989 r = color.R; 990 g = color.G; 991 b = color.B; 992 } 993 994 /// <summary> 995 /// Gets or sets alpha color value. 996 /// </summary> 997 public byte A 998 { 999 get { return a; } 1000 set { a = value; } 1001 } 1002 1003 /// <summary> 1004 /// Gets or sets red color value. 1005 /// </summary> 1006 public byte R 1007 { 1008 get { return r; } 1009 set { r = value; } 1010 } 1011 1012 /// <summary> 1013 /// Gets or sets green color value. 1014 /// </summary> 1015 public byte G 1016 { 1017 get { return g; } 1018 set { g = value; } 1019 } 1020 1021 /// <summary> 1022 /// Gets or sets blue color value. 1023 /// </summary> 1024 public byte B 1025 { 1026 get { return b; } 1027 set { b = value; } 1028 } 1029 1030 /// <summary> 1031 /// Gets TGA Field size in bytes. 1032 /// </summary> 1033 public const int Size = 4; 1034 1035 /// <summary> 1036 /// Make full independed copy of <see cref="TgaColorKey"/>. 1037 /// </summary> 1038 /// <returns>Copy of <see cref="TgaColorKey"/></returns> 1039 public TgaColorKey Clone() 1040 { 1041 return new TgaColorKey(a, r, g, b); 1042 } 1043 1044 /// <summary> 1045 /// Make full independed copy of <see cref="TgaColorKey"/>. 1046 /// </summary> 1047 /// <returns>Copy of <see cref="TgaColorKey"/></returns> 1048 object ICloneable.Clone() 1049 { 1050 return Clone(); 1051 } 1052 1053 public override bool Equals(object obj) 1054 { 1055 return ((obj is TgaColorKey) ? Equals((TgaColorKey)obj) : false); 1056 } 1057 1058 public bool Equals(TgaColorKey item) 1059 { 1060 return (a == item.a && r == item.r && g == item.g && b == item.b); 1061 } 1062 1063 public static bool operator ==(TgaColorKey item1, TgaColorKey item2) 1064 { 1065 if (ReferenceEquals(item1, null)) 1066 return ReferenceEquals(item2, null); 1067 1068 if (ReferenceEquals(item2, null)) 1069 return ReferenceEquals(item1, null); 1070 1071 return item1.Equals(item2); 1072 } 1073 1074 public static bool operator !=(TgaColorKey item1, TgaColorKey item2) 1075 { 1076 return !(item1 == item2); 1077 } 1078 1079 public override int GetHashCode() 1080 { 1081 return ToInt().GetHashCode(); 1082 } 1083 1084 /// <summary> 1085 /// Gets <see cref="TgaColorKey"/> like string. 1086 /// </summary> 1087 /// <returns>String in ARGB format.</returns> 1088 public override string ToString() 1089 { 1090 return String.Format("{0}={1}, {2}={3}, {4}={5}, {6}={7}", 1091 nameof(A), a, nameof(R), r, nameof(G), g, nameof(B), b); 1092 } 1093 1094 /// <summary> 1095 /// Convert <see cref="TgaColorKey"/> to byte array. 1096 /// </summary> 1097 /// <returns>Byte array with length = 4.</returns> 1098 public byte[] ToBytes() 1099 { 1100 return BitConverter.GetBytes(ToInt()); 1101 } 1102 1103 /// <summary> 1104 /// Gets <see cref="TgaColorKey"/> like GDI+ <see cref="Color"/>. 1105 /// </summary> 1106 /// <returns><see cref="Color"/> value of <see cref="TgaColorKey"/>.</returns> 1107 public Color ToColor() 1108 { 1109 return Color.FromArgb(a, r, g, b); 1110 } 1111 1112 /// <summary> 1113 /// Gets <see cref="TgaColorKey"/> like ARGB <see cref="int"/>. 1114 /// </summary> 1115 /// <returns>ARGB <see cref="int"/> value of <see cref="TgaColorKey"/>.</returns> 1116 public int ToInt() 1117 { 1118 return ToColor().ToArgb(); 1119 } 1120 } 1121 1122 /// <summary> 1123 /// This field (5 bytes) and its sub-fields describe the color map (if any) used for the image. 1124 /// If the Color Map Type field is set to zero, indicating that no color map exists, then 1125 /// these 5 bytes should be set to zero. These bytes always must be written to the file. 1126 /// </summary> 1127 public class TgaColorMapSpec : ICloneable 1128 { 1129 ushort firstEntryIndex = 0; 1130 ushort colorMapLength = 0; 1131 TgaColorMapEntrySize colorMapEntrySize = TgaColorMapEntrySize.Other; 1132 1133 /// <summary> 1134 /// Make new <see cref="TgaColorMapSpec"/>. 1135 /// </summary> 1136 public TgaColorMapSpec() 1137 { 1138 } 1139 1140 /// <summary> 1141 /// Make <see cref="TgaColorMapSpec"/> from bytes. 1142 /// </summary> 1143 /// <param name="Bytes">Array of bytes(byte[5]).</param> 1144 public TgaColorMapSpec(byte[] Bytes) 1145 { 1146 if (Bytes == null) 1147 throw new ArgumentNullException(nameof(Bytes) + " = null!"); 1148 if (Bytes.Length != Size) 1149 throw new ArgumentOutOfRangeException(nameof(Bytes.Length) + " must be equal " + Size + "!"); 1150 1151 firstEntryIndex = BitConverter.ToUInt16(Bytes, 0); 1152 colorMapLength = BitConverter.ToUInt16(Bytes, 2); 1153 colorMapEntrySize = (TgaColorMapEntrySize)Bytes[4]; 1154 } 1155 1156 /// <summary> 1157 /// Field 4.1 (2 bytes): 1158 /// Index of the first color map entry. Index refers to the starting entry in loading 1159 /// the color map. 1160 /// <para>Example: If you would have 1024 entries in the entire color map but you only 1161 /// need to store 72 of those entries, this field allows you to start in the middle of 1162 /// the color-map (e.g., position 342).</para> 1163 /// </summary> 1164 public ushort FirstEntryIndex 1165 { 1166 get { return firstEntryIndex; } 1167 set { firstEntryIndex = value; } 1168 } 1169 1170 /// <summary> 1171 /// Field 4.2 (2 bytes): 1172 /// Total number of color map entries included. 1173 /// </summary> 1174 public ushort ColorMapLength 1175 { 1176 get { return colorMapLength; } 1177 set { colorMapLength = value; } 1178 } 1179 1180 /// <summary> 1181 /// Field 4.3 (1 byte): 1182 /// Establishes the number of bits per entry. Typically 15, 16, 24 or 32-bit values are used. 1183 /// <para>When working with VDA or VDA/D cards it is preferred that you select 16 bits(5 bits 1184 /// per primary with 1 bit to select interrupt control) and set the 16th bit to 0 so that the 1185 /// interrupt bit is disabled. Even if this field is set to 15 bits(5 bits per primary) you 1186 /// must still parse the color map data 16 bits at a time and ignore the 16th bit.</para> 1187 /// <para>When working with a TARGA M8 card you would select 24 bits (8 bits per primary) 1188 /// since the color map is defined as 256 entries of 24 bit color values.</para> 1189 /// When working with a TrueVista card(ATVista or NuVista) you would select 24-bit(8 bits per 1190 /// primary) or 32-bit(8 bits per primary including Alpha channel) depending on your 1191 /// application’s use of look-up tables. It is suggested that when working with 16-bit and 1192 /// 32-bit color images, you store them as True-Color images and do not use the color map 1193 /// field to store look-up tables. Please refer to the TGA Extensions for fields better suited 1194 /// to storing look-up table information. 1195 /// </summary> 1196 public TgaColorMapEntrySize ColorMapEntrySize 1197 { 1198 get { return colorMapEntrySize; } 1199 set { colorMapEntrySize = value; } 1200 } 1201 1202 /// <summary> 1203 /// Gets TGA Field size in bytes. 1204 /// </summary> 1205 public const int Size = 5; 1206 1207 /// <summary> 1208 /// Make full independed copy of <see cref="TgaColorMapSpec"/>. 1209 /// </summary> 1210 /// <returns>Copy of <see cref="TgaColorMapSpec"/></returns> 1211 public TgaColorMapSpec Clone() 1212 { 1213 return new TgaColorMapSpec(ToBytes()); 1214 } 1215 1216 /// <summary> 1217 /// Make full independed copy of <see cref="TgaColorMapSpec"/>. 1218 /// </summary> 1219 /// <returns>Copy of <see cref="TgaColorMapSpec"/></returns> 1220 object ICloneable.Clone() 1221 { 1222 return Clone(); 1223 } 1224 1225 public override bool Equals(object obj) 1226 { 1227 return ((obj is TgaColorMapSpec) ? Equals((TgaColorMapSpec)obj) : false); 1228 } 1229 1230 public bool Equals(TgaColorMapSpec item) 1231 { 1232 return (firstEntryIndex == item.firstEntryIndex && 1233 colorMapLength == item.colorMapLength && 1234 colorMapEntrySize == item.colorMapEntrySize); 1235 } 1236 1237 public static bool operator ==(TgaColorMapSpec item1, TgaColorMapSpec item2) 1238 { 1239 if (ReferenceEquals(item1, null)) 1240 return ReferenceEquals(item2, null); 1241 1242 if (ReferenceEquals(item2, null)) 1243 return ReferenceEquals(item1, null); 1244 1245 return item1.Equals(item2); 1246 } 1247 1248 public static bool operator !=(TgaColorMapSpec item1, TgaColorMapSpec item2) 1249 { 1250 return !(item1 == item2); 1251 } 1252 1253 public override int GetHashCode() 1254 { 1255 unchecked 1256 { 1257 return (firstEntryIndex << 16 | colorMapLength).GetHashCode() ^ colorMapEntrySize.GetHashCode(); 1258 } 1259 } 1260 1261 public override string ToString() 1262 { 1263 return String.Format("{0}={1}, {2}={3}, {4}={5}", nameof(FirstEntryIndex), FirstEntryIndex, 1264 nameof(ColorMapLength), ColorMapLength, nameof(ColorMapEntrySize), ColorMapEntrySize); 1265 } 1266 1267 /// <summary> 1268 /// Convert ColorMapSpec to byte array. 1269 /// </summary> 1270 /// <returns>Byte array with length = 5.</returns> 1271 public byte[] ToBytes() 1272 { 1273 return BitConverterExt.ToBytes(firstEntryIndex, colorMapLength, (byte)colorMapEntrySize); 1274 } 1275 } 1276 1277 public class TgaComment : ICloneable 1278 { 1279 const int StrNLen = 80; //80 ASCII chars + 1 '\0' = 81 per SrtN! 1280 string origString = String.Empty; 1281 char blankSpaceChar = TgaString.DefaultBlankSpaceChar; 1282 1283 public TgaComment() 1284 { 1285 } 1286 1287 public TgaComment(string Str, char BlankSpaceChar = '\0') 1288 { 1289 if (Str == null) 1290 throw new ArgumentNullException(nameof(Str) + " = null!"); 1291 1292 origString = Str; 1293 blankSpaceChar = BlankSpaceChar; 1294 } 1295 1296 public TgaComment(byte[] Bytes) 1297 { 1298 if (Bytes == null) 1299 throw new ArgumentNullException(nameof(Bytes) + " = null!"); 1300 if (Bytes.Length != Size) 1301 throw new ArgumentOutOfRangeException(nameof(Bytes.Length) + " must be equal " + Size + "!"); 1302 1303 string s = Encoding.ASCII.GetString(Bytes, 0, StrNLen); 1304 s += Encoding.ASCII.GetString(Bytes, 81, StrNLen); 1305 s += Encoding.ASCII.GetString(Bytes, 162, StrNLen); 1306 s += Encoding.ASCII.GetString(Bytes, 243, StrNLen); 1307 1308 switch (s[s.Length - 1]) 1309 { 1310 case '\0': 1311 case ' ': 1312 blankSpaceChar = s[s.Length - 1]; 1313 origString = s.TrimEnd(new char[] { s[s.Length - 1] }); 1314 break; 1315 default: 1316 origString = s; 1317 break; 1318 } 1319 } 1320 1321 /// <summary> 1322 /// Gets TGA Field size in bytes. 1323 /// </summary> 1324 public const int Size = 81 * 4; 1325 1326 public string OriginalString 1327 { 1328 get { return origString; } 1329 set { origString = value; } 1330 } 1331 1332 public char BlankSpaceChar 1333 { 1334 get { return blankSpaceChar; } 1335 set { blankSpaceChar = value; } 1336 } 1337 1338 /// <summary> 1339 /// Make full independed copy of <see cref="TgaComment"/>. 1340 /// </summary> 1341 /// <returns>Copy of <see cref="TgaComment"/></returns> 1342 public TgaComment Clone() 1343 { 1344 return new TgaComment(origString, blankSpaceChar); 1345 } 1346 1347 /// <summary> 1348 /// Make full independed copy of <see cref="TgaComment"/>. 1349 /// </summary> 1350 /// <returns>Copy of <see cref="TgaComment"/></returns> 1351 object ICloneable.Clone() 1352 { 1353 return Clone(); 1354 } 1355 1356 public override bool Equals(object obj) 1357 { 1358 return ((obj is TgaComment) ? Equals((TgaComment)obj) : false); 1359 } 1360 1361 public bool Equals(TgaComment item) 1362 { 1363 return (origString == item.origString && blankSpaceChar == item.blankSpaceChar); 1364 } 1365 1366 public static bool operator ==(TgaComment item1, TgaComment item2) 1367 { 1368 if (ReferenceEquals(item1, null)) 1369 return ReferenceEquals(item2, null); 1370 1371 if (ReferenceEquals(item2, null)) 1372 return ReferenceEquals(item1, null); 1373 1374 return item1.Equals(item2); 1375 } 1376 1377 public static bool operator !=(TgaComment item1, TgaComment item2) 1378 { 1379 return !(item1 == item2); 1380 } 1381 1382 public override int GetHashCode() 1383 { 1384 return origString.GetHashCode() ^ blankSpaceChar.GetHashCode(); 1385 } 1386 1387 /// <summary> 1388 /// Get ASCII-Like string with string-terminators, example: "Line1 \0\0 Line2 \0\0\0". 1389 /// </summary> 1390 /// <returns>String with replaced string-terminators to "\0".</returns> 1391 public override string ToString() 1392 { 1393 return Encoding.ASCII.GetString(ToBytes()).Replace("\0", @"\0"); 1394 } 1395 1396 /// <summary> 1397 /// Get ASCII-Like string to first string-terminator, example: 1398 /// "Some string \0 Some Data \0" - > "Some string". 1399 /// </summary> 1400 /// <returns>String to first string-terminator.</returns> 1401 public string GetString() 1402 { 1403 String Str = Encoding.ASCII.GetString(ToBytes()); 1404 for (int i = 1; i < 4; i++) 1405 Str = Str.Insert((StrNLen + 1) * i + i - 1, "\n"); 1406 return Str.Replace("\0", String.Empty).TrimEnd(new char[] { '\n' }); 1407 } 1408 1409 /// <summary> 1410 /// Convert <see cref="TgaComment"/> to byte array. 1411 /// </summary> 1412 /// <returns>Byte array, every byte is ASCII symbol.</returns> 1413 public byte[] ToBytes() 1414 { 1415 return ToBytes(origString, blankSpaceChar); 1416 } 1417 1418 /// <summary> 1419 /// Convert <see cref="TgaComment"/> to byte array. 1420 /// </summary> 1421 /// <param name="Str">Input string.</param> 1422 /// <param name="BlankSpaceChar">Char for filling blank space in string.</param> 1423 /// <returns>Byte array, every byte is ASCII symbol.</returns> 1424 public static byte[] ToBytes(string Str, char BlankSpaceChar = '\0') 1425 { 1426 char[] C = new char[81 * 4]; 1427 1428 for (int i = 0; i < C.Length; i++) 1429 { 1430 if ((i + 82) % 81 == 0) 1431 C[i] = TgaString.DefaultEndingChar; 1432 else 1433 { 1434 int Index = i - i / 81; 1435 C[i] = (Index < Str.Length ? Str[Index] : BlankSpaceChar); 1436 } 1437 } 1438 return Encoding.ASCII.GetBytes(C); 1439 } 1440 } 1441 1442 public class TgaDateTime : ICloneable 1443 { 1444 ushort month = 0; 1445 ushort day = 0; 1446 ushort year = 0; 1447 ushort hour = 0; 1448 ushort minute = 0; 1449 ushort second = 0; 1450 1451 /// <summary> 1452 /// Make empty <see cref="TgaDateTime"/>. 1453 /// </summary> 1454 public TgaDateTime() 1455 { 1456 } 1457 1458 /// <summary> 1459 /// Make <see cref="TgaDateTime"/> from <see cref="DateTime"/>. 1460 /// </summary> 1461 /// <param name="DateAndTime">Some <see cref="DateTime"/> variable.</param> 1462 public TgaDateTime(DateTime DateAndTime) 1463 { 1464 month = (ushort)DateAndTime.Month; 1465 day = (ushort)DateAndTime.Day; 1466 year = (ushort)DateAndTime.Year; 1467 hour = (ushort)DateAndTime.Hour; 1468 minute = (ushort)DateAndTime.Minute; 1469 second = (ushort)DateAndTime.Second; 1470 } 1471 1472 /// <summary> 1473 /// Make <see cref="TgaDateTime"/> from ushort values. 1474 /// </summary> 1475 /// <param name="Month">Month (1 - 12).</param> 1476 /// <param name="Day">Day (1 - 31).</param> 1477 /// <param name="Year">Year (4 digit, ie. 1989).</param> 1478 /// <param name="Hour">Hour (0 - 23).</param> 1479 /// <param name="Minute">Minute (0 - 59).</param> 1480 /// <param name="Second">Second (0 - 59).</param> 1481 public TgaDateTime(ushort Month, ushort Day, ushort Year, ushort Hour, ushort Minute, ushort Second) 1482 { 1483 month = Month; 1484 day = Day; 1485 year = Year; 1486 hour = Hour; 1487 minute = Minute; 1488 second = Second; 1489 } 1490 1491 /// <summary> 1492 /// Make <see cref="TgaDateTime"/> from bytes. 1493 /// </summary> 1494 /// <param name="Bytes">Array of bytes(byte[12]).</param> 1495 public TgaDateTime(byte[] Bytes) 1496 { 1497 if (Bytes == null) 1498 throw new ArgumentNullException(nameof(Bytes) + " = null!"); 1499 else if (Bytes.Length != Size) 1500 throw new ArgumentOutOfRangeException(nameof(Bytes) + " must be equal " + Size + "!"); 1501 1502 month = BitConverter.ToUInt16(Bytes, 0); 1503 day = BitConverter.ToUInt16(Bytes, 2); 1504 year = BitConverter.ToUInt16(Bytes, 4); 1505 hour = BitConverter.ToUInt16(Bytes, 6); 1506 minute = BitConverter.ToUInt16(Bytes, 8); 1507 second = BitConverter.ToUInt16(Bytes, 10); 1508 } 1509 1510 /// <summary> 1511 /// Gets or Sets month (1 - 12). 1512 /// </summary> 1513 public ushort Month 1514 { 1515 get { return month; } 1516 set { month = value; } 1517 } 1518 1519 /// <summary> 1520 /// Gets or Sets day (1 - 31). 1521 /// </summary> 1522 public ushort Day 1523 { 1524 get { return day; } 1525 set { day = value; } 1526 } 1527 1528 /// <summary> 1529 /// Gets or Sets year (4 digit, ie. 1989). 1530 /// </summary> 1531 public ushort Year 1532 { 1533 get { return year; } 1534 set { year = value; } 1535 } 1536 1537 /// <summary> 1538 /// Gets or Sets hour (0 - 23). 1539 /// </summary> 1540 public ushort Hour 1541 { 1542 get { return hour; } 1543 set { hour = value; } 1544 } 1545 1546 /// <summary> 1547 /// Gets or Sets minute (0 - 59). 1548 /// </summary> 1549 public ushort Minute 1550 { 1551 get { return minute; } 1552 set { minute = value; } 1553 } 1554 1555 /// <summary> 1556 /// Gets or Sets second (0 - 59). 1557 /// </summary> 1558 public ushort Second 1559 { 1560 get { return second; } 1561 set { second = value; } 1562 } 1563 1564 /// <summary> 1565 /// Gets TGA Field size in bytes. 1566 /// </summary> 1567 public const int Size = 12; 1568 1569 /// <summary> 1570 /// Make full independed copy of <see cref="TgaDateTime"/>. 1571 /// </summary> 1572 /// <returns>Copy of <see cref="TgaDateTime"/></returns> 1573 public TgaDateTime Clone() 1574 { 1575 return new TgaDateTime(month, day, year, hour, minute, second); 1576 } 1577 1578 /// <summary> 1579 /// Make full independed copy of <see cref="TgaDateTime"/>. 1580 /// </summary> 1581 /// <returns>Copy of <see cref="TgaDateTime"/></returns> 1582 object ICloneable.Clone() 1583 { 1584 return Clone(); 1585 } 1586 1587 public override bool Equals(object obj) 1588 { 1589 return ((obj is TgaDateTime) ? Equals((TgaDateTime)obj) : false); 1590 } 1591 1592 public bool Equals(TgaDateTime item) 1593 { 1594 return ( 1595 month == item.month && 1596 day == item.day && 1597 year == item.year && 1598 hour == item.hour && 1599 minute == item.minute && 1600 second == item.second); 1601 } 1602 1603 public static bool operator ==(TgaDateTime item1, TgaDateTime item2) 1604 { 1605 if (ReferenceEquals(item1, null)) 1606 return ReferenceEquals(item2, null); 1607 1608 if (ReferenceEquals(item2, null)) 1609 return ReferenceEquals(item1, null); 1610 1611 return item1.Equals(item2); 1612 } 1613 1614 public static bool operator !=(TgaDateTime item1, TgaDateTime item2) 1615 { 1616 return !(item1 == item2); 1617 } 1618 1619 public override int GetHashCode() 1620 { 1621 unchecked 1622 { 1623 int hash = 17; 1624 hash = hash * 23 + (month << 16 | hour).GetHashCode(); 1625 hash = hash * 23 + (day << 16 | minute).GetHashCode(); 1626 hash = hash * 23 + (year << 16 | second).GetHashCode(); 1627 return hash; 1628 } 1629 } 1630 1631 /// <summary> 1632 /// Gets <see cref="TgaDateTime"/> like string. 1633 /// </summary> 1634 /// <returns>String in "1990.01.23 1:02:03" format.</returns> 1635 public override string ToString() 1636 { 1637 return String.Format("{0:D4}.{1:D2}.{2:D2} {3}:{4:D2}:{5:D2}", year, month, day, hour, minute, second); 1638 } 1639 1640 /// <summary> 1641 /// Convert <see cref="TgaDateTime"/> to byte array. 1642 /// </summary> 1643 /// <returns>Byte array with length = 12.</returns> 1644 public byte[] ToBytes() 1645 { 1646 return BitConverterExt.ToBytes(month, day, year, hour, minute, second); 1647 } 1648 1649 /// <summary> 1650 /// Gets <see cref="TgaDateTime"/> like <see cref="DateTime"/>. 1651 /// </summary> 1652 /// <returns><see cref="DateTime"/> value of <see cref="TgaDateTime"/>.</returns> 1653 public DateTime ToDateTime() 1654 { 1655 return new DateTime(year, month, day, hour, minute, second); 1656 } 1657 } 1658 1659 public class TgaDevEntry : ICloneable 1660 { 1661 // Directory 1662 ushort fieldTag = 0; 1663 uint fieldFileOffset = 0; 1664 // Field 1665 byte[] data = null; 1666 1667 /// <summary> 1668 /// Make empty <see cref="TgaDevEntry"/>. 1669 /// </summary> 1670 public TgaDevEntry() 1671 { 1672 } 1673 1674 /// <summary> 1675 /// Make <see cref="TgaDevEntry"/> from other <see cref="TgaDevEntry"/>. 1676 /// </summary> 1677 /// <param name="Entry">Some <see cref="TgaDevEntry"/> variable.</param> 1678 public TgaDevEntry(TgaDevEntry Entry) 1679 { 1680 if (Entry == null) 1681 throw new ArgumentNullException(); 1682 1683 fieldTag = Entry.fieldTag; 1684 fieldFileOffset = Entry.fieldFileOffset; 1685 data = BitConverterExt.ToBytes(Entry.data); 1686 } 1687 1688 /// <summary> 1689 /// Make <see cref="TgaDevEntry"/> from <see cref="Tag"/>, <see cref="Offset"/> and <see cref="FieldSize"/>. 1690 /// </summary> 1691 /// <param name="Tag">TAG ID (0 - 65535). See <see cref="Tag"/>.</param> 1692 /// <param name="Offset">TAG file offset in bytes. See <see cref="Offset"/>.</param> 1693 /// <param name="Data">This is DevEntry Field Data. See <see cref="Data"/>.</param> 1694 public TgaDevEntry(ushort Tag, uint Offset, byte[] Data = null) 1695 { 1696 fieldTag = Tag; 1697 fieldFileOffset = Offset; 1698 data = Data; 1699 } 1700 1701 /// <summary> 1702 /// Make <see cref="TgaDevEntry"/> from bytes. 1703 /// </summary> 1704 /// <param name="Bytes">Array of bytes(byte[6] or bigger, if <see cref="Data"/> exist).</param> 1705 public TgaDevEntry(byte[] Bytes) 1706 { 1707 if (Bytes == null) 1708 throw new ArgumentNullException(nameof(Bytes) + " = null!"); 1709 else if (Bytes.Length < 6) 1710 throw new ArgumentOutOfRangeException(nameof(Bytes) + " must be >= 6!"); 1711 1712 fieldTag = BitConverter.ToUInt16(Bytes, 0); 1713 fieldFileOffset = BitConverter.ToUInt32(Bytes, 2); 1714 1715 if (Bytes.Length > 6) 1716 data = BitConverterExt.GetElements(Bytes, 6, Bytes.Length - 6); 1717 } 1718 1719 /// <summary> 1720 /// Each TAG is a value in the range of 0 to 65535. Values from 0 - 32767 are available for developer use, 1721 /// while values from 32768 - 65535 are reserved for Truevision. 1722 /// </summary> 1723 public ushort Tag 1724 { 1725 get { return fieldTag; } 1726 set { fieldTag = value; } 1727 } 1728 1729 /// <summary> 1730 /// This OFFSET is a number of bytes from the beginning of the file to the start of the field 1731 /// referenced by the tag. 1732 /// </summary> 1733 public uint Offset 1734 { 1735 get { return fieldFileOffset; } 1736 set { fieldFileOffset = value; } 1737 } 1738 1739 /// <summary> 1740 /// Field DATA. 1741 /// Although the size and format of the actual Developer Area fields are totally up to the developer, 1742 /// please define your formats to address future considerations you might have concerning your fields. 1743 /// This means that if you anticipate changing a field, build flexibility into the format to make these 1744 /// changes easy on other developers.Major changes to an existing TAG’s definition should never happen. 1745 /// </summary> 1746 public byte[] Data 1747 { 1748 get { return data; } 1749 set { data = value; } 1750 } 1751 1752 /// <summary> 1753 /// The FIELD SIZE is a number of bytes in the field. Same like: <see cref="Data.Length"/>, 1754 /// if <see cref="Data"/> is null, return -1. 1755 /// </summary> 1756 public int FieldSize 1757 { 1758 get 1759 { 1760 if (Data == null) 1761 return -1; 1762 1763 return Data.Length; 1764 } 1765 } 1766 1767 /// <summary> 1768 /// Gets TGA <see cref="TgaDevEntry"/> size in bytes (Always constant and equal 10!). 1769 /// It is not <see cref="FieldSize"/>! It is just size of entry sizeof(ushort + uint + uint). 1770 /// </summary> 1771 public const int Size = 10; 1772 1773 /// <summary> 1774 /// Make full independed copy of <see cref="TgaDevEntry"/>. 1775 /// </summary> 1776 /// <returns>Copy of <see cref="TgaDevEntry"/></returns> 1777 public TgaDevEntry Clone() 1778 { 1779 return new TgaDevEntry(this); 1780 } 1781 1782 /// <summary> 1783 /// Make full independed copy of <see cref="TgaDevEntry"/>. 1784 /// </summary> 1785 /// <returns>Copy of <see cref="TgaDevEntry"/></returns> 1786 object ICloneable.Clone() 1787 { 1788 return Clone(); 1789 } 1790 1791 public override bool Equals(object obj) 1792 { 1793 return ((obj is TgaDevEntry) ? Equals((TgaDevEntry)obj) : false); 1794 } 1795 1796 public bool Equals(TgaDevEntry item) 1797 { 1798 return (fieldTag == item.fieldTag && 1799 fieldFileOffset == item.fieldFileOffset && 1800 BitConverterExt.IsArraysEqual(data, item.data)); 1801 } 1802 1803 public static bool operator ==(TgaDevEntry item1, TgaDevEntry item2) 1804 { 1805 if (ReferenceEquals(item1, null)) 1806 return ReferenceEquals(item2, null); 1807 1808 if (ReferenceEquals(item2, null)) 1809 return ReferenceEquals(item1, null); 1810 1811 return item1.Equals(item2); 1812 } 1813 1814 public static bool operator !=(TgaDevEntry item1, TgaDevEntry item2) 1815 { 1816 return !(item1 == item2); 1817 } 1818 1819 public override int GetHashCode() 1820 { 1821 unchecked 1822 { 1823 int hash = 17; 1824 hash = hash * 23 + fieldTag.GetHashCode(); 1825 hash = hash * 23 + fieldFileOffset.GetHashCode(); 1826 1827 if (data != null) 1828 for (int i = 0; i < data.Length; i++) 1829 hash = hash * 23 + data[i].GetHashCode(); 1830 1831 return hash; 1832 } 1833 } 1834 1835 /// <summary> 1836 /// Gets <see cref="TgaDevEntry"/> like string. 1837 /// </summary> 1838 /// <returns>String in "Tag={0}, Offset={1}, FieldSize={2}" format.</returns> 1839 public override string ToString() 1840 { 1841 return String.Format("{0}={1}, {1}={2}, {3}={4}", nameof(Tag), fieldTag, 1842 nameof(Offset), fieldFileOffset, nameof(FieldSize), FieldSize); 1843 } 1844 1845 /// <summary> 1846 /// Convert <see cref="TgaDevEntry"/> to byte array. (Not include <see cref="Data"/>!). 1847 /// </summary> 1848 /// <returns>Byte array with length = 10.</returns> 1849 public byte[] ToBytes() 1850 { 1851 return BitConverterExt.ToBytes(fieldTag, fieldFileOffset, (data == null ? 0 : data.Length)); 1852 } 1853 } //Not full ToBytes() 1854 1855 public class TgaFraction : ICloneable 1856 { 1857 ushort numerator = 0; 1858 ushort denominator = 0; 1859 1860 /// <summary> 1861 /// Make <see cref="TgaFraction"/> from <see cref="Numerator"/> and <see cref="Denominator"/>. 1862 /// </summary> 1863 /// <param name="Numerator">Numerator value.</param> 1864 /// <param name="Denominator">Denominator value.</param> 1865 public TgaFraction(ushort Numerator = 0, ushort Denominator = 0) 1866 { 1867 numerator = Numerator; 1868 denominator = Denominator; 1869 } 1870 1871 /// <summary> 1872 /// Make <see cref="TgaFraction"/> from bytes. 1873 /// </summary> 1874 /// <param name="Bytes">Array of bytes(byte[4]).</param> 1875 public TgaFraction(byte[] Bytes) 1876 { 1877 if (Bytes == null) 1878 throw new ArgumentNullException(nameof(Bytes) + " = null!"); 1879 if (Bytes.Length != Size) 1880 throw new ArgumentOutOfRangeException(nameof(Bytes.Length) + " must be equal " + Size + "!"); 1881 1882 numerator = BitConverter.ToUInt16(Bytes, 0); 1883 denominator = BitConverter.ToUInt16(Bytes, 2); 1884 } 1885 1886 /// <summary> 1887 /// Gets or sets numerator value. 1888 /// </summary> 1889 public ushort Numerator 1890 { 1891 get { return numerator; } 1892 set { numerator = value; } 1893 } 1894 1895 /// <summary> 1896 /// Gets or sets denominator value. 1897 /// </summary> 1898 public ushort Denominator 1899 { 1900 get { return denominator; } 1901 set { denominator = value; } 1902 } 1903 1904 /// <summary> 1905 /// Get aspect ratio = <see cref="Numerator"/> / <see cref="Denominator"/>. 1906 /// </summary> 1907 public float AspectRatio 1908 { 1909 get 1910 { 1911 if (numerator == denominator) 1912 return 1f; 1913 1914 return numerator / (float)denominator; 1915 } 1916 } 1917 1918 /// <summary> 1919 /// Gets Empty <see cref="TgaFraction"/>, all values are 0. 1920 /// </summary> 1921 public static readonly TgaFraction Empty = new TgaFraction(); 1922 1923 /// <summary> 1924 /// Gets One <see cref="TgaFraction"/>, all values are 1 (ones, 1 / 1 = 1). 1925 /// </summary> 1926 public static readonly TgaFraction One = new TgaFraction(1, 1); 1927 1928 /// <summary> 1929 /// Gets TGA Field size in bytes. 1930 /// </summary> 1931 public const int Size = 4; 1932 1933 /// <summary> 1934 /// Make full independed copy of <see cref="TgaFraction"/>. 1935 /// </summary> 1936 /// <returns>Copy of <see cref="TgaFraction"/></returns> 1937 public TgaFraction Clone() 1938 { 1939 return new TgaFraction(numerator, denominator); 1940 } 1941 1942 /// <summary> 1943 /// Make full independed copy of <see cref="TgaFraction"/>. 1944 /// </summary> 1945 /// <returns>Copy of <see cref="TgaFraction"/></returns> 1946 object ICloneable.Clone() 1947 { 1948 return Clone(); 1949 } 1950 1951 public override bool Equals(object obj) 1952 { 1953 return ((obj is TgaFraction) ? Equals((TgaFraction)obj) : false); 1954 } 1955 1956 public bool Equals(TgaFraction item) 1957 { 1958 return (numerator == item.numerator && denominator == item.denominator); 1959 } 1960 1961 public static bool operator ==(TgaFraction item1, TgaFraction item2) 1962 { 1963 if (ReferenceEquals(item1, null)) 1964 return ReferenceEquals(item2, null); 1965 1966 if (ReferenceEquals(item2, null)) 1967 return ReferenceEquals(item1, null); 1968 1969 return item1.Equals(item2); 1970 } 1971 1972 public static bool operator !=(TgaFraction item1, TgaFraction item2) 1973 { 1974 return !(item1 == item2); 1975 } 1976 1977 public override int GetHashCode() 1978 { 1979 return (numerator << 16 | denominator).GetHashCode(); 1980 } 1981 1982 /// <summary> 1983 /// Gets <see cref="TgaFraction"/> like string. 1984 /// </summary> 1985 /// <returns>String in "Numerator=1, Denominator=2" format.</returns> 1986 public override string ToString() 1987 { 1988 return String.Format("{0}={1}, {2}={3}", nameof(Numerator), numerator, 1989 nameof(Denominator), denominator); 1990 } 1991 1992 /// <summary> 1993 /// Convert <see cref="TgaFraction"/> to byte array. 1994 /// </summary> 1995 /// <returns>Byte array with length = 4.</returns> 1996 public byte[] ToBytes() 1997 { 1998 return BitConverterExt.ToBytes(numerator, denominator); 1999 } 2000 } 2001 2002 /// <summary> 2003 /// Contains image origin bits and alpha channel bits(or number of overlay bits) 2004 /// </summary> 2005 public class TgaImageDescriptor : ICloneable 2006 { 2007 TgaImgOrigin imageOrigin = 0; //bits 5-4 2008 byte alphaChannelBits = 0; //bits 3-0 2009 2010 /// <summary> 2011 /// Make empty <see cref="TgaImageDescriptor"/>. 2012 /// </summary> 2013 public TgaImageDescriptor() 2014 { 2015 } 2016 2017 /// <summary> 2018 /// Make <see cref="TgaImageDescriptor"/> from bytes. 2019 /// </summary> 2020 /// <param name="b">ImageDescriptor byte with reserved 7-6 bits, bits 5-4 used for 2021 /// <see cref="ImageOrigin"/>, 3-0 used as alpha channel bits or number of overlay bits.</param> 2022 public TgaImageDescriptor(byte b) 2023 { 2024 imageOrigin = (TgaImgOrigin)((b & 0x30) >> 4); 2025 alphaChannelBits = (byte)(b & 0x0F); 2026 } 2027 2028 /// <summary> 2029 /// Gets or Sets Image Origin bits (select from enum only, don'n use 5-4 bits!). 2030 /// </summary> 2031 public TgaImgOrigin ImageOrigin 2032 { 2033 get { return imageOrigin; } 2034 set { imageOrigin = value; } 2035 } 2036 2037 /// <summary> 2038 /// Gets or Sets alpha channel bits or number of overlay bits. 2039 /// </summary> 2040 public byte AlphaChannelBits 2041 { 2042 get { return alphaChannelBits; } 2043 set { alphaChannelBits = value; } 2044 } 2045 2046 /// <summary> 2047 /// Gets TGA Field size in bytes. 2048 /// </summary> 2049 public const int Size = 1; 2050 2051 /// <summary> 2052 /// Make full copy of <see cref="TgaImageDescriptor"/>. 2053 /// </summary> 2054 /// <returns>Full independent copy of <see cref="TgaImageDescriptor"/>.</returns> 2055 public TgaImageDescriptor Clone() 2056 { 2057 return new TgaImageDescriptor(ToByte()); 2058 } 2059 2060 /// <summary> 2061 /// Make full copy of <see cref="TgaImageDescriptor"/>. 2062 /// </summary> 2063 /// <returns>Full independent copy of <see cref="TgaImageDescriptor"/>.</returns> 2064 object ICloneable.Clone() 2065 { 2066 return Clone(); 2067 } 2068 2069 public override bool Equals(object obj) 2070 { 2071 return ((obj is TgaImageDescriptor) ? Equals((TgaImageDescriptor)obj) : false); 2072 } 2073 2074 public bool Equals(TgaImageDescriptor item) 2075 { 2076 return (imageOrigin == item.imageOrigin && alphaChannelBits == item.alphaChannelBits); 2077 } 2078 2079 public static bool operator ==(TgaImageDescriptor item1, TgaImageDescriptor item2) 2080 { 2081 if (ReferenceEquals(item1, null)) 2082 return ReferenceEquals(item2, null); 2083 2084 if (ReferenceEquals(item2, null)) 2085 return ReferenceEquals(item1, null); 2086 2087 return item1.Equals(item2); 2088 } 2089 2090 public static bool operator !=(TgaImageDescriptor item1, TgaImageDescriptor item2) 2091 { 2092 return !(item1 == item2); 2093 } 2094 2095 public override int GetHashCode() 2096 { 2097 unchecked 2098 { 2099 return ((int)ImageOrigin << 4 | alphaChannelBits).GetHashCode(); 2100 } 2101 } 2102 2103 public override string ToString() 2104 { 2105 return String.Format("{0}={1}, {2}={3}, ImageDescriptor_AsByte={4}", nameof(ImageOrigin), 2106 imageOrigin, nameof(AlphaChannelBits), alphaChannelBits, ToByte()); 2107 } 2108 2109 /// <summary> 2110 /// Gets ImageDescriptor byte. 2111 /// </summary> 2112 /// <returns>ImageDescriptor byte with reserved 7-6 bits, bits 5-4 used for imageOrigin, 2113 /// 3-0 used as alpha channel bits or number of overlay bits.</returns> 2114 public byte ToByte() 2115 { 2116 return (byte)(((int)imageOrigin << 4) | alphaChannelBits); 2117 } 2118 } 2119 2120 /// <summary> 2121 /// Image Specification - Field 5 (10 bytes): 2122 /// <para>This field and its sub-fields describe the image screen location, size and pixel depth. 2123 /// These information is always written to the file.</para> 2124 /// </summary> 2125 public class TgaImageSpec : ICloneable 2126 { 2127 ushort x_Origin = 0; 2128 ushort y_Origin = 0; 2129 ushort imageWidth = 0; 2130 ushort imageHeight = 0; 2131 TgaPixelDepth pixelDepth = TgaPixelDepth.Other; 2132 TgaImageDescriptor imageDescriptor = new TgaImageDescriptor(); 2133 2134 public TgaImageSpec() 2135 { 2136 } 2137 2138 /// <summary> 2139 /// Make ImageSpec from values. 2140 /// </summary> 2141 /// <param name="X_Origin">These specify the absolute horizontal coordinate for the lower 2142 /// left corner of the image as it is positioned on a display device having an origin at 2143 /// the lower left of the screen(e.g., the TARGA series).</param> 2144 /// <param name="Y_Origin">These specify the absolute vertical coordinate for the lower 2145 /// left corner of the image as it is positioned on a display device having an origin at 2146 /// the lower left of the screen(e.g., the TARGA series).</param> 2147 /// <param name="ImageWidth">This field specifies the width of the image in pixels.</param> 2148 /// <param name="ImageHeight">This field specifies the height of the image in pixels.</param> 2149 /// <param name="PixelDepth">This field indicates the number of bits per pixel. This number 2150 /// includes the Attribute or Alpha channel bits. Common values are 8, 16, 24 and 32 but 2151 /// other pixel depths could be used.</param> 2152 /// <param name="ImageDescriptor">Contains image origin bits and alpha channel bits 2153 /// (or number of overlay bits).</param> 2154 public TgaImageSpec(ushort X_Origin, ushort Y_Origin, ushort ImageWidth, ushort ImageHeight, 2155 TgaPixelDepth PixelDepth, TgaImageDescriptor ImageDescriptor) 2156 { 2157 x_Origin = X_Origin; 2158 y_Origin = Y_Origin; 2159 imageWidth = ImageWidth; 2160 imageHeight = ImageHeight; 2161 pixelDepth = PixelDepth; 2162 imageDescriptor = ImageDescriptor; 2163 } 2164 2165 /// <summary> 2166 /// Make ImageSpec from bytes. 2167 /// </summary> 2168 /// <param name="Bytes">Array of bytes(byte[10]).</param> 2169 public TgaImageSpec(byte[] Bytes) 2170 { 2171 if (Bytes == null) 2172 throw new ArgumentNullException(nameof(Bytes) + " = null!"); 2173 if (Bytes.Length != Size) 2174 throw new ArgumentOutOfRangeException(nameof(Bytes.Length) + " must be equal " + Size + "!"); 2175 2176 x_Origin = BitConverter.ToUInt16(Bytes, 0); 2177 y_Origin = BitConverter.ToUInt16(Bytes, 2); 2178 imageWidth = BitConverter.ToUInt16(Bytes, 4); 2179 imageHeight = BitConverter.ToUInt16(Bytes, 6); 2180 pixelDepth = (TgaPixelDepth)Bytes[8]; 2181 imageDescriptor = new TgaImageDescriptor(Bytes[9]); 2182 } 2183 2184 /// <summary> 2185 /// These specify the absolute horizontal coordinate for the lower left corner of the image 2186 /// as it is positioned on a display device having an origin at the lower left of the 2187 /// screen(e.g., the TARGA series). 2188 /// </summary> 2189 public ushort X_Origin 2190 { 2191 get { return x_Origin; } 2192 set { x_Origin = value; } 2193 } 2194 2195 /// <summary> 2196 /// These specify the absolute vertical coordinate for the lower left corner of the image 2197 /// as it is positioned on a display device having an origin at the lower left of the 2198 /// screen(e.g., the TARGA series). 2199 /// </summary> 2200 public ushort Y_Origin 2201 { 2202 get { return y_Origin; } 2203 set { y_Origin = value; } 2204 } 2205 2206 /// <summary> 2207 /// This field specifies the width of the image in pixels. 2208 /// </summary> 2209 public ushort ImageWidth 2210 { 2211 get { return imageWidth; } 2212 set { imageWidth = value; } 2213 } 2214 2215 /// <summary> 2216 /// This field specifies the height of the image in pixels. 2217 /// </summary> 2218 public ushort ImageHeight 2219 { 2220 get { return imageHeight; } 2221 set { imageHeight = value; } 2222 } 2223 2224 /// <summary> 2225 /// This field indicates the number of bits per pixel. This number includes the Attribute or 2226 /// Alpha channel bits. Common values are 8, 16, 24 and 32 but other pixel depths could be used. 2227 /// </summary> 2228 public TgaPixelDepth PixelDepth 2229 { 2230 get { return pixelDepth; } 2231 set { pixelDepth = value; } 2232 } 2233 2234 /// <summary> 2235 /// Contains image origin bits and alpha channel bits(or number of overlay bits). 2236 /// </summary> 2237 public TgaImageDescriptor ImageDescriptor 2238 { 2239 get { return imageDescriptor; } 2240 set { imageDescriptor = value; } 2241 } 2242 2243 /// <summary> 2244 /// Gets TGA Field size in bytes. 2245 /// </summary> 2246 public const int Size = 10; 2247 2248 /// <summary> 2249 /// Make full copy of <see cref="TgaImageDescriptor"/>. 2250 /// </summary> 2251 /// <returns></returns> 2252 public TgaImageSpec Clone() 2253 { 2254 return new TgaImageSpec(ToBytes()); 2255 } 2256 2257 /// <summary> 2258 /// Make full copy of <see cref="TgaImageDescriptor"/>. 2259 /// </summary> 2260 /// <returns></returns> 2261 object ICloneable.Clone() 2262 { 2263 return Clone(); 2264 } 2265 2266 public override bool Equals(object obj) 2267 { 2268 return ((obj is TgaImageSpec) ? Equals((TgaImageSpec)obj) : false); 2269 } 2270 2271 public bool Equals(TgaImageSpec item) 2272 { 2273 return ( 2274 x_Origin == item.x_Origin && 2275 y_Origin == item.y_Origin && 2276 imageWidth == item.imageWidth && 2277 imageHeight == item.imageHeight && 2278 pixelDepth == item.pixelDepth && 2279 imageDescriptor == item.imageDescriptor); 2280 } 2281 2282 public static bool operator ==(TgaImageSpec item1, TgaImageSpec item2) 2283 { 2284 if (ReferenceEquals(item1, null)) 2285 return ReferenceEquals(item2, null); 2286 2287 if (ReferenceEquals(item2, null)) 2288 return ReferenceEquals(item1, null); 2289 2290 return item1.Equals(item2); 2291 } 2292 2293 public static bool operator !=(TgaImageSpec item1, TgaImageSpec item2) 2294 { 2295 return !(item1 == item2); 2296 } 2297 2298 public override int GetHashCode() 2299 { 2300 unchecked 2301 { 2302 int hash = 17; 2303 hash = hash * 23 + x_Origin.GetHashCode(); 2304 hash = hash * 23 + y_Origin.GetHashCode(); 2305 hash = hash * 23 + imageWidth.GetHashCode(); 2306 hash = hash * 23 + imageHeight.GetHashCode(); 2307 hash = hash * 23 + pixelDepth.GetHashCode(); 2308 2309 if (imageDescriptor != null) 2310 hash = hash * 23 + imageDescriptor.GetHashCode(); 2311 2312 return hash; 2313 } 2314 } 2315 2316 public override string ToString() 2317 { 2318 return String.Format("{0}={1}, {2}={3}, {4}={5}, {6}={7}, {8}={9}, {10}={11}", 2319 nameof(X_Origin), x_Origin, 2320 nameof(Y_Origin), y_Origin, 2321 nameof(ImageWidth), imageWidth, 2322 nameof(ImageHeight), imageHeight, 2323 nameof(PixelDepth), pixelDepth, 2324 nameof(ImageDescriptor), imageDescriptor); 2325 } 2326 2327 /// <summary> 2328 /// Convert <see cref="TgaImageSpec"/> to byte array. 2329 /// </summary> 2330 /// <returns>Byte array with length = 10.</returns> 2331 public byte[] ToBytes() 2332 { 2333 return BitConverterExt.ToBytes(x_Origin, y_Origin, imageWidth, imageHeight, 2334 (byte)pixelDepth, (imageDescriptor == null ? byte.MinValue : imageDescriptor.ToByte())); 2335 } 2336 } 2337 2338 /// <summary> 2339 /// Postage Stamp Image (MaxSize 64x64, uncompressed, PixelDepth like in full image). 2340 /// </summary> 2341 public class TgaPostageStampImage : ICloneable 2342 { 2343 byte width = 0; 2344 byte height = 0; 2345 byte[] data = null; 2346 2347 public TgaPostageStampImage() 2348 { 2349 } 2350 2351 /// <summary> 2352 /// Make <see cref="TgaPostageStampImage"/> from bytes array. 2353 /// </summary> 2354 /// <param name="Bytes">Bytes array, first 2 bytes are <see cref="Width"/> and <see cref="Height"/>, 2355 /// next bytes - image data.</param> 2356 public TgaPostageStampImage(byte[] Bytes) 2357 { 2358 if (Bytes == null) 2359 throw new ArgumentNullException(nameof(Bytes) + " = null!"); 2360 if (Bytes.Length < 2) 2361 throw new ArgumentOutOfRangeException(nameof(Bytes.Length) + " must be >= " + 2 + "!"); 2362 2363 width = Bytes[0]; 2364 height = Bytes[1]; 2365 2366 if (Bytes.Length > 2) 2367 data = BitConverterExt.GetElements(Bytes, 2, Bytes.Length - 2); 2368 } 2369 2370 /// <summary> 2371 /// Make <see cref="TgaPostageStampImage"/> from bytes and size. 2372 /// </summary> 2373 /// <param name="Width">Image Width.</param> 2374 /// <param name="Height">Image Height.</param> 2375 /// <param name="Bytes">Postage Stamp Image Data.</param> 2376 public TgaPostageStampImage(byte Width, byte Height, byte[] Bytes) 2377 { 2378 if (Bytes == null) 2379 throw new ArgumentNullException(nameof(Bytes) + " = null!"); 2380 2381 width = Width; 2382 height = Height; 2383 data = Bytes; 2384 } 2385 2386 /// <summary> 2387 /// Postage Stamp Image Data 2388 /// </summary> 2389 public byte[] Data 2390 { 2391 get { return data; } 2392 set { data = value; } 2393 } 2394 2395 /// <summary> 2396 /// Postage Stamp Image Width (maximum = 64). 2397 /// </summary> 2398 public byte Width 2399 { 2400 get { return width; } 2401 set { width = value; } 2402 } 2403 2404 /// <summary> 2405 /// Postage Stamp Image Height (maximum = 64). 2406 /// </summary> 2407 public byte Height 2408 { 2409 get { return height; } 2410 set { height = value; } 2411 } 2412 2413 /// <summary> 2414 /// Make full copy of <see cref="TgaPostageStampImage"/>. 2415 /// </summary> 2416 /// <returns>Full independent copy of <see cref="TgaPostageStampImage"/>.</returns> 2417 public TgaPostageStampImage Clone() 2418 { 2419 return new TgaPostageStampImage(width, height, BitConverterExt.ToBytes(data)); 2420 } 2421 2422 /// <summary> 2423 /// Make full copy of <see cref="TgaPostageStampImage"/>. 2424 /// </summary> 2425 /// <returns>Full independent copy of <see cref="TgaPostageStampImage"/>.</returns> 2426 object ICloneable.Clone() 2427 { 2428 return Clone(); 2429 } 2430 2431 public override bool Equals(object obj) 2432 { 2433 return ((obj is TgaPostageStampImage) ? Equals((TgaPostageStampImage)obj) : false); 2434 } 2435 2436 public bool Equals(TgaPostageStampImage item) 2437 { 2438 return width == item.width && height == item.height && BitConverterExt.IsArraysEqual(data, item.data); 2439 } 2440 2441 public static bool operator ==(TgaPostageStampImage item1, TgaPostageStampImage item2) 2442 { 2443 if (ReferenceEquals(item1, null)) 2444 return ReferenceEquals(item2, null); 2445 2446 if (ReferenceEquals(item2, null)) 2447 return ReferenceEquals(item1, null); 2448 2449 return item1.Equals(item2); 2450 } 2451 2452 public static bool operator !=(TgaPostageStampImage item1, TgaPostageStampImage item2) 2453 { 2454 return !(item1 == item2); 2455 } 2456 2457 public override int GetHashCode() 2458 { 2459 unchecked 2460 { 2461 int hash = 27; 2462 hash = (13 * hash) + width.GetHashCode(); 2463 hash = (13 * hash) + height.GetHashCode(); 2464 if (data != null) 2465 for (int i = 0; i < data.Length; i++) 2466 hash = (13 * hash) + data[i].GetHashCode(); 2467 return hash; 2468 } 2469 } 2470 2471 public override string ToString() 2472 { 2473 return String.Format("{0}={1}, {2}={3}, DataLength={4}", 2474 nameof(Width), width, nameof(Height), height, (data == null ? -1 : data.Length)); 2475 } 2476 2477 /// <summary> 2478 /// Convert <see cref="TgaPostageStampImage"/> to byte array. 2479 /// </summary> 2480 /// <returns>Byte array.</returns> 2481 public byte[] ToBytes() 2482 { 2483 return BitConverterExt.ToBytes(width, height, data); 2484 } 2485 } 2486 2487 public class TgaSoftVersion : ICloneable 2488 { 2489 ushort versionNumber = 0; 2490 char versionLetter = ' '; 2491 2492 /// <summary> 2493 /// Gets Empty <see cref="TgaSoftVersion"/>, <see cref="VersionLetter"/> = ' ' (space). 2494 /// </summary> 2495 public TgaSoftVersion() 2496 { 2497 } 2498 2499 /// <summary> 2500 /// Make <see cref="TgaSoftVersion"/> from string. 2501 /// </summary> 2502 /// <param name="Str">Input string, example: "123d".</param> 2503 public TgaSoftVersion(string Str) 2504 { 2505 if (Str == null) 2506 throw new ArgumentNullException(); 2507 if (Str.Length < 3 || Str.Length > 4) 2508 throw new ArgumentOutOfRangeException(nameof(Str.Length) + " must be equal 3 or 4!"); 2509 2510 bool Res = ushort.TryParse(Str.Substring(0, 3), out versionNumber); 2511 if (Res && Str.Length == 4) 2512 versionLetter = Str[3]; 2513 } 2514 2515 /// <summary> 2516 /// Make <see cref="TgaSoftVersion"/> from bytes. 2517 /// </summary> 2518 /// <param name="Bytes">Bytes array (byte[3]).</param> 2519 public TgaSoftVersion(byte[] Bytes) 2520 { 2521 if (Bytes == null) 2522 throw new ArgumentNullException(nameof(Bytes) + " = null!"); 2523 if (Bytes.Length != Size) 2524 throw new ArgumentOutOfRangeException(nameof(Bytes.Length) + " must be equal " + Size + "!"); 2525 2526 versionNumber = BitConverter.ToUInt16(Bytes, 0); 2527 versionLetter = Encoding.ASCII.GetString(Bytes, 2, 1)[0]; 2528 } 2529 2530 public TgaSoftVersion(ushort VersionNumber, char VersionLetter = ' ') 2531 { 2532 versionNumber = VersionNumber; 2533 versionLetter = VersionLetter; 2534 } 2535 2536 public ushort VersionNumber 2537 { 2538 get { return versionNumber; } 2539 set { versionNumber = value; } 2540 } 2541 2542 public char VersionLetter 2543 { 2544 get { return versionLetter; } 2545 set { versionLetter = value; } 2546 } 2547 2548 /// <summary> 2549 /// Gets TGA Field size in bytes. 2550 /// </summary> 2551 public const int Size = 3; 2552 2553 /// <summary> 2554 /// Make full copy of <see cref="TgaSoftVersion"/>. 2555 /// </summary> 2556 /// <returns></returns> 2557 public TgaSoftVersion Clone() 2558 { 2559 return new TgaSoftVersion(versionNumber, versionLetter); 2560 } 2561 2562 /// <summary> 2563 /// Make full copy of <see cref="TgaSoftVersion"/>. 2564 /// </summary> 2565 /// <returns></returns> 2566 object ICloneable.Clone() 2567 { 2568 return Clone(); 2569 } 2570 2571 public override bool Equals(object obj) 2572 { 2573 return ((obj is TgaSoftVersion) ? Equals((TgaSoftVersion)obj) : false); 2574 } 2575 2576 public bool Equals(TgaSoftVersion item) 2577 { 2578 return (versionNumber == item.versionNumber && versionLetter == item.versionLetter); 2579 } 2580 2581 public static bool operator ==(TgaSoftVersion item1, TgaSoftVersion item2) 2582 { 2583 if (ReferenceEquals(item1, null)) 2584 return ReferenceEquals(item2, null); 2585 2586 if (ReferenceEquals(item2, null)) 2587 return ReferenceEquals(item1, null); 2588 2589 return item1.Equals(item2); 2590 } 2591 2592 public static bool operator !=(TgaSoftVersion item1, TgaSoftVersion item2) 2593 { 2594 return !(item1 == item2); 2595 } 2596 2597 public override int GetHashCode() 2598 { 2599 return versionNumber.GetHashCode() ^ versionLetter.GetHashCode(); 2600 } 2601 2602 public override string ToString() 2603 { 2604 return (versionNumber.ToString("000") + versionLetter).TrimEnd(new char[] { ' ', '\0' }); 2605 } 2606 2607 /// <summary> 2608 /// Convert <see cref="TgaSoftVersion"/> to byte array. 2609 /// </summary> 2610 /// <returns>Byte array, <see cref="VersionNumber"/> (2 bytes) and 2611 /// <see cref="VersionLetter"/> (ASCII symbol).</returns> 2612 public byte[] ToBytes() 2613 { 2614 return ToBytes(versionNumber, versionLetter); 2615 } 2616 2617 /// <summary> 2618 /// Convert <see cref="TgaSoftVersion"/> to byte array. 2619 /// </summary> 2620 /// <param name="VersionNumber">Set 123 for 1.23 version.</param> 2621 /// <param name="VersionLetter">Version letter, example: for 'a' - "1.23a".</param> 2622 /// <returns>Byte array, <see cref="VersionNumber"/> (2 bytes) and <see cref="VersionLetter"/> (ASCII symbol).</returns> 2623 public static byte[] ToBytes(ushort VersionNumber, char VersionLetter = ' ') 2624 { 2625 return BitConverterExt.ToBytes(VersionNumber, Encoding.ASCII.GetBytes(VersionLetter.ToString())); 2626 } 2627 } 2628 2629 /// <summary> 2630 /// Use it for working with ASCII strings in TGA files. 2631 /// </summary> 2632 public class TgaString : ICloneable 2633 { 2634 public const string XFileSignatuteConst = "TRUEVISION-XFILE"; 2635 public const string DotSymbolConst = "."; 2636 2637 string origString = String.Empty; 2638 int length = 0; 2639 char blankSpaceChar = DefaultBlankSpaceChar; 2640 bool useEnding = false; 2641 2642 public TgaString(bool UseEnding = false) 2643 { 2644 useEnding = UseEnding; 2645 } 2646 2647 public TgaString(byte[] Bytes, bool UseEnding = false) 2648 { 2649 if (Bytes == null) 2650 throw new ArgumentNullException(nameof(Bytes) + " = null!"); 2651 2652 length = Bytes.Length; 2653 useEnding = UseEnding; 2654 string s = Encoding.ASCII.GetString(Bytes, 0, Bytes.Length - (useEnding ? 1 : 0)); 2655 2656 if (s.Length > 0) 2657 switch (s[s.Length - 1]) 2658 { 2659 case '\0': 2660 case ' ': 2661 blankSpaceChar = s[s.Length - 1]; 2662 origString = s.TrimEnd(new char[] { s[s.Length - 1] }); 2663 break; 2664 default: 2665 origString = s; 2666 break; 2667 } 2668 } 2669 2670 public TgaString(int Length, bool UseEnding = false) 2671 { 2672 length = Length; 2673 useEnding = UseEnding; 2674 } 2675 2676 public TgaString(string Str, int Length, bool UseEnding = false, char BlankSpaceChar = '\0') 2677 { 2678 if (Str == null) 2679 throw new ArgumentNullException(nameof(Str) + " = null!"); 2680 2681 origString = Str; 2682 length = Length; 2683 blankSpaceChar = BlankSpaceChar; 2684 useEnding = UseEnding; 2685 } 2686 2687 public string OriginalString 2688 { 2689 get { return origString; } 2690 set { origString = value; } 2691 } 2692 2693 public int Length 2694 { 2695 get { return length; } 2696 set { length = value; } 2697 } 2698 2699 public char BlankSpaceChar 2700 { 2701 get { return blankSpaceChar; } 2702 set { blankSpaceChar = value; } 2703 } 2704 2705 public bool UseEndingChar 2706 { 2707 get { return useEnding; } 2708 set { useEnding = value; } 2709 } 2710 2711 /// <summary> 2712 /// Gets ending char, default '\0'. 2713 /// </summary> 2714 public static readonly char DefaultEndingChar = '\0'; 2715 2716 /// <summary> 2717 /// Gets blank space char, value = '\0'. 2718 /// </summary> 2719 public static readonly char DefaultBlankSpaceChar = '\0'; 2720 2721 /// <summary> 2722 /// Gets Empty <see cref="TgaString"/>. 2723 /// </summary> 2724 public static readonly TgaString Empty = new TgaString(); 2725 2726 /// <summary> 2727 /// Gets <see cref="TgaString"/> with <see cref="DefaultEndingChar"/> = '\0' and <see cref="UseEndingChar"/> = true. 2728 /// </summary> 2729 public static readonly TgaString ZeroTerminator = new TgaString(true); 2730 2731 /// <summary> 2732 /// Gets "." <see cref="TgaString"/> with dot (period) symbol. 2733 /// </summary> 2734 public static readonly TgaString DotSymbol = new TgaString(DotSymbolConst, DotSymbolConst.Length); 2735 2736 /// <summary> 2737 /// Gets "TRUEVISION-XFILE" <see cref="TgaString"/> (TGA File Format Version 2.0 signatute). 2738 /// </summary> 2739 public static readonly TgaString XFileSignatute = new TgaString(XFileSignatuteConst, XFileSignatuteConst.Length); 2740 2741 /// <summary> 2742 /// Make full independed copy of <see cref="TgaString"/>. 2743 /// </summary> 2744 /// <returns>Copy of <see cref="TgaString"/></returns> 2745 public TgaString Clone() 2746 { 2747 return new TgaString(origString, length, useEnding, blankSpaceChar); 2748 } 2749 2750 /// <summary> 2751 /// Make full independed copy of <see cref="TgaString"/>. 2752 /// </summary> 2753 /// <returns>Copy of <see cref="TgaString"/></returns> 2754 object ICloneable.Clone() 2755 { 2756 return Clone(); 2757 } 2758 2759 public override bool Equals(object obj) 2760 { 2761 return ((obj is TgaString) ? Equals((TgaString)obj) : false); 2762 } 2763 2764 public bool Equals(TgaString item) 2765 { 2766 return ( 2767 origString == item.origString && 2768 length == item.length && 2769 blankSpaceChar == item.blankSpaceChar && 2770 useEnding == item.useEnding); 2771 } 2772 2773 public static bool operator ==(TgaString item1, TgaString item2) 2774 { 2775 if (ReferenceEquals(item1, null)) 2776 return ReferenceEquals(item2, null); 2777 2778 if (ReferenceEquals(item2, null)) 2779 return ReferenceEquals(item1, null); 2780 2781 return item1.Equals(item2); 2782 } 2783 2784 public static bool operator !=(TgaString item1, TgaString item2) 2785 { 2786 return !(item1 == item2); 2787 } 2788 2789 public static TgaString operator +(TgaString item1, TgaString item2) 2790 { 2791 if (ReferenceEquals(item1, null) || ReferenceEquals(item2, null)) 2792 throw new ArgumentNullException(); 2793 2794 return new TgaString(BitConverterExt.ToBytes(item1.ToBytes(), item2.ToBytes())); 2795 } 2796 2797 public override int GetHashCode() 2798 { 2799 unchecked 2800 { 2801 int hash = 17; 2802 hash = hash * 23 + origString.GetHashCode(); 2803 hash = hash * 23 + length.GetHashCode(); 2804 hash = hash * 23 + blankSpaceChar.GetHashCode(); 2805 hash = hash * 23 + useEnding.GetHashCode(); 2806 return hash; 2807 } 2808 } 2809 2810 /// <summary> 2811 /// Get ASCII-Like string with string-terminators, example: "Some string\0\0\0\0\0". 2812 /// </summary> 2813 /// <returns>String with replaced string-terminators to "\0".</returns> 2814 public override string ToString() 2815 { 2816 return Encoding.ASCII.GetString(ToBytes()).Replace("\0", @"\0"); 2817 } 2818 2819 /// <summary> 2820 /// Get ASCII-Like string to first string-terminator, example: 2821 /// "Some string \0 Some Data \0" - > "Some string". 2822 /// </summary> 2823 /// <returns>String to first string-terminator.</returns> 2824 public string GetString() 2825 { 2826 String Str = Encoding.ASCII.GetString(ToBytes()); 2827 int EndIndex = Str.IndexOf('\0'); 2828 if (EndIndex != -1) 2829 Str = Str.Substring(0, EndIndex); 2830 return Str; 2831 } 2832 2833 /// <summary> 2834 /// Convert <see cref="TgaString"/> to byte array. 2835 /// </summary> 2836 /// <returns>Byte array, every byte is ASCII symbol.</returns> 2837 public byte[] ToBytes() 2838 { 2839 return ToBytes(origString, length, useEnding, blankSpaceChar); 2840 } 2841 2842 /// <summary> 2843 /// Convert <see cref="TgaString"/> to byte array. 2844 /// </summary> 2845 /// <param name="str">Input string.</param> 2846 /// <param name="Length">Length of output ASCII string with Ending char (if used).</param> 2847 /// <param name="UseEnding">Add <see cref="EndingChr"/> to string or not?</param> 2848 /// <param name="BlankSpaceChar">Char for filling blank space in string. If this char is '-' (only for example!), 2849 /// for string "ABC" with <see cref="Length"/> = 7, with <see cref="UseEnding"/> = true, 2850 /// <see cref="DefaultEndingChar"/> is '\0', result string is "ABC---\0".</param> 2851 /// <returns>Byte array, every byte is ASCII symbol.</returns> 2852 public static byte[] ToBytes(string str, int Length, bool UseEnding = true, char BlankSpaceChar = '\0') 2853 { 2854 char[] C = new char[Math.Max(Length, (UseEnding ? 1 : 0))]; 2855 2856 for (int i = 0; i < C.Length; i++) 2857 C[i] = (i < str.Length ? str[i] : BlankSpaceChar); 2858 2859 if (UseEnding) 2860 C[C.Length - 1] = DefaultEndingChar; 2861 2862 return Encoding.ASCII.GetBytes(C); 2863 } 2864 } 2865 2866 public class TgaTime : ICloneable 2867 { 2868 ushort hours = 0; 2869 ushort minutes = 0; 2870 ushort seconds = 0; 2871 2872 /// <summary> 2873 /// Make empty <see cref="TgaTime"/>. 2874 /// </summary> 2875 public TgaTime() 2876 { 2877 } 2878 2879 /// <summary> 2880 /// Make <see cref="TgaTime"/> from <see cref="TimeSpan"/>. 2881 /// </summary> 2882 /// <param name="Time">Some <see cref="TimeSpan"/> variable.</param> 2883 public TgaTime(TimeSpan Time) 2884 { 2885 hours = (ushort)Time.TotalHours; 2886 minutes = (ushort)Time.Minutes; 2887 seconds = (ushort)Time.Seconds; 2888 } 2889 2890 /// <summary> 2891 /// Make <see cref="TgaTime"/> from ushort values. 2892 /// </summary> 2893 /// <param name="Hours">Hour (0 - 65535).</param> 2894 /// <param name="Minutes">Minute (0 - 59).</param> 2895 /// <param name="Seconds">Second (0 - 59).</param> 2896 public TgaTime(ushort Hours, ushort Minutes, ushort Seconds) 2897 { 2898 hours = Hours; 2899 minutes = Minutes; 2900 seconds = Seconds; 2901 } 2902 2903 /// <summary> 2904 /// Make <see cref="TgaTime"/> from bytes. 2905 /// </summary> 2906 /// <param name="Bytes">Array of bytes(byte[6]).</param> 2907 public TgaTime(byte[] Bytes) 2908 { 2909 if (Bytes == null) 2910 throw new ArgumentNullException(nameof(Bytes) + " = null!"); 2911 else if (Bytes.Length != Size) 2912 throw new ArgumentOutOfRangeException(nameof(Bytes) + " must be equal " + Size + "!"); 2913 2914 hours = BitConverter.ToUInt16(Bytes, 0); 2915 minutes = BitConverter.ToUInt16(Bytes, 2); 2916 seconds = BitConverter.ToUInt16(Bytes, 4); 2917 } 2918 2919 /// <summary> 2920 /// Gets or Sets hour (0 - 65535). 2921 /// </summary> 2922 public ushort Hours 2923 { 2924 get { return hours; } 2925 set { hours = value; } 2926 } 2927 2928 /// <summary> 2929 /// Gets or Sets minute (0 - 59). 2930 /// </summary> 2931 public ushort Minutes 2932 { 2933 get { return minutes; } 2934 set { minutes = value; } 2935 } 2936 2937 /// <summary> 2938 /// Gets or Sets second (0 - 59). 2939 /// </summary> 2940 public ushort Seconds 2941 { 2942 get { return seconds; } 2943 set { seconds = value; } 2944 } 2945 2946 /// <summary> 2947 /// Gets TGA Field size in bytes. 2948 /// </summary> 2949 public const int Size = 6; 2950 2951 /// <summary> 2952 /// Make full independed copy of <see cref="TgaTime"/>. 2953 /// </summary> 2954 /// <returns>Copy of <see cref="TgaTime"/></returns> 2955 public TgaTime Clone() 2956 { 2957 return new TgaTime(hours, minutes, seconds); 2958 } 2959 2960 /// <summary> 2961 /// Make full independed copy of <see cref="TgaTime"/>. 2962 /// </summary> 2963 /// <returns>Copy of <see cref="TgaTime"/></returns> 2964 object ICloneable.Clone() 2965 { 2966 return Clone(); 2967 } 2968 2969 public override bool Equals(object obj) 2970 { 2971 return ((obj is TgaTime) ? Equals((TgaTime)obj) : false); 2972 } 2973 2974 public bool Equals(TgaTime item) 2975 { 2976 return (hours == item.hours && minutes == item.minutes && seconds == item.seconds); 2977 } 2978 2979 public static bool operator ==(TgaTime item1, TgaTime item2) 2980 { 2981 if (ReferenceEquals(item1, null)) 2982 return ReferenceEquals(item2, null); 2983 2984 if (ReferenceEquals(item2, null)) 2985 return ReferenceEquals(item1, null); 2986 2987 return item1.Equals(item2); 2988 } 2989 2990 public static bool operator !=(TgaTime item1, TgaTime item2) 2991 { 2992 return !(item1 == item2); 2993 } 2994 2995 public override int GetHashCode() 2996 { 2997 unchecked 2998 { 2999 int hash = 17; 3000 hash = hash * 23 + hours.GetHashCode(); 3001 hash = hash * 23 + (minutes << 16 | seconds).GetHashCode(); 3002 return hash; 3003 } 3004 } 3005 3006 /// <summary> 3007 /// Gets <see cref="TgaTime"/> like string. 3008 /// </summary> 3009 /// <returns>String in "H:M:S" format.</returns> 3010 public override string ToString() 3011 { 3012 return String.Format("{0}:{1}:{2}", hours, minutes, seconds); 3013 } 3014 3015 /// <summary> 3016 /// Convert <see cref="TgaTime"/> to byte array. 3017 /// </summary> 3018 /// <returns>Byte array with length = 6.</returns> 3019 public byte[] ToBytes() 3020 { 3021 return BitConverterExt.ToBytes(hours, minutes, seconds); 3022 } 3023 3024 /// <summary> 3025 /// Gets <see cref="TgaTime"/> like <see cref="TimeSpan"/>. 3026 /// </summary> 3027 /// <returns><see cref="TimeSpan"/> value of <see cref="TgaTime"/>.</returns> 3028 public TimeSpan ToTimeSpan() 3029 { 3030 return new TimeSpan(hours, minutes, seconds); 3031 } 3032 } 3033 3034 //////////////////////////////////////////////////////////////////////////////////////////////// 3035 3036 /// <summary> 3037 /// File Header Area (18 bytes) 3038 /// </summary> 3039 public class TgaHeader : ICloneable 3040 { 3041 byte idLength = 0; 3042 TgaColorMapType colorMapType = TgaColorMapType.NoColorMap; 3043 TgaImageType imageType = TgaImageType.NoImageData; 3044 TgaColorMapSpec colorMapSpec = new TgaColorMapSpec(); 3045 TgaImageSpec imageSpec = new TgaImageSpec(); 3046 3047 /// <summary> 3048 /// Make empty <see cref="TgaHeader"/>. 3049 /// </summary> 3050 public TgaHeader() 3051 { 3052 } 3053 3054 /// <summary> 3055 /// Make <see cref="TgaHeader"/> from bytes. 3056 /// </summary> 3057 /// <param name="Bytes">Bytes array (byte[18]).</param> 3058 public TgaHeader(byte[] Bytes) 3059 { 3060 if (Bytes == null) 3061 throw new ArgumentNullException(nameof(Bytes) + " = null!"); 3062 if (Bytes.Length != Size) 3063 throw new ArgumentOutOfRangeException(nameof(Bytes.Length) + " must be equal " + Size + "!"); 3064 3065 idLength = Bytes[0]; 3066 colorMapType = (TgaColorMapType)Bytes[1]; 3067 imageType = (TgaImageType)Bytes[2]; 3068 colorMapSpec = new TgaColorMapSpec(BitConverterExt.GetElements(Bytes, 3, TgaColorMapSpec.Size)); 3069 imageSpec = new TgaImageSpec(BitConverterExt.GetElements(Bytes, 8, TgaImageSpec.Size)); 3070 } 3071 3072 /// <summary> 3073 /// ID Length - Field 1 (1 byte): 3074 /// This field identifies the number of bytes contained in the <see cref="ImageID"/> Field. 3075 /// The maximum number of characters is 255. A value of zero indicates that no Image ID 3076 /// field is included with the image. 3077 /// </summary> 3078 public byte IDLength 3079 { 3080 get { return idLength; } 3081 set { idLength = value; } 3082 } 3083 3084 /// <summary> 3085 /// Color Map Type - Field 2 (1 byte): 3086 /// This field indicates the type of color map (if any) included with the image. 3087 /// There are currently 2 defined values for this field: 3088 /// <para>0 - indicates that no color-map data is included with this image;</para> 3089 /// <para>1 - indicates that a color-map is included with this image.</para> 3090 /// </summary> 3091 public TgaColorMapType ColorMapType 3092 { 3093 get { return colorMapType; } 3094 set { colorMapType = value; } 3095 } 3096 3097 /// <summary> 3098 /// Image Type - Field 3 (1 byte): 3099 /// <para>The TGA File Format can be used to store Pseudo-Color, True-Color and Direct-Color images 3100 /// of various pixel depths.</para> 3101 /// </summary> 3102 public TgaImageType ImageType 3103 { 3104 get { return imageType; } 3105 set { imageType = value; } 3106 } 3107 3108 /// <summary> 3109 /// Color Map Specification - Field 4 (5 bytes): 3110 /// <para>This field and its sub-fields describe the color map (if any) used for the image. 3111 /// If the Color Map Type field is set to zero, indicating that no color map exists, then 3112 /// these 5 bytes should be set to zero. These bytes always must be written to the file.</para> 3113 /// </summary> 3114 public TgaColorMapSpec ColorMapSpec 3115 { 3116 get { return colorMapSpec; } 3117 set { colorMapSpec = value; } 3118 } 3119 3120 /// <summary> 3121 /// Image Specification - Field 5 (10 bytes): 3122 /// <para>This field and its sub-fields describe the image screen location, size and pixel depth. 3123 /// These information is always written to the file.</para> 3124 /// </summary> 3125 public TgaImageSpec ImageSpec 3126 { 3127 get { return imageSpec; } 3128 set { imageSpec = value; } 3129 } 3130 3131 /// <summary> 3132 /// Gets TGA Header Section size in bytes. 3133 /// </summary> 3134 public const int Size = 18; 3135 3136 /// <summary> 3137 /// Make full copy of <see cref="TgaHeader"/>. 3138 /// </summary> 3139 /// <returns>Full independent copy of <see cref="TgaHeader"/>.</returns> 3140 public TgaHeader Clone() 3141 { 3142 return new TgaHeader(ToBytes()); 3143 } 3144 3145 /// <summary> 3146 /// Make full copy of <see cref="TgaHeader"/>. 3147 /// </summary> 3148 /// <returns>Full independent copy of <see cref="TgaHeader"/>.</returns> 3149 object ICloneable.Clone() 3150 { 3151 return Clone(); 3152 } 3153 3154 public override bool Equals(object obj) 3155 { 3156 return ((obj is TgaHeader) ? Equals((TgaHeader)obj) : false); 3157 } 3158 3159 public bool Equals(TgaHeader item) 3160 { 3161 return (idLength == item.idLength && 3162 colorMapType == item.colorMapType && 3163 imageType == item.imageType && 3164 colorMapSpec == item.colorMapSpec && 3165 imageSpec == item.imageSpec); 3166 } 3167 3168 public static bool operator ==(TgaHeader item1, TgaHeader item2) 3169 { 3170 if (ReferenceEquals(item1, null)) 3171 return ReferenceEquals(item2, null); 3172 3173 if (ReferenceEquals(item2, null)) 3174 return ReferenceEquals(item1, null); 3175 3176 return item1.Equals(item2); 3177 } 3178 3179 public static bool operator !=(TgaHeader item1, TgaHeader item2) 3180 { 3181 return !(item1 == item2); 3182 } 3183 3184 public override int GetHashCode() 3185 { 3186 unchecked 3187 { 3188 int hash = 17; 3189 hash = hash * 23 + (idLength << 24 | (byte)colorMapType << 8 | (byte)imageType).GetHashCode(); 3190 3191 if (colorMapSpec != null) 3192 hash = hash * 23 + colorMapSpec.GetHashCode(); 3193 3194 if (imageSpec != null) 3195 hash = hash * 23 + imageSpec.GetHashCode(); 3196 3197 return hash; 3198 } 3199 } 3200 3201 public override string ToString() 3202 { 3203 return String.Format("{0}={1}, {2}={3}, {4}={5}, {6}={7}, {8}={9}", 3204 nameof(IDLength), idLength, 3205 nameof(ColorMapType), colorMapType, 3206 nameof(ImageType), imageType, 3207 nameof(ColorMapSpec), colorMapSpec, 3208 nameof(ImageSpec), imageSpec); 3209 } 3210 3211 /// <summary> 3212 /// Convert <see cref="TgaHeader"/> to byte array. 3213 /// </summary> 3214 /// <returns>Byte array with size equal <see cref="Size"/>.</returns> 3215 public byte[] ToBytes() 3216 { 3217 return BitConverterExt.ToBytes(idLength, (byte)colorMapType, (byte)imageType, 3218 (colorMapSpec == null ? new byte[TgaColorMapSpec.Size] : colorMapSpec.ToBytes()), 3219 (imageSpec == null ? new byte[TgaImageSpec.Size] : imageSpec.ToBytes())); 3220 } 3221 } 3222 3223 /// <summary> 3224 /// Image Or ColorMap Area 3225 /// </summary> 3226 public class TgaImgOrColMap : ICloneable 3227 { 3228 TgaString imageID = null; 3229 byte[] colorMapData = null; 3230 byte[] imageData = null; 3231 3232 /// <summary> 3233 /// Make empty <see cref="TgaImgOrColMap"/>. 3234 /// </summary> 3235 public TgaImgOrColMap() 3236 { 3237 } 3238 3239 /// <summary> 3240 /// Make <see cref="TgaImgOrColMap"/> from arrays. 3241 /// </summary> 3242 /// <param name="ImageID">This optional field contains identifying information about the image. 3243 /// The maximum length for this field is 255 bytes. Refer to <see cref="TgaHeader.IDLength"/> 3244 /// for the length of this field. If field 1 is set to Zero indicating that no Image ID exists 3245 /// then these bytes are not written to the file.</param> 3246 /// <param name="ColorMapData">Color Map Data, see <see cref="ColorMapData"/> description.</param> 3247 /// <param name="ImageData">Image Data, see <see cref="ImageData"/> description.</param> 3248 public TgaImgOrColMap(TgaString ImageID, byte[] ColorMapData, byte[] ImageData) 3249 { 3250 imageID = ImageID; 3251 colorMapData = ColorMapData; 3252 imageData = ImageData; 3253 } 3254 3255 /// <summary> 3256 /// Image ID - Field 6 (variable): 3257 /// <para>This optional field contains identifying information about the image. The maximum length 3258 /// for this field is 255 bytes. Refer to <see cref="TgaHeader.IDLength"/> for the length of this 3259 /// field. If field 1 is set to Zero indicating that no Image ID exists then these bytes are not 3260 /// written to the file. Can have text inside (ASCII).</para> 3261 /// </summary> 3262 public TgaString ImageID 3263 { 3264 get { return imageID; } 3265 set { imageID = value; } 3266 } 3267 3268 /// <summary> 3269 /// Color Map Data - Field 7 (variable): 3270 /// <para>If the Color Map Type(field 2) field is set to zero indicating that no Color-Map 3271 /// exists then this field will not be present (i.e., no bytes written to the file).</para> 3272 /// <para>This variable-length field contains the actual color map information (LUT data). 3273 /// Field 4.3 specifies the width in bits of each color map entry while Field 4.2 specifies 3274 /// the number of color map entries in this field. These two fields together are used to 3275 /// determine the number of bytes contained in field 7.</para> 3276 /// <para>Each color map entry is stored using an integral number of bytes.The RGB specification 3277 /// for each color map entry is stored in successive bit-fields in the multi-byte entries. 3278 /// Each color bit-field is assumed to be MIN(Field4.3/3, 8) bits in length. If Field 4.3 3279 /// contains 24, then each color specification is 8 bits in length; if Field 4.3 contains 32, 3280 /// then each color specification is also 8 bits (32/3 gives 10, but 8 is smaller). 3281 /// Unused bit(s) in the multi-byte entries are assumed to specify attribute bits. The 3282 /// attribute bit field is often called the Alpha Channel, Overlay Bit(s) or Interrupt Bit(s).</para> 3283 /// For the TARGA M-8, ATVista and NuVista, the number of bits in a color map specification is 3284 /// 24 (or 32). The red, green, and blue components are each represented by one byte. 3285 /// </summary> 3286 public byte[] ColorMapData 3287 { 3288 get { return colorMapData; } 3289 set { colorMapData = value; } 3290 } 3291 3292 /// <summary> 3293 /// Image Data - Field 8 (variable): 3294 /// <para>This field contains (Width)x(Height) pixels. Each pixel specifies image data in one 3295 /// of the following formats:</para> 3296 /// <para>a single color-map index for Pseudo-Color; 3297 /// Attribute, Red, Green and Blue ordered data for True-Color; 3298 /// and independent color-map indices for Direct-Color.</para> 3299 /// <para>The values for Width and Height are specified in Fields 5.3 and 5.4 respectively. 3300 /// The number of attribute and color-definition bits for each pixel are defined in Fields 5.6 3301 /// and 5.5, respectively.Each pixel is stored as an integral number of bytes.</para> 3302 /// </summary> 3303 public byte[] ImageData 3304 { 3305 get { return imageData; } 3306 set { imageData = value; } 3307 } 3308 3309 /// <summary> 3310 /// Make full copy of <see cref="TgaImgOrColMap"/>. 3311 /// </summary> 3312 /// <returns>Full independed copy of <see cref="TgaImgOrColMap"/>.</returns> 3313 public TgaImgOrColMap Clone() 3314 { 3315 return new TgaImgOrColMap( 3316 (imageID == null ? null : imageID.Clone()), 3317 (colorMapData == null ? null : (byte[])colorMapData.Clone()), 3318 (imageData == null ? null : (byte[])imageData.Clone())); 3319 } 3320 3321 /// <summary> 3322 /// Make full copy of <see cref="TgaImgOrColMap"/>. 3323 /// </summary> 3324 /// <returns>Full independed copy of <see cref="TgaImgOrColMap"/>.</returns> 3325 object ICloneable.Clone() 3326 { 3327 return Clone(); 3328 } 3329 3330 public override bool Equals(object obj) 3331 { 3332 return ((obj is TgaImgOrColMap) ? Equals((TgaImgOrColMap)obj) : false); 3333 } 3334 3335 public bool Equals(TgaImgOrColMap item) 3336 { 3337 return imageID == item.imageID && 3338 BitConverterExt.IsArraysEqual(colorMapData, item.colorMapData) && 3339 BitConverterExt.IsArraysEqual(imageData, item.imageData); 3340 } 3341 3342 public static bool operator ==(TgaImgOrColMap item1, TgaImgOrColMap item2) 3343 { 3344 if (ReferenceEquals(item1, null)) 3345 return ReferenceEquals(item2, null); 3346 3347 if (ReferenceEquals(item2, null)) 3348 return ReferenceEquals(item1, null); 3349 3350 return item1.Equals(item2); 3351 } 3352 3353 public static bool operator !=(TgaImgOrColMap item1, TgaImgOrColMap item2) 3354 { 3355 return !(item1 == item2); 3356 } 3357 3358 public override int GetHashCode() 3359 { 3360 unchecked 3361 { 3362 int hash = 27; 3363 3364 if (imageID != null) 3365 hash = (13 * hash) + imageID.GetHashCode(); 3366 if (colorMapData != null) 3367 for (int i = 0; i < colorMapData.Length; i++) 3368 hash = (13 * hash) + colorMapData[i].GetHashCode(); 3369 if (imageData != null) 3370 for (int i = 0; i < imageData.Length; i++) 3371 hash = (13 * hash) + imageData[i].GetHashCode(); 3372 3373 return hash; 3374 } 3375 } 3376 } //No ToBytes() 3377 3378 /// <summary> 3379 /// Developer Area 3380 /// </summary> //? 3381 public class TgaDevArea : ICloneable 3382 { 3383 List<TgaDevEntry> entries = new List<TgaDevEntry>(); 3384 3385 public TgaDevArea() 3386 { 3387 } 3388 3389 public TgaDevArea(List<TgaDevEntry> Entries) 3390 { 3391 if (Entries == null) 3392 throw new ArgumentNullException(nameof(Entries) + " = null!"); 3393 3394 entries = Entries; 3395 } 3396 3397 /// <summary> 3398 /// Developer Data - Field 9 (variable): 3399 /// </summary> 3400 public List<TgaDevEntry> Entries 3401 { 3402 get { return entries; } 3403 set { entries = value; } 3404 } 3405 3406 public int Count 3407 { 3408 get { return entries.Count; } 3409 } 3410 3411 public TgaDevEntry this[int index] 3412 { 3413 get { return entries[index]; } 3414 set { entries[index] = value; } 3415 } 3416 3417 /// <summary> 3418 /// Make full copy of <see cref="TgaDevArea"/>. 3419 /// </summary> 3420 /// <returns>Full independent copy of <see cref="TgaDevArea"/>.</returns> 3421 public TgaDevArea Clone() 3422 { 3423 if (entries == null) 3424 return new TgaDevArea(null); 3425 3426 List<TgaDevEntry> L = new List<TgaDevEntry>(); 3427 for (int i = 0; i < entries.Count; i++) 3428 L.Add(entries[i].Clone()); 3429 3430 return new TgaDevArea(L); 3431 } 3432 3433 /// <summary> 3434 /// Make full copy of <see cref="TgaDevArea"/>. 3435 /// </summary> 3436 /// <returns>Full independent copy of <see cref="TgaDevArea"/>.</returns> 3437 object ICloneable.Clone() 3438 { 3439 return Clone(); 3440 } 3441 3442 public override bool Equals(object obj) 3443 { 3444 return ((obj is TgaDevArea) ? Equals((TgaDevArea)obj) : false); 3445 } 3446 3447 public bool Equals(TgaDevArea item) 3448 { 3449 return BitConverterExt.IsListsEqual(entries, item.entries); 3450 } 3451 3452 public static bool operator ==(TgaDevArea item1, TgaDevArea item2) 3453 { 3454 if (ReferenceEquals(item1, null)) 3455 return ReferenceEquals(item2, null); 3456 3457 if (ReferenceEquals(item2, null)) 3458 return ReferenceEquals(item1, null); 3459 3460 return item1.Equals(item2); 3461 } 3462 3463 public static bool operator !=(TgaDevArea item1, TgaDevArea item2) 3464 { 3465 return !(item1 == item2); 3466 } 3467 3468 public override int GetHashCode() 3469 { 3470 unchecked 3471 { 3472 int hash = 27; 3473 if (entries != null) 3474 for (int i = 0; i < entries.Count; i++) 3475 hash = (13 * hash) + entries[i].GetHashCode(); 3476 return hash; 3477 } 3478 } 3479 3480 /// <summary> 3481 /// Convert <see cref="TgaDevArea"/> (without Fields Data, only Directory!) to byte array. 3482 /// </summary> 3483 /// <returns>Byte array, Len = (NUMBER_OF_TAGS_IN_THE_DIRECTORY * 10) + 2 bytes in size. 3484 /// The "+ 2" includes the 2 bytes for the number of tags in the directory.</returns> 3485 public byte[] ToBytes() 3486 { 3487 if (entries == null) 3488 throw new Exception(nameof(Entries) + " = null!"); 3489 3490 ushort NumberOfEntries = (ushort)Math.Min(ushort.MaxValue, entries.Count); 3491 List<byte> DevDir = new List<byte>(BitConverter.GetBytes(NumberOfEntries)); 3492 3493 for (int i = 0; i < entries.Count; i++) 3494 { 3495 DevDir.AddRange(BitConverter.GetBytes(entries[i].Tag)); 3496 DevDir.AddRange(BitConverter.GetBytes(entries[i].Offset)); 3497 DevDir.AddRange(BitConverter.GetBytes(entries[i].FieldSize)); 3498 } 3499 3500 return DevDir.ToArray(); 3501 } 3502 } //Not full ToBytes() 3503 3504 /// <summary> 3505 /// Extension Area 3506 /// </summary> 3507 public class TgaExtArea : ICloneable 3508 { 3509 public const int MinSize = 495; //bytes 3510 3511 ushort extensionSize = MinSize; 3512 TgaString authorName = new TgaString(41, true); 3513 TgaComment authorComments = new TgaComment(); 3514 TgaDateTime dateTimeStamp = new TgaDateTime(); 3515 TgaString jobNameOrID = new TgaString(41, true); 3516 TgaTime jobTime = new TgaTime(); 3517 TgaString softwareID = new TgaString(41, true); 3518 TgaSoftVersion softVersion = new TgaSoftVersion(); 3519 TgaColorKey keyColor = new TgaColorKey(); 3520 TgaFraction pixelAspectRatio = TgaFraction.Empty; 3521 TgaFraction gammaValue = TgaFraction.Empty; 3522 uint colorCorrectionOffset = 0; 3523 uint postageStampOffset = 0; 3524 uint scanLineOffset = 0; 3525 TgaAttrType attributesType = TgaAttrType.NoAlpha; 3526 uint[] scanLineTable = null; 3527 TgaPostageStampImage postageStampImage = null; 3528 ushort[] colorCorrectionTable = null; 3529 byte[] otherDataInExtensionArea = null; 3530 3531 public TgaExtArea() 3532 { 3533 } 3534 3535 /// <summary> 3536 /// Make <see cref="TgaExtArea"/> from bytes. Warning: <see cref="ScanLineTable"/>, 3537 /// <see cref="PostageStampImage"/>, <see cref="ColorCorrectionTable"/> not included, 3538 /// because thea are can be not in the Extension Area of TGA file! 3539 /// </summary> 3540 /// <param name="Bytes">Bytes of <see cref="TgaExtArea"/>.</param> 3541 /// <param name="SLT">Scan Line Table.</param> 3542 /// <param name="PostImg">Postage Stamp Image.</param> 3543 /// <param name="CCT">Color Correction Table.</param> 3544 public TgaExtArea(byte[] Bytes, uint[] SLT = null, TgaPostageStampImage PostImg = null, ushort[] CCT = null) 3545 { 3546 if (Bytes == null) 3547 throw new ArgumentNullException(nameof(Bytes) + " = null!"); 3548 if (Bytes.Length < MinSize) 3549 throw new ArgumentOutOfRangeException(nameof(Bytes.Length) + " must be >= " + MinSize + "!"); 3550 3551 extensionSize = BitConverter.ToUInt16(Bytes, 0); 3552 authorName = new TgaString(BitConverterExt.GetElements(Bytes, 2, 41), true); 3553 authorComments = new TgaComment(BitConverterExt.GetElements(Bytes, 43, TgaComment.Size)); 3554 dateTimeStamp = new TgaDateTime(BitConverterExt.GetElements(Bytes, 367, TgaDateTime.Size)); 3555 jobNameOrID = new TgaString(BitConverterExt.GetElements(Bytes, 379, 41), true); 3556 jobTime = new TgaTime(BitConverterExt.GetElements(Bytes, 420, TgaTime.Size)); 3557 softwareID = new TgaString(BitConverterExt.GetElements(Bytes, 426, 41), true); 3558 softVersion = new TgaSoftVersion(BitConverterExt.GetElements(Bytes, 467, TgaSoftVersion.Size)); 3559 keyColor = new TgaColorKey(BitConverterExt.GetElements(Bytes, 470, TgaColorKey.Size)); 3560 pixelAspectRatio = new TgaFraction(BitConverterExt.GetElements(Bytes, 474, TgaFraction.Size)); 3561 gammaValue = new TgaFraction(BitConverterExt.GetElements(Bytes, 478, TgaFraction.Size)); 3562 colorCorrectionOffset = BitConverter.ToUInt32(Bytes, 482); 3563 postageStampOffset = BitConverter.ToUInt32(Bytes, 486); 3564 scanLineOffset = BitConverter.ToUInt32(Bytes, 490); 3565 attributesType = (TgaAttrType)Bytes[494]; 3566 3567 if (extensionSize > MinSize) 3568 otherDataInExtensionArea = BitConverterExt.GetElements(Bytes, 495, Bytes.Length - MinSize); 3569 3570 scanLineTable = SLT; 3571 postageStampImage = PostImg; 3572 colorCorrectionTable = CCT; 3573 } 3574 3575 #region Properties 3576 /// <summary> 3577 /// Extension Size - Field 10 (2 Bytes): 3578 /// This field is a SHORT field which specifies the number of BYTES in the fixedlength portion of 3579 /// the Extension Area. For Version 2.0 of the TGA File Format, this number should be set to 495. 3580 /// If the number found in this field is not 495, then the file will be assumed to be of a 3581 /// version other than 2.0. If it ever becomes necessary to alter this number, the change 3582 /// will be controlled by Truevision, and will be accompanied by a revision to the TGA File 3583 /// Format with an accompanying change in the version number. 3584 /// </summary> 3585 public ushort ExtensionSize 3586 { 3587 get { return extensionSize; } 3588 set { extensionSize = value; } 3589 } 3590 3591 /// <summary> 3592 /// Author Name - Field 11 (41 Bytes): 3593 /// Bytes 2-42 - This field is an ASCII field of 41 bytes where the last byte must be a null 3594 /// (binary zero). This gives a total of 40 ASCII characters for the name. If the field is used, 3595 /// it should contain the name of the person who created the image (author). If the field is not 3596 /// used, you may fill it with nulls or a series of blanks(spaces) terminated by a null. 3597 /// The 41st byte must always be a null. 3598 /// </summary> 3599 public TgaString AuthorName 3600 { 3601 get { return authorName; } 3602 set { authorName = value; } 3603 } 3604 3605 /// <summary> 3606 /// Author Comments - Field 12 (324 Bytes): 3607 /// Bytes 43-366 - This is an ASCII field consisting of 324 bytes which are organized as four lines 3608 /// of 80 characters, each followed by a null terminator.This field is provided, in addition to the 3609 /// original IMAGE ID field(in the original TGA format), because it was determined that a few 3610 /// developers had used the IMAGE ID field for their own purposes.This field gives the developer 3611 /// four lines of 80 characters each, to use as an Author Comment area. Each line is fixed to 81 3612 /// bytes which makes access to the four lines easy.Each line must be terminated by a null. 3613 /// If you do not use all 80 available characters in the line, place the null after the last 3614 /// character and blank or null fill the rest of the line. The 81st byte of each of the four 3615 /// lines must be null. 3616 /// </summary> 3617 public TgaComment AuthorComments 3618 { 3619 get { return authorComments; } 3620 set { authorComments = value; } 3621 } 3622 3623 /// <summary> 3624 /// Date/Time Stamp - Field 13 (12 Bytes): 3625 /// Bytes 367-378 - This field contains a series of 6 SHORT values which define the integer 3626 /// value for the date and time that the image was saved. This data is formatted as follows: 3627 /// <para>SHORT 0: Month(1 - 12)</para> 3628 /// <para>SHORT 1: Day(1 - 31)</para> 3629 /// <para>SHORT 2: Year(4 digit, ie. 1989)</para> 3630 /// <para>SHORT 3: Hour(0 - 23)</para> 3631 /// <para>SHORT 4: Minute(0 - 59)</para> 3632 /// <para>SHORT 5: Second(0 - 59)</para> 3633 /// Even though operating systems typically time- and date-stamp files, this feature is 3634 /// provided because the operating system may change the time and date stamp if the file is 3635 /// copied. By using this area, you are guaranteed an unmodified region for date and time 3636 /// recording. If the fields are not used, you should fill them with binary zeros (0). 3637 /// </summary> 3638 public TgaDateTime DateTimeStamp 3639 { 3640 get { return dateTimeStamp; } 3641 set { dateTimeStamp = value; } 3642 } 3643 3644 /// <summary> 3645 /// Job Name/ID - Field 14 (41 Bytes): 3646 /// Bytes 379-419 - This field is an ASCII field of 41 bytes where the last byte must be 3647 /// a binary zero. This gives a total of 40 ASCII characters for the job name or the ID. 3648 /// If the field is used, it should contain a name or id tag which refers to the job with 3649 /// which the image was associated.This allows production companies (and others) to tie 3650 /// images with jobs by using this field as a job name (i.e., CITY BANK) or job id number 3651 /// (i.e., CITY023). If the field is not used, you may fill it with a null terminated series 3652 /// of blanks (spaces) or nulls. In any case, the 41st byte must be a null. 3653 /// </summary> 3654 public TgaString JobNameOrID 3655 { 3656 get { return jobNameOrID; } 3657 set { jobNameOrID = value; } 3658 } 3659 3660 /// <summary> 3661 /// Job Time - Field 15 (6 Bytes): 3662 /// Bytes 420-425 - This field contains a series of 3 SHORT values which define the integer 3663 /// value for the job elapsed time when the image was saved.This data is formatted as follows: 3664 /// <para>SHORT 0: Hours(0 - 65535)</para> 3665 /// <para>SHORT 1: Minutes(0 - 59)</para> 3666 /// <para>SHORT 2: Seconds(0 - 59)</para> 3667 /// The purpose of this field is to allow production houses (and others) to keep a running total 3668 /// of the amount of time invested in a particular image. This may be useful for billing, costing, 3669 /// and time estimating. If the fields are not used, you should fill them with binary zeros (0). 3670 /// </summary> 3671 public TgaTime JobTime 3672 { 3673 get { return jobTime; } 3674 set { jobTime = value; } 3675 } 3676 3677 /// <summary> 3678 /// Software ID - Field 16 (41 Bytes): 3679 /// Bytes 426-466 - This field is an ASCII field of 41 bytes where the last byte must be 3680 /// a binary zero (null). This gives a total of 40 ASCII characters for the Software ID. 3681 /// The purpose of this field is to allow software to determine and record with what program 3682 /// a particular image was created.If the field is not used, you may fill it with a 3683 /// null terminated series of blanks (spaces) or nulls. The 41st byte must always be a null. 3684 /// </summary> 3685 public TgaString SoftwareID 3686 { 3687 get { return softwareID; } 3688 set { softwareID = value; } 3689 } 3690 3691 /// <summary> 3692 /// Software Version - Field 17 (3 Bytes): 3693 /// Bytes 467-469 - This field consists of two sub-fields, a SHORT and an ASCII BYTE. 3694 /// The purpose of this field is to define the version of software defined by the 3695 /// “Software ID” field above. The SHORT contains the version number as a binary 3696 /// integer times 100. 3697 /// <para>Therefore, software version 4.17 would be the integer value 417.This allows for 3698 /// two decimal positions of sub-version.The ASCII BYTE supports developers who also 3699 /// tag a release letter to the end. For example, if the version number is 1.17b, then 3700 /// the SHORT would contain 117. and the ASCII BYTE would contain “b”. 3701 /// The organization is as follows:</para> 3702 /// <para>SHORT (Bytes 0 - 1): Version Number * 100</para> 3703 /// <para>BYTE(Byte 2): Version Letter</para> 3704 /// If you do not use this field, set the SHORT to binary zero, and the BYTE to a space(“ “) 3705 /// </summary> 3706 public TgaSoftVersion SoftVersion 3707 { 3708 get { return softVersion; } 3709 set { softVersion = value; } 3710 } 3711 3712 /// <summary> 3713 /// Key Color - Field 18 (4 Bytes): 3714 /// Bytes 470-473 - This field contains a long value which is the key color in effect at 3715 /// the time the image is saved. The format is in A:R:G:B where ‘A’ (most significant byte) 3716 /// is the alpha channel key color(if you don’t have an alpha channel in your application, 3717 /// keep this byte zero [0]). 3718 /// <para>The Key Color can be thought of as the ‘background color’ or ‘transparent color’. 3719 /// This is the color of the ‘non image’ area of the screen, and the same color that the 3720 /// screen would be cleared to if erased in the application. If you don’t use this field, 3721 /// set it to all zeros (0). Setting the field to all zeros is the same as selecting a key 3722 /// color of black.</para> 3723 /// A good example of a key color is the ‘transparent color’ used in TIPS™ for WINDOW loading/saving. 3724 /// </summary> 3725 public TgaColorKey KeyColor 3726 { 3727 get { return keyColor; } 3728 set { keyColor = value; } 3729 } 3730 3731 /// <summary> 3732 /// Pixel Aspect Ratio - Field 19 (4 Bytes): 3733 /// Bytes 474-477 - This field contains two SHORT sub-fields, which when taken together 3734 /// specify a pixel size ratio.The format is as follows: 3735 /// <para>SHORT 0: Pixel Ratio Numerator(pixel width)</para> 3736 /// <para>SHORT 1: Pixel Ratio Denominator(pixel height)</para> 3737 /// These sub-fields may be used to determine the aspect ratio of a pixel. This is useful when 3738 /// it is important to preserve the proper aspect ratio of the saved image. If the two values 3739 /// are set to the same non-zero value, then the image is composed of square pixels. A zero 3740 /// in the second sub-field (denominator) indicates that no pixel aspect ratio is specified. 3741 /// </summary> 3742 public TgaFraction PixelAspectRatio 3743 { 3744 get { return pixelAspectRatio; } 3745 set { pixelAspectRatio = value; } 3746 } 3747 3748 /// <summary> 3749 /// Gamma Value - Field 20 (4 Bytes): 3750 /// Bytes 478-481 - This field contains two SHORT sub-fields, which when taken together in a ratio, 3751 /// provide a fractional gamma value.The format is as follows: 3752 /// <para>SHORT 0: Gamma Numerator</para> 3753 /// <para>SHORT 1: Gamma Denominator</para> 3754 /// The resulting value should be in the range of 0.0 to 10.0, with only one decimal place of 3755 /// precision necessary. An uncorrected image (an image with no gamma) should have the value 1.0 as 3756 /// the result.This may be accomplished by placing thesame, non-zero values in both positions 3757 /// (i.e., 1/1). If you decide to totally ignore this field, please set the denominator (the second 3758 /// SHORT) to the value zero. This will indicate that the Gamma Value field is not being used. 3759 /// </summary> 3760 public TgaFraction GammaValue 3761 { 3762 get { return gammaValue; } 3763 set { gammaValue = value; } 3764 } 3765 3766 /// <summary> 3767 /// Color Correction Offset - Field 21 (4 Bytes): 3768 /// Bytes 482-485 - This field is a 4-byte field containing a single offset value. This is an offset 3769 /// from the beginning of the file to the start of the Color Correction table. This table may be 3770 /// written anywhere between the end of the Image Data field (field 8) and the start of the TGA 3771 /// File Footer. If the image has no Color Correction Table or if the Gamma Value setting is 3772 /// sufficient, set this value to zero and do not write a Correction Table anywhere. 3773 /// </summary> 3774 public uint ColorCorrectionTableOffset 3775 { 3776 get { return colorCorrectionOffset; } 3777 set { colorCorrectionOffset = value; } 3778 } 3779 3780 /// <summary> 3781 /// Postage Stamp Offset - Field 22 (4 Bytes): 3782 /// Bytes 486-489 - This field is a 4-byte field containing a single offset value. This is an offset 3783 /// from the beginning of the file to the start of the Postage Stamp Image. The Postage Stamp Image 3784 /// must be written after Field 25 (Scan Line Table) but before the start of the TGA File Footer. 3785 /// If no postage stamp is stored, set this field to the value zero (0). 3786 /// </summary> 3787 public uint PostageStampOffset 3788 { 3789 get { return postageStampOffset; } 3790 set { postageStampOffset = value; } 3791 } 3792 3793 /// <summary> 3794 /// Scan Line Offset - Field 23 (4 Bytes): 3795 /// Bytes 490-493 - This field is a 4-byte field containing a single offset value. This is an 3796 /// offset from the beginning of the file to the start of the Scan Line Table. 3797 /// </summary> 3798 public uint ScanLineOffset 3799 { 3800 get { return scanLineOffset; } 3801 set { scanLineOffset = value; } 3802 } 3803 3804 /// <summary> 3805 /// Attributes Type - Field 24 (1 Byte): 3806 /// Byte 494 - This single byte field contains a value which specifies the type of Alpha channel 3807 /// data contained in the file. Value Meaning: 3808 /// <para>0: no Alpha data included (bits 3-0 of field 5.6 should also be set to zero)</para> 3809 /// <para>1: undefined data in the Alpha field, can be ignored</para> 3810 /// <para>2: undefined data in the Alpha field, but should be retained</para> 3811 /// <para>3: useful Alpha channel data is present</para> 3812 /// <para>4: pre-multiplied Alpha(see description below)</para> 3813 /// <para>5 -127: RESERVED</para> 3814 /// <para>128-255: Un-assigned</para> 3815 /// <para>Pre-multiplied Alpha Example: Suppose the Alpha channel data is being used to specify the 3816 /// opacity of each pixel(for use when the image is overlayed on another image), where 0 indicates 3817 /// that the pixel is completely transparent and a value of 1 indicates that the pixel is 3818 /// completely opaque(assume all component values have been normalized).</para> 3819 /// <para>A quadruple(a, r, g, b) of( 0.5, 1, 0, 0) would indicate that the pixel is pure red with a 3820 /// transparency of one-half. For numerous reasons(including image compositing) is is better to 3821 /// pre-multiply the individual color components with the value in the Alpha channel.</para> 3822 /// A pre-multiplication of the above would produce a quadruple(0.5, 0.5, 0, 0). 3823 /// A value of 3 in the Attributes Type Field(field 23) would indicate that the color components 3824 /// of the pixel have already been scaled by the value in the Alpha channel. 3825 /// </summary> 3826 public TgaAttrType AttributesType 3827 { 3828 get { return attributesType; } 3829 set { attributesType = value; } 3830 } 3831 3832 /// <summary> 3833 /// Scan Line Table - Field 25 (Variable): 3834 /// This information is provided, at the developers’ request, for two purposes: 3835 /// <para>1) To make random access of compressed images easy.</para> 3836 /// <para>2) To allow “giant picture” access in smaller “chunks”.</para> 3837 /// This table should contain a series of 4-byte offsets.Each offset you write should point to the 3838 /// start of the next scan line, in the order that the image was saved (i.e., top down or bottom up). 3839 /// The offset should be from the start of the file.Therefore, you will have a four byte value for 3840 /// each scan line in your image. This means that if your image is 768 pixels tall, you will have 768, 3841 /// 4-byte offset pointers (for a total of 3072 bytes). This size is not extreme, and thus this table 3842 /// can be built and maintained in memory, and then written out at the proper time. 3843 /// </summary> 3844 public uint[] ScanLineTable 3845 { 3846 get { return scanLineTable; } 3847 set { scanLineTable = value; } 3848 } 3849 3850 /// <summary> 3851 /// Postage Stamp Image - Field 26 (Variable): 3852 /// The Postage Stamp area is a smaller representation of the original image. This is useful for 3853 /// “browsing” a collection of image files. If your application can deal with a postage stamp image, 3854 /// it is recommended that you create one using sub-sampling techniques to create the best 3855 /// representation possible. The postage stamp image must be stored in the same format as the normal 3856 /// image specified in the file, but without any compression. The first byte of the postage stamp 3857 /// image specifies the X size of the stamp in pixels, the second byte of the stamp image specifies the 3858 /// Y size of the stamp in pixels. Truevision does not recommend stamps larger than 64x64 pixels, and 3859 /// suggests that any stamps stored larger be clipped. Obviously, the storage of the postage stamp 3860 /// relies heavily on the storage of the image. The two images are stored using the same format under 3861 /// the assumption that if you can read the image, then you can read the postage stamp. If the original 3862 /// image is color mapped, DO NOT average the postage stamp, as you will create new colors not in your map. 3863 /// </summary> 3864 public TgaPostageStampImage PostageStampImage 3865 { 3866 get { return postageStampImage; } 3867 set { postageStampImage = value; } 3868 } 3869 3870 /// <summary> 3871 /// Color Correction Table - Field 27 (2K Bytes): 3872 /// The Color Correction Table is a block of 256 x 4 SHORT values, where each set of 3873 /// four contiguous values are the desired A:R:G:B correction for that entry. This 3874 /// allows the user to store a correction table for image remapping or LUT driving. 3875 /// Since each color in the block is a SHORT, the maximum value for a color gun (i.e., 3876 /// A, R, G, B) is 65535, and the minimum value is zero.Therefore, BLACK maps to 3877 /// 0, 0, 0, 0 and WHITE maps to 65535, 65535, 65535, 65535. 3878 /// </summary> 3879 public ushort[] ColorCorrectionTable 3880 { 3881 get { return colorCorrectionTable; } 3882 set { colorCorrectionTable = value; } 3883 } 3884 3885 /// <summary> 3886 /// Other Data In Extension Area (if <see cref="ExtensionSize"/> > 495). 3887 /// </summary> 3888 public byte[] OtherDataInExtensionArea 3889 { 3890 get { return otherDataInExtensionArea; } 3891 set { otherDataInExtensionArea = value; } 3892 } 3893 #endregion 3894 3895 /// <summary> 3896 /// Make full copy of <see cref="TgaExtArea"/>. 3897 /// </summary> 3898 /// <returns>Full independent copy of <see cref="TgaExtArea"/>.</returns> 3899 public TgaExtArea Clone() 3900 { 3901 TgaExtArea NewExtArea = new TgaExtArea(); 3902 NewExtArea.extensionSize = extensionSize; 3903 NewExtArea.authorName = authorName.Clone(); 3904 NewExtArea.authorComments = authorComments.Clone(); 3905 NewExtArea.dateTimeStamp = dateTimeStamp.Clone(); 3906 NewExtArea.jobNameOrID = jobNameOrID.Clone(); 3907 NewExtArea.jobTime = jobTime.Clone(); 3908 NewExtArea.softwareID = softwareID.Clone(); 3909 NewExtArea.softVersion = softVersion.Clone(); 3910 NewExtArea.keyColor = keyColor.Clone(); 3911 NewExtArea.pixelAspectRatio = pixelAspectRatio.Clone(); 3912 NewExtArea.gammaValue = gammaValue.Clone(); 3913 NewExtArea.colorCorrectionOffset = colorCorrectionOffset; 3914 NewExtArea.postageStampOffset = postageStampOffset; 3915 NewExtArea.scanLineOffset = scanLineOffset; 3916 NewExtArea.attributesType = attributesType; 3917 3918 if (scanLineTable != null) 3919 NewExtArea.scanLineTable = (uint[])scanLineTable.Clone(); 3920 if (postageStampImage != null) 3921 NewExtArea.postageStampImage = new TgaPostageStampImage(postageStampImage.ToBytes()); 3922 if (colorCorrectionTable != null) 3923 NewExtArea.colorCorrectionTable = (ushort[])colorCorrectionTable.Clone(); 3924 3925 if (otherDataInExtensionArea != null) 3926 NewExtArea.otherDataInExtensionArea = (byte[])otherDataInExtensionArea.Clone(); 3927 3928 return NewExtArea; 3929 } 3930 3931 /// <summary> 3932 /// Make full copy of <see cref="TgaExtArea"/>. 3933 /// </summary> 3934 /// <returns>Full independent copy of <see cref="TgaExtArea"/>.</returns> 3935 object ICloneable.Clone() 3936 { 3937 return Clone(); 3938 } 3939 3940 public override bool Equals(object obj) 3941 { 3942 return ((obj is TgaExtArea) ? Equals((TgaExtArea)obj) : false); 3943 } 3944 3945 public bool Equals(TgaExtArea item) 3946 { 3947 return (extensionSize == item.extensionSize && 3948 authorName == item.authorName && 3949 authorComments == item.authorComments && 3950 dateTimeStamp == item.dateTimeStamp && 3951 jobNameOrID == item.jobNameOrID && 3952 jobTime == item.jobTime && 3953 softwareID == item.softwareID && 3954 softVersion == item.softVersion && 3955 keyColor == item.keyColor && 3956 pixelAspectRatio == item.pixelAspectRatio && 3957 gammaValue == item.gammaValue && 3958 colorCorrectionOffset == item.colorCorrectionOffset && 3959 postageStampOffset == item.postageStampOffset && 3960 scanLineOffset == item.scanLineOffset && 3961 attributesType == item.attributesType && 3962 3963 BitConverterExt.IsArraysEqual(scanLineTable, item.scanLineTable) && 3964 postageStampImage == item.postageStampImage && 3965 BitConverterExt.IsArraysEqual(colorCorrectionTable, item.colorCorrectionTable) && 3966 3967 BitConverterExt.IsArraysEqual(otherDataInExtensionArea, item.otherDataInExtensionArea)); 3968 } 3969 3970 public static bool operator ==(TgaExtArea item1, TgaExtArea item2) 3971 { 3972 if (ReferenceEquals(item1, null)) 3973 return ReferenceEquals(item2, null); 3974 3975 if (ReferenceEquals(item2, null)) 3976 return ReferenceEquals(item1, null); 3977 3978 return item1.Equals(item2); 3979 } 3980 3981 public static bool operator !=(TgaExtArea item1, TgaExtArea item2) 3982 { 3983 return !(item1 == item2); 3984 } 3985 3986 public override int GetHashCode() 3987 { 3988 unchecked 3989 { 3990 int hash = 27; 3991 hash = (13 * hash) + extensionSize.GetHashCode(); 3992 hash = (13 * hash) + authorName.GetHashCode(); 3993 hash = (13 * hash) + authorComments.GetHashCode(); 3994 hash = (13 * hash) + dateTimeStamp.GetHashCode(); 3995 hash = (13 * hash) + jobNameOrID.GetHashCode(); 3996 hash = (13 * hash) + jobTime.GetHashCode(); 3997 hash = (13 * hash) + softwareID.GetHashCode(); 3998 hash = (13 * hash) + softVersion.GetHashCode(); 3999 hash = (13 * hash) + keyColor.GetHashCode(); 4000 hash = (13 * hash) + pixelAspectRatio.GetHashCode(); 4001 hash = (13 * hash) + gammaValue.GetHashCode(); 4002 hash = (13 * hash) + colorCorrectionOffset.GetHashCode(); 4003 hash = (13 * hash) + postageStampOffset.GetHashCode(); 4004 hash = (13 * hash) + scanLineOffset.GetHashCode(); 4005 hash = (13 * hash) + attributesType.GetHashCode(); 4006 4007 if (scanLineTable != null) 4008 for (int i = 0; i < scanLineTable.Length; i++) 4009 hash = (13 * hash) + scanLineTable[i].GetHashCode(); 4010 4011 if (postageStampImage != null) 4012 hash = (13 * hash) + postageStampImage.GetHashCode(); 4013 4014 if (colorCorrectionTable != null) 4015 for (int i = 0; i < colorCorrectionTable.Length; i++) 4016 hash = (13 * hash) + colorCorrectionTable[i].GetHashCode(); 4017 4018 if (otherDataInExtensionArea != null) 4019 for (int i = 0; i < otherDataInExtensionArea.Length; i++) 4020 hash = (13 * hash) + otherDataInExtensionArea[i].GetHashCode(); 4021 4022 return hash; 4023 } 4024 } 4025 4026 /// <summary> 4027 /// Convert <see cref="TgaExtArea"/> to byte array. Warning: <see cref="ScanLineTable"/>, 4028 /// <see cref="PostageStampImage"/>, <see cref="ColorCorrectionTable"/> not included, 4029 /// because thea are can be not in the Extension Area of TGA file! 4030 /// </summary> 4031 /// <returns>Byte array.</returns> 4032 public byte[] ToBytes() 4033 { 4034 #region Exceptions check 4035 if (authorName == null) 4036 authorName = new TgaString(41, true); 4037 4038 if (authorComments == null) 4039 authorComments = new TgaComment(); 4040 4041 if (dateTimeStamp == null) 4042 dateTimeStamp = new TgaDateTime(DateTime.UtcNow); 4043 4044 if (jobNameOrID == null) 4045 jobNameOrID = new TgaString(41, true); 4046 4047 if (jobTime == null) 4048 jobTime = new TgaTime(); 4049 4050 if (softwareID == null) 4051 softwareID = new TgaString(41, true); 4052 4053 if (softVersion == null) 4054 softVersion = new TgaSoftVersion(); 4055 4056 if (keyColor == null) 4057 keyColor = new TgaColorKey(); 4058 4059 if (pixelAspectRatio == null) 4060 pixelAspectRatio = new TgaFraction(); 4061 4062 if (gammaValue == null) 4063 gammaValue = new TgaFraction(); 4064 #endregion 4065 4066 return BitConverterExt.ToBytes( 4067 extensionSize, 4068 authorName.ToBytes(), 4069 authorComments.ToBytes(), 4070 dateTimeStamp.ToBytes(), 4071 jobNameOrID.ToBytes(), 4072 jobTime.ToBytes(), 4073 softwareID.ToBytes(), 4074 softVersion.ToBytes(), 4075 keyColor.ToBytes(), 4076 pixelAspectRatio.ToBytes(), 4077 gammaValue.ToBytes(), 4078 colorCorrectionOffset, 4079 postageStampOffset, 4080 scanLineOffset, 4081 (byte)attributesType, 4082 otherDataInExtensionArea); 4083 } 4084 } //Not full ToBytes() 4085 4086 /// <summary> 4087 /// File Footer Area 4088 /// </summary> 4089 public class TgaFooter : ICloneable 4090 { 4091 uint extAreaOffset = 0; 4092 uint devDirOffset = 0; 4093 TgaString signature = TgaString.XFileSignatute; 4094 TgaString reservedChar = TgaString.DotSymbol; 4095 TgaString zeroStrTerminator = TgaString.ZeroTerminator; 4096 4097 /// <summary> 4098 /// Make NewXFile format TGA Footer with <see cref="ExtensionAreaOffset"/> = 0 and 4099 /// <see cref="DeveloperDirectoryOffset"/> = 0. 4100 /// </summary> 4101 public TgaFooter() 4102 { 4103 } 4104 4105 /// <summary> 4106 /// Make <see cref="TgaFooter"/> from values. 4107 /// </summary> 4108 /// <param name="ExtOff">Extension Area Offset, offset from the beginning of the file.</param> 4109 /// <param name="DevDirOff">Developer Directory Offset, offset from the beginning of the file.</param> 4110 /// <param name="Sign">New TGA format signature.</param> 4111 /// <param name="ReservChr">Reserved Character - ASCII character “.” (period).</param> 4112 /// <param name="Termin">Binary Zero Terminator, a binary zero which acts as a final terminator.</param> 4113 public TgaFooter(uint ExtOff, uint DevDirOff, TgaString Sign, TgaString ReservChr, TgaString Termin) 4114 { 4115 extAreaOffset = ExtOff; 4116 devDirOffset = DevDirOff; 4117 signature = Sign; 4118 reservedChar = ReservChr; 4119 zeroStrTerminator = Termin; 4120 } 4121 4122 /// <summary> 4123 /// Make <see cref="TgaFooter"/> from bytes (if signature is right). 4124 /// </summary> 4125 /// <param name="Bytes">Bytes array (byte[26]).</param> 4126 public TgaFooter(byte[] Bytes) 4127 { 4128 if (Bytes == null) 4129 throw new ArgumentNullException(nameof(Bytes) + " = null!"); 4130 if (Bytes.Length != Size) 4131 throw new ArgumentOutOfRangeException(nameof(Bytes.Length) + " must be equal " + Size + "!"); 4132 4133 extAreaOffset = BitConverter.ToUInt32(Bytes, 0); 4134 devDirOffset = BitConverter.ToUInt32(Bytes, 4); 4135 signature = new TgaString(BitConverterExt.GetElements(Bytes, 8, TgaString.XFileSignatuteConst.Length)); 4136 reservedChar = new TgaString(new byte[] { Bytes[24] }); 4137 zeroStrTerminator = new TgaString(new byte[] { Bytes[25] }); 4138 } 4139 4140 /// <summary> 4141 /// Byte 0-3 - Extension Area Offset - Field 28 4142 /// The first four bytes (bytes 0-3, the first LONG) of the TGA File Footer contain an 4143 /// offset from the beginning of the file to the start of the Extension Area. Simply 4144 /// SEEK to this location to position to the start of the Extension Area. If the 4145 /// Extension Area Offset is zero, no Extension Area exists in the file. 4146 /// </summary> 4147 public uint ExtensionAreaOffset 4148 { 4149 get { return extAreaOffset; } 4150 set { extAreaOffset = value; } 4151 } 4152 4153 /// <summary> 4154 /// Byte 4-7 - Developer Directory Offset - Field 29 4155 /// The next four bytes(bytes 4-7, the second LONG) contain an offset from the 4156 /// beginning of the file to the start of the Developer Directory. If the Developer 4157 /// Directory Offset is zero, then the Developer Area does not exist. 4158 /// </summary> 4159 public uint DeveloperDirectoryOffset 4160 { 4161 get { return devDirOffset; } 4162 set { devDirOffset = value; } 4163 } 4164 4165 /// <summary> 4166 /// Byte 8-23 - Signature - Field 30 4167 /// This string is exactly 16 bytes long and is formatted exactly as shown below 4168 /// capital letters), with a hyphen between “TRUEVISION” and “XFILE.” If the 4169 /// signature is detected, the file is assumed to be of the New TGA format and MAY, 4170 /// therefore, contain the Developer Area and/or the Extension Area fields.If the 4171 /// signature is not found, then the file is assumed to be in the Original TGA format. 4172 /// </summary> 4173 public TgaString Signature 4174 { 4175 get { return signature; } 4176 set { signature = value; } 4177 } 4178 4179 /// <summary> 4180 /// Byte 24 - Reserved Character - Field 31 4181 /// Byte 24 is an ASCII character “.” (period). This character MUST BE a period or 4182 /// the file is not considered a proper TGA file. 4183 /// </summary> 4184 public TgaString ReservedCharacter 4185 { 4186 get { return reservedChar; } 4187 set { reservedChar = value; } 4188 } 4189 4190 /// <summary> 4191 /// Byte 25 - Binary Zero String Terminator - Field 32 4192 /// Byte 25 is a binary zero which acts as a final terminator and allows the entire TGA 4193 /// File Footer to be read and utilized as a “C” string. 4194 /// </summary> 4195 public TgaString BinaryZeroStringTerminator 4196 { 4197 get { return zeroStrTerminator; } 4198 set { zeroStrTerminator = value; } 4199 } 4200 4201 /// <summary> 4202 /// Make full copy of <see cref="TgaFooter"/>. 4203 /// </summary> 4204 /// <returns></returns> 4205 public TgaFooter Clone() 4206 { 4207 return new TgaFooter(extAreaOffset, devDirOffset, signature.Clone(), 4208 reservedChar.Clone(), zeroStrTerminator.Clone()); 4209 } 4210 4211 /// <summary> 4212 /// Make full copy of <see cref="TgaFooter"/>. 4213 /// </summary> 4214 /// <returns></returns> 4215 object ICloneable.Clone() 4216 { 4217 return Clone(); 4218 } 4219 4220 /// <summary> 4221 /// Gets TGA Footer Section size in bytes. 4222 /// </summary> 4223 public const int Size = 26; 4224 4225 public override bool Equals(object obj) 4226 { 4227 return ((obj is TgaFooter) ? Equals((TgaFooter)obj) : false); 4228 } 4229 4230 public bool Equals(TgaFooter item) 4231 { 4232 return (extAreaOffset == item.extAreaOffset && 4233 devDirOffset == item.devDirOffset && 4234 signature == item.signature && 4235 reservedChar == item.reservedChar && 4236 zeroStrTerminator == item.zeroStrTerminator); 4237 } 4238 4239 public static bool operator ==(TgaFooter item1, TgaFooter item2) 4240 { 4241 if (ReferenceEquals(item1, null)) 4242 return ReferenceEquals(item2, null); 4243 4244 if (ReferenceEquals(item2, null)) 4245 return ReferenceEquals(item1, null); 4246 4247 return item1.Equals(item2); 4248 } 4249 4250 public static bool operator !=(TgaFooter item1, TgaFooter item2) 4251 { 4252 return !(item1 == item2); 4253 } 4254 4255 public override int GetHashCode() 4256 { 4257 unchecked 4258 { 4259 int hash = 17; 4260 hash = hash * 23 + extAreaOffset.GetHashCode(); 4261 hash = hash * 23 + devDirOffset.GetHashCode(); 4262 4263 if (signature != null) 4264 hash = hash * 23 + signature.GetHashCode(); 4265 4266 if (reservedChar != null) 4267 hash = hash * 23 + reservedChar.GetHashCode(); 4268 4269 if (zeroStrTerminator != null) 4270 hash = hash * 23 + zeroStrTerminator.GetHashCode(); 4271 4272 return hash; 4273 } 4274 } 4275 4276 public override string ToString() 4277 { 4278 return String.Format("{0}={1}, {2}={3}, FullSignature={4}", 4279 nameof(ExtensionAreaOffset), extAreaOffset, nameof(DeveloperDirectoryOffset), devDirOffset, 4280 (signature + reservedChar + zeroStrTerminator).ToString()); 4281 } 4282 4283 /// <summary> 4284 /// Convert <see cref="TgaFooter"/> to byte array. 4285 /// </summary> 4286 /// <returns>Byte array with size equal <see cref="Size"/>.</returns> 4287 public byte[] ToBytes() 4288 { 4289 return BitConverterExt.ToBytes(extAreaOffset, devDirOffset, 4290 signature.ToBytes(), reservedChar.ToBytes(), zeroStrTerminator.ToBytes()); 4291 } 4292 4293 /// <summary> 4294 /// Is footer is real footer of TGA File Format Version 2.0? 4295 /// Checking by <see cref="TgaString.XFileSignatute"/>. 4296 /// </summary> 4297 public bool IsFooterCorrect 4298 { 4299 get { return signature == TgaString.XFileSignatute; } 4300 } 4301 } 4302 4303 //////////////////////////////////////////////////////////////////////////////////////////////// 4304 4305 /// <summary> 4306 /// Simplify ByteConversion operations, like concatination of byte arrays, comparing and other. 4307 /// </summary> 4308 public static class BitConverterExt 4309 { 4310 /// <summary> 4311 /// Combine byte, byte[], (u)short, (u)int, (u)long values to byte[] array. 4312 /// </summary> 4313 /// <param name="obj">Array of byte, byte[], (u)short, (u)int, (u)long values.</param> 4314 /// <returns>Array of bytes, null when some object is null.</returns> 4315 public static byte[] ToBytes(params object[] obj) 4316 { 4317 if (obj == null) 4318 return null; 4319 4320 List<byte> BytesList = new List<byte>(); 4321 4322 for (int i = 0; i < obj.Length; i++) 4323 { 4324 if (obj[i] == null) 4325 continue; 4326 else if (obj[i] is byte) 4327 BytesList.Add((byte)obj[i]); 4328 else if (obj[i] is byte[]) 4329 BytesList.AddRange((byte[])obj[i]); 4330 else if (obj[i] is short) 4331 BytesList.AddRange(BitConverter.GetBytes((short)obj[i])); 4332 else if (obj[i] is ushort) 4333 BytesList.AddRange(BitConverter.GetBytes((ushort)obj[i])); 4334 else if (obj[i] is int) 4335 BytesList.AddRange(BitConverter.GetBytes((int)obj[i])); 4336 else if (obj[i] is uint) 4337 BytesList.AddRange(BitConverter.GetBytes((uint)obj[i])); 4338 else if (obj[i] is long) 4339 BytesList.AddRange(BitConverter.GetBytes((long)obj[i])); 4340 else if (obj[i] is ulong) 4341 BytesList.AddRange(BitConverter.GetBytes((ulong)obj[i])); 4342 } 4343 return BytesList.ToArray(); 4344 } 4345 4346 /// <summary> 4347 /// Copies a range of elements from an Array starting at the specified source index. 4348 /// The length and the index are specified as 32-bit integers. 4349 /// </summary> 4350 /// <param name="SrcArray">The <see cref="Array"/> that contains the data to copy.</param> 4351 /// <param name="Offset">A 32-bit integer that represents the index in the 4352 /// <see cref="SrcArray"/> at which copying begins.</param> 4353 /// <param name="Count">A 32-bit integer that represents the number of elements to copy.</param> 4354 /// <returns></returns> 4355 public static T[] GetElements<T>(T[] SrcArray, int Offset, int Count) 4356 { 4357 if (SrcArray == null) 4358 throw new ArgumentNullException(nameof(SrcArray) + " is null!"); 4359 4360 if (Offset >= SrcArray.Length || Offset < 0) 4361 throw new ArgumentOutOfRangeException(nameof(Offset) + " has wrong value!"); 4362 4363 if (Count <= 0 || Offset + Count > SrcArray.Length) 4364 throw new ArgumentOutOfRangeException(nameof(Count) + " has wrong value!"); 4365 4366 T[] Buff = new T[Count]; 4367 Array.Copy(SrcArray, Offset, Buff, 0, Buff.Length); 4368 return Buff; 4369 } 4370 4371 /// <summary> 4372 /// Compare N-dimensional Arrays. 4373 /// </summary> 4374 /// <typeparam name="T">Arrays Type.</typeparam> 4375 /// <param name="item1">First Array.</param> 4376 /// <param name="item2">Second Array.</param> 4377 /// <returns>True, if Arrays are equal.</returns> 4378 public static bool IsArraysEqual<T>(T[] item1, T[] item2) 4379 { 4380 if (ReferenceEquals(item1, item2)) 4381 return true; 4382 4383 if (item1 == null || item2 == null) 4384 return false; 4385 4386 if (item1.Length != item2.Length) 4387 return false; 4388 4389 EqualityComparer<T> comparer = EqualityComparer<T>.Default; 4390 for (int i = 0; i < item1.Length; i++) 4391 if (!comparer.Equals(item1[i], item2[i])) 4392 return false; 4393 return true; 4394 } 4395 4396 /// <summary> 4397 /// Compare Lists. 4398 /// </summary> 4399 /// <typeparam name="T">List Type.</typeparam> 4400 /// <param name="item1">First List.</param> 4401 /// <param name="item2">Second List.</param> 4402 /// <returns>True, if Lists are equal.</returns> 4403 public static bool IsListsEqual<T>(List<T> item1, List<T> item2) 4404 { 4405 if (ReferenceEquals(item1, item2)) 4406 return true; 4407 4408 if (item1 == null || item2 == null) 4409 return false; 4410 4411 if (item1.Count != item2.Count) 4412 return false; 4413 4414 for (int i = 0; i < item1.Count; i++) 4415 if (!item1[i].Equals(item2[i])) 4416 return false; 4417 return true; 4418 } 4419 4420 /// <summary> 4421 /// Compare elements in one Array with different offsets. 4422 /// </summary> 4423 /// <typeparam name="T">Array type.</typeparam> 4424 /// <param name="Arr">Some Array.</param> 4425 /// <param name="Offset1">First offset.</param> 4426 /// <param name="Offset2">Second offset.</param> 4427 /// <param name="Count">Elements count which must be compared.</param> 4428 /// <returns></returns> 4429 public static bool IsElementsEqual<T>(T[] Arr, int Offset1, int Offset2, int Count) 4430 { 4431 if (Arr == null) 4432 throw new ArgumentNullException(nameof(Arr) + " is null!"); 4433 4434 if (Offset1 >= Arr.Length || Offset1 < 0) 4435 throw new ArgumentOutOfRangeException(nameof(Offset1) + " has wrong value!"); 4436 4437 if (Offset2 >= Arr.Length || Offset2 < 0) 4438 throw new ArgumentOutOfRangeException(nameof(Offset2) + " has wrong value!"); 4439 4440 if (Count <= 0 || Offset1 + Count > Arr.Length || Offset2 + Count > Arr.Length) 4441 throw new ArgumentOutOfRangeException(nameof(Count) + " has wrong value!"); 4442 4443 if (Offset1 == Offset2) 4444 return true; 4445 4446 for (int i = 0; i < Count; i++) 4447 if (!Arr[Offset1 + i].Equals(Arr[Offset2 + i])) 4448 return false; 4449 4450 return true; 4451 } 4452 } 4453 #endregion 4454 4455 public class TGA : ICloneable 4456 { 4457 public TgaHeader Header = new TgaHeader(); 4458 public TgaImgOrColMap ImageOrColorMapArea = new TgaImgOrColMap(); 4459 public TgaDevArea DevArea = null; 4460 public TgaExtArea ExtArea = null; 4461 public TgaFooter Footer = null; 4462 4463 #region TGA Creation, Loading, Saving (all are public, have reference to private metods). 4464 /// <summary> 4465 /// Create new empty <see cref="TGA"/> istance. 4466 /// </summary> 4467 public TGA() 4468 { 4469 } 4470 4471 /// <summary> 4472 /// Create <see cref="TGA"/> instance with some params. If it must have ColorMap, 4473 /// check all ColorMap fields and settings after. 4474 /// </summary> 4475 /// <param name="Width">Image Width.</param> 4476 /// <param name="Height">Image Height.</param> 4477 /// <param name="PixDepth">Image Pixel Depth (bits / pixel), set ColorMap bpp after, if needed!</param> 4478 /// <param name="ImgType">Image Type (is RLE compressed, ColorMapped or GrayScaled).</param> 4479 /// <param name="AttrBits">Set numder of Attrbute bits (Alpha channel bits), default: 0, 1, 8.</param> 4480 /// <param name="NewFormat">Use new 2.0 TGA XFile format?</param> 4481 public TGA(ushort Width, ushort Height, TgaPixelDepth PixDepth = TgaPixelDepth.Bpp24, 4482 TgaImageType ImgType = TgaImageType.Uncompressed_TrueColor, byte AttrBits = 0, bool NewFormat = true) 4483 { 4484 if (Width <= 0 || Height <= 0 || PixDepth == TgaPixelDepth.Other) 4485 { 4486 Width = Height = 0; 4487 PixDepth = TgaPixelDepth.Other; 4488 ImgType = TgaImageType.NoImageData; 4489 AttrBits = 0; 4490 } 4491 else 4492 { 4493 int BytesPerPixel = (int)Math.Ceiling((double)PixDepth / 8.0); 4494 ImageOrColorMapArea.ImageData = new byte[Width * Height * BytesPerPixel]; 4495 4496 if (ImgType == TgaImageType.Uncompressed_ColorMapped || ImgType == TgaImageType.RLE_ColorMapped) 4497 { 4498 Header.ColorMapType = TgaColorMapType.ColorMap; 4499 Header.ColorMapSpec.FirstEntryIndex = 0; 4500 Header.ColorMapSpec.ColorMapEntrySize = (TgaColorMapEntrySize)Math.Ceiling((double)PixDepth / 8); 4501 } 4502 } 4503 4504 Header.ImageType = ImgType; 4505 Header.ImageSpec.ImageWidth = Width; 4506 Header.ImageSpec.ImageHeight = Height; 4507 Header.ImageSpec.PixelDepth = PixDepth; 4508 Header.ImageSpec.ImageDescriptor.AlphaChannelBits = AttrBits; 4509 4510 if (NewFormat) 4511 { 4512 Footer = new TgaFooter(); 4513 ExtArea = new TgaExtArea(); 4514 ExtArea.DateTimeStamp = new TgaDateTime(DateTime.UtcNow); 4515 ExtArea.AttributesType = (AttrBits > 0 ? TgaAttrType.UsefulAlpha : TgaAttrType.NoAlpha); 4516 } 4517 } 4518 4519 /// <summary> 4520 /// Make <see cref="TGA"/> from some <see cref="TGA"/> instance. 4521 /// Equal to <see cref="TGA.Clone()"/> function. 4522 /// </summary> 4523 /// <param name="tga">Original <see cref="TGA"/> instance.</param> 4524 public TGA(TGA tga) 4525 { 4526 Header = tga.Header.Clone(); 4527 ImageOrColorMapArea = tga.ImageOrColorMapArea.Clone(); 4528 DevArea = tga.DevArea.Clone(); 4529 ExtArea = tga.ExtArea.Clone(); 4530 Footer = tga.Footer.Clone(); 4531 } 4532 4533 /// <summary> 4534 /// Load <see cref="TGA"/> from file. 4535 /// </summary> 4536 /// <param name="filename">Full path to TGA file.</param> 4537 /// <returns>Loaded <see cref="TGA"/> file.</returns> 4538 public TGA(string filename) 4539 { 4540 LoadFunc(filename); 4541 } 4542 4543 /// <summary> 4544 /// Make <see cref="TGA"/> from bytes array. 4545 /// </summary> 4546 /// <param name="bytes">Bytes array (same like TGA File).</param> 4547 public TGA(byte[] bytes) 4548 { 4549 LoadFunc(bytes); 4550 } 4551 4552 /// <summary> 4553 /// Make <see cref="TGA"/> from <see cref="Stream"/>. 4554 /// For file opening better use <see cref="FromFile(string)"/>. 4555 /// </summary> 4556 /// <param name="stream">Some stream. You can use a lot of Stream types, but Stream must support: 4557 /// <see cref="Stream.CanSeek"/> and <see cref="Stream.CanRead"/>.</param> 4558 public TGA(Stream stream) 4559 { 4560 LoadFunc(stream); 4561 } 4562 4563 /// <summary> 4564 /// Make <see cref="TGA"/> from <see cref="Bitmap"/>. 4565 /// </summary> 4566 /// <param name="bmp">Input Bitmap, supported a lot of bitmaps types: 8/15/16/24/32 Bpp's.</param> 4567 /// <param name="UseRLE">Use RLE Compression?</param> 4568 /// <param name="NewFormat">Use new 2.0 TGA XFile format?</param> 4569 /// <param name="ColorMap2BytesEntry">Is Color Map Entry size equal 15 or 16 Bpp, else - 24 or 32.</param> 4570 public TGA(Bitmap bmp, bool UseRLE = false, bool NewFormat = true, bool ColorMap2BytesEntry = false) 4571 { 4572 LoadFunc(bmp, UseRLE, NewFormat, ColorMap2BytesEntry); 4573 } 4574 4575 /// <summary> 4576 /// Load <see cref="TGA"/> from file. 4577 /// </summary> 4578 /// <param name="filename">Full path to TGA file.</param> 4579 /// <returns>Loaded <see cref="TGA"/> file.</returns> 4580 public static TGA FromFile(string filename) 4581 { 4582 return new TGA(filename); 4583 } 4584 4585 /// <summary> 4586 /// Make <see cref="TGA"/> from bytes array. 4587 /// </summary> 4588 /// <param name="bytes">Bytes array (same like TGA File).</param> 4589 public static TGA FromBytes(byte[] bytes) 4590 { 4591 return new TGA(bytes); 4592 } 4593 4594 /// <summary> 4595 /// Make <see cref="TGA"/> from <see cref="Stream"/>. 4596 /// For file opening better use <see cref="FromFile(string)"/>. 4597 /// </summary> 4598 /// <param name="stream">Some stream. You can use a lot of Stream types, but Stream must support: 4599 /// <see cref="Stream.CanSeek"/> and <see cref="Stream.CanRead"/>.</param> 4600 public static TGA FromStream(Stream stream) 4601 { 4602 return new TGA(stream); 4603 } 4604 4605 /// <summary> 4606 /// Make <see cref="TGA"/> from <see cref="Bitmap"/>. 4607 /// </summary> 4608 /// <param name="bmp">Input Bitmap, supported a lot of bitmaps types: 8/15/16/24/32 Bpp's.</param> 4609 /// <param name="UseRLE">Use RLE Compression?</param> 4610 /// <param name="NewFormat">Use new 2.0 TGA XFile format?</param> 4611 /// <param name="ColorMap2BytesEntry">Is Color Map Entry size equal 15 or 16 Bpp, else - 24 or 32.</param> 4612 public static TGA FromBitmap(Bitmap bmp, bool UseRLE = false, 4613 bool NewFormat = true, bool ColorMap2BytesEntry = false) 4614 { 4615 return new TGA(bmp, UseRLE, NewFormat, ColorMap2BytesEntry); 4616 } 4617 4618 /// <summary> 4619 /// Save <see cref="TGA"/> to file. 4620 /// </summary> 4621 /// <param name="filename">Full path to file.</param> 4622 /// <returns>Return "true", if all done or "false", if failed.</returns> 4623 public bool Save(string filename) 4624 { 4625 try 4626 { 4627 bool Result = false; 4628 using (FileStream Fs = new FileStream(filename, FileMode.Create, FileAccess.Write, FileShare.None)) 4629 { 4630 using (MemoryStream Ms = new MemoryStream()) 4631 { 4632 Result = SaveFunc(Ms); 4633 Ms.WriteTo(Fs); 4634 Fs.Flush(); 4635 } 4636 } 4637 return Result; 4638 } 4639 catch 4640 { 4641 return false; 4642 } 4643 } 4644 4645 /// <summary> 4646 /// Save <see cref="TGA"/> to <see cref="Stream"/>. 4647 /// </summary> 4648 /// <param name="stream">Some stream, it must support: <see cref="Stream.CanWrite"/>.</param> 4649 /// <returns>Return "true", if all done or "false", if failed.</returns> 4650 public bool Save(Stream stream) 4651 { 4652 return SaveFunc(stream); 4653 } 4654 #endregion 4655 4656 /// <summary> 4657 /// Gets or Sets Image Width (see <see cref="Header.ImageSpec.ImageWidth"/>). 4658 /// </summary> 4659 public ushort Width 4660 { 4661 get { return Header.ImageSpec.ImageWidth; } 4662 set { Header.ImageSpec.ImageWidth = value; } 4663 } 4664 4665 /// <summary> 4666 /// Gets or Sets Image Height (see <see cref="Header.ImageSpec.ImageHeight"/>). 4667 /// </summary> 4668 public ushort Height 4669 { 4670 get { return Header.ImageSpec.ImageHeight; } 4671 set { Header.ImageSpec.ImageHeight = value; } 4672 } 4673 4674 /// <summary> 4675 /// Gets or Sets <see cref="TGA"/> image Size. 4676 /// </summary> 4677 public Size Size 4678 { 4679 get { return new Size(Header.ImageSpec.ImageWidth, Header.ImageSpec.ImageHeight); } 4680 set 4681 { 4682 Header.ImageSpec.ImageWidth = (ushort)value.Width; 4683 Header.ImageSpec.ImageHeight = (ushort)value.Height; 4684 } 4685 } 4686 4687 /// <summary> 4688 /// Make full independed copy of <see cref="TGA"/>. 4689 /// </summary> 4690 /// <returns>Full independed copy of <see cref="TGA"/>.</returns> 4691 public TGA Clone() 4692 { 4693 return new TGA(this); 4694 } 4695 4696 object ICloneable.Clone() 4697 { 4698 return Clone(); 4699 } 4700 4701 /// <summary> 4702 /// Flip <see cref="TGA"/> directions, for more info see <see cref="TgaImgOrigin"/>. 4703 /// </summary> 4704 /// <param name="Horizontal">Flip horizontal.</param> 4705 /// <param name="Vertical">Flip vertical.</param> 4706 public void Flip(bool Horizontal = false, bool Vertical = false) 4707 { 4708 int NewOrigin = (int)Header.ImageSpec.ImageDescriptor.ImageOrigin; 4709 NewOrigin = NewOrigin ^ ((Vertical ? 0x20 : 0) | (Horizontal ? 0x10 : 0)); 4710 Header.ImageSpec.ImageDescriptor.ImageOrigin = (TgaImgOrigin)NewOrigin; 4711 } 4712 4713 /// <summary> 4714 /// Get information from TGA image. 4715 /// </summary> 4716 /// <returns>MultiLine string with info fields (one per line).</returns> 4717 public string GetInfo() 4718 { 4719 StringBuilder SB = new StringBuilder(); 4720 4721 SB.AppendLine("Header:"); 4722 SB.AppendLine("\tID Length = " + Header.IDLength); 4723 SB.AppendLine("\tImage Type = " + Header.ImageType); 4724 SB.AppendLine("\tHeader -> ImageSpec:"); 4725 SB.AppendLine("\t\tImage Width = " + Header.ImageSpec.ImageWidth); 4726 SB.AppendLine("\t\tImage Height = " + Header.ImageSpec.ImageHeight); 4727 SB.AppendLine("\t\tPixel Depth = " + Header.ImageSpec.PixelDepth); 4728 SB.AppendLine("\t\tImage Descriptor (AsByte) = " + Header.ImageSpec.ImageDescriptor.ToByte()); 4729 SB.AppendLine("\t\tImage Descriptor -> AttributeBits = " + Header.ImageSpec.ImageDescriptor.AlphaChannelBits); 4730 SB.AppendLine("\t\tImage Descriptor -> ImageOrigin = " + Header.ImageSpec.ImageDescriptor.ImageOrigin); 4731 SB.AppendLine("\t\tX_Origin = " + Header.ImageSpec.X_Origin); 4732 SB.AppendLine("\t\tY_Origin = " + Header.ImageSpec.Y_Origin); 4733 SB.AppendLine("\tColorMap Type = " + Header.ColorMapType); 4734 SB.AppendLine("\tHeader -> ColorMapSpec:"); 4735 SB.AppendLine("\t\tColorMap Entry Size = " + Header.ColorMapSpec.ColorMapEntrySize); 4736 SB.AppendLine("\t\tColorMap Length = " + Header.ColorMapSpec.ColorMapLength); 4737 SB.AppendLine("\t\tFirstEntry Index = " + Header.ColorMapSpec.FirstEntryIndex); 4738 4739 SB.AppendLine("\nImage / Color Map Area:"); 4740 if (Header.IDLength > 0 && ImageOrColorMapArea.ImageID != null) 4741 SB.AppendLine("\tImage ID = \"" + ImageOrColorMapArea.ImageID.GetString() + "\""); 4742 else 4743 SB.AppendLine("\tImage ID = null"); 4744 4745 if (ImageOrColorMapArea.ImageData != null) 4746 SB.AppendLine("\tImage Data Length = " + ImageOrColorMapArea.ImageData.Length); 4747 else 4748 SB.AppendLine("\tImage Data = null"); 4749 4750 if (ImageOrColorMapArea.ColorMapData != null) 4751 SB.AppendLine("\tColorMap Data Length = " + ImageOrColorMapArea.ColorMapData.Length); 4752 else 4753 SB.AppendLine("\tColorMap Data = null"); 4754 4755 SB.AppendLine("\nDevelopers Area:"); 4756 if (DevArea != null) 4757 SB.AppendLine("\tCount = " + DevArea.Count); 4758 else 4759 SB.AppendLine("\tDevArea = null"); 4760 4761 SB.AppendLine("\nExtension Area:"); 4762 if (ExtArea != null) 4763 { 4764 SB.AppendLine("\tExtension Size = " + ExtArea.ExtensionSize); 4765 SB.AppendLine("\tAuthor Name = \"" + ExtArea.AuthorName.GetString() + "\""); 4766 SB.AppendLine("\tAuthor Comments = \"" + ExtArea.AuthorComments.GetString() + "\""); 4767 SB.AppendLine("\tDate / Time Stamp = " + ExtArea.DateTimeStamp); 4768 SB.AppendLine("\tJob Name / ID = \"" + ExtArea.JobNameOrID.GetString() + "\""); 4769 SB.AppendLine("\tJob Time = " + ExtArea.JobTime); 4770 SB.AppendLine("\tSoftware ID = \"" + ExtArea.SoftwareID.GetString() + "\""); 4771 SB.AppendLine("\tSoftware Version = \"" + ExtArea.SoftVersion + "\""); 4772 SB.AppendLine("\tKey Color = " + ExtArea.KeyColor); 4773 SB.AppendLine("\tPixel Aspect Ratio = " + ExtArea.PixelAspectRatio); 4774 SB.AppendLine("\tGamma Value = " + ExtArea.GammaValue); 4775 SB.AppendLine("\tColor Correction Table Offset = " + ExtArea.ColorCorrectionTableOffset); 4776 SB.AppendLine("\tPostage Stamp Offset = " + ExtArea.PostageStampOffset); 4777 SB.AppendLine("\tScan Line Offset = " + ExtArea.ScanLineOffset); 4778 SB.AppendLine("\tAttributes Type = " + ExtArea.AttributesType); 4779 4780 if (ExtArea.ScanLineTable != null) 4781 SB.AppendLine("\tScan Line Table = " + ExtArea.ScanLineTable.Length); 4782 else 4783 SB.AppendLine("\tScan Line Table = null"); 4784 4785 if (ExtArea.PostageStampImage != null) 4786 SB.AppendLine("\tPostage Stamp Image: " + ExtArea.PostageStampImage.ToString()); 4787 else 4788 SB.AppendLine("\tPostage Stamp Image = null"); 4789 4790 SB.AppendLine("\tColor Correction Table = " + (ExtArea.ColorCorrectionTable != null)); 4791 } 4792 else 4793 SB.AppendLine("\tExtArea = null"); 4794 4795 SB.AppendLine("\nFooter:"); 4796 if (Footer != null) 4797 { 4798 SB.AppendLine("\tExtension Area Offset = " + Footer.ExtensionAreaOffset); 4799 SB.AppendLine("\tDeveloper Directory Offset = " + Footer.DeveloperDirectoryOffset); 4800 SB.AppendLine("\tSignature (Full) = \"" + Footer.Signature.ToString() + 4801 Footer.ReservedCharacter.ToString() + Footer.BinaryZeroStringTerminator.ToString() + "\""); 4802 } 4803 else 4804 SB.AppendLine("\tFooter = null"); 4805 4806 return SB.ToString(); 4807 } 4808 4809 /// <summary> 4810 /// Check and update all fields with data length and offsets. 4811 /// </summary> 4812 /// <returns>Return "true", if all OK or "false", if checking failed.</returns> 4813 public bool CheckAndUpdateOffsets(out string ErrorStr) 4814 { 4815 ErrorStr = String.Empty; 4816 4817 if (Header == null) 4818 { 4819 ErrorStr = "Header = null"; 4820 return false; 4821 } 4822 4823 if (ImageOrColorMapArea == null) 4824 { 4825 ErrorStr = "ImageOrColorMapArea = null"; 4826 return false; 4827 } 4828 4829 uint Offset = TgaHeader.Size; // Virtual Offset 4830 4831 #region Header 4832 if (ImageOrColorMapArea.ImageID != null) 4833 { 4834 int StrMaxLen = 255; 4835 if (ImageOrColorMapArea.ImageID.UseEndingChar) 4836 StrMaxLen--; 4837 4838 Header.IDLength = (byte)Math.Min(ImageOrColorMapArea.ImageID.OriginalString.Length, StrMaxLen); 4839 ImageOrColorMapArea.ImageID.Length = Header.IDLength; 4840 Offset += Header.IDLength; 4841 } 4842 else 4843 Header.IDLength = 0; 4844 #endregion 4845 4846 #region ColorMap 4847 if (Header.ColorMapType != TgaColorMapType.NoColorMap) 4848 { 4849 if (Header.ColorMapSpec == null) 4850 { 4851 ErrorStr = "Header.ColorMapSpec = null"; 4852 return false; 4853 } 4854 4855 if (Header.ColorMapSpec.ColorMapLength == 0) 4856 { 4857 ErrorStr = "Header.ColorMapSpec.ColorMapLength = 0"; 4858 return false; 4859 } 4860 4861 if (ImageOrColorMapArea.ColorMapData == null) 4862 { 4863 ErrorStr = "ImageOrColorMapArea.ColorMapData = null"; 4864 return false; 4865 } 4866 4867 int CmBytesPerPixel = (int)Math.Ceiling((double)Header.ColorMapSpec.ColorMapEntrySize / 8.0); 4868 int LenBytes = Header.ColorMapSpec.ColorMapLength * CmBytesPerPixel; 4869 4870 if (LenBytes != ImageOrColorMapArea.ColorMapData.Length) 4871 { 4872 ErrorStr = "ImageOrColorMapArea.ColorMapData.Length has wrong size!"; 4873 return false; 4874 } 4875 4876 Offset += (uint)ImageOrColorMapArea.ColorMapData.Length; 4877 } 4878 #endregion 4879 4880 #region Image Data 4881 int BytesPerPixel = 0; 4882 if (Header.ImageType != TgaImageType.NoImageData) 4883 { 4884 if (Header.ImageSpec == null) 4885 { 4886 ErrorStr = "Header.ImageSpec = null"; 4887 return false; 4888 } 4889 4890 if (Header.ImageSpec.ImageWidth == 0 || Header.ImageSpec.ImageHeight == 0) 4891 { 4892 ErrorStr = "Header.ImageSpec.ImageWidth = 0 or Header.ImageSpec.ImageHeight = 0"; 4893 return false; 4894 } 4895 4896 if (ImageOrColorMapArea.ImageData == null) 4897 { 4898 ErrorStr = "ImageOrColorMapArea.ImageData = null"; 4899 return false; 4900 } 4901 4902 BytesPerPixel = (int)Math.Ceiling((double)Header.ImageSpec.PixelDepth / 8.0); 4903 if (Width * Height * BytesPerPixel != ImageOrColorMapArea.ImageData.Length) 4904 { 4905 ErrorStr = "ImageOrColorMapArea.ImageData.Length has wrong size!"; 4906 return false; 4907 } 4908 4909 if (Header.ImageType >= TgaImageType.RLE_ColorMapped && 4910 Header.ImageType <= TgaImageType.RLE_BlackWhite) 4911 { 4912 byte[] RLE = RLE_Encode(ImageOrColorMapArea.ImageData, Width, Height); 4913 if (RLE == null) 4914 { 4915 ErrorStr = "RLE Compressing error! Check Image Data size."; 4916 return false; 4917 } 4918 4919 Offset += (uint)RLE.Length; 4920 RLE = null; 4921 } 4922 else 4923 Offset += (uint)ImageOrColorMapArea.ImageData.Length; 4924 } 4925 #endregion 4926 4927 #region Footer, DevArea, ExtArea 4928 if (Footer != null) 4929 { 4930 #region DevArea 4931 if (DevArea != null) 4932 { 4933 int DevAreaCount = DevArea.Count; 4934 for (int i = 0; i < DevAreaCount; i++) 4935 if (DevArea[i] == null || DevArea[i].FieldSize <= 0) //Del Empty Entries 4936 { 4937 DevArea.Entries.RemoveAt(i); 4938 DevAreaCount--; 4939 i--; 4940 } 4941 4942 if (DevArea.Count <= 0) 4943 Footer.DeveloperDirectoryOffset = 0; 4944 4945 if (DevArea.Count > 2) 4946 { 4947 DevArea.Entries.Sort((a, b) => { return a.Tag.CompareTo(b.Tag); }); 4948 for (int i = 0; i < DevArea.Count - 1; i++) 4949 if (DevArea[i].Tag == DevArea[i + 1].Tag) 4950 { 4951 ErrorStr = "DevArea Enties has same Tags!"; 4952 return false; 4953 } 4954 } 4955 4956 for (int i = 0; i < DevArea.Count; i++) 4957 { 4958 DevArea[i].Offset = Offset; 4959 Offset += (uint)DevArea[i].FieldSize; 4960 } 4961 4962 Footer.DeveloperDirectoryOffset = Offset; 4963 Offset += (uint)(DevArea.Count * 10 + 2); 4964 } 4965 else 4966 Footer.DeveloperDirectoryOffset = 0; 4967 #endregion 4968 4969 #region ExtArea 4970 if (ExtArea != null) 4971 { 4972 ExtArea.ExtensionSize = TgaExtArea.MinSize; 4973 if (ExtArea.OtherDataInExtensionArea != null) 4974 ExtArea.ExtensionSize += (ushort)ExtArea.OtherDataInExtensionArea.Length; 4975 4976 ExtArea.DateTimeStamp = new TgaDateTime(DateTime.UtcNow); 4977 4978 Footer.ExtensionAreaOffset = Offset; 4979 Offset += ExtArea.ExtensionSize; 4980 4981 #region ScanLineTable 4982 if (ExtArea.ScanLineTable == null) 4983 ExtArea.ScanLineOffset = 0; 4984 else 4985 { 4986 if (ExtArea.ScanLineTable.Length != Height) 4987 { 4988 ErrorStr = "ExtArea.ScanLineTable.Length != Height"; 4989 return false; 4990 } 4991 4992 ExtArea.ScanLineOffset = Offset; 4993 Offset += (uint)(ExtArea.ScanLineTable.Length * 4); 4994 } 4995 #endregion 4996 4997 #region PostageStampImage 4998 if (ExtArea.PostageStampImage == null) 4999 ExtArea.PostageStampOffset = 0; 5000 else 5001 { 5002 if (ExtArea.PostageStampImage.Width == 0 || ExtArea.PostageStampImage.Height == 0) 5003 { 5004 ErrorStr = "ExtArea.PostageStampImage Width or Height is equal 0!"; 5005 return false; 5006 } 5007 5008 if (ExtArea.PostageStampImage.Data == null) 5009 { 5010 ErrorStr = "ExtArea.PostageStampImage.Data == null"; 5011 return false; 5012 } 5013 5014 int PImgSB = ExtArea.PostageStampImage.Width * ExtArea.PostageStampImage.Height * BytesPerPixel; 5015 if (Header.ImageType != TgaImageType.NoImageData && 5016 ExtArea.PostageStampImage.Data.Length != PImgSB) 5017 { 5018 ErrorStr = "ExtArea.PostageStampImage.Data.Length is wrong!"; 5019 return false; 5020 } 5021 5022 5023 ExtArea.PostageStampOffset = Offset; 5024 Offset += (uint)(ExtArea.PostageStampImage.Data.Length); 5025 } 5026 #endregion 5027 5028 #region ColorCorrectionTable 5029 if (ExtArea.ColorCorrectionTable == null) 5030 ExtArea.ColorCorrectionTableOffset = 0; 5031 else 5032 { 5033 if (ExtArea.ColorCorrectionTable.Length != 1024) 5034 { 5035 ErrorStr = "ExtArea.ColorCorrectionTable.Length != 256 * 4"; 5036 return false; 5037 } 5038 5039 ExtArea.ColorCorrectionTableOffset = Offset; 5040 Offset += (uint)(ExtArea.ColorCorrectionTable.Length * 2); 5041 } 5042 #endregion 5043 } 5044 else 5045 Footer.ExtensionAreaOffset = 0; 5046 #endregion 5047 5048 #region Footer 5049 if (Footer.ToBytes().Length != TgaFooter.Size) 5050 { 5051 ErrorStr = "Footer.Length is wrong!"; 5052 return false; 5053 } 5054 5055 Offset += TgaFooter.Size; 5056 #endregion 5057 } 5058 #endregion 5059 5060 return true; 5061 } 5062 5063 #region Convert 5064 /// <summary> 5065 /// Convert <see cref="TGA"/> to <see cref="Bitmap"/>. 5066 /// </summary> 5067 /// <param name="ForceUseAlpha">Force use alpha channel.</param> 5068 /// <returns>Bitmap or null, on error.</returns> 5069 public Bitmap ToBitmap(bool ForceUseAlpha = false) 5070 { 5071 return ToBitmapFunc(ForceUseAlpha, false); 5072 } 5073 5074 /// <summary> 5075 /// Convert <see cref="TGA"/> to bytes array. 5076 /// </summary> 5077 /// <returns>Bytes array, (equal to saved file, but in memory) or null (on error).</returns> 5078 public byte[] ToBytes() 5079 { 5080 try 5081 { 5082 byte[] Bytes; 5083 using (MemoryStream ms = new MemoryStream()) 5084 { 5085 Save(ms); 5086 Bytes = ms.ToArray(); 5087 ms.Flush(); 5088 } 5089 return Bytes; 5090 } 5091 catch 5092 { 5093 return null; 5094 } 5095 } 5096 5097 /// <summary> 5098 /// Convert TGA Image to new XFile format (v2.0). 5099 /// </summary> 5100 public void ToNewFormat() 5101 { 5102 if (Footer == null) 5103 Footer = new TgaFooter(); 5104 5105 if (ExtArea == null) 5106 { 5107 ExtArea = new TgaExtArea(); 5108 5109 ExtArea.DateTimeStamp = new TgaDateTime(DateTime.UtcNow); 5110 5111 if (Header.ImageSpec.ImageDescriptor.AlphaChannelBits > 0) 5112 ExtArea.AttributesType = TgaAttrType.UsefulAlpha; 5113 else 5114 ExtArea.AttributesType = TgaAttrType.NoAlpha; 5115 } 5116 } 5117 #endregion 5118 5119 #region Private functions 5120 bool LoadFunc(string filename) 5121 { 5122 if (!File.Exists(filename)) 5123 throw new FileNotFoundException("File: \"" + filename + "\" not found!"); 5124 5125 try 5126 { 5127 using (FileStream FS = new FileStream(filename, FileMode.Open, FileAccess.Read)) 5128 return LoadFunc(FS); 5129 } 5130 catch 5131 { 5132 return false; 5133 } 5134 } 5135 5136 bool LoadFunc(byte[] bytes) 5137 { 5138 if (bytes == null) 5139 throw new ArgumentNullException(); 5140 5141 try 5142 { 5143 using (MemoryStream FS = new MemoryStream(bytes, false)) 5144 return LoadFunc(FS); 5145 } 5146 catch 5147 { 5148 return false; 5149 } 5150 } 5151 5152 bool LoadFunc(Stream stream) 5153 { 5154 if (stream == null) 5155 throw new ArgumentNullException(); 5156 if (!(stream.CanRead && stream.CanSeek)) 5157 throw new FileLoadException("Stream reading or seeking is not avaiable!"); 5158 5159 try 5160 { 5161 stream.Seek(0, SeekOrigin.Begin); 5162 BinaryReader Br = new BinaryReader(stream); 5163 5164 Header = new TgaHeader(Br.ReadBytes(TgaHeader.Size)); 5165 5166 if (Header.IDLength > 0) 5167 ImageOrColorMapArea.ImageID = new TgaString(Br.ReadBytes(Header.IDLength)); 5168 5169 if (Header.ColorMapSpec.ColorMapLength > 0) 5170 { 5171 int CmBytesPerPixel = (int)Math.Ceiling((double)Header.ColorMapSpec.ColorMapEntrySize / 8.0); 5172 int LenBytes = Header.ColorMapSpec.ColorMapLength * CmBytesPerPixel; 5173 ImageOrColorMapArea.ColorMapData = Br.ReadBytes(LenBytes); 5174 } 5175 5176 #region Read Image Data 5177 int BytesPerPixel = (int)Math.Ceiling((double)Header.ImageSpec.PixelDepth / 8.0); 5178 if (Header.ImageType != TgaImageType.NoImageData) 5179 { 5180 int ImageDataSize = Width * Height * BytesPerPixel; 5181 switch (Header.ImageType) 5182 { 5183 case TgaImageType.RLE_ColorMapped: 5184 case TgaImageType.RLE_TrueColor: 5185 case TgaImageType.RLE_BlackWhite: 5186 5187 int DataOffset = 0; 5188 byte PacketInfo; 5189 int PacketCount; 5190 byte[] RLE_Bytes, RLE_Part; 5191 ImageOrColorMapArea.ImageData = new byte[ImageDataSize]; 5192 5193 do 5194 { 5195 PacketInfo = Br.ReadByte(); //1 type bit and 7 count bits. Len = Count + 1. 5196 PacketCount = (PacketInfo & 127) + 1; 5197 5198 if (PacketInfo >= 128) // bit7 = 1, RLE 5199 { 5200 RLE_Bytes = new byte[PacketCount * BytesPerPixel]; 5201 RLE_Part = Br.ReadBytes(BytesPerPixel); 5202 for (int i = 0; i < RLE_Bytes.Length; i++) 5203 RLE_Bytes[i] = RLE_Part[i % BytesPerPixel]; 5204 } 5205 else // RAW format 5206 RLE_Bytes = Br.ReadBytes(PacketCount * BytesPerPixel); 5207 5208 Buffer.BlockCopy(RLE_Bytes, 0, ImageOrColorMapArea.ImageData, DataOffset, RLE_Bytes.Length); 5209 DataOffset += RLE_Bytes.Length; 5210 } 5211 while (DataOffset < ImageDataSize); 5212 RLE_Bytes = null; 5213 break; 5214 5215 case TgaImageType.Uncompressed_ColorMapped: 5216 case TgaImageType.Uncompressed_TrueColor: 5217 case TgaImageType.Uncompressed_BlackWhite: 5218 ImageOrColorMapArea.ImageData = Br.ReadBytes(ImageDataSize); 5219 break; 5220 } 5221 } 5222 #endregion 5223 5224 #region Try parse Footer 5225 stream.Seek(-TgaFooter.Size, SeekOrigin.End); 5226 uint FooterOffset = (uint)stream.Position; 5227 TgaFooter MbFooter = new TgaFooter(Br.ReadBytes(TgaFooter.Size)); 5228 if (MbFooter.IsFooterCorrect) 5229 { 5230 Footer = MbFooter; 5231 uint DevDirOffset = Footer.DeveloperDirectoryOffset; 5232 uint ExtAreaOffset = Footer.ExtensionAreaOffset; 5233 5234 #region If Dev Area exist, read it. 5235 if (DevDirOffset != 0) 5236 { 5237 stream.Seek(DevDirOffset, SeekOrigin.Begin); 5238 DevArea = new TgaDevArea(); 5239 uint NumberOfTags = Br.ReadUInt16(); 5240 5241 ushort[] Tags = new ushort[NumberOfTags]; 5242 uint[] TagOffsets = new uint[NumberOfTags]; 5243 uint[] TagSizes = new uint[NumberOfTags]; 5244 5245 for (int i = 0; i < NumberOfTags; i++) 5246 { 5247 Tags[i] = Br.ReadUInt16(); 5248 TagOffsets[i] = Br.ReadUInt32(); 5249 TagSizes[i] = Br.ReadUInt32(); 5250 } 5251 5252 for (int i = 0; i < NumberOfTags; i++) 5253 { 5254 stream.Seek(TagOffsets[i], SeekOrigin.Begin); 5255 var Ent = new TgaDevEntry(Tags[i], TagOffsets[i], Br.ReadBytes((int)TagSizes[i])); 5256 DevArea.Entries.Add(Ent); 5257 } 5258 5259 Tags = null; 5260 TagOffsets = null; 5261 TagSizes = null; 5262 } 5263 #endregion 5264 5265 #region If Ext Area exist, read it. 5266 if (ExtAreaOffset != 0) 5267 { 5268 stream.Seek(ExtAreaOffset, SeekOrigin.Begin); 5269 ushort ExtAreaSize = Math.Max((ushort)TgaExtArea.MinSize, Br.ReadUInt16()); 5270 stream.Seek(ExtAreaOffset, SeekOrigin.Begin); 5271 ExtArea = new TgaExtArea(Br.ReadBytes(ExtAreaSize)); 5272 5273 if (ExtArea.ScanLineOffset > 0) 5274 { 5275 stream.Seek(ExtArea.ScanLineOffset, SeekOrigin.Begin); 5276 ExtArea.ScanLineTable = new uint[Height]; 5277 for (int i = 0; i < ExtArea.ScanLineTable.Length; i++) 5278 ExtArea.ScanLineTable[i] = Br.ReadUInt32(); 5279 } 5280 5281 if (ExtArea.PostageStampOffset > 0) 5282 { 5283 stream.Seek(ExtArea.PostageStampOffset, SeekOrigin.Begin); 5284 byte W = Br.ReadByte(); 5285 byte H = Br.ReadByte(); 5286 int ImgDataSize = W * H * BytesPerPixel; 5287 if (ImgDataSize > 0) 5288 ExtArea.PostageStampImage = new TgaPostageStampImage(W, H, Br.ReadBytes(ImgDataSize)); 5289 } 5290 5291 if (ExtArea.ColorCorrectionTableOffset > 0) 5292 { 5293 stream.Seek(ExtArea.ColorCorrectionTableOffset, SeekOrigin.Begin); 5294 ExtArea.ColorCorrectionTable = new ushort[256 * 4]; 5295 for (int i = 0; i < ExtArea.ColorCorrectionTable.Length; i++) 5296 ExtArea.ColorCorrectionTable[i] = Br.ReadUInt16(); 5297 } 5298 } 5299 #endregion 5300 } 5301 #endregion 5302 5303 Br.Close(); 5304 return true; 5305 } 5306 catch 5307 { 5308 return false; 5309 } 5310 } 5311 5312 bool LoadFunc(Bitmap bmp, bool UseRLE = false, bool NewFormat = true, bool ColorMap2BytesEntry = false) 5313 { 5314 if (bmp == null) 5315 throw new ArgumentNullException(); 5316 5317 try 5318 { 5319 Header.ImageSpec.ImageWidth = (ushort)bmp.Width; 5320 Header.ImageSpec.ImageHeight = (ushort)bmp.Height; 5321 Header.ImageSpec.ImageDescriptor.ImageOrigin = TgaImgOrigin.TopLeft; 5322 5323 switch (bmp.PixelFormat) 5324 { 5325 case PixelFormat.Indexed: 5326 case PixelFormat.Gdi: 5327 case PixelFormat.Alpha: 5328 case PixelFormat.Undefined: 5329 case PixelFormat.PAlpha: 5330 case PixelFormat.Extended: 5331 case PixelFormat.Max: 5332 case PixelFormat.Canonical: 5333 case PixelFormat.Format16bppRgb565: 5334 default: 5335 throw new FormatException(nameof(PixelFormat) + " is not supported!"); 5336 5337 case PixelFormat.Format1bppIndexed: 5338 case PixelFormat.Format4bppIndexed: 5339 case PixelFormat.Format8bppIndexed: 5340 case PixelFormat.Format16bppGrayScale: 5341 case PixelFormat.Format16bppRgb555: 5342 case PixelFormat.Format16bppArgb1555: 5343 case PixelFormat.Format24bppRgb: 5344 case PixelFormat.Format32bppRgb: 5345 case PixelFormat.Format32bppArgb: 5346 case PixelFormat.Format32bppPArgb: 5347 case PixelFormat.Format48bppRgb: 5348 case PixelFormat.Format64bppArgb: 5349 case PixelFormat.Format64bppPArgb: 5350 5351 int bpp = Math.Max(8, Image.GetPixelFormatSize(bmp.PixelFormat)); 5352 int BytesPP = bpp / 8; 5353 5354 if (bmp.PixelFormat == PixelFormat.Format16bppRgb555) 5355 bpp = 15; 5356 5357 bool IsAlpha = Image.IsAlphaPixelFormat(bmp.PixelFormat); 5358 bool IsPreAlpha = IsAlpha && bmp.PixelFormat.ToString().EndsWith("PArgb"); 5359 bool IsColorMapped = bmp.PixelFormat.ToString().EndsWith("Indexed"); 5360 5361 Header.ImageSpec.PixelDepth = (TgaPixelDepth)(BytesPP * 8); 5362 5363 if (IsAlpha) 5364 { 5365 Header.ImageSpec.ImageDescriptor.AlphaChannelBits = (byte)(BytesPP * 2); 5366 5367 if (bmp.PixelFormat == PixelFormat.Format16bppArgb1555) 5368 Header.ImageSpec.ImageDescriptor.AlphaChannelBits = 1; 5369 } 5370 5371 #region ColorMap 5372 bool IsGrayImage = (bmp.PixelFormat == PixelFormat.Format16bppGrayScale | IsColorMapped); 5373 5374 if (IsColorMapped && bmp.Palette != null) 5375 { 5376 Color[] Colors = bmp.Palette.Entries; 5377 5378 #region Analyze ColorMapType 5379 int AlphaSum = 0; 5380 bool ColorMapUseAlpha = false; 5381 5382 for (int i = 0; i < Colors.Length; i++) 5383 { 5384 IsGrayImage &= (Colors[i].R == Colors[i].G && Colors[i].G == Colors[i].B); 5385 ColorMapUseAlpha |= (Colors[i].A < 248); 5386 AlphaSum |= Colors[i].A; 5387 } 5388 ColorMapUseAlpha &= (AlphaSum > 0); 5389 5390 int CMapBpp = (ColorMap2BytesEntry ? 15 : 24) + (ColorMapUseAlpha ? (ColorMap2BytesEntry ? 1 : 8) : 0); 5391 int CMBytesPP = (int)Math.Ceiling(CMapBpp / 8.0); 5392 #endregion 5393 5394 Header.ColorMapSpec.ColorMapLength = Math.Min((ushort)Colors.Length, ushort.MaxValue); 5395 Header.ColorMapSpec.ColorMapEntrySize = (TgaColorMapEntrySize)CMapBpp; 5396 ImageOrColorMapArea.ColorMapData = new byte[Header.ColorMapSpec.ColorMapLength * CMBytesPP]; 5397 5398 byte[] CMapEntry = new byte[CMBytesPP]; 5399 5400 const float To5Bit = 32f / 256f; // Scale value from 8 to 5 bits. 5401 for (int i = 0; i < Colors.Length; i++) 5402 { 5403 switch (Header.ColorMapSpec.ColorMapEntrySize) 5404 { 5405 case TgaColorMapEntrySize.A1R5G5B5: 5406 case TgaColorMapEntrySize.X1R5G5B5: 5407 int R = (int)(Colors[i].R * To5Bit); 5408 int G = (int)(Colors[i].G * To5Bit) << 5; 5409 int B = (int)(Colors[i].B * To5Bit) << 10; 5410 int A = 0; 5411 5412 if (Header.ColorMapSpec.ColorMapEntrySize == TgaColorMapEntrySize.A1R5G5B5) 5413 A = ((Colors[i].A & 0x80) << 15); 5414 5415 CMapEntry = BitConverter.GetBytes(A | R | G | B); 5416 break; 5417 5418 case TgaColorMapEntrySize.R8G8B8: 5419 CMapEntry[0] = Colors[i].B; 5420 CMapEntry[1] = Colors[i].G; 5421 CMapEntry[2] = Colors[i].R; 5422 break; 5423 5424 case TgaColorMapEntrySize.A8R8G8B8: 5425 CMapEntry[0] = Colors[i].B; 5426 CMapEntry[1] = Colors[i].G; 5427 CMapEntry[2] = Colors[i].R; 5428 CMapEntry[3] = Colors[i].A; 5429 break; 5430 5431 case TgaColorMapEntrySize.Other: 5432 default: 5433 break; 5434 } 5435 5436 Buffer.BlockCopy(CMapEntry, 0, ImageOrColorMapArea.ColorMapData, i * CMBytesPP, CMBytesPP); 5437 } 5438 } 5439 #endregion 5440 5441 #region ImageType 5442 if (UseRLE) 5443 { 5444 if (IsGrayImage) 5445 Header.ImageType = TgaImageType.RLE_BlackWhite; 5446 else if (IsColorMapped) 5447 Header.ImageType = TgaImageType.RLE_ColorMapped; 5448 else 5449 Header.ImageType = TgaImageType.RLE_TrueColor; 5450 } 5451 else 5452 { 5453 if (IsGrayImage) 5454 Header.ImageType = TgaImageType.Uncompressed_BlackWhite; 5455 else if (IsColorMapped) 5456 Header.ImageType = TgaImageType.Uncompressed_ColorMapped; 5457 else 5458 Header.ImageType = TgaImageType.Uncompressed_TrueColor; 5459 } 5460 5461 Header.ColorMapType = (IsColorMapped ? TgaColorMapType.ColorMap : TgaColorMapType.NoColorMap); 5462 #endregion 5463 5464 #region NewFormat 5465 if (NewFormat) 5466 { 5467 Footer = new TgaFooter(); 5468 ExtArea = new TgaExtArea(); 5469 ExtArea.DateTimeStamp = new TgaDateTime(DateTime.UtcNow); 5470 5471 if (IsAlpha) 5472 { 5473 ExtArea.AttributesType = TgaAttrType.UsefulAlpha; 5474 5475 if (IsPreAlpha) 5476 ExtArea.AttributesType = TgaAttrType.PreMultipliedAlpha; 5477 } 5478 else 5479 { 5480 ExtArea.AttributesType = TgaAttrType.NoAlpha; 5481 5482 if (Header.ImageSpec.ImageDescriptor.AlphaChannelBits > 0) 5483 ExtArea.AttributesType = TgaAttrType.UndefinedAlphaButShouldBeRetained; 5484 } 5485 } 5486 #endregion 5487 5488 #region Bitmap width is aligned by 32 bits = 4 bytes! Delete it. 5489 int StrideBytes = bmp.Width * BytesPP; 5490 int PaddingBytes = (int)Math.Ceiling(StrideBytes / 4.0) * 4 - StrideBytes; 5491 5492 byte[] ImageData = new byte[(StrideBytes + PaddingBytes) * bmp.Height]; 5493 5494 Rectangle Re = new Rectangle(0, 0, bmp.Width, bmp.Height); 5495 BitmapData BmpData = bmp.LockBits(Re, ImageLockMode.ReadOnly, bmp.PixelFormat); 5496 Marshal.Copy(BmpData.Scan0, ImageData, 0, ImageData.Length); 5497 bmp.UnlockBits(BmpData); 5498 BmpData = null; 5499 5500 if (PaddingBytes > 0) //Need delete bytes align 5501 { 5502 ImageOrColorMapArea.ImageData = new byte[StrideBytes * bmp.Height]; 5503 for (int i = 0; i < bmp.Height; i++) 5504 Buffer.BlockCopy(ImageData, i * (StrideBytes + PaddingBytes), 5505 ImageOrColorMapArea.ImageData, i * StrideBytes, StrideBytes); 5506 } 5507 else 5508 ImageOrColorMapArea.ImageData = ImageData; 5509 5510 ImageData = null; 5511 5512 // Not official supported, but works (tested on 16bpp GrayScale test images)! 5513 if (bmp.PixelFormat == PixelFormat.Format16bppGrayScale) 5514 { 5515 for (long i = 0; i < ImageOrColorMapArea.ImageData.Length; i++) 5516 ImageOrColorMapArea.ImageData[i] ^= byte.MaxValue; 5517 } 5518 #endregion 5519 5520 break; 5521 } 5522 5523 return true; 5524 } 5525 catch 5526 { 5527 return false; 5528 } 5529 } 5530 5531 bool SaveFunc(Stream stream) 5532 { 5533 try 5534 { 5535 if (stream == null) 5536 throw new ArgumentNullException(); 5537 if (!(stream.CanWrite && stream.CanSeek)) 5538 throw new FileLoadException("Stream writing or seeking is not avaiable!"); 5539 5540 string CheckResult; 5541 if (!CheckAndUpdateOffsets(out CheckResult)) 5542 return false; 5543 5544 BinaryWriter Bw = new BinaryWriter(stream); 5545 Bw.Write(Header.ToBytes()); 5546 5547 if (ImageOrColorMapArea.ImageID != null) 5548 Bw.Write(ImageOrColorMapArea.ImageID.ToBytes()); 5549 5550 if (Header.ColorMapType != TgaColorMapType.NoColorMap) 5551 Bw.Write(ImageOrColorMapArea.ColorMapData); 5552 5553 #region ImageData 5554 if (Header.ImageType != TgaImageType.NoImageData) 5555 { 5556 if (Header.ImageType >= TgaImageType.RLE_ColorMapped && 5557 Header.ImageType <= TgaImageType.RLE_BlackWhite) 5558 Bw.Write(RLE_Encode(ImageOrColorMapArea.ImageData, Width, Height)); 5559 else 5560 Bw.Write(ImageOrColorMapArea.ImageData); 5561 } 5562 #endregion 5563 5564 #region Footer 5565 if (Footer != null) 5566 { 5567 #region DevArea 5568 if (DevArea != null) 5569 { 5570 for (int i = 0; i < DevArea.Count; i++) 5571 Bw.Write(DevArea[i].Data); 5572 5573 Bw.Write((ushort)DevArea.Count); 5574 5575 for (int i = 0; i < DevArea.Count; i++) 5576 { 5577 Bw.Write(DevArea[i].Tag); 5578 Bw.Write(DevArea[i].Offset); 5579 Bw.Write(DevArea[i].FieldSize); 5580 } 5581 } 5582 #endregion 5583 5584 #region ExtArea 5585 if (ExtArea != null) 5586 { 5587 Bw.Write(ExtArea.ToBytes()); 5588 5589 if (ExtArea.ScanLineTable != null) 5590 for (int i = 0; i < ExtArea.ScanLineTable.Length; i++) 5591 Bw.Write(ExtArea.ScanLineTable[i]); 5592 5593 if (ExtArea.PostageStampImage != null) 5594 Bw.Write(ExtArea.PostageStampImage.ToBytes()); 5595 5596 if (ExtArea.ColorCorrectionTable != null) 5597 for (int i = 0; i < ExtArea.ColorCorrectionTable.Length; i++) 5598 Bw.Write(ExtArea.ColorCorrectionTable[i]); 5599 } 5600 #endregion 5601 5602 Bw.Write(Footer.ToBytes()); 5603 } 5604 #endregion 5605 5606 Bw.Flush(); 5607 stream.Flush(); 5608 return true; 5609 } 5610 catch 5611 { 5612 return false; 5613 } 5614 } 5615 5616 /// <summary> 5617 /// Encode image with RLE compression (used RLE per line)! 5618 /// </summary> 5619 /// <param name="ImageData">Image data, bytes array with size = Width * Height * BytesPerPixel.</param> 5620 /// <param name="Width">Image Width, must be > 0.</param> 5621 /// <param name="Height">Image Height, must be > 0.</param> 5622 /// <returns>Bytes array with RLE compressed image data.</returns> 5623 byte[] RLE_Encode(byte[] ImageData, int Width, int Height) 5624 { 5625 if (ImageData == null) 5626 throw new ArgumentNullException(nameof(ImageData) + "in null!"); 5627 5628 if (Width <= 0 || Height <= 0) 5629 throw new ArgumentOutOfRangeException(nameof(Width) + " and " + nameof(Height) + " must be > 0!"); 5630 5631 int Bpp = ImageData.Length / Width / Height; // Bytes per pixel 5632 int ScanLineSize = Width * Bpp; 5633 5634 if (ScanLineSize * Height != ImageData.Length) 5635 throw new ArgumentOutOfRangeException("ImageData has wrong Length!"); 5636 5637 try 5638 { 5639 int Count = 0; 5640 int Pos = 0; 5641 bool IsRLE = false; 5642 List<byte> Encoded = new List<byte>(); 5643 byte[] RowData = new byte[ScanLineSize]; 5644 5645 for (int y = 0; y < Height; y++) 5646 { 5647 Pos = 0; 5648 Buffer.BlockCopy(ImageData, y * ScanLineSize, RowData, 0, ScanLineSize); 5649 5650 while (Pos < ScanLineSize) 5651 { 5652 if (Pos >= ScanLineSize - Bpp) 5653 { 5654 Encoded.Add(0); 5655 Encoded.AddRange(BitConverterExt.GetElements(RowData, Pos, Bpp)); 5656 Pos += Bpp; 5657 break; 5658 } 5659 5660 Count = 0; //1 5661 IsRLE = BitConverterExt.IsElementsEqual(RowData, Pos, Pos + Bpp, Bpp); 5662 5663 for (int i = Pos + Bpp; i < Math.Min(Pos + 128 * Bpp, ScanLineSize) - Bpp; i += Bpp) 5664 { 5665 if (IsRLE ^ BitConverterExt.IsElementsEqual(RowData, (IsRLE ? Pos : i), i + Bpp, Bpp)) 5666 { 5667 //Count--; 5668 break; 5669 } 5670 else 5671 Count++; 5672 } 5673 5674 int CountBpp = (Count + 1) * Bpp; 5675 Encoded.Add((byte)(IsRLE ? Count | 128 : Count)); 5676 Encoded.AddRange(BitConverterExt.GetElements(RowData, Pos, (IsRLE ? Bpp : CountBpp))); 5677 Pos += CountBpp; 5678 } 5679 } 5680 5681 return Encoded.ToArray(); 5682 } 5683 catch 5684 { 5685 return null; 5686 } 5687 } 5688 5689 /// <summary> 5690 /// Convert <see cref="TGA"/> to <see cref="Bitmap"/>. 5691 /// </summary> 5692 /// <param name="ForceUseAlpha">Force use alpha channel.</param> 5693 /// <param name="PostageStampImage">Get Postage Stamp Image (Thumb) or get main image?</param> 5694 /// <returns>Bitmap or null, on error.</returns> 5695 Bitmap ToBitmapFunc(bool ForceUseAlpha = false, bool PostageStampImage = false) 5696 { 5697 try 5698 { 5699 #region UseAlpha? 5700 bool UseAlpha = true; 5701 if (ExtArea != null) 5702 { 5703 switch (ExtArea.AttributesType) 5704 { 5705 case TgaAttrType.NoAlpha: 5706 case TgaAttrType.UndefinedAlphaCanBeIgnored: 5707 case TgaAttrType.UndefinedAlphaButShouldBeRetained: 5708 UseAlpha = false; 5709 break; 5710 case TgaAttrType.UsefulAlpha: 5711 case TgaAttrType.PreMultipliedAlpha: 5712 default: 5713 break; 5714 } 5715 } 5716 UseAlpha = (Header.ImageSpec.ImageDescriptor.AlphaChannelBits > 0 && UseAlpha) | ForceUseAlpha; 5717 #endregion 5718 5719 #region IsGrayImage 5720 bool IsGrayImage = Header.ImageType == TgaImageType.RLE_BlackWhite || 5721 Header.ImageType == TgaImageType.Uncompressed_BlackWhite; 5722 #endregion 5723 5724 #region Get PixelFormat 5725 PixelFormat PixFormat = PixelFormat.Format24bppRgb; 5726 5727 switch (Header.ImageSpec.PixelDepth) 5728 { 5729 case TgaPixelDepth.Bpp8: 5730 PixFormat = PixelFormat.Format8bppIndexed; 5731 break; 5732 5733 case TgaPixelDepth.Bpp16: 5734 if (IsGrayImage) 5735 PixFormat = PixelFormat.Format16bppGrayScale; 5736 else 5737 PixFormat = (UseAlpha ? PixelFormat.Format16bppArgb1555 : PixelFormat.Format16bppRgb555); 5738 break; 5739 5740 case TgaPixelDepth.Bpp24: 5741 PixFormat = PixelFormat.Format24bppRgb; 5742 break; 5743 5744 case TgaPixelDepth.Bpp32: 5745 if (UseAlpha) 5746 { 5747 var f = Footer; 5748 if (ExtArea?.AttributesType == TgaAttrType.PreMultipliedAlpha) 5749 PixFormat = PixelFormat.Format32bppPArgb; 5750 else 5751 PixFormat = PixelFormat.Format32bppArgb; 5752 } 5753 else 5754 PixFormat = PixelFormat.Format32bppRgb; 5755 break; 5756 5757 default: 5758 PixFormat = PixelFormat.Undefined; 5759 break; 5760 } 5761 #endregion 5762 5763 ushort BMP_Width = (PostageStampImage ? ExtArea.PostageStampImage.Width : Width); 5764 ushort BMP_Height = (PostageStampImage ? ExtArea.PostageStampImage.Height : Height); 5765 Bitmap BMP = new Bitmap(BMP_Width, BMP_Height, PixFormat); 5766 5767 #region ColorMap and GrayPalette 5768 if (Header.ColorMapType == TgaColorMapType.ColorMap && 5769 (Header.ImageType == TgaImageType.RLE_ColorMapped || 5770 Header.ImageType == TgaImageType.Uncompressed_ColorMapped)) 5771 { 5772 5773 ColorPalette ColorMap = BMP.Palette; 5774 Color[] CMapColors = ColorMap.Entries; 5775 5776 switch (Header.ColorMapSpec.ColorMapEntrySize) 5777 { 5778 case TgaColorMapEntrySize.X1R5G5B5: 5779 case TgaColorMapEntrySize.A1R5G5B5: 5780 const float To8Bit = 255f / 31f; // Scale value from 5 to 8 bits. 5781 for (int i = 0; i < Math.Min(CMapColors.Length, Header.ColorMapSpec.ColorMapLength); i++) 5782 { 5783 ushort A1R5G5B5 = BitConverter.ToUInt16(ImageOrColorMapArea.ColorMapData, i * 2); 5784 int A = (UseAlpha ? (A1R5G5B5 & 0x8000) >> 15 : 1) * 255; // (0 or 1) * 255 5785 int R = (int)(((A1R5G5B5 & 0x7C00) >> 10) * To8Bit); 5786 int G = (int)(((A1R5G5B5 & 0x3E0) >> 5) * To8Bit); 5787 int B = (int)((A1R5G5B5 & 0x1F) * To8Bit); 5788 CMapColors[i] = Color.FromArgb(A, R, G, B); 5789 } 5790 break; 5791 5792 case TgaColorMapEntrySize.R8G8B8: 5793 for (int i = 0; i < Math.Min(CMapColors.Length, Header.ColorMapSpec.ColorMapLength); i++) 5794 { 5795 int Index = i * 3; //RGB = 3 bytes 5796 int R = ImageOrColorMapArea.ColorMapData[Index + 2]; 5797 int G = ImageOrColorMapArea.ColorMapData[Index + 1]; 5798 int B = ImageOrColorMapArea.ColorMapData[Index]; 5799 CMapColors[i] = Color.FromArgb(R, G, B); 5800 } 5801 break; 5802 5803 case TgaColorMapEntrySize.A8R8G8B8: 5804 for (int i = 0; i < Math.Min(CMapColors.Length, Header.ColorMapSpec.ColorMapLength); i++) 5805 { 5806 int ARGB = BitConverter.ToInt32(ImageOrColorMapArea.ColorMapData, i * 4); 5807 CMapColors[i] = Color.FromArgb(UseAlpha ? ARGB | (0xFF << 24) : ARGB); 5808 } 5809 break; 5810 5811 default: 5812 ColorMap = null; 5813 break; 5814 } 5815 5816 if (ColorMap != null) 5817 BMP.Palette = ColorMap; 5818 } 5819 5820 if (PixFormat == PixelFormat.Format8bppIndexed && IsGrayImage) 5821 { 5822 ColorPalette GrayPalette = BMP.Palette; 5823 Color[] GrayColors = GrayPalette.Entries; 5824 for (int i = 0; i < GrayColors.Length; i++) 5825 GrayColors[i] = Color.FromArgb(i, i, i); 5826 BMP.Palette = GrayPalette; 5827 } 5828 #endregion 5829 5830 #region Bitmap width must by aligned (align value = 32 bits = 4 bytes)! 5831 byte[] ImageData; 5832 int BytesPerPixel = (int)Math.Ceiling((double)Header.ImageSpec.PixelDepth / 8.0); 5833 int StrideBytes = BMP.Width * BytesPerPixel; 5834 int PaddingBytes = (int)Math.Ceiling(StrideBytes / 4.0) * 4 - StrideBytes; 5835 5836 if (PaddingBytes > 0) //Need bytes align 5837 { 5838 ImageData = new byte[(StrideBytes + PaddingBytes) * BMP.Height]; 5839 for (int i = 0; i < BMP.Height; i++) 5840 Buffer.BlockCopy(PostageStampImage ? ExtArea.PostageStampImage.Data : 5841 ImageOrColorMapArea.ImageData, i * StrideBytes, ImageData, 5842 i * (StrideBytes + PaddingBytes), StrideBytes); 5843 } 5844 else 5845 ImageData = BitConverterExt.ToBytes(PostageStampImage ? ExtArea.PostageStampImage.Data : 5846 ImageOrColorMapArea.ImageData); 5847 5848 // Not official supported, but works (tested on 2 test images)! 5849 if (PixFormat == PixelFormat.Format16bppGrayScale) 5850 { 5851 for (long i = 0; i < ImageData.Length; i++) 5852 ImageData[i] ^= byte.MaxValue; 5853 } 5854 #endregion 5855 5856 Rectangle Re = new Rectangle(0, 0, BMP.Width, BMP.Height); 5857 BitmapData BmpData = BMP.LockBits(Re, ImageLockMode.WriteOnly, BMP.PixelFormat); 5858 Marshal.Copy(ImageData, 0, BmpData.Scan0, ImageData.Length); 5859 BMP.UnlockBits(BmpData); 5860 ImageData = null; 5861 BmpData = null; 5862 5863 if (ExtArea != null && ExtArea.KeyColor.ToInt() != 0) 5864 BMP.MakeTransparent(ExtArea.KeyColor.ToColor()); 5865 5866 #region Flip Image 5867 switch (Header.ImageSpec.ImageDescriptor.ImageOrigin) 5868 { 5869 case TgaImgOrigin.BottomLeft: 5870 BMP.RotateFlip(RotateFlipType.RotateNoneFlipY); 5871 break; 5872 case TgaImgOrigin.BottomRight: 5873 BMP.RotateFlip(RotateFlipType.RotateNoneFlipXY); 5874 break; 5875 case TgaImgOrigin.TopLeft: 5876 default: 5877 break; 5878 case TgaImgOrigin.TopRight: 5879 BMP.RotateFlip(RotateFlipType.RotateNoneFlipX); 5880 break; 5881 } 5882 #endregion 5883 5884 return BMP; 5885 } 5886 catch 5887 { 5888 return null; 5889 } 5890 } 5891 #endregion 5892 5893 #region Explicit 5894 public static explicit operator Bitmap(TGA tga) 5895 { 5896 return tga.ToBitmap(); 5897 } 5898 5899 public static explicit operator TGA(Bitmap bmp) 5900 { 5901 return FromBitmap(bmp); 5902 } 5903 #endregion 5904 5905 #region PostageStamp Image 5906 /// <summary> 5907 /// Convert <see cref="TgaPostageStampImage"/> to <see cref="Bitmap"/>. 5908 /// </summary> 5909 /// <param name="ForceUseAlpha">Force use alpha channel.</param> 5910 /// <returns>Bitmap or null.</returns> 5911 public Bitmap GetPostageStampImage(bool ForceUseAlpha = false) 5912 { 5913 if (ExtArea == null || ExtArea.PostageStampImage == null || ExtArea.PostageStampImage.Data == null || 5914 ExtArea.PostageStampImage.Width <= 0 || ExtArea.PostageStampImage.Height <= 0) 5915 return null; 5916 5917 return ToBitmapFunc(ForceUseAlpha, true); 5918 } 5919 5920 /// <summary> 5921 /// Update Postage Stamp Image or set it. 5922 /// </summary> 5923 public void UpdatePostageStampImage() 5924 { 5925 if (Header.ImageType == TgaImageType.NoImageData) 5926 { 5927 if (ExtArea != null) 5928 ExtArea.PostageStampImage = null; 5929 return; 5930 } 5931 5932 ToNewFormat(); 5933 if (ExtArea.PostageStampImage == null) 5934 ExtArea.PostageStampImage = new TgaPostageStampImage(); 5935 5936 int PS_Width = Header.ImageSpec.ImageWidth; 5937 int PS_Height = Header.ImageSpec.ImageHeight; 5938 5939 if (Width > 64 || Height > 64) 5940 { 5941 float AspectRatio = Width / (float)Height; 5942 PS_Width = (byte)(64f * (AspectRatio < 1f ? AspectRatio : 1f)); 5943 PS_Height = (byte)(64f / (AspectRatio > 1f ? AspectRatio : 1f)); 5944 } 5945 PS_Width = Math.Max(PS_Width, 4); 5946 PS_Height = Math.Max(PS_Height, 4); 5947 5948 ExtArea.PostageStampImage.Width = (byte)PS_Width; 5949 ExtArea.PostageStampImage.Height = (byte)PS_Height; 5950 5951 int BytesPerPixel = (int)Math.Ceiling((double)Header.ImageSpec.PixelDepth / 8.0); 5952 ExtArea.PostageStampImage.Data = new byte[PS_Width * PS_Height * BytesPerPixel]; 5953 5954 float WidthCoef = Width / (float)PS_Width; 5955 float HeightCoef = Height / (float)PS_Height; 5956 5957 for (int y = 0; y < PS_Height; y++) 5958 { 5959 int Y_Offset = (int)(y * HeightCoef) * Width * BytesPerPixel; 5960 int y_Offset = y * PS_Width * BytesPerPixel; 5961 5962 for (int x = 0; x < PS_Width; x++) 5963 { 5964 Buffer.BlockCopy(ImageOrColorMapArea.ImageData, Y_Offset + (int)(x * WidthCoef) * BytesPerPixel, 5965 ExtArea.PostageStampImage.Data, y_Offset + x * BytesPerPixel, BytesPerPixel); 5966 } 5967 } 5968 } 5969 5970 public void DeletePostageStampImage() 5971 { 5972 if (ExtArea != null) 5973 ExtArea.PostageStampImage = null; 5974 } 5975 #endregion 5976 } 5977 }