/* This file is part of Mitsuba, a physically based rendering system. Copyright (c) 2007-2011 by Wenzel Jakob and others. Mitsuba is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License Version 3 as published by the Free Software Foundation. Mitsuba is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include #include #include #include #include #include #include #include #include #include #include MTS_NAMESPACE_BEGIN /*!\plugin{bitmap}{Bitmap texture} * \parameters{ * \parameter{filename}{\String}{ * Filename of the bitmap to be loaded * } * \parameter{gamma}{\Float}{ * Gamma value of the source bitmap file * \default{\emph{automatic}, i.e. linear for EXR input, * and sRGB for everything else.} * } * \parameter{filterType}{\String}{ * Specifies the texture filturing that should be used for lookups * \begin{enumerate}[(i)] * \item \code{ewa}: Elliptically weighted average (a.k.a. * anisotropic filtering). This produces the best quality * \item \code{trilinear}: Simple trilinear (isotropic) filtering. * \item \code{none}: No filtering, do nearest neighbor lookups. * \end{enumerate} * Default: \code{ewa}. * } * \parameter{wrapMode}{\String}{ * This parameter defines the behavior of the texture outside of the $[0,1]$ $uv$ range. * \begin{enumerate}[(i)] * \item \code{repeat}: Repeat the texture (i.e. $uv$ coordinates * are taken modulo 2) * \item \code{clamp}: Clamp $uv$ coordinates to $[0,1]$ * \item \code{black}: Switch to a zero-valued texture * \item \code{white}: Switch to a one-valued texture * \end{enumerate} * Default: \code{repeat}. * } * \parameter{maxAnisotropy}{\Float}{ * Specifies an upper limit on the amount of anisotropy * of \code{ewa} lookups\default{8} * } * \parameter{uscale, vscale}{\Float}{ * Multiplicative factors that should be applied to UV values before a lookup * } * \parameter{uoffset, voffset}{\Float}{ * Numerical offset that should be applied to UV values before a lookup * } * } * This plugin implements a bitmap-based texture, which supports the following * file formats: * \begin{itemize} * \item OpenEXR * \item JPEG * \item PNG (Portable Network Graphics) * \item TGA (Targa) * \item BMP (Windows bitmaps) * \end{itemize} * * The plugin internally converts all bitmap data into a \emph{linear} space to ensure * a proper workflow. */ class BitmapTexture : public Texture2D { public: BitmapTexture(const Properties &props) : Texture2D(props) { m_filename = Thread::getThread()->getFileResolver()->resolve( props.getString("filename")); /* -1 means sRGB. Gamma is ignored when loading EXR files */ m_gamma = props.getFloat("gamma", -1); Log(EInfo, "Loading texture \"%s\"", m_filename.leaf().c_str()); ref fs = new FileStream(m_filename, FileStream::EReadOnly); std::string extension = boost::to_lower_copy(m_filename.extension()); std::string filterType = boost::to_lower_copy(props.getString("filterType", "ewa")); std::string wrapMode = boost::to_lower_copy(props.getString("wrapMode", "repeat")); if (filterType == "ewa") m_filterType = MIPMap::EEWA; else if (filterType == "trilinear") m_filterType = MIPMap::ETrilinear; else if (filterType == "none") m_filterType = MIPMap::ENone; else Log(EError, "Unknown filter type '%s' -- must be " "'ewa', 'isotropic', or 'none'!", filterType.c_str()); if (wrapMode == "repeat") m_wrapMode = MIPMap::ERepeat; else if (wrapMode == "clamp") m_wrapMode = MIPMap::EClamp; else if (wrapMode == "black") m_wrapMode = MIPMap::EBlack; else if (wrapMode == "white") m_wrapMode = MIPMap::EWhite; else Log(EError, "Unknown wrap mode '%s' -- must be " "'repeat', 'clamp', 'black', or 'white'!", filterType.c_str()); m_maxAnisotropy = props.getFloat("maxAnisotropy", 8); if (extension == ".exr") m_format = Bitmap::EEXR; else if (extension == ".jpg" || extension == ".jpeg") m_format = Bitmap::EJPEG; else if (extension == ".png") m_format = Bitmap::EPNG; else if (extension == ".tga") m_format = Bitmap::ETGA; else if (extension == ".bmp") m_format = Bitmap::EBMP; else Log(EError, "Cannot deduce the file type of '%s'!", m_filename.file_string().c_str()); ref bitmap = new Bitmap(m_format, fs); initializeFrom(bitmap); } BitmapTexture(Stream *stream, InstanceManager *manager) : Texture2D(stream, manager) { m_filename = stream->readString(); Log(EInfo, "Unserializing texture \"%s\"", m_filename.leaf().c_str()); m_gamma = stream->readFloat(); m_format = static_cast(stream->readInt()); m_filterType = (MIPMap::EFilterType) stream->readInt(); m_wrapMode = (MIPMap::EWrapMode) stream->readUInt(); m_maxAnisotropy = stream->readFloat(); uint32_t size = stream->readUInt(); ref mStream = new MemoryStream(size); stream->copyTo(mStream, size); mStream->setPos(0); ref bitmap = new Bitmap(m_format, mStream); initializeFrom(bitmap); if (Scheduler::getInstance()->hasRemoteWorkers() && !fs::exists(m_filename)) { /* This code is running on a machine different from the one that created the stream. Because we might later have to handle a call to serialize(), the whole bitmap must be kept in memory */ m_stream = mStream; m_stream->setPos(0); } } inline Float fromSRGBComponent(Float value) { if (value <= (Float) 0.04045) return value / (Float) 12.92; return std::pow((value + (Float) 0.055) / (Float) (1.0 + 0.055), (Float) 2.4); } void initializeFrom(Bitmap *bitmap) { ref corrected; m_bpp = bitmap->getBitsPerPixel(); if (bitmap->getBitsPerPixel() == 128) { /* Nothing needs to be done */ corrected = bitmap; } else { corrected = new Bitmap(bitmap->getWidth(), bitmap->getHeight(), 128); float tbl[256]; if (m_gamma == -1) { for (int i=0; i<256; ++i) tbl[i] = fromSRGBComponent((Float) i / (Float) 255); } else { for (int i=0; i<256; ++i) tbl[i] = std::pow((Float) i / (Float) 255, m_gamma); } uint8_t *data = bitmap->getData(); float *flData = corrected->getFloatData(); if (bitmap->getBitsPerPixel() == 32) { for (int y=0; ygetHeight(); ++y) { for (int x=0; xgetWidth(); ++x) { float r = tbl[*data++], g = tbl[*data++], b = tbl[*data++], a = *data++ / 255.0f; *flData++ = r; *flData++ = g; *flData++ = b; *flData++ = a; } } } else if (bitmap->getBitsPerPixel() == 24) { for (int y=0; ygetHeight(); ++y) { for (int x=0; xgetWidth(); ++x) { float r = tbl[*data++], g = tbl[*data++], b = tbl[*data++]; *flData++ = r; *flData++ = g; *flData++ = b; *flData++ = 1.0f; } } } else if (bitmap->getBitsPerPixel() == 16) { for (int y=0; ygetHeight(); ++y) { for (int x=0; xgetWidth(); ++x) { float col = tbl[*data++], a = *data++ / 255.0f; *flData++ = col; *flData++ = col; *flData++ = col; *flData++ = a; } } } else if (bitmap->getBitsPerPixel() == 8) { for (int y=0; ygetHeight(); ++y) { for (int x=0; xgetWidth(); ++x) { float col = tbl[*data++]; *flData++ = col; *flData++ = col; *flData++ = col; *flData++ = 1.0f; } } } else if (bitmap->getBitsPerPixel() == 1) { int pos = 0; for (int y=0; ygetHeight(); ++y) { for (int x=0; xgetWidth(); ++x) { int entry = pos / 8; int bit = pos % 8; int value = (data[entry] & (1 << bit)) ? 255 : 0; float col = tbl[value]; *flData++ = col; *flData++ = col; *flData++ = col; *flData++ = 1.0f; pos++; } } } else { Log(EError, "%i bpp images are currently not supported!", bitmap->getBitsPerPixel()); } } m_mipmap = MIPMap::fromBitmap(corrected, m_filterType, m_wrapMode, m_maxAnisotropy); } void serialize(Stream *stream, InstanceManager *manager) const { Texture2D::serialize(stream, manager); stream->writeString(m_filename.file_string()); stream->writeFloat(m_gamma); stream->writeInt(m_format); stream->writeInt(m_filterType); stream->writeUInt(m_wrapMode); stream->writeFloat(m_maxAnisotropy); if (m_stream.get()) { stream->writeUInt((uint32_t) m_stream->getSize()); stream->write(m_stream->getData(), m_stream->getSize()); } else { ref mStream = new MemoryStream(); ref is = new FileStream(m_filename, FileStream::EReadOnly); stream->writeUInt((uint32_t) is->getSize()); is->copyTo(stream); } } Spectrum getValue(const Point2 &uv) const { return m_mipmap->triangle(0, uv.x, uv.y); } Spectrum getValue(const Point2 &uv, Float dudx, Float dudy, Float dvdx, Float dvdy) const { return m_mipmap->getValue(uv.x, uv.y, dudx, dudy, dvdx, dvdy); } Spectrum getAverage() const { return m_mipmap->getAverage(); } Spectrum getMaximum() const { return m_mipmap->getMaximum(); } Spectrum getMinimum() const { return m_mipmap->getMinimum(); } bool isConstant() const { return false; } bool usesRayDifferentials() const { return true; } Vector3i getResolution() const { return Vector3i( m_mipmap->getWidth(), m_mipmap->getHeight(), 1 ); } std::string toString() const { std::ostringstream oss; oss << "BitmapTexture[" << endl << " filename = \"" << m_filename << "\"," << endl << " bpp = " << m_bpp; if (m_bpp < 128) { oss << "," << endl << " gamma = " << m_gamma << endl; } else { oss << endl; } oss << "]"; return oss.str(); } Shader *createShader(Renderer *renderer) const; MTS_DECLARE_CLASS() protected: ref m_mipmap; ref m_stream; fs::path m_filename; Bitmap::EFileFormat m_format; MIPMap::EFilterType m_filterType; MIPMap::EWrapMode m_wrapMode; Float m_maxAnisotropy; Float m_gamma; int m_bpp; }; // ================ Hardware shader implementation ================ class BitmapTextureShader : public Shader { public: BitmapTextureShader(Renderer *renderer, std::string filename, ref bitmap, const Point2 &uvOffset, const Vector2 &uvScale, MIPMap::EWrapMode wrapMode, Float maxAnisotropy) : Shader(renderer, ETextureShader), m_uvOffset(uvOffset), m_uvScale(uvScale) { m_gpuTexture = renderer->createGPUTexture(filename, bitmap); if (wrapMode == MIPMap::ERepeat) m_gpuTexture->setWrapType(GPUTexture::ERepeat); else m_gpuTexture->setWrapType(GPUTexture::EClampToEdge); m_gpuTexture->setMaxAnisotropy(maxAnisotropy); m_gpuTexture->init(); /* Release the memory on the host side */ m_gpuTexture->setBitmap(0, NULL); } void cleanup(Renderer *renderer) { m_gpuTexture->cleanup(); } void generateCode(std::ostringstream &oss, const std::string &evalName, const std::vector &depNames) const { oss << "uniform sampler2D " << evalName << "_texture;" << endl << "uniform vec2 " << evalName << "_uvOffset;" << endl << "uniform vec2 " << evalName << "_uvScale;" << endl << endl << "vec3 " << evalName << "(vec2 uv) {" << endl << " return texture2D(" << evalName << "_texture, vec2(" << endl << " uv.x * " << evalName << "_uvScale.x + " << evalName << "_uvOffset.x," << endl << " uv.y * " << evalName << "_uvScale.y + " << evalName << "_uvOffset.y)).rgb;" << endl << "}" << endl; } void resolve(const GPUProgram *program, const std::string &evalName, std::vector ¶meterIDs) const { parameterIDs.push_back(program->getParameterID(evalName + "_texture", false)); parameterIDs.push_back(program->getParameterID(evalName + "_uvOffset", false)); parameterIDs.push_back(program->getParameterID(evalName + "_uvScale", false)); } void bind(GPUProgram *program, const std::vector ¶meterIDs, int &textureUnitOffset) const { m_gpuTexture->bind(textureUnitOffset++); program->setParameter(parameterIDs[0], m_gpuTexture.get()); program->setParameter(parameterIDs[1], m_uvOffset); program->setParameter(parameterIDs[2], m_uvScale); } void unbind() const { m_gpuTexture->unbind(); } MTS_DECLARE_CLASS() private: ref m_gpuTexture; Point2 m_uvOffset; Vector2 m_uvScale; }; Shader *BitmapTexture::createShader(Renderer *renderer) const { return new BitmapTextureShader(renderer, m_filename.leaf(), m_mipmap->getLDRBitmap(), m_uvOffset, m_uvScale, m_wrapMode, (m_filterType == MIPMap::EEWA) ? m_maxAnisotropy : 1.0f); } MTS_IMPLEMENT_CLASS_S(BitmapTexture, false, Texture2D) MTS_IMPLEMENT_CLASS(BitmapTextureShader, false, Shader) MTS_EXPORT_PLUGIN(BitmapTexture, "Bitmap texture (EXR/JPG/PNG/TGA/BMP)"); MTS_NAMESPACE_END