diff --git a/CMakeLists.txt b/CMakeLists.txt index 020cfb8c..6bc05307 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -23,6 +23,7 @@ add_subdirectory(libc) add_subdirectory(LibELF) add_subdirectory(LibFont) add_subdirectory(LibGUI) +add_subdirectory(LibImage) add_subdirectory(LibInput) add_subdirectory(userspace) @@ -38,6 +39,7 @@ add_custom_target(headers DEPENDS libelf-headers DEPENDS libfont-headers DEPENDS libgui-headers + DEPENDS libimage-headers DEPENDS libinput-headers ) @@ -49,6 +51,7 @@ add_custom_target(install-sysroot DEPENDS libelf-install DEPENDS libfont-install DEPENDS libgui-install + DEPENDS libimage-install DEPENDS libinput-install ) diff --git a/LibImage/CMakeLists.txt b/LibImage/CMakeLists.txt new file mode 100644 index 00000000..33e1903e --- /dev/null +++ b/LibImage/CMakeLists.txt @@ -0,0 +1,25 @@ +cmake_minimum_required(VERSION 3.26) + +project(libimage CXX) + +set(LIBIMAGE_SOURCES + Image.cpp + Netbpm.cpp +) + +add_custom_target(libimage-headers + COMMAND ${CMAKE_COMMAND} -E copy_directory_if_different ${CMAKE_CURRENT_SOURCE_DIR}/include/ ${BANAN_INCLUDE}/ + DEPENDS sysroot +) + +add_library(libimage ${LIBIMAGE_SOURCES}) +add_dependencies(libimage headers libc-install) +target_link_libraries(libimage PUBLIC libc) + +add_custom_target(libimage-install + COMMAND ${CMAKE_COMMAND} -E copy_if_different ${CMAKE_CURRENT_BINARY_DIR}/libimage.a ${BANAN_LIB}/ + DEPENDS libimage + BYPRODUCTS ${BANAN_LIB}/libimage.a +) + +set(CMAKE_STATIC_LIBRARY_PREFIX "") diff --git a/LibImage/Image.cpp b/LibImage/Image.cpp new file mode 100644 index 00000000..36954ce1 --- /dev/null +++ b/LibImage/Image.cpp @@ -0,0 +1,79 @@ +#include +#include + +#include +#include + +#include +#include +#include + +namespace LibImage +{ + + BAN::ErrorOr> Image::load_from_file(BAN::StringView path) + { + int fd = -1; + + if (path.data()[path.size()] == '\0') + { + fd = open(path.data(), O_RDONLY); + } + else + { + BAN::String path_str; + TRY(path_str.append(path)); + fd = open(path_str.data(), O_RDONLY); + } + + if (fd == -1) + { + fprintf(stddbg, "open: %s\n", strerror(errno)); + return BAN::Error::from_errno(errno); + } + + BAN::ScopeGuard guard_file_close([fd] { close(fd); }); + + struct stat st; + if (fstat(fd, &st) == -1) + { + fprintf(stddbg, "fstat: %s\n", strerror(errno)); + return BAN::Error::from_errno(errno); + } + + if (st.st_size < 2) + { + fprintf(stddbg, "invalid image (too small)\n"); + return BAN::Error::from_errno(EINVAL); + } + + void* addr = mmap(nullptr, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0); + if (addr == MAP_FAILED) + { + fprintf(stddbg, "mmap: %s\n", strerror(errno)); + return BAN::Error::from_errno(errno); + } + + BAN::ScopeGuard guard_munmap([&] { munmap(addr, st.st_size); }); + + auto image_data_span = BAN::ConstByteSpan(reinterpret_cast(addr), st.st_size); + + uint16_t u16_signature = image_data_span.as(); + switch (u16_signature) + { + case 0x3650: + case 0x3550: + case 0x3450: + case 0x3350: + case 0x3250: + case 0x3150: + return TRY(load_netbpm(image_data_span)); + default: + fprintf(stderr, "unrecognized image format\n"); + break; + } + + return BAN::Error::from_errno(ENOTSUP); + } + +} diff --git a/LibImage/Netbpm.cpp b/LibImage/Netbpm.cpp new file mode 100644 index 00000000..ea7bca3e --- /dev/null +++ b/LibImage/Netbpm.cpp @@ -0,0 +1,120 @@ +#include + +#include + +#include +#include +#include + +namespace LibImage +{ + + static BAN::Optional parse_u64(BAN::ConstByteSpan& data) + { + size_t digit_count = 0; + while (digit_count < data.size() && isdigit(data[digit_count])) + digit_count++; + if (digit_count == 0) + return {}; + + uint64_t result = 0; + for (size_t i = 0; i < digit_count; i++) + { + if (BAN::Math::will_multiplication_overflow(result, 10)) + return {}; + result *= 10; + + if (BAN::Math::will_addition_overflow(result, data[i] - '0')) + return {}; + result += data[i] - '0'; + } + + data = data.slice(digit_count); + + return result; + } + + BAN::ErrorOr> load_netbpm(BAN::ConstByteSpan image_data) + { + if (image_data.size() < 11) + { + fprintf(stddbg, "invalid Netbpm image (too small)\n"); + return BAN::Error::from_errno(EINVAL); + } + + if (image_data[0] != 'P') + { + fprintf(stddbg, "not Netbpm image\n"); + return BAN::Error::from_errno(EINVAL); + } + if (image_data[1] != '6') + { + fprintf(stddbg, "unsupported Netbpm image\n"); + return BAN::Error::from_errno(EINVAL); + } + if (image_data[2] != '\n') + { + fprintf(stddbg, "invalid Netbpm image (invalid header)\n"); + return BAN::Error::from_errno(EINVAL); + } + image_data = image_data.slice(3); + + auto opt_width = parse_u64(image_data); + if (!opt_width.has_value() || image_data[0] != ' ') + { + fprintf(stddbg, "invalid Netbpm image (invalid width)\n"); + return BAN::Error::from_errno(EINVAL); + } + image_data = image_data.slice(1); + auto width = opt_width.value(); + + auto opt_height = parse_u64(image_data); + if (!opt_height.has_value() || image_data[0] != '\n') + { + fprintf(stddbg, "invalid Netbpm image (invalid height)\n"); + return BAN::Error::from_errno(EINVAL); + } + image_data = image_data.slice(1); + auto height = opt_height.value(); + + if (BAN::Math::will_multiplication_overflow(width, height) || BAN::Math::will_multiplication_overflow(width * height, 3)) + { + fprintf(stddbg, "invalid Netbpm image (size is over 64 bits overflows)\n"); + return BAN::Error::from_errno(EINVAL); + } + + auto header_end = parse_u64(image_data); + if (!header_end.has_value() || *header_end != 255 || image_data[0] != '\n') + { + fprintf(stddbg, "invalid Netbpm image (invalid header end)\n"); + return BAN::Error::from_errno(EINVAL); + } + image_data = image_data.slice(1); + + if (image_data.size() < width * height * 3) + { + fprintf(stddbg, "invalid Netbpm image (too small file size)\n"); + return BAN::Error::from_errno(EINVAL); + } + + BAN::Vector bitmap; + TRY(bitmap.resize(width * height)); + + // Fill bitmap + for (uint64_t y = 0; y < height; y++) + { + for (uint64_t x = 0; x < width; x++) + { + const uint64_t index = y * width + x; + auto& pixel = bitmap[index]; + pixel.r = image_data[index * 3 + 0]; + pixel.g = image_data[index * 3 + 1]; + pixel.b = image_data[index * 3 + 2]; + pixel.a = 0xFF; + } + } + + return TRY(BAN::UniqPtr::create(width, height, BAN::move(bitmap))); + } + +} diff --git a/LibImage/include/LibImage/Image.h b/LibImage/include/LibImage/Image.h new file mode 100644 index 00000000..0e9c0775 --- /dev/null +++ b/LibImage/include/LibImage/Image.h @@ -0,0 +1,47 @@ +#pragma once + +#include +#include +#include + +namespace LibImage +{ + + class Image + { + public: + struct Color + { + uint8_t r; + uint8_t g; + uint8_t b; + uint8_t a; + }; + + public: + static BAN::ErrorOr> load_from_file(BAN::StringView path); + + Color get_color(uint64_t x, uint64_t y) const { return m_bitmap[y * width() + x]; } + const BAN::Vector bitmap() const { return m_bitmap; } + + uint64_t width() const { return m_width; } + uint64_t height() const { return m_height; } + + private: + Image(uint64_t width, uint64_t height, BAN::Vector&& bitmap) + : m_width(width) + , m_height(height) + , m_bitmap(BAN::move(bitmap)) + { + ASSERT(m_bitmap.size() >= width * height); + } + + private: + const uint64_t m_width; + const uint64_t m_height; + const BAN::Vector m_bitmap; + + friend class BAN::UniqPtr; + }; + +} diff --git a/LibImage/include/LibImage/Netbpm.h b/LibImage/include/LibImage/Netbpm.h new file mode 100644 index 00000000..5e6554fb --- /dev/null +++ b/LibImage/include/LibImage/Netbpm.h @@ -0,0 +1,10 @@ +#include + +#include + +namespace LibImage +{ + + BAN::ErrorOr> load_netbpm(BAN::ConstByteSpan); + +} diff --git a/userspace/image/CMakeLists.txt b/userspace/image/CMakeLists.txt index 49431bfc..41188642 100644 --- a/userspace/image/CMakeLists.txt +++ b/userspace/image/CMakeLists.txt @@ -4,13 +4,11 @@ project(image CXX) set(SOURCES main.cpp - Image.cpp - Netbpm.cpp ) add_executable(image ${SOURCES}) target_compile_options(image PUBLIC -O2 -g) -target_link_libraries(image PUBLIC libc ban) +target_link_libraries(image PUBLIC libc ban libimage) add_custom_target(image-install COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_BINARY_DIR}/image ${BANAN_BIN}/ diff --git a/userspace/image/Image.cpp b/userspace/image/Image.cpp deleted file mode 100644 index 7dcaa138..00000000 --- a/userspace/image/Image.cpp +++ /dev/null @@ -1,144 +0,0 @@ -#include "Image.h" -#include "Netbpm.h" - -#include -#include -#include -#include - -BAN::UniqPtr Image::load_from_file(BAN::StringView path) -{ - int fd = -1; - - if (path.data()[path.size()] == '\0') - { - fd = open(path.data(), O_RDONLY); - } - else - { - char* buffer = (char*)malloc(path.size() + 1); - if (!buffer) - { - perror("malloc"); - return {}; - } - memcpy(buffer, path.data(), path.size()); - buffer[path.size()] = '\0'; - - fd = open(path.data(), O_RDONLY); - - free(buffer); - } - - if (fd == -1) - { - perror("open"); - return {}; - } - - struct stat st; - if (fstat(fd, &st) == -1) - { - perror("fstat"); - close(fd); - return {}; - } - - if (st.st_size < 2) - { - fprintf(stderr, "invalid image (too small)\n"); - close(fd); - return {}; - } - - void* addr = mmap(nullptr, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0); - if (addr == MAP_FAILED) - { - perror("mmap"); - close(fd); - return {}; - } - - BAN::UniqPtr image; - - uint16_t u16_signature = *reinterpret_cast(addr); - switch (u16_signature) - { - case 0x3650: - case 0x3550: - case 0x3450: - case 0x3350: - case 0x3250: - case 0x3150: - if (auto res = load_netbpm(addr, st.st_size); res.is_error()) - fprintf(stderr, "%s\n", strerror(res.error().get_error_code())); - else - image = res.release_value(); - break; - default: - fprintf(stderr, "unrecognized image format\n"); - break; - } - - munmap(addr, st.st_size); - close(fd); - - return image; -} - -bool Image::render_to_framebuffer() -{ - int fd = open("/dev/fb0", O_RDWR); - if (fd == -1) - { - perror("open"); - return false; - } - - framebuffer_info_t fb_info; - if (pread(fd, &fb_info, sizeof(fb_info), -1) == -1) - { - perror("pread"); - close(fd); - return false; - } - - ASSERT(BANAN_FB_BPP == 24 || BANAN_FB_BPP == 32); - - size_t mmap_size = fb_info.height * fb_info.width * BANAN_FB_BPP / 8; - - void* mmap_addr = mmap(nullptr, mmap_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); - if (mmap_addr == MAP_FAILED) - { - perror("mmap"); - close(fd); - return false; - } - - uint8_t* u8_fb = reinterpret_cast(mmap_addr); - - for (uint64_t y = 0; y < BAN::Math::min(height(), fb_info.height); y++) - { - for (uint64_t x = 0; x < BAN::Math::min(width(), fb_info.width); x++) - { - u8_fb[(y * fb_info.width + x) * BANAN_FB_BPP / 8 + 0] = m_bitmap[y * width() + x].r; - u8_fb[(y * fb_info.width + x) * BANAN_FB_BPP / 8 + 1] = m_bitmap[y * width() + x].g; - u8_fb[(y * fb_info.width + x) * BANAN_FB_BPP / 8 + 2] = m_bitmap[y * width() + x].b; - if constexpr(BANAN_FB_BPP == 32) - u8_fb[(y * fb_info.width + x) * BANAN_FB_BPP / 8 + 3] = m_bitmap[y * width() + x].a; - } - } - - if (msync(mmap_addr, mmap_size, MS_SYNC) == -1) - { - perror("msync"); - munmap(mmap_addr, mmap_size); - close(fd); - return false; - } - - munmap(mmap_addr, mmap_size); - close(fd); - - return true; -} diff --git a/userspace/image/Image.h b/userspace/image/Image.h deleted file mode 100644 index 02273b17..00000000 --- a/userspace/image/Image.h +++ /dev/null @@ -1,39 +0,0 @@ -#pragma once - -#include -#include -#include - -class Image -{ -public: - struct Color - { - uint8_t r; - uint8_t g; - uint8_t b; - uint8_t a; - }; - -public: - static BAN::UniqPtr load_from_file(BAN::StringView path); - - uint64_t width() const { return m_width; } - uint64_t height() const { return m_height; } - - bool render_to_framebuffer(); - -private: - Image(uint64_t width, uint64_t height, BAN::Vector&& bitmap) - : m_width(width) - , m_height(height) - , m_bitmap(BAN::move(bitmap)) - { } - -private: - const uint64_t m_width; - const uint64_t m_height; - const BAN::Vector m_bitmap; - - friend class BAN::UniqPtr; -}; diff --git a/userspace/image/Netbpm.cpp b/userspace/image/Netbpm.cpp deleted file mode 100644 index 4d89ed7e..00000000 --- a/userspace/image/Netbpm.cpp +++ /dev/null @@ -1,109 +0,0 @@ -#include "Netbpm.h" - -#include - -#include -#include -#include - -BAN::Optional parse_u64(const uint8_t*& data, size_t data_size) -{ - uint64_t result = 0; - - // max supported size 10^20 - 1 - for (size_t i = 0; i < 19; i++) - { - if (i >= data_size) - { - if (isdigit(*data)) - return {}; - return result; - } - - if (!isdigit(*data)) - return result; - - result = (result * 10) + (*data - '0'); - data++; - } - - return {}; -} - -BAN::ErrorOr> load_netbpm(const void* mmap_addr, size_t size) -{ - if (size < 11) - { - fprintf(stderr, "invalid Netbpm image (too small)\n"); - return BAN::Error::from_errno(EINVAL); - } - - const uint8_t* u8_ptr = reinterpret_cast(mmap_addr); - - if (u8_ptr[0] != 'P') - { - fprintf(stderr, "not Netbpm image\n"); - return BAN::Error::from_errno(EINVAL); - } - if (u8_ptr[1] != '6') - { - fprintf(stderr, "unsupported Netbpm image\n"); - return BAN::Error::from_errno(EINVAL); - } - if (u8_ptr[2] != '\n') - { - fprintf(stderr, "invalid Netbpm image (invalid header)\n"); - return BAN::Error::from_errno(EINVAL); - } - u8_ptr += 3; - - auto width = parse_u64(u8_ptr, size - (u8_ptr - reinterpret_cast(mmap_addr))); - if (!width.has_value() || *u8_ptr != ' ') - { - fprintf(stderr, "invalid Netbpm image (invalid width)\n"); - return BAN::Error::from_errno(EINVAL); - } - u8_ptr++; - - auto height = parse_u64(u8_ptr, size - (u8_ptr - reinterpret_cast(mmap_addr))); - if (!height.has_value() || *u8_ptr != '\n') - { - fprintf(stderr, "invalid Netbpm image (invalid height)\n"); - return BAN::Error::from_errno(EINVAL); - } - u8_ptr++; - - auto header_end = parse_u64(u8_ptr, size - (u8_ptr - reinterpret_cast(mmap_addr))); - if (!header_end.has_value() || *header_end != 255 || *u8_ptr != '\n') - { - fprintf(stderr, "invalid Netbpm image (invalid header end)\n"); - return BAN::Error::from_errno(EINVAL); - } - u8_ptr++; - - if (size - (u8_ptr - reinterpret_cast(mmap_addr)) < *width * *height * 3) - { - fprintf(stderr, "invalid Netbpm image (too small file size)\n"); - return BAN::Error::from_errno(EINVAL); - } - - printf("Netbpm image %" PRIu64 "x%" PRIu64 "\n", *width, *height); - - BAN::Vector bitmap; - TRY(bitmap.resize(*width * *height)); - - // Fill bitmap - for (uint64_t y = 0; y < *height; y++) - { - for (uint64_t x = 0; x < *width; x++) - { - auto& pixel = bitmap[y * *width + x]; - pixel.r = *u8_ptr++; - pixel.g = *u8_ptr++; - pixel.b = *u8_ptr++; - pixel.a = 0xFF; - } - } - - return TRY(BAN::UniqPtr::create(*width, *height, BAN::move(bitmap))); -} diff --git a/userspace/image/Netbpm.h b/userspace/image/Netbpm.h deleted file mode 100644 index 203a7582..00000000 --- a/userspace/image/Netbpm.h +++ /dev/null @@ -1,3 +0,0 @@ -#include "Image.h" - -BAN::ErrorOr> load_netbpm(const void* mmap_addr, size_t size); diff --git a/userspace/image/main.cpp b/userspace/image/main.cpp index 9be2586d..98960297 100644 --- a/userspace/image/main.cpp +++ b/userspace/image/main.cpp @@ -1,8 +1,64 @@ -#include "Image.h" +#include +#include #include +#include +#include +#include #include +void render_to_framebuffer(const LibImage::Image& image) +{ + int fd = open("/dev/fb0", O_RDWR); + if (fd == -1) + { + perror("open"); + exit(1); + } + + framebuffer_info_t fb_info; + if (pread(fd, &fb_info, sizeof(fb_info), -1) == -1) + { + perror("pread"); + exit(1); + } + + ASSERT(BANAN_FB_BPP == 24 || BANAN_FB_BPP == 32); + + size_t mmap_size = fb_info.height * fb_info.width * BANAN_FB_BPP / 8; + + void* mmap_addr = mmap(nullptr, mmap_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + if (mmap_addr == MAP_FAILED) + { + perror("mmap"); + exit(1); + } + + uint8_t* u8_fb = reinterpret_cast(mmap_addr); + + const auto& bitmap = image.bitmap(); + for (uint64_t y = 0; y < BAN::Math::min(image.height(), fb_info.height); y++) + { + for (uint64_t x = 0; x < BAN::Math::min(image.width(), fb_info.width); x++) + { + u8_fb[(y * fb_info.width + x) * BANAN_FB_BPP / 8 + 0] = bitmap[y * image.width() + x].r; + u8_fb[(y * fb_info.width + x) * BANAN_FB_BPP / 8 + 1] = bitmap[y * image.width() + x].g; + u8_fb[(y * fb_info.width + x) * BANAN_FB_BPP / 8 + 2] = bitmap[y * image.width() + x].b; + if constexpr(BANAN_FB_BPP == 32) + u8_fb[(y * fb_info.width + x) * BANAN_FB_BPP / 8 + 3] = bitmap[y * image.width() + x].a; + } + } + + if (msync(mmap_addr, mmap_size, MS_SYNC) == -1) + { + perror("msync"); + exit(1); + } + + munmap(mmap_addr, mmap_size); + close(fd); +} + int usage(char* arg0, int ret) { FILE* out = (ret == 0) ? stdout : stderr; @@ -15,12 +71,13 @@ int main(int argc, char** argv) if (argc != 2) return usage(argv[0], 1); - auto image = Image::load_from_file(argv[1]); - if (!image) + auto image_or_error = LibImage::Image::load_from_file(argv[1]); + if (image_or_error.is_error()) return 1; + auto image = image_or_error.release_value(); + ASSERT(image); - if (!image->render_to_framebuffer()) - return 1; + render_to_framebuffer(*image); for (;;) sleep(1);