/* * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "CubemapUtils.h" #include #include using namespace math; using namespace image; void CubemapUtils::clamp(image::Image& src) { // We clamp all values to 256 which correspond to the maximum value (before // gamma compression) that we can store in RGBM. // This clamping is necessary because: // - our importance-sampling (when calculating the pre-filtered mipmaps) // behaves badly with with very strong high-frequencies. // - SH can't encode such environments with a small number of bands. const size_t width = src.getWidth(); const size_t height = src.getHeight(); for (size_t y=0 ; y(src.getPixelRef(x, y)); c.x = std::min(c.x, 256.0f); c.y = std::min(c.y, 256.0f); c.z = std::min(c.z, 256.0f); } } } void CubemapUtils::equirectangularToCubemap(Cubemap& dst, const image::Image& src) { const size_t width = src.getWidth(); const size_t height = src.getHeight(); const double r = width * 0.5 * M_1_PI; process(dst, [&](EmptyState&, size_t y, Cubemap::Face f, Cubemap::Texel* data, size_t dim) { for (size_t x=0 ; x(dst, [&](EmptyState&, size_t y, Cubemap::Face f, Cubemap::Texel* data, size_t dim) { const image::Image& image(src.getImageForFace(f)); for (size_t x=0 ; x image.getWidth()) { x = dim, y = 3*dim; flags = Image::FLIP_XY; } else { x = 3*dim, y = dim; } break; case Cubemap::Face::PZ: x = dim, y = dim; break; } Image subImage; subImage.subset(image, x, y, dim, dim, flags); cm.setImageForFace(face, subImage); } void CubemapUtils::setAllFacesFromCross(Cubemap& cm, const Image& image) { cm.setGeometry(image.getHeight() > image.getWidth() ? Cubemap::Geometry::VERTICAL_CROSS : Cubemap::Geometry::HORIZONTAL_CROSS); CubemapUtils::setFaceFromCross(cm, Cubemap::Face::NX, image); CubemapUtils::setFaceFromCross(cm, Cubemap::Face::PX, image); CubemapUtils::setFaceFromCross(cm, Cubemap::Face::NY, image); CubemapUtils::setFaceFromCross(cm, Cubemap::Face::PY, image); CubemapUtils::setFaceFromCross(cm, Cubemap::Face::NZ, image); CubemapUtils::setFaceFromCross(cm, Cubemap::Face::PZ, image); } Image CubemapUtils::createCubemapImage(size_t dim, bool horizontal) { size_t width = 4 * dim; size_t height = 3 * dim; if (!horizontal) { std::swap(width, height); } // always allocate an extra column and row, to allow the cubemap to be "seamless" size_t bpr = (width + 1) * sizeof(Cubemap::Texel); bpr = (bpr + 31) & ~31; size_t bufSize = bpr * (height + 1); std::unique_ptr data(new uint8_t[bufSize]); memset(data.get(), 0, bufSize); Image image(std::move(data), width, height, bpr, sizeof(Cubemap::Texel)); return image; } std::string CubemapUtils::getFaceName(Cubemap::Face face) { switch (face) { case Cubemap::Face::NX: return "nx"; case Cubemap::Face::PX: return "px"; case Cubemap::Face::NY: return "ny"; case Cubemap::Face::PY: return "py"; case Cubemap::Face::NZ: return "nz"; case Cubemap::Face::PZ: return "pz"; } } Cubemap CubemapUtils::create(Image& image, size_t dim, bool horizontal) { Cubemap cm(dim); Image temp(CubemapUtils::createCubemapImage(dim, horizontal)); CubemapUtils::setAllFacesFromCross(cm, temp); std::swap(image, temp); return cm; } void CubemapUtils::copyImage(Image& dst, const Image& src) { assert(dst.getWidth() >= src.getWidth() && dst.getHeight() >= src.getHeight()); for (size_t y = 0, my = src.getHeight(); y < my; ++y) { memcpy(dst.getPixelRef(0, y), src.getPixelRef(0, y), src.getBytesPerRow()); } } void CubemapUtils::mirrorCubemap(Cubemap& dst, const Cubemap& src) { process(dst, [&](EmptyState&, size_t y, Cubemap::Face f, Cubemap::Texel* data, size_t dim) { for (size_t x=0 ; x(cml, [ & ](CubemapUtils::EmptyState&, size_t y, Cubemap::Face f, Cubemap::Texel* data, size_t dim) { for (size_t x = 0; x < dim; ++x, ++data) { bool grid = bool(((x / gridSize) ^ (y / gridSize)) & 1); Cubemap::Texel t = grid ? colors[(int)f] : 0; Cubemap::writeAt(data, t); } }); } /* * Area of a cube face's quadrant projected onto a sphere * * 1 +---+----------+ * | | | * |---+----------| * | |(x,y) | * | | | * | | | * -1 +---+----------+ * -1 1 * * * The quadran (-1,1)-(x,y) is projected onto the unit sphere * */ static inline double sphereQuadranArea(double x, double y) { return std::atan2(x*y, std::sqrt(x*x + y*y + 1)); } double CubemapUtils::solidAngle(size_t dim, size_t u, size_t v) { const double iDim = 1.0f / dim; double s = ((u + 0.5) * 2*iDim) - 1; double t = ((v + 0.5) * 2*iDim) - 1; const double x0 = s - iDim; const double y0 = t - iDim; const double x1 = s + iDim; const double y1 = t + iDim; double solidAngle = sphereQuadranArea(x0, y0) - sphereQuadranArea(x0, y1) - sphereQuadranArea(x1, y0) + sphereQuadranArea(x1, y1); return solidAngle; }