Nori  23
bitmap.cpp
1 /*
2  This file is part of Nori, a simple educational ray tracer
3 
4  Copyright (c) 2015 by Wenzel Jakob
5 
6  Nori is free software; you can redistribute it and/or modify
7  it under the terms of the GNU General Public License Version 3
8  as published by the Free Software Foundation.
9 
10  Nori is distributed in the hope that it will be useful,
11  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  GNU General Public License for more details.
14 
15  You should have received a copy of the GNU General Public License
16  along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18 
19 #include <nori/bitmap.h>
20 #include <ImfInputFile.h>
21 #include <ImfOutputFile.h>
22 #include <ImfChannelList.h>
23 #include <ImfStringAttribute.h>
24 #include <ImfVersion.h>
25 #include <ImfIO.h>
26 
27 #include <memory>
28 #define STB_IMAGE_WRITE_IMPLEMENTATION
29 #include <stb_image_write.h>
30 
31 NORI_NAMESPACE_BEGIN
32 
33 Bitmap::Bitmap(const std::string &filename) {
34  Imf::InputFile file(filename.c_str());
35  const Imf::Header &header = file.header();
36  const Imf::ChannelList &channels = header.channels();
37 
38  Imath::Box2i dw = file.header().dataWindow();
39  resize(dw.max.y - dw.min.y + 1, dw.max.x - dw.min.x + 1);
40 
41  cout << "Reading a " << cols() << "x" << rows() << " OpenEXR file from \""
42  << filename << "\"" << endl;
43 
44  const char *ch_r = nullptr, *ch_g = nullptr, *ch_b = nullptr;
45  for (Imf::ChannelList::ConstIterator it = channels.begin(); it != channels.end(); ++it) {
46  std::string name = toLower(it.name());
47 
48  if (it.channel().xSampling != 1 || it.channel().ySampling != 1) {
49  /* Sub-sampled layers are not supported */
50  continue;
51  }
52 
53  if (!ch_r && (name == "r" || name == "red" ||
54  endsWith(name, ".r") || endsWith(name, ".red"))) {
55  ch_r = it.name();
56  } else if (!ch_g && (name == "g" || name == "green" ||
57  endsWith(name, ".g") || endsWith(name, ".green"))) {
58  ch_g = it.name();
59  } else if (!ch_b && (name == "b" || name == "blue" ||
60  endsWith(name, ".b") || endsWith(name, ".blue"))) {
61  ch_b = it.name();
62  }
63  }
64 
65  if (!ch_r || !ch_g || !ch_b)
66  throw NoriException("This is not a standard RGB OpenEXR file!");
67 
68  size_t compStride = sizeof(float),
69  pixelStride = 3 * compStride,
70  rowStride = pixelStride * cols();
71 
72  char *ptr = reinterpret_cast<char *>(data());
73 
74  Imf::FrameBuffer frameBuffer;
75  frameBuffer.insert(ch_r, Imf::Slice(Imf::FLOAT, ptr, pixelStride, rowStride)); ptr += compStride;
76  frameBuffer.insert(ch_g, Imf::Slice(Imf::FLOAT, ptr, pixelStride, rowStride)); ptr += compStride;
77  frameBuffer.insert(ch_b, Imf::Slice(Imf::FLOAT, ptr, pixelStride, rowStride));
78  file.setFrameBuffer(frameBuffer);
79  file.readPixels(dw.min.y, dw.max.y);
80 }
81 
82 void Bitmap::save(const std::string &filenameStem) {
83  std::cout << "Saving " << filenameStem;
84  saveEXR(filenameStem);
85  savePNG(filenameStem);
86 }
87 
88 void Bitmap::saveEXR(const std::string &filenameStem) {
89  std::string filename = filenameStem + ".exr";
90  cout << "Writing a " << cols() << "x" << rows()
91  << " OpenEXR file to \"" << filename << "\"" << endl;
92 
93  Imf::Header header((int) cols(), (int) rows());
94  header.insert("comments", Imf::StringAttribute("Generated by Nori"));
95 
96  Imf::ChannelList &channels = header.channels();
97  channels.insert("R", Imf::Channel(Imf::FLOAT));
98  channels.insert("G", Imf::Channel(Imf::FLOAT));
99  channels.insert("B", Imf::Channel(Imf::FLOAT));
100 
101  Imf::FrameBuffer frameBuffer;
102  size_t compStride = sizeof(float),
103  pixelStride = 3 * compStride,
104  rowStride = pixelStride * cols();
105 
106  char *ptr = reinterpret_cast<char *>(data());
107  frameBuffer.insert("R", Imf::Slice(Imf::FLOAT, ptr, pixelStride, rowStride)); ptr += compStride;
108  frameBuffer.insert("G", Imf::Slice(Imf::FLOAT, ptr, pixelStride, rowStride)); ptr += compStride;
109  frameBuffer.insert("B", Imf::Slice(Imf::FLOAT, ptr, pixelStride, rowStride));
110 
111  Imf::OutputFile file(filename.c_str(), header);
112  file.setFrameBuffer(frameBuffer);
113  file.writePixels((int) rows());
114 }
115 
116 static float GammaCorrect(float value) {
117  if (value <= 0.0031308f) return 12.92f * value;
118  return 1.055f * std::pow(value, 1.f/2.4f) - 0.055f;
119 }
120 static float Clamp(float val, float low, float high) {
121  if (val < low)
122  return low;
123  else if (val > high)
124  return high;
125  else
126  return val;
127 }
128 
129 void Bitmap::savePNG(const std::string &filenameStem) {
130  std::string filename = filenameStem + ".png";
131  cout << "Writing a " << cols() << "x" << rows()
132  << " PNG file to \"" << filename << "\"" << endl;
133 
134  std::unique_ptr<uint8_t[]> rgb8(new uint8_t[3 * cols() * rows()]);
135  uint8_t *dst = rgb8.get();
136  for (int y = 0; y < rows(); ++y) {
137  for (int x = 0; x < cols(); ++x) {
138 #define TO_BYTE(v) (uint8_t) Clamp(255.f * GammaCorrect(v) + 0.5f, 0.f, 255.f)
139  dst[0] = TO_BYTE(coeff(y,x).r());
140  dst[1] = TO_BYTE(coeff(y,x).g());
141  dst[2] = TO_BYTE(coeff(y,x).b());
142 #undef TO_BYTE
143  dst += 3;
144  }
145  }
146  int ret = stbi_write_png(filename.c_str(),cols(),rows(),3,rgb8.get(),3*cols());
147  if (ret == 0) {
148  cout << "Bitmap::savePNG(): Could not save PNG file \"" << filename << "%s\"" << endl;
149  }
150 }
151 
152 NORI_NAMESPACE_END
void save(const std::string &filenameStem)
Save the bitmap as an EXR and PNG file with the specified filename.
Definition: bitmap.cpp:82
void saveEXR(const std::string &filenameStem)
Save the bitmap as an EXR file with the specified filename.
Definition: bitmap.cpp:88
void savePNG(const std::string &filenameStem)
Save the bitmap as a PNG file with the specified filename.
Definition: bitmap.cpp:129
Bitmap(const Vector2i &size=Vector2i(0, 0))
Allocate a new bitmap of the specified size.
Definition: bitmap.h:42
Simple exception class, which stores a human-readable error description.
Definition: common.h:148