vtk-m/vtkm/cont/ColorTable.cxx
Kenneth Moreland 38bdfec40a Move ColorTable::Sample methods to vtkm_cont
There is little value to declare them `inline`. Instead, just have them
compiled once in the `vtkm_cont` library.
2020-09-14 16:40:26 -06:00

1288 lines
42 KiB
C++

//============================================================================
// Copyright (c) Kitware, Inc.
// All rights reserved.
// See LICENSE.txt for details.
//
// This software is distributed WITHOUT ANY WARRANTY; without even
// the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
// PURPOSE. See the above copyright notice for more information.
//============================================================================
#include <algorithm>
#include <cctype>
#include <memory>
#include <vtkm/cont/ColorTable.h>
#include <vtkm/cont/ColorTable.hxx>
#include <vtkm/cont/ErrorBadType.h>
#include <vtkm/cont/TryExecute.h>
namespace
{
template <typename T>
struct MinDelta
{
};
// This value seems to work well for vtkm::Float32 ranges we have tested
template <>
struct MinDelta<vtkm::Float32>
{
static constexpr int value = 2048;
};
template <>
struct MinDelta<vtkm::Float64>
{
static constexpr vtkm::Int64 value = 2048L;
};
// Reperesents the following:
// T m = std::numeric_limits<T>::min();
// EquivSizeIntT im;
// std::memcpy(&im, &m, sizeof(T));
//
template <typename EquivSizeIntT>
struct MinRepresentable
{
};
template <>
struct MinRepresentable<vtkm::Float32>
{
static constexpr int value = 8388608;
};
template <>
struct MinRepresentable<vtkm::Float64>
{
static constexpr vtkm::Int64 value = 4503599627370496L;
};
inline bool rangeAlmostEqual(const vtkm::Range& r)
{
vtkm::Int64 irange[2];
// needs to be a memcpy to avoid strict aliasing issues, doing a count
// of 2*sizeof(T) to couple both values at the same time
std::memcpy(irange, &r.Min, sizeof(vtkm::Int64));
std::memcpy(irange + 1, &r.Max, sizeof(vtkm::Int64));
// determine the absolute delta between these two numbers.
const vtkm::Int64 delta = std::abs(irange[1] - irange[0]);
// If the numbers are not nearly equal, we don't touch them. This avoids running into
// pitfalls like BUG PV #17152.
return (delta < 1024) ? true : false;
}
template <typename T>
inline vtkm::Float64 expandRange(T r[2])
{
constexpr bool is_float32_type = std::is_same<T, vtkm::Float32>::value;
using IRange = typename std::conditional<is_float32_type, vtkm::Int32, vtkm::Int64>::type;
IRange irange[2];
// needs to be a memcpy to avoid strict aliasing issues, doing a count
// of 2*sizeof(T) to couple both values at the same time
std::memcpy(irange, r, sizeof(T) * 2);
const bool denormal = !std::isnormal(r[0]);
const IRange minInt = MinRepresentable<T>::value;
const IRange minDelta = denormal ? minInt + MinDelta<T>::value : MinDelta<T>::value;
// determine the absolute delta between these two numbers.
const vtkm::Int64 delta = std::abs(irange[1] - irange[0]);
// if our delta is smaller than the min delta push out the max value
// so that it is equal to minRange + minDelta. When our range is entirely
// negative we should instead subtract from our max, to max a larger negative
// value
if (delta < minDelta)
{
if (irange[0] < 0)
{
irange[1] = irange[0] - minDelta;
}
else
{
irange[1] = irange[0] + minDelta;
}
T result;
std::memcpy(&result, irange + 1, sizeof(T));
return static_cast<vtkm::Float64>(result);
}
return static_cast<vtkm::Float64>(r[1]);
}
inline vtkm::Range adjustRange(const vtkm::Range& r)
{
const bool spans_zero_boundary = r.Min < 0 && r.Max > 0;
if (spans_zero_boundary)
{ // nothing needs to be done, but this check is required.
// if we convert into integer space the delta difference will overflow
// an integer
return r;
}
if (rangeAlmostEqual(r))
{
return r;
}
// range should be left untouched as much as possible to
// to avoid loss of precision whenever possible. That is why
// we only modify the Max value
vtkm::Range result = r;
if (r.Min > static_cast<vtkm::Float64>(std::numeric_limits<vtkm::Float32>::lowest()) &&
r.Max < static_cast<vtkm::Float64>(std::numeric_limits<vtkm::Float32>::max()))
{ //We've found it best to offset it in vtkm::Float32 space if the numbers
//lay inside that representable range
vtkm::Float32 frange[2] = { static_cast<vtkm::Float32>(r.Min),
static_cast<vtkm::Float32>(r.Max) };
result.Max = expandRange(frange);
}
else
{
vtkm::Float64 drange[2] = { r.Min, r.Max };
result.Max = expandRange(drange);
}
return result;
}
inline vtkm::Vec3f_32 hsvTorgb(const vtkm::Vec3f_32& hsv)
{
vtkm::Vec3f_32 rgb;
constexpr vtkm::Float32 onethird = 1.0f / 3.0f;
constexpr vtkm::Float32 onesixth = 1.0f / 6.0f;
constexpr vtkm::Float32 twothird = 2.0f / 3.0f;
constexpr vtkm::Float32 fivesixth = 5.0f / 6.0f;
// compute RGB from HSV
if (hsv[0] > onesixth && hsv[0] <= onethird) // green/red
{
rgb[1] = 1.0f;
rgb[0] = (onethird - hsv[0]) * 6.0f;
rgb[2] = 0.0f;
}
else if (hsv[0] > onethird && hsv[0] <= 0.5f) // green/blue
{
rgb[1] = 1.0f;
rgb[2] = (hsv[0] - onethird) * 6.0f;
rgb[0] = 0.0f;
}
else if (hsv[0] > 0.5 && hsv[0] <= twothird) // blue/green
{
rgb[2] = 1.0f;
rgb[1] = (twothird - hsv[0]) * 6.0f;
rgb[0] = 0.0f;
}
else if (hsv[0] > twothird && hsv[0] <= fivesixth) // blue/red
{
rgb[2] = 1.0f;
rgb[0] = (hsv[0] - twothird) * 6.0f;
rgb[1] = 0.0f;
}
else if (hsv[0] > fivesixth && hsv[0] <= 1.0) // red/blue
{
rgb[0] = 1.0f;
rgb[2] = (1.0f - hsv[0]) * 6.0f;
rgb[1] = 0.0f;
}
else // red/green
{
rgb[0] = 1.0f;
rgb[1] = hsv[0] * 6;
rgb[2] = 0.0f;
}
// add Saturation to the equation.
rgb[0] = (hsv[1] * rgb[0] + (1.0f - hsv[1]));
rgb[1] = (hsv[1] * rgb[1] + (1.0f - hsv[1]));
rgb[2] = (hsv[1] * rgb[2] + (1.0f - hsv[1]));
rgb[0] *= hsv[2];
rgb[1] *= hsv[2];
rgb[2] *= hsv[2];
return rgb;
}
inline bool outside_vrange(vtkm::Float64 x)
{
return x < 0.0 || x > 1.0;
}
inline bool outside_vrange(vtkm::Float32 x)
{
return x < 0.0f || x > 1.0f;
}
template <typename T>
inline bool outside_vrange(const vtkm::Vec<T, 2>& x)
{
return outside_vrange(x[0]) || outside_vrange(x[1]);
}
template <typename T>
inline bool outside_vrange(const vtkm::Vec<T, 3>& x)
{
return outside_vrange(x[0]) || outside_vrange(x[1]) || outside_vrange(x[2]);
}
inline bool outside_range()
{
return false;
}
template <typename T>
inline bool outside_range(T&& t)
{
return outside_vrange(t);
}
template <typename T, typename U>
inline bool outside_range(T&& t, U&& u)
{
return outside_vrange(t) || outside_vrange(u);
}
template <typename T, typename U, typename V, typename... Args>
inline bool outside_range(T&& t, U&& u, V&& v, Args&&... args)
{
return outside_vrange(t) || outside_vrange(u) || outside_vrange(v) ||
outside_range(std::forward<Args>(args)...);
}
template <typename T>
inline vtkm::cont::ArrayHandle<T> buildSampleHandle(vtkm::Int32 numSamples,
T start,
T end,
T inc,
bool appendNanAndRangeColors)
{
//number of samples + end + appendNanAndRangeColors
vtkm::Int32 allocationSize = (appendNanAndRangeColors) ? numSamples + 5 : numSamples + 1;
vtkm::cont::ArrayHandle<T> handle;
handle.Allocate(allocationSize);
auto portal = handle.WritePortal();
vtkm::Id index = 0;
//Insert the below range first
if (appendNanAndRangeColors)
{
portal.Set(index++, std::numeric_limits<T>::lowest()); //below
}
//add number of samples which doesn't account for the end
T value = start;
for (vtkm::Int32 i = 0; i < numSamples; ++i, ++index, value += inc)
{
portal.Set(index, value);
}
portal.Set(index++, end);
if (appendNanAndRangeColors)
{
//push back the last value again so that when lookups near the max value
//occur we don't need to clamp as if they are out-of-bounds they will
//land in the extra 'end' color
portal.Set(index++, end);
portal.Set(index++, std::numeric_limits<T>::max()); //above
portal.Set(index++, vtkm::Nan<T>()); //nan
}
return handle;
}
template <typename OutputColors>
inline bool sampleColorTable(const vtkm::cont::ColorTable* self,
vtkm::Int32 numSamples,
OutputColors& colors,
vtkm::Float64 tolerance,
bool appendNanAndRangeColors)
{
vtkm::Range r = self->GetRange();
//We want the samples to start at Min, and end at Max so that means
//we want actually to interpolate numSample - 1 values. For example
//for range 0 - 1, we want the values 0, 0.5, and 1.
const vtkm::Float64 d_samples = static_cast<vtkm::Float64>(numSamples - 1);
const vtkm::Float64 d_delta = r.Length() / d_samples;
if (r.Min > static_cast<vtkm::Float64>(std::numeric_limits<vtkm::Float32>::lowest()) &&
r.Max < static_cast<vtkm::Float64>(std::numeric_limits<vtkm::Float32>::max()))
{
//we can try and see if Float32 space has enough resolution
const vtkm::Float32 f_samples = static_cast<vtkm::Float32>(numSamples - 1);
const vtkm::Float32 f_start = static_cast<vtkm::Float32>(r.Min);
const vtkm::Float32 f_delta = static_cast<vtkm::Float32>(r.Length()) / f_samples;
const vtkm::Float32 f_end = f_start + (f_delta * f_samples);
if (vtkm::Abs(static_cast<vtkm::Float64>(f_end) - r.Max) <= tolerance &&
vtkm::Abs(static_cast<vtkm::Float64>(f_delta) - d_delta) <= tolerance)
{
auto handle =
buildSampleHandle((numSamples - 1), f_start, f_end, f_delta, appendNanAndRangeColors);
return self->Map(handle, colors);
}
}
//otherwise we need to use Float64 space
auto handle = buildSampleHandle((numSamples - 1), r.Min, r.Max, d_delta, appendNanAndRangeColors);
return self->Map(handle, colors);
}
} // anonymous namespace
namespace vtkm
{
namespace cont
{
namespace detail
{
struct ColorTableInternals
{
std::string Name;
vtkm::ColorSpace Space = vtkm::ColorSpace::Lab;
vtkm::Range TableRange = { 1.0, 0.0 };
vtkm::Vec3f_32 NaNColor = { 0.5f, 0.0f, 0.0f };
vtkm::Vec3f_32 BelowRangeColor = { 0.0f, 0.0f, 0.0f };
vtkm::Vec3f_32 AboveRangeColor = { 0.0f, 0.0f, 0.0f };
bool UseClamping = true;
std::vector<vtkm::Float64> ColorNodePos;
std::vector<vtkm::Vec3f_32> ColorRGB;
std::vector<vtkm::Float64> OpacityNodePos;
std::vector<vtkm::Float32> OpacityAlpha;
std::vector<vtkm::Vec2f_32> OpacityMidSharp;
vtkm::cont::ArrayHandle<vtkm::Float64> ColorPosHandle;
vtkm::cont::ArrayHandle<vtkm::Vec3f_32> ColorRGBHandle;
vtkm::cont::ArrayHandle<vtkm::Float64> OpacityPosHandle;
vtkm::cont::ArrayHandle<vtkm::Float32> OpacityAlphaHandle;
vtkm::cont::ArrayHandle<vtkm::Vec2f_32> OpacityMidSharpHandle;
bool ColorArraysChanged = true;
bool OpacityArraysChanged = true;
vtkm::Id ModifiedCount = 1;
void Modified() { ++this->ModifiedCount; }
void RecalculateRange()
{
vtkm::Range r;
if (this->ColorNodePos.size() > 0)
{
r.Include(this->ColorNodePos.front());
r.Include(this->ColorNodePos.back());
}
if (this->OpacityNodePos.size() > 0)
{
r.Include(this->OpacityNodePos.front());
r.Include(this->OpacityNodePos.back());
}
this->TableRange = r;
}
};
} // namespace detail
namespace internal
{
std::set<std::string> GetPresetNames();
bool LoadColorTablePreset(vtkm::cont::ColorTable::Preset preset, vtkm::cont::ColorTable& table);
bool LoadColorTablePreset(std::string name, vtkm::cont::ColorTable& table);
} // namespace internal
//----------------------------------------------------------------------------
ColorTable::ColorTable(vtkm::cont::ColorTable::Preset preset)
: Internals(std::make_shared<detail::ColorTableInternals>())
{
const bool loaded = this->LoadPreset(preset);
if (!loaded)
{ //if we failed to load the requested color table, call SetColorSpace
//so that the internal host side cache is constructed and we leave
//the constructor in a valid state. We use LAB as it is the default
//when the no parameter constructor is called
this->SetColorSpace(vtkm::ColorSpace::Lab);
}
this->AddSegmentAlpha(
this->Internals->TableRange.Min, 1.0f, this->Internals->TableRange.Max, 1.0f);
}
//----------------------------------------------------------------------------
ColorTable::ColorTable(const std::string& name)
: Internals(std::make_shared<detail::ColorTableInternals>())
{
const bool loaded = this->LoadPreset(name);
if (!loaded)
{ //if we failed to load the requested color table, call SetColorSpace
//so that the internal host side cache is constructed and we leave
//the constructor in a valid state. We use LAB as it is the default
//when the no parameter constructor is called
this->SetColorSpace(vtkm::ColorSpace::Lab);
}
this->AddSegmentAlpha(
this->Internals->TableRange.Min, 1.0f, this->Internals->TableRange.Max, 1.0f);
}
//----------------------------------------------------------------------------
ColorTable::ColorTable(vtkm::ColorSpace space)
: Internals(std::make_shared<detail::ColorTableInternals>())
{
this->SetColorSpace(space);
}
//----------------------------------------------------------------------------
ColorTable::ColorTable(const vtkm::Range& range,
const vtkm::Vec3f_32& rgb1,
const vtkm::Vec3f_32& rgb2,
vtkm::ColorSpace space)
: Internals(std::make_shared<detail::ColorTableInternals>())
{
this->AddSegment(range.Min, rgb1, range.Max, rgb2);
this->AddSegmentAlpha(range.Min, 1.0f, range.Max, 1.0f);
this->SetColorSpace(space);
}
//----------------------------------------------------------------------------
ColorTable::ColorTable(const vtkm::Range& range,
const vtkm::Vec4f_32& rgba1,
const vtkm::Vec4f_32& rgba2,
vtkm::ColorSpace space)
: Internals(std::make_shared<detail::ColorTableInternals>())
{
vtkm::Vec3f_32 rgb1(rgba1[0], rgba1[1], rgba1[2]);
vtkm::Vec3f_32 rgb2(rgba2[0], rgba2[1], rgba2[2]);
this->AddSegment(range.Min, rgb1, range.Max, rgb2);
this->AddSegmentAlpha(range.Min, rgba1[3], range.Max, rgba2[3]);
this->SetColorSpace(space);
}
//----------------------------------------------------------------------------
ColorTable::ColorTable(const std::string& name,
vtkm::ColorSpace colorSpace,
const vtkm::Vec3f_64& nanColor,
const std::vector<vtkm::Float64>& rgbPoints,
const std::vector<vtkm::Float64>& alphaPoints)
: Internals(std::make_shared<detail::ColorTableInternals>())
{
this->SetName(name);
this->SetColorSpace(colorSpace);
this->SetNaNColor(nanColor);
this->FillColorTableFromDataPointer(static_cast<vtkm::Int32>(rgbPoints.size()), rgbPoints.data());
this->FillOpacityTableFromDataPointer(static_cast<vtkm::Int32>(alphaPoints.size()),
alphaPoints.data());
}
//----------------------------------------------------------------------------
ColorTable::~ColorTable() {}
//----------------------------------------------------------------------------
const std::string& ColorTable::GetName() const
{
return this->Internals->Name;
}
//----------------------------------------------------------------------------
void ColorTable::SetName(const std::string& name)
{
this->Internals->Name = name;
}
//----------------------------------------------------------------------------
bool ColorTable::LoadPreset(vtkm::cont::ColorTable::Preset preset)
{
return internal::LoadColorTablePreset(preset, *this);
}
//----------------------------------------------------------------------------
std::set<std::string> ColorTable::GetPresets()
{
return internal::GetPresetNames();
}
//----------------------------------------------------------------------------
bool ColorTable::LoadPreset(const std::string& name)
{
return internal::LoadColorTablePreset(name, *this);
}
//----------------------------------------------------------------------------
ColorTable ColorTable::MakeDeepCopy()
{
ColorTable dcopy(this->Internals->Space);
dcopy.Internals->TableRange = this->Internals->TableRange;
dcopy.Internals->NaNColor = this->Internals->NaNColor;
dcopy.Internals->BelowRangeColor = this->Internals->BelowRangeColor;
dcopy.Internals->AboveRangeColor = this->Internals->AboveRangeColor;
dcopy.Internals->UseClamping = this->Internals->UseClamping;
dcopy.Internals->ColorNodePos = this->Internals->ColorNodePos;
dcopy.Internals->ColorRGB = this->Internals->ColorRGB;
dcopy.Internals->OpacityNodePos = this->Internals->OpacityNodePos;
dcopy.Internals->OpacityAlpha = this->Internals->OpacityAlpha;
dcopy.Internals->OpacityMidSharp = this->Internals->OpacityMidSharp;
return dcopy;
}
//----------------------------------------------------------------------------
vtkm::ColorSpace ColorTable::GetColorSpace() const
{
return this->Internals->Space;
}
//----------------------------------------------------------------------------
void ColorTable::SetColorSpace(vtkm::ColorSpace space)
{
this->Internals->Space = space;
this->Internals->Modified();
}
//----------------------------------------------------------------------------
void ColorTable::SetClamping(bool state)
{
this->Internals->UseClamping = state;
this->Internals->Modified();
}
//----------------------------------------------------------------------------
bool ColorTable::GetClamping() const
{
return this->Internals->UseClamping;
}
//----------------------------------------------------------------------------
void ColorTable::SetBelowRangeColor(const vtkm::Vec3f_32& c)
{
this->Internals->BelowRangeColor = c;
this->Internals->Modified();
}
//----------------------------------------------------------------------------
const vtkm::Vec3f_32& ColorTable::GetBelowRangeColor() const
{
return this->Internals->BelowRangeColor;
}
//----------------------------------------------------------------------------
void ColorTable::SetAboveRangeColor(const vtkm::Vec3f_32& c)
{
this->Internals->AboveRangeColor = c;
this->Internals->Modified();
}
//----------------------------------------------------------------------------
const vtkm::Vec3f_32& ColorTable::GetAboveRangeColor() const
{
return this->Internals->AboveRangeColor;
}
//----------------------------------------------------------------------------
void ColorTable::SetNaNColor(const vtkm::Vec3f_32& c)
{
this->Internals->NaNColor = c;
this->Internals->Modified();
}
//----------------------------------------------------------------------------
const vtkm::Vec3f_32& ColorTable::GetNaNColor() const
{
return this->Internals->NaNColor;
}
//----------------------------------------------------------------------------
void ColorTable::Clear()
{
this->ClearColors();
this->ClearAlpha();
}
//---------------------------------------------------------------------------
void ColorTable::ClearColors()
{
this->Internals->ColorNodePos.clear();
this->Internals->ColorRGB.clear();
this->Internals->ColorArraysChanged = true;
this->Internals->Modified();
}
//---------------------------------------------------------------------------
void ColorTable::ClearAlpha()
{
this->Internals->OpacityNodePos.clear();
this->Internals->OpacityAlpha.clear();
this->Internals->OpacityMidSharp.clear();
this->Internals->OpacityArraysChanged = true;
this->Internals->Modified();
}
//---------------------------------------------------------------------------
void ColorTable::ReverseColors()
{
std::reverse(this->Internals->ColorRGB.begin(), this->Internals->ColorRGB.end());
this->Internals->ColorArraysChanged = true;
this->Internals->Modified();
}
//---------------------------------------------------------------------------
void ColorTable::ReverseAlpha()
{
std::reverse(this->Internals->OpacityAlpha.begin(), this->Internals->OpacityAlpha.end());
//To keep the shape correct the mid and sharp values of the last node are not included in the reversal
std::reverse(this->Internals->OpacityMidSharp.begin(),
this->Internals->OpacityMidSharp.end() - 1);
this->Internals->OpacityArraysChanged = true;
this->Internals->Modified();
}
//---------------------------------------------------------------------------
const vtkm::Range& ColorTable::GetRange() const
{
return this->Internals->TableRange;
}
//---------------------------------------------------------------------------
void ColorTable::RescaleToRange(const vtkm::Range& r)
{
if (r == this->GetRange())
{
return;
}
//make sure range has space.
auto newRange = adjustRange(r);
//slam control points down to 0.0 - 1.0, and than rescale to new range
const vtkm::Float64 minv = this->GetRange().Min;
const vtkm::Float64 oldScale = this->GetRange().Length();
const vtkm::Float64 newScale = newRange.Length();
VTKM_ASSERT(oldScale > 0);
VTKM_ASSERT(newScale > 0);
for (auto i = this->Internals->ColorNodePos.begin(); i != this->Internals->ColorNodePos.end();
++i)
{
const auto t = (*i - minv) / oldScale;
*i = (t * newScale) + newRange.Min;
}
for (auto i = this->Internals->OpacityNodePos.begin(); i != this->Internals->OpacityNodePos.end();
++i)
{
const auto t = (*i - minv) / oldScale;
*i = (t * newScale) + newRange.Min;
}
this->Internals->ColorArraysChanged = true;
this->Internals->OpacityArraysChanged = true;
this->Internals->TableRange = newRange;
this->Internals->Modified();
}
//---------------------------------------------------------------------------
vtkm::Int32 ColorTable::AddPoint(vtkm::Float64 x, const vtkm::Vec3f_32& rgb)
{
if (outside_range(rgb))
{
return -1;
}
std::size_t index = 0;
if (this->Internals->ColorNodePos.size() == 0 || this->Internals->ColorNodePos.back() < x)
{
this->Internals->ColorNodePos.emplace_back(x);
this->Internals->ColorRGB.emplace_back(rgb);
index = this->Internals->ColorNodePos.size();
}
else
{
auto begin = this->Internals->ColorNodePos.begin();
auto pos = std::lower_bound(begin, this->Internals->ColorNodePos.end(), x);
index = static_cast<std::size_t>(std::distance(begin, pos));
if (*pos == x)
{
this->Internals->ColorRGB[index] = rgb;
}
else
{
this->Internals->ColorRGB.emplace(
this->Internals->ColorRGB.begin() + std::distance(begin, pos), rgb);
this->Internals->ColorNodePos.emplace(pos, x);
}
}
this->Internals->TableRange.Include(x); //update range to include x
this->Internals->ColorArraysChanged = true;
this->Internals->Modified();
return static_cast<vtkm::Int32>(index);
}
//---------------------------------------------------------------------------
vtkm::Int32 ColorTable::AddPointHSV(vtkm::Float64 x, const vtkm::Vec3f_32& hsv)
{
return this->AddPoint(x, hsvTorgb(hsv));
}
//---------------------------------------------------------------------------
vtkm::Int32 ColorTable::AddSegment(vtkm::Float64 x1,
const vtkm::Vec3f_32& rgb1,
vtkm::Float64 x2,
const vtkm::Vec3f_32& rgb2)
{
if (outside_range(rgb1, rgb2))
{
return -1;
}
if (this->Internals->ColorNodePos.size() > 0)
{
//Todo:
// - This could be optimized so we do 2 less lower_bound calls when
// the table already exists
//When we add a segment we remove all points that are inside the line
auto nodeBegin = this->Internals->ColorNodePos.begin();
auto nodeEnd = this->Internals->ColorNodePos.end();
auto rgbBegin = this->Internals->ColorRGB.begin();
auto nodeStart = std::lower_bound(nodeBegin, nodeEnd, x1);
auto nodeStop = std::lower_bound(nodeBegin, nodeEnd, x2);
auto rgbStart = rgbBegin + std::distance(nodeBegin, nodeStart);
auto rgbStop = rgbBegin + std::distance(nodeBegin, nodeStop);
//erase is exclusive so if end->x == x2 it will be kept around, and
//than we will update it in AddPoint
this->Internals->ColorNodePos.erase(nodeStart, nodeStop);
this->Internals->ColorRGB.erase(rgbStart, rgbStop);
}
vtkm::Int32 pos = this->AddPoint(x1, rgb1);
this->AddPoint(x2, rgb2);
return pos;
}
//---------------------------------------------------------------------------
vtkm::Int32 ColorTable::AddSegmentHSV(vtkm::Float64 x1,
const vtkm::Vec3f_32& hsv1,
vtkm::Float64 x2,
const vtkm::Vec3f_32& hsv2)
{
return this->AddSegment(x1, hsvTorgb(hsv1), x2, hsvTorgb(hsv2));
}
//---------------------------------------------------------------------------
bool ColorTable::GetPoint(vtkm::Int32 index, vtkm::Vec4f_64& data) const
{
std::size_t i = static_cast<std::size_t>(index);
const std::size_t size = this->Internals->ColorNodePos.size();
if (index < 0 || i >= size)
{
return false;
}
const auto& pos = this->Internals->ColorNodePos[i];
const auto& rgb = this->Internals->ColorRGB[i];
data[0] = pos;
data[1] = rgb[0];
data[2] = rgb[1];
data[3] = rgb[2];
return true;
}
//---------------------------------------------------------------------------
vtkm::Int32 ColorTable::UpdatePoint(vtkm::Int32 index, const vtkm::Vec4f_64& data)
{
//skip data[0] as we don't care about position
if (outside_range(data[1], data[2], data[3]))
{
return -1;
}
std::size_t i = static_cast<std::size_t>(index);
const std::size_t size = this->Internals->ColorNodePos.size();
if (index < 0 || i >= size)
{
return -1;
}
//When updating the first question is has the relative position of the point changed?
//If it hasn't we can quickly just update the RGB value
auto oldPos = this->Internals->ColorNodePos.begin() + index;
auto newPos = std::lower_bound(
this->Internals->ColorNodePos.begin(), this->Internals->ColorNodePos.end(), data[0]);
if (oldPos == newPos)
{ //node's relative location hasn't changed
this->Internals->ColorArraysChanged = true;
auto& rgb = this->Internals->ColorRGB[i];
*newPos = data[0];
rgb[0] = static_cast<vtkm::Float32>(data[1]);
rgb[1] = static_cast<vtkm::Float32>(data[2]);
rgb[2] = static_cast<vtkm::Float32>(data[3]);
this->Internals->Modified();
return index;
}
else
{ //remove the point, and add the new values as the relative location is different
this->RemovePoint(index);
vtkm::Vec3f_32 newrgb(static_cast<vtkm::Float32>(data[1]),
static_cast<vtkm::Float32>(data[2]),
static_cast<vtkm::Float32>(data[3]));
return this->AddPoint(data[0], newrgb);
}
}
//---------------------------------------------------------------------------
bool ColorTable::RemovePoint(vtkm::Float64 x)
{
auto begin = this->Internals->ColorNodePos.begin();
auto pos = std::lower_bound(begin, this->Internals->ColorNodePos.end(), x);
return this->RemovePoint(static_cast<vtkm::Int32>(std::distance(begin, pos)));
}
//---------------------------------------------------------------------------
bool ColorTable::RemovePoint(vtkm::Int32 index)
{
std::size_t i = static_cast<std::size_t>(index);
const std::size_t size = this->Internals->ColorNodePos.size();
if (index < 0 || i >= size)
{
return false;
}
this->Internals->ColorNodePos.erase(this->Internals->ColorNodePos.begin() + index);
this->Internals->ColorRGB.erase(this->Internals->ColorRGB.begin() + index);
this->Internals->ColorArraysChanged = true;
this->Internals->RecalculateRange();
this->Internals->Modified();
return true;
}
//---------------------------------------------------------------------------
vtkm::Int32 ColorTable::GetNumberOfPoints() const
{
return static_cast<vtkm::Int32>(this->Internals->ColorNodePos.size());
}
//---------------------------------------------------------------------------
vtkm::Int32 ColorTable::AddPointAlpha(vtkm::Float64 x,
vtkm::Float32 alpha,
vtkm::Float32 midpoint,
vtkm::Float32 sharpness)
{
if (outside_range(alpha, midpoint, sharpness))
{
return -1;
}
const vtkm::Vec2f_32 midsharp(midpoint, sharpness);
std::size_t index = 0;
if (this->Internals->OpacityNodePos.size() == 0 || this->Internals->OpacityNodePos.back() < x)
{
this->Internals->OpacityNodePos.emplace_back(x);
this->Internals->OpacityAlpha.emplace_back(alpha);
this->Internals->OpacityMidSharp.emplace_back(midsharp);
index = this->Internals->OpacityNodePos.size();
}
else
{
auto begin = this->Internals->OpacityNodePos.begin();
auto pos = std::lower_bound(begin, this->Internals->OpacityNodePos.end(), x);
index = static_cast<std::size_t>(std::distance(begin, pos));
if (*pos == x)
{
this->Internals->OpacityAlpha[index] = alpha;
this->Internals->OpacityMidSharp[index] = midsharp;
}
else
{
this->Internals->OpacityAlpha.emplace(
this->Internals->OpacityAlpha.begin() + std::distance(begin, pos), alpha);
this->Internals->OpacityMidSharp.emplace(
this->Internals->OpacityMidSharp.begin() + std::distance(begin, pos), midsharp);
this->Internals->OpacityNodePos.emplace(pos, x);
}
}
this->Internals->OpacityArraysChanged = true;
this->Internals->TableRange.Include(x); //update range to include x
this->Internals->Modified();
return static_cast<vtkm::Int32>(index);
}
//---------------------------------------------------------------------------
vtkm::Int32 ColorTable::AddSegmentAlpha(vtkm::Float64 x1,
vtkm::Float32 alpha1,
vtkm::Float64 x2,
vtkm::Float32 alpha2,
const vtkm::Vec2f_32& mid_sharp1,
const vtkm::Vec2f_32& mid_sharp2)
{
if (outside_range(alpha1, alpha2, mid_sharp1, mid_sharp2))
{
return -1;
}
if (this->Internals->OpacityNodePos.size() > 0)
{
//Todo:
// - This could be optimized so we do 2 less lower_bound calls when
// the table already exists
//When we add a segment we remove all points that are inside the line
auto nodeBegin = this->Internals->OpacityNodePos.begin();
auto nodeEnd = this->Internals->OpacityNodePos.end();
auto alphaBegin = this->Internals->OpacityAlpha.begin();
auto midBegin = this->Internals->OpacityMidSharp.begin();
auto nodeStart = std::lower_bound(nodeBegin, nodeEnd, x1);
auto nodeStop = std::lower_bound(nodeBegin, nodeEnd, x2);
auto alphaStart = alphaBegin + std::distance(nodeBegin, nodeStart);
auto alphaStop = alphaBegin + std::distance(nodeBegin, nodeStop);
auto midStart = midBegin + std::distance(nodeBegin, nodeStart);
auto midStop = midBegin + std::distance(nodeBegin, nodeStop);
//erase is exclusive so if end->x == x2 it will be kept around, and
//than we will update it in AddPoint
this->Internals->OpacityNodePos.erase(nodeStart, nodeStop);
this->Internals->OpacityAlpha.erase(alphaStart, alphaStop);
this->Internals->OpacityMidSharp.erase(midStart, midStop);
}
vtkm::Int32 pos = this->AddPointAlpha(x1, alpha1, mid_sharp1[0], mid_sharp1[1]);
this->AddPointAlpha(x2, alpha2, mid_sharp2[0], mid_sharp2[1]);
return pos;
}
//---------------------------------------------------------------------------
bool ColorTable::GetPointAlpha(vtkm::Int32 index, vtkm::Vec4f_64& data) const
{
std::size_t i = static_cast<std::size_t>(index);
const std::size_t size = this->Internals->OpacityNodePos.size();
if (index < 0 || i >= size)
{
return false;
}
const auto& pos = this->Internals->OpacityNodePos[i];
const auto& alpha = this->Internals->OpacityAlpha[i];
const auto& midsharp = this->Internals->OpacityMidSharp[i];
data[0] = pos;
data[1] = alpha;
data[2] = midsharp[0];
data[3] = midsharp[1];
return true;
}
//---------------------------------------------------------------------------
vtkm::Int32 ColorTable::UpdatePointAlpha(vtkm::Int32 index, const vtkm::Vec4f_64& data)
{
//skip data[0] as we don't care about position
if (outside_range(data[1], data[2], data[3]))
{
return -1;
}
std::size_t i = static_cast<std::size_t>(index);
const std::size_t size = this->Internals->OpacityNodePos.size();
if (index < 0 || i >= size)
{
return -1;
}
//When updating the first question is has the relative position of the point changed?
//If it hasn't we can quickly just update the RGB value
auto oldPos = this->Internals->OpacityNodePos.begin() + index;
auto newPos = std::lower_bound(
this->Internals->OpacityNodePos.begin(), this->Internals->OpacityNodePos.end(), data[0]);
if (oldPos == newPos)
{ //node's relative location hasn't changed
this->Internals->OpacityArraysChanged = true;
auto& alpha = this->Internals->OpacityAlpha[i];
auto& midsharp = this->Internals->OpacityMidSharp[i];
*newPos = data[0];
alpha = static_cast<vtkm::Float32>(data[1]);
midsharp[0] = static_cast<vtkm::Float32>(data[2]);
midsharp[1] = static_cast<vtkm::Float32>(data[3]);
this->Internals->Modified();
return index;
}
else
{ //remove the point, and add the new values as the relative location is different
this->RemovePointAlpha(index);
return this->AddPointAlpha(data[0],
static_cast<vtkm::Float32>(data[1]),
static_cast<vtkm::Float32>(data[2]),
static_cast<vtkm::Float32>(data[3]));
}
}
//---------------------------------------------------------------------------
bool ColorTable::RemovePointAlpha(vtkm::Float64 x)
{
auto begin = this->Internals->OpacityNodePos.begin();
auto pos = std::lower_bound(begin, this->Internals->OpacityNodePos.end(), x);
return this->RemovePointAlpha(static_cast<vtkm::Int32>(std::distance(begin, pos)));
}
//---------------------------------------------------------------------------
bool ColorTable::RemovePointAlpha(vtkm::Int32 index)
{
std::size_t i = static_cast<std::size_t>(index);
const std::size_t size = this->Internals->OpacityNodePos.size();
if (index < 0 || i >= size)
{
return false;
}
this->Internals->OpacityNodePos.erase(this->Internals->OpacityNodePos.begin() + index);
this->Internals->OpacityAlpha.erase(this->Internals->OpacityAlpha.begin() + index);
this->Internals->OpacityMidSharp.erase(this->Internals->OpacityMidSharp.begin() + index);
this->Internals->OpacityArraysChanged = true;
this->Internals->RecalculateRange();
this->Internals->Modified();
return true;
}
//---------------------------------------------------------------------------
vtkm::Int32 ColorTable::GetNumberOfPointsAlpha() const
{
return static_cast<vtkm::Int32>(this->Internals->OpacityNodePos.size());
}
//---------------------------------------------------------------------------
bool ColorTable::FillColorTableFromDataPointer(vtkm::Int32 n, const vtkm::Float64* ptr)
{
if (n <= 0 || ptr == nullptr)
{
return false;
}
this->ClearColors();
std::size_t size = static_cast<std::size_t>(n / 4);
this->Internals->ColorNodePos.reserve(size);
this->Internals->ColorRGB.reserve(size);
for (std::size_t i = 0; i < size; ++i)
{ //allows us to support unsorted arrays
vtkm::Vec3f_32 rgb(static_cast<vtkm::Float32>(ptr[1]),
static_cast<vtkm::Float32>(ptr[2]),
static_cast<vtkm::Float32>(ptr[3]));
this->AddPoint(ptr[0], rgb);
ptr += 4;
}
this->Internals->ColorArraysChanged = true;
this->Internals->Modified();
return true;
}
//---------------------------------------------------------------------------
bool ColorTable::FillColorTableFromDataPointer(vtkm::Int32 n, const vtkm::Float32* ptr)
{
if (n <= 0 || ptr == nullptr)
{
return false;
}
this->ClearColors();
std::size_t size = static_cast<std::size_t>(n / 4);
this->Internals->ColorNodePos.reserve(size);
this->Internals->ColorRGB.reserve(size);
for (std::size_t i = 0; i < size; ++i)
{ //allows us to support unsorted arrays
vtkm::Vec3f_32 rgb(ptr[1], ptr[2], ptr[3]);
this->AddPoint(ptr[0], rgb);
ptr += 4;
}
this->Internals->ColorArraysChanged = true;
this->Internals->Modified();
return true;
}
//---------------------------------------------------------------------------
bool ColorTable::FillOpacityTableFromDataPointer(vtkm::Int32 n, const vtkm::Float64* ptr)
{
if (n <= 0 || ptr == nullptr)
{
return false;
}
this->ClearAlpha();
std::size_t size = static_cast<std::size_t>(n / 4);
this->Internals->OpacityNodePos.reserve(size);
this->Internals->OpacityAlpha.reserve(size);
this->Internals->OpacityMidSharp.reserve(size);
for (std::size_t i = 0; i < size; ++i)
{ //allows us to support unsorted arrays
this->AddPointAlpha(ptr[0],
static_cast<vtkm::Float32>(ptr[1]),
static_cast<vtkm::Float32>(ptr[2]),
static_cast<vtkm::Float32>(ptr[3]));
ptr += 4;
}
this->Internals->OpacityArraysChanged = true;
this->Internals->Modified();
return true;
}
//---------------------------------------------------------------------------
bool ColorTable::FillOpacityTableFromDataPointer(vtkm::Int32 n, const vtkm::Float32* ptr)
{
if (n <= 0 || ptr == nullptr)
{
return false;
}
this->ClearAlpha();
std::size_t size = static_cast<std::size_t>(n / 4);
this->Internals->OpacityNodePos.reserve(size);
this->Internals->OpacityAlpha.reserve(size);
this->Internals->OpacityMidSharp.reserve(size);
for (std::size_t i = 0; i < size; ++i)
{ //allows us to support unsorted arrays
this->AddPointAlpha(ptr[0], ptr[1], ptr[2], ptr[3]);
ptr += 4;
}
this->Internals->OpacityArraysChanged = true;
this->Internals->Modified();
return true;
}
//---------------------------------------------------------------------------
bool ColorTable::Sample(vtkm::Int32 numSamples,
vtkm::cont::ColorTableSamplesRGBA& samples,
vtkm::Float64 tolerance) const
{
if (numSamples <= 1)
{
return false;
}
samples.NumberOfSamples = numSamples;
samples.SampleRange = this->GetRange();
return sampleColorTable(this, numSamples, samples.Samples, tolerance, true);
}
//---------------------------------------------------------------------------
bool ColorTable::Sample(vtkm::Int32 numSamples,
vtkm::cont::ColorTableSamplesRGB& samples,
vtkm::Float64 tolerance) const
{
if (numSamples <= 1)
{
return false;
}
samples.NumberOfSamples = numSamples;
samples.SampleRange = this->GetRange();
return sampleColorTable(this, numSamples, samples.Samples, tolerance, true);
}
//---------------------------------------------------------------------------
bool ColorTable::Sample(vtkm::Int32 numSamples,
vtkm::cont::ArrayHandle<vtkm::Vec4ui_8>& colors,
vtkm::Float64 tolerance) const
{
if (numSamples <= 1)
{
return false;
}
return sampleColorTable(this, numSamples, colors, tolerance, false);
}
//---------------------------------------------------------------------------
bool ColorTable::Sample(vtkm::Int32 numSamples,
vtkm::cont::ArrayHandle<vtkm::Vec3ui_8>& colors,
vtkm::Float64 tolerance) const
{
if (numSamples <= 1)
{
return false;
}
return sampleColorTable(this, numSamples, colors, tolerance, false);
}
//----------------------------------------------------------------------------
void ColorTable::UpdateArrayHandles() const
{
//Only rebuild the array handles that have changed since the last time
//we have modified or color / opacity information
if (this->Internals->ColorArraysChanged)
{
this->Internals->ColorPosHandle =
vtkm::cont::make_ArrayHandle(this->Internals->ColorNodePos, vtkm::CopyFlag::Off);
this->Internals->ColorRGBHandle =
vtkm::cont::make_ArrayHandle(this->Internals->ColorRGB, vtkm::CopyFlag::Off);
this->Internals->ColorArraysChanged = false;
}
if (this->Internals->OpacityArraysChanged)
{
this->Internals->OpacityPosHandle =
vtkm::cont::make_ArrayHandle(this->Internals->OpacityNodePos, vtkm::CopyFlag::Off);
this->Internals->OpacityAlphaHandle =
vtkm::cont::make_ArrayHandle(this->Internals->OpacityAlpha, vtkm::CopyFlag::Off);
this->Internals->OpacityMidSharpHandle =
vtkm::cont::make_ArrayHandle(this->Internals->OpacityMidSharp, vtkm::CopyFlag::Off);
this->Internals->OpacityArraysChanged = false;
}
}
//---------------------------------------------------------------------------
vtkm::exec::ColorTable ColorTable::PrepareForExecution(vtkm::cont::DeviceAdapterId device,
vtkm::cont::Token& token) const
{
this->UpdateArrayHandles();
vtkm::exec::ColorTable execTable;
execTable.Space = this->Internals->Space;
execTable.NaNColor = this->Internals->NaNColor;
execTable.BelowRangeColor = this->Internals->BelowRangeColor;
execTable.AboveRangeColor = this->Internals->AboveRangeColor;
execTable.UseClamping = this->Internals->UseClamping;
VTKM_ASSERT(static_cast<vtkm::Id>(this->Internals->ColorNodePos.size()) ==
this->Internals->ColorPosHandle.GetNumberOfValues());
execTable.ColorSize =
static_cast<vtkm::Int32>(this->Internals->ColorPosHandle.GetNumberOfValues());
VTKM_ASSERT(static_cast<vtkm::Id>(execTable.ColorSize) ==
this->Internals->ColorRGBHandle.GetNumberOfValues());
execTable.ColorNodes = this->Internals->ColorPosHandle.PrepareForInput(device, token).GetArray();
execTable.RGB = this->Internals->ColorRGBHandle.PrepareForInput(device, token).GetArray();
VTKM_ASSERT(static_cast<vtkm::Id>(this->Internals->OpacityNodePos.size()) ==
this->Internals->OpacityPosHandle.GetNumberOfValues());
execTable.OpacitySize =
static_cast<vtkm::Int32>(this->Internals->OpacityPosHandle.GetNumberOfValues());
VTKM_ASSERT(static_cast<vtkm::Id>(execTable.OpacitySize) ==
this->Internals->OpacityAlphaHandle.GetNumberOfValues());
VTKM_ASSERT(static_cast<vtkm::Id>(execTable.OpacitySize) ==
this->Internals->OpacityMidSharpHandle.GetNumberOfValues());
execTable.ONodes = this->Internals->OpacityPosHandle.PrepareForInput(device, token).GetArray();
execTable.Alpha = this->Internals->OpacityAlphaHandle.PrepareForInput(device, token).GetArray();
execTable.MidSharp =
this->Internals->OpacityMidSharpHandle.PrepareForInput(device, token).GetArray();
return execTable;
}
vtkm::exec::ColorTable ColorTable::PrepareForExecution(vtkm::cont::DeviceAdapterId device) const
{
vtkm::cont::Token token;
return this->PrepareForExecution(device, token);
}
//---------------------------------------------------------------------------
vtkm::Id ColorTable::GetModifiedCount() const
{
return this->Internals->ModifiedCount;
}
}
} //namespace vtkm::cont