Make scale_for_display less likely to leave confusing gaps (#2589).
[dcpomatic.git] / src / lib / util.cc
index 82f31b8f137a8f372d5285a2f677530309cb9f97..a80f86c3ec7fb6e9333511d002a4fe55d3dd2ad7 100644 (file)
 #include "ratio.h"
 #include "rect.h"
 #include "render_text.h"
+#include "scope_guard.h"
 #include "string_text.h"
 #include "text_decoder.h"
 #include "util.h"
 #include "video_content.h"
 #include <dcp/atmos_asset.h>
 #include <dcp/decrypted_kdm.h>
+#include <dcp/file.h>
 #include <dcp/locale_convert.h>
 #include <dcp/picture_asset.h>
 #include <dcp/raw_convert.h>
@@ -73,6 +75,7 @@ LIBDCP_ENABLE_WARNINGS
 #include <unicode/utypes.h>
 #include <unicode/unistr.h>
 #include <unicode/translit.h>
+#include <unicode/brkiter.h>
 #include <boost/algorithm/string.hpp>
 #include <boost/range/algorithm/replace_if.hpp>
 #include <boost/thread.hpp>
@@ -81,7 +84,6 @@ LIBDCP_DISABLE_WARNINGS
 #include <boost/locale.hpp>
 LIBDCP_ENABLE_WARNINGS
 #ifdef DCPOMATIC_WINDOWS
-#include <boost/locale.hpp>
 #include <dbghelp.h>
 #endif
 #include <signal.h>
@@ -98,27 +100,20 @@ LIBDCP_ENABLE_WARNINGS
 #include "i18n.h"
 
 
-using std::bad_alloc;
 using std::cout;
 using std::endl;
-using std::istream;
 using std::list;
 using std::make_pair;
 using std::make_shared;
-using std::map;
+using std::max;
 using std::min;
-using std::ostream;
 using std::pair;
 using std::set_terminate;
 using std::shared_ptr;
 using std::string;
 using std::vector;
 using std::wstring;
-using boost::thread;
 using boost::optional;
-using boost::lexical_cast;
-using boost::bad_lexical_cast;
-using boost::scoped_array;
 using dcp::Size;
 using dcp::raw_convert;
 using dcp::locale_convert;
@@ -361,6 +356,58 @@ dcpomatic_setup_path_encoding ()
 #endif
 }
 
+
+class LogSink : public Kumu::ILogSink
+{
+public:
+       LogSink () {}
+       LogSink (LogSink const&) = delete;
+       LogSink& operator= (LogSink const&) = delete;
+
+       void WriteEntry(const Kumu::LogEntry& entry) override {
+               Kumu::AutoMutex L(m_lock);
+               WriteEntryToListeners(entry);
+               if (entry.TestFilter(m_filter)) {
+                       string buffer;
+                       entry.CreateStringWithOptions(buffer, m_options);
+                       LOG_GENERAL("asdcplib: %1", buffer);
+               }
+       }
+};
+
+
+void
+capture_asdcp_logs ()
+{
+       static LogSink log_sink;
+       Kumu::SetDefaultLogSink(&log_sink);
+}
+
+
+static
+void
+ffmpeg_log_callback(void* ptr, int level, const char* fmt, va_list vl)
+{
+       if (level > AV_LOG_WARNING) {
+               return;
+       }
+
+       char line[1024];
+       static int prefix = 0;
+       av_log_format_line(ptr, level, fmt, vl, line, sizeof (line), &prefix);
+       string str(line);
+       boost::algorithm::trim(str);
+       dcpomatic_log->log(String::compose("FFmpeg: %1", str), LogEntry::TYPE_GENERAL);
+}
+
+
+void
+capture_ffmpeg_logs()
+{
+       av_log_set_callback(ffmpeg_log_callback);
+}
+
+
 /** Call the required functions to set up DCP-o-matic's static arrays, etc.
  *  Must be called from the UI thread, if there is one.
  */
@@ -394,7 +441,13 @@ LIBDCP_ENABLE_WARNINGS
 
 #ifdef DCPOMATIC_WINDOWS
        putenv ("PANGOCAIRO_BACKEND=fontconfig");
-       putenv (String::compose("FONTCONFIG_PATH=%1", resources_path().string()).c_str());
+       if (boost::filesystem::exists(resources_path() / "fonts.conf")) {
+               /* The actual application after installation */
+               putenv(String::compose("FONTCONFIG_PATH=%1", resources_path().string()).c_str());
+       } else {
+               /* The place where fonts.conf is during tests */
+               putenv("FONTCONFIG_PATH=build\\fonts");
+       }
 #endif
 
 #ifdef DCPOMATIC_OSX
@@ -407,12 +460,12 @@ LIBDCP_ENABLE_WARNINGS
 
 #if defined(DCPOMATIC_WINDOWS) || defined(DCPOMATIC_OSX)
        /* Render something to fontconfig to create its cache */
