Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add wave bars customization options #187

Closed
wants to merge 10 commits into from
134 changes: 128 additions & 6 deletions src/GdImageRenderer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
#include <gdfonts.h>

#include <cassert>
#include <cmath>
#include <cstdio>
#include <cstring>
#include <iomanip>
Expand All @@ -54,6 +55,10 @@ GdImageRenderer::GdImageRenderer() :
background_color_(0),
waveform_color_(0),
axis_label_color_(0),
waveform_style_bars_(false),
bar_width_(1),
bar_gap_(0),
bar_style_rounded_(false),
render_axis_labels_(true),
auto_amplitude_scale_(false),
amplitude_scale_(1.0)
Expand All @@ -78,6 +83,10 @@ bool GdImageRenderer::create(
const int image_width,
const int image_height,
const WaveformColors& colors,
const bool waveform_style_bars,
const int bar_width,
const int bar_gap,
const bool bar_style_rounded,
const bool render_axis_labels,
const bool auto_amplitude_scale,
const double amplitude_scale)
Expand Down Expand Up @@ -116,8 +125,6 @@ bool GdImageRenderer::create(
return false;
}

image_ = gdImageCreateTrueColor(image_width, image_height);

if (image_ == nullptr) {
log(Error) << "Failed to create image\n";
return false;
Expand All @@ -132,6 +139,10 @@ bool GdImageRenderer::create(
sample_rate_ = buffer.getSampleRate();
samples_per_pixel_ = samples_per_pixel;
start_index_ = secondsToPixels(start_time);
waveform_style_bars_ = waveform_style_bars;
bar_width_ = bar_width;
bar_gap_ = bar_gap;
bar_style_rounded_ = bar_style_rounded;
render_axis_labels_ = render_axis_labels;
auto_amplitude_scale_ = auto_amplitude_scale;
amplitude_scale_ = amplitude_scale;
Expand Down Expand Up @@ -210,6 +221,7 @@ void GdImageRenderer::drawWaveform(const WaveformBuffer& buffer) const
{
// Avoid drawing over the right border
const int max_x = render_axis_labels_ ? image_width_ - 1 : image_width_;
const int radius = (int)ceil(bar_width_ / 2);

// Avoid drawing over the top and bottom borders
const int top_y = render_axis_labels_ ? 1 : 0;
Expand Down Expand Up @@ -257,24 +269,134 @@ void GdImageRenderer::drawWaveform(const WaveformBuffer& buffer) const
}

const int height = waveform_bottom_y - waveform_top_y + 1;
const int imageMiddleY = height / 2;
for (int i = start_index, x = start_x; i < buffer_size; ++i) {
if (x + bar_width_ > max_x) {
break;
}

for (int i = start_index, x = start_x; x < max_x && i < buffer_size; ++i, ++x) {
// Convert range [-32768, 32727] to [0, 65535]
int low = MathUtil::scale(buffer.getMinSample(channel, i), amplitude_scale) + 32768;
int high = MathUtil::scale(buffer.getMaxSample(channel, i), amplitude_scale) + 32768;
const int low = MathUtil::scale(buffer.getMinSample(channel, i), amplitude_scale) + 32768;
const int high = MathUtil::scale(buffer.getMaxSample(channel, i), amplitude_scale) + 32768;

// Scale to fit the bitmap
int high_y = waveform_top_y + height - 1 - high * height / 65536;
int low_y = waveform_top_y + height - 1 - low * height / 65536;

gdImageLine(image_, x, low_y, x, high_y, waveform_color_);
if (waveform_style_bars_) {
const int barMiddleY = (low_y - high_y) / 2 + high_y;
const int distanceFromMiddleY = imageMiddleY - barMiddleY;

high_y += distanceFromMiddleY;
low_y += distanceFromMiddleY;

drawRoundedRectangle(x, high_y, x + bar_width_ - 1, low_y - 1, radius);
x += bar_width_ + bar_gap_;
} else {
gdImageLine(image_, x, low_y, x, high_y, waveform_color_);
x++;
}
}

available_height -= row_height + 1;
waveform_top_y += row_height + 1;
}
}

void GdImageRenderer::drawRoundedRectangle(
const int x1,
const int y1,
const int x2,
const int y2,
const float radius) const
{
if(!bar_style_rounded_) {
gdImageFilledRectangle(image_, x1, y1, x2, y2, waveform_color_);
return;
}

double rad = fmin(radius, floor(fmin((x2 - x1) / 2, (y2 - y1) / 2)));
int width = x2 - x1;

if (rad != 0 && rad * 2 != width) {
// We need to fill the gap between the individual radiuses
gdImageFilledRectangle(
image_,
(int)(x1 + rad),
y1,
(int)(x2 - rad),
(int)(y1 + rad),
waveform_color_
);
gdImageFilledRectangle(
image_,
(int)(x1 + rad),
(int)(y2 - rad),
(int)(x2 - rad),
y2,
waveform_color_
);
}

// Drawing the arcs
gdImageFilledArc(
image_,
(int)(x1 + rad),
(int)(y1 + rad),
(int)(rad * 2),
(int)(rad * 2),
180,
270,
waveform_color_,
gdStyledBrushed
); // top left radius

gdImageFilledArc(
image_,
(int)(x2 - rad),
(int)(y1 + rad),
(int)(rad * 2),
(int)(rad * 2),
270,
0,
waveform_color_,
gdStyledBrushed
); // top right radius

gdImageFilledArc(
image_,
(int)(x2 - rad),
(int)(y2 - rad),
(int)(rad * 2),
(int)(rad * 2),
0,
90,
waveform_color_,
gdStyledBrushed
); // bottom right radius

gdImageFilledArc(
image_,
(int)(x1 + rad),
(int)(y2 - rad),
(int)(rad * 2),
(int)(rad * 2),
90,
180,
waveform_color_,
gdStyledBrushed
); // bottom left

gdImageFilledRectangle(
image_,
x1,
(int)(y1 + rad),
x2,
(int)(y2 - rad),
waveform_color_
);
}

//------------------------------------------------------------------------------

void GdImageRenderer::drawTimeAxisLabels() const
Expand Down
15 changes: 15 additions & 0 deletions src/GdImageRenderer.h
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,10 @@ class GdImageRenderer
int image_width,
int image_height,
const WaveformColors& colors,
bool waveform_style_bars,
int bar_width,
int bar_gap,
bool bar_style_rounded,
bool render_axis_labels,
bool auto_amplitude_scale,
double amplitude_scale
Expand All @@ -71,6 +75,12 @@ class GdImageRenderer
void drawBorder() const;

void drawWaveform(const WaveformBuffer& buffer) const;
void drawRoundedRectangle(
const int x1,
const int y1,
const int x2,
const int y2,
const float radius) const;

void drawTimeAxisLabels() const;

Expand Down Expand Up @@ -100,6 +110,11 @@ class GdImageRenderer
int waveform_color_;
int axis_label_color_;

bool waveform_style_bars_;
int bar_width_;
int bar_gap_;
bool bar_style_rounded_;

bool render_axis_labels_;

bool auto_amplitude_scale_;
Expand Down
4 changes: 4 additions & 0 deletions src/OptionHandler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -493,6 +493,10 @@ bool OptionHandler::renderWaveformImage(
options.getImageWidth(),
options.getImageHeight(),
colors,
options.isWaveFormStyleBars(),
options.getBarWidth(),
options.getBarGap(),
options.isBarStyleRounded(),
options.getRenderAxisLabels(),
options.isAutoAmplitudeScale(),
options.getAmplitudeScale()))
Expand Down
19 changes: 19 additions & 0 deletions src/Options.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,9 @@ Options::Options() :
image_height_(0),
bits_(16),
has_bits_(false),
bar_width_(1),
bar_gap_(0),
bar_style_rounded_(false),
render_axis_labels_(true),
auto_amplitude_scale_(false),
amplitude_scale_(1.0),
Expand Down Expand Up @@ -162,6 +165,22 @@ bool Options::parseCommandLine(int argc, const char* const* argv)
"waveform-color",
po::value<RGBA>(&waveform_color_),
"wave color (rrggbb[aa])"
)(
"waveform-style-bars",
po::value<bool>(&waveform_style_bars_)->default_value(false),
"waveform style bars (default to normal))"
)(
"bar-width",
po::value<int>(&bar_width_)->default_value(1),
"bar width (pixels)"
)(
"bar-gap",
po::value<int>(&bar_gap_)->default_value(0),
"bar gap (pixels)"
)(
"bar-style-rounded",
po::value<bool>(&bar_style_rounded_)->default_value(false),
"bar style rounded (default to square)"
)(
"axis-label-color",
po::value<RGBA>(&axis_label_color_),
Expand Down
10 changes: 10 additions & 0 deletions src/Options.h
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,11 @@ class Options

bool getRenderAxisLabels() const { return render_axis_labels_; }

bool isWaveFormStyleBars() const { return waveform_style_bars_; }
int getBarWidth() const { return bar_width_; }
int getBarGap() const { return bar_gap_; }
bool isBarStyleRounded() const { return bar_style_rounded_; }

bool isAutoAmplitudeScale() const { return auto_amplitude_scale_; }
double getAmplitudeScale() const { return amplitude_scale_; }

Expand Down Expand Up @@ -165,6 +170,11 @@ class Options
RGBA waveform_color_;
RGBA axis_label_color_;

bool waveform_style_bars_;
int bar_width_;
int bar_gap_;
bool bar_style_rounded_;

bool has_border_color_;
bool has_background_color_;
bool has_waveform_color_;
Expand Down
16 changes: 8 additions & 8 deletions test/GdImageRendererTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ void GdImageRendererTest::testImageRendering(bool axis_labels, const std::string
const WaveformColors& colors = audacity_waveform_colors;

GdImageRenderer renderer;
result = renderer.create(buffer, 5.0, 1000, 300, colors, axis_labels, false, 1.0); // zoom: 128
result = renderer.create(buffer, 5.0, 1000, 300, colors, 1, 0, false, axis_labels, false, 1.0); // zoom: 128
ASSERT_TRUE(result);

result = renderer.saveAsPng(filename.c_str());
Expand Down Expand Up @@ -159,7 +159,7 @@ TEST_F(GdImageRendererTest, shouldReportErrorIfImageWidthIsLessThanMinimum)
const WaveformColors& colors = audacity_waveform_colors;

GdImageRenderer renderer;
bool result = renderer.create(buffer, 5.0, 0, 300, colors, true, false, 1.0);
bool result = renderer.create(buffer, 5.0, 0, 300, colors, 1, 0, false, true, false, 1.0);

ASSERT_FALSE(result);
ASSERT_TRUE(output.str().empty());
Expand All @@ -179,7 +179,7 @@ TEST_F(GdImageRendererTest, shouldReportErrorIfImageHeightIsLessThanMinimum)
const WaveformColors& colors = audacity_waveform_colors;

GdImageRenderer renderer;
bool result = renderer.create(buffer, 5.0, 800, 0, colors, true, false, 1.0);
bool result = renderer.create(buffer, 5.0, 800, 0, colors, 1, 0, false, true, false, 1.0);

ASSERT_FALSE(result);
ASSERT_TRUE(output.str().empty());
Expand All @@ -199,7 +199,7 @@ TEST_F(GdImageRendererTest, shouldReportErrorIfSampleRateIsZero)
const WaveformColors& colors = audacity_waveform_colors;

GdImageRenderer renderer;
bool result = renderer.create(buffer, 5.0, 800, 250, colors, true, false, 1.0);
bool result = renderer.create(buffer, 5.0, 800, 250, colors, 1, 0, false, true, false, 1.0);

ASSERT_FALSE(result);
ASSERT_TRUE(output.str().empty());
Expand All @@ -219,7 +219,7 @@ TEST_F(GdImageRendererTest, shouldReportErrorIfSampleRateIsNegative)
const WaveformColors& colors = audacity_waveform_colors;

GdImageRenderer renderer;
bool result = renderer.create(buffer, 5.0, 800, 250, colors, true, false, 1.0);
bool result = renderer.create(buffer, 5.0, 800, 250, colors, 1, 0, false, true, false, 1.0);

ASSERT_FALSE(result);
ASSERT_TRUE(output.str().empty());
Expand All @@ -239,7 +239,7 @@ TEST_F(GdImageRendererTest, shouldReportErrorIfScaleIsZero)
const WaveformColors& colors = audacity_waveform_colors;

GdImageRenderer renderer;
bool result = renderer.create(buffer, 5.0, 800, 250, colors, true, false, 1.0);
bool result = renderer.create(buffer, 5.0, 800, 250, colors, 1, 0, false, true, false, 1.0);

ASSERT_FALSE(result);
ASSERT_TRUE(output.str().empty());
Expand All @@ -259,7 +259,7 @@ TEST_F(GdImageRendererTest, shouldReportErrorIfScaleIsNegative)
const WaveformColors& colors = audacity_waveform_colors;

GdImageRenderer renderer;
bool result = renderer.create(buffer, 5.0, 800, 250, colors, true, false, 1.0);
bool result = renderer.create(buffer, 5.0, 800, 250, colors, 1, 0, false, true, false, 1.0);

ASSERT_FALSE(result);
ASSERT_TRUE(output.str().empty());
Expand All @@ -278,7 +278,7 @@ TEST_F(GdImageRendererTest, shouldReportErrorIfWaveformBufferIsEmpty)
const WaveformColors& colors = audacity_waveform_colors;

GdImageRenderer renderer;
bool result = renderer.create(buffer, 5.0, 800, 250, colors, true, false, 1.0);
bool result = renderer.create(buffer, 5.0, 800, 250, colors, 1, 0, false, true, false, 1.0);

ASSERT_FALSE(result);
ASSERT_TRUE(output.str().empty());
Expand Down