summaryrefslogtreecommitdiffstats
path: root/src/world.cpp
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/world.cpp235
1 files changed, 194 insertions, 41 deletions
diff --git a/src/world.cpp b/src/world.cpp
index 391ab1a..621e5f7 100644
--- a/src/world.cpp
+++ b/src/world.cpp
@@ -348,64 +348,217 @@ void fog_update() {
348 348
349namespace { 349namespace {
350 350
351struct PngImage { 351// The game modules export their own (older) libpng symbols, so loading the
352 void *opaque; 352// system libpng16 makes its internal calls bind to the game's libpng with a
353 uint32_t version, width, height, format, flags, colormap_entries, 353// different png_struct layout (crash in png_safe_warning). Decode the PNG
354 warning_or_error; 354// ourselves with zlib instead: no symbol interposition, no extra dependency.
355 char message[64]; 355using UncompressFn = int (*)(unsigned char *, unsigned long *,
356}; 356 const unsigned char *, unsigned long);
357 357
358bool load_png_rgba(const char *path, std::vector<unsigned char> &pixels, 358UncompressFn zlib_uncompress() {
359 int &w, int &h) {
360 static void *lib = nullptr; 359 static void *lib = nullptr;
361 static bool tried = false; 360 static bool tried = false;
361 static UncompressFn fn = nullptr;
362 if (!tried) { 362 if (!tried) {
363 tried = true; 363 tried = true;
364 lib = dlopen("libpng16.so.16", RTLD_LAZY | RTLD_GLOBAL); 364 lib = dlopen("libz.so.1", RTLD_LAZY | RTLD_GLOBAL);
365 if (!lib) 365 if (!lib)
366 lib = dlopen("libpng16.so", RTLD_LAZY | RTLD_GLOBAL); 366 lib = dlopen("libz.so", RTLD_LAZY | RTLD_GLOBAL);
367 if (lib)
368 fn = (UncompressFn)dlsym(lib, "uncompress");
367 } 369 }
368 if (!lib) { 370 return fn;
369 logf("skybox: libpng16 not available"); 371}
372
373uint32_t be32(const unsigned char *p) {
374 return ((uint32_t)p[0] << 24) | ((uint32_t)p[1] << 16) |
375 ((uint32_t)p[2] << 8) | p[3];
376}
377
378int paeth(int a, int b, int c) {
379 int p = a + b - c;
380 int pa = p > a ? p - a : a - p;
381 int pb = p > b ? p - b : b - p;
382 int pc = p > c ? p - c : c - p;
383 if (pa <= pb && pa <= pc)
384 return a;
385 return pb <= pc ? b : c;
386}
387
388bool load_png_rgba(const char *path, std::vector<unsigned char> &pixels,
389 int &out_w, int &out_h) {
390 FILE *f = fopen(path, "rb");
391 if (!f) {
392 logf("skybox: cannot open %s", path);
393 return false;
394 }
395 fseek(f, 0, SEEK_END);
396 long fsz = ftell(f);
397 fseek(f, 0, SEEK_SET);
398 if (fsz <= 0 || fsz > 32 * 1024 * 1024) {
399 fclose(f);
400 logf("skybox: %s has an unsupported file size", path);
401 return false;
402 }
403 std::vector<unsigned char> file((size_t)fsz);
404 size_t got = fread(file.data(), 1, file.size(), f);
405 fclose(f);
406 if (got != file.size()) {
407 logf("skybox: short read on %s", path);
370 return false; 408 return false;
371 } 409 }
372 using BeginFn = int (*)(PngImage *, const char *); 410
373 using FinishFn = int (*)(PngImage *, void *, void *, int32_t, void *); 411 static const unsigned char sig[8] = {0x89, 'P', 'N', 'G', '\r',
374 using FreeFn = void (*)(PngImage *); 412 '\n', 0x1a, '\n'};
375 static BeginFn begin = 413 if (file.size() < 8 || memcmp(file.data(), sig, 8) != 0) {
376 (BeginFn)dlsym(lib, "png_image_begin_read_from_file"); 414 logf("skybox: %s is not a PNG", path);
377 static FinishFn finish = (FinishFn)dlsym(lib, "png_image_finish_read");
378 static FreeFn free_img = (FreeFn)dlsym(lib, "png_image_free");
379 if (!begin || !finish || !free_img) {
380 logf("skybox: libpng16 simplified API missing");
381 return false; 415 return false;
382 } 416 }
383 417
384 PngImage img{}; 418 uint32_t w = 0, h = 0;
385 img.version = 1; // PNG_IMAGE_VERSION 419 int depth = 0, ctype = -1, comp = 0, filt = 0, inter = 0;
386 img.format = 0x03; // PNG_FORMAT_RGBA 420 bool have_ihdr = false;
387 if (!begin(&img, path)) { 421 std::vector<unsigned char> idat, plte, trns;
388 logf("skybox: cannot open %s (%s)", path, 422 size_t pos = 8;
389 img.message[0] ? img.message : "read error"); 423 while (pos + 12 <= file.size()) {
424 uint32_t len = be32(file.data() + pos);
425 pos += 4;
426 const unsigned char *type = file.data() + pos;
427 pos += 4;
428 if ((size_t)len > file.size() - pos - 4)
429 break;
430 const unsigned char *data = file.data() + pos;
431 if (memcmp(type, "IHDR", 4) == 0 && len >= 13) {
432 w = be32(data);
433 h = be32(data + 4);
434 depth = data[8];
435 ctype = data[9];
436 comp = data[10];
437 filt = data[11];
438 inter = data[12];
439 have_ihdr = true;
440 } else if (memcmp(type, "PLTE", 4) == 0) {
441 plte.assign(data, data + len);
442 } else if (memcmp(type, "tRNS", 4) == 0) {
443 trns.assign(data, data + len);
444 } else if (memcmp(type, "IDAT", 4) == 0) {
445 idat.insert(idat.end(), data, data + len);
446 } else if (memcmp(type, "IEND", 4) == 0) {
447 break;
448 }
449 pos += (size_t)len + 4; // data + CRC
450 }
451
452 if (!have_ihdr || w == 0 || h == 0 || w > 8192 || h > 8192) {
453 logf("skybox: %s has an invalid PNG header", path);
454 return false;
455 }
456 if (depth != 8 || comp != 0 || filt != 0 || inter != 0) {
457 logf("skybox: %s: only 8-bit non-interlaced PNGs are supported", path);
390 return false; 458 return false;
391 } 459 }
392 if (img.width == 0 || img.height == 0 || img.width > 8192 || 460 int channels = ctype == 0 ? 1
393 img.height > 8192) { 461 : ctype == 2 ? 3
394 logf("skybox: %s has unsupported size %ux%u", path, img.width, img.height); 462 : ctype == 3 ? 1
395 free_img(&img); 463 : ctype == 4 ? 2
464 : ctype == 6 ? 4
465 : 0;
466 if (channels == 0 || idat.empty()) {
467 logf("skybox: %s: unsupported PNG colour type %d", path, ctype);
396 return false; 468 return false;
397 } 469 }
398 pixels.assign((size_t)img.width * img.height * 4, 255); 470 if (ctype == 3 && plte.size() < 3) {
399 if (!finish(&img, nullptr, pixels.data(), 0, nullptr)) { 471 logf("skybox: %s: paletted PNG without a palette", path);
400 logf("skybox: cannot decode %s (%s)", path,
401 img.message[0] ? img.message : "decode error");
402 pixels.clear();
403 free_img(&img);
404 return false; 472 return false;
405 } 473 }
406 w = (int)img.width; 474
407 h = (int)img.height; 475 size_t stride = (size_t)w * (size_t)channels;
408 free_img(&img); 476 size_t raw_size = (stride + 1) * (size_t)h;
477 std::vector<unsigned char> raw(raw_size);
478 unsigned long dest_len = (unsigned long)raw_size;
479 UncompressFn uncompress_fn = zlib_uncompress();
480 if (!uncompress_fn) {
481 logf("skybox: zlib unavailable");
482 return false;
483 }
484 if (uncompress_fn(raw.data(), &dest_len, idat.data(),
485 (unsigned long)idat.size()) != 0 ||
486 dest_len < raw_size) {
487 logf("skybox: %s: zlib could not inflate the image data", path);
488 return false;
489 }
490
491 std::vector<unsigned char> scan(stride * (size_t)h);
492 int bpp = channels;
493 for (uint32_t y = 0; y < h; y++) {
494 const unsigned char *src = raw.data() + (size_t)y * (stride + 1);
495 unsigned char ft = src[0];
496 const unsigned char *in = src + 1;
497 unsigned char *out = scan.data() + (size_t)y * stride;
498 const unsigned char *prev =
499 y ? scan.data() + (size_t)(y - 1) * stride : nullptr;
500 for (size_t x = 0; x < stride; x++) {
501 int a = x >= (size_t)bpp ? out[x - (size_t)bpp] : 0;
502 int b = prev ? prev[x] : 0;
503 int c = (prev && x >= (size_t)bpp) ? prev[x - (size_t)bpp] : 0;
504 int v = in[x];
505 switch (ft) {
506 case 0: break;
507 case 1: v += a; break;
508 case 2: v += b; break;
509 case 3: v += (a + b) >> 1; break;
510 case 4: v += paeth(a, b, c); break;
511 default:
512 logf("skybox: %s: bad PNG row filter %d", path, (int)ft);
513 return false;
514 }
515 out[x] = (unsigned char)(v & 0xff);
516 }
517 }
518
519 pixels.resize((size_t)w * (size_t)h * 4);
520 for (uint32_t y = 0; y < h; y++) {
521 const unsigned char *in = scan.data() + (size_t)y * stride;
522 unsigned char *o = pixels.data() + (size_t)y * (size_t)w * 4;
523 for (uint32_t x = 0; x < w; x++) {
524 switch (ctype) {
525 case 6:
526 o[0] = in[0]; o[1] = in[1]; o[2] = in[2]; o[3] = in[3];
527 in += 4;
528 break;
529 case 2:
530 o[0] = in[0]; o[1] = in[1]; o[2] = in[2]; o[3] = 255;
531 in += 3;
532 break;
533 case 0:
534 o[0] = o[1] = o[2] = in[0]; o[3] = 255;
535 in += 1;
536 break;
537 case 4:
538 o[0] = o[1] = o[2] = in[0]; o[3] = in[1];
539 in += 2;
540 break;
541 case 3: {
542 unsigned idx = in[0];
543 in += 1;
544 if ((size_t)idx * 3 + 2 < plte.size()) {
545 o[0] = plte[(size_t)idx * 3];
546 o[1] = plte[(size_t)idx * 3 + 1];
547 o[2] = plte[(size_t)idx * 3 + 2];
548 o[3] = (size_t)idx < trns.size() ? trns[idx] : 255;
549 } else {
550 o[0] = o[1] = o[2] = 0;
551 o[3] = 255;
552 }
553 break;
554 }
555 }
556 o += 4;
557 }
558 }
559
560 out_w = (int)w;
561 out_h = (int)h;
409 return true; 562 return true;
410} 563}
411 564