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)