diff --git a/include/mitsuba/core/bitmap.h b/include/mitsuba/core/bitmap.h
index d7f58d73..0884fb54 100644
--- a/include/mitsuba/core/bitmap.h
+++ b/include/mitsuba/core/bitmap.h
@@ -403,7 +403,7 @@ public:
void colorBalance(Float r, Float g, Float b);
/// Draw a filled rectangle with the specified position and size
- void fill(const Point2i &offset, const Vector2i &size, const Spectrum &value);
+ void fillRect(Point2i offset, Vector2i size, const Spectrum &value);
/// Bitmap equality operator (useful for unit-tests etc.)
bool operator==(const Bitmap &bitmap) const;
@@ -699,7 +699,25 @@ public:
* use different component formats or channels, or when the
* component format is \ref EBitmask.
*/
- void accumulate(const Bitmap *bitmap, const Point2i &offset);
+ void accumulate(const Bitmap *bitmap, Point2i sourceOffset,
+ Point2i targetOffset, Vector2i size);
+
+ /**
+ * \brief Accumulate the contents of another bitmap into the
+ * region of the specified offset
+ *
+ * This convenience function calls the main accumulate()
+ * implementation with size set to bitmap->getSize()
+ * and sourceOffset set to zero. Out-of-bounds regions are
+ * ignored. It is assumed that bitmap != this.
+ *
+ * \remark This function throws an exception when the bitmaps
+ * use different component formats or channels, or when the
+ * component format is \ref EBitmask.
+ */
+ inline void accumulate(const Bitmap *bitmap, Point2i targetOffset) {
+ accumulate(bitmap, Point2i(0), targetOffset, bitmap->getSize());
+ }
/**
* \brief Up- or down-sample this image to a different resolution
diff --git a/include/mitsuba/hw/font.h b/include/mitsuba/hw/font.h
index 55ed0ac0..c157237d 100644
--- a/include/mitsuba/hw/font.h
+++ b/include/mitsuba/hw/font.h
@@ -76,12 +76,22 @@ public:
/// Allocate memory for a certain font
Font(EFont font);
+ /// Draw text to the specified bitmap
+ void drawText(Bitmap *dest, Point2i pos, const std::string &text) const;
+
+ /// Compute the size covered by the given string when rendered using this font
+ Vector2i getSize(const std::string &text) const;
+
/// Upload the font to the GPU
void init(Renderer *renderer);
/// Free the GPU memory
void cleanup();
+ /// Convert the underlying bitmap to a different pixel format
+ void convert(Bitmap::EPixelFormat pixelFormat,
+ Bitmap::EComponentFormat componentFormat, Float gamma);
+
/// Return the name of this font
inline const std::string &getName() const { return m_name; }
diff --git a/src/films/hdrfilm.cpp b/src/films/hdrfilm.cpp
index adac3f14..967e5900 100644
--- a/src/films/hdrfilm.cpp
+++ b/src/films/hdrfilm.cpp
@@ -20,6 +20,7 @@
#include
#include
#include
+#include
#include
#include "banner.h"
@@ -114,7 +115,33 @@ MTS_NAMESPACE_BEGIN
*
*
* \end{xml}
-
+ *
+ * \subsubsection*{Annotations:}
+ * \label{sec:film-annotations}
+ * The \pluginref{ldrfilm} and \pluginref{hdrfilm} plugins support an additional
+ * feature referred to as \emph{annotations}, which can be quite useful under
+ * certain circumstances.
+ *
+ * Annotations are used to embed useful information inside a rendered image so
+ * that this information is later available to anyone viewing the image.
+ * Exemplary uses of this feature might be to store the frame or take number,
+ * camera parameters, or other relevant scene information.
+ *
+ * Annotations can either be created by means of a \emph{tag}, which is an entry
+ * in the metadata table of the image file (does not modify the actual image data),
+ * or a \emph{text} label which is ``burned'' into the image.
+ *
+ * The syntax of this looks as follows:
+ *
+ * \begin{xml}
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ * \end{xml}
*/
class HDRFilm : public Film {
public:
@@ -204,6 +231,25 @@ public:
}
+ std::vector keys = props.getPropertyNames();
+ for (size_t i=0; i args = tokenize(key.substr(5, key.length()-6), " ,");
+
+ if (args.size() != 2)
+ Log(EError, "Text command '%s' has an invalid number of arguments!", key.c_str());
+
+ Annotation annotation;
+ annotation.offset = Point2i(atoi(args[0].c_str()), atoi(args[1].c_str()));
+ annotation.text = props.getString(keys[i]);
+ m_annotations.push_back(annotation);
+ }
+ }
+
m_storage = new ImageBlock(Bitmap::ESpectrumAlphaWeight, m_cropSize);
}
@@ -321,6 +367,23 @@ public:
}
}
+ if (!m_annotations.empty()) {
+ ref font = new Font(Font::EBitstreamVeraMono14);
+ font->convert(bitmap->getPixelFormat(), bitmap->getComponentFormat(), 1.0f);
+
+ for (size_t i=0; igetSize(text);
+ bitmap->fillRect(offset-Vector2i(4, 4), size + Vector2i(8, 8), Spectrum(0.0f));
+ font->drawText(bitmap, offset, text);
+ }
+ }
+
+ for (std::map::const_iterator it = m_tags.begin();
+ it != m_tags.end(); ++it)
+ bitmap->getMetadata()[it->first] = it->second;
+
fs::path filename = m_destFile;
std::string extension = boost::to_lower_copy(filename.extension().string());
std::string properExtension = (m_fileFormat == Bitmap::EOpenEXR) ? ".exr" : ".rgbe";
@@ -367,6 +430,11 @@ public:
MTS_DECLARE_CLASS()
protected:
+ struct Annotation {
+ Point2i offset;
+ std::string text;
+ };
+
Bitmap::EFileFormat m_fileFormat;
Bitmap::EPixelFormat m_pixelFormat;
Bitmap::EComponentFormat m_componentFormat;
@@ -374,6 +442,9 @@ protected:
bool m_attachLog;
fs::path m_destFile;
ref m_storage;
+
+ std::vector m_annotations;
+ std::map m_tags;
};
MTS_IMPLEMENT_CLASS_S(HDRFilm, false, Film)
diff --git a/src/films/ldrfilm.cpp b/src/films/ldrfilm.cpp
index b26f2ca1..eab41b11 100644
--- a/src/films/ldrfilm.cpp
+++ b/src/films/ldrfilm.cpp
@@ -20,6 +20,7 @@
#include
#include
#include
+#include
#include
#include "banner.h"
@@ -113,6 +114,9 @@ MTS_NAMESPACE_BEGIN
* The RGB values exported by this plugin correspond to the ITU-R Rec. BT. 709-3
* primaries with a D65 white point. When $\texttt{gamma}$ is set to $\code{-1}$ (the default),
* the output is in the sRGB color space and will display as intended on compatible devices.
+ *
+ * Note that this plugin supports render-time \emph{annotations}, which
+ * are described on page~\pageref{sec:film-annotations}.
*/
class LDRFilm : public Film {
public:
@@ -176,6 +180,25 @@ public:
m_reinhardKey = props.getFloat("key", 0.18f);
m_reinhardBurn = props.getFloat("burn", 0.0);
+ std::vector keys = props.getPropertyNames();
+ for (size_t i=0; i args = tokenize(key.substr(5, key.length()-6), " ,");
+
+ if (args.size() != 2)
+ Log(EError, "Text command '%s' has an invalid number of arguments!", key.c_str());
+
+ Annotation annotation;
+ annotation.offset = Point2i(atoi(args[0].c_str()), atoi(args[1].c_str()));
+ annotation.text = props.getString(keys[i]);
+ m_annotations.push_back(annotation);
+ }
+ }
+
m_storage = new ImageBlock(Bitmap::ESpectrumAlphaWeight, m_cropSize);
}
@@ -313,6 +336,23 @@ public:
}
}
+ if (!m_annotations.empty()) {
+ ref font = new Font(Font::EBitstreamVeraMono14);
+ font->convert(bitmap->getPixelFormat(), bitmap->getComponentFormat(), m_gamma);
+
+ for (size_t i=0; igetSize(text);
+ bitmap->fillRect(offset-Vector2i(4, 4), size + Vector2i(8, 8), Spectrum(0.0f));
+ font->drawText(bitmap, offset, text);
+ }
+ }
+
+ for (std::map::const_iterator it = m_tags.begin();
+ it != m_tags.end(); ++it)
+ bitmap->getMetadata()[it->first] = it->second;
+
fs::path filename = m_destFile;
std::string extension = boost::to_lower_copy(filename.extension().string());
std::string expectedExtension;
@@ -367,6 +407,11 @@ public:
MTS_DECLARE_CLASS()
protected:
+ struct Annotation {
+ Point2i offset;
+ std::string text;
+ };
+
Bitmap::EFileFormat m_fileFormat;
Bitmap::EPixelFormat m_pixelFormat;
bool m_hasBanner;
@@ -375,6 +420,9 @@ protected:
ref m_storage;
ETonemapMethod m_tonemapMethod;
Float m_exposure, m_reinhardKey, m_reinhardBurn;
+
+ std::vector m_annotations;
+ std::map m_tags;
};
MTS_IMPLEMENT_CLASS_S(LDRFilm, false, Film)
diff --git a/src/films/tiledhdrfilm.cpp b/src/films/tiledhdrfilm.cpp
index 0f15b59e..73751155 100644
--- a/src/films/tiledhdrfilm.cpp
+++ b/src/films/tiledhdrfilm.cpp
@@ -432,7 +432,7 @@ public:
bool develop(const Point2i &sourceOffset, const Vector2i &size,
const Point2i &targetOffset, Bitmap *target) const {
- target->fill(targetOffset, size, Spectrum(0.0f));
+ target->fillRect(targetOffset, size, Spectrum(0.0f));
return false; /* Not supported by the tiled EXR film! */
}
diff --git a/src/libcore/bitmap.cpp b/src/libcore/bitmap.cpp
index a29cc62d..0419ff54 100644
--- a/src/libcore/bitmap.cpp
+++ b/src/libcore/bitmap.cpp
@@ -430,47 +430,59 @@ void Bitmap::flipVertically() {
}
}
-void Bitmap::accumulate(const Bitmap *bitmap, const Point2i &offset) {
+
+void Bitmap::accumulate(const Bitmap *bitmap, Point2i sourceOffset,
+ Point2i targetOffset, Vector2i size) {
Assert(getPixelFormat() == bitmap->getPixelFormat() &&
getComponentFormat() == bitmap->getComponentFormat() &&
getChannelCount() == bitmap->getChannelCount());
- const int
- offsetX = std::max(offset.x, 0),
- offsetY = std::max(offset.y, 0),
- endX = std::min(offset.x + bitmap->getSize().x, m_size.x),
- endY = std::min(offset.y + bitmap->getSize().y, m_size.y);
+ Vector2i offsetIncrease(
+ std::max(0, std::max(-sourceOffset.x, -targetOffset.x)),
+ std::max(0, std::max(-sourceOffset.y, -targetOffset.y))
+ );
- if (offsetX >= endX || offsetY >= endY)
+ sourceOffset += offsetIncrease;
+ targetOffset += offsetIncrease;
+ size -= offsetIncrease;
+
+ Vector2i sizeDecrease(
+ std::max(0, std::max(sourceOffset.x + size.x - bitmap->getWidth(), targetOffset.x + size.x - getWidth())),
+ std::max(0, std::max(sourceOffset.y + size.y - bitmap->getWidth(), targetOffset.y + size.y - getWidth())));
+
+ size -= sizeDecrease;
+
+ if (size.x <= 0 || size.y <= 0)
return;
const size_t
- columns = (endX - offsetX) * m_channelCount,
+ columns = size.x * m_channelCount,
pixelStride = getBytesPerPixel(),
- sourceStride = bitmap->getSize().x * pixelStride,
- targetStride = m_size.x * pixelStride;
+ sourceStride = bitmap->getWidth() * pixelStride,
+ targetStride = getWidth() * pixelStride;
const uint8_t *source = bitmap->getUInt8Data() +
- (offsetX - offset.x + (offsetY - offset.y) * bitmap->getSize().x) * pixelStride;
+ (sourceOffset.x + sourceOffset.y * (size_t) bitmap->getWidth()) * pixelStride;
uint8_t *target = m_data +
- (offsetX + offsetY * m_size.x) * pixelStride;
+ (targetOffset.x + targetOffset.y * (size_t) m_size.x) * pixelStride;
- for (int y = offsetY; y < endY; ++y) {
+ for (int y = 0; y < size.y; ++y) {
switch (m_componentFormat) {
case EUInt8:
for (size_t i = 0; i < columns; ++i)
- ((uint8_t *) target)[i] += ((uint8_t *) source)[i];
+ ((uint8_t *) target)[i] = (uint8_t) std::min(0xFF, ((uint8_t *) source)[i] + ((uint8_t *) target)[i]);
+
break;
case EUInt16:
for (size_t i = 0; i < columns; ++i)
- ((uint16_t *) target)[i] += ((uint16_t *) source)[i];
+ ((uint16_t *) target)[i] = (uint16_t) std::min(0xFFFF, ((uint16_t *) source)[i] + ((uint16_t *) target)[i]);
break;
case EUInt32:
for (size_t i = 0; i < columns; ++i)
- ((uint32_t *) target)[i] += ((uint32_t *) source)[i];
+ ((uint32_t *) target)[i] = std::min((uint32_t) 0xFFFFFFFFUL, ((uint32_t *) source)[i] + ((uint32_t *) target)[i]);
break;
case EFloat16:
@@ -554,8 +566,9 @@ void Bitmap::setPixel(const Point2i &pos, const Spectrum &value) {
}
void Bitmap::drawHLine(int y, int x1, int x2, const Spectrum &value) {
- AssertEx( y >= 0 && y < m_size.y &&
- x1 >= 0 && x2 < m_size.x, "Bitmap::drawVLine(): out of bounds!");
+ if (y < 0 || y >= m_size.y)
+ return;
+ x1 = std::max(x1, 0); x2 = std::min(x2, m_size.x-1);
const FormatConverter *cvt = FormatConverter::getInstance(
std::make_pair(EFloat, m_componentFormat)
@@ -574,8 +587,9 @@ void Bitmap::drawHLine(int y, int x1, int x2, const Spectrum &value) {
}
void Bitmap::drawVLine(int x, int y1, int y2, const Spectrum &value) {
- AssertEx( x >= 0 && x < m_size.x &&
- y1 >= 0 && y2 < m_size.y, "Bitmap::drawVLine(): out of bounds!");
+ if (x < 0 || x >= m_size.x)
+ return;
+ y1 = std::max(y1, 0); y2 = std::min(y2, m_size.y-1);
const FormatConverter *cvt = FormatConverter::getInstance(
std::make_pair(EFloat, m_componentFormat)
@@ -601,9 +615,12 @@ void Bitmap::drawRect(const Point2i &offset, const Vector2i &size, const Spectru
drawVLine(offset.x + size.x - 1, offset.y, offset.y + size.y - 1, value);
}
-void Bitmap::fill(const Point2i &offset, const Vector2i &size, const Spectrum &value) {
- AssertEx(offset.x >= 0 && offset.x + size.x <= m_size.x &&
- offset.y >= 0 && offset.y + size.y <= m_size.y, "Bitmap::fill(): out of bounds!");
+void Bitmap::fillRect(Point2i offset, Vector2i size, const Spectrum &value) {
+ int sx = std::max(0, -offset.x), sy = std::max(0, -offset.y);
+ size.x -= sx; size.y -= sy; offset.x += sx; offset.y += sy;
+
+ size.x -= std::max(0, offset.x + size.x - m_size.x);
+ size.y -= std::max(0, offset.y + size.y - m_size.y);
const FormatConverter *cvt = FormatConverter::getInstance(
std::make_pair(EFloat, m_componentFormat)
diff --git a/src/libhw/font.cpp b/src/libhw/font.cpp
index dc705f3c..8f288c40 100644
--- a/src/libhw/font.cpp
+++ b/src/libhw/font.cpp
@@ -74,6 +74,70 @@ Font::Font(EFont font) {
dscStream->read(m_kerningMatrix, 256*256);
}
+void Font::convert(Bitmap::EPixelFormat pixelFormat, Bitmap::EComponentFormat componentFormat, Float gamma) {
+ m_bitmap = m_bitmap->convert(pixelFormat, componentFormat, gamma);
+}
+
+void Font::drawText(Bitmap *dest, Point2i pos, const std::string &text) const {
+ int initial = pos.x;
+
+ for (size_t i=0; igetWidth(),
+ glyph.tx.y * m_bitmap->getHeight());
+
+ dest->accumulate(m_bitmap.get(), sourceOffset, targetOffset, glyph.size);
+
+ pos.x += glyph.horizontalAdvance;
+
+ if (i+1 < text.length())
+ pos.x += getKerning(character, text[i+1]);
+ }
+}
+
+Vector2i Font::getSize(const std::string &text) const {
+ Vector2i size(0, getMaxVerticalBearing());
+ int pos = 0;
+
+ for (size_t i=0; icreateGPUTexture(m_name, m_bitmap);
m_texture->setFilterType(GPUTexture::ENearest);
diff --git a/src/libpython/core.cpp b/src/libpython/core.cpp
index ab13f2a4..b0e0cb0b 100644
--- a/src/libpython/core.cpp
+++ b/src/libpython/core.cpp
@@ -708,6 +708,9 @@ void export_core() {
.def("clear", &InterpolatedSpectrum::clear)
.def("zeroExtend", &InterpolatedSpectrum::zeroExtend);
+ void (Bitmap::*accumulate_1)(const Bitmap *bitmap, Point2i sourceOffset, Point2i targetOffset, Vector2i size) = &Bitmap::accumulate;
+ void (Bitmap::*accumulate_2)(const Bitmap *bitmap, Point2i targetOffset) = &Bitmap::accumulate;
+
BP_CLASS(Bitmap, Object, (bp::init()))
.def(bp::init())
.def(bp::init())
@@ -716,7 +719,8 @@ void export_core() {
.def("expand", &Bitmap::expand, BP_RETURN_VALUE)
.def("flipVertically", &Bitmap::flipVertically)
.def("crop", &Bitmap::crop)
- .def("accumulate", &Bitmap::accumulate)
+ .def("accumulate", accumulate_1)
+ .def("accumulate", accumulate_2)
.def("clear", &Bitmap::clear)
.def("write", &Bitmap::write)
.def("setString", &Bitmap::setString)