Finally got blackman-harris to work correctly.

This commit is contained in:
King_DuckZ 2018-09-21 10:19:22 +01:00
parent e40b7c1a29
commit 94f6085e36
5 changed files with 337 additions and 15 deletions

View file

@ -6,6 +6,7 @@ add_executable(${PROJECT_NAME}
block.cpp
icon_fetch.cpp
grid.cpp
resize_harris.cpp
)
target_link_libraries(${PROJECT_NAME}

View file

@ -4,6 +4,10 @@
#include "icon_fetch.hpp"
#include <cassert>
namespace {
const constexpr int g_icon_width = 16 * 5;
} //unnamed namespace
namespace implem {
void draw_icons (
int icon_size,
@ -52,8 +56,8 @@ void grid_drawer_trigger::attached (
tabstop(wd);
for (int z = 0; z < static_cast<int>(m_icons.size()); ++z) {
const int x = (16 + 2) * (z % 3);
const int y = (16 + 2) * (z / 3);
const int x = (g_icon_width + 2) * (z % 3);
const int y = (g_icon_width + 2) * (z / 3);
m_icons[z].output(*m_widget, nana::point(x, y));
}
}
@ -89,6 +93,6 @@ void grid_drawer_trigger::refresh (nana::paint::graphics& graph) {
if (m_memorycard and not m_icons_added) {
m_icons_added = true;
implem::draw_icons(16, *m_memorycard, m_icons, *m_widget);
implem::draw_icons(g_icon_width, *m_memorycard, m_icons, *m_widget);
}
}

View file

@ -1,4 +1,5 @@
#include "icon_fetch.hpp"
#include "resize_harris.hpp"
#include <cstdint>
#include <cassert>
#include <climits>
@ -63,10 +64,6 @@ namespace {
std::transform(data.begin(), data.end(), data.begin(), &to_srgb);
}
std::vector<float> bilinear_resize (int in_w, int in_h, const std::vector<float>& data, int out_w, int out_h) {
return data;
}
std::vector<float> rgb4_to_float (const std::vector<char>& data, const std::vector<uint8_t>& palette, int w, int h) {
assert(w * h);
std::vector<float> retval(w * h * 3);
@ -105,11 +102,12 @@ namespace {
const int scanl_sz = scanline_size(w, 24);
std::vector<char> retval(scanl_sz * h);
assert(retval.size() >= data.size());
assert(data.size() == w * h * 3);
for (int y = 0; y < h; ++y) {
for (int x = 0; x < w; ++x) {
for (int c = 0; c < 3; ++c) {
retval[y * scanl_sz + x * 3 + c] = static_cast<char>(static_cast<uint8_t>(data[y * scanl_sz + x * 3 + c] * 255.0f));
retval[y * scanl_sz + x * 3 + c] = static_cast<char>(static_cast<uint8_t>(data[y * w * 3 + x * 3 + c] * 255.0f));
}
}
}
@ -172,11 +170,10 @@ std::vector<std::vector<char>> icon_fetch (const ConstBlock& block, int width, i
std::vector<char> resized_rgb;
if (scale) {
std::vector<float> float_data = rgb4_to_float(orig_rgb, palette, in_width, in_height);
//srgb_to_linear(float_data, in_width, in_height);
//auto float_resized_rgb = bilinear_resize(in_width, in_height, float_data, width, height);
//linear_to_srgb(float_resized_rgb, width, height);
//resized_rgb = float_to_rgb24(float_resized_rgb, width, height);
resized_rgb = float_to_rgb24(float_data, width, height);
srgb_to_linear(float_data, in_width, in_height);
auto float_resized_rgb = blackmanharris_resize(in_width, in_height, float_data, width, height);
linear_to_srgb(float_resized_rgb, width, height);
resized_rgb = float_to_rgb24(float_resized_rgb, width, height);
}
else {
std::swap(resized_rgb, orig_rgb);
@ -185,9 +182,9 @@ std::vector<std::vector<char>> icon_fetch (const ConstBlock& block, int width, i
assert(frame.size() == data_offset);
frame.resize(data_offset + scan_sz * height);
for (int32_t y = 0; y < in_height; ++y) {
for (int32_t y = 0; y < height; ++y) {
std::copy_n(
resized_rgb.begin() + scan_sz * (in_height - 1 - y),
resized_rgb.begin() + scan_sz * (height - 1 - y),
scan_sz,
frame.begin() + data_offset + y * scan_sz
);

309
src/gui/resize_harris.cpp Normal file
View file

@ -0,0 +1,309 @@
/*
Copyright 2008 Larry Gritz and the other authors and contributors.
All Rights Reserved.
Based on BSD-licensed software Copyright 2004 NVIDIA Corp.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of the software's owners nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
(This is the Modified BSD License)
*/
#include "resize_harris.hpp"
#include <array>
#include <cmath>
#include <cassert>
#include <algorithm>
#include <numeric>
namespace {
template <typename T>
[[gnu::pure]]
T clamp (T v, T m, T M) {
if (v < m)
return m;
else if (v > M)
return M;
else
return v;
}
class ClampedCoords {
public:
ClampedCoords (int tw, int th) :
m_xpos(0), m_ypos(0),
m_xbegin(0),
m_xend(0),
m_total_width(tw), m_total_height(th)
{
}
void reset (int xbegin, int xend, int ybegin) {
m_xpos = m_xbegin = xbegin;
m_ypos = ybegin;
m_xend = xend;
}
ClampedCoords& operator++() {
if (++m_xpos < m_xend) {
// Special case: we only incremented x, didn't change y
// or z, and the previous position was within the data
// window. Call a shortcut version of pos.
}
else {
// Wrap to the next scanline
m_xpos = m_xbegin;
++m_ypos;
}
}
void advance_row() { ++m_ypos; }
int index (int nchannels) const { return xpos() * nchannels + ypos() * m_total_width * nchannels; }
private:
int xpos() const { return clamp(m_xpos, 0, m_total_width - 1); }
int ypos() const { return clamp(m_ypos, 0, m_total_height - 1); }
int m_xpos, m_ypos;
int m_xbegin;
int m_xend;
int m_total_width, m_total_height;
};
[[gnu::pure]]
/// Return (x-floor(x)) and put (int)floor(x) in *xint. This is similar
/// to the built-in modf, but returns a true int, always rounds down
/// (compared to modf which rounds toward 0), and always returns
/// frac >= 0 (comapred to modf which can return <0 if x<0).
float floorfrac (float x, int *xint)
{
#if 1
float f = std::floor(x);
*xint = int(f);
return x - f;
#else /* vvv This idiom is slower */
int i = ifloor(x);
*xint = i;
return x - static_cast<float>(i); // Return the fraction left over
#endif
}
[[gnu::pure]]
int ifloor (float x) {
return static_cast<int>(std::floor(x));
}
[[gnu::pure]]
float blackman_harris_bh1d (float x) {
if (x < -1.0f || x > 1.0f) // Early out if outside filter range
return 0.0f;
// Compute BH. Straight from classic BH paper, but the usual
// formula assumes that the filter is centered at 0.5, so scale:
x = (x + 1.0f) * 0.5f;
const float A0 = 0.35875f;
const float A1 = -0.48829f;
const float A2 = 0.14128f;
const float A3 = -0.01168f;
const float m_pi(M_PI);
#if 0
// original -- three cos calls!
return A0 + A1 * cosf(2.f * m_pi * x)
+ A2 * cosf(4.f * m_pi * x) + A3 * cosf(6.f * m_pi * x);
#else
// Use trig identintities to reduce to just one cos.
// https://en.wikipedia.org/wiki/List_of_trigonometric_identities
// cos(2x) = 2 cos^2(x) - 1
// cos(3x) = 4 cos^3(x) 3 cos(x)
float cos2pix = std::cos(2.f * m_pi * x);
float cos4pix = 2.0f * cos2pix * cos2pix - 1.0f;
float cos6pix = cos2pix * (2.0f * cos4pix - 1.0f);
return A0 + A1 * cos2pix + A2 * cos4pix + A3 * cos6pix;
#endif
}
[[gnu::pure]]
float blackman_harris_xfilt (float x, float wrad_inv) {
return blackman_harris_bh1d(x * wrad_inv);
}
template <typename IT, typename T>
void normalize (IT beg, IT end, T div) {
while (beg != end) {
*beg /= div;
++beg;
}
}
} //unnamed namespace
std::vector<float> blackmanharris_resize (
int in_w,
int in_h,
const std::vector<float>& data,
int out_w,
int out_h
) {
assert(in_w > 0 and in_h > 0);
assert(out_w > 0 and out_h > 0);
//if (in_w == out_w and in_h == out_h)
// return data;
const constexpr int nchannels = 3;
// Local copies of the source image window, converted to float
const constexpr float srcfx = 0.0f;
const constexpr float srcfy = 0.0f;
const float srcfw = static_cast<float>(in_w);
const float srcfh = static_cast<float>(in_h);
// Ratios of dst/src size. Values larger than 1 indicate that we
// are maximizing (enlarging the image), and thus want to smoothly
// interpolate. Values less than 1 indicate that we are minimizing
// (shrinking the image), and thus want to properly filter out the
// high frequencies.
const float xratio = static_cast<float>(out_w) / srcfw; // 2 upsize, 0.5 downsize
const float yratio = static_cast<float>(out_h) / srcfh;
const float filter_width = 3.0f * std::max(1.0f, xratio);
const float& filter_height = filter_width;
const constexpr float dstfx = 0.0f;
const constexpr float dstfy = 0.0f;
const float dstfw = static_cast<float>(out_w);
const float dstfh = static_cast<float>(out_h);
const float dstpixelwidth = 1.0f / dstfw;
const float dstpixelheight = 1.0f / dstfh;
std::array<float, nchannels> pel;
const float filterrad = filter_width / 2.0f;
// radi,radj is the filter radius, as an integer, in source pixels. We
// will filter the source over [x-radi, x+radi] X [y-radj,y+radj].
const int radi = static_cast<int>(std::ceil (filterrad/xratio));
const int radj = static_cast<int>(std::ceil (filterrad/yratio));
const int xtaps = 2*radi + 1;
const int ytaps = 2*radj + 1;
std::vector<float> yfiltval(ytaps);
// For separable filters, horizontal tap weights will be the same
// for every column. So we precompute all the tap weights for every
// x position we'll need. We do the same thing in y, but row by row
// inside the loop (since we never revisit a y row). This
// substantially speeds up resize.
std::vector<float> xfiltval_all(xtaps * out_w);
for (int x = 0; x < out_w; ++x) {
float * const xfiltval = xfiltval_all.data() + x * xtaps;
const float s = (x-dstfx+0.5f)*dstpixelwidth;
const float src_xf = srcfx + s * srcfw;
int src_x;
float src_xf_frac = floorfrac (src_xf, &src_x);
std::fill(pel.begin(), pel.end(), 0.0f);
for (int i = 0; i < xtaps; ++i) {
const float w = blackman_harris_xfilt (xratio * (i-radi-(src_xf_frac-0.5f)), 2.0f / filter_width);
xfiltval[i] = w;
}
const float totalweight_x = std::accumulate(xfiltval, xfiltval + xtaps, 0.0f);
if (totalweight_x != 0.0f) {
normalize(xfiltval, xfiltval + xtaps, totalweight_x);
}
}
// We're going to loop over all output pixels we're interested in.
//
// (s,t) = NDC space coordinates of the output sample we are computing.
// This is the "sample point".
// (src_xf, src_xf) = source pixel space float coordinates of the
// sample we're computing. We want to compute the weighted sum
// of all the source image pixels that fall under the filter when
// centered at that location.
// (src_x, src_y) = image space integer coordinates of the floor,
// i.e., the closest pixel in the source image.
// src_xf_frac and src_yf_frac are the position within that pixel
// of our sample.
//
// Separate cases for separable and non-separable filters.
#if !defined(NDEBUG)
std::vector<float> retval(nchannels * out_w * out_h, 1.0f);
#else
std::vector<float> retval(nchannels * out_w * out_h);
#endif
auto out = retval.begin();
ClampedCoords srcpel(in_w, in_h);
for (int y = 0; y < out_h; ++y) {
const float t = (y-dstfy+0.5f)*dstpixelheight;
const float src_yf = srcfy + t * srcfh;
int src_y;
const float src_yf_frac = floorfrac (src_yf, &src_y);
// If using separable filters, our vertical set of filter tap
// weights will be the same for the whole scanline we're on. Just
// compute and normalize them once.
for (int j = 0; j < ytaps; ++j) {
const float w = blackman_harris_xfilt(yratio * (j-radj-(src_yf_frac-0.5f)), 2.0f / filter_height);
yfiltval[j] = w;
}
const float totalweight_y = std::accumulate(yfiltval.begin(), yfiltval.end(), 0.0f);
if (totalweight_y != 0.0f) {
normalize(yfiltval.begin(), yfiltval.end(), totalweight_y);
}
for (int x = 0; x < out_w; ++x, out += nchannels) {
const float s = (x-dstfx+0.5f)*dstpixelwidth;
const float src_xf = srcfx + s * srcfw;
const int src_x = ifloor (src_xf);
std::fill_n(pel.begin(), pel.size(), 0.0f);
const float * const xfiltval = xfiltval_all.data() + x * xtaps;
const float totalweight_x = std::accumulate(xfiltval, xfiltval + xtaps, 0.0f);
if (totalweight_x != 0.0f) {
srcpel.reset(src_x-radi, src_x+radi+1, src_y-radj/*, src_y+radj+1*/);
for (int j = -radj; j <= radj; ++j) {
float wy = yfiltval[j+radj];
if (wy == 0.0f) {
// 0 weight for this y tap -- move to next line
//srcpel.pos (srcpel.x(), srcpel.y()+1, srcpel.z());
srcpel.advance_row();
continue;
}
for (int i = 0; i < xtaps; ++i, ++srcpel) {
const float w = wy * xfiltval[i];
if (w) {
const auto index = srcpel.index(nchannels);
for (int c = 0; c < nchannels; ++c) {
pel[c] += w * data[index + c];
}
}
}
}
}
// Copy the pixel value (already normalized) to the output.
if (totalweight_y == 0.0f) {
std::fill_n(out, nchannels, 0.0f);
}
else {
assert(pel.size() == nchannels);
std::copy_n(pel.begin(), pel.size(), out);
}
}
}
return retval;
}

11
src/gui/resize_harris.hpp Normal file
View file

@ -0,0 +1,11 @@
#pragma once
#include <vector>
std::vector<float> blackmanharris_resize (
int in_w,
int in_h,
const std::vector<float>& data,
int out_w,
int out_h
);