Skip to content

Commit

Permalink
adding image editing tools (painting, clone stamp, etc) (#48)
Browse files Browse the repository at this point in the history
closes: #44
  • Loading branch information
wkjarosz authored Jun 16, 2021
1 parent 31de900 commit faab707
Show file tree
Hide file tree
Showing 29 changed files with 2,964 additions and 1,142 deletions.
7 changes: 7 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,9 @@ add_executable(HDRView
hdrview_version.cpp
hdrview_resources.cpp
src/async.h
src/box.h
src/brush.cpp
src/brush.h
src/color.h
src/colorslider.cpp
src/colorslider.h
Expand All @@ -151,6 +154,8 @@ add_executable(HDRView
src/common.cpp
src/common.h
src/dithermatrix256.h
src/drawline.cpp
src/drawline.h
src/dropdown.cpp
src/dropdown.h
src/editimagepanel.cpp
Expand Down Expand Up @@ -197,6 +202,8 @@ add_executable(HDRView
src/progress.h
src/range.h
src/timer.h
src/tool.cpp
src/tool.h
src/well.cpp
src/well.h
${EXTRA_SOURCE})
Expand Down
32 changes: 22 additions & 10 deletions src/box.h
Original file line number Diff line number Diff line change
Expand Up @@ -53,57 +53,69 @@ class Box
//-----------------------------------------------------------------------
//@{ \name Box manipulation
//-----------------------------------------------------------------------
void make_empty()
BoxT &make_empty()
{
min = Vec(std::numeric_limits<Value>::max());
max = Vec(std::numeric_limits<Value>::lowest());
return *this;
}
void enclose(const Vec &point)
BoxT expanded(Value d) const { return BoxT(min - d, max + d); }
BoxT expanded(const Vec &d) const { return BoxT(min - d, max + d); }
BoxT expanded(const BoxT &d) const { return BoxT(min - d.min, max + d.max); }
BoxT &set_size(const Vec &s)
{
max = min + s;
return *this;
}
BoxT &enclose(const Vec &point)
{
for (size_t i = 0; i < Dims; ++i)
{
min[i] = std::min(point[i], min[i]);
max[i] = std::max(point[i], max[i]);
}
return *this;
}
BoxT expanded(Value d) const { return BoxT(min - d, max + d); }
BoxT expanded(const Vec &d) const { return BoxT(min - d, max + d); }
BoxT expanded(const BoxT &d) const { return BoxT(min - d.min, max + d.max); }
void enclose(const BoxT &box)
BoxT &enclose(const BoxT &box)
{
for (size_t i = 0; i < Dims; ++i)
{
min[i] = std::min(box.min[i], min[i]);
max[i] = std::max(box.max[i], max[i]);
}
return *this;
}
void intersect(const BoxT &box)
BoxT &intersect(const BoxT &box)
{
for (size_t i = 0; i < Dims; ++i)
{
min[i] = std::max(box.min[i], min[i]);
max[i] = std::min(box.max[i], max[i]);
}
return *this;
}
void move_min_to(const Vec &newMin)
BoxT &move_min_to(const Vec &newMin)
{
Vec diff(newMin - min);
min = newMin;
max += diff;
return *this;
}
void move_max_to(const Vec &newMax)
BoxT &move_max_to(const Vec &newMax)
{
Vec diff(newMax - max);
max = newMax;
min += diff;
return *this;
}
void make_valid()
BoxT &make_valid()
{
for (size_t i = 0; i < Dims; ++i)
{
if (min[i] > max[i])
std::swap(min[i], max[i]);
}
return *this;
}
//@}

Expand Down
160 changes: 160 additions & 0 deletions src/brush.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
//
// Copyright (C) Wojciech Jarosz <[email protected]>. All rights reserved.
// Use of this source code is governed by a BSD-style license that can
// be found in the LICENSE.txt file.
//

#include "brush.h"

#include "color.h"
#include "common.h"
#include <nanogui/vector.h>

using namespace nanogui;

Brush::Brush(int radius, float hardness, float flow) :
m_size(-1), m_flow(flow), m_hardness(hardness), m_angle(0), m_roundness(100), m_spacing(0), m_spacing_pixels(0),
m_step(0), m_last_x(-1), m_last_y(-1)
{
set_radius(radius);
}

void Brush::set_step(int step) { m_step = step; }

void Brush::set_spacing(int spacing)
{
m_spacing = spacing;
m_spacing_pixels = std::max(1, int(spacing / 100.0f * std::min(m_brush.width(), m_brush.height())));
}

void Brush::set_radius(int radius)
{
if (m_size != radius - 1)
{
m_size = radius - 1;
make_brush();
}
}

void Brush::set_hardness(float hardness)
{
if (m_hardness != hardness)
{
m_hardness = hardness;
make_brush();
}
}

void Brush::set_angle(int angle)
{
if (m_angle != angle)
{
m_angle = angle;
make_brush();
}
}

void Brush::set_roundness(int roundness)
{
if (m_roundness != roundness)
{
m_roundness = roundness;
make_brush();
}
}

void Brush::set_flow(float flow) { m_flow = flow; }

void Brush::set_parameters(int radius, float hardness, int angle, int roundness, float flow, int spacing)
{
if (m_size != radius - 1 || m_hardness != hardness || m_angle != angle || m_roundness != roundness ||
m_spacing != spacing || m_flow != flow)
{
m_size = radius - 1;
m_hardness = hardness;
m_flow = flow;
m_angle = angle;
m_roundness = roundness;
set_spacing(spacing);
make_brush();
}
}

void Brush::make_brush()
{
int n = 2 * m_size + 1;
m_brush.resize(n, n);

float start = m_hardness * m_size;
float end = m_size + 1;

float b = 100.0f / m_roundness;
float theta = 2 * M_PI * m_angle / 360.0f;
float sin_theta = sin(theta);
float cos_theta = cos(theta);

int min_x = n, max_x = 0, min_y = n, max_y = 0;
for (int y = 0; y < n; y++)
{
for (int x = 0; x < n; x++)
{
Vector2f uv((x - m_size) * cos_theta + (y - m_size) * sin_theta,
b * ((y - m_size) * cos_theta - (x - m_size) * sin_theta));
m_brush(x, y) = 1.0f - pow(smoothStep(start, end, norm(uv)), 0.8f);
if (m_brush(x, y) > 0.00001f)
{
min_x = std::min(min_x, x);
min_y = std::min(min_y, y);
max_x = std::max(max_x, x);
max_y = std::max(max_y, y);
}
}
}

// resize the brush array to fit the brush
// Array2D<float> *oldBrush = m_brush;
// m_brush = new Array2D<float>(max_x - min_x + 1, max_y - min_y + 1);
// for (int y = min_y; y <= max_y; y++)
// for (int x = min_x; x <= max_x; x++) (*m_brush)(x - min_x, y - min_y) = (*oldBrush)(x, y);
// delete oldBrush;

set_spacing(m_spacing);
}

void Brush::stamp_onto(int x, int y, const PlotPixelFunc &plot_pixel, const Box2i &roi)
{
if (!(m_step++ == 0) && m_spacing)
{
int distance2 = sqr(x - m_last_x) + sqr(y - m_last_y);
if (distance2 < m_spacing_pixels * m_spacing_pixels)
return;
}

m_last_x = x;
m_last_y = y;
int size_x = (m_brush.width() - 1) / 2;
int size_y = (m_brush.height() - 1) / 2;
int c_offset_x = x - size_x;
int c_offset_y = y - size_y;
int i_start = std::clamp(x - size_x, roi.min.x(), roi.max.x());
int i_end = std::clamp(x + size_x + 1, roi.min.x(), roi.max.x());

int j_start = std::clamp(y - size_y, roi.min.y(), roi.max.y());
int j_end = std::clamp(y + size_y + 1, roi.min.y(), roi.max.y());

for (int j = j_start; j < j_end; j++)
for (int i = i_start; i < i_end; i++) plot_pixel(i, j, m_flow * m_brush(i - c_offset_x, j - c_offset_y));
}

void Brush::stamp_onto(HDRImage &raster, int x, int y, const SrcColorFunc &src_color, const Box2i &roi)
{
auto plot_pixel = [&raster, &src_color](int i, int j, float a)
{
auto c4 = src_color(i, j);
Color4 c3(c4.r, c4.g, c4.b, 1.f);
float alpha = a * c4.a;
raster(i, j) = c3 * alpha + raster(i, j) * (1.0f - alpha);
};

return stamp_onto(x, y, plot_pixel, roi);
}
53 changes: 53 additions & 0 deletions src/brush.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
//
// Copyright (C) Wojciech Jarosz <[email protected]>. All rights reserved.
// Use of this source code is governed by a BSD-style license that can
// be found in the LICENSE.txt file.
//

#pragma once
#include "fwd.h"
#include "hdrimage.h"

class Brush
{
public:
using SrcColorFunc = std::function<Color4(int x, int y)>;
using PlotPixelFunc = std::function<void(int x, int y, float)>;

Brush(int radius = 15, float hardness = 0.0f, float flow = 1.0f);

int step() { return m_step; }
int spacing() const { return m_spacing; }
int radius() const { return m_size + 1; }
float flow() const { return m_flow; }
float hardness() const { return m_hardness; }
int angle() const { return m_angle; }
int roundness() const { return m_roundness; }

void set_step(int);
void set_spacing(int);
void set_radius(int);
void set_flow(float);
void set_hardness(float s);
void set_angle(int angle);
void set_roundness(int roundness);

void set_parameters(int radius, float hardness, int angle, int roundness, float flow, int spacing);

void stamp_onto(HDRImage &raster, int x, int y, const SrcColorFunc &src_color, const Box2i &roi = Box2i());
void stamp_onto(int x, int y, const PlotPixelFunc &plot_pixel, const Box2i &roi = Box2i());

private:
void make_brush();

Array2D<float> m_brush;
int m_size;
float m_flow;
float m_hardness;
int m_angle;
int m_roundness;
int m_spacing;
int m_spacing_pixels;
int m_step;
int m_last_x, m_last_y;
};
2 changes: 2 additions & 0 deletions src/color.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ class Color3
// Color3(const Color3 & c) : r(c.r), g(c.g), b(c.b) {}
Color3(const Color3 &c) = default;
Color3(float x, float y, float z) : r(x), g(y), b(z) {}
Color3(const nanogui::Color &c) : r(c.r()), g(c.g()), b(c.b()) {}
explicit Color3(const float c) : r(c), g(c), b(c) {}
explicit Color3(const float *c) : r(c[0]), g(c[1]), b(c[2]) {}
Color3 &operator=(float c)
Expand Down Expand Up @@ -192,6 +193,7 @@ class Color4 : public Color3
Color4(float x, float y, float z, float w) : Color3(x, y, z), a(w) {}
Color4(float g, float a) : Color3(g), a(a) {}
Color4(const Color3 &c, float a) : Color3(c), a(a) {}
Color4(const nanogui::Color &c) : Color3(c.r(), c.g(), c.b()), a(c.a()) {}
explicit Color4(const float x) : Color3(x), a(x) {}
explicit Color4(const float *c) : Color3(c), a(c[3]) {}
const Color4 &operator=(float c)
Expand Down
3 changes: 1 addition & 2 deletions src/colorslider.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,7 @@ void ColorSlider::draw(NVGcontext *ctx)
if (m_mode == ColorMode::ALPHA)
{
int w, h;
auto checker = hdrview_image_icon(screen()->nvg_context(), checker4,
NVG_IMAGE_REPEATX | NVG_IMAGE_REPEATY | NVG_IMAGE_NEAREST);
auto checker = hdrview_image_icon(ctx, checker4, NVG_IMAGE_REPEATX | NVG_IMAGE_REPEATY | NVG_IMAGE_NEAREST);
nvgImageSize(ctx, checker, &w, &h);
NVGpaint paint = nvgImagePattern(ctx, m_pos.x(), m_pos.y() - 1, w, h, 0, checker, m_enabled ? 0.5f : 0.25f);
nvgFillPaint(ctx, paint);
Expand Down
Loading

0 comments on commit faab707

Please sign in to comment.