-       list<StringText> subs;
+       vector<StringText> subs;
        dcp::SubtitleString ss(
                optional<string>(), false, false, false, dcp::Colour(), 42, 1, dcp::Time(), dcp::Time(), 0, dcp::HAlign::CENTER, 0, dcp::VAlign::CENTER, 0, dcp::Direction::LTR,
                "Hello dolly", dcp::Effect::NONE, dcp::Colour(), dcp::Time(), dcp::Time(), 0
                );
-       subs.push_back(StringText(ss, 0, {}, dcp::SubtitleStandard::SMPTE_2014));
+       subs.push_back(StringText(ss, 0, make_shared<dcpomatic::Font>("foo"), dcp::SubtitleStandard::SMPTE_2014));
        render_text (subs, dcp::Size(640, 480), DCPTime(), 24);
 #endif
 
@@ -428,6 +481,7 @@ LIBDCP_ENABLE_WARNINGS
        ui_thread = boost::this_thread::get_id ();
 
        capture_asdcp_logs ();
+       capture_ffmpeg_logs();
 }
 
 #ifdef DCPOMATIC_WINDOWS
@@ -928,19 +982,25 @@ copy_in_bits (boost::filesystem::path from, boost::filesystem::path to, std::fun
 dcp::Size
 scale_for_display (dcp::Size s, dcp::Size display_container, dcp::Size film_container, PixelQuanta quanta)
 {
-       /* Now scale it down if the display container is smaller than the film container */
-       if (display_container != film_container) {
-               float const scale = min (
-                       float (display_container.width) / film_container.width,
-                       float (display_container.height) / film_container.height
+       if (std::abs(display_container.ratio() - film_container.ratio()) < 0.01) {
+               /* The display ratio is very close to what it should be, but it might not be exactly the same.
+                * Allow the image to stretch slightly (differently in x and y) so that we are less likely
+                * to get single pixel gaps in the preview.
+                */
+               return quanta.round(
+                       static_cast<float>(s.width) * display_container.width / film_container.width,
+                       static_cast<float>(s.height) * display_container.height / film_container.height
                        );
-
-               s.width = lrintf (s.width * scale);
-               s.height = lrintf (s.height * scale);
-               s = quanta.round (s);
+       } else {
+               /* The display ratio is quite different to the film, so scale it so that the dimension
+                * that needs to fit does fit.
+                */
+               auto const scale = min(
+                       static_cast<float>(display_container.width) / film_container.width,
+                       static_cast<float>(display_container.height) / film_container.height
+                       );
+               return quanta.round(s.width * scale, s.height * scale);
        }
-
-       return s;
 }
 
 
@@ -1013,33 +1073,6 @@ start_of_thread (string)
 #endif
 
 
-class LogSink : public Kumu::ILogSink
-{
-public:
-       LogSink () {}
-       LogSink (LogSink const&) = delete;
-       LogSink& operator= (LogSink const&) = delete;
-
-       void WriteEntry(const Kumu::LogEntry& entry) override {
-               Kumu::AutoMutex L(m_lock);
-               WriteEntryToListeners(entry);
-               if (entry.TestFilter(m_filter)) {
-                       string buffer;
-                       entry.CreateStringWithOptions(buffer, m_options);
-                       LOG_GENERAL("asdcplib: %1", buffer);
-               }
-       }
-};
-
-
-void
-capture_asdcp_logs ()
-{
-       static LogSink log_sink;
-       Kumu::SetDefaultLogSink(&log_sink);
-}
-
-
 string
 error_details(boost::system::error_code ec)
 {
@@ -1053,3 +1086,38 @@ contains_assetmap(boost::filesystem::path dir)
        return boost::filesystem::is_regular_file(dir / "ASSETMAP") || boost::filesystem::is_regular_file(dir / "ASSETMAP.xml");
 }
 
+
+string
+word_wrap(string input, int columns)
+{
+       icu::Locale locale;
+       UErrorCode status = U_ZERO_ERROR;
+       auto iter = icu::BreakIterator::createLineInstance(locale, status);
+       ScopeGuard sg = [iter]() { delete iter; };
+       if (U_FAILURE(status)) {
+               return input;
+       }
+
+       auto input_icu = icu::UnicodeString::fromUTF8(icu::StringPiece(input));
+       iter->setText(input_icu);
+
+       int position = 0;
+       string output;
+       while (position < input_icu.length()) {
+               int end_of_line = iter->preceding(position + columns + 1);
+               icu::UnicodeString line;
+               if (end_of_line <= position) {
+                       /* There's no good line-break position; just break in the middle of a word */
+                       line = input_icu.tempSubString(position, columns);
+                       position += columns;
+               } else {
+                       line = input_icu.tempSubString(position, end_of_line - position);
+                       position = end_of_line;
+               }
+               line.toUTF8String(output);
+               output += "\n";
+       }
+
+       return output;
+}
+