Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions src/effects/Caption.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -369,6 +369,14 @@ std::shared_ptr<openshot::Frame> Caption::GetFrame(std::shared_ptr<openshot::Fra
painter.setPen(pen);
}

// Draw the text outline first, then the fill so the border
// does not obscure the interior of the caption characters.
if (stroke_width_value > 0.0) {
painter.setBrush(Qt::NoBrush);
painter.drawPath(path);
}

painter.setPen(Qt::NoPen);
painter.setBrush(font_brush);
painter.drawPath(path);
}
Expand Down
72 changes: 72 additions & 0 deletions tests/Caption.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,78 @@ TEST_CASE("caption effect", "[libopenshot][caption]") {
clip1.Close();
}


SECTION("stroke before fill regression") {
// Compare the filled interior of caption glyphs with and
// without a thick outline. The outline must not darken
// pixels that belong to the original glyph fill.
const std::string text =
"00:00.000 --> 00:10.000\nOPEN";

openshot::Caption fill_only(text);
fill_only.color = openshot::Color("#ffffff");
fill_only.stroke = openshot::Color("#000000");
fill_only.stroke_width = openshot::Keyframe(0.0);
fill_only.font_size = openshot::Keyframe(110.0);
fill_only.top = openshot::Keyframe(0.2);

openshot::Caption outlined(text);
outlined.color = openshot::Color("#ffffff");
outlined.stroke = openshot::Color("#000000");
outlined.stroke_width = openshot::Keyframe(16.0);
outlined.font_size = openshot::Keyframe(110.0);
outlined.top = openshot::Keyframe(0.2);

// The timeline supplies the FPS used by caption cue timing.
openshot::Timeline timeline(
600, 300, openshot::Fraction(24, 1),
44100, 2, openshot::LAYOUT_STEREO);

timeline.AddEffect(&fill_only);

auto base_frame = std::make_shared<openshot::Frame>(
1, 600, 300, "#000000");
auto base = fill_only.GetFrame(base_frame, 1);

timeline.RemoveEffect(&fill_only);
timeline.AddEffect(&outlined);

auto outlined_frame = std::make_shared<openshot::Frame>(
1, 600, 300, "#000000");
auto result = outlined.GetFrame(outlined_frame, 1);

int fill_pixels = 0;
int darkened_pixels = 0;

for (int y = 0; y < 300; ++y) {
const unsigned char* baseline = base->GetPixels(y);
const unsigned char* actual = result->GetPixels(y);

for (int x = 0; x < 600; ++x) {
const int index = x * 4;

if (baseline[index] < 240 ||
baseline[index + 1] < 240 ||
baseline[index + 2] < 240)
continue;

++fill_pixels;

if (actual[index] < baseline[index] - 10)
++darkened_pixels;
}
}

INFO("Fill pixels: " << fill_pixels);
INFO("Darkened fill pixels: " << darkened_pixels);

REQUIRE(fill_pixels > 0);
CHECK(darkened_pixels == 0);

timeline.RemoveEffect(&outlined);
timeline.Close();
}

// Close QApplication
app.quit();
}