#include "rawimage.h" #include #include #include int THUMB_SIZE = 128; int THUMB_SIZE_BORDER = 138; int THUMB_SIZE_BORDER_Y = 158; double SATURATION = 0.95; bool QUALITY_RESIZE = true; #ifdef __SSE2__ template void fromPlanarSSE(const void *in, void *out, size_t count); #endif size_t RawImage::typeSize(RawImage::DataType type) { switch(type) { case RawImage::UINT8: return 1; case RawImage::UINT16: return 2; case RawImage::UINT32: case RawImage::FLOAT32: return 4; case RawImage::FLOAT64: return 8; default: return 1; } } void RawImage::allocate(uint32_t w, uint32_t h, uint32_t ch, DataType type) { m_width = w; m_height = h; m_channels = ch; m_ch = ch == 3 ? 4 : ch; m_origType = m_type = type; m_pixels = std::make_unique(m_width * m_height * m_ch * typeSize(type)); } RawImage::RawImage() { } RawImage::RawImage(uint32_t w, uint32_t h, uint32_t ch, DataType type) { allocate(w, h, ch, type); } RawImage::RawImage(const RawImage &d) { allocate(d.m_width, d.m_height, d.m_channels, d.m_type); std::memcpy(m_pixels.get(), d.m_pixels.get(), m_width * m_height * m_ch * typeSize(m_type)); m_stats = d.m_stats; } RawImage::RawImage(RawImage &&d) { m_pixels = std::move(d.m_pixels); m_original = std::move(d.m_original); m_width = d.m_width; m_height = d.m_height; m_channels = d.m_channels; m_ch = d.m_ch; m_type = d.m_type; m_origType = d.m_origType; m_stats = d.m_stats; m_thumbAspect = d.m_thumbAspect; } RawImage::RawImage(const QImage &img) { qDebug() << img; if(img.format() == QImage::Format_RGBX8888) { allocate(img.width(), img.height(), 3, UINT8); for(int i=0; i void calcStats(const T *data, size_t n, size_t w, RawImage::Stats &stats) { U sum[4] = {0}; U sumSq[4] = {0}; T min[4] = {std::numeric_limits::max(), std::numeric_limits::max(), std::numeric_limits::max(), std::numeric_limits::max()}; T max[4] = {std::numeric_limits::min(), std::numeric_limits::min(), std::numeric_limits::min(), std::numeric_limits::min()}; uint32_t histSize = 65536; if constexpr(std::is_same::value)histSize = 256; std::vector histogram[4]; histogram[0].resize(histSize); histogram[1].resize(histSize); histogram[2].resize(histSize); histogram[3].resize(histSize); T sat = SATURATION * std::numeric_limits::max(); if constexpr(!std::numeric_limits::is_integer)sat = SATURATION; uint32_t saturated[4] = {0}; auto statsFunc = [&](T d, int x) { sum[x] += d; sumSq[x] += (U)d * d; min[x] = std::min(min[x], d); max[x] = std::max(max[x], d); uint16_t idx; if constexpr(std::is_same::value)idx = d >> 16; if constexpr(std::is_same::value || std::is_same::value)idx = d; if constexpr(!std::numeric_limits::is_integer)idx = std::clamp((T)d * histSize, (T)0.0, (T)65535.0); histogram[x][idx]++; if(d > sat)saturated[x]++; }; auto findMedian = [histSize](std::vector &histogram, size_t n) -> size_t { size_t histSum = 0; for(size_t o=0; o < histSize; o++) { histSum += histogram[o]; if(histSum >= n/2) return o; } return 0; }; size_t na[4] = {n, n, n, n}; if constexpr(ch == 1) { na[1] /= 4; na[2] /= 2; na[3] /= 4; } for(size_t i = 0; i < n; i++) { statsFunc(data[i*ch], 0); if constexpr(ch >= 3) { statsFunc(data[i*ch + 1], 1); statsFunc(data[i*ch + 2], 2); } } if constexpr(ch == 1) { size_t h = (n / w) & (SIZE_MAX-1); w &= (SIZE_MAX-1); for(size_t y=0; y madHist(histSize, 0); madHist[0] = histogram[i][median]; for(size_t o = 1; o < histSize; o++) { if(median + o < histSize)madHist[o] += histogram[i][median + o]; if(o <= median)madHist[o] += histogram[i][median - o]; } stats.m_mad[i] = findMedian(madHist, na[i]); if constexpr(!std::numeric_limits::is_integer) { stats.m_median[i] /= 65535.0; stats.m_mad[i] /= 65535.0; } } stats.m_histogram.resize(histSize, 0); for(size_t i = 0; i < histSize; i++) for(size_t o = 0; o < ch; o++) stats.m_histogram[i] += histogram[o][i]; } void RawImage::calcStats() { if(m_stats.m_stats)return; m_stats.m_stats = true; switch(m_origType) { case UINT8: if(channels()==1) ::calcStats(static_cast(origData()), size(), width(), m_stats); else ::calcStats(static_cast(origData()), size(), width(), m_stats); break; case UINT16: if(channels()==1) ::calcStats(static_cast(origData()), size(), width(), m_stats); else ::calcStats(static_cast(origData()), size(), width(), m_stats); break; case UINT32: if(channels()==1) ::calcStats(static_cast(origData()), size(), width(), m_stats); else ::calcStats(static_cast(origData()), size(), width(), m_stats); break; case FLOAT32: if(channels()==1) ::calcStats(static_cast(origData()), size(), width(), m_stats); else ::calcStats(static_cast(origData()), size(), width(), m_stats); break; case FLOAT64: if(channels()==1) ::calcStats(static_cast(origData()), size(), width(), m_stats); else ::calcStats(static_cast(origData()), size(), width(), m_stats); break; } } void RawImage::rect(int &x, int &y, int w, int h, std::vector &r) const { /*r.resize(w*h); x -= w/2; y -= h/2; if(x<0)x = 0; if(y<0)y = 0; if(x+w >= m_img.cols)x = m_img.cols-w; if(y+h >= m_img.rows)y = m_img.rows-h; cv::Mat roiImg(m_img, cv::Rect(x, y, w, h)); cv::Mat doubleMat; roiImg.convertTo(doubleMat, CV_64F); r = std::vector(doubleMat.begin(), doubleMat.end());*/ } int RawImage::findPeaks(double background, double distance, std::vector &peaks) const { /*std::vector> contours; cv::Mat kernel = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(distance, distance)); cv::Mat img, mask, dilate, locMax, result; if(m_img.channels() == 1)img = m_img; else cv::cvtColor(m_img, img, cv::COLOR_RGB2GRAY); cv::dilate(img, dilate, kernel); cv::compare(img, dilate, locMax, cv::CMP_GE); cv::compare(img, cv::Scalar(background), mask, cv::CMP_GT); cv::bitwise_and(locMax, mask, result); cv::findContours(result, contours, cv::noArray(), cv::RETR_EXTERNAL, cv::CHAIN_APPROX_SIMPLE); peaks.reserve(contours.size()); for(auto contour : contours) { peaks.push_back(Peak(1, contour[0].x, contour[0].y)); } return peaks.size();*/ } uint32_t RawImage::width() const { return m_width; } uint32_t RawImage::height() const { return m_height; } uint32_t RawImage::channels() const { return m_channels; } uint32_t RawImage::size() const { return width()*height(); } RawImage::DataType RawImage::type() const { return m_type; } uint32_t RawImage::norm() const { switch(m_type) { case UINT8: return UINT8_MAX; case UINT16: return UINT16_MAX; case UINT32: return UINT32_MAX; default: return 1; } } uint32_t RawImage::widthBytes() const { return m_ch * m_width; } void* RawImage::data() { return m_pixels.get(); } const void *RawImage::data() const { return m_pixels.get(); } void *RawImage::data(uint32_t row, uint32_t col) { return m_pixels.get() + (m_width * row * m_ch + col * m_ch) * typeSize(m_type); } const void *RawImage::data(uint32_t row, uint32_t col) const { return m_pixels.get() + (m_width * row * m_ch + col * m_ch) * typeSize(m_type); } const void *RawImage::origData() const { if(m_original) return m_original.get(); else return m_pixels.get(); } const void *RawImage::origData(uint32_t row, uint32_t col) const { if(m_original) return m_original.get() + (m_width * row * m_ch + col * m_ch) * typeSize(m_origType); else return m_pixels.get() + (m_width * row * m_ch + col * m_ch) * typeSize(m_type); } bool RawImage::planar() const { return m_planar; } void RawImage::convertToThumbnail() { if(!valid()) return; if(m_thumbAspect == 0.0f) m_thumbAspect = (float)width() / height(); std::unique_ptr outptr = std::make_unique(THUMB_SIZE * THUMB_SIZE * 4 * sizeof(uint16_t)); uint16_t *out = reinterpret_cast(outptr.get()); auto loop = [&](uint16_t *out, auto *in, auto scale) { for(int i=0; i(m_pixels.get()), 256); break; case UINT16: loop(out, reinterpret_cast(m_pixels.get()), 1); break; case UINT32: loop(out, reinterpret_cast(m_pixels.get()), UINT16_MAX/(float)UINT32_MAX); break; case FLOAT32: loop(out, reinterpret_cast(m_pixels.get()), 65535.0); break; default: qWarning() << "FLOAT64 should not happend"; return; } m_pixels = std::move(outptr); m_width = THUMB_SIZE; m_height = THUMB_SIZE; m_ch = 4; m_channels = 3; m_type = UINT16; } void RawImage::convertToGLFormat() { size_t s = size() * m_ch; if(m_type == UINT32) { m_original = std::move(m_pixels); allocate(m_width, m_height, m_channels, FLOAT32); m_origType = UINT32; float *dst = reinterpret_cast(m_pixels.get()); uint32_t *src = reinterpret_cast(m_original.get()); for(size_t i = 0; i < s; i++) dst[i] = src[i] / (float)UINT32_MAX; } else if(m_type == FLOAT64) { m_original = std::move(m_pixels); allocate(m_width, m_height, m_channels, FLOAT32); m_origType = FLOAT64; float *dst = reinterpret_cast(m_pixels.get()); double *src = reinterpret_cast(m_original.get()); for(size_t i = 0; i < s; i++) dst[i] = src[i]; } } float RawImage::thumbAspect() const { return m_thumbAspect; } bool RawImage::pixel(int x, int y, double &r, double &g, double &b) const { if(x < 0 || y < 0 || x >= (int)width() || y >= (int)height())return false; switch(m_origType) { case UINT8: { const uint8_t *v = static_cast(origData(y, x)); if(m_channels == 1) { r = g = b = *v; } else { r = v[0]; g = v[1]; b = v[2]; } break; } case UINT16: { const uint16_t *v = static_cast(origData(y, x)); if(m_channels == 1) { r = g = b = *v; } else { r = v[0]; g = v[1]; b = v[2]; } break; } case UINT32: { const uint32_t *v = static_cast(origData(y, x)); if(m_channels == 1) { r = g = b = *v; } else { r = v[0]; g = v[1]; b = v[2]; } break; } case FLOAT32: { const float *v = static_cast(origData(y, x)); if(m_channels == 1) { r = g = b = *v; } else { r = v[0]; g = v[1]; b = v[2]; } break; } case FLOAT64: { const double *v = static_cast(origData(y, x)); if(m_channels == 1) { r = g = b = *v; } else { r = v[0]; g = v[1]; b = v[2]; } break; } } return true; } template void boxResample(uint32_t w, uint32_t h, uint32_t ch, uint32_t oldw, uint32_t oldh, const uint8_t *in_, uint8_t *out_) { if(oldw == 0 || oldh == 0)return; const T *in = reinterpret_cast(in_); T *out = reinterpret_cast(out_); float max = 255.0f; if constexpr(std::is_same::value) max = UINT16_MAX; float sx = (float)w / oldw; float sy = (float)h / oldh; for(uint32_t y = 0; y < h; y++)//iterate over destination Y { for(uint32_t x = 0; x < w; x++)//iterate over destination X { float p[4] = {0.0f}; uint32_t xx = x * oldw / w;//calculate source rect uint32_t yy = y * oldh / h; uint32_t xe = std::min((x + 1) * oldw / w, oldw - 1); uint32_t ye = std::min((y + 1) * oldh / h, oldh - 1); for(uint32_t o = yy; o <= ye; o++)//iterate over source Y { float cy = o * sy - y; if(cy < 0.0f)cy = sy + cy; else if(sy + cy > 1.0f)cy = 1.0f - cy; else cy = sy; if(yy==ye)cy = 1.0f; for(uint32_t i = xx; i <= xe; i++)//iterate over source X { float cx = i * sx - x; if(cx < 0.0f)cx = sx + cx; else if(sx + cx > 1.0f)cx = 1.0f - cx; else cx = sx; if(xx==xe)cx = 1.0f; for(uint32_t z = 0; z < ch; z++) p[z] += in[(o * oldw + i) * ch + z] * cy * cx; } } for(uint32_t z = 0; z < ch; z++) if constexpr(std::is_floating_point::value) out[(y * w + x) * ch + z] = p[z]; else out[(y * w + x) * ch + z] = std::clamp(std::round(p[z]), 0.0f, max); } } } void RawImage::resize(uint32_t w, uint32_t h) { if(!valid())return; std::unique_ptr old_pixels = std::move(m_pixels); uint32_t oldw = m_width; uint32_t oldh = m_height; m_thumbAspect = (float)m_width / m_height; allocate(w, h, m_channels, m_type); switch(m_type) { case RawImage::UINT8: boxResample(w, h, m_ch, oldw, oldh, old_pixels.get(), m_pixels.get()); break; case RawImage::UINT16: boxResample(w, h, m_ch, oldw, oldh, old_pixels.get(), m_pixels.get()); break; case RawImage::FLOAT32: boxResample(w, h, m_ch, oldw, oldh, old_pixels.get(), m_pixels.get()); break; default: qWarning() << "Resizing format not supported"; break; } } std::pair RawImage::unitScale() const { float min = *std::min_element(m_stats.m_min, m_stats.m_min + 4); float max = *std::max_element(m_stats.m_max, m_stats.m_max + 4); if(m_origType == UINT32) { min /= (float)UINT32_MAX; max /= (float)UINT32_MAX; } if(min < 0.0f || max > 1.0f) return {1.0f / (max - min), min / (max - min)}; else return {1.0f, 0.0f}; } void RawImage::flip() { std::unique_ptr tmp = std::move(m_pixels); allocate(m_width, m_height, m_channels, m_type); uint32_t rowSize = m_width * m_ch * typeSize(m_type); for(uint32_t i=0; i RawImage::fromPlanar(const RawImage &img) { return RawImage::fromPlanar(img.data(), img.width(), img.height(), img.channels(), img.type()); } std::shared_ptr RawImage::fromPlanar(const void *pixels, uint32_t w, uint32_t h, uint32_t ch, RawImage::DataType type) { std::shared_ptr image = std::make_shared(w, h, ch, type); size_t size = w * h; size_t ch2 = ch == 1 ? 1 : 4; auto convert = [&](auto *in, auto *out, auto alpha) { for(size_t i=0; i(pixels, image->data(), size); else fromPlanarSSE(pixels, image->data(), size); #else convert(static_cast(pixels), static_cast(image->data()), UINT8_MAX); #endif break; case UINT16: #ifdef __SSE2__ if(ch==3) fromPlanarSSE(pixels, static_cast(image->data()), size); else fromPlanarSSE(pixels, static_cast(image->data()), size); #else convert(static_cast(pixels), static_cast(image->data()), UINT16_MAX); #endif break; case UINT32: #ifdef __SSE2__ if(ch==3) fromPlanarSSE(pixels, image->data(), size); else fromPlanarSSE(pixels, image->data(), size); #else convert(static_cast(pixels), static_cast(image->data()), UINT32_MAX); #endif break; case FLOAT32: #ifdef __SSE2__ if(ch==3) fromPlanarSSE(pixels, image->data(), size); else fromPlanarSSE(pixels, image->data(), size); #else convert(static_cast(pixels), static_cast(image->data()), 1); #endif break; case FLOAT64: convert(static_cast(pixels), static_cast(image->data()), 1); break; } image->m_planar = false; return image; } std::vector RawImage::split() const { std::vector planes; planes.resize(m_channels); for(size_t i=0; i(data()), static_cast(planes[i].data()), i); break; case UINT16: extract(static_cast(data()), static_cast(planes[i].data()), i); break; case UINT32: case FLOAT32: extract(static_cast(data()), static_cast(planes[i].data()), i); break; case FLOAT64: extract(static_cast(data()), static_cast(planes[i].data()), i); break; } } return planes; } bool RawImage::valid() const { return m_width > 0 && m_height > 0; }