239 lines
8.0 KiB
C++
239 lines
8.0 KiB
C++
/*
|
|
* Copyright (C) 2015 The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
|
|
#include <fstream>
|
|
#include <iostream>
|
|
|
|
#include <math/vec3.h>
|
|
|
|
#include <imageio/ImageDecoder.h>
|
|
#include <imageio/ImageEncoder.h>
|
|
|
|
#include <utils/Path.h>
|
|
|
|
#include <getopt/getopt.h>
|
|
|
|
using namespace filament::math;
|
|
using namespace image;
|
|
|
|
static image::ImageEncoder::Format g_format = image::ImageEncoder::Format::PNG;
|
|
static bool g_formatSpecified = false;
|
|
static std::string g_compression = "";
|
|
|
|
static void blend(const LinearImage& normal, const LinearImage& detail, LinearImage output);
|
|
|
|
static void printUsage(const char* name) {
|
|
std::string execName(utils::Path(name).getName());
|
|
std::string usage(
|
|
"NORMALBLENDING is a tool for blending normal maps using Reoriented Normal Mapping\n"
|
|
"Usage:\n"
|
|
" NORMALBLENDING [options] <normal-map> <detail-map> <output-map>\n"
|
|
"\n"
|
|
"Supported input formats:\n"
|
|
" PNG, 8 and 16 bits\n"
|
|
" Radiance (.hdr)\n"
|
|
" Photoshop (.psd), 16 and 32 bits\n"
|
|
" OpenEXR (.exr)\n"
|
|
"\n"
|
|
"Options:\n"
|
|
" --help, -h\n"
|
|
" print this message\n\n"
|
|
" --license\n"
|
|
" Print copyright and license information\n\n"
|
|
" --format=[exr|hdr|psd|png|dds], -f [exr|hdr|psd|png|dds]\n"
|
|
" specify output file format, inferred from file name if omitted\n\n"
|
|
" --compression=COMPRESSION, -c COMPRESSION\n"
|
|
" format specific compression:\n"
|
|
" PNG: Ignored\n"
|
|
" Radiance: Ignored\n"
|
|
" Photoshop: 16 (default), 32\n"
|
|
" OpenEXR: RAW, RLE, ZIPS, ZIP, PIZ (default)\n"
|
|
" DDS: 8, 16 (default), 32\n\n"
|
|
);
|
|
|
|
const std::string from("NORMALBLENDING");
|
|
for (size_t pos = usage.find(from); pos != std::string::npos; pos = usage.find(from, pos)) {
|
|
usage.replace(pos, from.length(), execName);
|
|
}
|
|
printf("%s", usage.c_str());
|
|
}
|
|
|
|
static void license() {
|
|
static const char *license[] = {
|
|
#include "licenses/licenses.inc"
|
|
nullptr
|
|
};
|
|
|
|
const char **p = &license[0];
|
|
while (*p)
|
|
std::cout << *p++ << std::endl;
|
|
}
|
|
|
|
static int handleArguments(int argc, char* argv[]) {
|
|
static constexpr const char* OPTSTR = "hf:c:";
|
|
static const struct option OPTIONS[] = {
|
|
{ "help", no_argument, 0, 'h' },
|
|
{ "license", no_argument, 0, 'l' },
|
|
{ "format", required_argument, 0, 'f' },
|
|
{ "compression", required_argument, 0, 'c' },
|
|
{ 0, 0, 0, 0 } // termination of the option list
|
|
};
|
|
|
|
int opt;
|
|
int optionIndex = 0;
|
|
|
|
while ((opt = getopt_long(argc, argv, OPTSTR, OPTIONS, &optionIndex)) >= 0) {
|
|
std::string arg(optarg ? optarg : "");
|
|
switch (opt) {
|
|
default:
|
|
case 'h':
|
|
printUsage(argv[0]);
|
|
exit(0);
|
|
// break;
|
|
case 'l':
|
|
license();
|
|
exit(0);
|
|
// break;
|
|
case 'f':
|
|
if (arg == "png") {
|
|
g_format = ImageEncoder::Format::PNG;
|
|
g_formatSpecified = true;
|
|
}
|
|
if (arg == "hdr") {
|
|
g_format = ImageEncoder::Format::HDR;
|
|
g_formatSpecified = true;
|
|
}
|
|
if (arg == "exr") {
|
|
g_format = ImageEncoder::Format::EXR;
|
|
g_formatSpecified = true;
|
|
}
|
|
if (arg == "psd") {
|
|
g_format = ImageEncoder::Format::PSD;
|
|
g_formatSpecified = true;
|
|
}
|
|
if (arg == "dds") {
|
|
g_format = ImageEncoder::Format::DDS_LINEAR;
|
|
g_formatSpecified = true;
|
|
}
|
|
break;
|
|
case 'c':
|
|
g_compression = arg;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return optind;
|
|
}
|
|
|
|
int main(int argc, char* argv[]) {
|
|
int optionIndex = handleArguments(argc, argv);
|
|
|
|
int numArgs = argc - optionIndex;
|
|
if (numArgs < 3) {
|
|
printUsage(argv[0]);
|
|
return 1;
|
|
}
|
|
|
|
utils::Path normalMap(argv[optionIndex ]);
|
|
utils::Path detailMap(argv[optionIndex + 1]);
|
|
utils::Path outputMap(argv[optionIndex + 2]);
|
|
|
|
if (!normalMap.exists()) {
|
|
std::cerr << "The input normal map does not exist: " << normalMap << std::endl;
|
|
exit(1);
|
|
}
|
|
|
|
if (!detailMap.exists()) {
|
|
std::cerr << "The input detail map does not exist: " << detailMap << std::endl;
|
|
exit(1);
|
|
}
|
|
|
|
// make sure we load the normal maps as linear data
|
|
std::ifstream inputStream(normalMap, std::ios::binary);
|
|
LinearImage normalImage = ImageDecoder::decode(inputStream, normalMap,
|
|
ImageDecoder::ColorSpace::LINEAR);
|
|
if (!normalImage.isValid()) {
|
|
std::cerr << "The input normal map is invalid: " << normalMap << std::endl;
|
|
exit(1);
|
|
}
|
|
inputStream.close();
|
|
|
|
inputStream.open(detailMap, std::ios::binary);
|
|
LinearImage detailImage = ImageDecoder::decode(inputStream, detailMap,
|
|
ImageDecoder::ColorSpace::LINEAR);
|
|
if (!detailImage.isValid()) {
|
|
std::cerr << "The detail normal map is invalid: " << detailMap << std::endl;
|
|
exit(1);
|
|
}
|
|
inputStream.close();
|
|
|
|
// TODO: handle normal maps of different sizes
|
|
if (normalImage.getWidth() != detailImage.getWidth() ||
|
|
normalImage.getHeight() != detailImage.getHeight()) {
|
|
std::cerr << "The normal and detail maps must have the same dimensions:" << std::endl
|
|
<< " Normal map: " << normalImage.getWidth() << "x" << normalImage.getHeight()
|
|
<< std::endl
|
|
<< " Detail map: " << detailImage.getWidth() << "x" << detailImage.getHeight()
|
|
<< std::endl;
|
|
exit(1);
|
|
}
|
|
|
|
size_t width = normalImage.getWidth();
|
|
size_t height = normalImage.getHeight();
|
|
LinearImage image(width, height, 3);
|
|
|
|
blend(normalImage, detailImage, image);
|
|
|
|
if (!g_formatSpecified) {
|
|
g_format = ImageEncoder::chooseFormat(outputMap);
|
|
}
|
|
|
|
std::ofstream outputStream(outputMap, std::ios::binary | std::ios::trunc);
|
|
if (!outputStream.good()) {
|
|
std::cerr << "The output file cannot be opened: " << outputMap << std::endl;
|
|
exit(1);
|
|
}
|
|
|
|
ImageEncoder::encode(outputStream, g_format, image, g_compression, outputMap.getPath());
|
|
|
|
outputStream.close();
|
|
if (!outputStream.good()) {
|
|
std::cerr << "An error occurred while writing the output file: " << outputMap << std::endl;
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
void blend(const LinearImage& normal, const LinearImage& detail, LinearImage output) {
|
|
const size_t width = output.getWidth();
|
|
const size_t height = output.getHeight();
|
|
|
|
for (size_t y = 0; y < height; y++) {
|
|
auto normalRow = normal.get<float3>(0, y);
|
|
auto detailRow = detail.get<float3>(0, y);
|
|
auto outputRow = output.get<float3>(0, y);
|
|
|
|
for (size_t x = 0; x < width; x++, normalRow++, detailRow++, outputRow++) {
|
|
// Reoriented Normal Mapping
|
|
float3 t = *normalRow * float3( 2, 2, 2) + float3(-1, -1, 0);
|
|
float3 u = *detailRow * float3(-2, -2, 2) + float3( 1, 1, -1);
|
|
float3 r = normalize(t * dot(t, u) - u * t.z);
|
|
|
|
*outputRow = r * 0.5 + 0.5;
|
|
}
|
|
}
|
|
}
|