Embedding assets in extensions

I’ve found a neat way to embed files in a C++ header file using CMake and thought nit might be useful to share it for other extension devs.

I adapted a CMake file I found online here: How to Embed Files in Your C++ Project - That One Game Dev and changed it so that it would also use the file extension as part of the variable name used to reference the file contents.

My version is this:

function(target_embed_files TARGET_NAME)
    set(OPTIONS)
    set(SINGLE_VALUE)
    set(MULTIPLE_VALUE FILES)

    cmake_parse_arguments(
        EF
        "${OPTIONS}"
        "${SINGLE_VALUE}"
        "${MULTIPLE_VALUE}"
        ${ARGN}
    )

    string(TOUPPER "${TARGET_NAME}" TARGET_NAME_UPPER)
    string(MAKE_C_IDENTIFIER "${TARGET_NAME_UPPER}" TARGET_NAME_UPPER)

    string(TOLOWER "${TARGET_NAME}" TARGET_NAME_LOWER)
    string(MAKE_C_IDENTIFIER "${TARGET_NAME_LOWER}" TARGET_NAME_LOWER)

    set(GENERATE_HEADER_LOCATION "${CMAKE_BINARY_DIR}/embed/include")
    set(EMBED_HEADERS_LOCATION "${GENERATE_HEADER_LOCATION}/embed")
    set(HEADER_FILENAME "${TARGET_NAME_LOWER}.h")
    set(ARRAY_DECLARATIONS)

    foreach(INPUT_FILE IN LISTS EF_FILES)


        get_filename_component(FILEFULLNAME "${INPUT_FILE}" NAME)
        string(REPLACE "." "_" FILENAME "${FILEFULLNAME}")


        string(TOLOWER "${FILENAME}" FILENAME)
        string(MAKE_C_IDENTIFIER "${FILENAME}" FILENAME)

        file(READ "${INPUT_FILE}" BYTES HEX)
        string(REGEX REPLACE "(..)" "0x\\1, " BYTES "${BYTES}")

        string(CONFIGURE "const std::uint8_t ${FILENAME}[] = { ${BYTES} }\;" CURRENT_DECLARATION)

        list(APPEND ARRAY_DECLARATIONS "${CURRENT_DECLARATION}")
    endforeach()

    string(JOIN "\n" ARRAY_DECLARATIONS ${ARRAY_DECLARATIONS})

    set(BASIC_HEADER "\
#ifndef ${TARGET_NAME_UPPER}_EMBED_FILES
#define ${TARGET_NAME_UPPER}_EMBED_FILES

#include <cstdint>

${ARRAY_DECLARATIONS}

#endif
    ")

    string(CONFIGURE "${BASIC_HEADER}" BASIC_HEADER)

    file(WRITE "${EMBED_HEADERS_LOCATION}/${HEADER_FILENAME}" "${BASIC_HEADER}")
    target_include_directories(${TARGET_NAME} PUBLIC "${GENERATE_HEADER_LOCATION}")
endfunction()

then in your CMakeLists.txt file, you include it and then call the function thus:

include("embed.cmake")      # routine to create .h headers with embedded file contents

# include embedded web content
target_embed_files(${PROJECT_NAME} FILES    "content/css/bootstrap.min.css" 
                                            "content/css/songchooser.css"
                                            "content/js/bootstrap.bundle.min.js"
                                            "content/js/songchooser.js"
                                            "content/icon/bootstrap-icons.css"
                                            "content/icon/fonts/bootstrap-icons.woff"
                                            "content/icon/fonts/bootstrap-icons.woff2"
                                            "content/BouldenDigitalSiteIcon.png"
                                            "content/BouldenDigitalLogo-1024.png"
                                            "content/favicon.ico"
                                            "content/index.html"
)

and when you build your project is built, you end up with a file in the build directory looking a bit like this:

#ifndef MYEXTENSION_EMBED_FILES
#define MYEXTENSION_EMBED_FILES

#include <cstdint>

const std::uint8_t bootstrap_min_css[] = { 0x40, 0x63, 0x68, 0x61, 0x72, 0x73, 0x65, 0x74, 0x20, 0x22, 0x55, 0x54, 0x46, 0x2d, 0x38, 0x22, 0x3b, 0x2f, 0x2a, 0x21, 0x0d, 0x0a, 0x20, 0x2a, 0x20, 0x42, 0x6f, 0x6f, 0x74, 0x73, 0x74, 0x72, 0x61, 0x70, 0x20, 0x20, 0x76, 0x35, 0x2e, 0x33, 0x2e, 0x30, 0x2d, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x33, 0x20, 0x28, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x67, 0x65, 0x74, 0x62, 0x6f, 0x6f, 0x74, 0x73, 0x74, 0x72, 0x61, 0x70, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x29, 0x0d, 0x0a, 0x20, 0x2a, 0x20, 0x43, 0x6f, 0x70, 0x79, 0x72, 0x69, 0x67, 0x68, 0x74, 0x20, 0x32, 0x30, 0x31, 0x31, 0x2d, 0x32, 0x30, 0x32, 0x33, 0x20, 0x54,  };

#endif

which you can then #include into your extension code and convert the array to whatever format you need to deploy/serve it. I am using it to serve image, JavaScript and stylesheet assets to the WebView in my upcoming Advanced Song Selector extension.

2 Likes

If you’re using Juce and only embedding images, I found this worked:

  1. Add a subdirectory in your project to store the image files e.g. ‘resources’.
  2. Add a CMakeLists.txt into this subdirectory that lists the image files and includes an identifier e.g. ‘ImageBinaryData’.
juce_add_binary_data(ImageBinaryData SOURCES
          MyImage.png
)
  1. In the main/top-level CMakeLists.txt, include the ‘resources’ folder:
    add_subdirectory(resources)

  2. Add the identifier to the list of Juce modules you’re already including in your project e.g. ImageBinaryData is added at the end:

target_link_libraries(
  ${PROJECT_NAME}
  PRIVATE gigperformer::sdk::cpp juce::juce_gui_basics juce::juce_gui_extra
          juce::juce_core juce::juce_events juce::juce_graphics ImageBinaryData)
  1. Add the include statement for the binary data to your extension:
    #include "BinaryData.h"

  2. You can now reference the images like this:
    Image myImage = ImageFileFormat::loadFrom(BinaryData::MyImage_png, (size_t) BinaryData::MyImage_pngSize);

1 Like

That sounds like it is a rather similar procedure to the one I found. I do hope that a future release of JUCE might include a WebView like the one in Tracktion/choc as its current one doesn’t do 2-way binding - had it done so, I would have just used JUCE.