This commit is contained in:
Romain Guy
2018-08-17 12:06:23 -07:00
16 changed files with 263 additions and 226 deletions

View File

@@ -214,8 +214,7 @@ std::unique_ptr<uint8_t[]> fromLinearTosRGB(const LinearImage& image) {
T* d = reinterpret_cast<T*>(dst.get());
for (size_t y = 0; y < h; ++y) {
for (size_t x = 0; x < w; ++x, d += 3) {
float3 const* src = reinterpret_cast<float3 const*>(
image.getPixelRef((uint32_t) x, (uint32_t) y));
auto src = image.get<float3>((uint32_t) x, (uint32_t) y);
float3 l(linearTosRGB(saturate(*src)) * std::numeric_limits<T>::max());
for (size_t i = 0; i < 3; i++) {
d[i] = T(l[i]);
@@ -238,8 +237,7 @@ std::unique_ptr<uint8_t[]> fromLinearToRGB(const LinearImage& image) {
T* d = reinterpret_cast<T*>(dst.get());
for (size_t y = 0; y < h; ++y) {
for (size_t x = 0; x < w; ++x, d += 3) {
float3 const* src = reinterpret_cast<float3 const*>(
image.getPixelRef((uint32_t) x, (uint32_t) y));
auto src = image.get<float3>((uint32_t) x, (uint32_t) y);
float3 l(saturate(*src) * std::numeric_limits<T>::max());
for (size_t i = 0; i < 3; i++) {
d[i] = T(l[i]);
@@ -262,8 +260,7 @@ std::unique_ptr<uint8_t[]> fromLinearToRGBM(const LinearImage& image) {
T* d = reinterpret_cast<T*>(dst.get());
for (size_t y = 0; y < h; ++y) {
for (size_t x = 0; x < w; ++x, d += 4) {
float3 const* src = reinterpret_cast<float3 const*>(
image.getPixelRef((uint32_t) x, (uint32_t) y));
auto src = image.get<float3>((uint32_t) x, (uint32_t) y);
float4 l(linearToRGBM(*src) * std::numeric_limits<T>::max());
for (size_t i = 0; i < 4; i++) {
d[i] = T(l[i]);
@@ -273,6 +270,25 @@ std::unique_ptr<uint8_t[]> fromLinearToRGBM(const LinearImage& image) {
return dst;
}
// Creates a packed single-channel integer-based image from a floating-point image.
// For example if T is uint8_t, then this performs a transformation from [0,1] to [0,255].
template <typename T>
std::unique_ptr<uint8_t[]> fromLinearToGrayscale(const LinearImage& image) {
const size_t w = image.getWidth();
const size_t h = image.getHeight();
assert(image.getChannels() == 1);
std::unique_ptr<uint8_t[]> dst(new uint8_t[w * h * sizeof(T)]);
T* d = reinterpret_cast<T*>(dst.get());
for (size_t y = 0; y < h; ++y) {
float const* p = image.getPixelRef(0, y);
for (size_t x = 0; x < w; ++x, ++p, ++d) {
const float gray = math::saturate(*p) * std::numeric_limits<T>::max();
d[0] = T(gray);
}
}
return dst;
}
// Constructs a 3-channel LinearImage from an untyped data blob.
// The "proc" lambda converts a single color component into a float.
// The "transform" lambda performs an arbitrary float-to-float transformation.
@@ -280,7 +296,7 @@ template<typename T, typename PROCESS, typename TRANSFORM>
static LinearImage toLinear(size_t w, size_t h, size_t bpr,
const uint8_t* src, PROCESS proc, TRANSFORM transform) {
LinearImage result((uint32_t) w, (uint32_t) h, 3);
math::float3* d = reinterpret_cast<math::float3*>(result.getPixelRef());
auto d = result.get<math::float3>();
for (size_t y = 0; y < h; ++y) {
T const* p = reinterpret_cast<T const*>(src + y * bpr);
for (size_t x = 0; x < w; ++x, p += 3) {
@@ -308,7 +324,7 @@ template<typename T, typename PROCESS, typename TRANSFORM>
static LinearImage toLinearWithAlpha(size_t w, size_t h, size_t bpr,
const uint8_t* src, PROCESS proc, TRANSFORM transform) {
LinearImage result((uint32_t) w, (uint32_t) h, 4);
math::float4* d = reinterpret_cast<math::float4*>(result.getPixelRef());
auto d = result.get<math::float4>();
for (size_t y = 0; y < h; ++y) {
T const* p = reinterpret_cast<T const*>(src + y * bpr);
for (size_t x = 0; x < w; ++x, p += 4) {
@@ -332,7 +348,7 @@ static LinearImage toLinearWithAlpha(size_t w, size_t h, size_t bpr,
// Constructs a 3-channel LinearImage from RGBM data.
inline LinearImage toLinearFromRGBM(math::float4 const* src, uint32_t w, uint32_t h) {
LinearImage result(w, h, 3);
math::float3* dst = reinterpret_cast<math::float3*>(result.getPixelRef());
auto dst = result.get<math::float3>();
for (uint32_t row = 0; row < h; ++row) {
for (uint32_t col = 0; col < w; ++col, ++src, ++dst) {
*dst = RGBMtoLinear(*src);

View File

@@ -38,6 +38,9 @@ LinearImage verticalFlip(const LinearImage& image);
// Transforms normals (components live in [-1,+1]) into colors (components live in [0,+1]).
LinearImage vectorsToColors(const LinearImage& image);
// Creates a single-channel image by extracting the selected channel.
LinearImage extractChannel(const LinearImage& image, uint32_t channel);
// Constructs a multi-channel image by copying data from a sequence of single-channel images.
LinearImage combineChannels(std::initializer_list<LinearImage> images);
LinearImage combineChannels(LinearImage const* img, size_t count);

View File

@@ -17,6 +17,7 @@
#ifndef IMAGE_LINEARIMAGE_H
#define IMAGE_LINEARIMAGE_H
#include <cassert>
#include <cstdint>
/**
@@ -64,11 +65,13 @@ public:
* Gets a pointer to the underlying pixel data.
*/
float* getPixelRef() { return mData; }
template<typename T> T* get() { return reinterpret_cast<T*>(mData); }
/**
* Gets a pointer to immutable pixel data.
*/
float const* getPixelRef() const { return mData; }
template<typename T> T const* get() const { return reinterpret_cast<T const*>(mData); }
/**
* Gets a pointer to the pixel data at the given column and row. (not bounds checked)
@@ -77,6 +80,11 @@ public:
return mData + (column + row * mWidth) * mChannels;
}
template<typename T>
T* get(uint32_t column, uint32_t row) {
return reinterpret_cast<T*>(getPixelRef(column, row));
}
/**
* Gets a pointer to the immutable pixel data at the given column and row. (not bounds checked)
*/
@@ -84,10 +92,16 @@ public:
return mData + (column + row * mWidth) * mChannels;
}
template<typename T>
T const* get(uint32_t column, uint32_t row) const {
return reinterpret_cast<T const*>(getPixelRef(column, row));
}
uint32_t getWidth() const { return mWidth; }
uint32_t getHeight() const { return mHeight; }
uint32_t getChannels() const { return mChannels; }
void reset() { *this = LinearImage(); }
bool isValid() const { return mData; }
private:

View File

@@ -128,6 +128,19 @@ LinearImage vectorsToColors(const LinearImage& image) {
return result;
}
LinearImage extractChannel(const LinearImage& source, uint32_t channel) {
const uint32_t width = source.getWidth(), height = source.getHeight();
const uint32_t nchan = source.getChannels();
ASSERT_PRECONDITION(channel < nchan, "Channel is out of range.");
LinearImage result(width, height, 1);
auto src = source.getPixelRef();
auto dst = result.getPixelRef();
for (uint32_t n = 0, npixels = width * height; n < npixels; ++n, ++dst, src += nchan) {
dst[0] = src[channel];
}
return result;
}
LinearImage combineChannels(std::initializer_list<LinearImage> images) {
size_t count = images.end() - images.begin();
return combineChannels(images.begin(), count);

View File

@@ -247,7 +247,7 @@ TEST_F(ImageTest, ColorTransformRGB) { // NOLINT
LinearImage img = image::toLinear<uint16_t>(w, h, bpr, data,
[ ](uint16_t v) -> uint16_t { return v; },
sRGBToLinear<math::float3>);
auto pixels = reinterpret_cast<float3*>(img.getPixelRef());
auto pixels = img.get<float3>();
ASSERT_NEAR(pixels[0].x, 0.0f, 0.001f);
ASSERT_NEAR(pixels[0].y, 0.0f, 0.001f);
ASSERT_NEAR(pixels[0].z, 0.0f, 0.001f);

View File

@@ -17,9 +17,10 @@
#ifndef IMAGE_IMAGEDECODER_H_
#define IMAGE_IMAGEDECODER_H_
#include <iosfwd>
#include <string>
#include <image/Image.h>
#include <image/LinearImage.h>
namespace image {
@@ -30,12 +31,12 @@ public:
SRGB
};
static Image decode(std::istream& stream, const std::string& sourceName,
static LinearImage decode(std::istream& stream, const std::string& sourceName,
ColorSpace sourceSpace = ColorSpace::SRGB);
class Decoder {
public:
virtual Image decode() = 0;
virtual LinearImage decode() = 0;
virtual ~Decoder() = default;
ColorSpace getColorSpace() const noexcept {

View File

@@ -20,7 +20,7 @@
#include <iosfwd>
#include <string>
#include <image/Image.h>
#include <image/LinearImage.h>
namespace image {
@@ -43,7 +43,7 @@ public:
};
// the encode function only expects images that store pixels as floats
static void encode(std::ostream& stream, Format format, const Image& image,
static void encode(std::ostream& stream, Format format, const LinearImage& image,
const std::string& compression, const std::string& destName);
static Format chooseFormat(const std::string& name, bool forceLinear = false);
@@ -51,7 +51,7 @@ public:
class Encoder {
public:
virtual void encode(const Image& image) = 0;
virtual void encode(const LinearImage& image) = 0;
virtual ~Encoder() = default;
};
};

View File

@@ -42,6 +42,7 @@
#include <vector>
#include <image/ColorTransform.h>
#include <image/ImageOps.h>
namespace image {
@@ -60,7 +61,7 @@ private:
void init();
// ImageDecoder::Decoder interface
virtual Image decode() override;
virtual LinearImage decode() override;
static void cb_error(png_structp, png_const_charp);
static void cb_stream(png_structp png, png_bytep buffer, png_size_t size);
@@ -89,7 +90,7 @@ private:
HDRDecoder& operator=(const HDRDecoder&) = delete;
// ImageDecoder::Decoder interface
virtual Image decode() override;
virtual LinearImage decode() override;
static const char sigRadiance[];
static const char sigRGBE[];
@@ -112,7 +113,7 @@ private:
PSDDecoder& operator = (const PSDDecoder&) = delete;
// ImageDecoder::Decoder interface
virtual Image decode() override;
virtual LinearImage decode() override;
static const char sig[];
std::istream& mStream;
@@ -134,7 +135,7 @@ private:
EXRDecoder& operator = (const EXRDecoder&) = delete;
// ImageDecoder::Decoder interface
virtual Image decode() override;
virtual LinearImage decode() override;
static const char sig[];
std::istream& mStream;
@@ -144,7 +145,7 @@ private:
// -----------------------------------------------------------------------------------------------
Image ImageDecoder::decode(std::istream& stream, const std::string& sourceName,
LinearImage ImageDecoder::decode(std::istream& stream, const std::string& sourceName,
ColorSpace sourceSpace) {
Format format = Format::NONE;
@@ -168,7 +169,7 @@ Image ImageDecoder::decode(std::istream& stream, const std::string& sourceName,
std::unique_ptr<Decoder> decoder;
switch (format) {
case Format::NONE:
return Image();
return LinearImage();
case Format::PNG:
decoder.reset(PNGDecoder::create(stream));
decoder->setColorSpace(sourceSpace);
@@ -265,7 +266,7 @@ static Image toLinearWithAlphaDeprecated(size_t w, size_t h, size_t bpr,
return Image(std::move(dst), w, h, w * sizeof(math::float4), sizeof(math::float4), 4);
}
Image PNGDecoder::decode() {
LinearImage PNGDecoder::decode() {
std::unique_ptr<uint8_t[]> imageData;
try {
mInfo = png_create_info_struct(mPNG);
@@ -304,22 +305,22 @@ Image PNGDecoder::decode() {
if (colorType == PNG_COLOR_TYPE_RGBA) {
if (getColorSpace() == ImageDecoder::ColorSpace::SRGB) {
return toLinearWithAlphaDeprecated<uint16_t>(width, height, rowBytes, imageData,
return toLinearWithAlpha<uint16_t>(width, height, rowBytes, imageData,
[ ](uint16_t v) -> uint16_t { return ntohs(v); },
sRGBToLinear<math::float4>);
} else {
return toLinearWithAlphaDeprecated<uint16_t>(width, height, rowBytes, imageData,
return toLinearWithAlpha<uint16_t>(width, height, rowBytes, imageData,
[ ](uint16_t v) -> uint16_t { return ntohs(v); },
[ ](const math::float4& color) -> math::float4 { return color; });
}
} else {
// Convert to linear float (PNG 16 stores data in network order (big endian).
if (getColorSpace() == ImageDecoder::ColorSpace::SRGB) {
return toLinearDeprecated<uint16_t>(width, height, rowBytes, imageData,
return toLinear<uint16_t>(width, height, rowBytes, imageData,
[ ](uint16_t v) -> uint16_t { return ntohs(v); },
sRGBToLinear<math::float3>);
} else {
return toLinearDeprecated<uint16_t>(width, height, rowBytes, imageData,
return toLinear<uint16_t>(width, height, rowBytes, imageData,
[ ](uint16_t v) -> uint16_t { return ntohs(v); },
[ ](const math::float3& color) -> math::float3 { return color; });
}
@@ -330,7 +331,7 @@ Image PNGDecoder::decode() {
mStream.seekg(mStreamStartPos);
imageData.release();
}
return Image();
return LinearImage();
}
void PNGDecoder::cb_stream(png_structp png, png_bytep buffer, png_size_t size) {
@@ -375,7 +376,7 @@ HDRDecoder::HDRDecoder(std::istream& stream)
HDRDecoder::~HDRDecoder() = default;
Image HDRDecoder::decode() {
LinearImage HDRDecoder::decode() {
try {
float gamma;
float exposure;
@@ -398,13 +399,11 @@ Image HDRDecoder::decode() {
} while (true);
}
std::unique_ptr<uint8_t[]> data(new uint8_t[width * height * sizeof(math::float3)]);
Image image(std::move(data), width, height, width*sizeof(math::float3), sizeof(math::float3));
LinearImage image(width, height, 3);
uint32_t flags = 0;
if (sx == '-') flags |= Image::FLIP_X;
if (sy == '+') flags |= Image::FLIP_Y;
image.flip(flags);
if (sx == '-') image = horizontalFlip(image);
if (sy == '+') image = verticalFlip(image);
uint16_t w;
uint16_t magic;
@@ -447,7 +446,7 @@ Image HDRDecoder::decode() {
uint8_t const* g = &rgbe[width];
uint8_t const* b = &rgbe[2*width];
uint8_t const* e = &rgbe[3*width];
math::float3* i = static_cast<math::float3*>(image.getPixelRef(0, y));
math::float3* i = reinterpret_cast<math::float3*>(image.getPixelRef(0, y));
// (rgb/256) * 2^(e-128)
for (size_t x=0 ; x<width ; x++, r++, g++, b++, e++) {
math::float3 v(r[0], g[0], b[0]);
@@ -461,7 +460,7 @@ Image HDRDecoder::decode() {
std::cerr << "Runtime error while decoding HDR: " << e.what() << std::endl;
mStream.seekg(mStreamStartPos);
}
return Image();
return LinearImage();
}
// -----------------------------------------------------------------------------------------------
@@ -483,7 +482,7 @@ PSDDecoder::PSDDecoder(std::istream& stream)
PSDDecoder::~PSDDecoder() = default;
Image PSDDecoder::decode() {
LinearImage PSDDecoder::decode() {
#pragma pack(push, 1)
// IMPORTANT NOTE: PSD files use big endian storage
struct Header {
@@ -542,15 +541,13 @@ Image PSDDecoder::decode() {
throw std::runtime_error("compressed images are not supported");
}
std::unique_ptr<uint8_t[]> data(new uint8_t[width * height * sizeof(math::float3)]);
Image image(std::move(data), width, height,
width * sizeof(math::float3), sizeof(math::float3));
LinearImage image(width, height, 3);
if (depth == 32) {
for (size_t i = 0; i < 3; i++) {
for (size_t y = 0; y < height; y++) {
for (size_t x = 0; x < width; x++) {
math::float3& pixel = *static_cast<math::float3*>(image.getPixelRef(x, y));
math::float3& pixel = *reinterpret_cast<math::float3*>(image.getPixelRef(x, y));
pixel[i] = read32(mStream);
}
}
@@ -559,7 +556,7 @@ Image PSDDecoder::decode() {
for (size_t i = 0; i < 3; i++) {
for (size_t y = 0; y < height; y++) {
for (size_t x = 0; x < width; x++) {
math::float3& pixel = *static_cast<math::float3*>(image.getPixelRef(x, y));
math::float3& pixel = *reinterpret_cast<math::float3*>(image.getPixelRef(x, y));
pixel[i] = read16(mStream);
}
}
@@ -573,7 +570,7 @@ Image PSDDecoder::decode() {
mStream.seekg(mStreamStartPos);
}
return Image();
return LinearImage();
}
// -----------------------------------------------------------------------------------------------
@@ -595,7 +592,7 @@ EXRDecoder::EXRDecoder(std::istream& stream, const std::string& sourceName)
EXRDecoder::~EXRDecoder() = default;
Image EXRDecoder::decode() {
LinearImage EXRDecoder::decode() {
try {
// copy the EXR data in memory
std::vector<unsigned char> src;
@@ -614,19 +611,17 @@ Image EXRDecoder::decode() {
if (ret != TINYEXR_SUCCESS) {
std::cerr << "Could not decode OpenEXR: " << error << std::endl;
mStream.seekg(mStreamStartPos);
return Image();
return LinearImage();
}
src.resize(0);
std::unique_ptr<uint8_t[]> data(new uint8_t[width * height * sizeof(math::float3)]);
Image image(std::move(data), static_cast<size_t>(width), static_cast<size_t>(height),
width * sizeof(math::float3), sizeof(math::float3));
LinearImage image(width, height, 3);
size_t i = 0;
for (size_t y = 0; y < height; y++) {
for (size_t x = 0; x < width; x++) {
math::float3& pixel = *static_cast<math::float3*>(image.getPixelRef(x, y));
math::float3& pixel = *reinterpret_cast<math::float3*>(image.getPixelRef(x, y));
pixel.r = rgba[i++];
pixel.g = rgba[i++];
pixel.b = rgba[i++];
@@ -642,7 +637,7 @@ Image EXRDecoder::decode() {
mStream.seekg(mStreamStartPos);
}
return Image();
return LinearImage();
}
} // namespace image

View File

@@ -26,7 +26,7 @@
namespace image {
// TODO: (1) Remove usage of the old Image class. (2) Remove special treatment of 1-channel data.
// TODO: Remove special treatment of 1-channel data.
void updateOrCompare(LinearImage limgResult, const utils::Path& fnameGolden,
ComparisonMode mode, float epsilon) {
if (mode == ComparisonMode::SKIP) {
@@ -36,26 +36,15 @@ void updateOrCompare(LinearImage limgResult, const utils::Path& fnameGolden,
// Regenerate the PNG file at the given path.
if (mode == ComparisonMode::UPDATE) {
std::ofstream out(fnameGolden, std::ios::binary | std::ios::trunc);
const size_t width = limgResult.getWidth();
const size_t height = limgResult.getHeight();
const size_t nchan = limgResult.getChannels();
const size_t bpp = nchan * sizeof(float), bpr = width * bpp, nbytes = bpr * height;
std::unique_ptr<uint8_t[]> data(new uint8_t[nbytes]);
auto format = ImageEncoder::Format::PNG_LINEAR;
if (fnameGolden.getExtension() == "rgbm" && nchan == 3) {
if (fnameGolden.getExtension() == "rgbm") {
format = ImageEncoder::Format::RGBM;
}
if (nchan != 1) {
memcpy(data.get(), limgResult.getPixelRef(), nbytes);
Image im(std::move(data), width, height, bpr, bpp, nchan);
ImageEncoder::encode(out, format, im, "", fnameGolden);
if (limgResult.getChannels() != 1) {
ImageEncoder::encode(out, format, limgResult, "", fnameGolden);
} else {
auto limg2 = combineChannels({limgResult, limgResult, limgResult});
memcpy(data.get(), limg2.getPixelRef(), nbytes);
Image im(std::move(data), width, height, bpr, bpp, nchan);
ImageEncoder::encode(out, format, im, "", fnameGolden);
ImageEncoder::encode(out, format, limg2, "", fnameGolden);
}
return;
}
@@ -63,20 +52,13 @@ void updateOrCompare(LinearImage limgResult, const utils::Path& fnameGolden,
// Load the PNG file at the given path.
std::ifstream in(fnameGolden, std::ios::binary);
ASSERT_PRECONDITION(in, "Unable to open: %s", fnameGolden.c_str());
Image imgGolden = ImageDecoder::decode(in, fnameGolden, ImageDecoder::ColorSpace::LINEAR);
const size_t width = imgGolden.getWidth(), height = imgGolden.getHeight();
const size_t nchan = imgGolden.getChannelsCount();
LinearImage limgGolden = ImageDecoder::decode(in, fnameGolden, ImageDecoder::ColorSpace::LINEAR);
// Convert 4-channel RGBM into proper RGB.
LinearImage limgGolden;
if (fnameGolden.getExtension() == "rgbm" && nchan == 4) {
if (fnameGolden.getExtension() == "rgbm" && limgGolden.getChannels() == 4) {
limgGolden = toLinearFromRGBM(
static_cast<math::float4 const*>(imgGolden.getData()),
imgGolden.getWidth(), imgGolden.getHeight());
} else {
limgGolden = LinearImage(width, height, nchan);
memcpy(limgGolden.getPixelRef(), imgGolden.getData(),
width * height * sizeof(float) * nchan);
reinterpret_cast<math::float4 const*>(limgGolden.getPixelRef()),
limgGolden.getWidth(), limgGolden.getHeight());
}
// Expand the result image from L to RGB.

View File

@@ -65,9 +65,9 @@ private:
void init();
// ImageEncoder::Encoder interface
virtual void encode(const Image& image) override;
virtual void encode(const LinearImage& image) override;
int chooseColorType(const Image& image) const;
int chooseColorType(const LinearImage& image) const;
uint32_t getChannelsCount() const;
static void cb_error(png_structp png, png_const_charp error);
@@ -98,7 +98,7 @@ private:
HDREncoder& operator = (const HDREncoder&) = delete;
// ImageEncoder::Encoder interface
virtual void encode(const Image& image) override;
virtual void encode(const LinearImage& image) override;
static void float2rgbe(uint8_t rgbe[4], const math::float3& color);
static size_t countRepeats(uint8_t const* data, size_t length);
@@ -123,7 +123,7 @@ private:
PSDEncoder& operator = (const PSDEncoder&) = delete;
// ImageEncoder::Encoder interface
virtual void encode(const Image& image) override;
virtual void encode(const LinearImage& image) override;
std::ostream& mStream;
std::streampos mStreamStartPos;
@@ -147,7 +147,7 @@ private:
EXREncoder& operator = (const EXREncoder&) = delete;
// ImageEncoder::Encoder interface
virtual void encode(const Image& image) override;
virtual void encode(const LinearImage& image) override;
std::ostream& mStream;
std::streampos mStreamStartPos;
@@ -175,7 +175,7 @@ private:
DDSEncoder& operator = (const DDSEncoder&) = delete;
// ImageEncoder::Encoder interface
virtual void encode(const Image& image) override;
virtual void encode(const LinearImage& image) override;
std::ostream& mStream;
std::streampos mStreamStartPos;
@@ -185,7 +185,7 @@ private:
// ------------------------------------------------------------------------------------------------
void ImageEncoder::encode(std::ostream& stream, Format format, const Image& image,
void ImageEncoder::encode(std::ostream& stream, Format format, const LinearImage& image,
const std::string& compression, const std::string& destName) {
std::unique_ptr<Encoder> encoder;
switch(format) {
@@ -283,13 +283,14 @@ void PNGEncoder::init() {
png_set_write_fn(mPNG, this, cb_stream, NULL);
}
int PNGEncoder::chooseColorType(const Image& image) const {
size_t channels = image.getChannelsCount();
int PNGEncoder::chooseColorType(const LinearImage& image) const {
size_t channels = image.getChannels();
switch (channels) {
case 1:
return PNG_COLOR_TYPE_GRAY;
case 3:
default:
std::cerr << "Warning: strange number of channels in PNG" << std::endl;
case 3:
switch (mFormat) {
case PixelFormat::RGBM:
return PNG_COLOR_TYPE_RGBA;
@@ -308,10 +309,11 @@ uint32_t PNGEncoder::getChannelsCount() const {
}
}
void PNGEncoder::encode(const Image& image) {
size_t srcChannels = image.getChannelsCount();
void PNGEncoder::encode(const LinearImage& image) {
size_t srcChannels = image.getChannels();
if ((mFormat == PixelFormat::RGBM && srcChannels != 3) ||
(srcChannels != 1 && srcChannels != 3)) {
std::cerr << "Cannot encode PNG: " << srcChannels << " channels." << std::endl;
return;
}
@@ -333,22 +335,28 @@ void PNGEncoder::encode(const Image& image) {
png_write_info(mPNG, mInfo);
uint32_t channels = (srcChannels == 1 ? 1 : getChannelsCount());
std::unique_ptr<png_bytep[]> row_pointers(new png_bytep[height]);
std::unique_ptr<uint8_t[]> data;
switch (mFormat) {
case PixelFormat::RGBM:
data = fromLinearToRGBM<uint8_t>(image);
break;
case PixelFormat::sRGB:
case PixelFormat::LINEAR_RGB:
data = fromLinearToRGB<uint8_t>(image);
break;
uint32_t dstChannels;
if (srcChannels == 1) {
dstChannels = 1;
data = fromLinearToGrayscale<uint8_t>(image);
} else {
dstChannels = getChannelsCount();
switch (mFormat) {
case PixelFormat::RGBM:
data = fromLinearToRGBM<uint8_t>(image);
break;
case PixelFormat::sRGB:
case PixelFormat::LINEAR_RGB:
data = fromLinearToRGB<uint8_t>(image);
break;
}
}
for (size_t y = 0; y < height; y++) {
row_pointers[y] = reinterpret_cast<png_bytep>(&data[y * width * channels *
row_pointers[y] = reinterpret_cast<png_bytep>(&data[y * width * dstChannels *
sizeof(uint8_t)]);
}
@@ -465,8 +473,8 @@ void HDREncoder::rle(std::ostream& out, uint8_t const* data, size_t length) {
}
}
void HDREncoder::encode(const Image& image) {
if (image.getChannelsCount() != 3) {
void HDREncoder::encode(const LinearImage& image) {
if (image.getChannels() != 3) {
return;
}
@@ -495,7 +503,7 @@ void HDREncoder::encode(const Image& image) {
for (size_t y=0 ; y<height ; y++) {
// convert one scanline to RGBE
uint8_t p[4];
float3* data = static_cast<float3*>(image.getPixelRef(0, y));
auto data = image.get<float3>(0, y);
for (size_t x=0 ; x<width ; ++x, ++data) {
float2rgbe(p, *data);
r[x] = p[0];
@@ -559,13 +567,13 @@ static inline void write8i(std::ostream& stream, uint8_t v) {
stream.write(reinterpret_cast<const char*>(&v), sizeof(uint8_t));
}
void PSDEncoder::encode(const Image& image) {
void PSDEncoder::encode(const LinearImage& image) {
static const uint16_t kColorModeRGB = 3;
static const uint16_t kCompressionRAW = 0;
// preview mode: 0 = highlight compression, 1 = exposure & gamma
static const uint32_t kToningPreviewExposureGamma = 1;
if (image.getChannelsCount() != 3) {
if (image.getChannels() != 3) {
return;
}
@@ -652,7 +660,7 @@ void PSDEncoder::encode(const Image& image) {
if (depth == 32) {
for (size_t channel = 0; channel < 3; channel++) {
for (size_t y = 0; y < height; y++) {
const float3* data = static_cast<float3*>(image.getPixelRef(0, y));
auto data = image.get<float3>(0, y);
for (size_t x = 0; x < width; x++) {
write32(mStream, (*data)[channel]);
data++;
@@ -662,7 +670,7 @@ void PSDEncoder::encode(const Image& image) {
} else {
for (size_t channel = 0; channel < 3; channel++) {
for (size_t y = 0; y < height; y++) {
const float3* data = static_cast<float3*>(image.getPixelRef(0, y));
auto data = image.get<float3>(0, y);
for (size_t x = 0; x < width; x++) {
write16(mStream, linearTosRGB((*data)[channel]));
data++;
@@ -713,8 +721,8 @@ static int toEXRCompression(const std::string& c) {
throw std::runtime_error("unknown compression scheme " + c);
}
void EXREncoder::encode(const Image& image) {
if (image.getChannelsCount() != 3) {
void EXREncoder::encode(const LinearImage& image) {
if (image.getChannels() != 3) {
return;
}
@@ -738,7 +746,7 @@ void EXREncoder::encode(const Image& image) {
size_t i = 0;
for (size_t y = 0; y < height; y++) {
const float3* data = static_cast<float3*>(image.getPixelRef(0, y));
auto data = image.get<float3>(0, y);
for (size_t x = 0; x < width; x++, data++) {
r[i] = data->r;
g[i] = data->g;
@@ -875,7 +883,7 @@ DDSEncoder::DDSEncoder(std::ostream& stream, const std::string& compression, Pix
DDSEncoder::~DDSEncoder() {
}
static uint32_t chooseBpp(const Image& image, const std::string& compression) {
static uint32_t chooseBpp(const LinearImage& image, const std::string& compression) {
size_t depth = 16;
if (compression == "8") depth = 8;
if (compression == "32") depth = 32;
@@ -888,10 +896,10 @@ static uint32_t chooseBpp(const Image& image, const std::string& compression) {
{ 4, 8, 16 },
};
return formats[image.getChannelsCount() - 1][index];
return formats[image.getChannels() - 1][index];
}
static uint32_t chooseDXGIFormat(const Image& image, const std::string& compression) {
static uint32_t chooseDXGIFormat(const LinearImage& image, const std::string& compression) {
size_t depth = 16;
if (compression == "8") depth = 8;
if (compression == "32") depth = 32;
@@ -904,10 +912,10 @@ static uint32_t chooseDXGIFormat(const Image& image, const std::string& compress
{ DXGI_FORMAT_R8G8B8A8_UINT, DXGI_FORMAT_R16G16B16A16_FLOAT, DXGI_FORMAT_R32G32B32A32_FLOAT },
};
return formats[image.getChannelsCount() - 1][index];
return formats[image.getChannels() - 1][index];
}
void DDSEncoder::encode(const Image& image) {
void DDSEncoder::encode(const LinearImage& image) {
try {
size_t width = image.getWidth();
size_t height = image.getHeight();
@@ -946,7 +954,7 @@ void DDSEncoder::encode(const Image& image) {
switch (mFormat) {
case PixelFormat::sRGB:
for (size_t y = 0; y < height; y++) {
const float* data = static_cast<float*>(image.getPixelRef(0, y));
const float* data = image.getPixelRef(0, y);
for (size_t x = 0; x < width; x++) {
uint8_t b = (uint8_t) (linearTosRGB(saturate(*data)) * 255);
mStream.write((const char*) &b, 1);
@@ -956,7 +964,7 @@ void DDSEncoder::encode(const Image& image) {
break;
case PixelFormat::LINEAR_RGB:
for (size_t y = 0; y < height; y++) {
const float* data = static_cast<float*>(image.getPixelRef(0, y));
const float* data = image.getPixelRef(0, y);
for (size_t x = 0; x < width; x++) {
uint8_t b = (uint8_t) (saturate(*data) * 255);
mStream.write((const char*) &b, 1);
@@ -969,7 +977,7 @@ void DDSEncoder::encode(const Image& image) {
}
case DXGI_FORMAT_R16_FLOAT: {
for (size_t y = 0; y < height; y++) {
const float* data = static_cast<float*>(image.getPixelRef(0, y));
const float* data = image.getPixelRef(0, y);
for (size_t x = 0; x < width; x++) {
math::half p = math::half(*data);
mStream.write((const char*) &p, 2);
@@ -980,7 +988,7 @@ void DDSEncoder::encode(const Image& image) {
}
case DXGI_FORMAT_R32_FLOAT: {
for (size_t y = 0; y < height; y++) {
const float* data = static_cast<float*>(image.getPixelRef(0, y));
const float* data = image.getPixelRef(0, y);
mStream.write((const char*) data, width * sizeof(float));
}
break;
@@ -989,7 +997,7 @@ void DDSEncoder::encode(const Image& image) {
switch (mFormat) {
case PixelFormat::sRGB:
for (size_t y = 0; y < height; y++) {
const float2* data = static_cast<float2*>(image.getPixelRef(0, y));
const float2* data = reinterpret_cast<float2 const*>(image.getPixelRef(0, y));
for (size_t x = 0; x < width; x++) {
uint8_t b;
b = (uint8_t) (linearTosRGB(saturate(data->g)) * 255);
@@ -1002,7 +1010,7 @@ void DDSEncoder::encode(const Image& image) {
break;
case PixelFormat::LINEAR_RGB:
for (size_t y = 0; y < height; y++) {
const float2* data = static_cast<float2*>(image.getPixelRef(0, y));
const float2* data = reinterpret_cast<float2 const*>(image.getPixelRef(0, y));
for (size_t x = 0; x < width; x++) {
uint8_t b;
b = (uint8_t) (saturate(data->g) * 255);
@@ -1018,7 +1026,7 @@ void DDSEncoder::encode(const Image& image) {
}
case DXGI_FORMAT_R16G16_FLOAT: {
for (size_t y = 0; y < height; y++) {
const float2* data = static_cast<float2*>(image.getPixelRef(0, y));
const float2* data = reinterpret_cast<float2 const*>(image.getPixelRef(0, y));
for (size_t x = 0; x < width; x++) {
half2 p = half2(*data);
mStream.write((const char*) &p, sizeof(p));
@@ -1029,7 +1037,7 @@ void DDSEncoder::encode(const Image& image) {
}
case DXGI_FORMAT_R32G32_FLOAT: {
for (size_t y = 0; y < height; y++) {
const float2* data = static_cast<float2*>(image.getPixelRef(0, y));
const float2* data = reinterpret_cast<float2 const*>(image.getPixelRef(0, y));
mStream.write((const char*) data, width * sizeof(float2));
}
break;
@@ -1038,7 +1046,7 @@ void DDSEncoder::encode(const Image& image) {
switch (mFormat) {
case PixelFormat::sRGB:
for (size_t y = 0; y < height; y++) {
const float3* data = static_cast<float3*>(image.getPixelRef(0, y));
auto data = image.get<float3>(0, y);
for (size_t x = 0; x < width; x++) {
uint8_t r = (uint8_t) (linearTosRGB(saturate(data->r)) * 255);
uint8_t g = (uint8_t) (linearTosRGB(saturate(data->g)) * 255);
@@ -1052,7 +1060,7 @@ void DDSEncoder::encode(const Image& image) {
break;
case PixelFormat::LINEAR_RGB:
for (size_t y = 0; y < height; y++) {
const float3* data = static_cast<float3*>(image.getPixelRef(0, y));
auto data = image.get<float3>(0, y);
for (size_t x = 0; x < width; x++) {
uint8_t r = (uint8_t) (saturate(data->r) * 255);
uint8_t g = (uint8_t) (saturate(data->g) * 255);
@@ -1068,7 +1076,7 @@ void DDSEncoder::encode(const Image& image) {
}
case DXGI_FORMAT_R16G16B16A16_FLOAT: {
for (size_t y = 0; y < height; y++) {
const float3* data = static_cast<float3*>(image.getPixelRef(0, y));
auto data = image.get<float3>(0, y);
for (size_t x = 0; x < width; x++) {
half4 p = half4(half3(*data), 1);
mStream.write((const char*) &p, sizeof(ushort4));
@@ -1079,7 +1087,7 @@ void DDSEncoder::encode(const Image& image) {
}
case DXGI_FORMAT_R32G32B32A32_FLOAT: {
for (size_t y = 0; y < height; y++) {
const float3* data = static_cast<float3*>(image.getPixelRef(0, y));
auto data = image.get<float3>(0, y);
for (size_t x = 0; x < width; x++) {
float4 p = float4(3.0f, 3.0f, 3.0f, 1.0f);
mStream.write((const char*) &p, sizeof(float4));

View File

@@ -336,9 +336,8 @@ static void setup(Engine* engine, View* view, Scene* scene) {
}
template<typename T>
static Image toLinear(size_t w, size_t h, size_t bpr, const uint8_t* src) {
std::unique_ptr<uint8_t[]> buffer(new uint8_t[w * h * 3 * sizeof(float3)]);
Image result(std::move(buffer), w, h, w * sizeof(float3), sizeof(float3));
static LinearImage toLinear(size_t w, size_t h, size_t bpr, const uint8_t* src) {
LinearImage result(w, h, 3);
math::float3* d = reinterpret_cast<math::float3*>(result.getPixelRef(0, 0));
for (size_t y = 0; y < h; ++y) {
T const* p = reinterpret_cast<T const*>(src + y * bpr);
@@ -382,7 +381,7 @@ static void postRender(Engine*, View* view, Scene*, Renderer* renderer) {
CaptureState* state = static_cast<CaptureState*>(user);
const Viewport& v = state->view->getViewport();
Image image(toLinear<uint8_t>(v.width, v.height, v.width * 3,
LinearImage image(toLinear<uint8_t>(v.width, v.height, v.width * 3,
static_cast<uint8_t*>(buffer)));
int digits = (int) log10 ((double) g_materialVariantCount) + 1;

View File

@@ -24,6 +24,7 @@
#include <math/scalar.h>
#include <math/vec4.h>
#include <image/Image.h>
#include <imageio/ImageDecoder.h>
#include <imageio/ImageEncoder.h>
@@ -90,6 +91,8 @@ static void extractCubemapFaces(const utils::Path& iname, const Cubemap& cm, con
static void outputSh(std::ostream& out, const std::unique_ptr<math::double3[]>& sh, size_t numBands);
static void outputSpectrum(std::ostream& out, const std::unique_ptr<math::double3[]>& sh,
size_t numBands);
static void saveImage(const std::string& path, ImageEncoder::Format format, const Image& image,
const std::string& compression);
// -----------------------------------------------------------------------------------------------
@@ -393,22 +396,27 @@ int main(int argc, char* argv[]) {
std::cout << "Decoding image..." << std::endl;
}
std::ifstream input_stream(iname.getPath(), std::ios::binary);
Image inputImage = ImageDecoder::decode(input_stream, iname.getPath());
if (!inputImage.isValid()) {
std::cerr << "Unsupported image format!" << std::endl;
exit(0);
LinearImage linputImage = ImageDecoder::decode(input_stream, iname.getPath());
if (!linputImage.isValid()) {
std::cerr << "Unable to open image: " << iname.getPath() << std::endl;
exit(1);
}
if (inputImage.getChannelsCount() != 3) {
if (linputImage.getChannels() != 3) {
std::cerr << "Input image must be RGB (3 channels)! This image has "
<< inputImage.getChannelsCount() << " channels." << std::endl;
exit(0);
<< linputImage.getChannels() << " channels." << std::endl;
exit(1);
}
// Convert from LinearImage to the deprecated Image object which is used throughout cmgen.
std::unique_ptr<uint8_t[]> buf(new uint8_t[
linputImage.getWidth() * linputImage.getHeight() * sizeof(float3)]);
const size_t width = linputImage.getWidth(), height = linputImage.getHeight();
const size_t bpp = sizeof(float) * 3, bpr = bpp * width;
memcpy(buf.get(), linputImage.getPixelRef(), height * bpr);
Image inputImage(std::move(buf), width, height, bpr, bpp);
CubemapUtils::clamp(inputImage);
size_t width = inputImage.getWidth();
size_t height = inputImage.getHeight();
if ((isPOT(width) && (width * 3 == height * 4)) ||
(isPOT(height) && (height * 3 == width * 4))) {
// This is cross cubemap
@@ -588,10 +596,8 @@ void sphericalHarmonics(const utils::Path& iname, const Cubemap& inputCubemap) {
}
if (g_sh_file == ShFile::SH_CROSS) {
std::ofstream outputStream(g_sh_filename, std::ios::binary | std::ios::trunc);
ImageEncoder::encode(outputStream,
ImageEncoder::chooseFormat(g_sh_filename.getName()),
image, g_compression, g_sh_filename);
saveImage(g_sh_filename, ImageEncoder::chooseFormat(g_sh_filename.getName()),
image, g_compression);
}
if (g_sh_file == ShFile::SH_TEXT) {
std::ofstream outputStream(g_sh_filename, std::ios::trunc);
@@ -609,9 +615,7 @@ void sphericalHarmonics(const utils::Path& iname, const Cubemap& inputCubemap) {
std::string basename = iname.getNameWithoutExtension();
utils::Path filePath =
outputDir + (basename + "_sh" + (g_sh_irradiance ? "_i" : "_r") + ".png");
std::ofstream outputStream(filePath, std::ios::binary | std::ios::trunc);
ImageEncoder::encode(outputStream, ImageEncoder::Format::PNG, image, "",
filePath.getPath());
saveImage(filePath, ImageEncoder::Format::PNG, image, "");
}
{ // save a file with the "other one" (irradiance or radiance)
@@ -620,9 +624,7 @@ void sphericalHarmonics(const utils::Path& iname, const Cubemap& inputCubemap) {
std::string basename = iname.getNameWithoutExtension();
utils::Path filePath =
outputDir + (basename + "_sh" + (!g_sh_irradiance ? "_i" : "_r") + ".png");
std::ofstream outputStream(filePath, std::ios::binary | std::ios::trunc);
ImageEncoder::encode(outputStream, ImageEncoder::Format::PNG, image, "",
filePath.getPath());
saveImage(filePath, ImageEncoder::Format::PNG, image, "");
}
}
}
@@ -677,10 +679,7 @@ void iblMipmapPrefilter(const utils::Path& iname,
std::string ext = ImageEncoder::chooseExtension(debug_format);
std::string basename = iname.getNameWithoutExtension();
utils::Path filePath = outputDir + (basename + "_is_m" + (std::to_string(level) + ext));
std::ofstream outputStream(filePath, std::ios::binary | std::ios::trunc);
ImageEncoder::encode(outputStream, debug_format, img,
g_compression, filePath.getPath());
saveImage(filePath, debug_format, img, g_compression);
}
std::string ext = ImageEncoder::chooseExtension(g_format);
@@ -688,9 +687,7 @@ void iblMipmapPrefilter(const utils::Path& iname,
Cubemap::Face face = (Cubemap::Face)i;
std::string filename = outputDir
+ ("is_m" + std::to_string(level) + "_" + CubemapUtils::getFaceName(face) + ext);
std::ofstream outputStream(filename, std::ios::binary | std::ios::trunc);
ImageEncoder::encode(outputStream, g_format, dst.getImageForFace(face),
g_compression, filename);
saveImage(filename, g_format, dst.getImageForFace(face), g_compression);
}
}
}
@@ -737,10 +734,7 @@ void iblRoughnessPrefilter(const utils::Path& iname,
std::string ext = ImageEncoder::chooseExtension(debug_format);
std::string basename = iname.getNameWithoutExtension();
utils::Path filePath = outputDir + (basename + "_roughness_m" + (std::to_string(level) + ext));
std::ofstream outputStream(filePath, std::ios::binary | std::ios::trunc);
ImageEncoder::encode(outputStream, debug_format, image,
g_compression, filePath.getPath());
saveImage(filePath, debug_format, image, g_compression);
}
std::string ext = ImageEncoder::chooseExtension(g_format);
@@ -748,9 +742,7 @@ void iblRoughnessPrefilter(const utils::Path& iname,
Cubemap::Face face = (Cubemap::Face) j;
std::string filename = outputDir
+ ("m" + std::to_string(level) + "_" + CubemapUtils::getFaceName(face) + ext);
std::ofstream outputStream(filename, std::ios::binary | std::ios::trunc);
ImageEncoder::encode(outputStream, g_format, dst.getImageForFace(face),
g_compression, filename);
saveImage(filename, g_format, dst.getImageForFace(face), g_compression);
}
}
}
@@ -804,9 +796,8 @@ void iblLutDfg(const utils::Path& filename, size_t size, bool multiscatter) {
outputStream.flush();
outputStream.close();
} else {
std::ofstream outputStream(filename, std::ios::binary | std::ios::trunc);
ImageEncoder::Format format = ImageEncoder::chooseFormat(filename.getName(), true);
ImageEncoder::encode(outputStream, format, image, g_compression, filename.getPath());
saveImage(filename, format, image, g_compression);
}
}
@@ -819,8 +810,23 @@ void extractCubemapFaces(const utils::Path& iname, const Cubemap& cm, const util
for (size_t i=0 ; i<6 ; i++) {
Cubemap::Face face = (Cubemap::Face)i;
std::string filename(outputDir + (CubemapUtils::getFaceName(face) + ext));
std::ofstream outputStream(filename, std::ios::binary | std::ios::trunc);
ImageEncoder::encode(outputStream, g_format, cm.getImageForFace(face),
g_compression, filename);
saveImage(filename, g_format, cm.getImageForFace(face), g_compression);
}
}
static void saveImage(const std::string& path, ImageEncoder::Format format, const Image& image,
const std::string& compression) {
std::ofstream outputStream(path, std::ios::binary | std::ios::trunc);
LinearImage linearImage(image.getWidth(), image.getHeight(), 3);
// Copy row by row since the image has padding.
assert(image.getBytesPerPixel() == 12);
const size_t w = image.getWidth(), h = image.getHeight();
for (size_t row = 0; row < h; ++row) {
float* dst = linearImage.getPixelRef(0, row);
float const* src = static_cast<float const*>(image.getPixelRef(0, row));
memcpy(dst, src, w * 12);
}
ImageEncoder::encode(outputStream, format, linearImage, compression, path);
}

View File

@@ -100,12 +100,12 @@ static void processEnvMap(string inputPath, string resultPath, string goldenPath
std::cout << "Reading result image from " << resultPath << std::endl;
checkFileExistence(resultPath);
std::ifstream resultStream(resultPath.c_str(), std::ios::binary);
Image resultImage = ImageDecoder::decode(resultStream, resultPath);
LinearImage resultImage = ImageDecoder::decode(resultStream, resultPath);
ASSERT_EQ(resultImage.isValid(), true);
ASSERT_EQ(resultImage.getChannelsCount(), 4);
ASSERT_EQ(resultImage.getChannels(), 4);
LinearImage resultLImage = toLinearFromRGBM(
static_cast<math::float4 const*>(resultImage.getData()),
(uint32_t) resultImage.getWidth(), (uint32_t) resultImage.getHeight());
reinterpret_cast<math::float4 const*>(resultImage.getPixelRef()),
resultImage.getWidth(), resultImage.getHeight());
std::cout << "Golden image is at " << goldenPath << std::endl;
updateOrCompare(resultLImage, goldenPath, g_comparisonMode, 0.01f);

View File

@@ -34,7 +34,7 @@ static image::ImageEncoder::Format g_format = image::ImageEncoder::Format::PNG;
static bool g_formatSpecified = false;
static std::string g_compression = "";
static void blend(const Image& normal, const Image& detail, Image& output);
static void blend(const LinearImage& normal, const LinearImage& detail, LinearImage output);
static void printUsage(const char* name) {
std::string execName(utils::Path(name).getName());
@@ -159,7 +159,7 @@ int main(int argc, char* argv[]) {
// make sure we load the normal maps as linear data
std::ifstream inputStream(normalMap, std::ios::binary);
Image normalImage = ImageDecoder::decode(inputStream, normalMap,
LinearImage normalImage = ImageDecoder::decode(inputStream, normalMap,
ImageDecoder::ColorSpace::LINEAR);
if (!normalImage.isValid()) {
std::cerr << "The input normal map is invalid: " << normalMap << std::endl;
@@ -168,7 +168,7 @@ int main(int argc, char* argv[]) {
inputStream.close();
inputStream.open(detailMap, std::ios::binary);
Image detailImage = ImageDecoder::decode(inputStream, detailMap,
LinearImage detailImage = ImageDecoder::decode(inputStream, detailMap,
ImageDecoder::ColorSpace::LINEAR);
if (!detailImage.isValid()) {
std::cerr << "The detail normal map is invalid: " << detailMap << std::endl;
@@ -189,9 +189,7 @@ int main(int argc, char* argv[]) {
size_t width = normalImage.getWidth();
size_t height = normalImage.getHeight();
std::unique_ptr<uint8_t[]> buffer(new uint8_t[width * height * sizeof(float3)]);
Image image(std::move(buffer), width, height, width * sizeof(float3), sizeof(float3));
LinearImage image(width, height, 3);
blend(normalImage, detailImage, image);
@@ -214,14 +212,14 @@ int main(int argc, char* argv[]) {
}
}
void blend(const Image& normal, const Image& detail, Image& output) {
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++) {
float3* normalRow = static_cast<float3*>(normal.getPixelRef(0, y));
float3* detailRow = static_cast<float3*>(detail.getPixelRef(0, y));
float3* outputRow = static_cast<float3*>(output.getPixelRef(0, 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

View File

@@ -22,6 +22,8 @@
#include <math/vec3.h>
#include <image/ImageOps.h>
#include <imageio/ImageDecoder.h>
#include <imageio/ImageEncoder.h>
@@ -176,7 +178,7 @@ inline bool isPOT(size_t x) {
}
float solveVMF(const float2& pos, const size_t sampleCount, const float roughness,
const Image& normal) {
const LinearImage& normal) {
float3 averageNormal(0.0f);
float2 topLeft(-float(sampleCount) / 2.0f + 0.5f);
@@ -185,8 +187,7 @@ float solveVMF(const float2& pos, const size_t sampleCount, const float roughnes
for (size_t x = 0; x < sampleCount; x++) {
float2 offset(topLeft + float2(x, y));
float2 samplePos(floor(pos + offset) + 0.5f);
float3 sampleNormal = *static_cast<float3*>(
normal.getPixelRef(size_t(samplePos.x), size_t(samplePos.y)));
float3 sampleNormal = *normal.get<float3>(size_t(samplePos.x), size_t(samplePos.y));
sampleNormal = sampleNormal * 2.0f - 1.0f;
averageNormal += normalize(sampleNormal);
@@ -209,13 +210,13 @@ float solveVMF(const float2& pos, const size_t sampleCount, const float roughnes
return std::sqrt(roughness * roughness + (2.0f / kappa));
}
void prefilter(const Image& normal, const size_t mipLevel, Image& output) {
void prefilter(const LinearImage& normal, const size_t mipLevel, LinearImage& output) {
const size_t width = output.getWidth();
const size_t height = output.getHeight();
const size_t sampleCount = 1u << mipLevel;
for (size_t y = 0; y < height; y++) {
auto* outputRow = static_cast<float3*>(output.getPixelRef(0, y));
auto outputRow = output.get<float3>(0, y);
for (size_t x = 0; x < width; x++, outputRow++) {
const float2 uv = (float2(x, y) + 0.5f) / float2(width, height);
const float2 pos = uv * normal.getWidth();
@@ -226,15 +227,16 @@ void prefilter(const Image& normal, const size_t mipLevel, Image& output) {
}
template<bool FIRST_MIP>
void prefilter(const Image& normal, const Image& roughness, const size_t mipLevel, Image& output) {
void prefilter(const LinearImage& normal, const LinearImage& roughness, const size_t mipLevel,
LinearImage& output) {
const size_t width = output.getWidth();
const size_t height = output.getHeight();
const size_t sampleCount = 1u << mipLevel;
for (size_t y = 0; y < height; y++) {
auto* outputRow = static_cast<float3*>(output.getPixelRef(0, y));
auto outputRow = output.get<float3>(0, y);
for (size_t x = 0; x < width; x++, outputRow++) {
const float3* data = static_cast<float3*>(roughness.getPixelRef(x, y));
auto data = roughness.get<float3>(x, y);
if (FIRST_MIP) {
*outputRow = *data;
} else {
@@ -265,7 +267,7 @@ int main(int argc, char* argv[]) {
// make sure we load the normal maps as linear data
std::ifstream inputStream(normalMap, std::ios::binary);
Image normalImage = ImageDecoder::decode(inputStream, normalMap,
LinearImage normalImage = ImageDecoder::decode(inputStream, normalMap,
ImageDecoder::ColorSpace::LINEAR);
inputStream.close();
@@ -281,8 +283,8 @@ int main(int argc, char* argv[]) {
exit(1);
}
Image roughnessImage;
std::vector<Image> mipImages;
LinearImage roughnessImage;
std::vector<LinearImage> mipImages;
bool hasRoughnessMap = false;
if (!g_roughnessMap.isEmpty()) {
@@ -330,13 +332,13 @@ int main(int argc, char* argv[]) {
const size_t height = hasRoughnessMap ? roughnessImage.getHeight() : normalImage.getHeight();
const size_t mipLevels = size_t(std::log2f(width)) + 1;
size_t channels = 3;
bool exportGrayscale = false;
switch (g_format) {
case ImageEncoder::Format::DDS:
case ImageEncoder::Format::DDS_LINEAR:
case ImageEncoder::Format::PNG:
case ImageEncoder::Format::PNG_LINEAR:
channels = 1;
exportGrayscale = true;
break;
default:
break;
@@ -344,23 +346,21 @@ int main(int argc, char* argv[]) {
if (hasRoughnessMap) {
mipImages.push_back(std::move(roughnessImage));
Image* prevMip = &mipImages.at(0);
LinearImage* prevMip = &mipImages.at(0);
for (size_t i = 1; i <= mipLevels; i++) {
const size_t w = width >> i;
const size_t h = height >> i;
const size_t size = w * h * sizeof(float3);
std::unique_ptr<uint8_t[]> buffer(new uint8_t[size]);
Image image(std::move(buffer), w, h, w * sizeof(float3), sizeof(float3), channels);
LinearImage image(w, h, 3);
for (size_t y = 0; y < h; y++) {
auto* dst = static_cast<float3*>(image.getPixelRef(0, y));
auto dst = image.get<float3>(0, y);
for (size_t x = 0; x < w; x++, dst++) {
float3 aa = *static_cast<float3*>(prevMip->getPixelRef(x * 2, y * 2));
float3 ba = *static_cast<float3*>(prevMip->getPixelRef(x * 2 + 1, y * 2));
float3 ab = *static_cast<float3*>(prevMip->getPixelRef(x * 2, y * 2 + 1));
float3 bb = *static_cast<float3*>(prevMip->getPixelRef(x * 2 + 1, y * 2 + 1));
float3 aa = *prevMip->get<float3>(x * 2, y * 2);
float3 ba = *prevMip->get<float3>(x * 2 + 1, y * 2);
float3 ab = *prevMip->get<float3>(x * 2, y * 2 + 1);
float3 bb = *prevMip->get<float3>(x * 2 + 1, y * 2 + 1);
*dst = (aa + ba + ab + bb) / 4.0f;
}
}
@@ -376,23 +376,22 @@ int main(int argc, char* argv[]) {
JobSystem::Job* parent = js.createJob();
for (size_t i = 0; i < mipLevels; i++) {
JobSystem::Job* mip = jobs::createJob(js, parent,
[&normalImage, &mipImages, outputMap, i, width, height, channels, hasRoughnessMap]() {
[&normalImage, &mipImages, outputMap, i, width, height, exportGrayscale, hasRoughnessMap]() {
const size_t w = width >> i;
const size_t h = height >> i;
const size_t size = w * h * sizeof(float3);
std::unique_ptr<uint8_t[]> buffer(new uint8_t[size]);
Image image(std::move(buffer), w, h, w * sizeof(float3), sizeof(float3), channels);
LinearImage image(w, h, 3);
if (i == 0) {
if (hasRoughnessMap) {
if (mipImages.at(0).getBytesPerRow() == image.getBytesPerRow()) {
memcpy(image.getData(), mipImages.at(0).getData(), size);
if (mipImages.at(0).getWidth() == image.getWidth()) {
const size_t size = image.getWidth() * image.getHeight() * 12;
memcpy(image.getPixelRef(), mipImages.at(0).getPixelRef(), size);
} else {
prefilter<true>(normalImage, mipImages.at(0), 0, image);
}
} else {
std::fill_n(static_cast<float3*>(image.getData()), w * h, float3(g_roughness));
std::fill_n(image.get<float3>(), w * h, float3(g_roughness));
}
} else {
if (hasRoughnessMap) {
@@ -410,13 +409,16 @@ int main(int argc, char* argv[]) {
std::ofstream outputStream(out, std::ios::binary | std::ios::trunc);
if (!outputStream.good()) {
std::cerr << "The output file cannot be opened: " << out << std::endl;
} else {
ImageEncoder::encode(outputStream, g_format, image, g_compression, out.getPath());
outputStream.close();
if (!outputStream.good()) {
std::cerr << "An error occurred while writing the output file: " << out <<
std::endl;
}
return;
}
if (exportGrayscale) {
image = extractChannel(image, 0);
}
ImageEncoder::encode(outputStream, g_format, image, g_compression, out.getPath());
outputStream.close();
if (!outputStream.good()) {
std::cerr << "An error occurred while writing the output file: " << out <<
std::endl;
}
});
js.run(mip);

View File

@@ -23,6 +23,7 @@
#include <math/scalar.h>
#include <math/vec3.h>
#include <image/LinearImage.h>
#include <imageio/ImageEncoder.h>
#include <utils/JobSystem.h>
@@ -83,7 +84,7 @@ static float angleBetween(float thetav, float phiv, float theta, float phi) {
return acosf(cosGamma);
}
static void generateSky(const Image& image) {
static void generateSky(LinearImage image) {
printf("Sky parameters\n");
printf(" Elevation: %.2f°\n", g_elevation * 180.0 * M_1_PI);
printf(" Azimuth: %.2f°\n", g_azimuth * 180.0 * M_1_PI);
@@ -121,7 +122,7 @@ static void generateSky(const Image& image) {
size_t y0 = size_t(d);
for (size_t y = y0; y < y0 + c; y++) {
float3* UTILS_RESTRICT data = static_cast<float3*>(image.getPixelRef(0, y));
float3* UTILS_RESTRICT data = image.get<float3>(0, y);
float v = (y + 0.5f) / h;
float theta = float(M_PI * v);
@@ -193,7 +194,7 @@ static void generateSky(const Image& image) {
const size_t h = image.getHeight();
for (size_t y = 0; y < h; y++) {
float3* UTILS_RESTRICT data = static_cast<float3*>(image.getPixelRef(0, y));
float3* UTILS_RESTRICT data = image.get<float3>(0, y);
for (size_t x = 0; x < w; x++, data++) {
*data *= hdrScale;
if (g_tonemap) {
@@ -389,10 +390,9 @@ int main(int argc, char* argv[]) {
const uint32_t height = std::max(1u, g_outputWidth >> 1);
// allocate map
const size_t size = width * height * sizeof(float3);
std::unique_ptr<uint8_t[]> buffer(new uint8_t[size]);
Image image(std::move(buffer), width, height, width * sizeof(float3), sizeof(float3), 3);
LinearImage image(width, height, 3);
// render the sky and sun disk
generateSky(image);
// write the environment map to disk