std::shared_ptr TextureBuilder::combine()

in src/gltf/TextureBuilder.cpp [32:170]


std::shared_ptr<TextureData> TextureBuilder::combine(
    const std::vector<int>& ixVec,
    const std::string& tag,
    const pixel_merger& computePixel,
    bool includeAlphaChannel) {
  const std::string key = texIndicesKey(ixVec, tag);
  auto iter = textureByIndicesKey.find(key);
  if (iter != textureByIndicesKey.end()) {
    return iter->second;
  }

  int width = -1, height = -1;
  std::string mergedFilename = tag;
  std::vector<TexInfo> texes{};
  for (const int rawTexIx : ixVec) {
    TexInfo info(rawTexIx);
    if (rawTexIx >= 0) {
      const RawTexture& rawTex = raw.GetTexture(rawTexIx);
      const std::string& fileLoc = rawTex.fileLocation;
      const std::string& name = FileUtils::GetFileBase(FileUtils::GetFileName(fileLoc));
      if (!fileLoc.empty()) {
        info.pixels = stbi_load(fileLoc.c_str(), &info.width, &info.height, &info.channels, 0);
        if (!info.pixels) {
          fmt::printf("Warning: merge texture [%d](%s) could not be loaded.\n", rawTexIx, name);
        } else {
          if (width < 0) {
            width = info.width;
            height = info.height;
          } else if (width != info.width || height != info.height) {
            fmt::printf(
                "Warning: texture %s (%d, %d) can't be merged with previous texture(s) of dimension (%d, %d)\n",
                name,
                info.width,
                info.height,
                width,
                height);
            // this is bad enough that we abort the whole merge
            return nullptr;
          }
          mergedFilename += "_" + name;
        }
      }
    }
    texes.push_back(info);
  }
  // at the moment, the best choice of filename is also the best choice of name
  const std::string mergedName = mergedFilename;

  if (width < 0) {
    // no textures to merge; bail
    return nullptr;
  }
  // TODO: which channel combinations make sense in input files?

  // write 3 or 4 channels depending on whether or not we need transparency
  int channels = includeAlphaChannel ? 4 : 3;

  std::vector<uint8_t> mergedPixels(static_cast<size_t>(channels * width * height));
  for (int xx = 0; xx < width; xx++) {
    for (int yy = 0; yy < height; yy++) {
      std::vector<pixel> pixels(texes.size());
      std::vector<const pixel*> pixelPointers(texes.size(), nullptr);
      for (int jj = 0; jj < texes.size(); jj++) {
        const TexInfo& tex = texes[jj];
        // each texture's structure will depend on its channel count
        int ii = tex.channels * (xx + yy * width);
        int kk = 0;
        if (tex.pixels != nullptr) {
          for (; kk < tex.channels; kk++) {
            pixels[jj][kk] = tex.pixels[ii++] / 255.0f;
          }
        }
        for (; kk < pixels[jj].size(); kk++) {
          pixels[jj][kk] = 1.0f;
        }
        pixelPointers[jj] = &pixels[jj];
      }
      const pixel merged = computePixel(pixelPointers);
      int ii = channels * (xx + yy * width);
      for (int jj = 0; jj < channels; jj++) {
        mergedPixels[ii + jj] = static_cast<uint8_t>(fmax(0, fmin(255.0f, merged[jj] * 255.0f)));
      }
    }
  }

  // write a .png iff we need transparency in the destination texture
  bool png = includeAlphaChannel;

  std::vector<char> imgBuffer;
  int res;
  if (png) {
    res = stbi_write_png_to_func(
        WriteToVectorContext,
        &imgBuffer,
        width,
        height,
        channels,
        mergedPixels.data(),
        width * channels);
  } else {
    res = stbi_write_jpg_to_func(
        WriteToVectorContext, &imgBuffer, width, height, channels, mergedPixels.data(), 80);
  }
  if (!res) {
    fmt::printf("Warning: failed to generate merge texture '%s'.\n", mergedFilename);
    return nullptr;
  }

  ImageData* image;
  if (options.outputBinary) {
    const auto bufferView =
        gltf.AddRawBufferView(*gltf.defaultBuffer, imgBuffer.data(), to_uint32(imgBuffer.size()));
    image = new ImageData(mergedName, *bufferView, png ? "image/png" : "image/jpeg");
  } else {
    const std::string imageFilename = mergedFilename + (png ? ".png" : ".jpg");
    const std::string imagePath = outputFolder + imageFilename;
    FILE* fp = fopen(imagePath.c_str(), "wb");
    if (fp == nullptr) {
      fmt::printf("Warning:: Couldn't write file '%s' for writing.\n", imagePath);
      return nullptr;
    }

    if (fwrite(imgBuffer.data(), imgBuffer.size(), 1, fp) != 1) {
      fmt::printf(
          "Warning: Failed to write %lu bytes to file '%s'.\n", imgBuffer.size(), imagePath);
      fclose(fp);
      return nullptr;
    }
    fclose(fp);
    if (verboseOutput) {
      fmt::printf("Wrote %lu bytes to texture '%s'.\n", imgBuffer.size(), imagePath);
    }
    image = new ImageData(mergedName, imageFilename);
  }
  std::shared_ptr<TextureData> texDat = gltf.textures.hold(
      new TextureData(mergedName, *gltf.defaultSampler, *gltf.images.hold(image)));
  textureByIndicesKey.insert(std::make_pair(key, texDat));
  return texDat;
}