From 796f59fd438a84954a129650399f9b43d547d04c Mon Sep 17 00:00:00 2001 From: Anton Dukhovnikov Date: Wed, 1 Jan 2025 13:19:14 +1300 Subject: [PATCH] [feat]IBA:demosaic add X-Trans demosaicing Signed-off-by: Anton Dukhovnikov --- src/doc/oiiotool.rst | 14 +- src/doc/pythonbindings.rst | 5 +- src/include/OpenImageIO/imagebufalgo.h | 6 +- src/libOpenImageIO/imagebufalgo_demosaic.cpp | 1097 +++++++++++++---- .../imagebufalgo_demosaic_prv.h | 36 + src/libOpenImageIO/imagebufalgo_test.cpp | 203 +++ src/raw.imageio/rawinput.cpp | 41 +- 7 files changed, 1177 insertions(+), 225 deletions(-) create mode 100644 src/libOpenImageIO/imagebufalgo_demosaic_prv.h diff --git a/src/doc/oiiotool.rst b/src/doc/oiiotool.rst index 54fe603ad9..95f019f185 100644 --- a/src/doc/oiiotool.rst +++ b/src/doc/oiiotool.rst @@ -4212,13 +4212,17 @@ current top image. Optional appended modifiers include: `pattern=` *name* - sensor pattern. Currently supported patterns: "bayer" + sensor pattern. Currently supported patterns: "bayer", "xtrans". `layout=` *name* - photosite order of the specified pattern. For layouts of the Bayer - pattern supported: "RGGB", "GRBG", "GBRG", "BGGR". + photosite order of the specified pattern. The default value is "RGGB" + for Bayer, and "GRBGBR BGGRGG RGGBGG GBRGRB RGGBGG BGGRGG" for X-Trans. `algorithm=` *name* - the name of the algorithm to use: "linear"(simple bilinear demosaicing), - "MHC"(Malvar-He-Cutler algorithm) + the name of the algorithm to use. + The Bayer-pattern algorithms: + "linear"(simple bilinear demosaicing), + "MHC"(Malvar-He-Cutler algorithm). + The X-Trans-pattern algorithms: + "linear"(simple bilinear demosaicing). `white-balance=` *v1,v2,v3...* optional white balance weights, can contain either three (R,G,B) or four (R,G1,B,G2) values. The order of the white balance multipliers is as diff --git a/src/doc/pythonbindings.rst b/src/doc/pythonbindings.rst index fd73aad426..ce167a0f62 100644 --- a/src/doc/pythonbindings.rst +++ b/src/doc/pythonbindings.rst @@ -3701,9 +3701,10 @@ Color manipulation bool ImageBufAlgo.demosaic (dst, src, pattern="", algorithm="", layout="", white_balance=py::none(), roi=ROI.All, nthreads=0) Demosaic a raw digital camera image. - `demosaic` can currently process Bayer pattern images (pattern="bayer") + `demosaic` can currently process Bayer-pattern images (pattern="bayer") using two algorithms: "linear" (simple bilinear demosaicing), and "MHC" - (Malvar-He-Cutler algorithm). The optional white_balance parameter can take + (Malvar-He-Cutler algorithm); or X-Trans-pattern images (pattern="xtrans") + using "linear" algorithm. The optional white_balance parameter can take a tuple of three (R,G,B), or four (R,G1,B,G2) values. The order of the white balance multipliers is as specified, it does not depend on the matrix layout. diff --git a/src/include/OpenImageIO/imagebufalgo.h b/src/include/OpenImageIO/imagebufalgo.h index 02ea537945..638ef735b3 100644 --- a/src/include/OpenImageIO/imagebufalgo.h +++ b/src/include/OpenImageIO/imagebufalgo.h @@ -2164,7 +2164,7 @@ bool OIIO_API repremult (ImageBuf &dst, const ImageBuf &src, /// /// - "pattern" : string (default: "bayer") /// -/// The type of image sensor color filter array. Currently only the Bayer-pattern images are supported. +/// The type of image sensor color filter array. Currently only the Bayer-pattern or X-Trans-pattern images are supported. /// /// - "algorithm" : string (default: "linear") /// @@ -2173,8 +2173,10 @@ bool OIIO_API repremult (ImageBuf &dst, const ImageBuf &src, /// - `linear` - simple bilinear demosaicing. Fast, but can produce artefacts along sharp edges. /// - `MHC` - Malvar-He-Cutler linear demosaicing algorithm. Slower than `linear`, but produces /// significantly better results. +/// The following algorithms are supported for X-Trans-pattern images: +/// - `linear` - simple linear demosaicing. Fast, but can produce artefacts along sharp edges. /// -/// - "layout" : string (default: "RGGB") +/// - "layout" : string (default: "RGGB" for Bayer, "GRBGBR BGGRGG RGGBGG GBRGRB RGGBGG BGGRGG" for X-Trans) /// /// The order the color filter array elements are arranged in, pattern-specific. /// diff --git a/src/libOpenImageIO/imagebufalgo_demosaic.cpp b/src/libOpenImageIO/imagebufalgo_demosaic.cpp index d499b4bfb2..432fb13d46 100644 --- a/src/libOpenImageIO/imagebufalgo_demosaic.cpp +++ b/src/libOpenImageIO/imagebufalgo_demosaic.cpp @@ -9,6 +9,7 @@ #include #include +#include "imagebufalgo_demosaic_prv.h" #include "imageio_pvt.h" OIIO_NAMESPACE_BEGIN @@ -22,7 +23,11 @@ static const ustring white_balance_us("white_balance"); } // namespace -template class BayerDemosaicing { +namespace ImageBufAlgo { + +template +class DemosaicingBase { protected: /// Sliding window, holds `size*size` pixel values to filter over. /// The `size` is expected to be an odd number, so the pixel being @@ -32,15 +37,31 @@ template class BayerDemosaicing { /// the source iterator. struct Row { ImageBuf::ConstIterator iterator; - float white_balance[2]; + int x_offset; - float data[size]; + const int y_offset; + const float (&white_balance_values)[4]; + + float data[window_size]; float fetch() { - float result = iterator[0] * white_balance[x_offset]; - iterator++; - x_offset = 1 - x_offset; + float white_balance + = white_balance_values[channel_map[y_offset][x_offset]]; + float result = iterator[0] * white_balance; + + if (iterator.x() == iterator.range().xend - 1) { + // When we have reached the rightmost pixel, jump back by + // `pattern_size` pixels to re-fetch the last available + // column of the pixels with the required channel layout. + iterator.pos(iterator.x() - pattern_size + 1, iterator.y()); + } else { + iterator++; + } + + x_offset++; + if (x_offset == pattern_size) + x_offset = 0; return result; } }; @@ -49,28 +70,26 @@ template class BayerDemosaicing { /// Column mapping. Insead of shifting every pixel value as the sliding /// window advances, we just rotate the indices in this table. - int column_mapping[size]; + int column_mapping[window_size]; int src_xbegin; int src_xend; int src_ybegin; int src_yend; - int x; Window(int y, int xbegin, const ImageBuf& src, int x_offset, - int y_offset, const float white_balance[4]) + int y_offset, const float (&white_balance_values)[4]) { - assert(size >= 3); - assert(size % 2 == 1); + assert(window_size >= 3); + assert(window_size % 2 == 1); const ImageSpec& spec = src.spec(); src_xbegin = spec.x; src_xend = spec.x + spec.width; src_ybegin = spec.y; src_yend = spec.y + spec.height; - x = xbegin; - int central = size / 2; + int central = window_size / 2; int skip = src_xbegin - xbegin + central; if (skip < 0) @@ -78,31 +97,53 @@ template class BayerDemosaicing { int xstart = xbegin - central + skip; - for (int i = 0; i < size; i++) { + for (int i = 0; i < window_size; i++) { column_mapping[i] = i; } - for (int i = 0; i < size; i++) { + for (int i = 0; i < window_size; i++) { int ystart = y - central + i; - if (ystart < src_ybegin) { - ystart = src_ybegin + (src_ybegin - ystart) % 2; - } else if (ystart > src_yend - 1) { - ystart = src_yend - 1 - (ystart - src_yend + 1) % 2; + while (ystart < src_ybegin) { + ystart += pattern_size; + } + while (ystart > src_yend - 1) { + ystart -= pattern_size; } - int x_off = (xstart + x_offset) % 2; - int y_off = ((ystart + y_offset) % 2) * 2; + int x_off = (xstart + x_offset) % pattern_size; + int y_off = (ystart + y_offset) % pattern_size; Row row = { ImageBuf::ConstIterator(src, xstart, ystart), - { white_balance[y_off], white_balance[y_off + 1] }, - x_off }; - - for (int j = skip; j < size; j++) { + x_off, y_off, white_balance_values }; + + // Fill the window with the values needed to process the first + // (leftmost) pixel. First fetch the pixels which are directly + // available in the image. We may need to skip a few columns, + // as the image doesn't have any pixels to the left of the first + // pixel of the row. + for (int j = skip; j < window_size; j++) { row.data[j] = row.fetch(); } + // Now fill in the columns we had skipped. First, check if the + // rows we have already filled in have one with the same channel + // layout we need. If so, just copy the values. Otherwise + // calculate which column of the image would have such layout + // and read the pixels. for (int j = 0; j < skip; j++) { - row.data[j] = row.data[skip + (skip - j) % 2]; + int k = j - skip; + while (k < 0) + k += pattern_size; + + if (k + skip < window_size) { + row.data[j] = row.data[k + skip]; + } else { + float v; + src.getpixel(xstart + k, ystart, 0, &v, 1); + int x_off2 = (x_off + k) % pattern_size; + int chan = channel_map[y_off][x_off2]; + row.data[j] = v * white_balance_values[chan]; + } } rows.push_back(row); @@ -110,32 +151,18 @@ template class BayerDemosaicing { } /// Advances the sliding window to the right by one pixel. Rotates the - /// indices in the `column_mapping`. Fetches the rightmost column - /// from the source, if available. If we have reached the right border - /// and there are no more pixels to fetch, duplicates the values we - /// have fetched 2 steps ago, as the Bayer pattern repeats every 2 - /// columns. + /// indices in the `column_mapping`. void update() { - x++; - int curr = column_mapping[0]; - for (int i = 0; i < size - 1; i++) { + for (int i = 0; i < window_size - 1; i++) { column_mapping[i] = column_mapping[i + 1]; } - column_mapping[size - 1] = curr; + column_mapping[window_size - 1] = curr; - if (x + size / 2 < src_xend) { - for (int i = 0; i < size; i++) { - Row& row = rows[i]; - row.data[curr] = row.fetch(); - } - } else { - int src = column_mapping[size - 3]; - for (int i = 0; i < size; i++) { - Row& row = rows[i]; - row.data[curr] = row.data[src]; - } + for (int i = 0; i < window_size; i++) { + Row& row = rows[i]; + row.data[curr] = row.fetch(); } }; @@ -146,33 +173,59 @@ template class BayerDemosaicing { } }; - typedef void (*Decoder)(Window& window, ImageBuf::Iterator& out, - int chbegin); + struct Context { + Window& window; + ImageBuf::Iterator& out; + int chbegin; + int skip; + int count; + }; + + /// Check the boundaries and process the pixel. We only need to check the + /// boundaries for the first and the last few pixels of each line. As soon + /// as we have reached the pixel aligned with the default layout, we can + /// process the full stride without needing to check the boundaries + /// (2 pixels for Bayer and 6 pixels for XTrans). + template + inline static bool check_and_decode(Context& context, const Func& func) + { + if constexpr (check) { + if (context.skip > 0) { + context.skip--; + } else if (context.count == 0) { + return true; + } else { + func(); + context.out++; + context.count--; + context.window.update(); + } + return false; + } + + func(); + context.out++; + context.count--; + context.window.update(); + return false; + } - /// The decoder function pointers in RGGB order. /// All subclasses must initialize this table. - Decoder decoders[2][2] = { { nullptr, nullptr }, { nullptr, nullptr } }; + typedef void (*Decoder)(Context& context); + Decoder fast_decoders[pattern_size]; + Decoder slow_decoders[pattern_size]; + + int x_offset = 0; + int y_offset = 0; + + std::string error; public: - bool process(ImageBuf& dst, const ImageBuf& src, const std::string& layout, - const float white_balance[4], ROI roi, int nthreads) + bool process(ImageBuf& dst, const ImageBuf& src, + const float (&white_balance)[4], ROI roi, int nthreads) { - int x_offset, y_offset; - - if (layout == "RGGB") { - x_offset = 0; - y_offset = 0; - } else if (layout == "GRBG") { - x_offset = 1; - y_offset = 0; - } else if (layout == "GBRG") { - x_offset = 0; - y_offset = 1; - } else if (layout == "BGGR") { - x_offset = 1; - y_offset = 1; - } else { - dst.errorfmt("BayerDemosaicing::process() invalid Bayer layout"); + if (error.length() > 0) { + dst.errorfmt("Demosaic::process() {}", error); return false; } @@ -180,109 +233,186 @@ template class BayerDemosaicing { ImageBuf::Iterator it(dst, roi); for (int y = roi.ybegin; y < roi.yend; y++) { - typename BayerDemosaicing::Window window( - y, roi.xbegin, src, x_offset, y_offset, white_balance); - - int r = (y_offset + y) % 2; - Decoder calc_0 = decoders[r][(x_offset + roi.xbegin + 0) % 2]; - Decoder calc_1 = decoders[r][(x_offset + roi.xbegin + 1) % 2]; + typename DemosaicingBase::Window + window(y, roi.xbegin, src, x_offset, y_offset, + white_balance); - assert(calc_0 != nullptr); - assert(calc_1 != nullptr); + int r = (y_offset + y) % pattern_size; + Decoder& fast_decoder = fast_decoders[r]; + Decoder& slow_decoder = slow_decoders[r]; - int x = roi.xbegin; + int skip = (x_offset + roi.xbegin) % pattern_size; + int count = roi.width(); + Context context = { window, it, roi.chbegin, skip, count }; - // Process the leftmost pixel first. - if (x < roi.xend) { - (calc_0)(window, it, 0); - it++; - x++; + if (skip > 0) { + slow_decoder(context); } - // Now, process two pixels at a time. - while (x < roi.xend - 1) { - window.update(); - (calc_1)(window, it, 0); - it++; - x++; - - window.update(); - (calc_0)(window, it, 0); - it++; - x++; + size_t cc = context.count / pattern_size; + for (size_t i = 0; i < cc; i++) { + fast_decoder(context); } - // Process the rightmost pixel if needed. - if (x < roi.xend) { - window.update(); - (calc_1)(window, it, 0); - it++; - } + slow_decoder(context); } }); return true; }; -}; + inline static size_t channel_at_offset(int x_offset, int y_offset) + { + return channel_map[y_offset % pattern_size][x_offset % pattern_size]; + } -template -class LinearBayerDemosaicing : public BayerDemosaicing { -private: - static void - calc_red(typename BayerDemosaicing::Window& w, - ImageBuf::Iterator& out, int chbegin) + static void layout_from_offset(int x_offset, int y_offset, + std::string& layout, bool whitespaces) { - out[chbegin + 0] = w(1, 1); - out[chbegin + 1] = (w(0, 1) + w(2, 1) + w(1, 0) + w(1, 2)) / 4.0f; - out[chbegin + 2] = (w(0, 0) + w(0, 2) + w(2, 0) + w(2, 2)) / 4.0f; + const char* channels = "RGBG"; + + size_t length = pattern_size * pattern_size; + if (whitespaces) + length += pattern_size - 1; + + layout.resize(length); + + size_t i = 0; + for (size_t y = 0; y < pattern_size; y++) { + for (size_t x = 0; x < pattern_size; x++) { + int c = channel_at_offset(x + x_offset, y + y_offset); + assert(c < 4); + + layout[i] = channels[c]; + i++; + } + if (whitespaces) { + layout[i] = ' '; + i++; + } + } } - static void - calc_blue(typename BayerDemosaicing::Window& w, - ImageBuf::Iterator& out, int chbegin) + bool init_offsets(const std::string& layout) { - out[chbegin + 0] = (w(0, 0) + w(0, 2) + w(2, 0) + w(2, 2)) / 4.0f; - out[chbegin + 1] = (w(0, 1) + w(2, 1) + w(1, 0) + w(1, 2)) / 4.0f; - out[chbegin + 2] = w(1, 1); + if (layout.length() == 0) { + x_offset = 0; + y_offset = 0; + return true; + } + + std::string stripped_layout = layout; + if (layout.size() == pattern_size * pattern_size + pattern_size - 1) { + stripped_layout.erase(std::remove(stripped_layout.begin(), + stripped_layout.end(), ' '), + stripped_layout.end()); + } + + std::string current_layout; + + for (size_t y = 0; y < pattern_size; y++) { + for (size_t x = 0; x < pattern_size; x++) { + layout_from_offset(x, y, current_layout, false); + if (stripped_layout == current_layout) { + x_offset = x; + y_offset = y; + return true; + } + } + } + + x_offset = 0; + y_offset = 0; + error = "unrecognised layout \"" + layout + "\""; + return false; } - static void - calc_green_in_red(typename BayerDemosaicing::Window& w, - ImageBuf::Iterator& out, int chbegin) + DemosaicingBase(const std::string& layout) { this->init_offsets(layout); } +}; + +constexpr size_t bayer_channel_map[2][2] = { + { 0, 1 }, // RG + { 3, 2 } // GB +}; + +template +class BayerDemosaicing + : public DemosaicingBase { +public: + using DemosaicingBase::DemosaicingBase; +}; + +template +class LinearBayerDemosaicing : public BayerDemosaicing { +private: + using Window = typename LinearBayerDemosaicing::Window; + using Context = typename LinearBayerDemosaicing::Context; + + template static void calc_RG(Context& c) { - out[chbegin + 0] = (w(1, 0) + w(1, 2)) / 2.0f; - out[chbegin + 1] = w(1, 1); - out[chbegin + 2] = (w(0, 1) + w(2, 1)) / 2.0f; + auto& w = c.window; + auto ch = c.chbegin; + + if (LinearBayerDemosaicing::template check_and_decode(c, [&c, &w, + ch]() { + c.out[ch + 0] = w(1, 1); + c.out[ch + 1] = (w(0, 1) + w(2, 1) + w(1, 0) + w(1, 2)) / 4.0f; + c.out[ch + 2] = (w(0, 0) + w(0, 2) + w(2, 0) + w(2, 2)) / 4.0f; + })) + return; + + if (LinearBayerDemosaicing::template check_and_decode(c, [&c, &w, + ch]() { + c.out[ch + 0] = (w(1, 0) + w(1, 2)) / 2.0f; + c.out[ch + 1] = w(1, 1); + c.out[ch + 2] = (w(0, 1) + w(2, 1)) / 2.0f; + })) + return; } - static void - calc_green_in_blue(typename BayerDemosaicing::Window& w, - ImageBuf::Iterator& out, int chbegin) + template static void calc_GB(Context& c) { - out[chbegin + 0] = (w(0, 1) + w(2, 1)) / 2.0f; - out[chbegin + 1] = w(1, 1); - out[chbegin + 2] = (w(1, 0) + w(1, 2)) / 2.0f; + auto& w = c.window; + auto ch = c.chbegin; + + if (LinearBayerDemosaicing::template check_and_decode(c, [&c, &w, + ch]() { + c.out[ch + 0] = (w(0, 1) + w(2, 1)) / 2.0f; + c.out[ch + 1] = w(1, 1); + c.out[ch + 2] = (w(1, 0) + w(1, 2)) / 2.0f; + })) + return; + + if (LinearBayerDemosaicing::template check_and_decode(c, [&c, &w, + ch]() { + c.out[ch + 0] = (w(0, 0) + w(0, 2) + w(2, 0) + w(2, 2)) / 4.0f; + c.out[ch + 1] = (w(0, 1) + w(2, 1) + w(1, 0) + w(1, 2)) / 4.0f; + c.out[ch + 2] = w(1, 1); + })) + return; } public: - LinearBayerDemosaicing() + LinearBayerDemosaicing(const std::string& layout) + : BayerDemosaicing(layout) { - BayerDemosaicing::decoders[0][0] = calc_red; - BayerDemosaicing::decoders[0][1] = calc_green_in_red; - BayerDemosaicing::decoders[1][0] - = calc_green_in_blue; - BayerDemosaicing::decoders[1][1] = calc_blue; + this->fast_decoders[0] = calc_RG; + this->fast_decoders[1] = calc_GB; + + this->slow_decoders[0] = calc_RG; + this->slow_decoders[1] = calc_GB; }; }; -template -class MHCBayerDemosaicing : public BayerDemosaicing { +template +class MHCBayerDemosaicing : public BayerDemosaicing { private: - inline static void - mix1(typename BayerDemosaicing::Window& w, - float& out_mix1, float& out_mix2) + using Window = typename MHCBayerDemosaicing::Window; + + inline static void mix1(Window& w, float& out_mix1, float& out_mix2) { float tmp = w(0, 2) + w(4, 2) + w(2, 0) + w(2, 4); out_mix1 = (8.0f * w(2, 2) @@ -295,9 +425,7 @@ class MHCBayerDemosaicing : public BayerDemosaicing { / 16.0f; } - inline static void - mix2(typename BayerDemosaicing::Window& w, - float& out_mix1, float& out_mix2) + inline static void mix2(Window& w, float& out_mix1, float& out_mix2) { float tmp = w(1, 1) + w(1, 3) + w(3, 1) + w(3, 3); @@ -311,92 +439,530 @@ class MHCBayerDemosaicing : public BayerDemosaicing { / 16.0f; } - static void - calc_red(typename BayerDemosaicing::Window& w, - ImageBuf::Iterator& out, int chbegin) + using Context = typename MHCBayerDemosaicing::Context; + + + template static void calc_RG(Context& c) + { + auto& w = c.window; + auto ch = c.chbegin; + + if (MHCBayerDemosaicing::template check_and_decode(c, [&c, &w, + ch]() { + float val1, val2; + mix1(w, val1, val2); + + c.out[ch + 0] = w(2, 2); + c.out[ch + 1] = val1; + c.out[ch + 2] = val2; + })) + return; + + if (MHCBayerDemosaicing::template check_and_decode(c, [&c, &w, + ch]() { + float val1, val2; + mix2(w, val1, val2); + + c.out[ch + 0] = val1; + c.out[ch + 1] = w(2, 2); + c.out[ch + 2] = val2; + })) + return; + } + + template static void calc_GB(Context& c) + { + auto& w = c.window; + auto ch = c.chbegin; + + if (MHCBayerDemosaicing::template check_and_decode(c, [&c, &w, + ch]() { + float val1, val2; + mix2(w, val1, val2); + + c.out[ch + 0] = val2; + c.out[ch + 1] = w(2, 2); + c.out[ch + 2] = val1; + })) + return; + + if (MHCBayerDemosaicing::template check_and_decode(c, [&c, &w, + ch]() { + float val1, val2; + mix1(w, val1, val2); + + c.out[ch + 0] = val2; + c.out[ch + 1] = val1; + c.out[ch + 2] = w(2, 2); + })) + return; + } + +public: + MHCBayerDemosaicing(const std::string& layout) + : BayerDemosaicing(layout) + { + this->fast_decoders[0] = calc_RG; + this->fast_decoders[1] = calc_GB; + + this->slow_decoders[0] = calc_RG; + this->slow_decoders[1] = calc_GB; + }; +}; + +constexpr size_t xtrans_channel_map[6][6] = { + { 1, 0, 2, 1, 2, 0 }, // GRBGBR + { 2, 1, 1, 0, 1, 1 }, // BGGRGG + { 0, 1, 1, 2, 1, 1 }, // RGGBGG + { 1, 2, 0, 1, 0, 2 }, // GBRGRB + { 0, 1, 1, 2, 1, 1 }, // RGGBGG + { 2, 1, 1, 0, 1, 1 }, // BGGRGG +}; + +template +class XTransDemosaicing + : public DemosaicingBase { +public: + using DemosaicingBase::DemosaicingBase; +}; + +template +class LinearXTransDemosaicing : public XTransDemosaicing { +private: + using Context = typename LinearXTransDemosaicing::Context; + + // ..b.. + // a.X.d + // ..c.. + inline static float cross(float a, float b, float c, float d) + { + return (a + d + (b + c) * 2.0) / 6.0; + } + + // ...b. + // .aX.. + // ...c. + inline static float triangle(float a, float b, float c) { - float val1, val2; - mix1(w, val1, val2); + return (a + (b + c) * M_SQRT1_2) / (1.0 + M_SQRT1_2 + M_SQRT1_2); + } - out[chbegin + 0] = w(2, 2); - out[chbegin + 1] = val1; - out[chbegin + 2] = val2; + // ..bd. + // .aX.. + // ..ce. + inline static float pentagon(float a, float b, float c, float d, float e) + { + return (a + b + c + (d + e) * M_SQRT1_2) + / (3.0 + M_SQRT1_2 + M_SQRT1_2); } - static void - calc_blue(typename BayerDemosaicing::Window& w, - ImageBuf::Iterator& out, int chbegin) + // ...b. + // .aX.. + // ....d + // ..c.. + inline static float square(float a, float b, float c, float d) { - float val1, val2; - mix1(w, val1, val2); + return (a + b * M_SQRT1_2 + c * 0.5 + d / sqrt(5.0)) + / (1.5 + M_SQRT1_2 + 1.0 / sqrt(5.0)); + } + + template inline static bool calc_GRB_bgg(Context& c) + { + auto& w = c.window; + auto ch = c.chbegin; + + if (LinearXTransDemosaicing::template check_and_decode( + c, [&c, &w, ch]() { + // ggrgg + // ggbgg + // brGrb + // ggbgg + // ggrgg + c.out[ch + 0] = cross(w(0, 2), w(2, 1), w(2, 3), w(4, 2)); + c.out[ch + 1] = w(2, 2); + c.out[ch + 2] = cross(w(2, 0), w(1, 2), w(3, 2), w(2, 4)); + })) + return true; + + if (LinearXTransDemosaicing::template check_and_decode( + c, [&c, &w, ch]() { + // grggb + // gbggr + // rgRbg + // gbggr + // grggb + c.out[ch + 0] = w(2, 2); + c.out[ch + 1] = pentagon(w(2, 1), w(1, 2), w(3, 2), w(1, 3), + w(3, 3)); + c.out[ch + 2] = triangle(w(2, 3), w(1, 1), w(3, 1)); + })) + return true; + + if (LinearXTransDemosaicing::template check_and_decode( + c, [&c, &w, ch]() { + // rggbg + // bggrg + // grBgb + // bggrg + // rggbg + c.out[ch + 0] = triangle(w(2, 1), w(1, 3), w(3, 3)); + c.out[ch + 1] = pentagon(w(2, 3), w(1, 2), w(3, 2), w(1, 1), + w(3, 1)); + c.out[ch + 2] = w(2, 2); + })) + return true; + + return false; + } - out[chbegin + 0] = val2; - out[chbegin + 1] = val1; - out[chbegin + 2] = w(2, 2); + template inline static bool calc_GBR_rgg(Context& c) + { + auto& w = c.window; + auto ch = c.chbegin; + + if (LinearXTransDemosaicing::template check_and_decode( + c, [&c, &w, ch]() { + // ggbgg + // ggrgg + // rbGbr + // ggrgg + // ggbgg + c.out[ch + 0] = cross(w(2, 0), w(1, 2), w(3, 2), w(2, 4)); + c.out[ch + 1] = w(2, 2); + c.out[ch + 2] = cross(w(0, 2), w(2, 1), w(2, 3), w(4, 2)); + })) + return true; + + if (LinearXTransDemosaicing::template check_and_decode( + c, [&c, &w, ch]() { + // gbggr + // grggb + // bgBrg + // grggb + // gbggr + c.out[ch + 0] = triangle(w(2, 3), w(1, 1), w(3, 1)); + c.out[ch + 1] = pentagon(w(2, 1), w(1, 2), w(3, 2), w(1, 3), + w(3, 3)); + c.out[ch + 2] = w(2, 2); + })) + return true; + + if (LinearXTransDemosaicing::template check_and_decode( + c, [&c, &w, ch]() { + // bggrg + // rggbg + // gbRgr + // rggbg + // bggrg + c.out[ch + 0] = w(2, 2); + c.out[ch + 1] = pentagon(w(2, 3), w(1, 2), w(3, 2), w(1, 1), + w(3, 1)); + c.out[ch + 2] = triangle(w(2, 1), w(1, 3), w(3, 3)); + })) + return true; + + return false; } - static void - calc_green_in_red(typename BayerDemosaicing::Window& w, - ImageBuf::Iterator& out, int chbegin) + template inline static bool calc_BGG_rgg(Context& c) { - float val1, val2; - mix2(w, val1, val2); + auto& w = c.window; + auto ch = c.chbegin; + + if (LinearXTransDemosaicing::template check_and_decode( + c, [&c, &w, ch]() { + // ggbgg + // brgrb + // ggBgg + // ggrgg + // rbgbr + c.out[ch + 0] = triangle(w(3, 2), w(1, 1), w(1, 3)); + c.out[ch + 1] = pentagon(w(1, 2), w(2, 1), w(2, 3), w(3, 1), + w(3, 3)); + c.out[ch + 2] = w(2, 2); + })) + return true; + + if (LinearXTransDemosaicing::template check_and_decode( + c, [&c, &w, ch]() { + // gbggr + // rgrbg + // gbGgr + // grggb + // bgbrg + c.out[ch + 0] = square(w(1, 2), w(3, 1), w(2, 4), w(4, 3)); + c.out[ch + 1] = w(2, 2); + c.out[ch + 2] = square(w(2, 1), w(1, 3), w(4, 2), w(3, 4)); + })) + return true; + + if (LinearXTransDemosaicing::template check_and_decode( + c, [&c, &w, ch]() { + // bggrg + // grbgb + // bgGrg + // rggbg + // gbrgr + c.out[ch + 0] = square(w(2, 3), w(1, 1), w(4, 2), w(3, 0)); + c.out[ch + 1] = w(2, 2); + c.out[ch + 2] = square(w(1, 2), w(3, 3), w(2, 0), w(4, 1)); + })) + return true; + + return false; + } - out[chbegin + 0] = val1; - out[chbegin + 1] = w(2, 2); - out[chbegin + 2] = val2; + template inline static bool calc_RGG_bgg(Context& c) + { + auto& w = c.window; + auto ch = c.chbegin; + + if (LinearXTransDemosaicing::template check_and_decode( + c, [&c, &w, ch]() { + // ggrgg + // rbgbr + // ggRgg + // ggbgg + // brgrb + c.out[ch + 0] = w(2, 2); + c.out[ch + 1] = pentagon(w(1, 2), w(2, 1), w(2, 3), w(3, 1), + w(3, 3)); + c.out[ch + 2] = triangle(w(3, 2), w(1, 1), w(1, 3)); + })) + return true; + + if (LinearXTransDemosaicing::template check_and_decode( + c, [&c, &w, ch]() { + // ggrggb + // rbgbrg + // ggrGgb + // ggbggr + // brgrbg + c.out[ch + 0] = square(w(2, 1), w(1, 3), w(4, 2), w(3, 4)); + c.out[ch + 1] = w(2, 2); + c.out[ch + 2] = square(w(1, 2), w(3, 1), w(2, 4), w(4, 3)); + })) + return true; + + if (LinearXTransDemosaicing::template check_and_decode( + c, [&c, &w, ch]() { + // grggbg + // bgbrgr + // grgGbg + // gbggrg + // rgrbgb + c.out[ch + 0] = square(w(1, 2), w(3, 3), w(2, 0), w(4, 1)); + c.out[ch + 1] = w(2, 2); + c.out[ch + 2] = square(w(2, 3), w(1, 1), w(4, 2), w(3, 0)); + })) + return true; + + return false; } - static void - calc_green_in_blue(typename BayerDemosaicing::Window& w, - ImageBuf::Iterator& out, int chbegin) + template inline static bool calc_RGG_gbr(Context& c) { - float val1, val2; - mix2(w, val1, val2); + auto& w = c.window; + auto ch = c.chbegin; + + if (LinearXTransDemosaicing::template check_and_decode( + c, [&c, &w, ch]() { + // brgrb + // ggbgg + // ggRgg + // rbgbr + // ggrgg + c.out[ch + 0] = w(2, 2); + c.out[ch + 1] = pentagon(w(3, 2), w(2, 1), w(2, 3), w(1, 1), + w(1, 3)); + c.out[ch + 2] = triangle(w(1, 2), w(3, 1), w(3, 3)); + })) + return true; + + if (LinearXTransDemosaicing::template check_and_decode( + c, [&c, &w, ch]() { + // rgrbg + // gbggr + // grGgb + // bgbrg + // grggb + c.out[ch + 0] = square(w(2, 1), w(3, 3), w(0, 2), w(1, 4)); + c.out[ch + 1] = w(2, 2); + c.out[ch + 2] = square(w(3, 2), w(1, 1), w(2, 4), w(0, 3)); + })) + return true; + + if (LinearXTransDemosaicing::template check_and_decode( + c, [&c, &w, ch]() { + // grbgb + // bggrg + // rgGbg + // gbrgr + // rggbg + c.out[ch + 0] = square(w(3, 2), w(1, 3), w(2, 0), w(3, 4)); + c.out[ch + 1] = w(2, 2); + c.out[ch + 2] = square(w(2, 3), w(3, 1), w(0, 2), w(1, 0)); + })) + return true; + + return false; + } - out[chbegin + 0] = val2; - out[chbegin + 1] = w(2, 2); - out[chbegin + 2] = val1; + template inline static bool calc_BGG_grb(Context& c) + { + auto& w = c.window; + auto ch = c.chbegin; + + if (LinearXTransDemosaicing::template check_and_decode( + c, [&c, &w, ch]() { + // rbgbr + // ggrgg + // ggBgg + // brgrb + // ggbgg + c.out[ch + 0] = triangle(w(1, 2), w(3, 1), w(3, 3)); + c.out[ch + 1] = pentagon(w(3, 2), w(2, 1), w(2, 3), w(1, 1), + w(1, 3)); + c.out[ch + 2] = w(2, 2); + })) + return true; + + if (LinearXTransDemosaicing::template check_and_decode( + c, [&c, &w, ch]() { + // bgbrg + // grggb + // gbGgr + // rgrbg + // gbggr + c.out[ch + 0] = square(w(3, 2), w(1, 1), w(2, 4), w(0, 3)); + c.out[ch + 1] = w(2, 2); + c.out[ch + 2] = square(w(2, 1), w(3, 3), w(0, 2), w(1, 4)); + })) + return true; + + if (LinearXTransDemosaicing::template check_and_decode( + c, [&c, &w, ch]() { + // gbrgr + // rggbg + // bgGrg + // grbgb + // bggrg + c.out[ch + 0] = square(w(2, 3), w(3, 1), w(0, 2), w(1, 0)); + c.out[ch + 1] = w(2, 2); + c.out[ch + 2] = square(w(3, 2), w(1, 3), w(2, 0), w(3, 4)); + })) + return true; + + return false; + } + + template static void calc_GRBGBR_bggrgg(Context& c) + { + if (calc_GRB_bgg(c)) + return; + if (calc_GBR_rgg(c)) + return; + } + + template static void calc_BGGRGG_rggbgg(Context& c) + { + if (calc_BGG_rgg(c)) + return; + if (calc_RGG_bgg(c)) + return; + } + + template static void calc_RGGBGG_gbrgrb(Context& c) + { + if (calc_RGG_gbr(c)) + return; + if (calc_BGG_grb(c)) + return; + } + + template static void calc_GBRGRB_rggbgg(Context& c) + { + if (calc_GBR_rgg(c)) + return; + if (calc_GRB_bgg(c)) + return; + } + + template static void calc_RGGBGG_bggrgg(Context& c) + { + if (calc_RGG_bgg(c)) + return; + if (calc_BGG_rgg(c)) + return; + } + + template static void calc_BGGRGG_grbgbr(Context& c) + { + if (calc_BGG_grb(c)) + return; + if (calc_RGG_gbr(c)) + return; } public: - MHCBayerDemosaicing() + LinearXTransDemosaicing(const std::string& layout) + : XTransDemosaicing(layout) { - BayerDemosaicing::decoders[0][0] = calc_red; - BayerDemosaicing::decoders[0][1] = calc_green_in_red; - BayerDemosaicing::decoders[1][0] - = calc_green_in_blue; - BayerDemosaicing::decoders[1][1] = calc_blue; - }; + this->slow_decoders[0] = calc_GRBGBR_bggrgg; + this->slow_decoders[1] = calc_BGGRGG_rggbgg; + this->slow_decoders[2] = calc_RGGBGG_gbrgrb; + this->slow_decoders[3] = calc_GBRGRB_rggbgg; + this->slow_decoders[4] = calc_RGGBGG_bggrgg; + this->slow_decoders[5] = calc_BGGRGG_grbgbr; + + this->fast_decoders[0] = calc_GRBGBR_bggrgg; + this->fast_decoders[1] = calc_BGGRGG_rggbgg; + this->fast_decoders[2] = calc_RGGBGG_gbrgrb; + this->fast_decoders[3] = calc_GBRGRB_rggbgg; + this->fast_decoders[4] = calc_RGGBGG_bggrgg; + this->fast_decoders[5] = calc_BGGRGG_grbgbr; + } }; template static bool bayer_demosaic_linear_impl(ImageBuf& dst, const ImageBuf& src, - const std::string& bayer_pattern, - const float white_balance[4], ROI roi, int nthreads) + const std::string& layout, + const float (&white_balance)[4], ROI roi, + int nthreads) { - LinearBayerDemosaicing obj; - return obj.process(dst, src, bayer_pattern, white_balance, roi, nthreads); + LinearBayerDemosaicing obj(layout); + return obj.process(dst, src, white_balance, roi, nthreads); } template static bool bayer_demosaic_MHC_impl(ImageBuf& dst, const ImageBuf& src, - const std::string& bayer_pattern, - const float white_balance[4], ROI roi, int nthreads) + const std::string& layout, + const float (&white_balance)[4], ROI roi, int nthreads) { - MHCBayerDemosaicing obj; - return obj.process(dst, src, bayer_pattern, white_balance, roi, nthreads); + MHCBayerDemosaicing obj(layout); + return obj.process(dst, src, white_balance, roi, nthreads); return true; } +template +static bool +xtrans_demosaic_linear_impl(ImageBuf& dst, const ImageBuf& src, + const std::string& layout, + const float (&white_balance)[4], ROI roi, + int nthreads) +{ + LinearXTransDemosaicing obj(layout); + return obj.process(dst, src, white_balance, roi, nthreads); +} bool -ImageBufAlgo::demosaic(ImageBuf& dst, const ImageBuf& src, KWArgs options, - ROI roi, int nthreads) +demosaic(ImageBuf& dst, const ImageBuf& src, KWArgs options, ROI roi, + int nthreads) { bool ok = false; pvt::LoggedTimer logtime("IBA::demosaic"); @@ -404,7 +970,8 @@ ImageBufAlgo::demosaic(ImageBuf& dst, const ImageBuf& src, KWArgs options, std::string pattern; std::string algorithm; std::string layout; - float white_balance_RGGB[4] = { 1.0f, 1.0f, 1.0f, 1.0f }; + float white_balance_RGBG[4] = { 1.0f, 1.0f, 1.0f, 1.0f }; + std::string error; for (auto&& pv : options) { if (pv.name() == pattern_us) { @@ -428,19 +995,19 @@ ImageBufAlgo::demosaic(ImageBuf& dst, const ImageBuf& src, KWArgs options, } else if (pv.name() == white_balance_us) { if (pv.type() == TypeFloat && pv.nvalues() == 4) { // The order in the options is always (R,G1,B,G2) - white_balance_RGGB[0] = pv.get_float_indexed(0); - white_balance_RGGB[1] = pv.get_float_indexed(1); - white_balance_RGGB[2] = pv.get_float_indexed(3); - white_balance_RGGB[3] = pv.get_float_indexed(2); + white_balance_RGBG[0] = pv.get_float_indexed(0); + white_balance_RGBG[1] = pv.get_float_indexed(1); + white_balance_RGBG[2] = pv.get_float_indexed(2); + white_balance_RGBG[3] = pv.get_float_indexed(3); - if (white_balance_RGGB[2] == 0) - white_balance_RGGB[2] = white_balance_RGGB[1]; + if (white_balance_RGBG[3] == 0) + white_balance_RGBG[3] = white_balance_RGBG[1]; } else if (pv.type() == TypeFloat && pv.nvalues() == 3) { // The order in the options is always (R,G,B) - white_balance_RGGB[0] = pv.get_float_indexed(0); - white_balance_RGGB[1] = pv.get_float_indexed(1); - white_balance_RGGB[2] = white_balance_RGGB[1]; - white_balance_RGGB[3] = pv.get_float_indexed(2); + white_balance_RGBG[0] = pv.get_float_indexed(0); + white_balance_RGBG[1] = pv.get_float_indexed(1); + white_balance_RGBG[2] = pv.get_float_indexed(2); + white_balance_RGBG[3] = white_balance_RGBG[2]; } else { dst.errorfmt("ImageBufAlgo::demosaic() invalid white balance"); } @@ -450,7 +1017,6 @@ ImageBufAlgo::demosaic(ImageBuf& dst, const ImageBuf& src, KWArgs options, } } - ROI dst_roi = roi; if (!dst_roi.defined()) { dst_roi = src.roi(); @@ -476,25 +1042,31 @@ ImageBufAlgo::demosaic(ImageBuf& dst, const ImageBuf& src, KWArgs options, algorithm = "linear"; } - if (layout.length() == 0) { - layout = "RGGB"; - } - if (algorithm == "linear") { OIIO_DISPATCH_COMMON_TYPES2(ok, "bayer_demosaic_linear", bayer_demosaic_linear_impl, dst.spec().format, src.spec().format, - dst, src, layout, white_balance_RGGB, - dst_roi, nthreads); + dst, src, layout, white_balance_RGBG, + dst_roi, 1); } else if (algorithm == "MHC") { OIIO_DISPATCH_COMMON_TYPES2(ok, "bayer_demosaic_MHC", bayer_demosaic_MHC_impl, dst.spec().format, src.spec().format, - dst, src, layout, white_balance_RGGB, - dst_roi, nthreads); + dst, src, layout, white_balance_RGBG, + dst_roi, 1); } else { dst.errorfmt("ImageBufAlgo::demosaic() invalid algorithm"); } + } else if (pattern == "xtrans") { + if (algorithm.length() == 0) { + algorithm = "linear"; + } + + OIIO_DISPATCH_COMMON_TYPES2(ok, "xtrans_demosaic_linear", + xtrans_demosaic_linear_impl, + dst.spec().format, src.spec().format, dst, + src, layout, white_balance_RGBG, dst_roi, + 1); } else { dst.errorfmt("ImageBufAlgo::demosaic() invalid pattern"); } @@ -503,8 +1075,7 @@ ImageBufAlgo::demosaic(ImageBuf& dst, const ImageBuf& src, KWArgs options, } ImageBuf -ImageBufAlgo::demosaic(const ImageBuf& src, KWArgs options, ROI roi, - int nthreads) +demosaic(const ImageBuf& src, KWArgs options, ROI roi, int nthreads) { ImageBuf result; bool ok = demosaic(result, src, options, roi, nthreads); @@ -513,4 +1084,106 @@ ImageBufAlgo::demosaic(const ImageBuf& src, KWArgs options, ROI roi, return result; } +/// Creates a mosaiced version of the input image using the provided Demosaic +/// class's pattern, layout, and white balancing weights. Used for testing. +template +void +mosaic(ImageBuf& dst, const ImageBuf& src, int x_offset, int y_offset, + const float (&wb)[4], int nthreads) +{ + ImageSpec src_spec = src.spec(); + + ROI roi = src.roi_full(); + + ImageBufAlgo::parallel_image(roi, nthreads, [&](ROI roi) { + ImageBuf::ConstIterator s(src, roi); + ImageBuf::Iterator d(dst, roi); + + for (int y = roi.ybegin; y < roi.yend; y++) { + for (int x = roi.xbegin; x < roi.xend; x++) { + size_t chan = Demosaic::channel_at_offset(x_offset + x, + y_offset + y); + size_t c = chan; + if (c == 3) + c = 1; + d[0] = s[c] / wb[chan]; + s++; + d++; + } + } + }); +} + +/// Creates a mosaiced version of the input image using the provided pattern, +/// layout, and white balancing weights. Returns the layout string calculated +/// from the given offsets. Used for testing. +template +std::string +mosaic(ImageBuf& dst, const ImageBuf& src, int x_offset, int y_offset, + const std::string& pattern, const float (&white_balance)[4], + int nthreads) +{ + std::string layout; + + if (pattern == "bayer") { + BayerDemosaicing::layout_from_offset(x_offset, + y_offset, layout, + false); + mosaic, T, T>(dst, src, x_offset, + y_offset, white_balance, + nthreads); + } else if (pattern == "xtrans") { + XTransDemosaicing::layout_from_offset(x_offset, + y_offset, layout, + false); + mosaic, T, T>(dst, src, x_offset, + y_offset, + white_balance, + nthreads); + } else { + return ""; + } + + + return layout; +} + +std::string +mosaic_float(ImageBuf& dst, const ImageBuf& src, int x_offset, int y_offset, + const std::string& pattern, const float (&white_balance)[4], + int nthreads) +{ + return mosaic(dst, src, x_offset, y_offset, pattern, white_balance, + nthreads); +} + +std::string +mosaic_half(ImageBuf& dst, const ImageBuf& src, int x_offset, int y_offset, + const std::string& pattern, const float (&white_balance)[4], + int nthreads) +{ + return mosaic(dst, src, x_offset, y_offset, pattern, white_balance, + nthreads); +} + +std::string +mosaic_uint16(ImageBuf& dst, const ImageBuf& src, int x_offset, int y_offset, + const std::string& pattern, const float (&white_balance)[4], + int nthreads) +{ + return mosaic(dst, src, x_offset, y_offset, pattern, + white_balance, nthreads); +} + +std::string +mosaic_uint8(ImageBuf& dst, const ImageBuf& src, int x_offset, int y_offset, + const std::string& pattern, const float (&white_balance)[4], + int nthreads) +{ + return mosaic(dst, src, x_offset, y_offset, pattern, white_balance, + nthreads); +} + +} // namespace ImageBufAlgo + OIIO_NAMESPACE_END diff --git a/src/libOpenImageIO/imagebufalgo_demosaic_prv.h b/src/libOpenImageIO/imagebufalgo_demosaic_prv.h new file mode 100644 index 0000000000..204a5f7bb2 --- /dev/null +++ b/src/libOpenImageIO/imagebufalgo_demosaic_prv.h @@ -0,0 +1,36 @@ +// Copyright Contributors to the OpenImageIO project. +// SPDX-License-Identifier: Apache-2.0 +// https://github.com/AcademySoftwareFoundation/OpenImageIO + + +#pragma once + +#include + +OIIO_NAMESPACE_BEGIN + +namespace ImageBufAlgo { + +std::string OIIO_API +mosaic_float(ImageBuf& dst, const ImageBuf& src, int x_offset, int y_offset, + const std::string& pattern, const float (&white_balance)[4], + int nthreads); + +std::string OIIO_API +mosaic_half(ImageBuf& dst, const ImageBuf& src, int x_offset, int y_offset, + const std::string& pattern, const float (&white_balance)[4], + int nthreads); + +std::string OIIO_API +mosaic_uint16(ImageBuf& dst, const ImageBuf& src, int x_offset, int y_offset, + const std::string& pattern, const float (&white_balance)[4], + int nthreads); + +std::string OIIO_API +mosaic_uint8(ImageBuf& dst, const ImageBuf& src, int x_offset, int y_offset, + const std::string& pattern, const float (&white_balance)[4], + int nthreads); + +} // namespace ImageBufAlgo + +OIIO_NAMESPACE_END diff --git a/src/libOpenImageIO/imagebufalgo_test.cpp b/src/libOpenImageIO/imagebufalgo_test.cpp index 9d02969da5..cc14daa63b 100644 --- a/src/libOpenImageIO/imagebufalgo_test.cpp +++ b/src/libOpenImageIO/imagebufalgo_test.cpp @@ -26,6 +26,8 @@ #include #include +#include "imagebufalgo_demosaic_prv.h" + #if USE_OPENCV # include #endif @@ -1310,6 +1312,206 @@ test_simple_perpixel() } +template +std::string +mosaic(ImageBuf& dst, const ImageBuf& src, int x_offset, int y_offset, + const std::string& pattern, const float (&white_balance)[4], + int nthreads); + +template<> +std::string +mosaic(ImageBuf& dst, const ImageBuf& src, int x_offset, int y_offset, + const std::string& pattern, const float (&white_balance)[4], + int nthreads) +{ + return ImageBufAlgo::mosaic_float(dst, src, x_offset, y_offset, pattern, + white_balance, nthreads); +} + +template<> +std::string +mosaic(ImageBuf& dst, const ImageBuf& src, int x_offset, int y_offset, + const std::string& pattern, const float (&white_balance)[4], + int nthreads) +{ + return ImageBufAlgo::mosaic_half(dst, src, x_offset, y_offset, pattern, + white_balance, nthreads); +} + +template<> +std::string +mosaic(ImageBuf& dst, const ImageBuf& src, int x_offset, int y_offset, + const std::string& pattern, const float (&white_balance)[4], + int nthreads) +{ + return ImageBufAlgo::mosaic_uint16(dst, src, x_offset, y_offset, pattern, + white_balance, nthreads); +} + +template<> +std::string +mosaic(ImageBuf& dst, const ImageBuf& src, int x_offset, int y_offset, + const std::string& pattern, const float (&white_balance)[4], + int nthreads) +{ + return ImageBufAlgo::mosaic_uint8(dst, src, x_offset, y_offset, pattern, + white_balance, nthreads); +} + +struct DemosaicTestConfig { + const char* pattern; + size_t size_x; + size_t size_y; + size_t algos_count; +}; + +struct DemosaicTestAlgo { + const char* name; + const int inset; +}; + +template +static void +test_demosaic(const DemosaicTestConfig& config, const DemosaicTestAlgo* algos, + const ImageBuf& src_image, const float (&wb)[4], + const float thresholds[]) +{ + for (size_t y = 0; y < config.size_y; y++) { + for (size_t x = 0; x < config.size_x; x++) { + auto type = TypeDescFromC().value(); + + ImageSpec src_spec = src_image.spec(); + ImageSpec dst_spec(src_spec.width, src_spec.height, 1, type); + ImageBuf mosaiced_image(dst_spec); + + std::string layout = mosaic(mosaiced_image, src_image, x, y, + config.pattern, wb, 0); + + std::string pattern(config.pattern); + std::string ext = type.is_floating_point() ? "exr" : "png"; + + if (write_images) { + std::string path = pattern + "_" + std::string(type.c_str()) + + "_" + std::to_string(y) + "_" + + std::to_string(x) + "_src." + ext; + + auto imageOutput = ImageOutput::create(ext); + imageOutput->open(path, mosaiced_image.spec()); + mosaiced_image.write(imageOutput.get()); + } + + for (size_t i = 0; i < config.algos_count; i++) { + std::string algo(algos[i].name); + + ParamValueList list; + list.push_back(ParamValue("pattern", pattern)); + list.push_back(ParamValue("algorithm", algo)); + list.push_back(ParamValue("layout", layout)); + list.push_back( + ParamValue("white_balance", TypeDesc::FLOAT, 4, wb)); + ImageBuf demosaiced_image + = OIIO::ImageBufAlgo::demosaic(mosaiced_image, list); + + int inset = algos[i].inset; + float threshold = thresholds[i]; + + ROI roi = src_image.roi(); + roi.xbegin += inset; + roi.ybegin += inset; + roi.xend -= inset; + roi.yend -= inset; + + ImageBufAlgo::CompareResults cr + = ImageBufAlgo::compare(src_image, demosaiced_image, + threshold, threshold, roi); + OIIO_CHECK_FALSE(cr.error); + + if (write_images) { + std::string path = pattern + "_" + std::string(type.c_str()) + + "_" + std::to_string(y) + "_" + + std::to_string(x) + "_" + algo + "." + + ext; + auto imageOutput = ImageOutput::create(ext); + imageOutput->open(path, demosaiced_image.spec()); + demosaiced_image.write(imageOutput.get()); + } + } + } + } +} + + +static void +test_demosaic() +{ + print("Testing Demosaicing\n"); + + ImageSpec src_spec(256, 256, 3, TypeDesc::FLOAT); + ImageBuf src_image(src_spec); + ImageBufAlgo::fill(src_image, { 0.0f, 0.0f, 0.9f }, { 0.0f, 0.9f, 0.0f }, + { 0.9f, 0.0f, 0.9f }, { 0.9f, 0.9f, 0.0f }); + + float wb[4] = { 2.0, 1.1, 1.5, 0.9 }; + + const DemosaicTestConfig bayerConfig = { "bayer", 2, 2, 2 }; + const DemosaicTestAlgo bayerAlgos[] = { { "linear", 1 }, { "MHC", 2 } }; + + // There are 6x6=36 possible permutations of the XTrans pattern, + // of which only 18 are unique. It is sufficient to only test all variants of + // the top 3 vertical offsets, the bottom half is the same, but somewhat + // shuffled. + const DemosaicTestConfig xtransConfig = { "xtrans", 6, 3, 1 }; + const DemosaicTestAlgo xtransAlgos[] = { { "linear", 2 } }; + + + const float bayer_thresholds[4][2] = { + { 1.8e-07, 2.4e-07 }, // float + { 0.00049, 0.00049 }, // half + { 3.1e-05, 4.6e-05 }, // int16 + { 0.0079, 0.012 } // int8 + }; + + const float xtrans_thresholds[4][1] = { + { 0.00099 }, // float + { 0.0015 }, // half + { 0.0011 }, // int16 + { 0.0079 } // int8 + }; + + constexpr bool write_files = false; + ImageBuf true_image; + + if (write_files) { + auto imageOutput = OIIO::ImageOutput::create("exr"); + imageOutput->open("source.exr", src_image.spec()); + src_image.write(imageOutput.get()); + } + + true_image.copy(src_image, TypeDesc::FLOAT); + test_demosaic(bayerConfig, bayerAlgos, true_image, wb, + bayer_thresholds[0]); + test_demosaic(xtransConfig, xtransAlgos, true_image, wb, + xtrans_thresholds[0]); + + true_image.copy(src_image, TypeDesc::HALF); + test_demosaic(bayerConfig, bayerAlgos, true_image, wb, + bayer_thresholds[1]); + test_demosaic(xtransConfig, xtransAlgos, true_image, wb, + xtrans_thresholds[1]); + + true_image.copy(src_image, TypeDesc::UINT16); + test_demosaic(bayerConfig, bayerAlgos, true_image, + wb, bayer_thresholds[2]); + test_demosaic(xtransConfig, xtransAlgos, true_image, + wb, xtrans_thresholds[2]); + + true_image.copy(src_image, TypeDesc::UINT8); + test_demosaic(bayerConfig, bayerAlgos, true_image, wb, + bayer_thresholds[3]); + test_demosaic(xtransConfig, xtransAlgos, true_image, + wb, xtrans_thresholds[3]); +} + int main(int argc, char** argv) @@ -1351,6 +1553,7 @@ main(int argc, char** argv) test_opencv(); test_color_management(); test_yee(); + test_demosaic(); test_simple_perpixel(); test_simple_perpixel(); diff --git a/src/raw.imageio/rawinput.cpp b/src/raw.imageio/rawinput.cpp index 7e70a06a21..93a9417f21 100644 --- a/src/raw.imageio/rawinput.cpp +++ b/src/raw.imageio/rawinput.cpp @@ -246,7 +246,7 @@ OIIO_PLUGIN_EXPORTS_END namespace { std::string -libraw_filter_to_str(unsigned int filters, const char* cdesc) +libraw_bayer_filter_to_str(unsigned int filters, const char* cdesc) { char result[5] = { 0, 0, 0, 0, 0 }; for (size_t i = 0; i < 4; i++) { @@ -256,6 +256,28 @@ libraw_filter_to_str(unsigned int filters, const char* cdesc) } return result; } + +std::string +libraw_xtrans_filter_to_str(char (&filters)[6][6]) +{ + const char mapping[3] = { 'R', 'G', 'B' }; + + std::string result; + result.resize(41); + + for (size_t y = 0; y < 6; y++) { + for (size_t x = 0; x < 6; x++) { + char c = filters[y][x]; + if (c > 2 || c < 0) + c = 0; + + result[y * 7 + x] = mapping[(size_t)c]; + } + if (y > 0) + result[y * 7 - 1] = ' '; + } + return result; +} } // namespace bool @@ -657,9 +679,20 @@ RawInput::open_raw(bool unpack, const std::string& name, m_spec.channelnames.emplace_back("Y"); // Put the details about the filter pattern into the metadata - std::string filter( - libraw_filter_to_str(m_processor->imgdata.idata.filters, - m_processor->imgdata.idata.cdesc)); + std::string filter; + const bool is_xtrans + = strncmp(m_processor->imgdata.idata.make, "Fujifilm", 8) == 0 + && m_processor->imgdata.idata.filters == 9; + + if (is_xtrans) { + filter = libraw_xtrans_filter_to_str( + m_processor->imgdata.idata.xtrans); + } else { + filter = libraw_bayer_filter_to_str( + m_processor->imgdata.idata.filters, + m_processor->imgdata.idata.cdesc); + } + if (filter.empty()) { filter = "unknown"; }