/* * Copyright 2018 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using std::istringstream; using std::string; using std::swap; using std::vector; using filament::math::float3; using filament::math::float4; using namespace image; class ImageTest : public testing::Test {}; static ComparisonMode g_comparisonMode; static utils::Path g_comparisonPath; // Just for fun, define a tiny Ray-Sphere intersector, which we'll use to generate a reasonable // normal map for testing purposes. struct Ray { float3 orig, dir; }; struct Sphere { float3 center; float radius2; }; static bool intersect(Ray ray, Sphere sphere, float* t); // Creates a "size x size" normal map that looks like a hemisphere embedded in a plane. static LinearImage createNormalMap(uint32_t size); // Creates a "size x size" height map that looks like a hemisphere embedded in a plane. static LinearImage createDepthMap(uint32_t size); // Creates a tiny monochrome image from a pattern string. static LinearImage createGrayFromAscii(const string& pattern); // Creates a tiny RGB image from a pattern string. static LinearImage createColorFromAscii(const string& pattern); // Saves an image to disk or does a load-and-compare, depending on g_comparisonMode. static void updateOrCompare(const LinearImage& limg, const utils::Path& fname); // Subtracts two images, does an abs(), then normalizes such that min/max transform to 0/1. static LinearImage diffImages(const LinearImage& a, const LinearImage& b); TEST_F(ImageTest, LuminanceFilters) { // NOLINT auto tiny = createGrayFromAscii("000 010 000"); ASSERT_EQ(tiny.getWidth(), 3); ASSERT_EQ(tiny.getHeight(), 3); auto src = transpose(createGrayFromAscii("01 23 45")); auto ref = createGrayFromAscii("024 135"); ASSERT_EQ(src.getWidth(), 3); ASSERT_EQ(src.getHeight(), 2); for (int i = 0; i < 6; i++) { EXPECT_FLOAT_EQ(src.getPixelRef()[i], ref.getPixelRef()[i]); } auto row = createGrayFromAscii("010"); auto mag1 = resampleImage(row, 6, 1, Filter::HERMITE); ASSERT_EQ(mag1.getWidth(), 6); ASSERT_EQ(mag1.getHeight(), 1); auto mag2 = resampleImage(row, 7, 2, Filter::HERMITE); ASSERT_EQ(mag2.getWidth(), 7); ASSERT_EQ(mag2.getHeight(), 2); auto box = resampleImage(tiny, 6, 6, Filter::BOX); auto nearest = resampleImage(tiny, 6, 6, Filter::NEAREST); auto ref3 = createGrayFromAscii("000000 000000 001100 001100 000000 000000"); for (int i = 0; i < 36; i++) { EXPECT_FLOAT_EQ(box.getPixelRef()[i], ref3.getPixelRef()[i]); EXPECT_FLOAT_EQ(nearest.getPixelRef()[i], ref3.getPixelRef()[i]); } auto grays0 = resampleImage(tiny, 100, 100, Filter::GAUSSIAN_SCALARS); auto mag3 = transpose(resampleImage(tiny, 32, 8, Filter::GAUSSIAN_SCALARS)); auto grays1 = resampleImage(mag3, 100, 100, Filter::NEAREST); updateOrCompare(horizontalStack({grays0, grays1}), "grays.png"); } TEST_F(ImageTest, DistanceField) { // NOLINT auto tiny = createGrayFromAscii("100000 000000 001100 001100 000000 000000"); auto src = resampleImage(tiny, 256, 256, Filter::BOX); auto presence = [] (const LinearImage& img, uint32_t col, uint32_t row, void*) { return img.getPixelRef(col, row)[0] ? true : false; }; auto cf = computeCoordField(src, presence, nullptr); auto edt = edtFromCoordField(cf, true); float maxdist = 0; const uint32_t width = edt.getWidth(); const uint32_t height = edt.getHeight(); for (int32_t row = 0; row < height; ++row) { float* dst = edt.getPixelRef(0, row); for (uint32_t col = 0; col < width; ++col) { maxdist = std::max(maxdist, dst[col]); } } for (int32_t row = 0; row < height; ++row) { float* dst = edt.getPixelRef(0, row); for (uint32_t col = 0; col < width; ++col) { dst[col] /= maxdist; } } updateOrCompare(horizontalStack({src, edt}), "edt.png"); tiny = createColorFromAscii("00000 01020 00400 04000 00000"); src = resampleImage(tiny, 256, 256, Filter::MITCHELL); for (int32_t row = 0; row < src.getHeight(); ++row) { for (uint32_t col = 0; col < src.getWidth(); ++col) { float& r = src.getPixelRef(col, row)[0]; float& g = src.getPixelRef(col, row)[1]; float& b = src.getPixelRef(col, row)[2]; bool inside = r > 0.4 || g > 0.4 || b > 0.4; if (!inside) { r = g = b = 0.4f; } } } auto isInside = [] (const LinearImage& img, uint32_t col, uint32_t row, void*) { float r = img.getPixelRef(col, row)[0]; float g = img.getPixelRef(col, row)[1]; float b = img.getPixelRef(col, row)[2]; return !(r > 0.4 && g > 0.4 && b > 0.4); }; cf = computeCoordField(src, isInside, nullptr); auto voronoi = voronoiFromCoordField(cf, src); updateOrCompare(horizontalStack({src, voronoi}), "voronoi.png"); } TEST_F(ImageTest, ColorFilters) { // NOLINT // Test color space with a classic RED => GREEN color gradient. LinearImage color1 = createColorFromAscii("12"); auto color2 = resampleImage(color1, 100, 100, Filter::NEAREST); auto color3 = resampleImage(color1, 100, 100, Filter::GAUSSIAN_SCALARS); auto color4 = resampleImage(color1, 100, 100, Filter::LANCZOS); auto color5 = diffImages(color3, color4); // Try enlarging a 5x5 image using MITCHELL and LANCZOS filters. LinearImage color6 = createColorFromAscii("44444 41014 40704 41014 44444"); auto color6b = resampleImage(color6, 100, 100, Filter::NEAREST); auto color7 = resampleImage(color6, 100, 100, Filter::MITCHELL); auto color8 = resampleImage(color6, 100, 100, Filter::LANCZOS); auto color9 = resampleImage(color6, 100, 100, Filter::GAUSSIAN_SCALARS); // Minification tests. Each of these do a nearest magnification afterwards for visualization // purposes. auto magnify = [](LinearImage img) { return resampleImage(img, 100, 100, Filter::NEAREST); }; auto colora = magnify(resampleImage(color9, 3, 3, Filter::NEAREST)); auto colorb = magnify(resampleImage(color9, 1, 1, Filter::NEAREST)); auto colorc = magnify(resampleImage(color9, 3, 3, Filter::BOX)); auto colord = magnify(resampleImage(color9, 1, 1, Filter::BOX)); auto colors0 = horizontalStack({color2, color3, color4, color5}); auto colors1 = horizontalStack({color6b, color7, color8, color9}); auto colors2 = horizontalStack({colora, colorb, colorc, colord}); auto colors = verticalStack({colors0, colors1, colors2}); // Even more minification tests.... auto colore = magnify(resampleImage(colors, 5, 5, Filter::DEFAULT)); auto colorf = magnify(resampleImage(colors, 50, 50, Filter::DEFAULT)); auto colorg = magnify(resampleImage(colors, 5, 5, Filter::HERMITE)); auto colorh = magnify(resampleImage(colors, 50, 50, Filter::HERMITE)); auto colori = horizontalStack({colore, colorf, colorg, colorh}); colors = verticalStack({colors, colori}); updateOrCompare(colors, "colors.png"); ASSERT_EQ(colors.getWidth(), 400); ASSERT_EQ(colors.getHeight(), 400); // Test radius multiplier (blurring). ImageSampler sampler; sampler.horizontalFilter = sampler.verticalFilter = Filter::GAUSSIAN_SCALARS; sampler.filterRadiusMultiplier = 1; auto blurred0 = resampleImage(color6b, 100, 100, sampler); sampler.filterRadiusMultiplier = 10; auto blurred1 = resampleImage(color6b, 100, 100, sampler); sampler.filterRadiusMultiplier = 20; auto blurred2 = resampleImage(color6b, 100, 100, sampler); auto blurred3 = resampleImage(color6b, 101, 100, sampler); auto blurred4 = resampleImage(color6b, 99, 100, sampler); auto blurred = horizontalStack({blurred0, blurred1, blurred2, blurred3, blurred4}); // Test extraction via sourceRegion and subsequent blurring. sampler.sourceRegion = {0, 0.25f, 0.25f, 0.5f}; sampler.filterRadiusMultiplier = 1; auto region0 = resampleImage(colors, 100, 100, sampler); sampler.filterRadiusMultiplier = 10; auto region1 = resampleImage(colors, 100, 100, sampler); sampler.filterRadiusMultiplier = 20; auto region2 = resampleImage(colors, 100, 100, sampler); auto region3 = resampleImage(colors, 101, 100, sampler); auto region4 = resampleImage(colors, 99, 100, sampler); auto region = horizontalStack({region0, region1, region2, region3, region4}); blurred = verticalStack({blurred, region}); updateOrCompare(blurred, "blurred.png"); // Sample the reddish-white pixel in the post-blurred image. SingleSample result; computeSingleSample(colors, 0.375, 0.375, &result); auto red = int(result[0] * 255.0f); auto grn = int(result[1] * 255.0f); auto blu = int(result[2] * 255.0f); ASSERT_EQ(red, 204); ASSERT_EQ(grn, 200); ASSERT_EQ(blu, 200); } TEST_F(ImageTest, VectorFilters) { // NOLINT auto toColors = vectorsToColors; auto normals = createNormalMap(1024); auto wrong = resampleImage(toColors(normals), 16, 16, Filter::GAUSSIAN_SCALARS); auto right = toColors(resampleImage(normals, 16, 16, Filter::GAUSSIAN_NORMALS)); auto diff = diffImages(wrong, right); auto atlas = horizontalStack({wrong, right, diff}); atlas = resampleImage(atlas, 300, 100, Filter::NEAREST); updateOrCompare(atlas, "normals.png"); } TEST_F(ImageTest, DepthFilters) { // NOLINT auto depths = createDepthMap(1024); auto wrong = resampleImage(depths, 16, 16, Filter::GAUSSIAN_SCALARS); auto right = resampleImage(depths, 16, 16, Filter::MINIMUM); auto diff = diffImages(wrong, right); auto atlas = horizontalStack({wrong, right, diff}); atlas = resampleImage(atlas, 300, 100, Filter::NEAREST); updateOrCompare(atlas, "depths.png"); } TEST_F(ImageTest, ImageOps) { // NOLINT auto finalize = [] (LinearImage image) { return resampleImage(image, 100, 100, Filter::NEAREST); }; LinearImage x22 = [finalize] () { auto original = createColorFromAscii("12 34"); auto hflipped = finalize(horizontalFlip(original)); auto vflipped = finalize(verticalFlip(original)); return horizontalStack({finalize(original), hflipped, vflipped}); }(); LinearImage x23 = [finalize] () { auto original = createColorFromAscii("123 456"); auto hflipped = finalize(horizontalFlip(original)); auto vflipped = finalize(verticalFlip(original)); return horizontalStack({finalize(original), hflipped, vflipped}); }(); LinearImage x32 = [finalize] () { auto original = createColorFromAscii("12 34 56"); auto hflipped = finalize(horizontalFlip(original)); auto vflipped = finalize(verticalFlip(original)); return horizontalStack({finalize(original), hflipped, vflipped}); }(); auto atlas = verticalStack({x22, x23, x32}); updateOrCompare(atlas, "imageops.png"); } TEST_F(ImageTest, ColorTransformRGB) { // NOLINT constexpr size_t w = 2; constexpr size_t h = 3; constexpr uint16_t texels[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 20000, 40000, 60000, }; constexpr size_t bpr = w * sizeof(uint16_t) * 3; std::unique_ptr data(new uint8_t[h * bpr]); memcpy(data.get(), texels, sizeof(texels)); LinearImage img = image::toLinear(w, h, bpr, data, [ ](uint16_t v) -> uint16_t { return v; }, sRGBToLinear< filament::math::float3>); auto pixels = img.get(); ASSERT_NEAR(pixels[0].x, 0.0f, 0.001f); ASSERT_NEAR(pixels[0].y, 0.0f, 0.001f); ASSERT_NEAR(pixels[0].z, 0.0f, 0.001f); ASSERT_NEAR(pixels[5].x, 0.07583023f, 0.001f); ASSERT_NEAR(pixels[5].y, 0.33077413f, 0.001f); ASSERT_NEAR(pixels[5].z, 0.81851715f, 0.001f); } TEST_F(ImageTest, ColorTransformRGBA) { // NOLINT constexpr size_t w = 4; constexpr size_t h = 1; constexpr uint16_t texels[] = { 10000, 20000, 40000, 60000, 11000, 21000, 41000, 61000, 13000, 23000, 43000, 63000, 15000, 25000, 45000, 65000, }; constexpr size_t bpr = w * sizeof(uint16_t) * 4; std::unique_ptr data(new uint8_t[h * bpr]); memcpy(data.get(), texels, sizeof(texels)); LinearImage img = image::toLinearWithAlpha(w, h, bpr, data, [ ](uint16_t v) -> uint16_t { return v; }, sRGBToLinear< filament::math::float4>); auto pixels = reinterpret_cast(img.getPixelRef()); ASSERT_NEAR(pixels[3].x, 0.04282892f, 0.001f); ASSERT_NEAR(pixels[3].y, 0.12025354f, 0.001f); ASSERT_NEAR(pixels[3].z, 0.42922019f, 0.001f); ASSERT_NEAR(pixels[3].w, 0.99183642f, 0.001f); } TEST_F(ImageTest, Mipmaps) { // NOLINT Filter filter = filterFromString("HERMITE"); ASSERT_EQ(filter, Filter::HERMITE); // Miplevels: 5x10, 2x5, 1x2, 1x1. LinearImage src = createColorFromAscii( "44444 41014 40704 41014 44444 44444 41014 40704 41014 44444"); uint32_t count = getMipmapCount(src); ASSERT_EQ(count, 3); vector mips(count); generateMipmaps(src, filter, mips.data(), count); updateOrCompare(src, "mip0_5x10.png"); for (uint32_t index = 0; index < count; ++index) { updateOrCompare(mips[index], "mip" + std::to_string(index + 1) + "_5x10.png"); } // Test color space with a classic RED => GREEN color gradient. src = createColorFromAscii("12"); src = resampleImage(src, 200, 100, Filter::NEAREST); count = getMipmapCount(src); ASSERT_EQ(count, 7); mips.resize(count); generateMipmaps(src, filter, mips.data(), count); updateOrCompare(src, "mip0_200x100.png"); for (uint32_t index = 0; index < count; ++index) { updateOrCompare(mips[index], "mip" + std::to_string(index + 1) + "_200x100.png"); } } TEST_F(ImageTest, Ktx) { // NOLINT uint8_t foo[] = {1, 2, 3}; uint8_t* data; uint32_t size; Ktx1Bundle nascent(2, 1, true); ASSERT_EQ(nascent.getNumMipLevels(), 2); ASSERT_EQ(nascent.getArrayLength(), 1); ASSERT_TRUE(nascent.isCubemap()); ASSERT_FALSE(nascent.getBlob({0, 0, 0}, &data, &size)); ASSERT_TRUE(nascent.setBlob({0, 0, 0}, foo, sizeof(foo))); ASSERT_TRUE(nascent.getBlob({0, 0, 0}, &data, &size)); ASSERT_EQ(size, sizeof(foo)); ASSERT_EQ(nascent.getMetadata("foo"), nullptr); const uint32_t KTX_HEADER_SIZE = 16 * 4; auto getFileSize = [](const char* filename) { std::ifstream in(filename, std::ifstream::ate | std::ifstream::binary); return in.tellg(); }; if (g_comparisonMode == ComparisonMode::COMPARE) { const auto path = g_comparisonPath + "conftestimage_R11_EAC.ktx"; const auto fileSize = getFileSize(path.c_str()); ASSERT_GT(fileSize, 0); vector buffer(fileSize); std::ifstream in(path, std::ifstream::in | std::ifstream::binary); ASSERT_TRUE(in.read((char*) buffer.data(), fileSize)); Ktx1Bundle deserialized(buffer.data(), buffer.size()); ASSERT_EQ(deserialized.getNumMipLevels(), 1); ASSERT_EQ(deserialized.getArrayLength(), 1); ASSERT_EQ(deserialized.isCubemap(), false); ASSERT_EQ(deserialized.getInfo().pixelWidth, 64); ASSERT_EQ(deserialized.getInfo().pixelHeight, 32); ASSERT_EQ(deserialized.getInfo().pixelDepth, 0); data = nullptr; size = 0; ASSERT_TRUE(deserialized.getBlob({0, 0, 0}, &data, &size)); ASSERT_EQ(size, 1024); ASSERT_NE(data, nullptr); uint32_t serializedSize = deserialized.getSerializedLength(); ASSERT_EQ(serializedSize, KTX_HEADER_SIZE + sizeof(uint32_t) + 1024); ASSERT_EQ(serializedSize, fileSize); vector reserialized(serializedSize); ASSERT_TRUE(deserialized.serialize(reserialized.data(), serializedSize)); ASSERT_EQ(reserialized, buffer); deserialized.setMetadata("foo", "bar"); string val(deserialized.getMetadata("foo")); ASSERT_EQ(val, "bar"); serializedSize = deserialized.getSerializedLength(); reserialized.resize(serializedSize); ASSERT_TRUE(deserialized.serialize(reserialized.data(), serializedSize)); Ktx1Bundle bundleWithMetadata(reserialized.data(), reserialized.size()); val = string(bundleWithMetadata.getMetadata("foo")); ASSERT_EQ(val, "bar"); } } TEST_F(ImageTest, getSphericalHarmonics) { Ktx1Bundle ktx(2, 1, true); const char* sphereHarmonics = R"(0.199599 0.197587 0.208682 0.0894955 0.126985 0.187462 0.0921711 0.102497 0.105308 -0.0322833 -0.053886 -0.0661181 -0.0734081 -0.0808731 -0.0788446 0.0620748 0.0851526 0.100914 0.00763482 0.00564362 -0.000848833 -0.102654 -0.102815 -0.0930881 -0.022778 -0.0281883 -0.0377256 )"; ktx.setMetadata("sh", sphereHarmonics); float3 harmonics[9]; ktx.getSphericalHarmonics(harmonics); ASSERT_FLOAT_EQ(harmonics[0].x, 0.199599); ASSERT_FLOAT_EQ(harmonics[0].y, 0.197587); ASSERT_FLOAT_EQ(harmonics[0].z, 0.208682); ASSERT_FLOAT_EQ(harmonics[1].x, 0.0894955); ASSERT_FLOAT_EQ(harmonics[1].y, 0.126985); ASSERT_FLOAT_EQ(harmonics[1].z, 0.187462); ASSERT_FLOAT_EQ(harmonics[2].x, 0.0921711); ASSERT_FLOAT_EQ(harmonics[2].y, 0.102497); ASSERT_FLOAT_EQ(harmonics[2].z, 0.105308); ASSERT_FLOAT_EQ(harmonics[3].x, -0.0322833); ASSERT_FLOAT_EQ(harmonics[3].y, -0.053886); ASSERT_FLOAT_EQ(harmonics[3].z, -0.0661181); ASSERT_FLOAT_EQ(harmonics[4].x, -0.0734081); ASSERT_FLOAT_EQ(harmonics[4].y, -0.0808731); ASSERT_FLOAT_EQ(harmonics[4].z, -0.0788446); ASSERT_FLOAT_EQ(harmonics[5].x, 0.0620748); ASSERT_FLOAT_EQ(harmonics[5].y, 0.0851526); ASSERT_FLOAT_EQ(harmonics[5].z, 0.100914); ASSERT_FLOAT_EQ(harmonics[6].x, 0.00763482); ASSERT_FLOAT_EQ(harmonics[6].y, 0.00564362); ASSERT_FLOAT_EQ(harmonics[6].z, -0.000848833); ASSERT_FLOAT_EQ(harmonics[7].x, -0.102654); ASSERT_FLOAT_EQ(harmonics[7].y, -0.102815); ASSERT_FLOAT_EQ(harmonics[7].z, -0.0930881); ASSERT_FLOAT_EQ(harmonics[8].x, -0.022778); ASSERT_FLOAT_EQ(harmonics[8].y, -0.0281883); ASSERT_FLOAT_EQ(harmonics[8].z, -0.0377256); } static void printUsage(const char* name) { string exec_name(utils::Path(name).getName()); string usage( "TEST is a unit test runner for the Filament image library\n" "Usages:\n" " TEST compare [gtest options]\n" " TEST update [gtest options]\n" " TEST [gtest options]\n" "\n"); const string from("TEST"); for (size_t pos = usage.find(from); pos != string::npos; pos = usage.find(from, pos)) { usage.replace(pos, from.length(), exec_name); } printf("%s", usage.c_str()); } int main(int argc, char** argv) { ::testing::InitGoogleTest(&argc, argv); if (argc < 2) { std::cerr << "\nWARNING: No path provided, skipping reference image comparison.\n\n"; g_comparisonMode = ComparisonMode::SKIP; return RUN_ALL_TESTS(); } const string cmd = argv[1]; if (cmd == "help") { printUsage(argv[0]); return 0; } if (cmd == "compare" || cmd == "update") { if (argc != 3) { printUsage(argv[0]); return 1; } g_comparisonPath = argv[2]; } if (cmd == "compare") { g_comparisonMode = ComparisonMode::COMPARE; return RUN_ALL_TESTS(); } if (cmd == "update") { g_comparisonMode = ComparisonMode::UPDATE; return RUN_ALL_TESTS(); } printUsage(argv[0]); return 1; } static LinearImage createNormalMap(uint32_t size) { LinearImage result(size, size, 3); auto vectors = (float3*) result.getPixelRef(); const float invsize = 1.0f / size; const Sphere sphere { .center = float3(0.5, 0.5, 0.0), .radius2 = 0.15 }; for (uint32_t n = 0; n < size * size; ++n) { const uint32_t row = n / size, col = n % size; const Ray ray { .orig = { (col + 0.5f) * invsize, 1.0f - (row + 0.5f) * invsize, 1 }, .dir = {0, 0, -1} }; float t; bool isect = intersect(ray, sphere, &t); if (isect) { float3 p = ray.orig + t * ray.dir; vectors[n] = normalize(p - sphere.center); } else { vectors[n] = {0, 0, 1}; } } return result; } static LinearImage createDepthMap(uint32_t size) { LinearImage result(size, size, 1); auto depths = result.getPixelRef(); const float invsize = 1.0f / size; const Sphere sphere { .center = float3(0.5, 0.5, 0.0), .radius2 = 0.15 }; for (uint32_t n = 0; n < size * size; ++n) { const uint32_t row = n / size, col = n % size; const Ray ray { .orig = { (col + 0.5f) * invsize, 1.0f - (row + 0.5f) * invsize, 1 }, .dir = {0, 0, -1} }; float t; bool isect = intersect(ray, sphere, &t); if (isect) { float3 p = ray.orig + t * ray.dir; depths[n] = p.z; } else { depths[n] = 1; } } return result; } static LinearImage createGrayFromAscii(const string& pattern) { uint32_t width = 0; uint32_t height = 0; string row; // Compute the required size. for (istringstream istream(pattern); istream >> row; ++height) { width = (uint32_t) row.size(); } // Allocate the sequence of pixels. LinearImage result(width, height, 1); // Fill in the pixel data. istringstream istream(pattern); float* seq = result.getPixelRef(); for (int i = 0; istream >> row;) { for (char c : row) { seq[i++] = c - '0'; } } return result; } static LinearImage createColorFromAscii(const string& pattern) { uint32_t width = 0; uint32_t height = 0; string row; // Compute the required size. for (istringstream istream(pattern); istream >> row; ++height) { width = (uint32_t) row.size(); } // Allocate the sequence of pixels. LinearImage result(width, height, 3); // Fill in the pixel data. istringstream istream(pattern); float* seq = result.getPixelRef(); for (int i = 0; istream >> row;) { for (char c : row) { uint32_t val = c - (uint32_t)('0'); seq[i++] = (val >> 0u) & 1u; seq[i++] = (val >> 1u) & 1u; seq[i++] = (val >> 2u) & 1u; auto col = (float3*) (seq + i - 3); *col = sRGBToLinear(*col); } } return result; } static void updateOrCompare(const LinearImage& limg, const utils::Path& fname) { image::updateOrCompare(limg, g_comparisonPath + fname, g_comparisonMode, 0.0f); } static bool solve(float a, float b, float c, float *x0, float *x1) { float discr = b * b - 4 * a * c; if (discr < 0) return false; if (discr == 0) { *x0 = *x1 = -0.5f * b / a; } else { float q = (b > 0) ? -0.5f * (b + sqrtf(discr)) : -0.5f * (b - sqrtf(discr)); *x0 = q / a; *x1 = c / q; } if (*x0 > *x1) swap(*x0, *x1); return true; } static bool intersect(Ray ray, Sphere sphere, float* t) { float t0, t1; float3 L = ray.orig - sphere.center; float a = dot(ray.dir, ray.dir); float b = 2 * dot(ray.dir, L); float c = dot(L, L) - sphere.radius2; if (!solve(a, b, c, &t0, &t1)) return false; if (t0 > t1) swap(t0, t1); if (t0 < 0) { t0 = t1; if (t0 < 0) return false; } *t = t0; return true; } static LinearImage diffImages(const LinearImage& a, const LinearImage& b) { const uint32_t width = a.getWidth(), height = a.getHeight(), nchan = a.getChannels(); FILAMENT_CHECK_PRECONDITION( width == b.getWidth() && height == b.getHeight() && nchan == b.getChannels()) << "Images must have same shape."; LinearImage result(width, height, nchan); float* dst = result.getPixelRef(); float const* srca = a.getPixelRef(); float const* srcb = b.getPixelRef(); float largest = 0; float smallest = std::numeric_limits::max(); for (uint32_t n = 0; n < width * height * nchan; ++n) { float delta = std::abs(srca[n] - srcb[n]); largest = std::max(largest, delta); smallest = std::min(smallest, delta); dst[n] = delta; } const float scale = (largest == smallest) ? 1.0f : (1.0f / largest - smallest); for (uint32_t n = 0; n < width * height * nchan; ++n) { dst[n] = (dst[n] - smallest) * scale; } return result; }