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;
}