Split up notcurses/notcurses-core (#1297)

Extract `libnotcurses-core` from `libnotcurses`. The former contains everything except multimedia code. The latter contains multimedia stuff (a wrapper around FFmpeg or OIIO). If built with `-DUSE_MULTIMEDIA=none`, there will not be any `libnotcurses.so` generated. `libnotcurses.so` uses library constructors/destructors to insert its implementation into the `ncvisual` stack at runtime. Users linking `-lnotcurses` will get the full implementation; users linking `-lnotcurses-core` only will get the stack less multimedia code.

The upshot of this is that someone can compile/install only `libnotcurses-core`, and a program linked against it will work just fine. This eliminates the need to install the full (large) dependency stack of the multimedia code unless necessary. This will hopefully be useful for e.g. installers etc. Closes #339.
pull/1305/head
Nick Black 3 years ago committed by GitHub
parent c62a3f7464
commit 9112185657
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -60,6 +60,12 @@ if("${USE_COVERAGE}")
string(APPEND CMAKE_CXX_FLAGS_DEBUG " --coverage -fprofile-instr-generate -fcoverage-mapping") string(APPEND CMAKE_CXX_FLAGS_DEBUG " --coverage -fprofile-instr-generate -fcoverage-mapping")
endif() endif()
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
set(PKGCONFIG_DIR "${CMAKE_INSTALL_LIBDIR}/pkgconfig")
else()
set(PKGCONFIG_DIR "${CMAKE_INSTALL_PREFIX}/libdata/pkgconfig")
endif()
# global compiler flags # global compiler flags
add_compile_definitions(FORTIFY_SOURCE=2) add_compile_definitions(FORTIFY_SOURCE=2)
add_compile_options(-Wall -Wextra -W -Wshadow -Wformat -fexceptions) add_compile_options(-Wall -Wextra -W -Wshadow -Wformat -fexceptions)
@ -114,32 +120,31 @@ endif()
set_property(GLOBAL APPEND PROPERTY PACKAGES_FOUND qrcodegen) set_property(GLOBAL APPEND PROPERTY PACKAGES_FOUND qrcodegen)
endif() endif()
find_library(LIBRT rt) find_library(LIBRT rt)
feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES)
file(GLOB COMPATSRC CONFIGURE_DEPENDS src/compat/compat.c) file(GLOB COMPATSRC CONFIGURE_DEPENDS src/compat/compat.c)
# libnotcurses (core shared library, core static library) ############################################################################
file(GLOB NCSRCS CONFIGURE_DEPENDS src/lib/*.c src/lib/*.cpp) # libnotcurses-core (core shared library, core static library)
add_library(notcurses SHARED ${NCSRCS} ${COMPATSRC}) file(GLOB NCCORESRCS CONFIGURE_DEPENDS src/lib/*.c src/lib/*.cpp)
add_library(notcurses-core SHARED ${NCCORESRCS} ${COMPATSRC})
if(${USE_STATIC}) if(${USE_STATIC})
add_library(notcurses-static STATIC ${NCSRCS}) add_library(notcurses-core-static STATIC ${NCCORESRCS})
else() else()
add_library(notcurses-static STATIC EXCLUDE_FROM_ALL ${NCSRCS}) add_library(notcurses-core-static STATIC EXCLUDE_FROM_ALL ${NCCORESRCS})
endif() endif()
set_target_properties( set_target_properties(
notcurses-static PROPERTIES notcurses-core-static PROPERTIES
OUTPUT_NAME notcurses OUTPUT_NAME notcurses-core
) )
set_target_properties(notcurses-core PROPERTIES
feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES)
set_target_properties(notcurses PROPERTIES
VERSION ${PROJECT_VERSION} VERSION ${PROJECT_VERSION}
SOVERSION ${PROJECT_VERSION_MAJOR} SOVERSION ${PROJECT_VERSION_MAJOR}
) )
set_target_properties(notcurses-static PROPERTIES set_target_properties(notcurses-core-static PROPERTIES
VERSION ${PROJECT_VERSION} VERSION ${PROJECT_VERSION}
) )
target_include_directories(notcurses target_include_directories(notcurses-core
PRIVATE PRIVATE
include include
src src
@ -147,7 +152,7 @@ target_include_directories(notcurses
"${TERMINFO_INCLUDE_DIRS}" "${TERMINFO_INCLUDE_DIRS}"
"${READLINE_INCLUDE_DIRS}" "${READLINE_INCLUDE_DIRS}"
) )
target_include_directories(notcurses-static target_include_directories(notcurses-core-static
PRIVATE PRIVATE
include include
src src
@ -155,7 +160,7 @@ target_include_directories(notcurses-static
"${TERMINFO_STATIC_INCLUDE_DIRS}" "${TERMINFO_STATIC_INCLUDE_DIRS}"
"${READLINE_STATIC_INCLUDE_DIRS}" "${READLINE_STATIC_INCLUDE_DIRS}"
) )
target_link_libraries(notcurses target_link_libraries(notcurses-core
PRIVATE PRIVATE
"${TERMINFO_LIBRARIES}" "${TERMINFO_LIBRARIES}"
"${READLINE_LIBRARIES}" "${READLINE_LIBRARIES}"
@ -164,7 +169,7 @@ target_link_libraries(notcurses
PUBLIC PUBLIC
Threads::Threads Threads::Threads
) )
target_link_libraries(notcurses-static target_link_libraries(notcurses-core-static
PRIVATE PRIVATE
"${TERMINFO_STATIC_LIBRARIES}" "${TERMINFO_STATIC_LIBRARIES}"
"${READLINE_STATIC_LIBRARIES}" "${READLINE_STATIC_LIBRARIES}"
@ -173,32 +178,81 @@ target_link_libraries(notcurses-static
PUBLIC PUBLIC
Threads::Threads Threads::Threads
) )
target_link_directories(notcurses target_link_directories(notcurses-core
PRIVATE PRIVATE
"${TERMINFO_LIBRARY_DIRS}" "${TERMINFO_LIBRARY_DIRS}"
"${READLINE_LIBRARY_DIRS}" "${READLINE_LIBRARY_DIRS}"
) )
target_link_directories(notcurses-static target_link_directories(notcurses-core-static
PRIVATE PRIVATE
"${TERMINFO_STATIC_LIBRARY_DIRS}" "${TERMINFO_STATIC_LIBRARY_DIRS}"
"${READLINE_STATIC_LIBRARY_DIRS}" "${READLINE_STATIC_LIBRARY_DIRS}"
) )
# don't want these on freebsd/dragonfly/osx
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
target_compile_definitions(notcurses-core
PUBLIC
_XOPEN_SOURCE=700 # wcwidth(3) requires _XOPEN_SOURCE, and is in our headers
PRIVATE
_GNU_SOURCE _DEFAULT_SOURCE
)
target_compile_definitions(notcurses-core-static
PUBLIC
_XOPEN_SOURCE=700 # wcwidth(3) requires _XOPEN_SOURCE, and is in our headers
PRIVATE
_GNU_SOURCE _DEFAULT_SOURCE
)
endif()
if(${USE_QRCODEGEN}) if(${USE_QRCODEGEN})
target_link_libraries(notcurses PRIVATE qrcodegen) target_link_libraries(notcurses-core PRIVATE qrcodegen)
target_link_libraries(notcurses-static PRIVATE qrcodegen) target_link_libraries(notcurses-core-static PRIVATE qrcodegen)
endif() endif()
if(${USE_FFMPEG}) ############################################################################
# libnotcurses (multimedia shared library+static library)
file(GLOB NCSRCS CONFIGURE_DEPENDS src/media/*.c src/media/*.cpp)
add_library(notcurses SHARED ${NCSRCS} ${COMPATSRC})
if(${USE_STATIC})
add_library(notcurses-static STATIC ${NCSRCS})
else()
add_library(notcurses-static STATIC EXCLUDE_FROM_ALL ${NCSRCS})
endif()
set_target_properties(
notcurses-static PROPERTIES
OUTPUT_NAME notcurses
)
target_include_directories(notcurses target_include_directories(notcurses
PRIVATE
include
src
src/lib
"${PROJECT_BINARY_DIR}/include"
)
target_include_directories(notcurses-static
PRIVATE
include
src
src/lib
"${PROJECT_BINARY_DIR}/include"
)
target_link_libraries(notcurses
PUBLIC
notcurses-core
)
target_link_libraries(notcurses-static
PUBLIC PUBLIC
notcurses-core-static
)
if(${USE_FFMPEG})
target_include_directories(notcurses
PRIVATE
"${AVCODEC_INCLUDE_DIRS}" "${AVCODEC_INCLUDE_DIRS}"
"${AVFORMAT_INCLUDE_DIRS}" "${AVFORMAT_INCLUDE_DIRS}"
"${AVUTIL_INCLUDE_DIRS}" "${AVUTIL_INCLUDE_DIRS}"
"${SWSCALE_INCLUDE_DIRS}" "${SWSCALE_INCLUDE_DIRS}"
) )
target_include_directories(notcurses-static target_include_directories(notcurses-static
PUBLIC PRIVATE
"${AVCODEC_STATIC_INCLUDE_DIRS}" "${AVCODEC_STATIC_INCLUDE_DIRS}"
"${AVFORMAT_STATIC_INCLUDE_DIRS}" "${AVFORMAT_STATIC_INCLUDE_DIRS}"
"${AVUTIL_STATIC_INCLUDE_DIRS}" "${AVUTIL_STATIC_INCLUDE_DIRS}"
@ -209,7 +263,6 @@ target_link_libraries(notcurses
"${AVCODEC_LIBRARIES}" "${AVCODEC_LIBRARIES}"
"${AVFORMAT_LIBRARIES}" "${AVFORMAT_LIBRARIES}"
"${SWSCALE_LIBRARIES}" "${SWSCALE_LIBRARIES}"
PUBLIC
"${AVUTIL_LIBRARIES}" "${AVUTIL_LIBRARIES}"
) )
target_link_libraries(notcurses-static target_link_libraries(notcurses-static
@ -217,7 +270,6 @@ target_link_libraries(notcurses-static
"${AVCODEC_STATIC_LIBRARIES}" "${AVCODEC_STATIC_LIBRARIES}"
"${AVFORMAT_STATIC_LIBRARIES}" "${AVFORMAT_STATIC_LIBRARIES}"
"${SWSCALE_STATIC_LIBRARIES}" "${SWSCALE_STATIC_LIBRARIES}"
PUBLIC
"${AVUTIL_STATIC_LIBRARIES}" "${AVUTIL_STATIC_LIBRARIES}"
) )
target_link_directories(notcurses target_link_directories(notcurses
@ -225,7 +277,6 @@ target_link_directories(notcurses
"${AVCODEC_LIBRARY_DIRS}" "${AVCODEC_LIBRARY_DIRS}"
"${AVFORMAT_LIBRARY_DIRS}" "${AVFORMAT_LIBRARY_DIRS}"
"${SWSCALE_LIBRARY_DIRS}" "${SWSCALE_LIBRARY_DIRS}"
PUBLIC
"${AVUTIL_LIBRARY_DIRS}" "${AVUTIL_LIBRARY_DIRS}"
) )
target_link_directories(notcurses-static target_link_directories(notcurses-static
@ -233,7 +284,6 @@ target_link_directories(notcurses-static
"${AVCODEC_STATIC_LIBRARY_DIRS}" "${AVCODEC_STATIC_LIBRARY_DIRS}"
"${AVFORMAT_STATIC_LIBRARY_DIRS}" "${AVFORMAT_STATIC_LIBRARY_DIRS}"
"${SWSCALE_STATIC_LIBRARY_DIRS}" "${SWSCALE_STATIC_LIBRARY_DIRS}"
PUBLIC
"${AVUTIL_STATIC_LIBRARY_DIRS}" "${AVUTIL_STATIC_LIBRARY_DIRS}"
) )
elseif(${USE_OIIO}) elseif(${USE_OIIO})
@ -245,28 +295,8 @@ target_link_directories(notcurses PRIVATE ${OIIO_LIBRARY_DIRS})
target_link_directories(notcurses-static PRIVATE ${OIIO_STATIC_LIBRARY_DIRS}) target_link_directories(notcurses-static PRIVATE ${OIIO_STATIC_LIBRARY_DIRS})
endif() endif()
# don't want these on freebsd ############################################################################
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux") # libnotcurses++ (C++ wrappers)
target_compile_definitions(notcurses
PUBLIC
_XOPEN_SOURCE=700 # wcwidth(3) requires _XOPEN_SOURCE, and is in our headers
PRIVATE
_GNU_SOURCE _DEFAULT_SOURCE
)
target_compile_definitions(notcurses-static
PUBLIC
_XOPEN_SOURCE=700 # wcwidth(3) requires _XOPEN_SOURCE, and is in our headers
PRIVATE
_GNU_SOURCE _DEFAULT_SOURCE
)
set(PKGCONFIG_DIR "${CMAKE_INSTALL_LIBDIR}/pkgconfig")
elseif(${CMAKE_SYSTEM_NAME} STREQUAL "FreeBSD")
set(PKGCONFIG_DIR "${CMAKE_INSTALL_PREFIX}/libdata/pkgconfig")
elseif(${CMAKE_SYSTEM_NAME} STREQUAL "DragonFly")
set(PKGCONFIG_DIR "${CMAKE_INSTALL_PREFIX}/libdata/pkgconfig")
endif()
# libnotcurses++
set(NCPP_SOURCES set(NCPP_SOURCES
src/libcpp/FDPlane.cc src/libcpp/FDPlane.cc
src/libcpp/Menu.cc src/libcpp/Menu.cc
@ -375,31 +405,6 @@ install(FILES ${NOTCURSES_HEADERS} DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/notcu
install(FILES ${NCPP_HEADERS} DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/ncpp) install(FILES ${NCPP_HEADERS} DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/ncpp)
install(FILES ${NCPP_INTERNAL_HEADERS} DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/ncpp/internal) install(FILES ${NCPP_INTERNAL_HEADERS} DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/ncpp/internal)
# notcurses-demo
file(GLOB DEMOSRCS CONFIGURE_DEPENDS src/demo/*.c)
add_executable(notcurses-demo ${DEMOSRCS} ${COMPATSRC})
target_compile_definitions(notcurses-demo
PRIVATE
_GNU_SOURCE
)
target_include_directories(notcurses-demo
PRIVATE
include
src
"${PROJECT_BINARY_DIR}/include"
PUBLIC
"${AVCODEC_INCLUDE_DIRS}"
"${AVFORMAT_INCLUDE_DIRS}"
"${AVUTIL_INCLUDE_DIRS}"
"${SWSCALE_INCLUDE_DIRS}"
)
target_link_libraries(notcurses-demo
PRIVATE
notcurses
unistring
${MATH_LIBRARIES}
)
# tiny proofs of concept, one binary per source file # tiny proofs of concept, one binary per source file
if(USE_POC) if(USE_POC)
file(GLOB POCSRCS CONFIGURE_DEPENDS src/poc/*.c src/poc/*.cpp) file(GLOB POCSRCS CONFIGURE_DEPENDS src/poc/*.c src/poc/*.cpp)
@ -496,19 +501,35 @@ if(USE_DOXYGEN)
endif() endif()
endif() endif()
# ncneofetch ############################################################################
file(GLOB FETCHSRCS CONFIGURE_DEPENDS src/fetch/*.c) # notcurses-demo
add_executable(ncneofetch ${FETCHSRCS}) file(GLOB DEMOSRCS CONFIGURE_DEPENDS src/demo/*.c)
target_include_directories(ncneofetch add_executable(notcurses-demo ${DEMOSRCS} ${COMPATSRC})
target_compile_definitions(notcurses-demo
PRIVATE
_GNU_SOURCE
)
target_include_directories(notcurses-demo
PRIVATE PRIVATE
include include
src
"${PROJECT_BINARY_DIR}/include" "${PROJECT_BINARY_DIR}/include"
PUBLIC
"${AVCODEC_INCLUDE_DIRS}"
"${AVFORMAT_INCLUDE_DIRS}"
"${AVUTIL_INCLUDE_DIRS}"
"${SWSCALE_INCLUDE_DIRS}"
) )
target_link_libraries(ncneofetch target_link_libraries(notcurses-demo
PRIVATE PRIVATE
notcurses notcurses
unistring
${MATH_LIBRARIES}
PUBLIC
Threads::Threads
) )
############################################################################
# notcurses-input # notcurses-input
file(GLOB INPUTSRCS CONFIGURE_DEPENDS src/input/input.cpp) file(GLOB INPUTSRCS CONFIGURE_DEPENDS src/input/input.cpp)
add_executable(notcurses-input ${INPUTSRCS}) add_executable(notcurses-input ${INPUTSRCS})
@ -522,34 +543,53 @@ target_link_libraries(notcurses-input
notcurses++ notcurses++
) )
# ncls ############################################################################
file(GLOB LSSRC CONFIGURE_DEPENDS src/ls/*.cpp) # notcurses-tetris
add_executable(ncls ${LSSRC}) file(GLOB TETRISSRC CONFIGURE_DEPENDS src/tetris/*.cpp)
target_include_directories(ncls add_executable(notcurses-tetris ${TETRISSRC})
target_include_directories(notcurses-tetris
PRIVATE PRIVATE
include include
"${PROJECT_BINARY_DIR}/include" "${PROJECT_BINARY_DIR}/include"
) )
target_link_libraries(ncls target_link_libraries(notcurses-tetris
PRIVATE PRIVATE
notcurses++ notcurses++
) )
# notcurses-tetris # all further binaries require multimedia support
file(GLOB TETRISSRC CONFIGURE_DEPENDS src/tetris/*.cpp) if(NOT ${USE_MULTIMEDIA} STREQUAL "none")
add_executable(notcurses-tetris ${TETRISSRC}) ############################################################################
target_include_directories(notcurses-tetris # ncneofetch
file(GLOB FETCHSRCS CONFIGURE_DEPENDS src/fetch/*.c)
add_executable(ncneofetch ${FETCHSRCS})
target_include_directories(ncneofetch
PRIVATE PRIVATE
include include
"${PROJECT_BINARY_DIR}/include" "${PROJECT_BINARY_DIR}/include"
) )
target_link_libraries(notcurses-tetris target_link_libraries(ncneofetch
PRIVATE
notcurses
)
############################################################################
# ncls
file(GLOB LSSRC CONFIGURE_DEPENDS src/ls/*.cpp)
add_executable(ncls ${LSSRC})
target_include_directories(ncls
PRIVATE
include
"${PROJECT_BINARY_DIR}/include"
)
target_link_libraries(ncls
PRIVATE PRIVATE
notcurses++ notcurses++
) )
############################################################################
# notcurses-view # notcurses-view
if(${USE_FFMPEG} OR ${USE_OIIO}) if(NOT ${USE_MULTIMEDIA} STREQUAL "none")
file(GLOB VIEWSRCS CONFIGURE_DEPENDS src/view/*.cpp) file(GLOB VIEWSRCS CONFIGURE_DEPENDS src/view/*.cpp)
add_executable(notcurses-view ${VIEWSRCS} ${COMPATSRC}) add_executable(notcurses-view ${VIEWSRCS} ${COMPATSRC})
target_include_directories(notcurses-view target_include_directories(notcurses-view
@ -563,8 +603,10 @@ target_link_libraries(notcurses-view
notcurses++ notcurses++
) )
endif() endif()
endif()
# Testing ############################################################################
# testing
if(${BUILD_TESTING}) if(${BUILD_TESTING})
#set(CMAKE_CTEST_ARGUMENTS "-V") #set(CMAKE_CTEST_ARGUMENTS "-V")
if(${USE_DOCTEST}) if(${USE_DOCTEST})
@ -579,8 +621,8 @@ target_include_directories(notcurses-tester
) )
target_link_libraries(notcurses-tester target_link_libraries(notcurses-tester
PRIVATE PRIVATE
unistring
notcurses++ notcurses++
unistring
"${TERMINFO_LIBRARIES}" "${TERMINFO_LIBRARIES}"
) )
add_test( add_test(
@ -629,11 +671,14 @@ add_custom_target(demo
) )
# pkg-config support # pkg-config support
configure_file(tools/notcurses-core.pc.in
${CMAKE_CURRENT_BINARY_DIR}/notcurses-core.pc
@ONLY
)
configure_file(tools/notcurses.pc.in configure_file(tools/notcurses.pc.in
${CMAKE_CURRENT_BINARY_DIR}/notcurses.pc ${CMAKE_CURRENT_BINARY_DIR}/notcurses.pc
@ONLY @ONLY
) )
configure_file(tools/notcurses++.pc.in configure_file(tools/notcurses++.pc.in
${CMAKE_CURRENT_BINARY_DIR}/notcurses++.pc ${CMAKE_CURRENT_BINARY_DIR}/notcurses++.pc
@ONLY @ONLY
@ -686,7 +731,7 @@ install(FILES
DESTINATION ${PKGCONFIG_DIR} DESTINATION ${PKGCONFIG_DIR}
) )
if(${USE_FFMPEG} OR ${USE_OIIO}) if(NOT ${USE_MULTIMEDIA} STREQUAL "none")
file(GLOB TESTDATA CONFIGURE_DEPENDS data/*) file(GLOB TESTDATA CONFIGURE_DEPENDS data/*)
# Don't install source materia for self-originated multimedia # Don't install source materia for self-originated multimedia
list(FILTER TESTDATA EXCLUDE REGEX ".*xcf$") list(FILTER TESTDATA EXCLUDE REGEX ".*xcf$")
@ -701,14 +746,14 @@ install(FILES ${MARKDOWN} DESTINATION ${CMAKE_INSTALL_DOCDIR})
install(TARGETS notcurses-demo DESTINATION bin) install(TARGETS notcurses-demo DESTINATION bin)
install(TARGETS notcurses-input DESTINATION bin) install(TARGETS notcurses-input DESTINATION bin)
install(TARGETS ncls DESTINATION bin)
install(TARGETS ncneofetch DESTINATION bin)
install(TARGETS notcurses-tetris DESTINATION bin) install(TARGETS notcurses-tetris DESTINATION bin)
if(${USE_FFMPEG} OR ${USE_OIIO}) if(NOT ${USE_MULTIMEDIA} STREQUAL "none")
install(TARGETS ncneofetch DESTINATION bin)
install(TARGETS ncls DESTINATION bin)
install(TARGETS notcurses-view DESTINATION bin) install(TARGETS notcurses-view DESTINATION bin)
endif() endif()
install(TARGETS notcurses notcurses++ install(TARGETS notcurses-core notcurses notcurses++
LIBRARY LIBRARY
DESTINATION ${CMAKE_INSTALL_LIBDIR} DESTINATION ${CMAKE_INSTALL_LIBDIR}
COMPONENT Libraries COMPONENT Libraries
@ -716,7 +761,7 @@ install(TARGETS notcurses notcurses++
) )
if(${USE_STATIC}) if(${USE_STATIC})
install( install(
TARGETS notcurses-static notcurses++-static TARGETS notcurses-core-static notcurses-static notcurses++-static
LIBRARY LIBRARY
DESTINATION ${CMAKE_INSTALL_LIBDIR} DESTINATION ${CMAKE_INSTALL_LIBDIR}
COMPONENT Libraries COMPONENT Libraries

@ -14,6 +14,14 @@ On an APT-based distribution, run:
`apt-get install build-essential cmake doctest-dev libavformat-dev libavutil-dev libncurses-dev libreadline-dev libqrcodegen-dev libswscale-dev libunistring-dev pandoc pkg-config` `apt-get install build-essential cmake doctest-dev libavformat-dev libavutil-dev libncurses-dev libreadline-dev libqrcodegen-dev libswscale-dev libunistring-dev pandoc pkg-config`
If you only intend to build core Notcurses (without multimedia support), run:
`apt-get install build-essential cmake libncurses-dev libreadline-dev libqrcodegen-dev pandoc pkg-config`
If you only intend to build core Notcurses (without multimedia support), run:
`apt-get install build-essential cmake libncurses-dev libreadline-dev libqrcodegen-dev pandoc pkg-config`
If you want to build the Python wrappers, you'll also need: If you want to build the Python wrappers, you'll also need:
`apt-get install python3-cffi python3-dev python3-pypandoc python3-setuptools` `apt-get install python3-cffi python3-dev python3-pypandoc python3-setuptools`
@ -28,8 +36,11 @@ If you want to build the Rust wrappers, you'll also need:
## Building ## Building
* Create a subdirectory, traditionally `build`. Enter the directory. * Create a subdirectory, traditionally `build` (this is not strictly necessary,
* `cmake ..`. You might want to set e.g. `CMAKE_BUILD_TYPE`. but it keeps your source tree clean). Enter the directory.
* `cmake ..`
** You might want to set e.g. `CMAKE_BUILD_TYPE`. Use `-DVAR=val`.
** To build without multimedia support, use `-DUSE_MULTIMEDIA=none`.
* `make` * `make`
* `make test` * `make test`
* `make install` * `make install`
@ -89,4 +100,3 @@ but must be `Debug` for use of `USE_COVERAGE`.
* `USE_POC`: build small, uninstalled proof-of-concept binaries * `USE_POC`: build small, uninstalled proof-of-concept binaries
* `USE_QRCODEGEN`: build qrcode support via libqrcodegen * `USE_QRCODEGEN`: build qrcode support via libqrcodegen
* `USE_STATIC`: build static libraries (in addition to shared ones) * `USE_STATIC`: build static libraries (in addition to shared ones)

@ -1,6 +1,12 @@
This document attempts to list user-visible changes and any major internal This document attempts to list user-visible changes and any major internal
rearrangements of Notcurses. rearrangements of Notcurses.
* 2.1.6 (not yet released):
* Notcurses has been split into two libraries, `notcurses-core` and
`notcurses`. The latter contains the heavyweight multimedia code,
so that applications which don't need this functionality can link against
only the former. `pkg-config` support is present for both.
* 2.1.5 (2021-01-15): * 2.1.5 (2021-01-15):
* Notcurses **now depends on GNU Readline at build and runtime**, entirely * Notcurses **now depends on GNU Readline at build and runtime**, entirely
for the benefit of direct mode, which now prepares GNU Readline for safe for the benefit of direct mode, which now prepares GNU Readline for safe

@ -86,6 +86,8 @@ Why use this non-standard library?
* The library object exports a minimal set of symbols. Where reasonable, * The library object exports a minimal set of symbols. Where reasonable,
`static inline` header-only code is used. This facilitates compiler `static inline` header-only code is used. This facilitates compiler
optimizations, and reduces loader time. optimizations, and reduces loader time.
* Notcurses can be built without its multimedia functionality, requiring a
significantly lesser set of dependencies.
* All APIs natively support the Universal Character Set (Unicode). The `cell` * All APIs natively support the Universal Character Set (Unicode). The `cell`
API is based around Unicode's [Extended Grapheme Cluster](https://unicode.org/reports/tr29/) concept. API is based around Unicode's [Extended Grapheme Cluster](https://unicode.org/reports/tr29/) concept.

@ -1,7 +1,7 @@
# Usage # Usage
As of version 2.0.0, Notcurses honors Semantic Versioning, the API is stable, As of version 2.0.0, the Notcurses API is stable, and the project is committed
and the project is committed to backwards compatibility. to backwards compatibility.
* [Direct Mode](#direct-mode) * [Direct Mode](#direct-mode)
* [Alignment](#alignment) * [Alignment](#alignment)
@ -15,16 +15,17 @@ and the project is committed to backwards compatibility.
* [Stats](#stats) * [Stats](#stats)
* [C++](#c++) * [C++](#c++)
A full API reference [is available](https://nick-black.com/notcurses/). Manual A full API reference [is available](https://nick-black.com/notcurses/) in the
pages ought have been installed along with Notcurses. This document is a form of manual pages; these ought have been installed along with Notcurses. This document is a
secondary reference, and should not be considered authoritative. For a more secondary reference, and should not be considered authoritative. For a more
unified commentary, consider the [paperback](https://www.amazon.com/dp/B086PNVNC9) unified commentary, consider the [paperback](https://www.amazon.com/dp/B086PNVNC9)
(also available as a free PDF from https://nick-black.com). (also available as a [free PDF](https://nick-black.com/dankwiki/index.php?title=Hacking_The_Planet!_with_Notcurses).
A program wishing to use Notcurses will need to link it, ideally using the A program wishing to use Notcurses will need to link it, ideally using the
output of `pkg-config --libs notcurses`. It is advised to compile with the output of `pkg-config --libs notcurses`. It is advised to compile with the
output of `pkg-config --cflags notcurses`. If using CMake, a support file is output of `pkg-config --cflags notcurses`. To use the minimal core Notcurses,
provided, and can be accessed as `Notcurses`. without multimedia support, use `pkg-config --libs notcurses-core` etc. If
using CMake, a support file is provided, and can be accessed as `Notcurses`.
Before calling into Notcurses—and usually as one of the first calls of the Before calling into Notcurses—and usually as one of the first calls of the
program—be sure to call `setlocale(3)` with an appropriate UTF-8 locale. It is program—be sure to call `setlocale(3)` with an appropriate UTF-8 locale. It is

Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

After

Width:  |  Height:  |  Size: 23 KiB

@ -8,9 +8,10 @@ notcurses - TUI library for modern terminal emulators
# SYNOPSIS # SYNOPSIS
**#include <notcurses/notcurses.h>** **#include <notcurses/notcurses.h>** or
**#include <notcurses/notcurses-core.h>**
**-lnotcurses** **-lnotcurses** or **-lnotcurses-core**
# DESCRIPTION # DESCRIPTION
@ -23,7 +24,8 @@ A program wishing to use Notcurses will need to link it, ideally using the
output of **pkg-config --libs notcurses** (see **pkg-config(1)**). It is output of **pkg-config --libs notcurses** (see **pkg-config(1)**). It is
advised to compile with the output of **pkg-config --cflags notcurses**. If advised to compile with the output of **pkg-config --cflags notcurses**. If
using CMake, a support file is provided, and can be accessed as **Notcurses** using CMake, a support file is provided, and can be accessed as **Notcurses**
(see **cmake(1)**). (see **cmake(1)**). If multimedia capabilities are not needed, it is possible
to link against a minimal Notcurses using **pkg-config --libs notcurses-core**.
**notcurses_init(3)** can then be used to initialize a Notcurses instance for a **notcurses_init(3)** can then be used to initialize a Notcurses instance for a
given **FILE** (usually **stdout**, usually attached to a terminal). given **FILE** (usually **stdout**, usually attached to a terminal).

@ -105,7 +105,6 @@ int dragon_demo(struct notcurses* nc){
} }
DEMO_RENDER(nc); DEMO_RENDER(nc);
demo_nanosleep(nc, &scaled); demo_nanosleep(nc, &scaled);
ncplane_erase(n);
}while(lasttotal != r); }while(lasttotal != r);
ncvisual_destroy(ncv); ncvisual_destroy(ncv);
return 0; return 0;

@ -878,13 +878,6 @@ int ncblit_rgba(const void* data, int linesize, const struct ncvisual_options* v
leny, lenx, blend); leny, lenx, blend);
} }
int rgba_blit_dispatch(ncplane* nc, const struct blitset* bset, int placey,
int placex, int linesize, const void* data, int begy,
int begx, int leny, int lenx, bool blendcolors){
return bset->blit(nc, placey, placex, linesize, data, begy, begx,
leny, lenx, blendcolors);
}
ncblitter_e ncvisual_media_defblitter(const notcurses* nc, ncscale_e scale){ ncblitter_e ncvisual_media_defblitter(const notcurses* nc, ncscale_e scale){
return rgba_blitter_default(nc->utf8, scale, nc->tcache.sextants); return rgba_blitter_default(nc->utf8, scale, nc->tcache.sextants);
} }

@ -1,72 +0,0 @@
#ifndef NOTCURSES_FFMPEG
#define NOTCURSES_FFMPEG
#include "version.h"
#ifdef USE_FFMPEG
extern "C" {
#include <libavutil/error.h>
#include <libavutil/frame.h>
#include <libavutil/pixdesc.h>
#include <libavutil/version.h>
#include <libavutil/imgutils.h>
#include <libavutil/rational.h>
#include <libswscale/swscale.h>
#include <libswscale/version.h>
#include <libavformat/version.h>
#include <libavformat/avformat.h>
} // extern "C"
struct AVFormatContext;
struct AVCodecContext;
struct AVFrame;
struct AVCodec;
struct AVCodecParameters;
struct AVPacket;
typedef struct ncvisual_details {
int packet_outstanding;
struct AVFormatContext* fmtctx;
struct AVCodecContext* codecctx; // video codec context
struct AVCodecContext* subtcodecctx; // subtitle codec context
struct AVFrame* frame; // frame as read
struct AVFrame* oframe; // RGBA frame
struct AVCodec* codec;
struct AVCodecParameters* cparams;
struct AVCodec* subtcodec;
struct AVPacket* packet;
struct SwsContext* swsctx;
AVSubtitle subtitle;
int stream_index; // match against this following av_read_frame()
int sub_stream_index; // subtitle stream index, can be < 0 if no subtitles
} ncvisual_details;
static inline auto
ncvisual_details_init(ncvisual_details* deets) -> int {
memset(deets, 0, sizeof(*deets));
deets->stream_index = -1;
deets->sub_stream_index = -1;
if((deets->frame = av_frame_alloc()) == nullptr){
return -1;
}
return 0;
}
static inline auto
ncvisual_details_destroy(ncvisual_details* deets) -> void {
avcodec_close(deets->codecctx);
avcodec_free_context(&deets->codecctx);
av_frame_free(&deets->frame);
av_freep(&deets->oframe);
//avcodec_parameters_free(&ncv->cparams);
sws_freeContext(deets->swsctx);
av_packet_free(&deets->packet);
avformat_close_input(&deets->fmtctx);
avsubtitle_free(&deets->subtitle);
}
#endif
#endif

@ -681,9 +681,13 @@ memdup(const void* src, size_t len){
void* bgra_to_rgba(const void* data, int rows, int rowstride, int cols); void* bgra_to_rgba(const void* data, int rows, int rowstride, int cols);
int rgba_blit_dispatch(ncplane* nc, const struct blitset* bset, int placey, static inline int
int placex, int linesize, const void* data, int begy, rgba_blit_dispatch(ncplane* nc, const struct blitset* bset, int placey,
int begx, int leny, int lenx, bool blendcolors); int placex, int linesize, const void* data, int begy,
int begx, int leny, int lenx, bool blendcolors){
return bset->blit(nc, placey, placex, linesize, data, begy, begx,
leny, lenx, blendcolors);
}
// find the "center" cell of two lengths. in the case of even rows/columns, we // find the "center" cell of two lengths. in the case of even rows/columns, we
// place the center on the top/left. in such a case there will be one more // place the center on the top/left. in such a case there will be one more
@ -1068,6 +1072,8 @@ int setup_signals(void* nc, bool no_quit_sigs, bool no_winch_sig,
int(*handler)(void*)); int(*handler)(void*));
int drop_signals(void* nc); int drop_signals(void* nc);
void ncvisual_printbanner(const notcurses* nc);
#ifdef __cplusplus #ifdef __cplusplus
} }
#endif #endif

@ -802,19 +802,7 @@ init_banner(const notcurses* nc){
#error "No __BYTE_ORDER__ definition" #error "No __BYTE_ORDER__ definition"
#endif #endif
, curses_version()); , curses_version());
#ifdef USE_FFMPEG ncvisual_printbanner(nc);
printf(" avformat %u.%u.%u avutil %u.%u.%u swscale %u.%u.%u\n",
LIBAVFORMAT_VERSION_MAJOR, LIBAVFORMAT_VERSION_MINOR, LIBAVFORMAT_VERSION_MICRO,
LIBAVUTIL_VERSION_MAJOR, LIBAVUTIL_VERSION_MINOR, LIBAVUTIL_VERSION_MICRO,
LIBSWSCALE_VERSION_MAJOR, LIBSWSCALE_VERSION_MINOR, LIBSWSCALE_VERSION_MICRO);
#else
#ifdef USE_OIIO
printf(" openimageio %s\n", oiio_version());
#else
term_fg_palindex(nc, stderr, nc->tcache.colors <= 88 ? 1 % nc->tcache.colors : 0xcb);
fprintf(stderr, "\n Warning! Notcurses was built without multimedia support.\n");
#endif
#endif
fflush(stdout); fflush(stdout);
term_fg_palindex(nc, stderr, nc->tcache.colors <= 88 ? 1 % nc->tcache.colors : 0xcb); term_fg_palindex(nc, stderr, nc->tcache.colors <= 88 ? 1 % nc->tcache.colors : 0xcb);
if(!nc->tcache.RGBflag){ // FIXME if(!nc->tcache.RGBflag){ // FIXME

@ -1,38 +0,0 @@
#ifndef NOTCURSES_OIIO
#define NOTCURSES_OIIO
// OpenImageIO implementation of ncvisual_details
#include "version.h"
#ifdef USE_OIIO
#include <OpenImageIO/filter.h>
#include <OpenImageIO/version.h>
#include <OpenImageIO/imageio.h>
#include <OpenImageIO/imagebuf.h>
#include <OpenImageIO/imagebufalgo.h>
typedef struct ncvisual_details {
std::unique_ptr<OIIO::ImageInput> image; // must be close()d
std::unique_ptr<uint32_t[]> frame;
std::unique_ptr<OIIO::ImageBuf> ibuf;
uint64_t framenum;
} ncvisual_details;
static inline auto
ncvisual_details_init(ncvisual_details *deets) -> int {
deets->image = nullptr;
deets->frame = nullptr;
deets->ibuf = nullptr;
deets->framenum = 0;
return 0;
}
static inline auto
ncvisual_details_destroy(ncvisual_details* deets) -> void {
if(deets->image){
deets->image->close();
}
}
#endif
#endif

@ -4,48 +4,40 @@
#include "builddef.h" #include "builddef.h"
#include "notcurses/notcurses.h" #include "notcurses/notcurses.h"
#ifdef USE_FFMPEG
#include "ffmpeg.h"
#else
#ifdef USE_OIIO
#include "oiio.h"
#else
typedef struct ncvisual_details {
} ncvisual_details;
static inline auto ncvisual_details_init(ncvisual_details* deets) -> int {
(void)deets;
return 0;
}
static inline auto
ncvisual_details_destroy(ncvisual_details* deets) -> void {
(void)deets;
}
#endif
#endif
struct ncplane; struct ncplane;
struct ncvisual_details;
typedef struct ncvisual { typedef struct ncvisual {
struct ncvisual_details* details;// implementation-specific details
uint32_t* data; // (scaled) RGBA image data, rowstride bytes per row
int cols, rows; int cols, rows;
// lines are sometimes padded. this many true bytes per row in data. // lines are sometimes padded. this many true bytes per row in data.
int rowstride; int rowstride;
ncvisual_details details;// implementation-specific details
uint32_t* data; // (scaled) RGBA image data, rowstride bytes per row
bool owndata; // we own data iff owndata == true bool owndata; // we own data iff owndata == true
} ncvisual; } ncvisual;
static inline auto typedef struct ncvisual_implementation {
ncvisual_create(void) -> ncvisual* { int (*ncvisual_init)(int loglevel);
auto ret = new ncvisual{}; int (*ncvisual_decode)(ncvisual*);
if(ret == nullptr){ int (*ncvisual_blit)(ncvisual* ncv, int rows, int cols, ncplane* n,
return nullptr; const struct blitset* bset, int placey, int placex,
} int begy, int begx, int leny, int lenx,
ncvisual_details_init(&ret->details); bool blendcolors);
return ret; ncvisual* (*ncvisual_create)(void);
} ncvisual* (*ncvisual_from_file)(const char* s);
void (*ncvisual_printbanner)(const struct notcurses* nc);
// ncv constructors other than ncvisual_from_file() need to set up the
// AVFrame* 'frame' according to their own data, which is assumed to
// have been prepared already in 'ncv'.
void (*ncvisual_details_seed)(ncvisual* ncv);
void (*ncvisual_details_destroy)(struct ncvisual_details* deets);
bool canopen_images;
bool canopen_videos;
} ncvisual_implementation;
// ugh! need export this for pluggable multimedia modules without dlopen()
__attribute__((visibility("default")))
int notcurses_set_ncvisual_implementation(const ncvisual_implementation* imp);
static inline auto static inline auto
ncvisual_set_data(ncvisual* ncv, uint32_t* data, bool owned) -> void { ncvisual_set_data(ncvisual* ncv, uint32_t* data, bool owned) -> void {

@ -4,10 +4,111 @@
#include "visual-details.h" #include "visual-details.h"
#include "internal.h" #include "internal.h"
// ncv constructors other than ncvisual_from_file() need to set up the // FIXME make this a weak symbol instead so we work with static linking
// AVFrame* 'frame' according to their own data, which is assumed to static const ncvisual_implementation* impl;
// have been prepared already in 'ncv'. static pthread_rwlock_t impllock = PTHREAD_RWLOCK_INITIALIZER;
auto ncvisual_details_seed(struct ncvisual* ncv) -> void;
int notcurses_set_ncvisual_implementation(const ncvisual_implementation* imp){
int ret = -1;
if(pthread_rwlock_wrlock(&impllock)){
return -1;
}
if(impl == nullptr){
impl = imp;
}
ret |= pthread_rwlock_unlock(&impllock);
return ret;
}
auto ncvisual_decode(ncvisual* nc) -> int {
int ret = -1;
if(pthread_rwlock_rdlock(&impllock)){
return -1;
}
if(impl){
ret = impl->ncvisual_decode(nc);
}
ret |= pthread_rwlock_unlock(&impllock);
return ret;
}
auto ncvisual_blit(ncvisual* ncv, int rows, int cols, ncplane* n,
const struct blitset* bset, int placey, int placex,
int begy, int begx, int leny, int lenx,
bool blendcolors) -> int {
int ret = -1;
if(pthread_rwlock_rdlock(&impllock)){
return -1;
}
if(impl){
if(impl->ncvisual_blit(ncv, rows, cols, n, bset, placey, placex,
begy, begx, leny, lenx, blendcolors) >= 0){
ret = 0;
}
}else{
if(rgba_blit_dispatch(n, bset, placey, placex, ncv->rowstride, ncv->data,
begy, begx, leny, lenx, blendcolors) >= 0){
ret = 0;
}
}
ret |= pthread_rwlock_unlock(&impllock);
return ret;
}
auto ncvisual_details_seed(struct ncvisual* ncv) -> void {
pthread_rwlock_rdlock(&impllock);
if(impl){
impl->ncvisual_details_seed(ncv);
}
pthread_rwlock_unlock(&impllock);
}
auto ncvisual_init(int loglevel) -> int {
int ret = 0; // default to success here
if(pthread_rwlock_rdlock(&impllock)){
return -1;
}
if(impl){
ret = impl->ncvisual_init(loglevel);
}
ret |= pthread_rwlock_unlock(&impllock);
return ret;
}
auto ncvisual_from_file(const char* filename) -> ncvisual* {
ncvisual* ret = nullptr;
if(pthread_rwlock_rdlock(&impllock) == 0){
if(impl){
ret = impl->ncvisual_from_file(filename);
}
pthread_rwlock_unlock(&impllock);
}
return ret;
}
auto ncvisual_create(void) -> ncvisual* {
ncvisual* ret = nullptr;
if(pthread_rwlock_rdlock(&impllock) == 0){
if(impl){
ret = impl->ncvisual_create();
}else{
ret = new ncvisual{};
}
pthread_rwlock_unlock(&impllock);
}
return ret;
}
auto ncvisual_printbanner(const notcurses* nc) -> void {
pthread_rwlock_rdlock(&impllock);
if(impl){
impl->ncvisual_printbanner(nc);
}else{
term_fg_palindex(nc, stderr, nc->tcache.colors <= 88 ? 1 % nc->tcache.colors : 0xcb);
fprintf(stderr, "\n Warning! Notcurses was built without multimedia support.\n");
}
pthread_rwlock_unlock(&impllock);
}
auto ncvisual_geom(const notcurses* nc, const ncvisual* n, auto ncvisual_geom(const notcurses* nc, const ncvisual* n,
const struct ncvisual_options* vopts, const struct ncvisual_options* vopts,
@ -484,11 +585,15 @@ auto ncvisual_from_plane(const ncplane* n, ncblitter_e blit, int begy, int begx,
auto ncvisual_destroy(ncvisual* ncv) -> void { auto ncvisual_destroy(ncvisual* ncv) -> void {
if(ncv){ if(ncv){
ncvisual_details_destroy(&ncv->details); pthread_rwlock_rdlock(&impllock);
if(impl){
impl->ncvisual_details_destroy(ncv->details);
}
if(ncv->owndata){ if(ncv->owndata){
free(ncv->data); free(ncv->data);
} }
delete ncv; delete ncv;
pthread_rwlock_unlock(&impllock);
} }
} }
@ -567,24 +672,18 @@ auto ncvisual_polyfill_yx(ncvisual* n, int y, int x, uint32_t rgba) -> int {
return ncvisual_polyfill_recurse(n, y, x, rgba, *pixel); return ncvisual_polyfill_recurse(n, y, x, rgba, *pixel);
} }
#ifndef USE_OIIO // built without ffmpeg or oiio
#ifndef USE_FFMPEG
auto ncvisual_from_file(const char* filename) -> ncvisual* {
(void)filename;
return nullptr;
}
auto notcurses_canopen_images(const notcurses* nc __attribute__ ((unused))) -> bool { auto notcurses_canopen_images(const notcurses* nc __attribute__ ((unused))) -> bool {
return false; if(!impl){
return false;
}
return impl->canopen_images;
} }
auto notcurses_canopen_videos(const notcurses* nc __attribute__ ((unused))) -> bool { auto notcurses_canopen_videos(const notcurses* nc __attribute__ ((unused))) -> bool {
return false; if(!impl){
} return false;
}
auto ncvisual_decode(ncvisual* nc) -> int { return impl->canopen_videos;
(void)nc;
return -1;
} }
auto ncvisual_decode_loop(ncvisual* nc) -> int { auto ncvisual_decode_loop(ncvisual* nc) -> int {
@ -609,28 +708,6 @@ auto ncvisual_subtitle(const ncvisual* ncv) -> char* {
return nullptr; return nullptr;
} }
auto ncvisual_init(int loglevel) -> int {
(void)loglevel;
return 0; // allow success here
}
auto ncvisual_blit(ncvisual* ncv, int rows, int cols, ncplane* n,
const struct blitset* bset, int placey, int placex,
int begy, int begx, int leny, int lenx,
bool blendcolors) -> int {
(void)rows;
(void)cols;
if(rgba_blit_dispatch(n, bset, placey, placex, ncv->rowstride, ncv->data,
begy, begx, leny, lenx, blendcolors) < 0){
return -1;
}
return 0;
}
auto ncvisual_details_seed(struct ncvisual* ncv) -> void {
(void)ncv;
}
auto ncvisual_resize(ncvisual* nc, int rows, int cols) -> int { auto ncvisual_resize(ncvisual* nc, int rows, int cols) -> int {
// we'd need to verify that it's RGBA as well, except that if we've got no // we'd need to verify that it's RGBA as well, except that if we've got no
// multimedia engine, we've only got memory-assembled ncvisuals, which are // multimedia engine, we've only got memory-assembled ncvisuals, which are
@ -640,6 +717,3 @@ auto ncvisual_resize(ncvisual* nc, int rows, int cols) -> int {
} }
return -1; return -1;
} }
#endif
#endif

@ -1,18 +1,47 @@
#include "builddef.h" #include "builddef.h"
#ifdef USE_FFMPEG #ifdef USE_FFMPEG
#include "ffmpeg.h" extern "C" {
#include <libavutil/error.h>
#include <libavutil/frame.h>
#include <libavutil/pixdesc.h>
#include <libavutil/version.h>
#include <libavutil/imgutils.h>
#include <libavutil/rational.h>
#include <libswscale/swscale.h>
#include <libswscale/version.h>
#include <libavformat/version.h>
#include <libavformat/avformat.h>
}
#include "internal.h" #include "internal.h"
#include "visual-details.h" #include "visual-details.h"
#define IMGALLOCALIGN 32 struct AVFormatContext;
struct AVCodecContext;
struct AVFrame;
struct AVCodec;
struct AVCodecParameters;
struct AVPacket;
bool notcurses_canopen_images(const notcurses* nc __attribute__ ((unused))) { typedef struct ncvisual_details {
return true; int packet_outstanding;
} struct AVFormatContext* fmtctx;
struct AVCodecContext* codecctx; // video codec context
struct AVCodecContext* subtcodecctx; // subtitle codec context
struct AVFrame* frame; // frame as read
struct AVFrame* oframe; // RGBA frame
struct AVCodec* codec;
struct AVCodecParameters* cparams;
struct AVCodec* subtcodec;
struct AVPacket* packet;
struct SwsContext* swsctx;
AVSubtitle subtitle;
int stream_index; // match against this following av_read_frame()
int sub_stream_index; // subtitle stream index, can be < 0 if no subtitles
} ncvisual_details;
bool notcurses_canopen_videos(const notcurses* nc __attribute__ ((unused))) { #define IMGALLOCALIGN 32
return true;
} static void inject_implementation(void) __attribute__ ((constructor));
/*static void /*static void
print_frame_summary(const AVCodecContext* cctx, const AVFrame* f) { print_frame_summary(const AVCodecContext* cctx, const AVFrame* f) {
@ -86,8 +115,8 @@ deass(const char* ass) {
} }
auto ncvisual_subtitle(const ncvisual* ncv) -> char* { auto ncvisual_subtitle(const ncvisual* ncv) -> char* {
for(unsigned i = 0 ; i < ncv->details.subtitle.num_rects ; ++i){ for(unsigned i = 0 ; i < ncv->details->subtitle.num_rects ; ++i){
const AVSubtitleRect* rect = ncv->details.subtitle.rects[i]; const AVSubtitleRect* rect = ncv->details->subtitle.rects[i];
if(rect->type == SUBTITLE_ASS){ if(rect->type == SUBTITLE_ASS){
return deass(rect->ass); return deass(rect->ass);
}else if(rect->type == SUBTITLE_TEXT) {; }else if(rect->type == SUBTITLE_TEXT) {;
@ -107,49 +136,50 @@ averr2ncerr(int averr){
return -1; return -1;
} }
int ncvisual_decode(ncvisual* nc){ static int
if(nc->details.fmtctx == nullptr){ // not a file-backed ncvisual ffmpeg_decode(ncvisual* nc){
if(nc->details->fmtctx == nullptr){ // not a file-backed ncvisual
return -1; return -1;
} }
bool have_frame = false; bool have_frame = false;
bool unref = false; bool unref = false;
// FIXME what if this was set up with e.g. ncvisual_from_rgba()? // FIXME what if this was set up with e.g. ncvisual_from_rgba()?
if(nc->details.oframe){ if(nc->details->oframe){
av_freep(&nc->details.oframe->data[0]); av_freep(&nc->details->oframe->data[0]);
} }
do{ do{
do{ do{
if(nc->details.packet_outstanding){ if(nc->details->packet_outstanding){
break; break;
} }
if(unref){ if(unref){
av_packet_unref(nc->details.packet); av_packet_unref(nc->details->packet);
} }
int averr; int averr;
if((averr = av_read_frame(nc->details.fmtctx, nc->details.packet)) < 0){ if((averr = av_read_frame(nc->details->fmtctx, nc->details->packet)) < 0){
/*if(averr != AVERROR_EOF){ /*if(averr != AVERROR_EOF){
fprintf(stderr, "Error reading frame info (%s)\n", av_err2str(averr)); fprintf(stderr, "Error reading frame info (%s)\n", av_err2str(averr));
}*/ }*/
return averr2ncerr(averr); return averr2ncerr(averr);
} }
unref = true; unref = true;
if(nc->details.packet->stream_index == nc->details.sub_stream_index){ if(nc->details->packet->stream_index == nc->details->sub_stream_index){
int result = 0, ret; int result = 0, ret;
ret = avcodec_decode_subtitle2(nc->details.subtcodecctx, &nc->details.subtitle, &result, nc->details.packet); ret = avcodec_decode_subtitle2(nc->details->subtcodecctx, &nc->details->subtitle, &result, nc->details->packet);
if(ret >= 0 && result){ if(ret >= 0 && result){
// FIXME? // FIXME?
} }
} }
}while(nc->details.packet->stream_index != nc->details.stream_index); }while(nc->details->packet->stream_index != nc->details->stream_index);
++nc->details.packet_outstanding; ++nc->details->packet_outstanding;
int averr = avcodec_send_packet(nc->details.codecctx, nc->details.packet); int averr = avcodec_send_packet(nc->details->codecctx, nc->details->packet);
if(averr < 0){ if(averr < 0){
//fprintf(stderr, "Error processing AVPacket\n"); //fprintf(stderr, "Error processing AVPacket\n");
return averr2ncerr(averr); return averr2ncerr(averr);
} }
--nc->details.packet_outstanding; --nc->details->packet_outstanding;
av_packet_unref(nc->details.packet); av_packet_unref(nc->details->packet);
averr = avcodec_receive_frame(nc->details.codecctx, nc->details.frame); averr = avcodec_receive_frame(nc->details->codecctx, nc->details->frame);
if(averr >= 0){ if(averr >= 0){
have_frame = true; have_frame = true;
}else if(averr == AVERROR(EAGAIN) || averr == AVERROR_EOF){ }else if(averr == AVERROR(EAGAIN) || averr == AVERROR_EOF){
@ -159,12 +189,12 @@ int ncvisual_decode(ncvisual* nc){
return averr2ncerr(averr); return averr2ncerr(averr);
} }
}while(!have_frame); }while(!have_frame);
//print_frame_summary(nc->details.codecctx, nc->details.frame); //print_frame_summary(nc->details->codecctx, nc->details->frame);
const AVFrame* f = nc->details.frame; const AVFrame* f = nc->details->frame;
nc->rowstride = f->linesize[0]; nc->rowstride = f->linesize[0];
nc->cols = nc->details.frame->width; nc->cols = nc->details->frame->width;
nc->rows = nc->details.frame->height; nc->rows = nc->details->frame->height;
//fprintf(stderr, "good decode! %d/%d %d %p\n", nc->details.frame->height, nc->details.frame->width, nc->rowstride, f->data); //fprintf(stderr, "good decode! %d/%d %d %p\n", nc->details->frame->height, nc->details->frame->width, nc->rowstride, f->data);
ncvisual_set_data(nc, reinterpret_cast<uint32_t*>(f->data[0]), false); ncvisual_set_data(nc, reinterpret_cast<uint32_t*>(f->data[0]), false);
return 0; return 0;
} }
@ -172,7 +202,7 @@ int ncvisual_decode(ncvisual* nc){
// resize frame to oframe, converting to RGBA (if necessary) along the way // resize frame to oframe, converting to RGBA (if necessary) along the way
int ncvisual_resize(ncvisual* nc, int rows, int cols) { int ncvisual_resize(ncvisual* nc, int rows, int cols) {
const int targformat = AV_PIX_FMT_RGBA; const int targformat = AV_PIX_FMT_RGBA;
AVFrame* inf = nc->details.oframe ? nc->details.oframe : nc->details.frame; AVFrame* inf = nc->details->oframe ? nc->details->oframe : nc->details->frame;
//fprintf(stderr, "got format: %d (%d/%d) want format: %d (%d/%d)\n", inf->format, nc->rows, nc->cols, targformat, rows, cols); //fprintf(stderr, "got format: %d (%d/%d) want format: %d (%d/%d)\n", inf->format, nc->rows, nc->cols, targformat, rows, cols);
if(inf->format == targformat && nc->rows == rows && nc->cols == cols){ if(inf->format == targformat && nc->rows == rows && nc->cols == cols){
return 0; return 0;
@ -231,72 +261,92 @@ int ncvisual_resize(ncvisual* nc, int rows, int cols) {
nc->rows = rows; nc->rows = rows;
nc->cols = cols; nc->cols = cols;
ncvisual_set_data(nc, reinterpret_cast<uint32_t*>(sframe->data[0]), true); ncvisual_set_data(nc, reinterpret_cast<uint32_t*>(sframe->data[0]), true);
if(nc->details.oframe){ if(nc->details->oframe){
//av_freep(nc->details.oframe->data); //av_freep(nc->details->oframe->data);
av_freep(&nc->details.oframe); av_freep(&nc->details->oframe);
} }
nc->details.oframe = sframe; nc->details->oframe = sframe;
//fprintf(stderr, "SIZE SCALED: %d %d (%u)\n", nc->details.oframe->height, nc->details.oframe->width, nc->details.oframe->linesize[0]); //fprintf(stderr, "SIZE SCALED: %d %d (%u)\n", nc->details->oframe->height, nc->details->oframe->width, nc->details->oframe->linesize[0]);
return 0; return 0;
} }
ncvisual* ncvisual_from_file(const char* filename) { auto ffmpeg_details_init(void) -> ncvisual_details* {
auto deets = new ncvisual_details{};
deets->stream_index = -1;
deets->sub_stream_index = -1;
if((deets->frame = av_frame_alloc()) == nullptr){
delete deets;
return nullptr;
}
return deets;
}
auto ffmpeg_create() -> ncvisual* {
auto nc = new ncvisual{};
if((nc->details = ffmpeg_details_init()) == nullptr){
delete nc;
return nullptr;
}
return nc;
}
ncvisual* ffmpeg_from_file(const char* filename) {
AVStream* st; AVStream* st;
ncvisual* ncv = ncvisual_create(); ncvisual* ncv = ffmpeg_create();
if(ncv == nullptr){ if(ncv == nullptr){
// fprintf(stderr, "Couldn't create %s (%s)\n", filename, strerror(errno)); // fprintf(stderr, "Couldn't create %s (%s)\n", filename, strerror(errno));
return nullptr; return nullptr;
} }
//fprintf(stderr, "FRAME FRAME: %p\n", ncv->details.frame); //fprintf(stderr, "FRAME FRAME: %p\n", ncv->details->frame);
int averr = avformat_open_input(&ncv->details.fmtctx, filename, nullptr, nullptr); int averr = avformat_open_input(&ncv->details->fmtctx, filename, nullptr, nullptr);
if(averr < 0){ if(averr < 0){
//fprintf(stderr, "Couldn't open %s (%d)\n", filename, averr); //fprintf(stderr, "Couldn't open %s (%d)\n", filename, averr);
goto err; goto err;
} }
averr = avformat_find_stream_info(ncv->details.fmtctx, nullptr); averr = avformat_find_stream_info(ncv->details->fmtctx, nullptr);
if(averr < 0){ if(averr < 0){
//fprintf(stderr, "Error extracting stream info from %s (%d)\n", filename, averr); //fprintf(stderr, "Error extracting stream info from %s (%d)\n", filename, averr);
goto err; goto err;
} }
//av_dump_format(ncv->details.fmtctx, 0, filename, false); //av_dump_format(ncv->details->fmtctx, 0, filename, false);
if((averr = av_find_best_stream(ncv->details.fmtctx, AVMEDIA_TYPE_SUBTITLE, -1, -1, &ncv->details.subtcodec, 0)) >= 0){ if((averr = av_find_best_stream(ncv->details->fmtctx, AVMEDIA_TYPE_SUBTITLE, -1, -1, &ncv->details->subtcodec, 0)) >= 0){
ncv->details.sub_stream_index = averr; ncv->details->sub_stream_index = averr;
if((ncv->details.subtcodecctx = avcodec_alloc_context3(ncv->details.subtcodec)) == nullptr){ if((ncv->details->subtcodecctx = avcodec_alloc_context3(ncv->details->subtcodec)) == nullptr){
//fprintf(stderr, "Couldn't allocate decoder for %s\n", filename); //fprintf(stderr, "Couldn't allocate decoder for %s\n", filename);
goto err; goto err;
} }
// FIXME do we need avcodec_parameters_to_context() here? // FIXME do we need avcodec_parameters_to_context() here?
if(avcodec_open2(ncv->details.subtcodecctx, ncv->details.subtcodec, nullptr) < 0){ if(avcodec_open2(ncv->details->subtcodecctx, ncv->details->subtcodec, nullptr) < 0){
//fprintf(stderr, "Couldn't open codec for %s (%s)\n", filename, av_err2str(*averr)); //fprintf(stderr, "Couldn't open codec for %s (%s)\n", filename, av_err2str(*averr));
goto err; goto err;
} }
}else{ }else{
ncv->details.sub_stream_index = -1; ncv->details->sub_stream_index = -1;
} }
//fprintf(stderr, "FRAME FRAME: %p\n", ncv->details.frame); //fprintf(stderr, "FRAME FRAME: %p\n", ncv->details->frame);
if((ncv->details.packet = av_packet_alloc()) == nullptr){ if((ncv->details->packet = av_packet_alloc()) == nullptr){
// fprintf(stderr, "Couldn't allocate packet for %s\n", filename); // fprintf(stderr, "Couldn't allocate packet for %s\n", filename);
goto err; goto err;
} }
if((averr = av_find_best_stream(ncv->details.fmtctx, AVMEDIA_TYPE_VIDEO, -1, -1, &ncv->details.codec, 0)) < 0){ if((averr = av_find_best_stream(ncv->details->fmtctx, AVMEDIA_TYPE_VIDEO, -1, -1, &ncv->details->codec, 0)) < 0){
// fprintf(stderr, "Couldn't find visuals in %s (%s)\n", filename, av_err2str(*averr)); // fprintf(stderr, "Couldn't find visuals in %s (%s)\n", filename, av_err2str(*averr));
goto err; goto err;
} }
ncv->details.stream_index = averr; ncv->details->stream_index = averr;
if(ncv->details.codec == nullptr){ if(ncv->details->codec == nullptr){
//fprintf(stderr, "Couldn't find decoder for %s\n", filename); //fprintf(stderr, "Couldn't find decoder for %s\n", filename);
goto err; goto err;
} }
st = ncv->details.fmtctx->streams[ncv->details.stream_index]; st = ncv->details->fmtctx->streams[ncv->details->stream_index];
if((ncv->details.codecctx = avcodec_alloc_context3(ncv->details.codec)) == nullptr){ if((ncv->details->codecctx = avcodec_alloc_context3(ncv->details->codec)) == nullptr){
//fprintf(stderr, "Couldn't allocate decoder for %s\n", filename); //fprintf(stderr, "Couldn't allocate decoder for %s\n", filename);
goto err; goto err;
} }
if(avcodec_parameters_to_context(ncv->details.codecctx, st->codecpar) < 0){ if(avcodec_parameters_to_context(ncv->details->codecctx, st->codecpar) < 0){
goto err; goto err;
} }
if(avcodec_open2(ncv->details.codecctx, ncv->details.codec, nullptr) < 0){ if(avcodec_open2(ncv->details->codecctx, ncv->details->codec, nullptr) < 0){
//fprintf(stderr, "Couldn't open codec for %s (%s)\n", filename, av_err2str(*averr)); //fprintf(stderr, "Couldn't open codec for %s (%s)\n", filename, av_err2str(*averr));
goto err; goto err;
} }
@ -304,11 +354,11 @@ ncvisual* ncvisual_from_file(const char* filename) {
//fprintf(stderr, "Couldn't allocate codec params for %s\n", filename); //fprintf(stderr, "Couldn't allocate codec params for %s\n", filename);
goto err; goto err;
} }
if((*averr = avcodec_parameters_from_context(ncv->cparams, ncv->details.codecctx)) < 0){ if((*averr = avcodec_parameters_from_context(ncv->cparams, ncv->details->codecctx)) < 0){
//fprintf(stderr, "Couldn't get codec params for %s (%s)\n", filename, av_err2str(*averr)); //fprintf(stderr, "Couldn't get codec params for %s (%s)\n", filename, av_err2str(*averr));
goto err; goto err;
}*/ }*/
//fprintf(stderr, "FRAME FRAME: %p\n", ncv->details.frame); //fprintf(stderr, "FRAME FRAME: %p\n", ncv->details->frame);
// frame is set up in prep_details(), so that format can be set there, as // frame is set up in prep_details(), so that format can be set there, as
// is necessary when it is prepared from inputs other than files. oframe // is necessary when it is prepared from inputs other than files. oframe
// is set up whenever we convert to RGBA. // is set up whenever we convert to RGBA.
@ -344,8 +394,8 @@ int ncvisual_stream(notcurses* nc, ncvisual* ncv, float timescale,
do{ do{
// codecctx seems to be off by a factor of 2 regularly. instead, go with // codecctx seems to be off by a factor of 2 regularly. instead, go with
// the time_base from the avformatctx. // the time_base from the avformatctx.
double tbase = av_q2d(ncv->details.fmtctx->streams[ncv->details.stream_index]->time_base); double tbase = av_q2d(ncv->details->fmtctx->streams[ncv->details->stream_index]->time_base);
int64_t ts = ncv->details.frame->best_effort_timestamp; int64_t ts = ncv->details->frame->best_effort_timestamp;
if(frame == 1 && ts){ if(frame == 1 && ts){
usets = true; usets = true;
} }
@ -362,8 +412,8 @@ int ncvisual_stream(notcurses* nc, ncvisual* ncv, float timescale,
activevopts.n = newn; activevopts.n = newn;
} }
++frame; ++frame;
uint64_t duration = ncv->details.frame->pkt_duration * tbase * NANOSECS_IN_SEC; uint64_t duration = ncv->details->frame->pkt_duration * tbase * NANOSECS_IN_SEC;
//fprintf(stderr, "use: %u dur: %ju ts: %ju cctx: %f fctx: %f\n", usets, duration, ts, av_q2d(ncv->details.codecctx->time_base), av_q2d(ncv->details.fmtctx->streams[ncv->stream_index]->time_base)); //fprintf(stderr, "use: %u dur: %ju ts: %ju cctx: %f fctx: %f\n", usets, duration, ts, av_q2d(ncv->details->codecctx->time_base), av_q2d(ncv->details->fmtctx->streams[ncv->stream_index]->time_base));
double schedns = nsbegin; double schedns = nsbegin;
if(usets){ if(usets){
if(tbase == 0){ if(tbase == 0){
@ -401,7 +451,7 @@ int ncvisual_stream(notcurses* nc, ncvisual* ncv, float timescale,
int ncvisual_decode_loop(ncvisual* ncv){ int ncvisual_decode_loop(ncvisual* ncv){
int r = ncvisual_decode(ncv); int r = ncvisual_decode(ncv);
if(r == 1){ if(r == 1){
if(av_seek_frame(ncv->details.fmtctx, ncv->details.stream_index, 0, AVSEEK_FLAG_FRAME) < 0){ if(av_seek_frame(ncv->details->fmtctx, ncv->details->stream_index, 0, AVSEEK_FLAG_FRAME) < 0){
// FIXME log error // FIXME log error
return -1; return -1;
} }
@ -412,12 +462,11 @@ int ncvisual_decode_loop(ncvisual* ncv){
return r; return r;
} }
int ncvisual_blit(ncvisual* ncv, int rows, int cols, ncplane* n, int ffmpeg_blit(ncvisual* ncv, int rows, int cols, ncplane* n,
const struct blitset* bset, int placey, int placex, const struct blitset* bset, int placey, int placex,
int begy, int begx, int leny, int lenx, int begy, int begx, int leny, int lenx, bool blendcolors) {
bool blendcolors) { const AVFrame* inframe = ncv->details->oframe ? ncv->details->oframe : ncv->details->frame;
const AVFrame* inframe = ncv->details.oframe ? ncv->details.oframe : ncv->details.frame; //fprintf(stderr, "inframe: %p oframe: %p frame: %p\n", inframe, ncv->details->oframe, ncv->details->frame);
//fprintf(stderr, "inframe: %p oframe: %p frame: %p\n", inframe, ncv->details.oframe, ncv->details.frame);
void* data = nullptr; void* data = nullptr;
int stride = 0; int stride = 0;
AVFrame* sframe = nullptr; AVFrame* sframe = nullptr;
@ -431,14 +480,14 @@ int ncvisual_blit(ncvisual* ncv, int rows, int cols, ncplane* n,
return -1; return -1;
} }
//fprintf(stderr, "WHN NCV: %d/%d\n", inframe->width, inframe->height); //fprintf(stderr, "WHN NCV: %d/%d\n", inframe->width, inframe->height);
ncv->details.swsctx = sws_getCachedContext(ncv->details.swsctx, ncv->details->swsctx = sws_getCachedContext(ncv->details->swsctx,
ncv->cols, ncv->rows, ncv->cols, ncv->rows,
static_cast<AVPixelFormat>(inframe->format), static_cast<AVPixelFormat>(inframe->format),
cols, rows, cols, rows,
static_cast<AVPixelFormat>(targformat), static_cast<AVPixelFormat>(targformat),
SWS_LANCZOS, nullptr, nullptr, nullptr); SWS_LANCZOS, nullptr, nullptr, nullptr);
if(ncv->details.swsctx == nullptr){ if(ncv->details->swsctx == nullptr){
//fprintf(stderr, "Error retrieving details.swsctx\n"); //fprintf(stderr, "Error retrieving details->swsctx\n");
return -1; return -1;
} }
memcpy(sframe, inframe, sizeof(*inframe)); memcpy(sframe, inframe, sizeof(*inframe));
@ -453,7 +502,7 @@ int ncvisual_blit(ncvisual* ncv, int rows, int cols, ncplane* n,
//fprintf(stderr, "Error allocating visual data (%d X %d)\n", sframe->height, sframe->width); //fprintf(stderr, "Error allocating visual data (%d X %d)\n", sframe->height, sframe->width);
return -1; return -1;
} }
int height = sws_scale(ncv->details.swsctx, (const uint8_t* const*)inframe->data, int height = sws_scale(ncv->details->swsctx, (const uint8_t* const*)inframe->data,
inframe->linesize, 0, inframe->height, sframe->data, inframe->linesize, 0, inframe->height, sframe->data,
sframe->linesize); sframe->linesize);
if(height < 0){ if(height < 0){
@ -484,20 +533,58 @@ int ncvisual_blit(ncvisual* ncv, int rows, int cols, ncplane* n,
return 0; return 0;
} }
auto ncvisual_details_seed(ncvisual* ncv) -> void { auto ffmpeg_details_seed(ncvisual* ncv) -> void {
assert(nullptr == ncv->details.oframe); assert(nullptr == ncv->details->oframe);
ncv->details.frame->data[0] = reinterpret_cast<uint8_t*>(ncv->data); ncv->details->frame->data[0] = reinterpret_cast<uint8_t*>(ncv->data);
ncv->details.frame->data[1] = nullptr; ncv->details->frame->data[1] = nullptr;
ncv->details.frame->linesize[0] = ncv->rowstride; ncv->details->frame->linesize[0] = ncv->rowstride;
ncv->details.frame->linesize[1] = 0; ncv->details->frame->linesize[1] = 0;
ncv->details.frame->width = ncv->cols; ncv->details->frame->width = ncv->cols;
ncv->details.frame->height = ncv->rows; ncv->details->frame->height = ncv->rows;
ncv->details.frame->format = AV_PIX_FMT_RGBA; ncv->details->frame->format = AV_PIX_FMT_RGBA;
} }
int ncvisual_init(int loglevel) { auto ffmpeg_init(int loglevel) -> int {
av_log_set_level(loglevel); av_log_set_level(loglevel);
// FIXME could also use av_log_set_callback() and capture the message... // FIXME could also use av_log_set_callback() and capture the message...
return 0; return 0;
} }
void ffmpeg_printbanner(const notcurses* nc __attribute__ ((unused))){
printf(" avformat %u.%u.%u avutil %u.%u.%u swscale %u.%u.%u\n",
LIBAVFORMAT_VERSION_MAJOR, LIBAVFORMAT_VERSION_MINOR, LIBAVFORMAT_VERSION_MICRO,
LIBAVUTIL_VERSION_MAJOR, LIBAVUTIL_VERSION_MINOR, LIBAVUTIL_VERSION_MICRO,
LIBSWSCALE_VERSION_MAJOR, LIBSWSCALE_VERSION_MINOR, LIBSWSCALE_VERSION_MICRO);
}
auto ffmpeg_details_destroy(ncvisual_details* deets) -> void {
avcodec_close(deets->codecctx);
avcodec_free_context(&deets->codecctx);
av_frame_free(&deets->frame);
av_freep(&deets->oframe);
//avcodec_parameters_free(&ncv->cparams);
sws_freeContext(deets->swsctx);
av_packet_free(&deets->packet);
avformat_close_input(&deets->fmtctx);
avsubtitle_free(&deets->subtitle);
delete deets;
}
const static ncvisual_implementation ffmpeg_impl = {
.ncvisual_init = ffmpeg_init,
.ncvisual_decode = ffmpeg_decode,
.ncvisual_blit = ffmpeg_blit,
.ncvisual_create = ffmpeg_create,
.ncvisual_from_file = ffmpeg_from_file,
.ncvisual_printbanner = ffmpeg_printbanner,
.ncvisual_details_seed = ffmpeg_details_seed,
.ncvisual_details_destroy = ffmpeg_details_destroy,
.canopen_images = true,
.canopen_videos = true,
};
static void inject_implementation(void){
notcurses_set_ncvisual_implementation(&ffmpeg_impl);
}
#endif #endif

@ -1,29 +1,63 @@
#include "builddef.h" #include "builddef.h"
#ifdef USE_OIIO #ifdef USE_OIIO
#include "oiio.h" #include <OpenImageIO/filter.h>
#include <OpenImageIO/version.h>
#include <OpenImageIO/imageio.h>
#include <OpenImageIO/imagebuf.h>
#include <OpenImageIO/imagebufalgo.h>
#include "internal.h" #include "internal.h"
#include "visual-details.h" #include "visual-details.h"
bool notcurses_canopen_images(const notcurses* nc __attribute__ ((unused))) { static void inject_implementation(void) __attribute__ ((constructor));
return true;
typedef struct ncvisual_details {
std::unique_ptr<OIIO::ImageInput> image; // must be close()d
std::unique_ptr<uint32_t[]> frame;
std::unique_ptr<OIIO::ImageBuf> ibuf;
uint64_t framenum;
} ncvisual_details;
static inline auto
oiio_details_init(void) -> ncvisual_details* {
auto deets = new ncvisual_details{};
if(deets){
deets->image = nullptr;
deets->frame = nullptr;
deets->ibuf = nullptr;
deets->framenum = 0;
}
return deets;
} }
bool notcurses_canopen_videos(const notcurses* nc __attribute__ ((unused))) { static inline auto
return false; // too slow for reliable use at the moment oiio_details_destroy(ncvisual_details* deets) -> void {
if(deets->image){
deets->image->close();
}
delete deets;
} }
ncvisual* ncvisual_from_file(const char* filename) { auto oiio_create() -> ncvisual* {
ncvisual* ncv = ncvisual_create(); auto nc = new ncvisual{};
if((nc->details = oiio_details_init()) == nullptr){
delete nc;
return nullptr;
}
return nc;
}
ncvisual* oiio_from_file(const char* filename) {
ncvisual* ncv = oiio_create();
if(ncv == nullptr){ if(ncv == nullptr){
return nullptr; return nullptr;
} }
ncv->details.image = OIIO::ImageInput::open(filename); ncv->details->image = OIIO::ImageInput::open(filename);
if(!ncv->details.image){ if(!ncv->details->image){
// fprintf(stderr, "Couldn't create %s (%s)\n", filename, strerror(errno)); // fprintf(stderr, "Couldn't create %s (%s)\n", filename, strerror(errno));
ncvisual_destroy(ncv); ncvisual_destroy(ncv);
return nullptr; return nullptr;
} }
/*const auto &spec = ncv->details.image->spec_dimensions(0); /*const auto &spec = ncv->details->image->spec_dimensions(0);
std::cout << "Opened " << filename << ": " << spec.height << "x" << std::cout << "Opened " << filename << ": " << spec.height << "x" <<
spec.width << "@" << spec.nchannels << " (" << spec.format << ")" << std::endl;*/ spec.width << "@" << spec.nchannels << " (" << spec.format << ")" << std::endl;*/
if(ncvisual_decode(ncv)){ if(ncvisual_decode(ncv)){
@ -33,38 +67,38 @@ spec.width << "@" << spec.nchannels << " (" << spec.format << ")" << std::endl;*
return ncv; return ncv;
} }
int ncvisual_decode(ncvisual* nc) { int oiio_decode(ncvisual* nc) {
//fprintf(stderr, "current subimage: %d frame: %p\n", nc->details.image->current_subimage(), nc->details.frame.get()); //fprintf(stderr, "current subimage: %d frame: %p\n", nc->details->image->current_subimage(), nc->details->frame.get());
const auto &spec = nc->details.image->spec_dimensions(nc->details.framenum); const auto &spec = nc->details->image->spec_dimensions(nc->details->framenum);
if(nc->details.frame){ if(nc->details->frame){
//fprintf(stderr, "seeking subimage: %d\n", nc->details.image->current_subimage() + 1); //fprintf(stderr, "seeking subimage: %d\n", nc->details->image->current_subimage() + 1);
OIIO::ImageSpec newspec; OIIO::ImageSpec newspec;
if(!nc->details.image->seek_subimage(nc->details.image->current_subimage() + 1, 0, newspec)){ if(!nc->details->image->seek_subimage(nc->details->image->current_subimage() + 1, 0, newspec)){
return 1; return 1;
} }
// FIXME check newspec vis-a-vis image->spec()? // FIXME check newspec vis-a-vis image->spec()?
} }
//fprintf(stderr, "SUBIMAGE: %d\n", nc->details.image->current_subimage()); //fprintf(stderr, "SUBIMAGE: %d\n", nc->details->image->current_subimage());
auto pixels = spec.width * spec.height;// * spec.nchannels; auto pixels = spec.width * spec.height;// * spec.nchannels;
if(spec.nchannels < 3 || spec.nchannels > 4){ if(spec.nchannels < 3 || spec.nchannels > 4){
return -1; // FIXME get some to test with return -1; // FIXME get some to test with
} }
nc->details.frame = std::make_unique<uint32_t[]>(pixels); nc->details->frame = std::make_unique<uint32_t[]>(pixels);
if(spec.nchannels == 3){ // FIXME replace with channel shuffle if(spec.nchannels == 3){ // FIXME replace with channel shuffle
std::fill(nc->details.frame.get(), nc->details.frame.get() + pixels, 0xfffffffful); std::fill(nc->details->frame.get(), nc->details->frame.get() + pixels, 0xfffffffful);
} }
//fprintf(stderr, "READING: %d %ju\n", nc->details.image->current_subimage(), nc->details.framenum); //fprintf(stderr, "READING: %d %ju\n", nc->details->image->current_subimage(), nc->details->framenum);
if(!nc->details.image->read_image(nc->details.framenum++, 0, 0, spec.nchannels, OIIO::TypeDesc(OIIO::TypeDesc::UINT8, 4), nc->details.frame.get(), 4)){ if(!nc->details->image->read_image(nc->details->framenum++, 0, 0, spec.nchannels, OIIO::TypeDesc(OIIO::TypeDesc::UINT8, 4), nc->details->frame.get(), 4)){
return -1; return -1;
} }
//fprintf(stderr, "READ: %d %ju\n", nc->details.image->current_subimage(), nc->details.framenum); //fprintf(stderr, "READ: %d %ju\n", nc->details->image->current_subimage(), nc->details->framenum);
/*for(int i = 0 ; i < pixels ; ++i){ /*for(int i = 0 ; i < pixels ; ++i){
//fprintf(stderr, "%06d %02x %02x %02x %02x\n", i, //fprintf(stderr, "%06d %02x %02x %02x %02x\n", i,
fprintf(stderr, "%06d %d %d %d %d\n", i, fprintf(stderr, "%06d %d %d %d %d\n", i,
(nc->details.frame[i]) & 0xff, (nc->details->frame[i]) & 0xff,
(nc->details.frame[i] >> 8) & 0xff, (nc->details->frame[i] >> 8) & 0xff,
(nc->details.frame[i] >> 16) & 0xff, (nc->details->frame[i] >> 16) & 0xff,
nc->details.frame[i] >> 24 nc->details->frame[i] >> 24
); );
}*/ }*/
nc->cols = spec.width; nc->cols = spec.width;
@ -72,10 +106,10 @@ int ncvisual_decode(ncvisual* nc) {
nc->rowstride = nc->cols * 4; nc->rowstride = nc->cols * 4;
OIIO::ImageSpec rgbaspec = spec; OIIO::ImageSpec rgbaspec = spec;
rgbaspec.nchannels = 4; rgbaspec.nchannels = 4;
nc->details.ibuf = std::make_unique<OIIO::ImageBuf>(rgbaspec, nc->details.frame.get()); nc->details->ibuf = std::make_unique<OIIO::ImageBuf>(rgbaspec, nc->details->frame.get());
//fprintf(stderr, "SUBS: %d\n", nc->details.ibuf->nsubimages()); //fprintf(stderr, "SUBS: %d\n", nc->details->ibuf->nsubimages());
ncvisual_set_data(nc, static_cast<uint32_t*>(nc->details.ibuf->localpixels()), false); ncvisual_set_data(nc, static_cast<uint32_t*>(nc->details->ibuf->localpixels()), false);
//fprintf(stderr, "POST-DECODE DATA: %d %d %p %p\n", nc->rows, nc->cols, nc->data, nc->details.ibuf->localpixels()); //fprintf(stderr, "POST-DECODE DATA: %d %d %p %p\n", nc->rows, nc->cols, nc->data, nc->details->ibuf->localpixels());
return 0; return 0;
} }
@ -83,10 +117,10 @@ int ncvisual_decode_loop(ncvisual* ncv){
int r = ncvisual_decode(ncv); int r = ncvisual_decode(ncv);
if(r == 1){ if(r == 1){
OIIO::ImageSpec newspec; OIIO::ImageSpec newspec;
if(ncv->details.image->seek_subimage(0, 0, newspec)){ if(ncv->details->image->seek_subimage(0, 0, newspec)){
return -1; return -1;
} }
ncv->details.framenum = 0; ncv->details->framenum = 0;
if(ncvisual_decode(ncv) < 0){ if(ncvisual_decode(ncv) < 0){
return -1; return -1;
} }
@ -98,13 +132,13 @@ int ncvisual_decode_loop(ncvisual* ncv){
int ncvisual_resize(ncvisual* nc, int rows, int cols) { int ncvisual_resize(ncvisual* nc, int rows, int cols) {
//fprintf(stderr, "%d/%d -> %d/%d on the resize\n", ncv->rows, ncv->cols, rows, cols); //fprintf(stderr, "%d/%d -> %d/%d on the resize\n", ncv->rows, ncv->cols, rows, cols);
auto ibuf = std::make_unique<OIIO::ImageBuf>(); auto ibuf = std::make_unique<OIIO::ImageBuf>();
if(nc->details.ibuf && (nc->cols != cols || nc->rows != rows)){ // scale it if(nc->details->ibuf && (nc->cols != cols || nc->rows != rows)){ // scale it
OIIO::ImageSpec sp{}; OIIO::ImageSpec sp{};
sp.width = cols; sp.width = cols;
sp.height = rows; sp.height = rows;
ibuf->reset(sp, OIIO::InitializePixels::Yes); ibuf->reset(sp, OIIO::InitializePixels::Yes);
OIIO::ROI roi(0, cols, 0, rows, 0, 1, 0, 4); OIIO::ROI roi(0, cols, 0, rows, 0, 1, 0, 4);
if(!OIIO::ImageBufAlgo::resize(*ibuf, *nc->details.ibuf, "", 0, roi)){ if(!OIIO::ImageBufAlgo::resize(*ibuf, *nc->details->ibuf, "", 0, roi)){
return -1; return -1;
} }
nc->cols = cols; nc->cols = cols;
@ -112,12 +146,12 @@ int ncvisual_resize(ncvisual* nc, int rows, int cols) {
nc->rowstride = cols * 4; nc->rowstride = cols * 4;
ncvisual_set_data(nc, static_cast<uint32_t*>(ibuf->localpixels()), false); ncvisual_set_data(nc, static_cast<uint32_t*>(ibuf->localpixels()), false);
//fprintf(stderr, "HAVE SOME NEW DATA: %p\n", ibuf->localpixels()); //fprintf(stderr, "HAVE SOME NEW DATA: %p\n", ibuf->localpixels());
nc->details.ibuf = std::move(ibuf); nc->details->ibuf = std::move(ibuf);
} }
return 0; return 0;
} }
int ncvisual_blit(struct ncvisual* ncv, int rows, int cols, int oiio_blit(struct ncvisual* ncv, int rows, int cols,
ncplane* n, const struct blitset* bset, ncplane* n, const struct blitset* bset,
int placey, int placex, int begy, int begx, int placey, int placex, int begy, int begx,
int leny, int lenx, bool blendcolors) { int leny, int lenx, bool blendcolors) {
@ -125,13 +159,13 @@ int ncvisual_blit(struct ncvisual* ncv, int rows, int cols,
void* data = nullptr; void* data = nullptr;
int stride = 0; int stride = 0;
auto ibuf = std::make_unique<OIIO::ImageBuf>(); auto ibuf = std::make_unique<OIIO::ImageBuf>();
if(ncv->details.ibuf && (ncv->cols != cols || ncv->rows != rows)){ // scale it if(ncv->details->ibuf && (ncv->cols != cols || ncv->rows != rows)){ // scale it
OIIO::ImageSpec sp{}; OIIO::ImageSpec sp{};
sp.width = cols; sp.width = cols;
sp.height = rows; sp.height = rows;
ibuf->reset(sp, OIIO::InitializePixels::Yes); ibuf->reset(sp, OIIO::InitializePixels::Yes);
OIIO::ROI roi(0, cols, 0, rows, 0, 1, 0, 4); OIIO::ROI roi(0, cols, 0, rows, 0, 1, 0, 4);
if(!OIIO::ImageBufAlgo::resize(*ibuf, *ncv->details.ibuf, "", 0, roi)){ if(!OIIO::ImageBufAlgo::resize(*ibuf, *ncv->details->ibuf, "", 0, roi)){
return -1; return -1;
} }
stride = cols * 4; stride = cols * 4;
@ -205,37 +239,53 @@ char* ncvisual_subtitle(const ncvisual* ncv) { // no support in OIIO
/* /*
auto ncvisual_rotate(ncvisual* ncv, double rads) -> int { auto ncvisual_rotate(ncvisual* ncv, double rads) -> int {
OIIO::ROI roi(0, ncv->cols, 0, ncv->rows, 0, 1, 0, 4); OIIO::ROI roi(0, ncv->cols, 0, ncv->rows, 0, 1, 0, 4);
auto tmpibuf = std::move(*ncv->details.ibuf); auto tmpibuf = std::move(*ncv->details->ibuf);
ncv->details.ibuf = std::make_unique<OIIO::ImageBuf>(); ncv->details->ibuf = std::make_unique<OIIO::ImageBuf>();
OIIO::ImageSpec sp{}; OIIO::ImageSpec sp{};
sp.set_format(OIIO::TypeDesc(OIIO::TypeDesc::UINT8, 4)); sp.set_format(OIIO::TypeDesc(OIIO::TypeDesc::UINT8, 4));
sp.nchannels = 4; sp.nchannels = 4;
ncv->details.ibuf->reset(); ncv->details->ibuf->reset();
if(!OIIO::ImageBufAlgo::rotate(*ncv->details.ibuf, tmpibuf, rads, "", 0, true, roi)){ if(!OIIO::ImageBufAlgo::rotate(*ncv->details->ibuf, tmpibuf, rads, "", 0, true, roi)){
return NCERR_DECODE; // FIXME need we do anything further? return NCERR_DECODE; // FIXME need we do anything further?
} }
ncv->rowstride = ncv->cols * 4; ncv->rowstride = ncv->cols * 4;
ncvisual_set_data(ncv, static_cast<uint32_t*>(ncv->details.ibuf->localpixels()), false); ncvisual_set_data(ncv, static_cast<uint32_t*>(ncv->details->ibuf->localpixels()), false);
return NCERR_SUCCESS; return NCERR_SUCCESS;
} }
*/ */
auto ncvisual_details_seed(ncvisual* ncv) -> void { auto oiio_details_seed(ncvisual* ncv) -> void {
(void)ncv; (void)ncv;
// FIXME? // FIXME?
} }
int ncvisual_init(int loglevel) { int oiio_init(int loglevel) {
// FIXME set OIIO global attribute "debug" based on loglevel // FIXME set OIIO global attribute "debug" based on loglevel
(void)loglevel; (void)loglevel;
// FIXME check OIIO_VERSION_STRING components against linked openimageio_version() // FIXME check OIIO_VERSION_STRING components against linked openimageio_version()
return 0; // allow success here return 0; // allow success here
} }
extern "C" {
// FIXME would be nice to have OIIO::attributes("libraries") in here // FIXME would be nice to have OIIO::attributes("libraries") in here
const char* oiio_version(void){ void oiio_printbanner(const notcurses* nc __attribute__ ((unused))){
return OIIO_VERSION_STRING; printf(" openimageio %s\n", OIIO_VERSION_STRING);
} }
const static ncvisual_implementation oiio_impl = {
.ncvisual_init = oiio_init,
.ncvisual_decode = oiio_decode,
.ncvisual_blit = oiio_blit,
.ncvisual_create = oiio_create,
.ncvisual_from_file = oiio_from_file,
.ncvisual_printbanner = oiio_printbanner,
.ncvisual_details_seed = oiio_details_seed,
.ncvisual_details_destroy = oiio_details_destroy,
.canopen_images = true,
.canopen_videos = false,
};
static void inject_implementation(void){
notcurses_set_ncvisual_implementation(&oiio_impl);
} }
#endif #endif

@ -8,5 +8,5 @@ Description: C++ bindings for notcurses
Version: @PROJECT_VERSION@ Version: @PROJECT_VERSION@
Requires: notcurses >= @PROJECT_VERSION@ Requires: notcurses >= @PROJECT_VERSION@
Libs: -L${libdir} -lnotcurses++ Libs: -L${libdir} -lnotcurses -lnotcurses++
Cflags: -I${includedir} Cflags: -I${includedir}

@ -0,0 +1,12 @@
prefix=@CMAKE_INSTALL_PREFIX@
exec_prefix=@CMAKE_INSTALL_PREFIX@
libdir=${prefix}/@CMAKE_INSTALL_LIBDIR@
includedir=${prefix}/@CMAKE_INSTALL_INCLUDEDIR@
Name: @PROJECT_NAME@
Description: TUI library for modern terminal emulators
Version: @PROJECT_VERSION@
Requires:
Libs: -L${libdir} -lnotcurses-core
Cflags: -I${includedir}

@ -8,5 +8,5 @@ Description: TUI library for modern terminal emulators
Version: @PROJECT_VERSION@ Version: @PROJECT_VERSION@
Requires: Requires:
Libs: -L${libdir} -lnotcurses Libs: -L${libdir} -lnotcurses-core -lnotcurses
Cflags: -I${includedir} Cflags: -I${includedir}

Loading…
Cancel
Save