1 //ketmar: Adam didn't wrote this, don't blame him! 2 module arsd.targa; 3 4 import arsd.color; 5 import std.stdio : File; // sorry 6 7 static if (__traits(compiles, { import iv.vfs; })) enum ArsdTargaHasIVVFS = true; else enum ArsdTargaHasIVVFS = false; 8 static if (ArsdTargaHasIVVFS) import iv.vfs; 9 10 11 // ////////////////////////////////////////////////////////////////////////// // 12 public MemoryImage loadTgaMem (const(void)[] buf, const(char)[] filename=null) { 13 static struct MemRO { 14 const(ubyte)[] data; 15 long pos; 16 17 this (const(void)[] abuf) { data = cast(const(ubyte)[])abuf; } 18 19 @property long tell () { return pos; } 20 @property long size () { return data.length; } 21 22 void seek (long offset, int whence=Seek.Set) { 23 switch (whence) { 24 case Seek.Set: 25 if (offset < 0 || offset > data.length) throw new Exception("invalid offset"); 26 pos = offset; 27 break; 28 case Seek.Cur: 29 if (offset < -pos || offset > data.length-pos) throw new Exception("invalid offset"); 30 pos += offset; 31 break; 32 case Seek.End: 33 pos = data.length+offset; 34 if (pos < 0 || pos > data.length) throw new Exception("invalid offset"); 35 break; 36 default: 37 throw new Exception("invalid offset origin"); 38 } 39 } 40 41 ptrdiff_t read (void* buf, size_t count) @system { 42 if (pos >= data.length) return 0; 43 if (count > 0) { 44 import core.stdc.string : memcpy; 45 long rlen = data.length-pos; 46 if (rlen >= count) rlen = count; 47 assert(rlen != 0); 48 memcpy(buf, data.ptr+pos, cast(size_t)rlen); 49 pos += rlen; 50 return cast(ptrdiff_t)rlen; 51 } else { 52 return 0; 53 } 54 } 55 } 56 57 auto rd = MemRO(buf); 58 return loadTga(rd, filename); 59 } 60 61 static if (ArsdTargaHasIVVFS) public MemoryImage loadTga (VFile fl) { return loadTgaImpl(fl, fl.name); } 62 public MemoryImage loadTga (File fl) { return loadTgaImpl(fl, fl.name); } 63 public MemoryImage loadTga(T:const(char)[]) (T fname) { 64 static if (is(T == typeof(null))) { 65 throw new Exception("cannot load nameless tga"); 66 } else { 67 static if (ArsdTargaHasIVVFS) { 68 return loadTga(VFile(fname)); 69 } else static if (is(T == string)) { 70 return loadTga(File(fname), fname); 71 } else { 72 return loadTga(File(fname.idup), fname); 73 } 74 } 75 } 76 77 // pass filename to ease detection 78 // hack around "has scoped destruction, cannot build closure" 79 public MemoryImage loadTga(ST) (auto ref ST fl, const(char)[] filename=null) if (isReadableStream!ST && isSeekableStream!ST) { return loadTgaImpl(fl, filename); } 80 81 private MemoryImage loadTgaImpl(ST) (auto ref ST fl, const(char)[] filename) { 82 enum TGAFILESIGNATURE = "TRUEVISION-XFILE.\x00"; 83 84 static immutable ubyte[32] cmap16 = [0,8,16,25,33,41,49,58,66,74,82,90,99,107,115,123,132,140,148,156,165,173,181,189,197,206,214,222,230,239,247,255]; 85 86 static struct Header { 87 ubyte idsize; 88 ubyte cmapType; 89 ubyte imgType; 90 ushort cmapFirstIdx; 91 ushort cmapSize; 92 ubyte cmapElementSize; 93 ushort originX; 94 ushort originY; 95 ushort width; 96 ushort height; 97 ubyte bpp; 98 ubyte imgdsc; 99 100 @property bool zeroBits () const pure nothrow @safe @nogc { return ((imgdsc&0xc0) == 0); } 101 @property bool xflip () const pure nothrow @safe @nogc { return ((imgdsc&0b010000) != 0); } 102 @property bool yflip () const pure nothrow @safe @nogc { return ((imgdsc&0b100000) == 0); } 103 } 104 105 static struct ExtFooter { 106 uint extofs; 107 uint devdirofs; 108 char[18] sign=0; 109 } 110 111 static struct Extension { 112 ushort size; 113 char[41] author=0; 114 char[324] comments=0; 115 ushort month, day, year; 116 ushort hour, minute, second; 117 char[41] jid=0; 118 ushort jhours, jmins, jsecs; 119 char[41] producer=0; 120 ushort prodVer; 121 ubyte prodSubVer; 122 ubyte keyR, keyG, keyB, keyZero; 123 ushort pixratioN, pixratioD; 124 ushort gammaN, gammaD; 125 uint ccofs; 126 uint wtfofs; 127 uint scanlineofs; 128 ubyte attrType; 129 } 130 131 ExtFooter extfooter; 132 uint rleBC, rleDC; 133 ubyte[4] rleLast; 134 Color[256] cmap; 135 136 void readPixel(bool asRLE, uint bytesPerPixel) (ubyte[] pixel, scope ubyte delegate () readByte) { 137 static if (asRLE) { 138 if (rleDC > 0) { 139 // still counting 140 static if (bytesPerPixel == 1) pixel.ptr[0] = rleLast.ptr[0]; 141 else pixel.ptr[0..bytesPerPixel] = rleLast.ptr[0..bytesPerPixel]; 142 --rleDC; 143 return; 144 } 145 if (rleBC > 0) { 146 --rleBC; 147 } else { 148 ubyte b = readByte(); 149 if (b&0x80) rleDC = (b&0x7f); else rleBC = (b&0x7f); 150 } 151 foreach (immutable idx; 0..bytesPerPixel) rleLast.ptr[idx] = pixel.ptr[idx] = readByte(); 152 } else { 153 foreach (immutable idx; 0..bytesPerPixel) pixel.ptr[idx] = readByte(); 154 } 155 } 156 157 // 8 bit color-mapped row 158 Color readColorCM8(bool asRLE) (scope ubyte delegate () readByte) { 159 ubyte[1] pixel = void; 160 readPixel!(asRLE, 1)(pixel[], readByte); 161 auto cmp = cast(const(ubyte)*)(cmap.ptr+pixel.ptr[0]); 162 return Color(cmp[0], cmp[1], cmp[2]); 163 } 164 165 // 8 bit greyscale 166 Color readColorBM8(bool asRLE) (scope ubyte delegate () readByte) { 167 ubyte[1] pixel = void; 168 readPixel!(asRLE, 1)(pixel[], readByte); 169 return Color(pixel.ptr[0], pixel.ptr[0], pixel.ptr[0]); 170 } 171 172 // 16 bit greyscale 173 Color readColorBM16(bool asRLE) (scope ubyte delegate () readByte) { 174 ubyte[2] pixel = void; 175 readPixel!(asRLE, 2)(pixel[], readByte); 176 immutable ubyte v = cast(ubyte)((pixel.ptr[0]|(pixel.ptr[1]<<8))>>8); 177 return Color(v, v, v); 178 } 179 180 // 16 bit 181 Color readColor16(bool asRLE) (scope ubyte delegate () readByte) { 182 ubyte[2] pixel = void; 183 readPixel!(asRLE, 2)(pixel[], readByte); 184 immutable v = pixel.ptr[0]+(pixel.ptr[1]<<8); 185 return Color(cmap16.ptr[(v>>10)&0x1f], cmap16.ptr[(v>>5)&0x1f], cmap16.ptr[v&0x1f]); 186 } 187 188 // 24 bit or 32 bit 189 Color readColorTrue(bool asRLE, uint bytesPerPixel) (scope ubyte delegate () readByte) { 190 ubyte[bytesPerPixel] pixel = void; 191 readPixel!(asRLE, bytesPerPixel)(pixel[], readByte); 192 static if (bytesPerPixel == 4) { 193 return Color(pixel.ptr[2], pixel.ptr[1], pixel.ptr[0], pixel.ptr[3]); 194 } else { 195 return Color(pixel.ptr[2], pixel.ptr[1], pixel.ptr[0]); 196 } 197 } 198 199 bool isGoodExtension (const(char)[] filename) { 200 if (filename.length >= 4) { 201 // try extension 202 auto ext = filename[$-4..$]; 203 if (ext[0] == '.' && (ext[1] == 'T' || ext[1] == 't') && (ext[2] == 'G' || ext[2] == 'g') && (ext[3] == 'A' || ext[3] == 'a')) return true; 204 } 205 // try signature 206 return false; 207 } 208 209 bool detect(ST) (auto ref ST fl, const(char)[] filename) if (isReadableStream!ST && isSeekableStream!ST) { 210 bool goodext = false; 211 if (fl.size < 45) return false; // minimal 1x1 tga 212 if (filename.length) { goodext = isGoodExtension(filename); if (!goodext) return false; } 213 // try footer 214 fl.seek(-(4*2+18), Seek.End); 215 extfooter.extofs = fl.readNum!uint; 216 extfooter.devdirofs = fl.readNum!uint; 217 fl.rawReadExact(extfooter.sign[]); 218 if (extfooter.sign != TGAFILESIGNATURE) { 219 //if (!goodext) return false; 220 extfooter = extfooter.init; 221 return true; // alas, footer is optional 222 } 223 return true; 224 } 225 226 if (!detect(fl, filename)) throw new Exception("not a TGA"); 227 fl.seek(0); 228 Header hdr; 229 fl.readStruct(hdr); 230 // parse header 231 // arbitrary size limits 232 if (hdr.width == 0 || hdr.width > 32000) throw new Exception("invalid tga width"); 233 if (hdr.height == 0 || hdr.height > 32000) throw new Exception("invalid tga height"); 234 switch (hdr.bpp) { 235 case 1: case 2: case 4: case 8: case 15: case 16: case 24: case 32: break; 236 default: throw new Exception("invalid tga bpp"); 237 } 238 uint bytesPerPixel = ((hdr.bpp)>>3); 239 if (bytesPerPixel == 0 || bytesPerPixel > 4) throw new Exception("invalid tga pixel size"); 240 bool loadCM = false; 241 // get the row reading function 242 ubyte readByte () { ubyte b; fl.rawReadExact((&b)[0..1]); return b; } 243 scope Color delegate (scope ubyte delegate () readByte) readColor; 244 switch (hdr.imgType) { 245 case 2: // true color, no rle 246 switch (bytesPerPixel) { 247 case 2: readColor = &readColor16!false; break; 248 case 3: readColor = &readColorTrue!(false, 3); break; 249 case 4: readColor = &readColorTrue!(false, 4); break; 250 default: throw new Exception("invalid tga pixel size"); 251 } 252 break; 253 case 10: // true color, rle 254 switch (bytesPerPixel) { 255 case 2: readColor = &readColor16!true; break; 256 case 3: readColor = &readColorTrue!(true, 3); break; 257 case 4: readColor = &readColorTrue!(true, 4); break; 258 default: throw new Exception("invalid tga pixel size"); 259 } 260 break; 261 case 3: // black&white, no rle 262 switch (bytesPerPixel) { 263 case 1: readColor = &readColorBM8!false; break; 264 case 2: readColor = &readColorBM16!false; break; 265 default: throw new Exception("invalid tga pixel size"); 266 } 267 break; 268 case 11: // black&white, rle 269 switch (bytesPerPixel) { 270 case 1: readColor = &readColorBM8!true; break; 271 case 2: readColor = &readColorBM16!true; break; 272 default: throw new Exception("invalid tga pixel size"); 273 } 274 break; 275 case 1: // colormap, no rle 276 if (bytesPerPixel != 1) throw new Exception("invalid tga pixel size"); 277 loadCM = true; 278 readColor = &readColorCM8!false; 279 break; 280 case 9: // colormap, rle 281 if (bytesPerPixel != 1) throw new Exception("invalid tga pixel size"); 282 loadCM = true; 283 readColor = &readColorCM8!true; 284 break; 285 default: throw new Exception("invalid tga format"); 286 } 287 // check for valid colormap 288 switch (hdr.cmapType) { 289 case 0: 290 if (hdr.cmapFirstIdx != 0 || hdr.cmapSize != 0) throw new Exception("invalid tga colormap type"); 291 break; 292 case 1: 293 if (hdr.cmapElementSize != 15 && hdr.cmapElementSize != 16 && hdr.cmapElementSize != 24 && hdr.cmapElementSize != 32) throw new Exception("invalid tga colormap type"); 294 if (hdr.cmapSize == 0) throw new Exception("invalid tga colormap type"); 295 break; 296 default: throw new Exception("invalid tga colormap type"); 297 } 298 if (!hdr.zeroBits) throw new Exception("invalid tga header"); 299 void loadColormap () { 300 if (hdr.cmapType != 1) throw new Exception("invalid tga colormap type"); 301 // calculate color map size 302 uint colorEntryBytes = 0; 303 switch (hdr.cmapElementSize) { 304 case 15: 305 case 16: colorEntryBytes = 2; break; 306 case 24: colorEntryBytes = 3; break; 307 case 32: colorEntryBytes = 4; break; 308 default: throw new Exception("invalid tga colormap type"); 309 } 310 uint colorMapBytes = colorEntryBytes*hdr.cmapSize; 311 if (colorMapBytes == 0) throw new Exception("invalid tga colormap type"); 312 // if we're going to use the color map, read it in. 313 if (loadCM) { 314 if (hdr.cmapFirstIdx+hdr.cmapSize > 256) throw new Exception("invalid tga colormap type"); 315 ubyte readCMB () { 316 if (colorMapBytes == 0) return 0; 317 --colorMapBytes; 318 return readByte; 319 } 320 cmap[] = Color.black; 321 auto cmp = cmap.ptr; 322 switch (colorEntryBytes) { 323 case 2: 324 foreach (immutable n; 0..hdr.cmapSize) { 325 uint v = readCMB(); 326 v |= readCMB()<<8; 327 cmp.b = cmap16.ptr[v&0x1f]; 328 cmp.g = cmap16.ptr[(v>>5)&0x1f]; 329 cmp.r = cmap16.ptr[(v>>10)&0x1f]; 330 ++cmp; 331 } 332 break; 333 case 3: 334 foreach (immutable n; 0..hdr.cmapSize) { 335 cmp.b = readCMB(); 336 cmp.g = readCMB(); 337 cmp.r = readCMB(); 338 ++cmp; 339 } 340 break; 341 case 4: 342 foreach (immutable n; 0..hdr.cmapSize) { 343 cmp.b = readCMB(); 344 cmp.g = readCMB(); 345 cmp.r = readCMB(); 346 cmp.a = readCMB(); 347 ++cmp; 348 } 349 break; 350 default: throw new Exception("invalid tga colormap type"); 351 } 352 } else { 353 // skip colormap 354 fl.seek(colorMapBytes, Seek.Cur); 355 } 356 } 357 358 // now load the data 359 fl.seek(hdr.idsize, Seek.Cur); 360 if (hdr.cmapType != 0) loadColormap(); 361 362 // we don't know if alpha is premultiplied yet 363 bool hasAlpha = (bytesPerPixel == 4); 364 bool validAlpha = hasAlpha; 365 bool premult = false; 366 367 auto tcimg = new TrueColorImage(hdr.width, hdr.height); 368 scope(failure) .destroy(tcimg); 369 370 { 371 // read image data 372 immutable bool xflip = hdr.xflip, yflip = hdr.yflip; 373 Color* pixdata = tcimg.imageData.colors.ptr; 374 if (yflip) pixdata += (hdr.height-1)*hdr.width; 375 foreach (immutable y; 0..hdr.height) { 376 auto d = pixdata; 377 if (xflip) d += hdr.width-1; 378 foreach (immutable x; 0..hdr.width) { 379 *d = readColor(&readByte); 380 if (xflip) --d; else ++d; 381 } 382 if (yflip) pixdata -= hdr.width; else pixdata += hdr.width; 383 } 384 } 385 386 if (hasAlpha) { 387 if (extfooter.extofs != 0) { 388 Extension ext; 389 fl.seek(extfooter.extofs); 390 fl.readStruct(ext); 391 // some idiotic writers set 494 instead 495, tolerate that 392 if (ext.size < 494) throw new Exception("invalid tga extension record"); 393 if (ext.attrType == 4) { 394 // premultiplied alpha 395 foreach (ref Color clr; tcimg.imageData.colors) { 396 if (clr.a != 0) { 397 clr.r = Color.clampToByte(clr.r*255/clr.a); 398 clr.g = Color.clampToByte(clr.g*255/clr.a); 399 clr.b = Color.clampToByte(clr.b*255/clr.a); 400 } 401 } 402 } else if (ext.attrType != 3) { 403 validAlpha = false; 404 } 405 } else { 406 // some writers sets all alphas to zero, check for that 407 validAlpha = false; 408 foreach (ref Color clr; tcimg.imageData.colors) if (clr.a != 0) { validAlpha = true; break; } 409 } 410 if (!validAlpha) foreach (ref Color clr; tcimg.imageData.colors) clr.a = 255; 411 } 412 return tcimg; 413 } 414 415 416 // ////////////////////////////////////////////////////////////////////////// // 417 private: 418 static if (!ArsdTargaHasIVVFS) { 419 import core.stdc.stdio : SEEK_SET, SEEK_CUR, SEEK_END; 420 421 enum Seek : int { 422 Set = SEEK_SET, 423 Cur = SEEK_CUR, 424 End = SEEK_END, 425 } 426 427 428 // ////////////////////////////////////////////////////////////////////////// // 429 // augmentation checks 430 // is this "low-level" stream that can be read? 431 enum isLowLevelStreamR(T) = is(typeof((inout int=0) { 432 auto t = T.init; 433 ubyte[1] b; 434 ptrdiff_t r = t.read(b.ptr, 1); 435 })); 436 437 // is this "low-level" stream that can be written? 438 enum isLowLevelStreamW(T) = is(typeof((inout int=0) { 439 auto t = T.init; 440 ubyte[1] b; 441 ptrdiff_t w = t.write(b.ptr, 1); 442 })); 443 444 445 // is this "low-level" stream that can be seeked? 446 enum isLowLevelStreamS(T) = is(typeof((inout int=0) { 447 auto t = T.init; 448 long p = t.lseek(0, 0); 449 })); 450 451 452 // ////////////////////////////////////////////////////////////////////////// // 453 // augment low-level streams with `rawRead` 454 T[] rawRead(ST, T) (auto ref ST st, T[] buf) if (isLowLevelStreamR!ST && !is(T == const) && !is(T == immutable)) { 455 if (buf.length > 0) { 456 auto res = st.read(buf.ptr, buf.length*T.sizeof); 457 if (res == -1 || res%T.sizeof != 0) throw new Exception("read error"); 458 return buf[0..res/T.sizeof]; 459 } else { 460 return buf[0..0]; 461 } 462 } 463 464 // augment low-level streams with `rawWrite` 465 void rawWrite(ST, T) (auto ref ST st, in T[] buf) if (isLowLevelStreamW!ST) { 466 if (buf.length > 0) { 467 auto res = st.write(buf.ptr, buf.length*T.sizeof); 468 if (res == -1 || res%T.sizeof != 0) throw new Exception("write error"); 469 } 470 } 471 472 // read exact size or throw error 473 package(arsd) T[] rawReadExact(ST, T) (auto ref ST st, T[] buf) if (isReadableStream!ST && !is(T == const) && !is(T == immutable)) { 474 if (buf.length == 0) return buf; 475 auto left = buf.length*T.sizeof; 476 auto dp = cast(ubyte*)buf.ptr; 477 while (left > 0) { 478 auto res = st.rawRead(cast(void[])(dp[0..left])); 479 if (res.length == 0) throw new Exception("read error"); 480 dp += res.length; 481 left -= res.length; 482 } 483 return buf; 484 } 485 486 // write exact size or throw error (just for convenience) 487 void rawWriteExact(ST, T) (auto ref ST st, in T[] buf) if (isWriteableStream!ST) { st.rawWrite(buf); } 488 489 // if stream doesn't have `.size`, but can be seeked, emulate it 490 long size(ST) (auto ref ST st) if (isSeekableStream!ST && !streamHasSize!ST) { 491 auto opos = st.tell; 492 st.seek(0, Seek.End); 493 auto res = st.tell; 494 st.seek(opos); 495 return res; 496 } 497 498 499 // ////////////////////////////////////////////////////////////////////////// // 500 // check if a given stream supports `eof` 501 enum streamHasEof(T) = is(typeof((inout int=0) { 502 auto t = T.init; 503 bool n = t.eof; 504 })); 505 506 // check if a given stream supports `seek` 507 enum streamHasSeek(T) = is(typeof((inout int=0) { 508 import core.stdc.stdio : SEEK_END; 509 auto t = T.init; 510 t.seek(0); 511 t.seek(0, SEEK_END); 512 })); 513 514 // check if a given stream supports `tell` 515 enum streamHasTell(T) = is(typeof((inout int=0) { 516 auto t = T.init; 517 long pos = t.tell; 518 })); 519 520 // check if a given stream supports `size` 521 enum streamHasSize(T) = is(typeof((inout int=0) { 522 auto t = T.init; 523 long pos = t.size; 524 })); 525 526 // check if a given stream supports `rawRead()`. 527 // it's enough to support `void[] rawRead (void[] buf)` 528 enum isReadableStream(T) = is(typeof((inout int=0) { 529 auto t = T.init; 530 ubyte[1] b; 531 auto v = cast(void[])b; 532 t.rawRead(v); 533 })); 534 535 // check if a given stream supports `rawWrite()`. 536 // it's enough to support `inout(void)[] rawWrite (inout(void)[] buf)` 537 enum isWriteableStream(T) = is(typeof((inout int=0) { 538 auto t = T.init; 539 ubyte[1] b; 540 t.rawWrite(cast(void[])b); 541 })); 542 543 // check if a given stream supports `.seek(ofs, [whence])`, and `.tell` 544 enum isSeekableStream(T) = (streamHasSeek!T && streamHasTell!T); 545 546 // check if we can get size of a given stream. 547 // this can be done either with `.size`, or with `.seek` and `.tell` 548 enum isSizedStream(T) = (streamHasSize!T || isSeekableStream!T); 549 550 // ////////////////////////////////////////////////////////////////////////// // 551 private enum isGoodEndianness(string s) = (s == "LE" || s == "le" || s == "BE" || s == "be"); 552 553 private template isLittleEndianness(string s) if (isGoodEndianness!s) { 554 enum isLittleEndianness = (s == "LE" || s == "le"); 555 } 556 557 private template isBigEndianness(string s) if (isGoodEndianness!s) { 558 enum isLittleEndianness = (s == "BE" || s == "be"); 559 } 560 561 private template isSystemEndianness(string s) if (isGoodEndianness!s) { 562 version(LittleEndian) { 563 enum isSystemEndianness = isLittleEndianness!s; 564 } else { 565 enum isSystemEndianness = isBigEndianness!s; 566 } 567 } 568 569 570 // ////////////////////////////////////////////////////////////////////////// // 571 // write integer value of the given type, with the given endianness (default: little-endian) 572 // usage: st.writeNum!ubyte(10) 573 void writeNum(T, string es="LE", ST) (auto ref ST st, T n) if (isGoodEndianness!es && isWriteableStream!ST && __traits(isIntegral, T)) { 574 static assert(T.sizeof <= 8); // just in case 575 static if (isSystemEndianness!es) { 576 st.rawWriteExact((&n)[0..1]); 577 } else { 578 ubyte[T.sizeof] b = void; 579 version(LittleEndian) { 580 // convert to big-endian 581 foreach_reverse (ref x; b) { x = n&0xff; n >>= 8; } 582 } else { 583 // convert to little-endian 584 foreach (ref x; b) { x = n&0xff; n >>= 8; } 585 } 586 st.rawWriteExact(b[]); 587 } 588 } 589 590 591 // read integer value of the given type, with the given endianness (default: little-endian) 592 // usage: auto v = st.readNum!ubyte 593 T readNum(T, string es="LE", ST) (auto ref ST st) if (isGoodEndianness!es && isReadableStream!ST && __traits(isIntegral, T)) { 594 static assert(T.sizeof <= 8); // just in case 595 static if (isSystemEndianness!es) { 596 T v = void; 597 st.rawReadExact((&v)[0..1]); 598 return v; 599 } else { 600 ubyte[T.sizeof] b = void; 601 st.rawReadExact(b[]); 602 T v = 0; 603 version(LittleEndian) { 604 // convert from big-endian 605 foreach (ubyte x; b) { v <<= 8; v |= x; } 606 } else { 607 // conver from little-endian 608 foreach_reverse (ubyte x; b) { v <<= 8; v |= x; } 609 } 610 return v; 611 } 612 } 613 614 615 private enum reverseBytesMixin = " 616 foreach (idx; 0..b.length/2) { 617 ubyte t = b[idx]; 618 b[idx] = b[$-idx-1]; 619 b[$-idx-1] = t; 620 } 621 "; 622 623 624 // write floating value of the given type, with the given endianness (default: little-endian) 625 // usage: st.writeNum!float(10) 626 void writeNum(T, string es="LE", ST) (auto ref ST st, T n) if (isGoodEndianness!es && isWriteableStream!ST && __traits(isFloating, T)) { 627 static assert(T.sizeof <= 8); 628 static if (isSystemEndianness!es) { 629 st.rawWriteExact((&n)[0..1]); 630 } else { 631 import core.stdc.string : memcpy; 632 ubyte[T.sizeof] b = void; 633 memcpy(b.ptr, &v, T.sizeof); 634 mixin(reverseBytesMixin); 635 st.rawWriteExact(b[]); 636 } 637 } 638 639 640 // read floating value of the given type, with the given endianness (default: little-endian) 641 // usage: auto v = st.readNum!float 642 T readNum(T, string es="LE", ST) (auto ref ST st) if (isGoodEndianness!es && isReadableStream!ST && __traits(isFloating, T)) { 643 static assert(T.sizeof <= 8); 644 T v = void; 645 static if (isSystemEndianness!es) { 646 st.rawReadExact((&v)[0..1]); 647 } else { 648 import core.stdc.string : memcpy; 649 ubyte[T.sizeof] b = void; 650 st.rawReadExact(b[]); 651 mixin(reverseBytesMixin); 652 memcpy(&v, b.ptr, T.sizeof); 653 } 654 return v; 655 } 656 657 658 // ////////////////////////////////////////////////////////////////////////// // 659 void readStruct(string es="LE", SS, ST) (auto ref ST fl, ref SS st) 660 if (is(SS == struct) && isGoodEndianness!es && isReadableStream!ST) 661 { 662 void unserData(T) (ref T v) { 663 import std.traits : Unqual; 664 alias UT = Unqual!T; 665 static if (is(T : V[], V)) { 666 // array 667 static if (__traits(isStaticArray, T)) { 668 foreach (ref it; v) unserData(it); 669 } else static if (is(UT == char)) { 670 // special case: dynamic `char[]` array will be loaded as asciiz string 671 char c; 672 for (;;) { 673 if (fl.rawRead((&c)[0..1]).length == 0) break; // don't require trailing zero on eof 674 if (c == 0) break; 675 v ~= c; 676 } 677 } else { 678 assert(0, "cannot load dynamic arrays yet"); 679 } 680 } else static if (is(T : V[K], K, V)) { 681 assert(0, "cannot load associative arrays yet"); 682 } else static if (__traits(isIntegral, UT) || __traits(isFloating, UT)) { 683 // this takes care of `*char` and `bool` too 684 v = cast(UT)fl.readNum!(UT, es); 685 } else static if (is(T == struct)) { 686 // struct 687 import std.traits : FieldNameTuple, hasUDA; 688 foreach (string fldname; FieldNameTuple!T) { 689 unserData(__traits(getMember, v, fldname)); 690 } 691 } 692 } 693 694 unserData(st); 695 } 696 }