pax_global_header00006660000000000000000000000064151071432250014512gustar00rootroot0000000000000052 comment=5695a711dd1cff1f01fa6542f3fe6a15de082c63 sammycage-plutovg-5695a71/000077500000000000000000000000001510714322500154415ustar00rootroot00000000000000sammycage-plutovg-5695a71/.github/000077500000000000000000000000001510714322500170015ustar00rootroot00000000000000sammycage-plutovg-5695a71/.github/FUNDING.yml000066400000000000000000000001011510714322500206060ustar00rootroot00000000000000# These are supported funding model platforms github: sammycage sammycage-plutovg-5695a71/.github/workflows/000077500000000000000000000000001510714322500210365ustar00rootroot00000000000000sammycage-plutovg-5695a71/.github/workflows/main.yml000066400000000000000000000022461510714322500225110ustar00rootroot00000000000000name: Build on: [push, pull_request] jobs: linux: runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v4 - name: Build with meson run: | pip install meson pip install ninja meson setup build meson compile -C build - name: Build with cmake run: | cmake . cmake --build . macos: runs-on: macos-latest steps: - name: Checkout code uses: actions/checkout@v4 - name: Build with meson run: | pip install meson pip install ninja meson setup build meson compile -C build - name: Build with cmake run: | cmake . cmake --build . windows: runs-on: windows-latest steps: - name: Checkout code uses: actions/checkout@v4 - name: Build with meson run: | pip install meson pip install ninja meson setup build meson compile -C build - name: Build with cmake run: | cmake . cmake --build . sammycage-plutovg-5695a71/.gitignore000066400000000000000000000000161510714322500174260ustar00rootroot00000000000000/build *.user sammycage-plutovg-5695a71/CMakeLists.txt000066400000000000000000000111411510714322500201770ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.15) set(PLUTOVG_VERSION_MAJOR 1) set(PLUTOVG_VERSION_MINOR 3) set(PLUTOVG_VERSION_MICRO 2) project(plutovg LANGUAGES C VERSION ${PLUTOVG_VERSION_MAJOR}.${PLUTOVG_VERSION_MINOR}.${PLUTOVG_VERSION_MICRO}) set(plutovg_sources source/plutovg-blend.c source/plutovg-canvas.c source/plutovg-font.c source/plutovg-matrix.c source/plutovg-paint.c source/plutovg-path.c source/plutovg-rasterize.c source/plutovg-surface.c source/plutovg-ft-math.c source/plutovg-ft-raster.c source/plutovg-ft-stroker.c ) set(plutovg_headers include/plutovg.h source/plutovg-private.h source/plutovg-utils.h source/plutovg-ft-math.h source/plutovg-ft-raster.h source/plutovg-ft-stroker.h source/plutovg-ft-types.h source/plutovg-stb-image-write.h source/plutovg-stb-image.h source/plutovg-stb-truetype.h ) add_library(plutovg ${plutovg_sources} ${plutovg_headers}) add_library(plutovg::plutovg ALIAS plutovg) include(GNUInstallDirs) include(CMakePackageConfigHelpers) include(CheckIncludeFile) set_target_properties(plutovg PROPERTIES SOVERSION ${PLUTOVG_VERSION_MAJOR} C_VISIBILITY_PRESET hidden C_STANDARD_REQUIRED ON C_STANDARD 11 ) target_include_directories(plutovg PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include ${CMAKE_CURRENT_SOURCE_DIR}/source ) target_include_directories(plutovg PUBLIC $ $ ) find_library(MATH_LIBRARY m) if(MATH_LIBRARY) target_link_libraries(plutovg PRIVATE m) endif() check_include_file("threads.h" HAVE_THREADS_H) if(HAVE_THREADS_H) target_compile_definitions(plutovg PRIVATE HAVE_THREADS_H) endif() find_package(Threads) if(Threads_FOUND) target_link_libraries(plutovg PRIVATE Threads::Threads) endif() find_library(STDTHREADS_LIBRARY stdthreads) if(STDTHREADS_LIBRARY) target_link_libraries(plutovg PRIVATE stdthreads) endif() target_compile_definitions(plutovg PRIVATE PLUTOVG_BUILD) if(NOT BUILD_SHARED_LIBS) target_compile_definitions(plutovg PUBLIC PLUTOVG_BUILD_STATIC) endif() option(PLUTOVG_DISABLE_FONT_FACE_CACHE_LOAD "Disable loading font face cache from files and directories" OFF) if(PLUTOVG_DISABLE_FONT_FACE_CACHE_LOAD) target_compile_definitions(plutovg PRIVATE PLUTOVG_DISABLE_FONT_FACE_CACHE_LOAD) endif() configure_package_config_file( "${CMAKE_CURRENT_SOURCE_DIR}/cmake/plutovgConfig.cmake.in" "${CMAKE_CURRENT_BINARY_DIR}/plutovgConfig.cmake" INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/plutovg ) write_basic_package_version_file(plutovgConfigVersion.cmake VERSION ${PROJECT_VERSION} COMPATIBILITY SameMajorVersion ) install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/include/plutovg.h DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/plutovg ) install(TARGETS plutovg EXPORT plutovgTargets LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} ) install(EXPORT plutovgTargets FILE plutovgTargets.cmake DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/plutovg NAMESPACE plutovg:: ) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/plutovgConfig.cmake ${CMAKE_CURRENT_BINARY_DIR}/plutovgConfigVersion.cmake DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/plutovg ) export(EXPORT plutovgTargets FILE ${CMAKE_CURRENT_BINARY_DIR}/plutovgTargets.cmake NAMESPACE plutovg:: ) file(RELATIVE_PATH plutovg_pc_prefix_relative "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}/pkgconfig" "${CMAKE_INSTALL_PREFIX}" ) set(plutovg_pc_cflags "") set(plutovg_pc_libs_private "") if(MATH_LIBRARY) string(APPEND plutovg_pc_libs_private " -lm") endif() if(Threads_FOUND AND CMAKE_THREAD_LIBS_INIT) string(APPEND plutovg_pc_libs_private " ${CMAKE_THREAD_LIBS_INIT}") endif() if(NOT BUILD_SHARED_LIBS) string(APPEND plutovg_pc_cflags " -DPLUTOVG_BUILD_STATIC") endif() string(CONFIGURE [[ prefix=${pcfiledir}/@plutovg_pc_prefix_relative@ includedir=${prefix}/@CMAKE_INSTALL_INCLUDEDIR@ libdir=${prefix}/@CMAKE_INSTALL_LIBDIR@ Name: PlutoVG Description: Tiny 2D vector graphics library in C Version: @PROJECT_VERSION@ Cflags: -I${includedir}/plutovg@plutovg_pc_cflags@ Libs: -L${libdir} -lplutovg Libs.private:@plutovg_pc_libs_private@ ]] plutovg_pc @ONLY) file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/plutovg.pc" "${plutovg_pc}") install(FILES "${CMAKE_CURRENT_BINARY_DIR}/plutovg.pc" DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig" ) option(PLUTOVG_BUILD_EXAMPLES "Build examples" ON) if(PLUTOVG_BUILD_EXAMPLES) add_subdirectory(examples) endif() sammycage-plutovg-5695a71/LICENSE000066400000000000000000000021201510714322500164410ustar00rootroot00000000000000MIT License Copyright (c) 2020-2025 Samuel Ugochukwu Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. sammycage-plutovg-5695a71/README.md000066400000000000000000000060161510714322500167230ustar00rootroot00000000000000[![Actions](https://github.com/sammycage/plutovg/actions/workflows/main.yml/badge.svg)](https://github.com/sammycage/plutovg/actions) [![License](https://img.shields.io/badge/License-MIT-blue.svg)](https://github.com/sammycage/plutovg/blob/main/LICENSE) [![Releases](https://img.shields.io/github/v/release/sammycage/plutovg)](https://github.com/sammycage/plutovg/releases) [![CodeFactor](https://www.codefactor.io/repository/github/sammycage/plutovg/badge)](https://www.codefactor.io/repository/github/sammycage/plutovg) # PlutoVG PlutoVG is a standalone 2D vector graphics library in C. ## Features - Path Filling, Stroking and Dashing - Solid, Gradient and Texture Paints - Fonts and Texts - Clipping and Compositing - Transformations - Images ## Example ```c #include int main(void) { const int width = 150; const int height = 150; const float center_x = width / 2.f; const float center_y = height / 2.f; const float face_radius = 70; const float mouth_radius = 50; const float eye_radius = 10; const float eye_offset_x = 25; const float eye_offset_y = 20; const float eye_x = center_x - eye_offset_x; const float eye_y = center_y - eye_offset_y; plutovg_surface_t* surface = plutovg_surface_create(width, height); plutovg_canvas_t* canvas = plutovg_canvas_create(surface); plutovg_canvas_save(canvas); plutovg_canvas_arc(canvas, center_x, center_y, face_radius, 0, PLUTOVG_TWO_PI, 0); plutovg_canvas_set_rgb(canvas, 1, 1, 0); plutovg_canvas_fill_preserve(canvas); plutovg_canvas_set_rgb(canvas, 0, 0, 0); plutovg_canvas_set_line_width(canvas, 5); plutovg_canvas_stroke(canvas); plutovg_canvas_restore(canvas); plutovg_canvas_save(canvas); plutovg_canvas_arc(canvas, eye_x, eye_y, eye_radius, 0, PLUTOVG_TWO_PI, 0); plutovg_canvas_arc(canvas, center_x + eye_offset_x, eye_y, eye_radius, 0, PLUTOVG_TWO_PI, 0); plutovg_canvas_set_rgb(canvas, 0, 0, 0); plutovg_canvas_fill(canvas); plutovg_canvas_restore(canvas); plutovg_canvas_save(canvas); plutovg_canvas_arc(canvas, center_x, center_y, mouth_radius, 0, PLUTOVG_PI, 0); plutovg_canvas_set_rgb(canvas, 0, 0, 0); plutovg_canvas_set_line_width(canvas, 5); plutovg_canvas_stroke(canvas); plutovg_canvas_restore(canvas); plutovg_surface_write_to_png(surface, "smiley.png"); plutovg_canvas_destroy(canvas); plutovg_surface_destroy(surface); return 0; } ``` output: ![smiley.png](smiley.png) ## Installation Follow the steps below to install PlutoVG using either [Meson](https://mesonbuild.com/) or [CMake](https://cmake.org/). ### Using Meson ```bash git clone https://github.com/sammycage/plutovg.git cd plutovg meson setup build meson compile -C build meson install -C build ``` ### Using CMake ```bash git clone https://github.com/sammycage/plutovg.git cd plutovg cmake -B build . cmake --build build cmake --install build ``` ## Projects using PlutoVG - [LunaSVG](https://github.com/sammycage/lunasvg) - [PlutoSVG](https://github.com/sammycage/plutosvg) sammycage-plutovg-5695a71/cmake/000077500000000000000000000000001510714322500165215ustar00rootroot00000000000000sammycage-plutovg-5695a71/cmake/plutovgConfig.cmake.in000066400000000000000000000002061510714322500227540ustar00rootroot00000000000000@PACKAGE_INIT@ include(CMakeFindDependencyMacro) find_dependency(Threads) include("${CMAKE_CURRENT_LIST_DIR}/plutovgTargets.cmake") sammycage-plutovg-5695a71/examples/000077500000000000000000000000001510714322500172575ustar00rootroot00000000000000sammycage-plutovg-5695a71/examples/CMakeLists.txt000066400000000000000000000001061510714322500220140ustar00rootroot00000000000000add_executable(smiley smiley.c) target_link_libraries(smiley plutovg) sammycage-plutovg-5695a71/examples/meson.build000066400000000000000000000000741510714322500214220ustar00rootroot00000000000000executable('smiley', 'smiley.c', dependencies: plutovg_dep) sammycage-plutovg-5695a71/examples/smiley.c000066400000000000000000000032521510714322500207270ustar00rootroot00000000000000#include int main(void) { const int width = 150; const int height = 150; const float center_x = width / 2.f; const float center_y = height / 2.f; const float face_radius = 70; const float mouth_radius = 50; const float eye_radius = 10; const float eye_offset_x = 25; const float eye_offset_y = 20; const float eye_x = center_x - eye_offset_x; const float eye_y = center_y - eye_offset_y; plutovg_surface_t* surface = plutovg_surface_create(width, height); plutovg_canvas_t* canvas = plutovg_canvas_create(surface); plutovg_canvas_save(canvas); plutovg_canvas_arc(canvas, center_x, center_y, face_radius, 0, PLUTOVG_TWO_PI, 0); plutovg_canvas_set_rgb(canvas, 1, 1, 0); plutovg_canvas_fill_preserve(canvas); plutovg_canvas_set_rgb(canvas, 0, 0, 0); plutovg_canvas_set_line_width(canvas, 5); plutovg_canvas_stroke(canvas); plutovg_canvas_restore(canvas); plutovg_canvas_save(canvas); plutovg_canvas_arc(canvas, eye_x, eye_y, eye_radius, 0, PLUTOVG_TWO_PI, 0); plutovg_canvas_arc(canvas, center_x + eye_offset_x, eye_y, eye_radius, 0, PLUTOVG_TWO_PI, 0); plutovg_canvas_set_rgb(canvas, 0, 0, 0); plutovg_canvas_fill(canvas); plutovg_canvas_restore(canvas); plutovg_canvas_save(canvas); plutovg_canvas_arc(canvas, center_x, center_y, mouth_radius, 0, PLUTOVG_PI, 0); plutovg_canvas_set_rgb(canvas, 0, 0, 0); plutovg_canvas_set_line_width(canvas, 5); plutovg_canvas_stroke(canvas); plutovg_canvas_restore(canvas); plutovg_surface_write_to_png(surface, "smiley.png"); plutovg_canvas_destroy(canvas); plutovg_surface_destroy(surface); return 0; } sammycage-plutovg-5695a71/include/000077500000000000000000000000001510714322500170645ustar00rootroot00000000000000sammycage-plutovg-5695a71/include/plutovg.h000066400000000000000000003056761510714322500207560ustar00rootroot00000000000000/* * Copyright (c) 2020-2025 Samuel Ugochukwu * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ #ifndef PLUTOVG_H #define PLUTOVG_H #include #ifdef __cplusplus extern "C" { #endif #if defined(PLUTOVG_BUILD_STATIC) #define PLUTOVG_EXPORT #define PLUTOVG_IMPORT #elif (defined(_WIN32) || defined(__CYGWIN__)) #define PLUTOVG_EXPORT __declspec(dllexport) #define PLUTOVG_IMPORT __declspec(dllimport) #elif defined(__GNUC__) && (__GNUC__ >= 4) #define PLUTOVG_EXPORT __attribute__((__visibility__("default"))) #define PLUTOVG_IMPORT #else #define PLUTOVG_EXPORT #define PLUTOVG_IMPORT #endif #ifdef PLUTOVG_BUILD #define PLUTOVG_API PLUTOVG_EXPORT #else #define PLUTOVG_API PLUTOVG_IMPORT #endif #define PLUTOVG_VERSION_MAJOR 1 #define PLUTOVG_VERSION_MINOR 3 #define PLUTOVG_VERSION_MICRO 2 #define PLUTOVG_VERSION_ENCODE(major, minor, micro) (((major) * 10000) + ((minor) * 100) + ((micro) * 1)) #define PLUTOVG_VERSION PLUTOVG_VERSION_ENCODE(PLUTOVG_VERSION_MAJOR, PLUTOVG_VERSION_MINOR, PLUTOVG_VERSION_MICRO) #define PLUTOVG_VERSION_XSTRINGIZE(major, minor, micro) #major"."#minor"."#micro #define PLUTOVG_VERSION_STRINGIZE(major, minor, micro) PLUTOVG_VERSION_XSTRINGIZE(major, minor, micro) #define PLUTOVG_VERSION_STRING PLUTOVG_VERSION_STRINGIZE(PLUTOVG_VERSION_MAJOR, PLUTOVG_VERSION_MINOR, PLUTOVG_VERSION_MICRO) /** * @brief Gets the version of the plutovg library. * @return An integer representing the version of the plutovg library. */ PLUTOVG_API int plutovg_version(void); /** * @brief Gets the version of the plutovg library as a string. * @return A string representing the version of the plutovg library. */ PLUTOVG_API const char* plutovg_version_string(void); /** * @brief A function pointer type for a cleanup callback. * @param closure A pointer to the resource to be cleaned up. */ typedef void (*plutovg_destroy_func_t)(void* closure); /** * @brief A function pointer type for a write callback. * @param closure A pointer to user-defined data or context. * @param data A pointer to the data to be written. * @param size The size of the data in bytes. */ typedef void (*plutovg_write_func_t)(void* closure, void* data, int size); #define PLUTOVG_PI 3.14159265358979323846f #define PLUTOVG_TWO_PI 6.28318530717958647693f #define PLUTOVG_HALF_PI 1.57079632679489661923f #define PLUTOVG_SQRT2 1.41421356237309504880f #define PLUTOVG_KAPPA 0.55228474983079339840f #define PLUTOVG_DEG2RAD(x) ((x) * (PLUTOVG_PI / 180.0f)) #define PLUTOVG_RAD2DEG(x) ((x) * (180.0f / PLUTOVG_PI)) /** * @brief A structure representing a point in 2D space. */ typedef struct plutovg_point { float x; ///< The x-coordinate of the point. float y; ///< The y-coordinate of the point. } plutovg_point_t; #define PLUTOVG_MAKE_POINT(x, y) ((plutovg_point_t){x, y}) #define PLUTOVG_EMPTY_POINT PLUTOVG_MAKE_POINT(0, 0) /** * @brief A structure representing a rectangle in 2D space. */ typedef struct plutovg_rect { float x; ///< The x-coordinate of the top-left corner of the rectangle. float y; ///< The y-coordinate of the top-left corner of the rectangle. float w; ///< The width of the rectangle. float h; ///< The height of the rectangle. } plutovg_rect_t; #define PLUTOVG_MAKE_RECT(x, y, w, h) ((plutovg_rect_t){x, y, w, h}) #define PLUTOVG_EMPTY_RECT PLUTOVG_MAKE_RECT(0, 0, 0, 0) /** * @brief A structure representing a 2D transformation matrix. */ typedef struct plutovg_matrix { float a; ///< The horizontal scaling factor. float b; ///< The vertical shearing factor. float c; ///< The horizontal shearing factor. float d; ///< The vertical scaling factor. float e; ///< The horizontal translation offset. float f; ///< The vertical translation offset. } plutovg_matrix_t; #define PLUTOVG_MAKE_MATRIX(a, b, c, d, e, f) ((plutovg_matrix_t){a, b, c, d, e, f}) #define PLUTOVG_MAKE_SCALE(x, y) PLUTOVG_MAKE_MATRIX(x, 0, 0, y, 0, 0) #define PLUTOVG_MAKE_TRANSLATE(x, y) PLUTOVG_MAKE_MATRIX(1, 0, 0, 1, x, y) #define PLUTOVG_IDENTITY_MATRIX PLUTOVG_MAKE_MATRIX(1, 0, 0, 1, 0, 0) /** * @brief Initializes a 2D transformation matrix. * @param matrix A pointer to the `plutovg_matrix_t` object to be initialized. * @param a The horizontal scaling factor. * @param b The vertical shearing factor. * @param c The horizontal shearing factor. * @param d The vertical scaling factor. * @param e The horizontal translation offset. * @param f The vertical translation offset. */ PLUTOVG_API void plutovg_matrix_init(plutovg_matrix_t* matrix, float a, float b, float c, float d, float e, float f); /** * @brief Initializes a 2D transformation matrix to the identity matrix. * @param matrix A pointer to the `plutovg_matrix_t` object to be initialized. */ PLUTOVG_API void plutovg_matrix_init_identity(plutovg_matrix_t* matrix); /** * @brief Initializes a 2D transformation matrix for translation. * @param matrix A pointer to the `plutovg_matrix_t` object to be initialized. * @param tx The translation offset in the x-direction. * @param ty The translation offset in the y-direction. */ PLUTOVG_API void plutovg_matrix_init_translate(plutovg_matrix_t* matrix, float tx, float ty); /** * @brief Initializes a 2D transformation matrix for scaling. * @param matrix A pointer to the `plutovg_matrix_t` object to be initialized. * @param sx The scaling factor in the x-direction. * @param sy The scaling factor in the y-direction. */ PLUTOVG_API void plutovg_matrix_init_scale(plutovg_matrix_t* matrix, float sx, float sy); /** * @brief Initializes a 2D transformation matrix for rotation. * @param matrix A pointer to the `plutovg_matrix_t` object to be initialized. * @param angle The rotation angle in radians. */ PLUTOVG_API void plutovg_matrix_init_rotate(plutovg_matrix_t* matrix, float angle); /** * @brief Initializes a 2D transformation matrix for shearing. * @param matrix A pointer to the `plutovg_matrix_t` object to be initialized. * @param shx The shearing factor in the x-direction. * @param shy The shearing factor in the y-direction. */ PLUTOVG_API void plutovg_matrix_init_shear(plutovg_matrix_t* matrix, float shx, float shy); /** * @brief Adds a translation with offsets `tx` and `ty` to the matrix. * @param matrix A pointer to the `plutovg_matrix_t` object to be modified. * @param tx The translation offset in the x-direction. * @param ty The translation offset in the y-direction. */ PLUTOVG_API void plutovg_matrix_translate(plutovg_matrix_t* matrix, float tx, float ty); /** * @brief Scales the matrix by factors `sx` and `sy` * @param matrix A pointer to the `plutovg_matrix_t` object to be modified. * @param sx The scaling factor in the x-direction. * @param sy The scaling factor in the y-direction. */ PLUTOVG_API void plutovg_matrix_scale(plutovg_matrix_t* matrix, float sx, float sy); /** * @brief Rotates the matrix by the specified angle (in radians). * @param matrix A pointer to the `plutovg_matrix_t` object to be modified. * @param angle The rotation angle in radians. */ PLUTOVG_API void plutovg_matrix_rotate(plutovg_matrix_t* matrix, float angle); /** * @brief Shears the matrix by factors `shx` and `shy`. * @param matrix A pointer to the `plutovg_matrix_t` object to be modified. * @param shx The shearing factor in the x-direction. * @param shy The shearing factor in the y-direction. */ PLUTOVG_API void plutovg_matrix_shear(plutovg_matrix_t* matrix, float shx, float shy); /** * @brief Multiplies `left` and `right` matrices and stores the result in `matrix`. * @note `matrix` can be identical to either `left` or `right`. * @param matrix A pointer to the `plutovg_matrix_t` object to store the result. * @param left A pointer to the first `plutovg_matrix_t` matrix. * @param right A pointer to the second `plutovg_matrix_t` matrix. */ PLUTOVG_API void plutovg_matrix_multiply(plutovg_matrix_t* matrix, const plutovg_matrix_t* left, const plutovg_matrix_t* right); /** * @brief Calculates the inverse of `matrix` and stores it in `inverse`. * * If `inverse` is `NULL`, the function only checks if the matrix is invertible. * * @note `matrix` and `inverse` can be identical. * @param matrix A pointer to the `plutovg_matrix_t` object to invert. * @param inverse A pointer to the `plutovg_matrix_t` object to store the result, or `NULL`. * @return `true` if the matrix is invertible; `false` otherwise. */ PLUTOVG_API bool plutovg_matrix_invert(const plutovg_matrix_t* matrix, plutovg_matrix_t* inverse); /** * @brief Transforms the point `(x, y)` using `matrix` and stores the result in `(xx, yy)`. * @param matrix A pointer to a `plutovg_matrix_t` object. * @param x The x-coordinate of the point to transform. * @param y The y-coordinate of the point to transform. * @param xx A pointer to store the transformed x-coordinate. * @param yy A pointer to store the transformed y-coordinate. */ PLUTOVG_API void plutovg_matrix_map(const plutovg_matrix_t* matrix, float x, float y, float* xx, float* yy); /** * @brief Transforms the `src` point using `matrix` and stores the result in `dst`. * @note `src` and `dst` can be identical. * @param matrix A pointer to a `plutovg_matrix_t` object. * @param src A pointer to the `plutovg_point_t` object to transform. * @param dst A pointer to the `plutovg_point_t` to store the transformed point. */ PLUTOVG_API void plutovg_matrix_map_point(const plutovg_matrix_t* matrix, const plutovg_point_t* src, plutovg_point_t* dst); /** * @brief Transforms an array of `src` points using `matrix` and stores the results in `dst`. * @note `src` and `dst` can be identical. * @param matrix A pointer to a `plutovg_matrix_t` object. * @param src A pointer to the array of `plutovg_point_t` objects to transform. * @param dst A pointer to the array of `plutovg_point_t` to store the transformed points. * @param count The number of points to transform. */ PLUTOVG_API void plutovg_matrix_map_points(const plutovg_matrix_t* matrix, const plutovg_point_t* src, plutovg_point_t* dst, int count); /** * @brief Transforms the `src` rectangle using `matrix` and stores the result in `dst`. * @note `src` and `dst` can be identical. * @param matrix A pointer to a `plutovg_matrix_t` object. * @param src A pointer to the `plutovg_rect_t` object to transform. * @param dst A pointer to the `plutovg_rect_t` to store the transformed rectangle. */ PLUTOVG_API void plutovg_matrix_map_rect(const plutovg_matrix_t* matrix, const plutovg_rect_t* src, plutovg_rect_t* dst); /** * @brief Parses an SVG transform string into a matrix. * * @param matrix A pointer to a `plutovg_matrix_t` object to store the result. * @param data Input SVG transform string. * @param length Length of the string, or `-1` if null-terminated. * * @return `true` on success, `false` on failure. */ PLUTOVG_API bool plutovg_matrix_parse(plutovg_matrix_t* matrix, const char* data, int length); /** * @brief Represents a 2D path for drawing operations. */ typedef struct plutovg_path plutovg_path_t; /** * @brief Enumeration defining path commands. */ typedef enum plutovg_path_command { PLUTOVG_PATH_COMMAND_MOVE_TO, ///< Moves the current point to a new position. PLUTOVG_PATH_COMMAND_LINE_TO, ///< Draws a straight line to a new point. PLUTOVG_PATH_COMMAND_CUBIC_TO, ///< Draws a cubic Bézier curve to a new point. PLUTOVG_PATH_COMMAND_CLOSE ///< Closes the current path by drawing a line to the starting point. } plutovg_path_command_t; /** * @brief Union representing a path element. * * A path element can be a command with a length or a coordinate point. * Each command type in the path element array is followed by a specific number of points: * - `PLUTOVG_PATH_COMMAND_MOVE_TO`: 1 point * - `PLUTOVG_PATH_COMMAND_LINE_TO`: 1 point * - `PLUTOVG_PATH_COMMAND_CUBIC_TO`: 3 points * - `PLUTOVG_PATH_COMMAND_CLOSE`: 1 point * * @example * const plutovg_path_element_t* elements; * int count = plutovg_path_get_elements(path, &elements); * for(int i = 0; i < count; i += elements[i].header.length) { * plutovg_path_command_t command = elements[i].header.command; * switch(command) { * case PLUTOVG_PATH_COMMAND_MOVE_TO: * printf("MoveTo: %g %g\n", elements[i + 1].point.x, elements[i + 1].point.y); * break; * case PLUTOVG_PATH_COMMAND_LINE_TO: * printf("LineTo: %g %g\n", elements[i + 1].point.x, elements[i + 1].point.y); * break; * case PLUTOVG_PATH_COMMAND_CUBIC_TO: * printf("CubicTo: %g %g %g %g %g %g\n", * elements[i + 1].point.x, elements[i + 1].point.y, * elements[i + 2].point.x, elements[i + 2].point.y, * elements[i + 3].point.x, elements[i + 3].point.y); * break; * case PLUTOVG_PATH_COMMAND_CLOSE: * printf("Close: %g %g\n", elements[i + 1].point.x, elements[i + 1].point.y); * break; * } * } */ typedef union plutovg_path_element { struct { plutovg_path_command_t command; ///< The path command. int length; ///< Number of elements including the header. } header; ///< Header for path commands. plutovg_point_t point; ///< A coordinate point in the path. } plutovg_path_element_t; /** * @brief Iterator for traversing path elements in a path. */ typedef struct plutovg_path_iterator { const plutovg_path_element_t* elements; ///< Pointer to the array of path elements. int size; ///< Total number of elements in the array. int index; ///< Current position in the array. } plutovg_path_iterator_t; /** * @brief Initializes a path iterator for a given path. * * @param it The path iterator to initialize. * @param path The path to iterate over. */ PLUTOVG_API void plutovg_path_iterator_init(plutovg_path_iterator_t* it, const plutovg_path_t* path); /** * @brief Checks if there are more elements to iterate over. * * @param it The path iterator. * @return `true` if there are more elements; otherwise, `false`. */ PLUTOVG_API bool plutovg_path_iterator_has_next(const plutovg_path_iterator_t* it); /** * @brief Retrieves the current command and its associated points, then advances the iterator. * * @param it The path iterator. * @param points An array to store the points for the current command. * @return The path command for the current element. */ PLUTOVG_API plutovg_path_command_t plutovg_path_iterator_next(plutovg_path_iterator_t* it, plutovg_point_t points[3]); /** * @brief Creates a new path object. * * @return A pointer to the newly created path object. */ PLUTOVG_API plutovg_path_t* plutovg_path_create(void); /** * @brief Increases the reference count of a path object. * * @param path A pointer to a `plutovg_path_t` object. * @return A pointer to the same `plutovg_path_t` object. */ PLUTOVG_API plutovg_path_t* plutovg_path_reference(plutovg_path_t* path); /** * @brief Decreases the reference count of a path object. * * This function decrements the reference count of the given path object. If * the reference count reaches zero, the path object is destroyed and its * resources are freed. * * @param path A pointer to the `plutovg_path_t` object. */ PLUTOVG_API void plutovg_path_destroy(plutovg_path_t* path); /** * @brief Retrieves the reference count of a path object. * * @param path A pointer to a `plutovg_path_t` object. * @return The current reference count of the path object. */ PLUTOVG_API int plutovg_path_get_reference_count(const plutovg_path_t* path); /** * @brief Retrieves the elements of a path. * * Provides access to the array of path elements. * * @param path A pointer to a `plutovg_path_t` object. * @param elements A pointer to a pointer that will be set to the array of path elements. * @return The number of elements in the path. */ PLUTOVG_API int plutovg_path_get_elements(const plutovg_path_t* path, const plutovg_path_element_t** elements); /** * @brief Moves the current point to a new position. * * This function moves the current point to the specified coordinates without * drawing a line. This is equivalent to the `M` command in SVG path syntax. * * @param path A pointer to a `plutovg_path_t` object. * @param x The x-coordinate of the new position. * @param y The y-coordinate of the new position. */ PLUTOVG_API void plutovg_path_move_to(plutovg_path_t* path, float x, float y); /** * @brief Adds a straight line segment to the path. * * This function adds a straight line segment from the current point to the * specified coordinates. This is equivalent to the `L` command in SVG path syntax. * * @param path A pointer to a `plutovg_path_t` object. * @param x The x-coordinate of the end point of the line segment. * @param y The y-coordinate of the end point of the line segment. */ PLUTOVG_API void plutovg_path_line_to(plutovg_path_t* path, float x, float y); /** * @brief Adds a quadratic Bézier curve to the path. * * This function adds a quadratic Bézier curve segment from the current point * to the specified end point, using the given control point. This is equivalent * to the `Q` command in SVG path syntax. * * @param path A pointer to a `plutovg_path_t` object. * @param x1 The x-coordinate of the control point. * @param y1 The y-coordinate of the control point. * @param x2 The x-coordinate of the end point of the curve. * @param y2 The y-coordinate of the end point of the curve. */ PLUTOVG_API void plutovg_path_quad_to(plutovg_path_t* path, float x1, float y1, float x2, float y2); /** * @brief Adds a cubic Bézier curve to the path. * * This function adds a cubic Bézier curve segment from the current point * to the specified end point, using the given two control points. This is * equivalent to the `C` command in SVG path syntax. * * @param path A pointer to a `plutovg_path_t` object. * @param x1 The x-coordinate of the first control point. * @param y1 The y-coordinate of the first control point. * @param x2 The x-coordinate of the second control point. * @param y2 The y-coordinate of the second control point. * @param x3 The x-coordinate of the end point of the curve. * @param y3 The y-coordinate of the end point of the curve. */ PLUTOVG_API void plutovg_path_cubic_to(plutovg_path_t* path, float x1, float y1, float x2, float y2, float x3, float y3); /** * @brief Adds an elliptical arc to the path. * * This function adds an elliptical arc segment from the current point to the * specified end point. The arc is defined by the radii, rotation angle, and * flags for large arc and sweep. This is equivalent to the `A` command in SVG * path syntax. * * @param path A pointer to a `plutovg_path_t` object. * @param rx The x-radius of the ellipse. * @param ry The y-radius of the ellipse. * @param angle The rotation angle of the ellipse in radians. * @param large_arc_flag If true, draw the large arc; otherwise, draw the small arc. * @param sweep_flag If true, draw the arc in the positive-angle direction; otherwise, in the negative-angle direction. * @param x The x-coordinate of the end point of the arc. * @param y The y-coordinate of the end point of the arc. */ PLUTOVG_API void plutovg_path_arc_to(plutovg_path_t* path, float rx, float ry, float angle, bool large_arc_flag, bool sweep_flag, float x, float y); /** * @brief Closes the current sub-path. * * This function closes the current sub-path by drawing a straight line back to * the start point of the sub-path. This is equivalent to the `Z` command in SVG * path syntax. * * @param path A pointer to a `plutovg_path_t` object. */ PLUTOVG_API void plutovg_path_close(plutovg_path_t* path); /** * @brief Retrieves the current point of the path. * * Gets the current point's coordinates in the path. This point is the last * position used or the point where the path was last moved to. * * @param path A pointer to a `plutovg_path_t` object. * @param x The x-coordinate of the current point. * @param y The y-coordinate of the current point. */ PLUTOVG_API void plutovg_path_get_current_point(const plutovg_path_t* path, float* x, float* y); /** * @brief Reserves space for path elements. * * Reserves space for a specified number of elements in the path. This helps optimize * memory allocation for future path operations. * * @param path A pointer to a `plutovg_path_t` object. * @param count The number of path elements to reserve space for. */ PLUTOVG_API void plutovg_path_reserve(plutovg_path_t* path, int count); /** * @brief Resets the path. * * Clears all path data, effectively resetting the `plutovg_path_t` object to its initial state. * * @param path A pointer to a `plutovg_path_t` object. */ PLUTOVG_API void plutovg_path_reset(plutovg_path_t* path); /** * @brief Adds a rectangle to the path. * * Adds a rectangle defined by the top-left corner (x, y) and dimensions (w, h) to the path. * * @param path A pointer to a `plutovg_path_t` object. * @param x The x-coordinate of the rectangle's top-left corner. * @param y The y-coordinate of the rectangle's top-left corner. * @param w The width of the rectangle. * @param h The height of the rectangle. */ PLUTOVG_API void plutovg_path_add_rect(plutovg_path_t* path, float x, float y, float w, float h); /** * @brief Adds a rounded rectangle to the path. * * Adds a rounded rectangle defined by the top-left corner (x, y), dimensions (w, h), * and corner radii (rx, ry) to the path. * * @param path A pointer to a `plutovg_path_t` object. * @param x The x-coordinate of the rectangle's top-left corner. * @param y The y-coordinate of the rectangle's top-left corner. * @param w The width of the rectangle. * @param h The height of the rectangle. * @param rx The x-radius of the rectangle's corners. * @param ry The y-radius of the rectangle's corners. */ PLUTOVG_API void plutovg_path_add_round_rect(plutovg_path_t* path, float x, float y, float w, float h, float rx, float ry); /** * @brief Adds an ellipse to the path. * * Adds an ellipse defined by the center (cx, cy) and radii (rx, ry) to the path. * * @param path A pointer to a `plutovg_path_t` object. * @param cx The x-coordinate of the ellipse's center. * @param cy The y-coordinate of the ellipse's center. * @param rx The x-radius of the ellipse. * @param ry The y-radius of the ellipse. */ PLUTOVG_API void plutovg_path_add_ellipse(plutovg_path_t* path, float cx, float cy, float rx, float ry); /** * @brief Adds a circle to the path. * * Adds a circle defined by its center (cx, cy) and radius (r) to the path. * * @param path A pointer to a `plutovg_path_t` object. * @param cx The x-coordinate of the circle's center. * @param cy The y-coordinate of the circle's center. * @param r The radius of the circle. */ PLUTOVG_API void plutovg_path_add_circle(plutovg_path_t* path, float cx, float cy, float r); /** * @brief Adds an arc to the path. * * Adds an arc defined by the center (cx, cy), radius (r), start angle (a0), end angle (a1), * and direction (ccw) to the path. * * @param path A pointer to a `plutovg_path_t` object. * @param cx The x-coordinate of the arc's center. * @param cy The y-coordinate of the arc's center. * @param r The radius of the arc. * @param a0 The start angle of the arc in radians. * @param a1 The end angle of the arc in radians. * @param ccw If true, the arc is drawn counter-clockwise; if false, clockwise. */ PLUTOVG_API void plutovg_path_add_arc(plutovg_path_t* path, float cx, float cy, float r, float a0, float a1, bool ccw); /** * @brief Adds a sub-path to the path. * * Adds all elements from another path (`source`) to the current path, optionally * applying a transformation matrix. * * @param path A pointer to a `plutovg_path_t` object. * @param source A pointer to the `plutovg_path_t` object to copy elements from. * @param matrix A pointer to a `plutovg_matrix_t` object, or `NULL` to apply no transformation. */ PLUTOVG_API void plutovg_path_add_path(plutovg_path_t* path, const plutovg_path_t* source, const plutovg_matrix_t* matrix); /** * @brief Applies a transformation matrix to the path. * * Transforms the entire path using the provided transformation matrix. * * @param path A pointer to a `plutovg_path_t` object. * @param matrix A pointer to a `plutovg_matrix_t` object. */ PLUTOVG_API void plutovg_path_transform(plutovg_path_t* path, const plutovg_matrix_t* matrix); /** * @brief Callback function type for traversing a path. * * This function type defines a callback used to traverse path elements. * * @param closure A pointer to user-defined data passed to the callback. * @param command The current path command. * @param points An array of points associated with the command. * @param npoints The number of points in the array. */ typedef void (*plutovg_path_traverse_func_t)(void* closure, plutovg_path_command_t command, const plutovg_point_t* points, int npoints); /** * @brief Traverses the path and calls the callback for each element. * * @param path A pointer to a `plutovg_path_t` object. * @param traverse_func The callback function to be called for each element of the path. * @param closure User-defined data passed to the callback. */ PLUTOVG_API void plutovg_path_traverse(const plutovg_path_t* path, plutovg_path_traverse_func_t traverse_func, void* closure); /** * @brief Traverses the path with Bézier curves flattened to line segments. * * @param path A pointer to a `plutovg_path_t` object. * @param traverse_func The callback function to be called for each element of the path. * @param closure User-defined data passed to the callback. */ PLUTOVG_API void plutovg_path_traverse_flatten(const plutovg_path_t* path, plutovg_path_traverse_func_t traverse_func, void* closure); /** * @brief Traverses the path with a dashed pattern and calls the callback for each segment. * * @param path A pointer to a `plutovg_path_t` object. * @param offset The starting offset into the dash pattern. * @param dashes An array of dash lengths. * @param ndashes The number of elements in the `dashes` array. * @param traverse_func The callback function to be called for each element of the path. * @param closure User-defined data passed to the callback. */ PLUTOVG_API void plutovg_path_traverse_dashed(const plutovg_path_t* path, float offset, const float* dashes, int ndashes, plutovg_path_traverse_func_t traverse_func, void* closure); /** * @brief Creates a copy of the path. * * @param path A pointer to the `plutovg_path_t` object to clone. * @return A pointer to the newly created path clone. */ PLUTOVG_API plutovg_path_t* plutovg_path_clone(const plutovg_path_t* path); /** * @brief Creates a copy of the path with Bézier curves flattened to line segments. * * @param path A pointer to the `plutovg_path_t` object to clone. * @return A pointer to the newly created path clone with flattened curves. */ PLUTOVG_API plutovg_path_t* plutovg_path_clone_flatten(const plutovg_path_t* path); /** * @brief Creates a copy of the path with a dashed pattern applied. * * @param path A pointer to the `plutovg_path_t` object to clone. * @param offset The starting offset into the dash pattern. * @param dashes An array of dash lengths. * @param ndashes The number of elements in the `dashes` array. * @return A pointer to the newly created path clone with dashed pattern. */ PLUTOVG_API plutovg_path_t* plutovg_path_clone_dashed(const plutovg_path_t* path, float offset, const float* dashes, int ndashes); /** * @brief Computes the bounding box and total length of the path. * * @param path A pointer to a `plutovg_path_t` object. * @param extents A pointer to a `plutovg_rect_t` object to store the bounding box. * @param tight If `true`, computes a precise bounding box; otherwise, aligns to control points. * @return The total length of the path. */ PLUTOVG_API float plutovg_path_extents(const plutovg_path_t* path, plutovg_rect_t* extents, bool tight); /** * @brief Calculates the total length of the path. * * @param path A pointer to a `plutovg_path_t` object. * @return The total length of the path. */ PLUTOVG_API float plutovg_path_length(const plutovg_path_t* path); /** * @brief Parses SVG path data into a `plutovg_path_t` object. * * @param path A pointer to the `plutovg_path_t` object to populate. * @param data The SVG path data string. * @param length The length of `data`, or `-1` for null-terminated data. * @return `true` if successful; `false` otherwise. */ PLUTOVG_API bool plutovg_path_parse(plutovg_path_t* path, const char* data, int length); /** * @brief Text encodings used for converting text data to code points. */ typedef enum plutovg_text_encoding { PLUTOVG_TEXT_ENCODING_LATIN1, ///< Latin-1 encoding PLUTOVG_TEXT_ENCODING_UTF8, ///< UTF-8 encoding PLUTOVG_TEXT_ENCODING_UTF16, ///< UTF-16 encoding PLUTOVG_TEXT_ENCODING_UTF32 ///< UTF-32 encoding } plutovg_text_encoding_t; /** * @brief Iterator for traversing code points in text data. */ typedef struct plutovg_text_iterator { const void* text; ///< Pointer to the text data. int length; ///< Length of the text data. plutovg_text_encoding_t encoding; ///< Encoding format of the text data. int index; ///< Current position in the text data. } plutovg_text_iterator_t; /** * @brief Represents a Unicode code point. */ typedef unsigned int plutovg_codepoint_t; /** * @brief Initializes a text iterator. * * @param it Pointer to the text iterator. * @param text Pointer to the text data. * @param length Length of the text data, or -1 if the data is null-terminated. * @param encoding Encoding of the text data. */ PLUTOVG_API void plutovg_text_iterator_init(plutovg_text_iterator_t* it, const void* text, int length, plutovg_text_encoding_t encoding); /** * @brief Checks if there are more code points to iterate. * * @param it Pointer to the text iterator. * @return `true` if more code points are available; otherwise, `false`. */ PLUTOVG_API bool plutovg_text_iterator_has_next(const plutovg_text_iterator_t* it); /** * @brief Retrieves the next code point and advances the iterator. * * @param it Pointer to the text iterator. * @return The next code point. */ PLUTOVG_API plutovg_codepoint_t plutovg_text_iterator_next(plutovg_text_iterator_t* it); /** * @brief Represents a font face. */ typedef struct plutovg_font_face plutovg_font_face_t; /** * @brief Loads a font face from a file. * * @param filename Path to the font file. * @param ttcindex Index of the font face within a TrueType Collection (TTC). * @return A pointer to the loaded `plutovg_font_face_t` object, or `NULL` on failure. */ PLUTOVG_API plutovg_font_face_t* plutovg_font_face_load_from_file(const char* filename, int ttcindex); /** * @brief Loads a font face from memory. * * @param data Pointer to the font data. * @param length Length of the font data. * @param ttcindex Index of the font face within a TrueType Collection (TTC). * @param destroy_func Function to free the font data when no longer needed. * @param closure User-defined data passed to `destroy_func`. * @return A pointer to the loaded `plutovg_font_face_t` object, or `NULL` on failure. */ PLUTOVG_API plutovg_font_face_t* plutovg_font_face_load_from_data(const void* data, unsigned int length, int ttcindex, plutovg_destroy_func_t destroy_func, void* closure); /** * @brief Increments the reference count of a font face. * * @param face A pointer to a `plutovg_font_face_t` object. * @return A pointer to the same `plutovg_font_face_t` object with an incremented reference count. */ PLUTOVG_API plutovg_font_face_t* plutovg_font_face_reference(plutovg_font_face_t* face); /** * @brief Decrements the reference count and potentially destroys the font face. * * @param face A pointer to a `plutovg_font_face_t` object. */ PLUTOVG_API void plutovg_font_face_destroy(plutovg_font_face_t* face); /** * @brief Retrieves the current reference count of a font face. * * @param face A pointer to a `plutovg_font_face_t` object. * @return The reference count of the font face. */ PLUTOVG_API int plutovg_font_face_get_reference_count(const plutovg_font_face_t* face); /** * @brief Retrieves metrics for a font face at a specified size. * * @param face A pointer to a `plutovg_font_face_t` object. * @param size The font size in pixels. * @param ascent Pointer to store the ascent metric. * @param descent Pointer to store the descent metric. * @param line_gap Pointer to store the line gap metric. * @param extents Pointer to a `plutovg_rect_t` object to store the font bounding box. */ PLUTOVG_API void plutovg_font_face_get_metrics(const plutovg_font_face_t* face, float size, float* ascent, float* descent, float* line_gap, plutovg_rect_t* extents); /** * @brief Retrieves metrics for a specified glyph at a given size. * * @param face A pointer to a `plutovg_font_face_t` object. * @param size The font size in pixels. * @param codepoint The Unicode code point of the glyph. * @param advance_width Pointer to store the advance width of the glyph. * @param left_side_bearing Pointer to store the left side bearing of the glyph. * @param extents Pointer to a `plutovg_rect_t` object to store the glyph bounding box. */ PLUTOVG_API void plutovg_font_face_get_glyph_metrics(plutovg_font_face_t* face, float size, plutovg_codepoint_t codepoint, float* advance_width, float* left_side_bearing, plutovg_rect_t* extents); /** * @brief Retrieves the path of a glyph and its advance width. * * @param face A pointer to a `plutovg_font_face_t` object. * @param size The font size in pixels. * @param x The x-coordinate for positioning the glyph. * @param y The y-coordinate for positioning the glyph. * @param codepoint The Unicode code point of the glyph. * @param path Pointer to a `plutovg_path_t` object to store the glyph path. * @return The advance width of the glyph. */ PLUTOVG_API float plutovg_font_face_get_glyph_path(plutovg_font_face_t* face, float size, float x, float y, plutovg_codepoint_t codepoint, plutovg_path_t* path); /** * @brief Traverses the path of a glyph and calls a callback for each path element. * * @param face A pointer to a `plutovg_font_face_t` object. * @param size The font size in pixels. * @param x The x-coordinate for positioning the glyph. * @param y The y-coordinate for positioning the glyph. * @param codepoint The Unicode code point of the glyph. * @param traverse_func The callback function to be called for each path element. * @param closure User-defined data passed to the callback function. * @return The advance width of the glyph. */ PLUTOVG_API float plutovg_font_face_traverse_glyph_path(plutovg_font_face_t* face, float size, float x, float y, plutovg_codepoint_t codepoint, plutovg_path_traverse_func_t traverse_func, void* closure); /** * @brief Computes the bounding box of a text string and its advance width. * * @param face A pointer to a `plutovg_font_face_t` object. * @param size The font size in pixels. * @param text Pointer to the text data. * @param length Length of the text data, or -1 if null-terminated. * @param encoding Encoding of the text data. * @param extents Pointer to a `plutovg_rect_t` object to store the bounding box of the text. * @return The total advance width of the text. */ PLUTOVG_API float plutovg_font_face_text_extents(plutovg_font_face_t* face, float size, const void* text, int length, plutovg_text_encoding_t encoding, plutovg_rect_t* extents); /** * @brief Represents a cache of loaded font faces. */ typedef struct plutovg_font_face_cache plutovg_font_face_cache_t; /** * @brief Create a new, empty font‐face cache. * * @return Pointer to a newly allocated `plutovg_font_face_cache_t` object. */ PLUTOVG_API plutovg_font_face_cache_t* plutovg_font_face_cache_create(void); /** * @brief Increments the reference count of a font‐face cache. * * @param cache A pointer to a `plutovg_font_face_cache_t` object. * @return A pointer to the same `plutovg_font_face_cache_t` object with an incremented reference count. */ PLUTOVG_API plutovg_font_face_cache_t* plutovg_font_face_cache_reference(plutovg_font_face_cache_t* cache); /** * @brief Decrement the reference count of a font‐face cache and destroy it when it reaches zero. * * @param cache A pointer to a `plutovg_font_face_cache_t` object to release. */ PLUTOVG_API void plutovg_font_face_cache_destroy(plutovg_font_face_cache_t* cache); /** * @brief Retrieve the current reference count of a font‐face cache. * * @param cache A pointer to a `plutovg_font_face_cache_t` object. * @return The current reference count, or 0 if cache is NULL. */ PLUTOVG_API int plutovg_font_face_cache_reference_count(const plutovg_font_face_cache_t* cache); /** * @brief Remove all entries from a font‐face cache. * * @param cache A pointer to a `plutovg_font_face_cache_t` object to reset. */ PLUTOVG_API void plutovg_font_face_cache_reset(plutovg_font_face_cache_t* cache); /** * @brief Add a font face to the cache with the specified family and style. * * @param cache A pointer to a `plutovg_font_face_cache_t` object. * @param family The font family name. * @param bold Whether the font is bold. * @param italic Whether the font is italic. * @param face A pointer to the `plutovg_font_face_t` to add. The cache increments its reference count. */ PLUTOVG_API void plutovg_font_face_cache_add(plutovg_font_face_cache_t* cache, const char* family, bool bold, bool italic, plutovg_font_face_t* face); /** * @brief Load a font face from a file and add it to the cache with the specified family and style. * * @param cache A pointer to a `plutovg_font_face_cache_t` object. * @param family The font family name to associate with the face. * @param bold Whether the font is bold. * @param italic Whether the font is italic. * @param filename Path to the font file. * @param ttcindex Index of the face in a TrueType collection (use 0 for non-TTC fonts). * @return `true` on success, `false` if the file could not be loaded. */ PLUTOVG_API bool plutovg_font_face_cache_add_file(plutovg_font_face_cache_t* cache, const char* family, bool bold, bool italic, const char* filename, int ttcindex); /** * @brief Retrieve a font face from the cache by family and style. * * @param cache A pointer to a `plutovg_font_face_cache_t` object. * @param family The font family name. * @param bold Whether the font is bold. * @param italic Whether the font is italic. * @return A pointer to the matching `plutovg_font_face_t` object, or NULL if not found. The returned face is owned by the cache and must not be destroyed by the caller. */ PLUTOVG_API plutovg_font_face_t* plutovg_font_face_cache_get(plutovg_font_face_cache_t* cache, const char* family, bool bold, bool italic); /** * @brief Load all font faces from a file and add them to the cache. * * @param cache A pointer to a `plutovg_font_face_cache_t` object. * @param filename Path to the font file (TrueType, OpenType, or font collection). * @return The number of faces successfully loaded, or `-1` if font face cache loading is disabled. */ PLUTOVG_API int plutovg_font_face_cache_load_file(plutovg_font_face_cache_t* cache, const char* filename); /** * @brief Load all font faces from files in a directory recursively and add them to the cache. * * This scans the specified directory recursively and loads all supported font files. * * @param cache A pointer to a `plutovg_font_face_cache_t` object. * @param dirname Path to the directory containing font files. * @return The number of faces successfully loaded, or `-1` if font face cache loading is disabled. */ PLUTOVG_API int plutovg_font_face_cache_load_dir(plutovg_font_face_cache_t* cache, const char* dirname); /** * @brief Load all available system font faces and add them to the cache. * * This scans standard system font directories recursively and loads all supported font files. * * @param cache A pointer to a `plutovg_font_face_cache_t` object. * @return The number of faces successfully loaded, or `-1` if font face cache loading is disabled. */ PLUTOVG_API int plutovg_font_face_cache_load_sys(plutovg_font_face_cache_t* cache); /** * @brief Represents a color with red, green, blue, and alpha components. */ typedef struct plutovg_color { float r; ///< Red component (0 to 1). float g; ///< Green component (0 to 1). float b; ///< Blue component (0 to 1). float a; ///< Alpha (opacity) component (0 to 1). } plutovg_color_t; #define PLUTOVG_MAKE_COLOR(r, g, b, a) ((plutovg_color_t){r, g, b, a}) #define PLUTOVG_BLACK_COLOR PLUTOVG_MAKE_COLOR(0, 0, 0, 1) #define PLUTOVG_WHITE_COLOR PLUTOVG_MAKE_COLOR(1, 1, 1, 1) #define PLUTOVG_RED_COLOR PLUTOVG_MAKE_COLOR(1, 0, 0, 1) #define PLUTOVG_GREEN_COLOR PLUTOVG_MAKE_COLOR(0, 1, 0, 1) #define PLUTOVG_BLUE_COLOR PLUTOVG_MAKE_COLOR(0, 0, 1, 1) #define PLUTOVG_YELLOW_COLOR PLUTOVG_MAKE_COLOR(1, 1, 0, 1) #define PLUTOVG_CYAN_COLOR PLUTOVG_MAKE_COLOR(0, 1, 1, 1) #define PLUTOVG_MAGENTA_COLOR PLUTOVG_MAKE_COLOR(1, 0, 1, 1) /** * @brief Initializes a color using RGB components in the 0-1 range. * * @param color A pointer to a `plutovg_color_t` object. * @param r Red component (0 to 1). * @param g Green component (0 to 1). * @param b Blue component (0 to 1). */ PLUTOVG_API void plutovg_color_init_rgb(plutovg_color_t* color, float r, float g, float b); /** * @brief Initializes a color using RGBA components in the 0-1 range. * * @param color A pointer to a `plutovg_color_t` object. * @param r Red component (0 to 1). * @param g Green component (0 to 1). * @param b Blue component (0 to 1). * @param a Alpha component (0 to 1). */ PLUTOVG_API void plutovg_color_init_rgba(plutovg_color_t* color, float r, float g, float b, float a); /** * @brief Initializes a color using RGB components in the 0-255 range. * * @param color A pointer to a `plutovg_color_t` object. * @param r Red component (0 to 255). * @param g Green component (0 to 255). * @param b Blue component (0 to 255). */ PLUTOVG_API void plutovg_color_init_rgb8(plutovg_color_t* color, int r, int g, int b); /** * @brief Initializes a color using RGBA components in the 0-255 range. * * @param color A pointer to a `plutovg_color_t` object. * @param r Red component (0 to 255). * @param g Green component (0 to 255). * @param b Blue component (0 to 255). * @param a Alpha component (0 to 255). */ PLUTOVG_API void plutovg_color_init_rgba8(plutovg_color_t* color, int r, int g, int b, int a); /** * @brief Initializes a color from a 32-bit unsigned RGBA value. * * @param color A pointer to a `plutovg_color_t` object. * @param value 32-bit unsigned RGBA value. */ PLUTOVG_API void plutovg_color_init_rgba32(plutovg_color_t* color, unsigned int value); /** * @brief Initializes a color from a 32-bit unsigned ARGB value. * * @param color A pointer to a `plutovg_color_t` object. * @param value 32-bit unsigned ARGB value. */ PLUTOVG_API void plutovg_color_init_argb32(plutovg_color_t* color, unsigned int value); /** * @brief Initializes a color with the specified HSL color values. * * @param color A pointer to a `plutovg_color_t` object. * @param h Hue component in degrees (0 to 360). * @param s Saturation component (0 to 1). * @param l Lightness component (0 to 1). */ PLUTOVG_API void plutovg_color_init_hsl(plutovg_color_t* color, float h, float s, float l); /** * @brief Initializes a color with the specified HSLA color values. * * @param color A pointer to a `plutovg_color_t` object. * @param h Hue component in degrees (0 to 360). * @param s Saturation component (0 to 1). * @param l Lightness component (0 to 1). * @param a Alpha component (0 to 1). */ PLUTOVG_API void plutovg_color_init_hsla(plutovg_color_t* color, float h, float s, float l, float a); /** * @brief Converts a color to a 32-bit unsigned RGBA value. * * @param color A pointer to a `plutovg_color_t` object. * * @return 32-bit unsigned RGBA value. */ PLUTOVG_API unsigned int plutovg_color_to_rgba32(const plutovg_color_t* color); /** * @brief Converts a color to a 32-bit unsigned ARGB value. * * @param color A pointer to a `plutovg_color_t` object. * * @return 32-bit unsigned ARGB value. */ PLUTOVG_API unsigned int plutovg_color_to_argb32(const plutovg_color_t* color); /** * @brief Parses a color from a string using CSS color syntax. * * @param color A pointer to a `plutovg_color_t` object to store the parsed color. * @param data A pointer to the input string containing the color data. * @param length The length of the input string in bytes, or `-1` if the string is null-terminated. * * @return The number of characters consumed on success (including leading/trailing spaces), or 0 on failure. */ PLUTOVG_API int plutovg_color_parse(plutovg_color_t* color, const char* data, int length); /** * @brief Represents an image surface for drawing operations. * * Stores pixel data in a 32-bit premultiplied ARGB format (0xAARRGGBB), * where red, green, and blue channels are multiplied by the alpha channel * and divided by 255. */ typedef struct plutovg_surface plutovg_surface_t; /** * @brief Creates a new image surface with the specified dimensions. * * @param width The width of the surface in pixels. * @param height The height of the surface in pixels. * @return A pointer to the newly created `plutovg_surface_t` object. */ PLUTOVG_API plutovg_surface_t* plutovg_surface_create(int width, int height); /** * @brief Creates an image surface using existing pixel data. * * @param data Pointer to the pixel data. * @param width The width of the surface in pixels. * @param height The height of the surface in pixels. * @param stride The number of bytes per row in the pixel data. * @return A pointer to the newly created `plutovg_surface_t` object. */ PLUTOVG_API plutovg_surface_t* plutovg_surface_create_for_data(unsigned char* data, int width, int height, int stride); /** * @brief Loads an image surface from a file. * * @param filename Path to the image file. * @return Pointer to the surface, or `NULL` on failure. */ PLUTOVG_API plutovg_surface_t* plutovg_surface_load_from_image_file(const char* filename); /** * @brief Loads an image surface from raw image data. * * @param data Pointer to the image data. * @param length Length of the data in bytes. * @return Pointer to the surface, or `NULL` on failure. */ PLUTOVG_API plutovg_surface_t* plutovg_surface_load_from_image_data(const void* data, int length); /** * @brief Loads an image surface from base64-encoded data. * * @param data Pointer to the base64-encoded image data. * @param length Length of the data in bytes, or `-1` if null-terminated. * @return Pointer to the surface, or `NULL` on failure. */ PLUTOVG_API plutovg_surface_t* plutovg_surface_load_from_image_base64(const char* data, int length); /** * @brief Increments the reference count for a surface. * * @param surface Pointer to the `plutovg_surface_t` object. * @return Pointer to the `plutovg_surface_t` object. */ PLUTOVG_API plutovg_surface_t* plutovg_surface_reference(plutovg_surface_t* surface); /** * @brief Decrements the reference count and destroys the surface if the count reaches zero. * * @param surface Pointer to the `plutovg_surface_t` object . */ PLUTOVG_API void plutovg_surface_destroy(plutovg_surface_t* surface); /** * @brief Gets the current reference count of a surface. * * @param surface Pointer to the `plutovg_surface_t` object. * @return The reference count of the surface. */ PLUTOVG_API int plutovg_surface_get_reference_count(const plutovg_surface_t* surface); /** * @brief Gets the pixel data of the surface. * * @param surface Pointer to the `plutovg_surface_t` object. * @return Pointer to the pixel data. */ PLUTOVG_API unsigned char* plutovg_surface_get_data(const plutovg_surface_t* surface); /** * @brief Gets the width of the surface. * * @param surface Pointer to the `plutovg_surface_t` object. * @return Width of the surface in pixels. */ PLUTOVG_API int plutovg_surface_get_width(const plutovg_surface_t* surface); /** * @brief Gets the height of the surface. * * @param surface Pointer to the `plutovg_surface_t` object. * @return Height of the surface in pixels. */ PLUTOVG_API int plutovg_surface_get_height(const plutovg_surface_t* surface); /** * @brief Gets the stride of the surface. * * @param surface Pointer to the `plutovg_surface_t` object. * @return Number of bytes per row. */ PLUTOVG_API int plutovg_surface_get_stride(const plutovg_surface_t* surface); /** * @brief Clears the entire surface with the specified color. * * @param surface Pointer to the target surface. * @param color Pointer to the color used for clearing. */ PLUTOVG_API void plutovg_surface_clear(plutovg_surface_t* surface, const plutovg_color_t* color); /** * @brief Writes the surface to a PNG file. * * @param surface Pointer to the `plutovg_surface_t` object. * @param filename Path to the output PNG file. * @return `true` if successful, `false` otherwise. */ PLUTOVG_API bool plutovg_surface_write_to_png(const plutovg_surface_t* surface, const char* filename); /** * @brief Writes the surface to a JPEG file. * * @param surface Pointer to the `plutovg_surface_t` object. * @param filename Path to the output JPEG file. * @param quality JPEG quality (0 to 100). * @return `true` if successful, `false` otherwise. */ PLUTOVG_API bool plutovg_surface_write_to_jpg(const plutovg_surface_t* surface, const char* filename, int quality); /** * @brief Writes the surface to a PNG stream. * * @param surface Pointer to the `plutovg_surface_t` object. * @param write_func Callback function for writing data. * @param closure User-defined data passed to the callback. * @return `true` if successful, `false` otherwise. */ PLUTOVG_API bool plutovg_surface_write_to_png_stream(const plutovg_surface_t* surface, plutovg_write_func_t write_func, void* closure); /** * @brief Writes the surface to a JPEG stream. * * @param surface Pointer to the `plutovg_surface_t` object. * @param write_func Callback function for writing data. * @param closure User-defined data passed to the callback. * @param quality JPEG quality (0 to 100). * @return `true` if successful, `false` otherwise. */ PLUTOVG_API bool plutovg_surface_write_to_jpg_stream(const plutovg_surface_t* surface, plutovg_write_func_t write_func, void* closure, int quality); /** * @brief Converts pixel data from premultiplied ARGB to RGBA format. * * Transforms pixel data from native-endian 32-bit ARGB premultiplied format * to a non-premultiplied RGBA byte sequence. * * @param dst Pointer to the destination buffer (can overlap with `src`). * @param src Pointer to the source buffer in ARGB premultiplied format. * @param width Image width in pixels. * @param height Image height in pixels. * @param stride Number of bytes per image row in the buffers. */ PLUTOVG_API void plutovg_convert_argb_to_rgba(unsigned char* dst, const unsigned char* src, int width, int height, int stride); /** * @brief Converts pixel data from RGBA to premultiplied ARGB format. * * Transforms pixel data from a non-premultiplied RGBA byte sequence * to a native-endian 32-bit ARGB premultiplied format. * * @param dst Pointer to the destination buffer (can overlap with `src`). * @param src Pointer to the source buffer in RGBA format. * @param width Image width in pixels. * @param height Image height in pixels. * @param stride Number of bytes per image row in the buffers. */ PLUTOVG_API void plutovg_convert_rgba_to_argb(unsigned char* dst, const unsigned char* src, int width, int height, int stride); /** * @brief Defines the type of texture, either plain or tiled. */ typedef enum { PLUTOVG_TEXTURE_TYPE_PLAIN, ///< Plain texture. PLUTOVG_TEXTURE_TYPE_TILED ///< Tiled texture. } plutovg_texture_type_t; /** * @brief Defines the spread method for gradients. */ typedef enum { PLUTOVG_SPREAD_METHOD_PAD, ///< Pad the gradient's edges. PLUTOVG_SPREAD_METHOD_REFLECT, ///< Reflect the gradient beyond its bounds. PLUTOVG_SPREAD_METHOD_REPEAT ///< Repeat the gradient pattern. } plutovg_spread_method_t; /** * @brief Represents a gradient stop. */ typedef struct { float offset; ///< The offset of the gradient stop, as a value between 0 and 1. plutovg_color_t color; ///< The color of the gradient stop. } plutovg_gradient_stop_t; /** * @brief Represents a paint object used for drawing operations. */ typedef struct plutovg_paint plutovg_paint_t; /** * @brief Creates a solid RGB paint. * * @param r The red component (0 to 1). * @param g The green component (0 to 1). * @param b The blue component (0 to 1). * @return A pointer to the created `plutovg_paint_t` object. */ PLUTOVG_API plutovg_paint_t* plutovg_paint_create_rgb(float r, float g, float b); /** * @brief Creates a solid RGBA paint. * * @param r The red component (0 to 1). * @param g The green component (0 to 1). * @param b The blue component (0 to 1). * @param a The alpha component (0 to 1). * @return A pointer to the created `plutovg_paint_t` object. */ PLUTOVG_API plutovg_paint_t* plutovg_paint_create_rgba(float r, float g, float b, float a); /** * @brief Creates a solid color paint. * * @param color A pointer to the `plutovg_color_t` object. * @return A pointer to the created `plutovg_paint_t` object. */ PLUTOVG_API plutovg_paint_t* plutovg_paint_create_color(const plutovg_color_t* color); /** * @brief Creates a linear gradient paint. * * @param x1 The x coordinate of the gradient start. * @param y1 The y coordinate of the gradient start. * @param x2 The x coordinate of the gradient end. * @param y2 The y coordinate of the gradient end. * @param spread The gradient spread method. * @param stops Array of gradient stops. * @param nstops Number of gradient stops. * @param matrix Optional transformation matrix. * @return A pointer to the created `plutovg_paint_t` object. */ PLUTOVG_API plutovg_paint_t* plutovg_paint_create_linear_gradient(float x1, float y1, float x2, float y2, plutovg_spread_method_t spread, const plutovg_gradient_stop_t* stops, int nstops, const plutovg_matrix_t* matrix); /** * @brief Creates a radial gradient paint. * * @param cx The x coordinate of the gradient center. * @param cy The y coordinate of the gradient center. * @param cr The radius of the gradient. * @param fx The x coordinate of the focal point. * @param fy The y coordinate of the focal point. * @param fr The radius of the focal point. * @param spread The gradient spread method. * @param stops Array of gradient stops. * @param nstops Number of gradient stops. * @param matrix Optional transformation matrix. * @return A pointer to the created `plutovg_paint_t` object. */ PLUTOVG_API plutovg_paint_t* plutovg_paint_create_radial_gradient(float cx, float cy, float cr, float fx, float fy, float fr, plutovg_spread_method_t spread, const plutovg_gradient_stop_t* stops, int nstops, const plutovg_matrix_t* matrix); /** * @brief Creates a texture paint from a surface. * * @param surface The texture surface. * @param type The texture type (plain or tiled). * @param opacity The opacity of the texture (0 to 1). * @param matrix Optional transformation matrix. * @return A pointer to the created `plutovg_paint_t` object. */ PLUTOVG_API plutovg_paint_t* plutovg_paint_create_texture(plutovg_surface_t* surface, plutovg_texture_type_t type, float opacity, const plutovg_matrix_t* matrix); /** * @brief Increments the reference count of a paint object. * * @param paint A pointer to the `plutovg_paint_t` object. * @return A pointer to the referenced `plutovg_paint_t` object. */ PLUTOVG_API plutovg_paint_t* plutovg_paint_reference(plutovg_paint_t* paint); /** * @brief Decrements the reference count and destroys the paint if the count reaches zero. * * @param paint A pointer to the `plutovg_paint_t` object. */ PLUTOVG_API void plutovg_paint_destroy(plutovg_paint_t* paint); /** * @brief Retrieves the reference count of a paint object. * * @param paint A pointer to the `plutovg_paint_t` object. * @return The reference count of the `plutovg_paint_t` object. */ PLUTOVG_API int plutovg_paint_get_reference_count(const plutovg_paint_t* paint); /** * @brief Defines fill rule types for filling paths. */ typedef enum { PLUTOVG_FILL_RULE_NON_ZERO, ///< Non-zero winding fill rule. PLUTOVG_FILL_RULE_EVEN_ODD ///< Even-odd fill rule. } plutovg_fill_rule_t; /** * @brief Defines compositing operations. */ typedef enum { PLUTOVG_OPERATOR_CLEAR, ///< Clears the destination (resulting in a fully transparent image). PLUTOVG_OPERATOR_SRC, ///< Source replaces destination. PLUTOVG_OPERATOR_DST, ///< Destination is kept, source is ignored. PLUTOVG_OPERATOR_SRC_OVER, ///< Source is composited over destination. PLUTOVG_OPERATOR_DST_OVER, ///< Destination is composited over source. PLUTOVG_OPERATOR_SRC_IN, ///< Source within destination (only the overlapping part of source is shown). PLUTOVG_OPERATOR_DST_IN, ///< Destination within source. PLUTOVG_OPERATOR_SRC_OUT, ///< Source outside destination (non-overlapping part of source is shown). PLUTOVG_OPERATOR_DST_OUT, ///< Destination outside source. PLUTOVG_OPERATOR_SRC_ATOP, ///< Source atop destination (source shown over destination but only in the destination's bounds). PLUTOVG_OPERATOR_DST_ATOP, ///< Destination atop source (destination shown over source but only in the source's bounds). PLUTOVG_OPERATOR_XOR ///< Source and destination are combined, but their overlapping regions are cleared. } plutovg_operator_t; /** * @brief Defines the shape used at the ends of open subpaths. */ typedef enum { PLUTOVG_LINE_CAP_BUTT, ///< Flat edge at the end of the stroke. PLUTOVG_LINE_CAP_ROUND, ///< Rounded ends at the end of the stroke. PLUTOVG_LINE_CAP_SQUARE ///< Square ends at the end of the stroke. } plutovg_line_cap_t; /** * @brief Defines the shape used at the corners of paths. */ typedef enum { PLUTOVG_LINE_JOIN_MITER, ///< Miter join with sharp corners. PLUTOVG_LINE_JOIN_ROUND, ///< Rounded join. PLUTOVG_LINE_JOIN_BEVEL ///< Beveled join with a flattened corner. } plutovg_line_join_t; /** * @brief Represents a drawing context. */ typedef struct plutovg_canvas plutovg_canvas_t; /** * @brief Creates a drawing context on a surface. * * @param surface A pointer to a `plutovg_surface_t` object. * @return A pointer to the newly created `plutovg_canvas_t` object. */ PLUTOVG_API plutovg_canvas_t* plutovg_canvas_create(plutovg_surface_t* surface); /** * @brief Increases the reference count of the canvas. * * @param canvas A pointer to a `plutovg_canvas_t` object. * @return The same pointer to the `plutovg_canvas_t` object. */ PLUTOVG_API plutovg_canvas_t* plutovg_canvas_reference(plutovg_canvas_t* canvas); /** * @brief Decreases the reference count and destroys the canvas when it reaches zero. * * @param canvas A pointer to a `plutovg_canvas_t` object. */ PLUTOVG_API void plutovg_canvas_destroy(plutovg_canvas_t* canvas); /** * @brief Retrieves the reference count of the canvas. * * @param canvas A pointer to a `plutovg_canvas_t` object. * @return The current reference count. */ PLUTOVG_API int plutovg_canvas_get_reference_count(const plutovg_canvas_t* canvas); /** * @brief Gets the surface associated with the canvas. * * @param canvas A pointer to a `plutovg_canvas_t` object. * @return A pointer to the `plutovg_surface_t` object. */ PLUTOVG_API plutovg_surface_t* plutovg_canvas_get_surface(const plutovg_canvas_t* canvas); /** * @brief Saves the current state of the canvas. * * @param canvas A pointer to a `plutovg_canvas_t` object. */ PLUTOVG_API void plutovg_canvas_save(plutovg_canvas_t* canvas); /** * @brief Restores the canvas to the most recently saved state. * * @param canvas A pointer to a `plutovg_canvas_t` object. */ PLUTOVG_API void plutovg_canvas_restore(plutovg_canvas_t* canvas); /** * @brief Sets the current paint to a solid color. * * If not set, the default paint is opaque black color. * * @param canvas A pointer to a `plutovg_canvas_t` object. * @param r The red component (0 to 1). * @param g The green component (0 to 1). * @param b The blue component (0 to 1). */ PLUTOVG_API void plutovg_canvas_set_rgb(plutovg_canvas_t* canvas, float r, float g, float b); /** * @brief Sets the current paint to a solid color. * * If not set, the default paint is opaque black color. * * @param canvas A pointer to a `plutovg_canvas_t` object. * @param r The red component (0 to 1). * @param g The green component (0 to 1). * @param b The blue component (0 to 1). * @param a The alpha component (0 to 1). */ PLUTOVG_API void plutovg_canvas_set_rgba(plutovg_canvas_t* canvas, float r, float g, float b, float a); /** * @brief Sets the current paint to a solid color. * * If not set, the default paint is opaque black color. * * @param canvas A pointer to a `plutovg_canvas_t` object. * @param color A pointer to a `plutovg_color_t` object. */ PLUTOVG_API void plutovg_canvas_set_color(plutovg_canvas_t* canvas, const plutovg_color_t* color); /** * @brief Sets the current paint to a linear gradient. * * If not set, the default paint is opaque black color. * * @param canvas A pointer to a `plutovg_canvas_t` object. * @param x1 The x coordinate of the start point. * @param y1 The y coordinate of the start point. * @param x2 The x coordinate of the end point. * @param y2 The y coordinate of the end point. * @param spread The gradient spread method. * @param stops Array of gradient stops. * @param nstops Number of gradient stops. * @param matrix Optional transformation matrix. */ PLUTOVG_API void plutovg_canvas_set_linear_gradient(plutovg_canvas_t* canvas, float x1, float y1, float x2, float y2, plutovg_spread_method_t spread, const plutovg_gradient_stop_t* stops, int nstops, const plutovg_matrix_t* matrix); /** * @brief Sets the current paint to a radial gradient. * * If not set, the default paint is opaque black color. * * @param canvas A pointer to a `plutovg_canvas_t` object. * @param cx The x coordinate of the center. * @param cy The y coordinate of the center. * @param cr The radius of the gradient. * @param fx The x coordinate of the focal point. * @param fy The y coordinate of the focal point. * @param fr The radius of the focal point. * @param spread The gradient spread method. * @param stops Array of gradient stops. * @param nstops Number of gradient stops. * @param matrix Optional transformation matrix. */ PLUTOVG_API void plutovg_canvas_set_radial_gradient(plutovg_canvas_t* canvas, float cx, float cy, float cr, float fx, float fy, float fr, plutovg_spread_method_t spread, const plutovg_gradient_stop_t* stops, int nstops, const plutovg_matrix_t* matrix); /** * @brief Sets the current paint to a texture. * * If not set, the default paint is opaque black color. * * @param canvas A pointer to a `plutovg_canvas_t` object. * @param surface The texture surface. * @param type The texture type (plain or tiled). * @param opacity The opacity of the texture (0 to 1). * @param matrix Optional transformation matrix. */ PLUTOVG_API void plutovg_canvas_set_texture(plutovg_canvas_t* canvas, plutovg_surface_t* surface, plutovg_texture_type_t type, float opacity, const plutovg_matrix_t* matrix); /** * @brief Sets the current paint. * * If not set, the default paint is opaque black color. * * @param canvas A pointer to a `plutovg_canvas_t` object. * @param paint The paint to be used for subsequent drawing operations. */ PLUTOVG_API void plutovg_canvas_set_paint(plutovg_canvas_t* canvas, plutovg_paint_t* paint); /** * @brief Retrieves the current paint. * * If not set, the default paint is opaque black color. * * @param canvas A pointer to a `plutovg_canvas_t` object. * @param color A pointer to a `plutovg_color_t` object where the current color will be stored. * @return The current `plutovg_paint_t` used for drawing operations. If no paint is set, `NULL` is returned. */ PLUTOVG_API plutovg_paint_t* plutovg_canvas_get_paint(const plutovg_canvas_t* canvas, plutovg_color_t* color); /** * @brief Assigns a font-face cache to the canvas for font management. * * @param canvas A pointer to a `plutovg_canvas_t` object. * @param cache A pointer to a `plutovg_font_face_cache_t` object, or NULL to unset the current cache. */ PLUTOVG_API void plutovg_canvas_set_font_face_cache(plutovg_canvas_t* canvas, plutovg_font_face_cache_t* cache); /** * @brief Returns the font-face cache associated with the canvas. * * @param canvas A pointer to a `plutovg_canvas_t` object. * @return A pointer to the associated `plutovg_font_face_cache_t` object, or NULL if none is set. */ PLUTOVG_API plutovg_font_face_cache_t* plutovg_canvas_get_font_face_cache(const plutovg_canvas_t* canvas); /** * @brief Add a font face to the canvas using the specified family and style. * * @param canvas A pointer to a `plutovg_canvas_t` object. * @param family The font family name to associate with the face. * @param bold Whether the font is bold. * @param italic Whether the font is italic. * @param face A pointer to the `plutovg_font_face_t` object to add. */ PLUTOVG_API void plutovg_canvas_add_font_face(plutovg_canvas_t* canvas, const char* family, bool bold, bool italic, plutovg_font_face_t* face); /** * @brief Load a font face from a file and add it to the canvas using the specified family and style. * * @param canvas A pointer to a `plutovg_canvas_t` object. * @param family The font family name to associate with the face. * @param bold Whether the font is bold. * @param italic Whether the font is italic. * @param filename Path to the font file. * @param ttcindex Index within a TrueType Collection (use 0 for regular font files). * @return `true` on success, or `false` if the font could not be loaded. */ PLUTOVG_API bool plutovg_canvas_add_font_file(plutovg_canvas_t* canvas, const char* family, bool bold, bool italic, const char* filename, int ttcindex); /** * @brief Selects and sets the current font face on the canvas. * * @param canvas A pointer to a `plutovg_canvas_t` object. * @param family The font family name to select. * @param bold Whether to match a bold variant. * @param italic Whether to match an italic variant. * @return `true` if a matching font was found and set, `false` otherwise. */ PLUTOVG_API bool plutovg_canvas_select_font_face(plutovg_canvas_t* canvas, const char* family, bool bold, bool italic); /** * @brief Sets the font face and size for text rendering on the canvas. * * If not set, the default font face is `NULL`, and the default face size is 12. * * @param canvas A pointer to a `plutovg_canvas_t` object. * @param face A pointer to a `plutovg_font_face_t` object representing the font face to use. * @param size The size of the font, in pixels. This determines the height of the rendered text. */ PLUTOVG_API void plutovg_canvas_set_font(plutovg_canvas_t* canvas, plutovg_font_face_t* face, float size); /** * @brief Sets the font face for text rendering on the canvas. * * If not set, the default font face is `NULL`. * * @param canvas A pointer to a `plutovg_canvas_t` object. * @param face A pointer to a `plutovg_font_face_t` object representing the font face to use. */ PLUTOVG_API void plutovg_canvas_set_font_face(plutovg_canvas_t* canvas, plutovg_font_face_t* face); /** * @brief Retrieves the current font face used for text rendering on the canvas. * * If not set, the default font face is `NULL`. * * @param canvas A pointer to a `plutovg_canvas_t` object. * @return A pointer to a `plutovg_font_face_t` object representing the current font face. */ PLUTOVG_API plutovg_font_face_t* plutovg_canvas_get_font_face(const plutovg_canvas_t* canvas); /** * @brief Sets the font size for text rendering on the canvas. * * If not set, the default font size is 12. * * @param canvas A pointer to a `plutovg_canvas_t` object. * @param size The size of the font, in pixels. This value defines the height of the rendered text. */ PLUTOVG_API void plutovg_canvas_set_font_size(plutovg_canvas_t* canvas, float size); /** * @brief Retrieves the current font size used for text rendering on the canvas. * * If not set, the default font size is 12. * * @param canvas A pointer to a `plutovg_canvas_t` object. * @return The current font size, in pixels. This value represents the height of the rendered text. */ PLUTOVG_API float plutovg_canvas_get_font_size(const plutovg_canvas_t* canvas); /** * @brief Sets the fill rule. * * If not set, the default fill rule is `PLUTOVG_FILL_RULE_NON_ZERO`. * * @param canvas A pointer to a `plutovg_canvas_t` object. * @param winding The fill rule. */ PLUTOVG_API void plutovg_canvas_set_fill_rule(plutovg_canvas_t* canvas, plutovg_fill_rule_t winding); /** * @brief Retrieves the current fill rule. * * If not set, the default fill rule is `PLUTOVG_FILL_RULE_NON_ZERO`. * * @param canvas A pointer to a `plutovg_canvas_t` object. * @return The current fill rule. */ PLUTOVG_API plutovg_fill_rule_t plutovg_canvas_get_fill_rule(const plutovg_canvas_t* canvas); /** * @brief Sets the compositing operator. * * If not set, the default compositing operator is `PLUTOVG_OPERATOR_SRC_OVER`. * * @param canvas A pointer to a `plutovg_canvas_t` object. * @param op The compositing operator. */ PLUTOVG_API void plutovg_canvas_set_operator(plutovg_canvas_t* canvas, plutovg_operator_t op); /** * @brief Retrieves the current compositing operator. * * If not set, the default compositing operator is `PLUTOVG_OPERATOR_SRC_OVER`. * * @param canvas A pointer to a `plutovg_canvas_t` object. * @return The current compositing operator. */ PLUTOVG_API plutovg_operator_t plutovg_canvas_get_operator(const plutovg_canvas_t* canvas); /** * @brief Sets the global opacity. * * If not set, the default global opacity is 1. * * @param canvas A pointer to a `plutovg_canvas_t` object. * @param opacity The opacity value (0 to 1). */ PLUTOVG_API void plutovg_canvas_set_opacity(plutovg_canvas_t* canvas, float opacity); /** * @brief Retrieves the current global opacity. * * If not set, the default global opacity is 1. * * @param canvas A pointer to a `plutovg_canvas_t` object. * @return The current opacity value. */ PLUTOVG_API float plutovg_canvas_get_opacity(const plutovg_canvas_t* canvas); /** * @brief Sets the line width. * * If not set, the default line width is 1. * * @param canvas A pointer to a `plutovg_canvas_t` object. * @param line_width The width of the stroke. */ PLUTOVG_API void plutovg_canvas_set_line_width(plutovg_canvas_t* canvas, float line_width); /** * @brief Retrieves the current line width. * * If not set, the default line width is 1. * * @param canvas A pointer to a `plutovg_canvas_t` object. * @return The current line width. */ PLUTOVG_API float plutovg_canvas_get_line_width(const plutovg_canvas_t* canvas); /** * @brief Sets the line cap style. * * If not set, the default line cap is `PLUTOVG_LINE_CAP_BUTT`. * * @param canvas A pointer to a `plutovg_canvas_t` object. * @param line_cap The line cap style. */ PLUTOVG_API void plutovg_canvas_set_line_cap(plutovg_canvas_t* canvas, plutovg_line_cap_t line_cap); /** * @brief Retrieves the current line cap style. * * If not set, the default line cap is `PLUTOVG_LINE_CAP_BUTT`. * * @param canvas A pointer to a `plutovg_canvas_t` object. * @return The current line cap style. */ PLUTOVG_API plutovg_line_cap_t plutovg_canvas_get_line_cap(const plutovg_canvas_t* canvas); /** * @brief Sets the line join style. * * If not set, the default line join is `PLUTOVG_LINE_JOIN_MITER`. * * @param canvas A pointer to a `plutovg_canvas_t` object. * @param line_join The line join style. */ PLUTOVG_API void plutovg_canvas_set_line_join(plutovg_canvas_t* canvas, plutovg_line_join_t line_join); /** * @brief Retrieves the current line join style. * * If not set, the default line join is `PLUTOVG_LINE_JOIN_MITER`. * * @param canvas A pointer to a `plutovg_canvas_t` object. * @return The current line join style. */ PLUTOVG_API plutovg_line_join_t plutovg_canvas_get_line_join(const plutovg_canvas_t* canvas); /** * @brief Sets the miter limit. * * If not set, the default miter limit is 10. * * @param canvas A pointer to a `plutovg_canvas_t` object. * @param miter_limit The miter limit value. */ PLUTOVG_API void plutovg_canvas_set_miter_limit(plutovg_canvas_t* canvas, float miter_limit); /** * @brief Retrieves the current miter limit. * * If not set, the default miter limit is 10. * * @param canvas A pointer to a `plutovg_canvas_t` object. * @return The current miter limit value. */ PLUTOVG_API float plutovg_canvas_get_miter_limit(const plutovg_canvas_t* canvas); /** * @brief Sets the dash pattern. * * If not set, the default dash offset is 0, and the default dash array is `NULL`. * * @param canvas A pointer to a `plutovg_canvas_t` object. * @param offset The dash offset. * @param dashes Array of dash lengths. * @param ndashes Number of dash lengths. */ PLUTOVG_API void plutovg_canvas_set_dash(plutovg_canvas_t* canvas, float offset, const float* dashes, int ndashes); /** * @brief Sets the dash offset. * * If not set, the default dash offset is 0. * * @param canvas A pointer to a `plutovg_canvas_t` object. * @param offset The dash offset. */ PLUTOVG_API void plutovg_canvas_set_dash_offset(plutovg_canvas_t* canvas, float offset); /** * @brief Retrieves the current dash offset. * * If not set, the default dash offset is 0. * * @param canvas A pointer to a `plutovg_canvas_t` object. * @return The current dash offset. */ PLUTOVG_API float plutovg_canvas_get_dash_offset(const plutovg_canvas_t* canvas); /** * @brief Sets the dash pattern. * * If not set, the default dash array is `NULL`. * * @param canvas A pointer to a `plutovg_canvas_t` object. * @param dashes Array of dash lengths. * @param ndashes Number of dash lengths. */ PLUTOVG_API void plutovg_canvas_set_dash_array(plutovg_canvas_t* canvas, const float* dashes, int ndashes); /** * @brief Retrieves the current dash pattern. * * If not set, the default dash array is `NULL`. * * @param canvas A pointer to a `plutovg_canvas_t` object. * @param dashes Pointer to store the dash array. * @return The number of dash lengths. */ PLUTOVG_API int plutovg_canvas_get_dash_array(const plutovg_canvas_t* canvas, const float** dashes); /** * @brief Translates the current transformation matrix by offsets `tx` and `ty`. * @param canvas A pointer to a `plutovg_canvas_t` object. * @param tx The translation offset in the x-direction. * @param ty The translation offset in the y-direction. */ PLUTOVG_API void plutovg_canvas_translate(plutovg_canvas_t* canvas, float tx, float ty); /** * @brief Scales the current transformation matrix by factors `sx` and `sy`. * @param canvas A pointer to a `plutovg_canvas_t` object. * @param sx The scaling factor in the x-direction. * @param sy The scaling factor in the y-direction. */ PLUTOVG_API void plutovg_canvas_scale(plutovg_canvas_t* canvas, float sx, float sy); /** * @brief Shears the current transformation matrix by factors `shx` and `shy`. * @param canvas A pointer to a `plutovg_canvas_t` object. * @param shx The shearing factor in the x-direction. * @param shy The shearing factor in the y-direction. */ PLUTOVG_API void plutovg_canvas_shear(plutovg_canvas_t* canvas, float shx, float shy); /** * @brief Rotates the current transformation matrix by the specified angle (in radians). * @param canvas A pointer to a `plutovg_canvas_t` object. * @param angle The rotation angle in radians. */ PLUTOVG_API void plutovg_canvas_rotate(plutovg_canvas_t* canvas, float angle); /** * @brief Multiplies the current transformation matrix with the specified `matrix`. * @param canvas A pointer to a `plutovg_canvas_t` object. * @param matrix A pointer to the `plutovg_matrix_t` object. */ PLUTOVG_API void plutovg_canvas_transform(plutovg_canvas_t* canvas, const plutovg_matrix_t* matrix); /** * @brief Resets the current transformation matrix to the identity matrix. * @param canvas A pointer to a `plutovg_canvas_t` object. */ PLUTOVG_API void plutovg_canvas_reset_matrix(plutovg_canvas_t* canvas); /** * @brief Resets the current transformation matrix to the specified `matrix`. * @param canvas A pointer to a `plutovg_canvas_t` object. * @param matrix A pointer to the `plutovg_matrix_t` object. */ PLUTOVG_API void plutovg_canvas_set_matrix(plutovg_canvas_t* canvas, const plutovg_matrix_t* matrix); /** * @brief Stores the current transformation matrix in `matrix`. * @param canvas A pointer to a `plutovg_canvas_t` object. * @param A pointer to the `plutovg_matrix_t` to store the matrix. */ PLUTOVG_API void plutovg_canvas_get_matrix(const plutovg_canvas_t* canvas, plutovg_matrix_t* matrix); /** * @brief Transforms the point `(x, y)` using the current transformation matrix and stores the result in `(xx, yy)`. * @param canvas A pointer to a `plutovg_canvas_t` object. * @param x The x-coordinate of the point to transform. * @param y The y-coordinate of the point to transform. * @param xx A pointer to store the transformed x-coordinate. * @param yy A pointer to store the transformed y-coordinate. */ PLUTOVG_API void plutovg_canvas_map(const plutovg_canvas_t* canvas, float x, float y, float* xx, float* yy); /** * @brief Transforms the `src` point using the current transformation matrix and stores the result in `dst`. * @note `src` and `dst` can be identical. * @param canvas A pointer to a `plutovg_canvas_t` object. * @param src A pointer to the `plutovg_point_t` point to transform. * @param dst A pointer to the `plutovg_point_t` to store the transformed point. */ PLUTOVG_API void plutovg_canvas_map_point(const plutovg_canvas_t* canvas, const plutovg_point_t* src, plutovg_point_t* dst); /** * @brief Transforms the `src` rectangle using the current transformation matrix and stores the result in `dst`. * @note `src` and `dst` can be identical. * @param canvas A pointer to a `plutovg_canvas_t` object. * @param src A pointer to the `plutovg_rect_t` rectangle to transform. * @param dst A pointer to the `plutovg_rect_t` to store the transformed rectangle. */ PLUTOVG_API void plutovg_canvas_map_rect(const plutovg_canvas_t* canvas, const plutovg_rect_t* src, plutovg_rect_t* dst); /** * @brief Moves the current point to a new position. * * Moves the current point to the specified coordinates without adding a line. * This operation is added to the current path. Equivalent to the SVG `M` command. * * @param canvas A pointer to a `plutovg_canvas_t` object. * @param x The x-coordinate of the new position. * @param y The y-coordinate of the new position. */ PLUTOVG_API void plutovg_canvas_move_to(plutovg_canvas_t* canvas, float x, float y); /** * @brief Adds a straight line segment to the current path. * * Adds a straight line from the current point to the specified coordinates. * This segment is added to the current path. Equivalent to the SVG `L` command. * * @param canvas A pointer to a `plutovg_canvas_t` object. * @param x The x-coordinate of the end point of the line. * @param y The y-coordinate of the end point of the line. */ PLUTOVG_API void plutovg_canvas_line_to(plutovg_canvas_t* canvas, float x, float y); /** * @brief Adds a quadratic Bézier curve to the current path. * * Adds a quadratic Bézier curve from the current point to the specified end point, * using the given control point. This curve is added to the current path. Equivalent to the SVG `Q` command. * * @param canvas A pointer to a `plutovg_canvas_t` object. * @param x1 The x-coordinate of the control point. * @param y1 The y-coordinate of the control point. * @param x2 The x-coordinate of the end point of the curve. * @param y2 The y-coordinate of the end point of the curve. */ PLUTOVG_API void plutovg_canvas_quad_to(plutovg_canvas_t* canvas, float x1, float y1, float x2, float y2); /** * @brief Adds a cubic Bézier curve to the current path. * * Adds a cubic Bézier curve from the current point to the specified end point, * using the given control points. This curve is added to the current path. Equivalent to the SVG `C` command. * * @param canvas A pointer to a `plutovg_canvas_t` object. * @param x1 The x-coordinate of the first control point. * @param y1 The y-coordinate of the first control point. * @param x2 The x-coordinate of the second control point. * @param y2 The y-coordinate of the second control point. * @param x3 The x-coordinate of the end point of the curve. * @param y3 The y-coordinate of the end point of the curve. */ PLUTOVG_API void plutovg_canvas_cubic_to(plutovg_canvas_t* canvas, float x1, float y1, float x2, float y2, float x3, float y3); /** * @brief Adds an elliptical arc to the current path. * * Adds an elliptical arc from the current point to the specified end point, * defined by radii, rotation angle, and flags for arc type and direction. * This arc segment is added to the current path. Equivalent to the SVG `A` command. * * @param canvas A pointer to a `plutovg_canvas_t` object. * @param rx The x-radius of the ellipse. * @param ry The y-radius of the ellipse. * @param angle The rotation angle of the ellipse in degrees. * @param large_arc_flag If true, add the large arc; otherwise, add the small arc. * @param sweep_flag If true, add the arc in the positive-angle direction; otherwise, in the negative-angle direction. * @param x The x-coordinate of the end point. * @param y The y-coordinate of the end point. */ PLUTOVG_API void plutovg_canvas_arc_to(plutovg_canvas_t* canvas, float rx, float ry, float angle, bool large_arc_flag, bool sweep_flag, float x, float y); /** * @brief Adds a rectangle to the current path. * * Adds a rectangle with the specified position and dimensions to the current path. * * @param canvas A pointer to a `plutovg_canvas_t` object. * @param x The x-coordinate of the rectangle's origin. * @param y The y-coordinate of the rectangle's origin. * @param w The width of the rectangle. * @param h The height of the rectangle. */ PLUTOVG_API void plutovg_canvas_rect(plutovg_canvas_t* canvas, float x, float y, float w, float h); /** * @brief Adds a rounded rectangle to the current path. * * Adds a rectangle with rounded corners defined by the specified position, * dimensions, and corner radii to the current path. * * @param canvas A pointer to a `plutovg_canvas_t` object. * @param x The x-coordinate of the rectangle's origin. * @param y The y-coordinate of the rectangle's origin. * @param w The width of the rectangle. * @param h The height of the rectangle. * @param rx The x-radius of the corners. * @param ry The y-radius of the corners. */ PLUTOVG_API void plutovg_canvas_round_rect(plutovg_canvas_t* canvas, float x, float y, float w, float h, float rx, float ry); /** * @brief Adds an ellipse to the current path. * * Adds an ellipse centered at the specified coordinates with the given radii to the current path. * * @param canvas A pointer to a `plutovg_canvas_t` object. * @param cx The x-coordinate of the ellipse's center. * @param cy The y-coordinate of the ellipse's center. * @param rx The x-radius of the ellipse. * @param ry The y-radius of the ellipse. */ PLUTOVG_API void plutovg_canvas_ellipse(plutovg_canvas_t* canvas, float cx, float cy, float rx, float ry); /** * @brief Adds a circle to the current path. * * Adds a circle centered at the specified coordinates with the given radius to the current path. * * @param canvas A pointer to a `plutovg_canvas_t` object. * @param cx The x-coordinate of the circle's center. * @param cy The y-coordinate of the circle's center. * @param r The radius of the circle. */ PLUTOVG_API void plutovg_canvas_circle(plutovg_canvas_t* canvas, float cx, float cy, float r); /** * @brief Adds an arc to the current path. * * Adds an arc centered at the specified coordinates, with a given radius, * starting and ending at the specified angles. The direction of the arc is * determined by `ccw`. This arc segment is added to the current path. * * @param canvas A pointer to a `plutovg_canvas_t` object. * @param cx The x-coordinate of the arc's center. * @param cy The y-coordinate of the arc's center. * @param r The radius of the arc. * @param a0 The starting angle of the arc in radians. * @param a1 The ending angle of the arc in radians. * @param ccw If true, add the arc counter-clockwise; otherwise, clockwise. */ PLUTOVG_API void plutovg_canvas_arc(plutovg_canvas_t* canvas, float cx, float cy, float r, float a0, float a1, bool ccw); /** * @brief Adds a path to the current path. * * Appends the elements of the specified path to the current path. * * @param canvas A pointer to a `plutovg_canvas_t` object. * @param path A pointer to the `plutovg_path_t` object to be added. */ PLUTOVG_API void plutovg_canvas_add_path(plutovg_canvas_t* canvas, const plutovg_path_t* path); /** * @brief Starts a new path on the canvas. * * Begins a new path, clearing any existing path data. The new path starts with no commands. * * @param canvas A pointer to a `plutovg_canvas_t` object. */ PLUTOVG_API void plutovg_canvas_new_path(plutovg_canvas_t* canvas); /** * @brief Closes the current path. * * Closes the current path by adding a straight line back to the starting point. * * @param canvas A pointer to a `plutovg_canvas_t` object. */ PLUTOVG_API void plutovg_canvas_close_path(plutovg_canvas_t* canvas); /** * @brief Retrieves the current point of the canvas. * * Gets the coordinates of the current point in the canvas, which is the last point * added or moved to in the current path. * * @param canvas A pointer to a `plutovg_canvas_t` object. * @param x The x-coordinate of the current point. * @param y The y-coordinate of the current point. */ PLUTOVG_API void plutovg_canvas_get_current_point(const plutovg_canvas_t* canvas, float* x, float* y); /** * @brief Gets the current path from the canvas. * * Retrieves the path object representing the sequence of path commands added to the canvas. * * @param canvas A pointer to a `plutovg_canvas_t` object. * @return The current path. */ PLUTOVG_API plutovg_path_t* plutovg_canvas_get_path(const plutovg_canvas_t* canvas); /** * @brief Tests whether a point lies within the current fill region. * * Determines whether the point at coordinates `(x, y)` falls within the area * that would be filled by a `plutovg_canvas_fill()` operation, given the current path, * fill rule, and transformation state. * * @note Clipping and surface dimensions are not considered in this test. * * @param canvas A pointer to a `plutovg_canvas_t` object. * @param x The X coordinate of the point, in user space. * @param y The Y coordinate of the point, in user space. * @return `true` if the point is within the fill region, `false` otherwise. */ PLUTOVG_API bool plutovg_canvas_fill_contains(plutovg_canvas_t* canvas, float x, float y); /** * @brief Tests whether a point lies within the current stroke region. * * Determines whether the point at coordinates `(x, y)` falls within the area * that would be stroked by a `plutovg_canvas_stroke()` operation, given the current path, * stroke width, joins, caps, miter limit, dash pattern, and transformation state. * * @note Clipping and surface dimensions are not considered in this test. * * @param canvas A pointer to a `plutovg_canvas_t` object. * @param x The X coordinate of the point, in user space. * @param y The Y coordinate of the point, in user space. * @return `true` if the point is within the stroke region, `false` otherwise. */ PLUTOVG_API bool plutovg_canvas_stroke_contains(plutovg_canvas_t* canvas, float x, float y); /** * @brief Tests whether a point lies within the current clipping region. * * Determines whether the point at coordinates `(x, y)` falls within the active clipping * region on the canvas. * * If no clipping is applied, the default clipping region covers the entire canvas * area starting at `(0, 0)` with width and height equal to the canvas dimensions. * * @param canvas A pointer to a `plutovg_canvas_t` object. * @param x The X coordinate of the point, in user space. * @param y The Y coordinate of the point, in user space. * @return `true` if the point is within the clipping region, `false` otherwise. */ PLUTOVG_API bool plutovg_canvas_clip_contains(plutovg_canvas_t* canvas, float x, float y); /** * @brief Computes the bounding box of the area that would be affected by a fill operation. * * Computes an axis-aligned bounding box in user space that encloses the area * which would be affected by a fill operation (`plutovg_canvas_fill()`) given the current path, * fill rule, and transformation state. * * @note Clipping and surface dimensions are not considered in this calculation. * * @param canvas A pointer to a `plutovg_canvas_t` object. * @param extents A pointer to a `plutovg_rect_t` structure that receives the bounding box. */ PLUTOVG_API void plutovg_canvas_fill_extents(plutovg_canvas_t* canvas, plutovg_rect_t* extents); /** * @brief Computes the bounding box of the area that would be affected by a stroke operation. * * Computes an axis-aligned bounding box in user space that encloses the area * which would be affected by a stroke operation (`plutovg_canvas_stroke()`) given the current path, * stroke width, joins, caps, miter limit, dash pattern, and transformation state. * * @note Clipping and surface dimensions are not considered in this calculation. * * @param canvas A pointer to a `plutovg_canvas_t` object. * @param extents A pointer to a `plutovg_rect_t` structure that receives the bounding box. */ PLUTOVG_API void plutovg_canvas_stroke_extents(plutovg_canvas_t* canvas, plutovg_rect_t* extents); /** * @brief Gets the bounding box of the current clipping region. * * Computes an axis-aligned bounding box in user space that encloses the currently active * clipping region on the canvas. * * If no clip is applied, the returned rectangle covers the entire canvas area, * starting at `(0, 0)` with width and height equal to the canvas dimensions. * * @param canvas A pointer to a `plutovg_canvas_t` object. * @param extents A pointer to a `plutovg_rect_t` structure that receives the bounding box. */ PLUTOVG_API void plutovg_canvas_clip_extents(plutovg_canvas_t* canvas, plutovg_rect_t* extents); /** * @brief A drawing operator that fills the current path according to the current fill rule. * * The current path will be cleared after this operation. * * @param canvas A pointer to a `plutovg_canvas_t` object. */ PLUTOVG_API void plutovg_canvas_fill(plutovg_canvas_t* canvas); /** * @brief A drawing operator that strokes the current path according to the current stroke settings. * * The current path will be cleared after this operation. * * @param canvas A pointer to a `plutovg_canvas_t` object. */ PLUTOVG_API void plutovg_canvas_stroke(plutovg_canvas_t* canvas); /** * @brief A drawing operator that intersects the current clipping region with the current path according to the current fill rule. * * The current path will be cleared after this operation. * * @param canvas A pointer to a `plutovg_canvas_t` object. */ PLUTOVG_API void plutovg_canvas_clip(plutovg_canvas_t* canvas); /** * @brief A drawing operator that paints the current clipping region using the current paint. * * @note The current path will not be affected by this operation. * @param canvas A pointer to a `plutovg_canvas_t` object. */ PLUTOVG_API void plutovg_canvas_paint(plutovg_canvas_t* canvas); /** * @brief A drawing operator that fills the current path according to the current fill rule. * * The current path will be preserved after this operation. * * @param canvas A pointer to a `plutovg_canvas_t` object. */ PLUTOVG_API void plutovg_canvas_fill_preserve(plutovg_canvas_t* canvas); /** * @brief A drawing operator that strokes the current path according to the current stroke settings. * * The current path will be preserved after this operation. * * @param canvas A pointer to a `plutovg_canvas_t` object. */ PLUTOVG_API void plutovg_canvas_stroke_preserve(plutovg_canvas_t* canvas); /** * @brief A drawing operator that intersects the current clipping region with the current path according to the current fill rule. * * The current path will be preserved after this operation. * * @param canvas A pointer to a `plutovg_canvas_t` object. */ PLUTOVG_API void plutovg_canvas_clip_preserve(plutovg_canvas_t* canvas); /** * @brief Fills a rectangle according to the current fill rule. * * @note The current path will be cleared by this operation. * @param canvas A pointer to a `plutovg_canvas_t` object. * @param x The x-coordinate of the rectangle's origin. * @param y The y-coordinate of the rectangle's origin. * @param w The width of the rectangle. * @param h The height of the rectangle. */ PLUTOVG_API void plutovg_canvas_fill_rect(plutovg_canvas_t* canvas, float x, float y, float w, float h); /** * @brief Fills a path according to the current fill rule. * * @note The current path will be cleared by this operation. * @param canvas A pointer to a `plutovg_canvas_t` object. * @param path The `plutovg_path_t` object. */ PLUTOVG_API void plutovg_canvas_fill_path(plutovg_canvas_t* canvas, const plutovg_path_t* path); /** * @brief Strokes a rectangle with the current stroke settings. * * @note The current path will be cleared by this operation. * @param canvas A pointer to a `plutovg_canvas_t` object. * @param x The x-coordinate of the rectangle's origin. * @param y The y-coordinate of the rectangle's origin. * @param w The width of the rectangle. * @param h The height of the rectangle. */ PLUTOVG_API void plutovg_canvas_stroke_rect(plutovg_canvas_t* canvas, float x, float y, float w, float h); /** * @brief Strokes a path with the current stroke settings. * * @note The current path will be cleared by this operation. * @param canvas A pointer to a `plutovg_canvas_t` object. * @param path The `plutovg_path_t` object. */ PLUTOVG_API void plutovg_canvas_stroke_path(plutovg_canvas_t* canvas, const plutovg_path_t* path); /** * @brief Intersects the current clipping region with a rectangle according to the current fill rule. * * @note The current path will be cleared by this operation. * @param canvas A pointer to a `plutovg_canvas_t` object. * @param x The x-coordinate of the rectangle's origin. * @param y The y-coordinate of the rectangle's origin. * @param w The width of the rectangle. * @param h The height of the rectangle. */ PLUTOVG_API void plutovg_canvas_clip_rect(plutovg_canvas_t* canvas, float x, float y, float w, float h); /** * @brief Intersects the current clipping region with a path according to the current fill rule. * * @note The current path will be cleared by this operation. * @param canvas A pointer to a `plutovg_canvas_t` object. * @param path The `plutovg_path_t` object. */ PLUTOVG_API void plutovg_canvas_clip_path(plutovg_canvas_t* canvas, const plutovg_path_t* path); /** * @brief Adds a glyph to the current path at the specified origin. * * @param canvas A pointer to a `plutovg_canvas_t` object. * @param codepoint The glyph codepoint. * @param x The x-coordinate of the origin. * @param y The y-coordinate of the origin. * @return The advance width of the glyph. */ PLUTOVG_API float plutovg_canvas_add_glyph(plutovg_canvas_t* canvas, plutovg_codepoint_t codepoint, float x, float y); /** * @brief Adds text to the current path at the specified origin. * * @param canvas A pointer to a `plutovg_canvas_t` object. * @param text The text data. * @param length The length of the text data, or -1 if null-terminated. * @param encoding The encoding of the text data. * @param x The x-coordinate of the origin. * @param y The y-coordinate of the origin. * @return The total advance width of the text. */ PLUTOVG_API float plutovg_canvas_add_text(plutovg_canvas_t* canvas, const void* text, int length, plutovg_text_encoding_t encoding, float x, float y); /** * @brief Fills a text at the specified origin. * * @note The current path will be cleared by this operation. * @param canvas A pointer to a `plutovg_canvas_t` object. * @param text The text data. * @param length The length of the text data, or -1 if null-terminated. * @param encoding The encoding of the text data. * @param x The x-coordinate of the origin. * @param y The y-coordinate of the origin. * @return The total advance width of the text. */ PLUTOVG_API float plutovg_canvas_fill_text(plutovg_canvas_t* canvas, const void* text, int length, plutovg_text_encoding_t encoding, float x, float y); /** * @brief Strokes a text at the specified origin. * * @note The current path will be cleared by this operation. * @param canvas A pointer to a `plutovg_canvas_t` object. * @param text The text data. * @param length The length of the text data, or -1 if null-terminated. * @param encoding The encoding of the text data. * @param x The x-coordinate of the origin. * @param y The y-coordinate of the origin. * @return The total advance width of the text. */ PLUTOVG_API float plutovg_canvas_stroke_text(plutovg_canvas_t* canvas, const void* text, int length, plutovg_text_encoding_t encoding, float x, float y); /** * @brief Intersects the current clipping region with text at the specified origin. * * @note The current path will be cleared by this operation. * @param canvas A pointer to a `plutovg_canvas_t` object. * @param text The text data. * @param length The length of the text data, or -1 if null-terminated. * @param encoding The encoding of the text data. * @param x The x-coordinate of the origin. * @param y The y-coordinate of the origin. * @return The total advance width of the text. */ PLUTOVG_API float plutovg_canvas_clip_text(plutovg_canvas_t* canvas, const void* text, int length, plutovg_text_encoding_t encoding, float x, float y); /** * @brief Retrieves font metrics for the current font. * * @param canvas A pointer to a `plutovg_canvas_t` object. * @param ascent The ascent of the font. * @param descent The descent of the font. * @param line_gap The line gap of the font. * @param extents The bounding box of the font. */ PLUTOVG_API void plutovg_canvas_font_metrics(const plutovg_canvas_t* canvas, float* ascent, float* descent, float* line_gap, plutovg_rect_t* extents); /** * @brief Retrieves metrics for a specific glyph. * * @param canvas A pointer to a `plutovg_canvas_t` object. * @param codepoint The glyph codepoint. * @param advance_width The advance width of the glyph. * @param left_side_bearing The left side bearing of the glyph. * @param extents The bounding box of the glyph. */ PLUTOVG_API void plutovg_canvas_glyph_metrics(plutovg_canvas_t* canvas, plutovg_codepoint_t codepoint, float* advance_width, float* left_side_bearing, plutovg_rect_t* extents); /** * @brief Retrieves the extents of a text. * * @param canvas A pointer to a `plutovg_canvas_t` object. * @param text The text data. * @param length The length of the text data, or -1 if null-terminated. * @param encoding The encoding of the text data. * @param extents The bounding box of the text. * @return The total advance width of the text. */ PLUTOVG_API float plutovg_canvas_text_extents(plutovg_canvas_t* canvas, const void* text, int length, plutovg_text_encoding_t encoding, plutovg_rect_t* extents); #ifdef __cplusplus } #endif #endif // PLUTOVG_H sammycage-plutovg-5695a71/meson.build000066400000000000000000000042621510714322500176070ustar00rootroot00000000000000project('plutovg', 'c', version: '1.3.2', license: 'MIT', meson_version: '>=1.3.0', default_options: ['c_std=gnu11'] ) plutovg_deps = [] plutovg_compile_args = [] cc = meson.get_compiler('c') plutovg_c_args = ['-DPLUTOVG_BUILD'] plutovg_c_args += cc.get_supported_arguments( '-Wno-sign-compare', '-Wno-unused-function' ) if get_option('font-face-cache-load').disabled() plutovg_c_args += ['-DPLUTOVG_DISABLE_FONT_FACE_CACHE_LOAD'] endif if get_option('default_library') == 'static' plutovg_c_args += ['-DPLUTOVG_BUILD_STATIC'] plutovg_compile_args += ['-DPLUTOVG_BUILD_STATIC'] endif math_dep = cc.find_library('m', required: false) if math_dep.found() plutovg_deps += [math_dep] endif threads_dep = dependency('threads', required: false) if threads_dep.found() plutovg_deps += [threads_dep] endif stdthreads_dep = cc.find_library('stdthreads', required: false) if stdthreads_dep.found() plutovg_deps += [stdthreads_dep] endif if cc.check_header('threads.h') plutovg_c_args += ['-DHAVE_THREADS_H'] endif plutovg_sources = [ 'source/plutovg-blend.c', 'source/plutovg-canvas.c', 'source/plutovg-font.c', 'source/plutovg-matrix.c', 'source/plutovg-paint.c', 'source/plutovg-path.c', 'source/plutovg-rasterize.c', 'source/plutovg-surface.c', 'source/plutovg-ft-math.c', 'source/plutovg-ft-raster.c', 'source/plutovg-ft-stroker.c' ] plutovg_lib = library('plutovg', plutovg_sources, include_directories: include_directories('include', 'source'), dependencies: plutovg_deps, version: meson.project_version(), c_args: plutovg_c_args, gnu_symbol_visibility: 'hidden', install: true ) plutovg_dep = declare_dependency( link_with: plutovg_lib, include_directories: include_directories('include'), compile_args: plutovg_compile_args ) meson.override_dependency('plutovg', plutovg_dep) install_headers('include/plutovg.h', subdir: 'plutovg') if not get_option('examples').disabled() subdir('examples') endif pkgmod = import('pkgconfig') pkgmod.generate(plutovg_lib, name: 'PlutoVG', description: 'Tiny 2D vector graphics library', filebase: 'plutovg', subdirs: 'plutovg' ) sammycage-plutovg-5695a71/meson_options.txt000066400000000000000000000004011510714322500210710ustar00rootroot00000000000000option('examples', type : 'feature', value : 'auto') option('tests', type : 'feature', value : 'auto') option('font-face-cache-load', type : 'feature', value : 'auto', description : 'Enable loading font face cache from files and directories' ) sammycage-plutovg-5695a71/smiley.png000066400000000000000000000143701510714322500174560ustar00rootroot00000000000000PNG  IHDR<qIDATx^c!0!0dBq4! ) OyJ" ş AbQ070ՁXJCi5 Ea ob_FRz T# ]}@Nh1a1#]L4 9 Md (! tU9PObᒶz%h b}J#EXAZXy@hd`2@3c' U9)x m&,PV t? f&%>V655@4 EID o0|õk r=m@|a4@m@H F+zt ;Av3WUe zhڢ~}p,bvo;vJ5>( }}!MTC'O00̞ս)I*t]5@lnA-Nb$.rB< h=-J g00րC1mA&h 䃶tP9Ua;"DGS3$`;14k@ 0'%VC Dw70,Uo)ԪpSd k?0aOY5;ERBODX1&ᓧ@'6`@SRJ#@C 688zZ*; W`x t1%IJGOT ÛGpA=F>gB-kv sE6xH `QϠ9C͋@B&2EOT C.DAc.+V00DD00dp vd$pYï_??@͛JaաFb^@;O; 2:FYkX/4aI Itm@,*b aІtPLW`@wЁ.B$tCAg~DD`nLsC!l:u =awi6J/=:>Tlka &,FӉAшiSJx6( T~gD W+C]<i$`w phK\$aAAKbI*Jpi/x h'e+5*rWyY>3cL=1x0$,Њ *RWE9l88YjBXT& %0P L9ԭENXS:àAA >> e\M؈:h ԭ@aЂi%O zȱqx( xc 5 Bxh<^b`Ǽ3P|6ytJeMT #0`E 9aa,ȹ!p=xBNXSFh`i9a1F*GT¡y4GCGZbV  %aV j y}4a&,Cxc>3t9߿ ymm@0pq4(@a 64Fr B(0ݒ/_lu0%捦HBgLfІOD%,!Ey ^? x2Vo`o9u;X]U [jgfu:3 wvV lˎ=9Sr1<$,:s&ʂ %S֭ .&.jS wcLM8IGCjPRYSdW]EAWbnHhua=JoN<^B$?Uu4p?Eu<Ёqt)eqߏ}u2qVV9&?v f.O魸FFXV#9 K=a%=iӰ_lhHi[s) [P--ŔmE72Gx F] \m ./B{n+Wb˄ G4n%@!X@J4⍑ˀC:}?pO(;(Lpuab?(P!3gbiff Am?@Ք0F3ZV@%=qWRe , {GG Ac 8Lq؁j$Plf"VjQPOkUW6qv.{;)+gx`:0`2؇@0o^[G&MM\艫{Xqr2|5Ce- 4J-jE e"݃m7Q'WF`Av xdP Ԇ܉kb*p%*PbF<| k@'Dɡ^ *p'.Sׯ=F4((\%PG T%% :ך5ԍF܉ d?(^#h e䰦VCV}z(a1 A^"/F9 8ǹ@7knu҉ T-}1hZC D.xt"l))?D㸖( wO X^DԮY#D1 0X-qRpU#h'03,\ABjLӠg`;GpPf]E:[:`0X;aJ`Z{Evh~:pɁc[8U6P*:Ϗ?h5Gq כXU{o(t qkPɁ jS@q3 1 t=l ԠUo_P! q>4OTQtК3SRSbDhiXQBt2027H~Fp/aEPtv OM1AQy80Ao/Ci)ȟX\JTn@GC5a *X(]b#g5?A/]w k̃ؠ@AR|&10Wq'P=/a@k@'b~@ퟁ*h݉ e.MN1gNہjAg~V 5TAUd0/gn/s-uaiJ"X1 cd8jCNr}RC}H@ 040U`w-SF4^ ?05 ZtW2 tpIGq'+`jwҠs3Y9ͤA+lO i$,͙# 43#tta F%4:P54Ka\0֓0p=}hNH6#bPҼH|^PM02P%(  b!o IENDB`sammycage-plutovg-5695a71/source/000077500000000000000000000000001510714322500167415ustar00rootroot00000000000000sammycage-plutovg-5695a71/source/FTL.TXT000066400000000000000000000150761510714322500200000ustar00rootroot00000000000000 The FreeType Project LICENSE ---------------------------- 2006-Jan-27 Copyright 1996-2002, 2006 by David Turner, Robert Wilhelm, and Werner Lemberg Introduction ============ The FreeType Project is distributed in several archive packages; some of them may contain, in addition to the FreeType font engine, various tools and contributions which rely on, or relate to, the FreeType Project. This license applies to all files found in such packages, and which do not fall under their own explicit license. The license affects thus the FreeType font engine, the test programs, documentation and makefiles, at the very least. This license was inspired by the BSD, Artistic, and IJG (Independent JPEG Group) licenses, which all encourage inclusion and use of free software in commercial and freeware products alike. As a consequence, its main points are that: o We don't promise that this software works. However, we will be interested in any kind of bug reports. (`as is' distribution) o You can use this software for whatever you want, in parts or full form, without having to pay us. (`royalty-free' usage) o You may not pretend that you wrote this software. If you use it, or only parts of it, in a program, you must acknowledge somewhere in your documentation that you have used the FreeType code. (`credits') We specifically permit and encourage the inclusion of this software, with or without modifications, in commercial products. We disclaim all warranties covering The FreeType Project and assume no liability related to The FreeType Project. Finally, many people asked us for a preferred form for a credit/disclaimer to use in compliance with this license. We thus encourage you to use the following text: """ Portions of this software are copyright � The FreeType Project (www.freetype.org). All rights reserved. """ Please replace with the value from the FreeType version you actually use. Legal Terms =========== 0. Definitions -------------- Throughout this license, the terms `package', `FreeType Project', and `FreeType archive' refer to the set of files originally distributed by the authors (David Turner, Robert Wilhelm, and Werner Lemberg) as the `FreeType Project', be they named as alpha, beta or final release. `You' refers to the licensee, or person using the project, where `using' is a generic term including compiling the project's source code as well as linking it to form a `program' or `executable'. This program is referred to as `a program using the FreeType engine'. This license applies to all files distributed in the original FreeType Project, including all source code, binaries and documentation, unless otherwise stated in the file in its original, unmodified form as distributed in the original archive. If you are unsure whether or not a particular file is covered by this license, you must contact us to verify this. The FreeType Project is copyright (C) 1996-2000 by David Turner, Robert Wilhelm, and Werner Lemberg. All rights reserved except as specified below. 1. No Warranty -------------- THE FREETYPE PROJECT IS PROVIDED `AS IS' WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT WILL ANY OF THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY DAMAGES CAUSED BY THE USE OR THE INABILITY TO USE, OF THE FREETYPE PROJECT. 2. Redistribution ----------------- This license grants a worldwide, royalty-free, perpetual and irrevocable right and license to use, execute, perform, compile, display, copy, create derivative works of, distribute and sublicense the FreeType Project (in both source and object code forms) and derivative works thereof for any purpose; and to authorize others to exercise some or all of the rights granted herein, subject to the following conditions: o Redistribution of source code must retain this license file (`FTL.TXT') unaltered; any additions, deletions or changes to the original files must be clearly indicated in accompanying documentation. The copyright notices of the unaltered, original files must be preserved in all copies of source files. o Redistribution in binary form must provide a disclaimer that states that the software is based in part of the work of the FreeType Team, in the distribution documentation. We also encourage you to put an URL to the FreeType web page in your documentation, though this isn't mandatory. These conditions apply to any software derived from or based on the FreeType Project, not just the unmodified files. If you use our work, you must acknowledge us. However, no fee need be paid to us. 3. Advertising -------------- Neither the FreeType authors and contributors nor you shall use the name of the other for commercial, advertising, or promotional purposes without specific prior written permission. We suggest, but do not require, that you use one or more of the following phrases to refer to this software in your documentation or advertising materials: `FreeType Project', `FreeType Engine', `FreeType library', or `FreeType Distribution'. As you have not signed this license, you are not required to accept it. However, as the FreeType Project is copyrighted material, only this license, or another one contracted with the authors, grants you the right to use, distribute, and modify it. Therefore, by using, distributing, or modifying the FreeType Project, you indicate that you understand and accept all the terms of this license. 4. Contacts ----------- There are two mailing lists related to FreeType: o freetype@nongnu.org Discusses general use and applications of FreeType, as well as future and wanted additions to the library and distribution. If you are looking for support, start in this list if you haven't found anything to help you in the documentation. o freetype-devel@nongnu.org Discusses bugs, as well as engine internals, design issues, specific licenses, porting, etc. Our home page can be found at http://www.freetype.org sammycage-plutovg-5695a71/source/plutovg-blend.c000066400000000000000000001042611510714322500216730ustar00rootroot00000000000000#include "plutovg-private.h" #include "plutovg-utils.h" #include #include #define COLOR_TABLE_SIZE 1024 typedef struct { plutovg_matrix_t matrix; plutovg_spread_method_t spread; uint32_t colortable[COLOR_TABLE_SIZE]; union { struct { float x1, y1; float x2, y2; } linear; struct { float cx, cy, cr; float fx, fy, fr; } radial; } values; } gradient_data_t; typedef struct { plutovg_matrix_t matrix; uint8_t* data; int width; int height; int stride; int const_alpha; } texture_data_t; typedef struct { float dx; float dy; float l; float off; } linear_gradient_values_t; typedef struct { float dx; float dy; float dr; float sqrfr; float a; bool extended; } radial_gradient_values_t; static inline uint32_t premultiply_color_with_opacity(const plutovg_color_t* color, float opacity) { uint32_t alpha = lroundf(color->a * opacity * 255); uint32_t pr = lroundf(color->r * alpha); uint32_t pg = lroundf(color->g * alpha); uint32_t pb = lroundf(color->b * alpha); return (alpha << 24) | (pr << 16) | (pg << 8) | (pb); } static inline uint32_t INTERPOLATE_PIXEL(uint32_t x, uint32_t a, uint32_t y, uint32_t b) { uint32_t t = (x & 0xff00ff) * a + (y & 0xff00ff) * b; t = (t + ((t >> 8) & 0xff00ff) + 0x800080) >> 8; t &= 0xff00ff; x = ((x >> 8) & 0xff00ff) * a + ((y >> 8) & 0xff00ff) * b; x = (x + ((x >> 8) & 0xff00ff) + 0x800080); x &= 0xff00ff00; x |= t; return x; } static inline uint32_t BYTE_MUL(uint32_t x, uint32_t a) { uint32_t t = (x & 0xff00ff) * a; t = (t + ((t >> 8) & 0xff00ff) + 0x800080) >> 8; t &= 0xff00ff; x = ((x >> 8) & 0xff00ff) * a; x = (x + ((x >> 8) & 0xff00ff) + 0x800080); x &= 0xff00ff00; x |= t; return x; } #ifdef __SSE2__ #include void plutovg_memfill32(unsigned int* dest, int length, unsigned int value) { __m128i vector_data = _mm_set_epi32(value, value, value, value); while(length && ((uintptr_t)dest & 0xf)) { *dest++ = value; length--; } while(length >= 32) { _mm_store_si128((__m128i*)(dest), vector_data); _mm_store_si128((__m128i*)(dest + 4), vector_data); _mm_store_si128((__m128i*)(dest + 8), vector_data); _mm_store_si128((__m128i*)(dest + 12), vector_data); _mm_store_si128((__m128i*)(dest + 16), vector_data); _mm_store_si128((__m128i*)(dest + 20), vector_data); _mm_store_si128((__m128i*)(dest + 24), vector_data); _mm_store_si128((__m128i*)(dest + 28), vector_data); dest += 32; length -= 32; } if(length >= 16) { _mm_store_si128((__m128i*)(dest), vector_data); _mm_store_si128((__m128i*)(dest + 4), vector_data); _mm_store_si128((__m128i*)(dest + 8), vector_data); _mm_store_si128((__m128i*)(dest + 12), vector_data); dest += 16; length -= 16; } if(length >= 8) { _mm_store_si128((__m128i*)(dest), vector_data); _mm_store_si128((__m128i*)(dest + 4), vector_data); dest += 8; length -= 8; } if(length >= 4) { _mm_store_si128((__m128i*)(dest), vector_data); dest += 4; length -= 4; } while(length) { *dest++ = value; length--; } } #else void plutovg_memfill32(unsigned int* dest, int length, unsigned int value) { while(length--) { *dest++ = value; } } #endif // __SSE2__ static inline int gradient_clamp(const gradient_data_t* gradient, int ipos) { if(gradient->spread == PLUTOVG_SPREAD_METHOD_REPEAT) { ipos = ipos % COLOR_TABLE_SIZE; ipos = ipos < 0 ? COLOR_TABLE_SIZE + ipos : ipos; } else if(gradient->spread == PLUTOVG_SPREAD_METHOD_REFLECT) { const int limit = COLOR_TABLE_SIZE * 2; ipos = ipos % limit; ipos = ipos < 0 ? limit + ipos : ipos; ipos = ipos >= COLOR_TABLE_SIZE ? limit - 1 - ipos : ipos; } else { if(ipos < 0) { ipos = 0; } else if(ipos >= COLOR_TABLE_SIZE) { ipos = COLOR_TABLE_SIZE - 1; } } return ipos; } #define FIXPT_BITS 8 #define FIXPT_SIZE (1 << FIXPT_BITS) static inline uint32_t gradient_pixel_fixed(const gradient_data_t* gradient, int fixed_pos) { int ipos = (fixed_pos + (FIXPT_SIZE / 2)) >> FIXPT_BITS; return gradient->colortable[gradient_clamp(gradient, ipos)]; } static inline uint32_t gradient_pixel(const gradient_data_t* gradient, float pos) { int ipos = (int)(pos * (COLOR_TABLE_SIZE - 1) + 0.5f); return gradient->colortable[gradient_clamp(gradient, ipos)]; } static void fetch_linear_gradient(uint32_t* buffer, const linear_gradient_values_t* v, const gradient_data_t* gradient, int y, int x, int length) { float t, inc; float rx = 0, ry = 0; if(v->l == 0.f) { t = inc = 0; } else { rx = gradient->matrix.c * (y + 0.5f) + gradient->matrix.a * (x + 0.5f) + gradient->matrix.e; ry = gradient->matrix.d * (y + 0.5f) + gradient->matrix.b * (x + 0.5f) + gradient->matrix.f; t = v->dx * rx + v->dy * ry + v->off; inc = v->dx * gradient->matrix.a + v->dy * gradient->matrix.b; t *= (COLOR_TABLE_SIZE - 1); inc *= (COLOR_TABLE_SIZE - 1); } const uint32_t* end = buffer + length; if(inc > -1e-5f && inc < 1e-5f) { plutovg_memfill32(buffer, length, gradient_pixel_fixed(gradient, (int)(t * FIXPT_SIZE))); } else { if(t + inc * length < (float)(INT_MAX >> (FIXPT_BITS + 1)) && t + inc * length > (float)(INT_MIN >> (FIXPT_BITS + 1))) { int t_fixed = (int)(t * FIXPT_SIZE); int inc_fixed = (int)(inc * FIXPT_SIZE); while(buffer < end) { *buffer = gradient_pixel_fixed(gradient, t_fixed); t_fixed += inc_fixed; ++buffer; } } else { while(buffer < end) { *buffer = gradient_pixel(gradient, t / COLOR_TABLE_SIZE); t += inc; ++buffer; } } } } static void fetch_radial_gradient(uint32_t* buffer, const radial_gradient_values_t* v, const gradient_data_t* gradient, int y, int x, int length) { if(v->a == 0.f) { plutovg_memfill32(buffer, length, 0); return; } float rx = gradient->matrix.c * (y + 0.5f) + gradient->matrix.e + gradient->matrix.a * (x + 0.5f); float ry = gradient->matrix.d * (y + 0.5f) + gradient->matrix.f + gradient->matrix.b * (x + 0.5f); rx -= gradient->values.radial.fx; ry -= gradient->values.radial.fy; float inv_a = 1.f / (2.f * v->a); float delta_rx = gradient->matrix.a; float delta_ry = gradient->matrix.b; float b = 2 * (v->dr * gradient->values.radial.fr + rx * v->dx + ry * v->dy); float delta_b = 2 * (delta_rx * v->dx + delta_ry * v->dy); float b_delta_b = 2 * b * delta_b; float delta_b_delta_b = 2 * delta_b * delta_b; float bb = b * b; float delta_bb = delta_b * delta_b; b *= inv_a; delta_b *= inv_a; float rxrxryry = rx * rx + ry * ry; float delta_rxrxryry = delta_rx * delta_rx + delta_ry * delta_ry; float rx_plus_ry = 2 * (rx * delta_rx + ry * delta_ry); float delta_rx_plus_ry = 2 * delta_rxrxryry; inv_a *= inv_a; float det = (bb - 4 * v->a * (v->sqrfr - rxrxryry)) * inv_a; float delta_det = (b_delta_b + delta_bb + 4 * v->a * (rx_plus_ry + delta_rxrxryry)) * inv_a; float delta_delta_det = (delta_b_delta_b + 4 * v->a * delta_rx_plus_ry) * inv_a; const uint32_t* end = buffer + length; if(v->extended) { while(buffer < end) { uint32_t result = 0; if(det >= 0) { float w = sqrtf(det) - b; if(gradient->values.radial.fr + v->dr * w >= 0) { result = gradient_pixel(gradient, w); } } *buffer = result; det += delta_det; delta_det += delta_delta_det; b += delta_b; ++buffer; } } else { while(buffer < end) { *buffer++ = gradient_pixel(gradient, sqrtf(det) - b); det += delta_det; delta_det += delta_delta_det; b += delta_b; } } } static void composition_solid_clear(uint32_t* dest, int length, uint32_t color, uint32_t const_alpha) { if(const_alpha == 255) { plutovg_memfill32(dest, length, 0); } else { uint32_t ialpha = 255 - const_alpha; for(int i = 0; i < length; i++) { dest[i] = BYTE_MUL(dest[i], ialpha); } } } static void composition_solid_source(uint32_t* dest, int length, uint32_t color, uint32_t const_alpha) { if(const_alpha == 255) { plutovg_memfill32(dest, length, color); } else { uint32_t ialpha = 255 - const_alpha; color = BYTE_MUL(color, const_alpha); for(int i = 0; i < length; i++) { dest[i] = color + BYTE_MUL(dest[i], ialpha); } } } static void composition_solid_destination(uint32_t* dest, int length, uint32_t color, uint32_t const_alpha) { } static void composition_solid_source_over(uint32_t* dest, int length, uint32_t color, uint32_t const_alpha) { if(const_alpha != 255) color = BYTE_MUL(color, const_alpha); uint32_t ialpha = 255 - plutovg_alpha(color); for(int i = 0; i < length; i++) { dest[i] = color + BYTE_MUL(dest[i], ialpha); } } static void composition_solid_destination_over(uint32_t* dest, int length, uint32_t color, uint32_t const_alpha) { if(const_alpha != 255) color = BYTE_MUL(color, const_alpha); for(int i = 0; i < length; i++) { uint32_t d = dest[i]; dest[i] = d + BYTE_MUL(color, plutovg_alpha(~d)); } } static void composition_solid_source_in(uint32_t* dest, int length, uint32_t color, uint32_t const_alpha) { if(const_alpha == 255) { for(int i = 0; i < length; i++) { dest[i] = BYTE_MUL(color, plutovg_alpha(dest[i])); } } else { color = BYTE_MUL(color, const_alpha); uint32_t cia = 255 - const_alpha; for(int i = 0; i < length; i++) { uint32_t d = dest[i]; dest[i] = INTERPOLATE_PIXEL(color, plutovg_alpha(d), d, cia); } } } static void composition_solid_destination_in(uint32_t* dest, int length, uint32_t color, uint32_t const_alpha) { uint32_t a = plutovg_alpha(color); if(const_alpha != 255) a = BYTE_MUL(a, const_alpha) + 255 - const_alpha; for(int i = 0; i < length; i++) { dest[i] = BYTE_MUL(dest[i], a); } } static void composition_solid_source_out(uint32_t* dest, int length, uint32_t color, uint32_t const_alpha) { if(const_alpha == 255) { for(int i = 0; i < length; i++) { dest[i] = BYTE_MUL(color, plutovg_alpha(~dest[i])); } } else { color = BYTE_MUL(color, const_alpha); uint32_t cia = 255 - const_alpha; for(int i = 0; i < length; i++) { uint32_t d = dest[i]; dest[i] = INTERPOLATE_PIXEL(color, plutovg_alpha(~d), d, cia); } } } static void composition_solid_destination_out(uint32_t* dest, int length, uint32_t color, uint32_t const_alpha) { uint32_t a = plutovg_alpha(~color); if(const_alpha != 255) a = BYTE_MUL(a, const_alpha) + 255 - const_alpha; for(int i = 0; i < length; i++) { dest[i] = BYTE_MUL(dest[i], a); } } static void composition_solid_source_atop(uint32_t* dest, int length, uint32_t color, uint32_t const_alpha) { if(const_alpha != 255) color = BYTE_MUL(color, const_alpha); uint32_t sia = plutovg_alpha(~color); for(int i = 0; i < length; i++) { uint32_t d = dest[i]; dest[i] = INTERPOLATE_PIXEL(color, plutovg_alpha(d), d, sia); } } static void composition_solid_destination_atop(uint32_t* dest, int length, uint32_t color, uint32_t const_alpha) { uint32_t a = plutovg_alpha(color); if(const_alpha != 255) { color = BYTE_MUL(color, const_alpha); a = plutovg_alpha(color) + 255 - const_alpha; } for(int i = 0; i < length; i++) { uint32_t d = dest[i]; dest[i] = INTERPOLATE_PIXEL(d, a, color, plutovg_alpha(~d)); } } static void composition_solid_xor(uint32_t* dest, int length, uint32_t color, uint32_t const_alpha) { if(const_alpha != 255) color = BYTE_MUL(color, const_alpha); uint32_t sia = plutovg_alpha(~color); for(int i = 0; i < length; i++) { uint32_t d = dest[i]; dest[i] = INTERPOLATE_PIXEL(color, plutovg_alpha(~d), d, sia); } } typedef void(*composition_solid_function_t)(uint32_t* dest, int length, uint32_t color, uint32_t const_alpha); static const composition_solid_function_t composition_solid_table[] = { composition_solid_clear, composition_solid_source, composition_solid_destination, composition_solid_source_over, composition_solid_destination_over, composition_solid_source_in, composition_solid_destination_in, composition_solid_source_out, composition_solid_destination_out, composition_solid_source_atop, composition_solid_destination_atop, composition_solid_xor }; static void composition_clear(uint32_t* dest, int length, const uint32_t* src, uint32_t const_alpha) { if(const_alpha == 255) { plutovg_memfill32(dest, length, 0); } else { uint32_t ialpha = 255 - const_alpha; for(int i = 0; i < length; i++) { dest[i] = BYTE_MUL(dest[i], ialpha); } } } static void composition_source(uint32_t* dest, int length, const uint32_t* src, uint32_t const_alpha) { if(const_alpha == 255) { memcpy(dest, src, length * sizeof(uint32_t)); } else { uint32_t ialpha = 255 - const_alpha; for(int i = 0; i < length; i++) { dest[i] = INTERPOLATE_PIXEL(src[i], const_alpha, dest[i], ialpha); } } } static void composition_destination(uint32_t* dest, int length, const uint32_t* src, uint32_t const_alpha) { } static void composition_source_over(uint32_t* dest, int length, const uint32_t* src, uint32_t const_alpha) { if(const_alpha == 255) { for(int i = 0; i < length; i++) { uint32_t s = src[i]; if(s >= 0xff000000) { dest[i] = s; } else if (s != 0) { dest[i] = s + BYTE_MUL(dest[i], plutovg_alpha(~s)); } } } else { for(int i = 0; i < length; i++) { uint32_t s = BYTE_MUL(src[i], const_alpha); dest[i] = s + BYTE_MUL(dest[i], plutovg_alpha(~s)); } } } static void composition_destination_over(uint32_t* dest, int length, const uint32_t* src, uint32_t const_alpha) { if(const_alpha == 255) { for(int i = 0; i < length; i++) { uint32_t d = dest[i]; dest[i] = d + BYTE_MUL(src[i], plutovg_alpha(~d)); } } else { for(int i = 0; i < length; i++) { uint32_t d = dest[i]; uint32_t s = BYTE_MUL(src[i], const_alpha); dest[i] = d + BYTE_MUL(s, plutovg_alpha(~d)); } } } static void composition_source_in(uint32_t* dest, int length, const uint32_t* src, uint32_t const_alpha) { if(const_alpha == 255) { for(int i = 0; i < length; i++) { dest[i] = BYTE_MUL(src[i], plutovg_alpha(dest[i])); } } else { uint32_t cia = 255 - const_alpha; for(int i = 0; i < length; i++) { uint32_t d = dest[i]; uint32_t s = BYTE_MUL(src[i], const_alpha); dest[i] = INTERPOLATE_PIXEL(s, plutovg_alpha(d), d, cia); } } } static void composition_destination_in(uint32_t* dest, int length, const uint32_t* src, uint32_t const_alpha) { if(const_alpha == 255) { for(int i = 0; i < length; i++) { dest[i] = BYTE_MUL(dest[i], plutovg_alpha(src[i])); } } else { uint32_t cia = 255 - const_alpha; for(int i = 0; i < length; i++) { uint32_t a = BYTE_MUL(plutovg_alpha(src[i]), const_alpha) + cia; dest[i] = BYTE_MUL(dest[i], a); } } } static void composition_source_out(uint32_t* dest, int length, const uint32_t* src, uint32_t const_alpha) { if(const_alpha == 255) { for(int i = 0; i < length; i++) { dest[i] = BYTE_MUL(src[i], plutovg_alpha(~dest[i])); } } else { uint32_t cia = 255 - const_alpha; for(int i = 0; i < length; i++) { uint32_t s = BYTE_MUL(src[i], const_alpha); uint32_t d = dest[i]; dest[i] = INTERPOLATE_PIXEL(s, plutovg_alpha(~d), d, cia); } } } static void composition_destination_out(uint32_t* dest, int length, const uint32_t* src, uint32_t const_alpha) { if(const_alpha == 255) { for(int i = 0; i < length; i++) { dest[i] = BYTE_MUL(dest[i], plutovg_alpha(~src[i])); } } else { uint32_t cia = 255 - const_alpha; for(int i = 0; i < length; i++) { uint32_t sia = BYTE_MUL(plutovg_alpha(~src[i]), const_alpha) + cia; dest[i] = BYTE_MUL(dest[i], sia); } } } static void composition_source_atop(uint32_t* dest, int length, const uint32_t* src, uint32_t const_alpha) { if(const_alpha == 255) { for(int i = 0; i < length; i++) { uint32_t s = src[i]; uint32_t d = dest[i]; dest[i] = INTERPOLATE_PIXEL(s, plutovg_alpha(d), d, plutovg_alpha(~s)); } } else { for(int i = 0; i < length; i++) { uint32_t s = BYTE_MUL(src[i], const_alpha); uint32_t d = dest[i]; dest[i] = INTERPOLATE_PIXEL(s, plutovg_alpha(d), d, plutovg_alpha(~s)); } } } static void composition_destination_atop(uint32_t* dest, int length, const uint32_t* src, uint32_t const_alpha) { if(const_alpha == 255) { for(int i = 0; i < length; i++) { uint32_t s = src[i]; uint32_t d = dest[i]; dest[i] = INTERPOLATE_PIXEL(d, plutovg_alpha(s), s, plutovg_alpha(~d)); } } else { uint32_t cia = 255 - const_alpha; for(int i = 0; i < length; i++) { uint32_t s = BYTE_MUL(src[i], const_alpha); uint32_t d = dest[i]; uint32_t a = plutovg_alpha(s) + cia; dest[i] = INTERPOLATE_PIXEL(d, a, s, plutovg_alpha(~d)); } } } static void composition_xor(uint32_t* dest, int length, const uint32_t* src, uint32_t const_alpha) { if(const_alpha == 255) { for(int i = 0; i < length; i++) { uint32_t d = dest[i]; uint32_t s = src[i]; dest[i] = INTERPOLATE_PIXEL(s, plutovg_alpha(~d), d, plutovg_alpha(~s)); } } else { for(int i = 0; i < length; i++) { uint32_t d = dest[i]; uint32_t s = BYTE_MUL(src[i], const_alpha); dest[i] = INTERPOLATE_PIXEL(s, plutovg_alpha(~d), d, plutovg_alpha(~s)); } } } typedef void(*composition_function_t)(uint32_t* dest, int length, const uint32_t* src, uint32_t const_alpha); static const composition_function_t composition_table[] = { composition_clear, composition_source, composition_destination, composition_source_over, composition_destination_over, composition_source_in, composition_destination_in, composition_source_out, composition_destination_out, composition_source_atop, composition_destination_atop, composition_xor }; static void blend_solid(plutovg_surface_t* surface, plutovg_operator_t op, uint32_t solid, const plutovg_span_buffer_t* span_buffer) { composition_solid_function_t func = composition_solid_table[op]; int count = span_buffer->spans.size; const plutovg_span_t* spans = span_buffer->spans.data; while(count--) { uint32_t* target = (uint32_t*)(surface->data + spans->y * surface->stride) + spans->x; func(target, spans->len, solid, spans->coverage); ++spans; } } #define BUFFER_SIZE 1024 static void blend_linear_gradient(plutovg_surface_t* surface, plutovg_operator_t op, const gradient_data_t* gradient, const plutovg_span_buffer_t* span_buffer) { composition_function_t func = composition_table[op]; unsigned int buffer[BUFFER_SIZE]; linear_gradient_values_t v; v.dx = gradient->values.linear.x2 - gradient->values.linear.x1; v.dy = gradient->values.linear.y2 - gradient->values.linear.y1; v.l = v.dx * v.dx + v.dy * v.dy; v.off = 0.f; if(v.l != 0.f) { v.dx /= v.l; v.dy /= v.l; v.off = -v.dx * gradient->values.linear.x1 - v.dy * gradient->values.linear.y1; } int count = span_buffer->spans.size; const plutovg_span_t* spans = span_buffer->spans.data; while(count--) { int length = spans->len; int x = spans->x; while(length) { int l = plutovg_min(length, BUFFER_SIZE); fetch_linear_gradient(buffer, &v, gradient, spans->y, x, l); uint32_t* target = (uint32_t*)(surface->data + spans->y * surface->stride) + x; func(target, l, buffer, spans->coverage); x += l; length -= l; } ++spans; } } static void blend_radial_gradient(plutovg_surface_t* surface, plutovg_operator_t op, const gradient_data_t* gradient, const plutovg_span_buffer_t* span_buffer) { composition_function_t func = composition_table[op]; unsigned int buffer[BUFFER_SIZE]; radial_gradient_values_t v; v.dx = gradient->values.radial.cx - gradient->values.radial.fx; v.dy = gradient->values.radial.cy - gradient->values.radial.fy; v.dr = gradient->values.radial.cr - gradient->values.radial.fr; v.sqrfr = gradient->values.radial.fr * gradient->values.radial.fr; v.a = v.dr * v.dr - v.dx * v.dx - v.dy * v.dy; v.extended = gradient->values.radial.fr != 0.f || v.a <= 0.f; int count = span_buffer->spans.size; const plutovg_span_t* spans = span_buffer->spans.data; while(count--) { int length = spans->len; int x = spans->x; while(length) { int l = plutovg_min(length, BUFFER_SIZE); fetch_radial_gradient(buffer, &v, gradient, spans->y, x, l); uint32_t* target = (uint32_t*)(surface->data + spans->y * surface->stride) + x; func(target, l, buffer, spans->coverage); x += l; length -= l; } ++spans; } } static void blend_untransformed_argb(plutovg_surface_t* surface, plutovg_operator_t op, const texture_data_t* texture, const plutovg_span_buffer_t* span_buffer) { composition_function_t func = composition_table[op]; const int image_width = texture->width; const int image_height = texture->height; int xoff = (int)(texture->matrix.e); int yoff = (int)(texture->matrix.f); int count = span_buffer->spans.size; const plutovg_span_t* spans = span_buffer->spans.data; while(count--) { int x = spans->x; int length = spans->len; int sx = xoff + x; int sy = yoff + spans->y; if(sy >= 0 && sy < image_height && sx < image_width) { if(sx < 0) { x -= sx; length += sx; sx = 0; } if(sx + length > image_width) length = image_width - sx; if(length > 0) { const int coverage = (spans->coverage * texture->const_alpha) >> 8; const uint32_t* src = (const uint32_t*)(texture->data + sy * texture->stride) + sx; uint32_t* dest = (uint32_t*)(surface->data + spans->y * surface->stride) + x; func(dest, length, src, coverage); } } ++spans; } } #define FIXED_SCALE (1 << 16) static void blend_transformed_argb(plutovg_surface_t* surface, plutovg_operator_t op, const texture_data_t* texture, const plutovg_span_buffer_t* span_buffer) { composition_function_t func = composition_table[op]; uint32_t buffer[BUFFER_SIZE]; int image_width = texture->width; int image_height = texture->height; int fdx = (int)(texture->matrix.a * FIXED_SCALE); int fdy = (int)(texture->matrix.b * FIXED_SCALE); int count = span_buffer->spans.size; const plutovg_span_t* spans = span_buffer->spans.data; while(count--) { uint32_t* target = (uint32_t*)(surface->data + spans->y * surface->stride) + spans->x; const float cx = spans->x + 0.5f; const float cy = spans->y + 0.5f; int x = (int)((texture->matrix.c * cy + texture->matrix.a * cx + texture->matrix.e) * FIXED_SCALE); int y = (int)((texture->matrix.d * cy + texture->matrix.b * cx + texture->matrix.f) * FIXED_SCALE); int length = spans->len; const int coverage = (spans->coverage * texture->const_alpha) >> 8; while(length) { int l = plutovg_min(length, BUFFER_SIZE); const uint32_t* end = buffer + l; uint32_t* b = buffer; while(b < end) { int px = x >> 16; int py = y >> 16; if((px < 0) || (px >= image_width) || (py < 0) || (py >= image_height)) { *b = 0x00000000; } else { *b = ((const uint32_t*)(texture->data + py * texture->stride))[px]; } x += fdx; y += fdy; ++b; } func(target, l, buffer, coverage); target += l; length -= l; } ++spans; } } static void blend_untransformed_tiled_argb(plutovg_surface_t* surface, plutovg_operator_t op, const texture_data_t* texture, const plutovg_span_buffer_t* span_buffer) { composition_function_t func = composition_table[op]; int image_width = texture->width; int image_height = texture->height; int xoff = (int)(texture->matrix.e) % image_width; int yoff = (int)(texture->matrix.f) % image_height; if(xoff < 0) xoff += image_width; if(yoff < 0) { yoff += image_height; } int count = span_buffer->spans.size; const plutovg_span_t* spans = span_buffer->spans.data; while(count--) { int x = spans->x; int length = spans->len; int sx = (xoff + spans->x) % image_width; int sy = (spans->y + yoff) % image_height; if(sx < 0) sx += image_width; if(sy < 0) { sy += image_height; } const int coverage = (spans->coverage * texture->const_alpha) >> 8; while(length) { int l = plutovg_min(image_width - sx, length); if(BUFFER_SIZE < l) l = BUFFER_SIZE; const uint32_t* src = (const uint32_t*)(texture->data + sy * texture->stride) + sx; uint32_t* dest = (uint32_t*)(surface->data + spans->y * surface->stride) + x; func(dest, l, src, coverage); x += l; sx += l; length -= l; if(sx >= image_width) { sx = 0; } } ++spans; } } static void blend_transformed_tiled_argb(plutovg_surface_t* surface, plutovg_operator_t op, const texture_data_t* texture, const plutovg_span_buffer_t* span_buffer) { composition_function_t func = composition_table[op]; uint32_t buffer[BUFFER_SIZE]; int image_width = texture->width; int image_height = texture->height; const int scanline_offset = texture->stride / 4; int fdx = (int)(texture->matrix.a * FIXED_SCALE); int fdy = (int)(texture->matrix.b * FIXED_SCALE); int count = span_buffer->spans.size; const plutovg_span_t* spans = span_buffer->spans.data; while(count--) { uint32_t* target = (uint32_t*)(surface->data + spans->y * surface->stride) + spans->x; const uint32_t* image_bits = (const uint32_t*)texture->data; const float cx = spans->x + 0.5f; const float cy = spans->y + 0.5f; int x = (int)((texture->matrix.c * cy + texture->matrix.a * cx + texture->matrix.e) * FIXED_SCALE); int y = (int)((texture->matrix.d * cy + texture->matrix.b * cx + texture->matrix.f) * FIXED_SCALE); const int coverage = (spans->coverage * texture->const_alpha) >> 8; int length = spans->len; while(length) { int l = plutovg_min(length, BUFFER_SIZE); const uint32_t* end = buffer + l; uint32_t* b = buffer; while(b < end) { int px = x >> 16; int py = y >> 16; px %= image_width; py %= image_height; if(px < 0) px += image_width; if(py < 0) py += image_height; int y_offset = py * scanline_offset; assert(px >= 0 && px < image_width); assert(py >= 0 && py < image_height); *b = image_bits[y_offset + px]; x += fdx; y += fdy; ++b; } func(target, l, buffer, coverage); target += l; length -= l; } ++spans; } } static void plutovg_blend_color(plutovg_canvas_t* canvas, const plutovg_color_t* color, const plutovg_span_buffer_t* span_buffer) { plutovg_state_t* state = canvas->state; uint32_t solid = premultiply_color_with_opacity(color, state->opacity); uint32_t alpha = plutovg_alpha(solid); if(alpha == 255 && state->op == PLUTOVG_OPERATOR_SRC_OVER) { blend_solid(canvas->surface, PLUTOVG_OPERATOR_SRC, solid, span_buffer); } else { blend_solid(canvas->surface, state->op, solid, span_buffer); } } static void plutovg_blend_gradient(plutovg_canvas_t* canvas, const plutovg_gradient_paint_t* gradient, const plutovg_span_buffer_t* span_buffer) { if(gradient->nstops == 0) return; plutovg_state_t* state = canvas->state; gradient_data_t data; data.spread = gradient->spread; data.matrix = gradient->matrix; plutovg_matrix_multiply(&data.matrix, &data.matrix, &state->matrix); if(!plutovg_matrix_invert(&data.matrix, &data.matrix)) return; int i, pos = 0, nstops = gradient->nstops; const plutovg_gradient_stop_t *curr, *next, *start, *last; uint32_t curr_color, next_color, last_color; uint32_t dist, idist; float delta, t, incr, fpos; float opacity = state->opacity; start = gradient->stops; curr = start; curr_color = premultiply_color_with_opacity(&curr->color, opacity); data.colortable[pos++] = curr_color; incr = 1.0f / COLOR_TABLE_SIZE; fpos = 1.5f * incr; while(fpos <= curr->offset) { data.colortable[pos] = data.colortable[pos - 1]; ++pos; fpos += incr; } for(i = 0; i < nstops - 1; i++) { curr = (start + i); next = (start + i + 1); if(curr->offset == next->offset) continue; delta = 1.f / (next->offset - curr->offset); next_color = premultiply_color_with_opacity(&next->color, opacity); while(fpos < next->offset && pos < COLOR_TABLE_SIZE) { t = (fpos - curr->offset) * delta; dist = (uint32_t)(255 * t); idist = 255 - dist; data.colortable[pos] = INTERPOLATE_PIXEL(curr_color, idist, next_color, dist); ++pos; fpos += incr; } curr_color = next_color; } last = start + nstops - 1; last_color = premultiply_color_with_opacity(&last->color, opacity); for(; pos < COLOR_TABLE_SIZE; ++pos) { data.colortable[pos] = last_color; } if(gradient->type == PLUTOVG_GRADIENT_TYPE_LINEAR) { data.values.linear.x1 = gradient->values[0]; data.values.linear.y1 = gradient->values[1]; data.values.linear.x2 = gradient->values[2]; data.values.linear.y2 = gradient->values[3]; blend_linear_gradient(canvas->surface, state->op, &data, span_buffer); } else { data.values.radial.cx = gradient->values[0]; data.values.radial.cy = gradient->values[1]; data.values.radial.cr = gradient->values[2]; data.values.radial.fx = gradient->values[3]; data.values.radial.fy = gradient->values[4]; data.values.radial.fr = gradient->values[5]; blend_radial_gradient(canvas->surface, state->op, &data, span_buffer); } } static void plutovg_blend_texture(plutovg_canvas_t* canvas, const plutovg_texture_paint_t* texture, const plutovg_span_buffer_t* span_buffer) { if(texture->surface == NULL) return; plutovg_state_t* state = canvas->state; texture_data_t data; data.matrix = texture->matrix; data.data = texture->surface->data; data.width = texture->surface->width; data.height = texture->surface->height; data.stride = texture->surface->stride; data.const_alpha = lroundf(state->opacity * texture->opacity * 256); plutovg_matrix_multiply(&data.matrix, &data.matrix, &state->matrix); if(!plutovg_matrix_invert(&data.matrix, &data.matrix)) return; const plutovg_matrix_t* matrix = &data.matrix; if(matrix->a == 1 && matrix->b == 0 && matrix->c == 0 && matrix->d == 1) { if(texture->type == PLUTOVG_TEXTURE_TYPE_PLAIN) { blend_untransformed_argb(canvas->surface, state->op, &data, span_buffer); } else { blend_untransformed_tiled_argb(canvas->surface, state->op, &data, span_buffer); } } else { if(texture->type == PLUTOVG_TEXTURE_TYPE_PLAIN) { blend_transformed_argb(canvas->surface, state->op, &data, span_buffer); } else { blend_transformed_tiled_argb(canvas->surface, state->op, &data, span_buffer); } } } void plutovg_blend(plutovg_canvas_t* canvas, const plutovg_span_buffer_t* span_buffer) { if(span_buffer->spans.size == 0) return; if(canvas->state->paint == NULL) { plutovg_blend_color(canvas, &canvas->state->color, span_buffer); return; } plutovg_paint_t* paint = canvas->state->paint; if(paint->type == PLUTOVG_PAINT_TYPE_COLOR) { plutovg_solid_paint_t* solid = (plutovg_solid_paint_t*)(paint); plutovg_blend_color(canvas, &solid->color, span_buffer); } else if(paint->type == PLUTOVG_PAINT_TYPE_GRADIENT) { plutovg_gradient_paint_t* gradient = (plutovg_gradient_paint_t*)(paint); plutovg_blend_gradient(canvas, gradient, span_buffer); } else { plutovg_texture_paint_t* texture = (plutovg_texture_paint_t*)(paint); plutovg_blend_texture(canvas, texture, span_buffer); } } sammycage-plutovg-5695a71/source/plutovg-canvas.c000066400000000000000000000613271510714322500220670ustar00rootroot00000000000000#include "plutovg-private.h" #include "plutovg-utils.h" int plutovg_version(void) { return PLUTOVG_VERSION; } const char* plutovg_version_string(void) { return PLUTOVG_VERSION_STRING; } #define PLUTOVG_DEFAULT_STROKE_STYLE ((plutovg_stroke_style_t){1.f, PLUTOVG_LINE_CAP_BUTT, PLUTOVG_LINE_JOIN_MITER, 10.f}) static plutovg_state_t* plutovg_state_create(void) { plutovg_state_t* state = malloc(sizeof(plutovg_state_t)); state->paint = NULL; state->font_face = NULL; state->color = PLUTOVG_BLACK_COLOR; state->matrix = PLUTOVG_IDENTITY_MATRIX; state->stroke.style = PLUTOVG_DEFAULT_STROKE_STYLE; state->stroke.dash.offset = 0.f; plutovg_array_init(state->stroke.dash.array); plutovg_span_buffer_init(&state->clip_spans); state->winding = PLUTOVG_FILL_RULE_NON_ZERO; state->op = PLUTOVG_OPERATOR_SRC_OVER; state->font_size = 12.f; state->opacity = 1.f; state->clipping = false; state->next = NULL; return state; } static void plutovg_state_reset(plutovg_state_t* state) { plutovg_paint_destroy(state->paint); plutovg_font_face_destroy(state->font_face); state->paint = NULL; state->font_face = NULL; state->color = PLUTOVG_BLACK_COLOR; state->matrix = PLUTOVG_IDENTITY_MATRIX; state->stroke.style = PLUTOVG_DEFAULT_STROKE_STYLE; state->stroke.dash.offset = 0.f; plutovg_array_clear(state->stroke.dash.array); plutovg_span_buffer_reset(&state->clip_spans); state->winding = PLUTOVG_FILL_RULE_NON_ZERO; state->op = PLUTOVG_OPERATOR_SRC_OVER; state->font_size = 12.f; state->opacity = 1.f; state->clipping = false; } static void plutovg_state_copy(plutovg_state_t* state, const plutovg_state_t* source) { state->paint = plutovg_paint_reference(source->paint); state->font_face = plutovg_font_face_reference(source->font_face); state->color = source->color; state->matrix = source->matrix; state->stroke.style = source->stroke.style; state->stroke.dash.offset = source->stroke.dash.offset; plutovg_array_clear(state->stroke.dash.array); plutovg_array_append(state->stroke.dash.array, source->stroke.dash.array); plutovg_span_buffer_copy(&state->clip_spans, &source->clip_spans); state->winding = source->winding; state->op = source->op; state->font_size = source->font_size; state->opacity = source->opacity; state->clipping = source->clipping; } static void plutovg_state_destroy(plutovg_state_t* state) { plutovg_paint_destroy(state->paint); plutovg_font_face_destroy(state->font_face); plutovg_array_destroy(state->stroke.dash.array); plutovg_span_buffer_destroy(&state->clip_spans); free(state); } plutovg_canvas_t* plutovg_canvas_create(plutovg_surface_t* surface) { plutovg_canvas_t* canvas = malloc(sizeof(plutovg_canvas_t)); plutovg_init_reference(canvas); canvas->surface = plutovg_surface_reference(surface); canvas->path = plutovg_path_create(); canvas->state = plutovg_state_create(); canvas->freed_state = NULL; canvas->face_cache = NULL; canvas->clip_rect = PLUTOVG_MAKE_RECT(0, 0, surface->width, surface->height); plutovg_span_buffer_init(&canvas->clip_spans); plutovg_span_buffer_init(&canvas->fill_spans); return canvas; } plutovg_canvas_t* plutovg_canvas_reference(plutovg_canvas_t* canvas) { plutovg_increment_reference(canvas); return canvas; } void plutovg_canvas_destroy(plutovg_canvas_t* canvas) { if(plutovg_destroy_reference(canvas)) { while(canvas->state) { plutovg_state_t* state = canvas->state; canvas->state = state->next; plutovg_state_destroy(state); } while(canvas->freed_state) { plutovg_state_t* state = canvas->freed_state; canvas->freed_state = state->next; plutovg_state_destroy(state); } plutovg_font_face_cache_destroy(canvas->face_cache); plutovg_span_buffer_destroy(&canvas->fill_spans); plutovg_span_buffer_destroy(&canvas->clip_spans); plutovg_surface_destroy(canvas->surface); plutovg_path_destroy(canvas->path); free(canvas); } } int plutovg_canvas_get_reference_count(const plutovg_canvas_t* canvas) { return plutovg_get_reference_count(canvas); } plutovg_surface_t* plutovg_canvas_get_surface(const plutovg_canvas_t* canvas) { return canvas->surface; } void plutovg_canvas_save(plutovg_canvas_t* canvas) { plutovg_state_t* new_state = canvas->freed_state; if(new_state == NULL) new_state = plutovg_state_create(); else canvas->freed_state = new_state->next; plutovg_state_copy(new_state, canvas->state); new_state->next = canvas->state; canvas->state = new_state; } void plutovg_canvas_restore(plutovg_canvas_t* canvas) { if(canvas->state->next == NULL) return; plutovg_state_t* old_state = canvas->state; canvas->state = old_state->next; plutovg_state_reset(old_state); old_state->next = canvas->freed_state; canvas->freed_state = old_state; } void plutovg_canvas_set_rgb(plutovg_canvas_t* canvas, float r, float g, float b) { plutovg_canvas_set_rgba(canvas, r, g, b, 1.f); } void plutovg_canvas_set_rgba(plutovg_canvas_t* canvas, float r, float g, float b, float a) { plutovg_color_init_rgba(&canvas->state->color, r, g, b, a); plutovg_canvas_set_paint(canvas, NULL); } void plutovg_canvas_set_color(plutovg_canvas_t* canvas, const plutovg_color_t* color) { plutovg_canvas_set_rgba(canvas, color->r, color->g, color->b, color->a); } void plutovg_canvas_set_linear_gradient(plutovg_canvas_t* canvas, float x1, float y1, float x2, float y2, plutovg_spread_method_t spread, const plutovg_gradient_stop_t* stops, int nstops, const plutovg_matrix_t* matrix) { plutovg_paint_t* paint = plutovg_paint_create_linear_gradient(x1, y1, x2, y2, spread, stops, nstops, matrix); plutovg_canvas_set_paint(canvas, paint); plutovg_paint_destroy(paint); } void plutovg_canvas_set_radial_gradient(plutovg_canvas_t* canvas, float cx, float cy, float cr, float fx, float fy, float fr, plutovg_spread_method_t spread, const plutovg_gradient_stop_t* stops, int nstops, const plutovg_matrix_t* matrix) { plutovg_paint_t* paint = plutovg_paint_create_radial_gradient(cx, cy, cr, fx, fy, fr, spread, stops, nstops, matrix); plutovg_canvas_set_paint(canvas, paint); plutovg_paint_destroy(paint); } void plutovg_canvas_set_texture(plutovg_canvas_t* canvas, plutovg_surface_t* surface, plutovg_texture_type_t type, float opacity, const plutovg_matrix_t* matrix) { plutovg_paint_t* paint = plutovg_paint_create_texture(surface, type, opacity, matrix); plutovg_canvas_set_paint(canvas, paint); plutovg_paint_destroy(paint); } void plutovg_canvas_set_paint(plutovg_canvas_t* canvas, plutovg_paint_t* paint) { paint = plutovg_paint_reference(paint); plutovg_paint_destroy(canvas->state->paint); canvas->state->paint = paint; } plutovg_paint_t* plutovg_canvas_get_paint(const plutovg_canvas_t* canvas, plutovg_color_t* color) { if(color) *color = canvas->state->color; return canvas->state->paint; } void plutovg_canvas_set_font_face_cache(plutovg_canvas_t* canvas, plutovg_font_face_cache_t* cache) { cache = plutovg_font_face_cache_reference(cache); plutovg_font_face_cache_destroy(canvas->face_cache); canvas->face_cache = cache; } plutovg_font_face_cache_t* plutovg_canvas_get_font_face_cache(const plutovg_canvas_t* canvas) { return canvas->face_cache; } void plutovg_canvas_add_font_face(plutovg_canvas_t* canvas, const char* family, bool bold, bool italic, plutovg_font_face_t* face) { if(canvas->face_cache == NULL) canvas->face_cache = plutovg_font_face_cache_create(); plutovg_font_face_cache_add(canvas->face_cache, family, bold, italic, face); } bool plutovg_canvas_add_font_file(plutovg_canvas_t* canvas, const char* family, bool bold, bool italic, const char* filename, int ttcindex) { if(canvas->face_cache == NULL) canvas->face_cache = plutovg_font_face_cache_create(); return plutovg_font_face_cache_add_file(canvas->face_cache, family, bold, italic, filename, ttcindex); } bool plutovg_canvas_select_font_face(plutovg_canvas_t* canvas, const char* family, bool bold, bool italic) { if(canvas->face_cache == NULL) return false; plutovg_font_face_t* face = plutovg_font_face_cache_get(canvas->face_cache, family, bold, italic); if(face == NULL) return false; plutovg_canvas_set_font_face(canvas, face); return true; } void plutovg_canvas_set_font(plutovg_canvas_t* canvas, plutovg_font_face_t* face, float size) { plutovg_canvas_set_font_face(canvas, face); plutovg_canvas_set_font_size(canvas, size); } void plutovg_canvas_set_font_face(plutovg_canvas_t* canvas, plutovg_font_face_t* face) { face = plutovg_font_face_reference(face); plutovg_font_face_destroy(canvas->state->font_face); canvas->state->font_face = face; } plutovg_font_face_t* plutovg_canvas_get_font_face(const plutovg_canvas_t* canvas) { return canvas->state->font_face; } void plutovg_canvas_set_font_size(plutovg_canvas_t* canvas, float size) { canvas->state->font_size = size; } float plutovg_canvas_get_font_size(const plutovg_canvas_t* canvas) { return canvas->state->font_size; } void plutovg_canvas_set_fill_rule(plutovg_canvas_t* canvas, plutovg_fill_rule_t winding) { canvas->state->winding = winding; } plutovg_fill_rule_t plutovg_canvas_get_fill_rule(const plutovg_canvas_t* canvas) { return canvas->state->winding; } void plutovg_canvas_set_operator(plutovg_canvas_t* canvas, plutovg_operator_t op) { canvas->state->op = op; } plutovg_operator_t plutovg_canvas_get_operator(const plutovg_canvas_t* canvas) { return canvas->state->op; } void plutovg_canvas_set_opacity(plutovg_canvas_t* canvas, float opacity) { canvas->state->opacity = plutovg_clamp(opacity, 0.f, 1.f); } float plutovg_canvas_get_opacity(const plutovg_canvas_t* canvas) { return canvas->state->opacity; } void plutovg_canvas_set_line_width(plutovg_canvas_t* canvas, float line_width) { canvas->state->stroke.style.width = line_width; } float plutovg_canvas_get_line_width(const plutovg_canvas_t* canvas) { return canvas->state->stroke.style.width; } void plutovg_canvas_set_line_cap(plutovg_canvas_t* canvas, plutovg_line_cap_t line_cap) { canvas->state->stroke.style.cap = line_cap; } plutovg_line_cap_t plutovg_canvas_get_line_cap(const plutovg_canvas_t* canvas) { return canvas->state->stroke.style.cap; } void plutovg_canvas_set_line_join(plutovg_canvas_t* canvas, plutovg_line_join_t line_join) { canvas->state->stroke.style.join = line_join; } plutovg_line_join_t plutovg_canvas_get_line_join(const plutovg_canvas_t* canvas) { return canvas->state->stroke.style.join; } void plutovg_canvas_set_miter_limit(plutovg_canvas_t* canvas, float miter_limit) { canvas->state->stroke.style.miter_limit = miter_limit; } float plutovg_canvas_get_miter_limit(const plutovg_canvas_t* canvas) { return canvas->state->stroke.style.miter_limit; } void plutovg_canvas_set_dash(plutovg_canvas_t* canvas, float offset, const float* dashes, int ndashes) { plutovg_canvas_set_dash_offset(canvas, offset); plutovg_canvas_set_dash_array(canvas, dashes, ndashes); } void plutovg_canvas_set_dash_offset(plutovg_canvas_t* canvas, float offset) { canvas->state->stroke.dash.offset = offset; } float plutovg_canvas_get_dash_offset(const plutovg_canvas_t* canvas) { return canvas->state->stroke.dash.offset; } void plutovg_canvas_set_dash_array(plutovg_canvas_t* canvas, const float* dashes, int ndashes) { plutovg_array_clear(canvas->state->stroke.dash.array); plutovg_array_append_data(canvas->state->stroke.dash.array, dashes, ndashes); } int plutovg_canvas_get_dash_array(const plutovg_canvas_t* canvas, const float** dashes) { if(dashes) *dashes = canvas->state->stroke.dash.array.data; return canvas->state->stroke.dash.array.size; } void plutovg_canvas_translate(plutovg_canvas_t* canvas, float tx, float ty) { plutovg_matrix_translate(&canvas->state->matrix, tx, ty); } void plutovg_canvas_scale(plutovg_canvas_t* canvas, float sx, float sy) { plutovg_matrix_scale(&canvas->state->matrix, sx, sy); } void plutovg_canvas_shear(plutovg_canvas_t* canvas, float shx, float shy) { plutovg_matrix_shear(&canvas->state->matrix, shx, shy); } void plutovg_canvas_rotate(plutovg_canvas_t* canvas, float angle) { plutovg_matrix_rotate(&canvas->state->matrix, angle); } void plutovg_canvas_transform(plutovg_canvas_t* canvas, const plutovg_matrix_t* matrix) { plutovg_matrix_multiply(&canvas->state->matrix, matrix, &canvas->state->matrix); } void plutovg_canvas_reset_matrix(plutovg_canvas_t* canvas) { plutovg_matrix_init_identity(&canvas->state->matrix); } void plutovg_canvas_set_matrix(plutovg_canvas_t* canvas, const plutovg_matrix_t* matrix) { canvas->state->matrix = matrix ? *matrix : PLUTOVG_IDENTITY_MATRIX; } void plutovg_canvas_get_matrix(const plutovg_canvas_t* canvas, plutovg_matrix_t* matrix) { *matrix = canvas->state->matrix; } void plutovg_canvas_map(const plutovg_canvas_t* canvas, float x, float y, float* xx, float* yy) { plutovg_matrix_map(&canvas->state->matrix, x, y, xx, yy); } void plutovg_canvas_map_point(const plutovg_canvas_t* canvas, const plutovg_point_t* src, plutovg_point_t* dst) { plutovg_matrix_map_point(&canvas->state->matrix, src, dst); } void plutovg_canvas_map_rect(const plutovg_canvas_t* canvas, const plutovg_rect_t* src, plutovg_rect_t* dst) { plutovg_matrix_map_rect(&canvas->state->matrix, src, dst); } void plutovg_canvas_move_to(plutovg_canvas_t* canvas, float x, float y) { plutovg_path_move_to(canvas->path, x, y); } void plutovg_canvas_line_to(plutovg_canvas_t* canvas, float x, float y) { plutovg_path_line_to(canvas->path, x, y); } void plutovg_canvas_quad_to(plutovg_canvas_t* canvas, float x1, float y1, float x2, float y2) { plutovg_path_quad_to(canvas->path, x1, y1, x2, y2); } void plutovg_canvas_cubic_to(plutovg_canvas_t* canvas, float x1, float y1, float x2, float y2, float x3, float y3) { plutovg_path_cubic_to(canvas->path, x1, y1, x2, y2, x3, y3); } void plutovg_canvas_arc_to(plutovg_canvas_t* canvas, float rx, float ry, float angle, bool large_arc_flag, bool sweep_flag, float x, float y) { plutovg_path_arc_to(canvas->path, rx, ry, angle, large_arc_flag, sweep_flag, x, y); } void plutovg_canvas_rect(plutovg_canvas_t* canvas, float x, float y, float w, float h) { plutovg_path_add_rect(canvas->path, x, y, w, h); } void plutovg_canvas_round_rect(plutovg_canvas_t* canvas, float x, float y, float w, float h, float rx, float ry) { plutovg_path_add_round_rect(canvas->path, x, y, w, h, rx, ry); } void plutovg_canvas_ellipse(plutovg_canvas_t* canvas, float cx, float cy, float rx, float ry) { plutovg_path_add_ellipse(canvas->path, cx, cy, rx, ry); } void plutovg_canvas_circle(plutovg_canvas_t* canvas, float cx, float cy, float r) { plutovg_path_add_circle(canvas->path, cx, cy, r); } void plutovg_canvas_arc(plutovg_canvas_t* canvas, float cx, float cy, float r, float a0, float a1, bool ccw) { plutovg_path_add_arc(canvas->path, cx, cy, r, a0, a1, ccw); } void plutovg_canvas_add_path(plutovg_canvas_t* canvas, const plutovg_path_t* path) { plutovg_path_add_path(canvas->path, path, NULL); } void plutovg_canvas_new_path(plutovg_canvas_t* canvas) { plutovg_path_reset(canvas->path); } void plutovg_canvas_close_path(plutovg_canvas_t* canvas) { plutovg_path_close(canvas->path); } void plutovg_canvas_get_current_point(const plutovg_canvas_t* canvas, float* x, float* y) { plutovg_path_get_current_point(canvas->path, x, y); } plutovg_path_t* plutovg_canvas_get_path(const plutovg_canvas_t* canvas) { return canvas->path; } bool plutovg_canvas_fill_contains(plutovg_canvas_t* canvas, float x, float y) { plutovg_rasterize(&canvas->fill_spans, canvas->path, &canvas->state->matrix, NULL, NULL, canvas->state->winding); return plutovg_span_buffer_contains(&canvas->fill_spans, x, y); } bool plutovg_canvas_stroke_contains(plutovg_canvas_t* canvas, float x, float y) { plutovg_rasterize(&canvas->fill_spans, canvas->path, &canvas->state->matrix, NULL, NULL, canvas->state->winding); return plutovg_span_buffer_contains(&canvas->fill_spans, x, y); } bool plutovg_canvas_clip_contains(plutovg_canvas_t* canvas, float x, float y) { if(canvas->state->clipping) { return plutovg_span_buffer_contains(&canvas->state->clip_spans, x, y); } float l = canvas->clip_rect.x; float t = canvas->clip_rect.y; float r = canvas->clip_rect.x + canvas->clip_rect.w; float b = canvas->clip_rect.y + canvas->clip_rect.h; return x >= l && x <= r && y >= t && y <= b; } void plutovg_canvas_fill_extents(plutovg_canvas_t *canvas, plutovg_rect_t* extents) { plutovg_rasterize(&canvas->fill_spans, canvas->path, &canvas->state->matrix, NULL, NULL, canvas->state->winding); plutovg_span_buffer_extents(&canvas->fill_spans, extents); } void plutovg_canvas_stroke_extents(plutovg_canvas_t *canvas, plutovg_rect_t* extents) { plutovg_rasterize(&canvas->fill_spans, canvas->path, &canvas->state->matrix, NULL, &canvas->state->stroke, PLUTOVG_FILL_RULE_NON_ZERO); plutovg_span_buffer_extents(&canvas->fill_spans, extents); } void plutovg_canvas_clip_extents(plutovg_canvas_t* canvas, plutovg_rect_t* extents) { if(canvas->state->clipping) { plutovg_span_buffer_extents(&canvas->state->clip_spans, extents); } else { extents->x = canvas->clip_rect.x; extents->y = canvas->clip_rect.y; extents->w = canvas->clip_rect.w; extents->h = canvas->clip_rect.h; } } void plutovg_canvas_fill(plutovg_canvas_t* canvas) { plutovg_canvas_fill_preserve(canvas); plutovg_canvas_new_path(canvas); } void plutovg_canvas_stroke(plutovg_canvas_t* canvas) { plutovg_canvas_stroke_preserve(canvas); plutovg_canvas_new_path(canvas); } void plutovg_canvas_clip(plutovg_canvas_t* canvas) { plutovg_canvas_clip_preserve(canvas); plutovg_canvas_new_path(canvas); } void plutovg_canvas_paint(plutovg_canvas_t* canvas) { if(canvas->state->clipping) { plutovg_blend(canvas, &canvas->state->clip_spans); } else { plutovg_span_buffer_init_rect(&canvas->clip_spans, 0, 0, canvas->surface->width, canvas->surface->height); plutovg_blend(canvas, &canvas->clip_spans); } } void plutovg_canvas_fill_preserve(plutovg_canvas_t* canvas) { plutovg_rasterize(&canvas->fill_spans, canvas->path, &canvas->state->matrix, &canvas->clip_rect, NULL, canvas->state->winding); if(canvas->state->clipping) { plutovg_span_buffer_intersect(&canvas->clip_spans, &canvas->fill_spans, &canvas->state->clip_spans); plutovg_blend(canvas, &canvas->clip_spans); } else { plutovg_blend(canvas, &canvas->fill_spans); } } void plutovg_canvas_stroke_preserve(plutovg_canvas_t* canvas) { plutovg_rasterize(&canvas->fill_spans, canvas->path, &canvas->state->matrix, &canvas->clip_rect, &canvas->state->stroke, PLUTOVG_FILL_RULE_NON_ZERO); if(canvas->state->clipping) { plutovg_span_buffer_intersect(&canvas->clip_spans, &canvas->fill_spans, &canvas->state->clip_spans); plutovg_blend(canvas, &canvas->clip_spans); } else { plutovg_blend(canvas, &canvas->fill_spans); } } void plutovg_canvas_clip_preserve(plutovg_canvas_t* canvas) { if(canvas->state->clipping) { plutovg_rasterize(&canvas->fill_spans, canvas->path, &canvas->state->matrix, &canvas->clip_rect, NULL, canvas->state->winding); plutovg_span_buffer_intersect(&canvas->clip_spans, &canvas->fill_spans, &canvas->state->clip_spans); plutovg_span_buffer_copy(&canvas->state->clip_spans, &canvas->clip_spans); } else { plutovg_rasterize(&canvas->state->clip_spans, canvas->path, &canvas->state->matrix, &canvas->clip_rect, NULL, canvas->state->winding); canvas->state->clipping = true; } } void plutovg_canvas_fill_rect(plutovg_canvas_t* canvas, float x, float y, float w, float h) { plutovg_canvas_new_path(canvas); plutovg_canvas_rect(canvas, x, y, w, h); plutovg_canvas_fill(canvas); } void plutovg_canvas_fill_path(plutovg_canvas_t* canvas, const plutovg_path_t* path) { plutovg_canvas_new_path(canvas); plutovg_canvas_add_path(canvas, path); plutovg_canvas_fill(canvas); } void plutovg_canvas_stroke_rect(plutovg_canvas_t* canvas, float x, float y, float w, float h) { plutovg_canvas_new_path(canvas); plutovg_canvas_rect(canvas, x, y, w, h); plutovg_canvas_stroke(canvas); } void plutovg_canvas_stroke_path(plutovg_canvas_t* canvas, const plutovg_path_t* path) { plutovg_canvas_new_path(canvas); plutovg_canvas_add_path(canvas, path); plutovg_canvas_stroke(canvas); } void plutovg_canvas_clip_rect(plutovg_canvas_t* canvas, float x, float y, float w, float h) { plutovg_canvas_new_path(canvas); plutovg_canvas_rect(canvas, x, y, w, h); plutovg_canvas_clip(canvas); } void plutovg_canvas_clip_path(plutovg_canvas_t* canvas, const plutovg_path_t* path) { plutovg_canvas_new_path(canvas); plutovg_canvas_add_path(canvas, path); plutovg_canvas_clip(canvas); } float plutovg_canvas_add_glyph(plutovg_canvas_t* canvas, plutovg_codepoint_t codepoint, float x, float y) { plutovg_state_t* state = canvas->state; if(state->font_face && state->font_size > 0.f) return plutovg_font_face_get_glyph_path(state->font_face, state->font_size, x, y, codepoint, canvas->path); return 0.f; } float plutovg_canvas_add_text(plutovg_canvas_t* canvas, const void* text, int length, plutovg_text_encoding_t encoding, float x, float y) { plutovg_state_t* state = canvas->state; if(state->font_face == NULL || state->font_size <= 0.f) return 0.f; plutovg_text_iterator_t it; plutovg_text_iterator_init(&it, text, length, encoding); float advance_width = 0.f; while(plutovg_text_iterator_has_next(&it)) { plutovg_codepoint_t codepoint = plutovg_text_iterator_next(&it); advance_width += plutovg_font_face_get_glyph_path(state->font_face, state->font_size, x + advance_width, y, codepoint, canvas->path); } return advance_width; } float plutovg_canvas_fill_text(plutovg_canvas_t* canvas, const void* text, int length, plutovg_text_encoding_t encoding, float x, float y) { plutovg_canvas_new_path(canvas); float advance_width = plutovg_canvas_add_text(canvas, text, length, encoding, x, y); plutovg_canvas_fill(canvas); return advance_width; } float plutovg_canvas_stroke_text(plutovg_canvas_t* canvas, const void* text, int length, plutovg_text_encoding_t encoding, float x, float y) { plutovg_canvas_new_path(canvas); float advance_width = plutovg_canvas_add_text(canvas, text, length, encoding, x, y); plutovg_canvas_stroke(canvas); return advance_width; } float plutovg_canvas_clip_text(plutovg_canvas_t* canvas, const void* text, int length, plutovg_text_encoding_t encoding, float x, float y) { plutovg_canvas_new_path(canvas); float advance_width = plutovg_canvas_add_text(canvas, text, length, encoding, x, y); plutovg_canvas_clip(canvas); return advance_width; } void plutovg_canvas_font_metrics(const plutovg_canvas_t* canvas, float* ascent, float* descent, float* line_gap, plutovg_rect_t* extents) { plutovg_state_t* state = canvas->state; if(state->font_face && state->font_size > 0.f) { plutovg_font_face_get_metrics(state->font_face, state->font_size, ascent, descent, line_gap, extents); return; } if(ascent) *ascent = 0.f; if(descent) *descent = 0.f; if(line_gap) *line_gap = 0.f; if(extents) { extents->x = 0.f; extents->y = 0.f; extents->w = 0.f; extents->h = 0.f; } } void plutovg_canvas_glyph_metrics(plutovg_canvas_t* canvas, plutovg_codepoint_t codepoint, float* advance_width, float* left_side_bearing, plutovg_rect_t* extents) { plutovg_state_t* state = canvas->state; if(state->font_face && state->font_size > 0.f) { plutovg_font_face_get_glyph_metrics(state->font_face, state->font_size, codepoint, advance_width, left_side_bearing, extents); return; } if(advance_width) *advance_width = 0.f; if(left_side_bearing) *left_side_bearing = 0.f; if(extents) { extents->x = 0.f; extents->y = 0.f; extents->w = 0.f; extents->h = 0.f; } } float plutovg_canvas_text_extents(plutovg_canvas_t* canvas, const void* text, int length, plutovg_text_encoding_t encoding, plutovg_rect_t* extents) { plutovg_state_t* state = canvas->state; if(state->font_face && state->font_size > 0.f) { return plutovg_font_face_text_extents(state->font_face, state->font_size, text, length, encoding, extents); } if(extents) { extents->x = 0.f; extents->y = 0.f; extents->w = 0.f; extents->h = 0.f; } return 0.f; } sammycage-plutovg-5695a71/source/plutovg-font.c000066400000000000000000001061521510714322500215560ustar00rootroot00000000000000#include "plutovg-private.h" #include "plutovg-utils.h" #include #include #define STBTT_STATIC #define STB_TRUETYPE_IMPLEMENTATION #include "plutovg-stb-truetype.h" static int plutovg_text_iterator_length(const void* data, plutovg_text_encoding_t encoding) { int length = 0; switch(encoding) { case PLUTOVG_TEXT_ENCODING_LATIN1: case PLUTOVG_TEXT_ENCODING_UTF8: { const uint8_t* text = data; while(*text++) length++; break; } case PLUTOVG_TEXT_ENCODING_UTF16: { const uint16_t* text = data; while(*text++) length++; break; } case PLUTOVG_TEXT_ENCODING_UTF32: { const uint32_t* text = data; while(*text++) length++; break; } default: assert(false); } return length; } void plutovg_text_iterator_init(plutovg_text_iterator_t* it, const void* text, int length, plutovg_text_encoding_t encoding) { if(length == -1) length = plutovg_text_iterator_length(text, encoding); it->text = text; it->length = length; it->encoding = encoding; it->index = 0; } bool plutovg_text_iterator_has_next(const plutovg_text_iterator_t* it) { return it->index < it->length; } plutovg_codepoint_t plutovg_text_iterator_next(plutovg_text_iterator_t* it) { plutovg_codepoint_t codepoint = 0; switch(it->encoding) { case PLUTOVG_TEXT_ENCODING_LATIN1: { const uint8_t* text = it->text; codepoint = text[it->index++]; break; } case PLUTOVG_TEXT_ENCODING_UTF8: { static const uint8_t trailing[256] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5 }; static const uint32_t offsets[6] = { 0x00000000, 0x00003080, 0x000E2080, 0x03C82080, 0xFA082080, 0x82082080 }; const uint8_t* text = it->text; uint8_t trailing_offset = trailing[text[it->index]]; uint32_t offset_value = offsets[trailing_offset]; while(trailing_offset > 0 && it->index < it->length - 1) { codepoint += text[it->index++]; codepoint <<= 6; trailing_offset--; } codepoint += text[it->index++]; codepoint -= offset_value; break; } case PLUTOVG_TEXT_ENCODING_UTF16: { const uint16_t* text = it->text; codepoint = text[it->index++]; if(((codepoint) & 0xfffffc00) == 0xd800) { if(it->index < it->length && (((codepoint) & 0xfffffc00) == 0xdc00)) { uint16_t trail = text[it->index++]; codepoint = (codepoint << 10) + trail - ((0xD800u << 10) - 0x10000u + 0xDC00u); } } break; } case PLUTOVG_TEXT_ENCODING_UTF32: { const uint32_t* text = it->text; codepoint = text[it->index++]; break; } default: assert(false); } return codepoint; } #if defined(_WIN32) #include typedef CRITICAL_SECTION plutovg_mutex_t; #define plutovg_mutex_init(mutex) InitializeCriticalSection(mutex) #define plutovg_mutex_lock(mutex) EnterCriticalSection(mutex) #define plutovg_mutex_unlock(mutex) LeaveCriticalSection(mutex) #define plutovg_mutex_destroy(mutex) DeleteCriticalSection(mutex) #elif defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L && defined(HAVE_THREADS_H) && !defined(__STDC_NO_THREADS__) #include typedef mtx_t plutovg_mutex_t; #define plutovg_mutex_init(mutex) mtx_init(mutex, mtx_plain | mtx_recursive) #define plutovg_mutex_lock(mutex) mtx_lock(mutex) #define plutovg_mutex_unlock(mutex) mtx_unlock(mutex) #define plutovg_mutex_destroy(mutex) mtx_destroy(mutex) #else typedef int plutovg_mutex_t; #define plutovg_mutex_init(mutex) ((void)(mutex)) #define plutovg_mutex_lock(mutex) ((void)(mutex)) #define plutovg_mutex_unlock(mutex) ((void)(mutex)) #define plutovg_mutex_destroy(mutex) ((void)(mutex)) #endif typedef struct plutovg_glyph { plutovg_codepoint_t codepoint; stbtt_vertex* vertices; int nvertices; int index; int advance_width; int left_side_bearing; int x1; int y1; int x2; int y2; struct plutovg_glyph* next; } plutovg_glyph_t; typedef struct { plutovg_glyph_t** glyphs; size_t size; size_t capacity; } plutovg_glyph_cache_t; struct plutovg_font_face { plutovg_ref_count_t ref_count; int ascent; int descent; int line_gap; int x1; int y1; int x2; int y2; stbtt_fontinfo info; plutovg_mutex_t mutex; plutovg_glyph_cache_t cache; plutovg_destroy_func_t destroy_func; void* closure; }; static void plutovg_glyph_cache_init(plutovg_glyph_cache_t* cache) { cache->glyphs = NULL; cache->size = 0; cache->capacity = 0; } static void plutovg_glyph_cache_finish(plutovg_glyph_cache_t* cache, plutovg_font_face_t* face) { plutovg_mutex_lock(&face->mutex); if(cache->glyphs) { for(size_t i = 0; i < cache->capacity; ++i) { plutovg_glyph_t* glyph = cache->glyphs[i]; while(glyph) { plutovg_glyph_t* next = glyph->next; stbtt_FreeShape(&face->info, glyph->vertices); free(glyph); glyph = next; } } free(cache->glyphs); cache->glyphs = NULL; cache->capacity = 0; cache->size = 0; } plutovg_mutex_unlock(&face->mutex); } #define GLYPH_CACHE_INIT_CAPACITY 128 static plutovg_glyph_t* plutovg_glyph_cache_get(plutovg_glyph_cache_t* cache, plutovg_font_face_t* face, plutovg_codepoint_t codepoint) { plutovg_mutex_lock(&face->mutex); if(cache->glyphs == NULL) { assert(cache->size == 0); cache->glyphs = calloc(GLYPH_CACHE_INIT_CAPACITY, sizeof(plutovg_glyph_t*)); cache->capacity = GLYPH_CACHE_INIT_CAPACITY; } size_t index = codepoint & (cache->capacity - 1); plutovg_glyph_t* glyph = cache->glyphs[index]; while(glyph && glyph->codepoint != codepoint) { glyph = glyph->next; } if(glyph == NULL) { glyph = malloc(sizeof(plutovg_glyph_t)); glyph->codepoint = codepoint; glyph->index = stbtt_FindGlyphIndex(&face->info, codepoint); glyph->nvertices = stbtt_GetGlyphShape(&face->info, glyph->index, &glyph->vertices); stbtt_GetGlyphHMetrics(&face->info, glyph->index, &glyph->advance_width, &glyph->left_side_bearing); if(!stbtt_GetGlyphBox(&face->info, glyph->index, &glyph->x1, &glyph->y1, &glyph->x2, &glyph->y2)) { glyph->x1 = glyph->y1 = glyph->x2 = glyph->y2 = 0; } glyph->next = cache->glyphs[index]; cache->glyphs[index] = glyph; cache->size += 1; if(cache->size > (cache->capacity * 3 / 4)) { size_t newcapacity = cache->capacity << 1; plutovg_glyph_t** newglyphs = calloc(newcapacity, sizeof(plutovg_glyph_t*)); for(size_t i = 0; i < cache->capacity; ++i) { plutovg_glyph_t* entry = cache->glyphs[i]; while(entry) { plutovg_glyph_t* next = entry->next; size_t newindex = entry->codepoint & (newcapacity - 1); entry->next = newglyphs[newindex]; newglyphs[newindex] = entry; entry = next; } } free(cache->glyphs); cache->glyphs = newglyphs; cache->capacity = newcapacity; } } plutovg_mutex_unlock(&face->mutex); return glyph; } plutovg_font_face_t* plutovg_font_face_load_from_file(const char* filename, int ttcindex) { FILE* fp = fopen(filename, "rb"); if(fp == NULL) { return NULL; } fseek(fp, 0, SEEK_END); long length = ftell(fp); if(length == -1L) { fclose(fp); return NULL; } void* data = malloc(length); if(data == NULL) { fclose(fp); return NULL; } fseek(fp, 0, SEEK_SET); size_t nread = fread(data, 1, length, fp); fclose(fp); if(nread != length) { free(data); return NULL; } return plutovg_font_face_load_from_data(data, length, ttcindex, free, data); } plutovg_font_face_t* plutovg_font_face_load_from_data(const void* data, unsigned int length, int ttcindex, plutovg_destroy_func_t destroy_func, void* closure) { stbtt_fontinfo info; int offset = stbtt_GetFontOffsetForIndex(data, ttcindex); if(offset == -1 || !stbtt_InitFont(&info, data, offset)) { if(destroy_func) destroy_func(closure); return NULL; } plutovg_font_face_t* face = malloc(sizeof(plutovg_font_face_t)); plutovg_init_reference(face); face->info = info; stbtt_GetFontVMetrics(&face->info, &face->ascent, &face->descent, &face->line_gap); stbtt_GetFontBoundingBox(&face->info, &face->x1, &face->y1, &face->x2, &face->y2); plutovg_mutex_init(&face->mutex); plutovg_glyph_cache_init(&face->cache); face->destroy_func = destroy_func; face->closure = closure; return face; } plutovg_font_face_t* plutovg_font_face_reference(plutovg_font_face_t* face) { plutovg_increment_reference(face); return face; } void plutovg_font_face_destroy(plutovg_font_face_t* face) { if(plutovg_destroy_reference(face)) { plutovg_glyph_cache_finish(&face->cache, face); plutovg_mutex_destroy(&face->mutex); if(face->destroy_func) face->destroy_func(face->closure); free(face); } } int plutovg_font_face_get_reference_count(const plutovg_font_face_t* face) { return plutovg_get_reference_count(face); } static float plutovg_font_face_get_scale(const plutovg_font_face_t* face, float size) { return stbtt_ScaleForMappingEmToPixels(&face->info, size); } void plutovg_font_face_get_metrics(const plutovg_font_face_t* face, float size, float* ascent, float* descent, float* line_gap, plutovg_rect_t* extents) { float scale = plutovg_font_face_get_scale(face, size); if(ascent) *ascent = face->ascent * scale; if(descent) *descent = face->descent * scale; if(line_gap) *line_gap = face->line_gap * scale; if(extents) { extents->x = face->x1 * scale; extents->y = face->y2 * -scale; extents->w = (face->x2 - face->x1) * scale; extents->h = (face->y1 - face->y2) * -scale; } } static plutovg_glyph_t* plutovg_font_face_get_glyph(plutovg_font_face_t* face, plutovg_codepoint_t codepoint) { return plutovg_glyph_cache_get(&face->cache, face, codepoint); } void plutovg_font_face_get_glyph_metrics(plutovg_font_face_t* face, float size, plutovg_codepoint_t codepoint, float* advance_width, float* left_side_bearing, plutovg_rect_t* extents) { float scale = plutovg_font_face_get_scale(face, size); plutovg_glyph_t* glyph = plutovg_font_face_get_glyph(face, codepoint); if(advance_width) *advance_width = glyph->advance_width * scale; if(left_side_bearing) *left_side_bearing = glyph->left_side_bearing * scale; if(extents) { extents->x = glyph->x1 * scale; extents->y = glyph->y2 * -scale; extents->w = (glyph->x2 - glyph->x1) * scale; extents->h = (glyph->y1 - glyph->y2) * -scale; } } static void glyph_traverse_func(void* closure, plutovg_path_command_t command, const plutovg_point_t* points, int npoints) { plutovg_path_t* path = (plutovg_path_t*)(closure); switch(command) { case PLUTOVG_PATH_COMMAND_MOVE_TO: plutovg_path_move_to(path, points[0].x, points[0].y); break; case PLUTOVG_PATH_COMMAND_LINE_TO: plutovg_path_line_to(path, points[0].x, points[0].y); break; case PLUTOVG_PATH_COMMAND_CUBIC_TO: plutovg_path_cubic_to(path, points[0].x, points[0].y, points[1].x, points[1].y, points[2].x, points[2].y); break; case PLUTOVG_PATH_COMMAND_CLOSE: assert(false); } } float plutovg_font_face_get_glyph_path(plutovg_font_face_t* face, float size, float x, float y, plutovg_codepoint_t codepoint, plutovg_path_t* path) { return plutovg_font_face_traverse_glyph_path(face, size, x, y, codepoint, glyph_traverse_func, path); } float plutovg_font_face_traverse_glyph_path(plutovg_font_face_t* face, float size, float x, float y, plutovg_codepoint_t codepoint, plutovg_path_traverse_func_t traverse_func, void* closure) { float scale = plutovg_font_face_get_scale(face, size); plutovg_matrix_t matrix; plutovg_matrix_init_translate(&matrix, x, y); plutovg_matrix_scale(&matrix, scale, -scale); plutovg_point_t points[3]; plutovg_point_t current_point = {0, 0}; plutovg_glyph_t* glyph = plutovg_font_face_get_glyph(face, codepoint); for(int i = 0; i < glyph->nvertices; i++) { switch(glyph->vertices[i].type) { case STBTT_vmove: points[0].x = glyph->vertices[i].x; points[0].y = glyph->vertices[i].y; current_point = points[0]; plutovg_matrix_map_points(&matrix, points, points, 1); traverse_func(closure, PLUTOVG_PATH_COMMAND_MOVE_TO, points, 1); break; case STBTT_vline: points[0].x = glyph->vertices[i].x; points[0].y = glyph->vertices[i].y; current_point = points[0]; plutovg_matrix_map_points(&matrix, points, points, 1); traverse_func(closure, PLUTOVG_PATH_COMMAND_LINE_TO, points, 1); break; case STBTT_vcurve: points[0].x = 2.f / 3.f * glyph->vertices[i].cx + 1.f / 3.f * current_point.x; points[0].y = 2.f / 3.f * glyph->vertices[i].cy + 1.f / 3.f * current_point.y; points[1].x = 2.f / 3.f * glyph->vertices[i].cx + 1.f / 3.f * glyph->vertices[i].x; points[1].y = 2.f / 3.f * glyph->vertices[i].cy + 1.f / 3.f * glyph->vertices[i].y; points[2].x = glyph->vertices[i].x; points[2].y = glyph->vertices[i].y; current_point = points[2]; plutovg_matrix_map_points(&matrix, points, points, 3); traverse_func(closure, PLUTOVG_PATH_COMMAND_CUBIC_TO, points, 3); break; case STBTT_vcubic: points[0].x = glyph->vertices[i].cx; points[0].y = glyph->vertices[i].cy; points[1].x = glyph->vertices[i].cx1; points[1].y = glyph->vertices[i].cy1; points[2].x = glyph->vertices[i].x; points[2].y = glyph->vertices[i].y; current_point = points[2]; plutovg_matrix_map_points(&matrix, points, points, 3); traverse_func(closure, PLUTOVG_PATH_COMMAND_CUBIC_TO, points, 3); break; default: assert(false); } } return glyph->advance_width * scale; } float plutovg_font_face_text_extents(plutovg_font_face_t* face, float size, const void* text, int length, plutovg_text_encoding_t encoding, plutovg_rect_t* extents) { plutovg_text_iterator_t it; plutovg_text_iterator_init(&it, text, length, encoding); plutovg_rect_t* text_extents = NULL; float total_advance_width = 0.f; while(plutovg_text_iterator_has_next(&it)) { plutovg_codepoint_t codepoint = plutovg_text_iterator_next(&it); float advance_width; if(extents == NULL) { plutovg_font_face_get_glyph_metrics(face, size, codepoint, &advance_width, NULL, NULL); total_advance_width += advance_width; continue; } plutovg_rect_t glyph_extents; plutovg_font_face_get_glyph_metrics(face, size, codepoint, &advance_width, NULL, &glyph_extents); glyph_extents.x += total_advance_width; total_advance_width += advance_width; if(text_extents == NULL) { text_extents = extents; *text_extents = glyph_extents; continue; } float x1 = plutovg_min(text_extents->x, glyph_extents.x); float y1 = plutovg_min(text_extents->y, glyph_extents.y); float x2 = plutovg_max(text_extents->x + text_extents->w, glyph_extents.x + glyph_extents.w); float y2 = plutovg_max(text_extents->y + text_extents->h, glyph_extents.y + glyph_extents.h); text_extents->x = x1; text_extents->y = y1; text_extents->w = x2 - x1; text_extents->h = y2 - y1; } if(extents && !text_extents) { extents->x = 0; extents->y = 0; extents->w = 0; extents->h = 0; } return total_advance_width; } typedef struct plutovg_font_face_entry { plutovg_font_face_t* face; char* family; char* filename; int ttcindex; bool bold; bool italic; struct plutovg_font_face_entry* next; } plutovg_font_face_entry_t; struct plutovg_font_face_cache { plutovg_ref_count_t ref_count; plutovg_mutex_t mutex; plutovg_font_face_entry_t** entries; int size; int capacity; bool is_sorted; }; plutovg_font_face_cache_t* plutovg_font_face_cache_create(void) { plutovg_font_face_cache_t* cache = malloc(sizeof(plutovg_font_face_cache_t)); plutovg_init_reference(cache); plutovg_mutex_init(&cache->mutex); cache->entries = NULL; cache->size = 0; cache->capacity = 0; cache->is_sorted = false; return cache; } plutovg_font_face_cache_t* plutovg_font_face_cache_reference(plutovg_font_face_cache_t* cache) { plutovg_increment_reference(cache); return cache; } void plutovg_font_face_cache_destroy(plutovg_font_face_cache_t* cache) { if(plutovg_destroy_reference(cache)) { plutovg_font_face_cache_reset(cache); plutovg_mutex_destroy(&cache->mutex); free(cache); } } int plutovg_font_face_cache_reference_count(const plutovg_font_face_cache_t* cache) { return plutovg_get_reference_count(cache); } void plutovg_font_face_cache_reset(plutovg_font_face_cache_t* cache) { plutovg_mutex_lock(&cache->mutex); for(int i = 0; i < cache->size; ++i) { plutovg_font_face_entry_t* entry = cache->entries[i]; do { plutovg_font_face_entry_t* next = entry->next; plutovg_font_face_destroy(entry->face); free(entry); entry = next; } while(entry); } free(cache->entries); cache->entries = NULL; cache->size = 0; cache->capacity = 0; cache->is_sorted = false; plutovg_mutex_unlock(&cache->mutex); } static void plutovg_font_face_cache_add_entry(plutovg_font_face_cache_t* cache, plutovg_font_face_entry_t* entry) { plutovg_mutex_lock(&cache->mutex); for(int i = 0; i < cache->size; ++i) { if(strcmp(entry->family, cache->entries[i]->family) == 0) { entry->next = cache->entries[i]; cache->entries[i] = entry; goto unlock; } } if(cache->size >= cache->capacity) { cache->capacity = cache->capacity == 0 ? 8 : cache->capacity << 2; cache->entries = realloc(cache->entries, cache->capacity * sizeof(plutovg_font_face_entry_t*)); } entry->next = NULL; cache->entries[cache->size++] = entry; cache->is_sorted = false; unlock: plutovg_mutex_unlock(&cache->mutex); } void plutovg_font_face_cache_add(plutovg_font_face_cache_t* cache, const char* family, bool bold, bool italic, plutovg_font_face_t* face) { if(family == NULL) family = ""; size_t family_length = strlen(family) + 1; plutovg_font_face_entry_t* entry = malloc(family_length + sizeof(plutovg_font_face_entry_t)); entry->face = plutovg_font_face_reference(face); entry->family = (char*)(entry + 1); memcpy(entry->family, family, family_length); entry->filename = NULL; entry->ttcindex = 0; entry->bold = bold; entry->italic = italic; plutovg_font_face_cache_add_entry(cache, entry); } bool plutovg_font_face_cache_add_file(plutovg_font_face_cache_t* cache, const char* family, bool bold, bool italic, const char* filename, int ttcindex) { plutovg_font_face_t* face = plutovg_font_face_load_from_file(filename, ttcindex); if(face == NULL) return false; plutovg_font_face_cache_add(cache, family, bold, italic, face); plutovg_font_face_destroy(face); return true; } static plutovg_font_face_entry_t* plutovg_font_face_entry_select(plutovg_font_face_entry_t* a, plutovg_font_face_entry_t* b, bool bold, bool italic) { int a_score = (bold == a->bold) + (italic == a->italic); int b_score = (bold == b->bold) + (italic == b->italic); return a_score > b_score ? a : b; } static int plutovg_font_face_entry_compare(const void* a, const void* b) { const plutovg_font_face_entry_t* a_entry = *(const plutovg_font_face_entry_t**)a; const plutovg_font_face_entry_t* b_entry = *(const plutovg_font_face_entry_t**)b; return strcmp(a_entry->family, b_entry->family); } plutovg_font_face_t* plutovg_font_face_cache_get(plutovg_font_face_cache_t* cache, const char* family, bool bold, bool italic) { plutovg_mutex_lock(&cache->mutex); if(!cache->is_sorted && cache->size > 0) { qsort(cache->entries, cache->size, sizeof(cache->entries[0]), plutovg_font_face_entry_compare); cache->is_sorted = true; } plutovg_font_face_entry_t entry_key; entry_key.family = (char*)(family); plutovg_font_face_entry_t* entry_key_ptr = &entry_key; plutovg_font_face_entry_t** entry_result = bsearch( &entry_key_ptr, cache->entries, cache->size, sizeof(cache->entries[0]), plutovg_font_face_entry_compare ); plutovg_font_face_t* face = NULL; if(entry_result) { plutovg_font_face_entry_t* selected = *entry_result; plutovg_font_face_entry_t* entry = selected->next; while(entry) { selected = plutovg_font_face_entry_select(entry, selected, bold, italic); entry = entry->next; } if(selected->filename && selected->face == NULL) selected->face = plutovg_font_face_load_from_file(selected->filename, selected->ttcindex); face = selected->face; } plutovg_mutex_unlock(&cache->mutex); return face; } #ifndef PLUTOVG_DISABLE_FONT_FACE_CACHE_LOAD #include #ifdef _WIN32 #include #else #include #include #include #ifdef __linux__ #include #else #include #endif #include #include #endif #ifdef _WIN32 static void* plutovg_mmap(const char* filename, long* length) { HANDLE file = CreateFileA(filename, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if(file == INVALID_HANDLE_VALUE) return NULL; DWORD size = GetFileSize(file, NULL); if(size == INVALID_FILE_SIZE) { CloseHandle(file); return NULL; } HANDLE mapping = CreateFileMappingA(file, NULL, PAGE_READONLY, 0, 0, NULL); if(mapping == NULL) { CloseHandle(file); return NULL; } void* data = MapViewOfFile(mapping, FILE_MAP_READ, 0, 0, 0); CloseHandle(mapping); CloseHandle(file); if(data == NULL) return NULL; *length = size; return data; } static void plutovg_unmap(void* data, long length) { UnmapViewOfFile(data); } #else static void* plutovg_mmap(const char* filename, long* length) { int fd = open(filename, O_RDONLY); if(fd < 0) return NULL; struct stat st; if(fstat(fd, &st) < 0) { close(fd); return NULL; } if(st.st_size == 0) { close(fd); return NULL; } void* data = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0); close(fd); if(data == MAP_FAILED) return NULL; *length = st.st_size; return data; } static void plutovg_unmap(void* data, long length) { munmap(data, length); } #endif // _WIN32 int plutovg_font_face_cache_load_file(plutovg_font_face_cache_t* cache, const char* filename) { long length; stbtt_uint8* data = plutovg_mmap(filename, &length); if(data == NULL) { return 0; } int num_faces = 0; int num_fonts = stbtt_GetNumberOfFonts(data); for(int index = 0; index < num_fonts; ++index) { int offset = stbtt_GetFontOffsetForIndex(data, index); if(offset == -1 || !stbtt__isfont(data + offset)) { continue; } stbtt_uint32 nm = stbtt__find_table(data, offset, "name"); stbtt_uint16 nm_count = ttUSHORT(data + nm + 2); const stbtt_uint8* unicode_family_name = NULL; const stbtt_uint8* roman_family_name = NULL; size_t family_length = 0; for(stbtt_int32 i = 0; i < nm_count; ++i) { stbtt_uint32 loc = nm + 6 + 12 * i; stbtt_uint16 nm_id = ttUSHORT(data + loc + 6); if(nm_id != 1) { continue; } stbtt_uint16 platform = ttUSHORT(data + loc + 0); stbtt_uint16 encoding = ttUSHORT(data + loc + 2); const stbtt_uint8* family_name = data + nm + ttUSHORT(data + nm + 4) + ttUSHORT(data + loc + 10); if(platform == 1 && encoding == 0) { family_length = ttUSHORT(data + loc + 8); roman_family_name = family_name; continue; } if(platform == 0 || (platform == 3 && encoding == 1) || (platform == 3 && encoding == 10)) { family_length = ttUSHORT(data + loc + 8); unicode_family_name = family_name; break; } } if(unicode_family_name == NULL && roman_family_name == NULL) continue; size_t filename_length = strlen(filename) + 1; size_t max_family_length = (unicode_family_name ? 3 * (family_length / 2) : family_length * 3) + 1; plutovg_font_face_entry_t* entry = malloc(max_family_length + filename_length + sizeof(plutovg_font_face_entry_t)); entry->family = (char*)(entry + 1); entry->filename = entry->family + max_family_length; memcpy(entry->filename, filename, filename_length); size_t family_index = 0; if(unicode_family_name) { const stbtt_uint8* family_name = unicode_family_name; while(family_length) { stbtt_uint16 ch = family_name[0] * 256 + family_name[1]; if(ch < 0x80) { entry->family[family_index++] = ch; } else if(ch < 0x800) { entry->family[family_index++] = (0xc0 + (ch >> 6)); entry->family[family_index++] = (0x80 + (ch & 0x3f)); } else if(ch >= 0xd800 && ch < 0xdc00) { stbtt_uint16 ch2 = family_name[2] * 256 + family_name[3]; stbtt_uint32 c = ((ch - 0xd800) << 10) + (ch2 - 0xdc00) + 0x10000; entry->family[family_index++] = (0xf0 + (c >> 18)); entry->family[family_index++] = (0x80 + ((c >> 12) & 0x3f)); entry->family[family_index++] = (0x80 + ((c >> 6) & 0x3f)); entry->family[family_index++] = (0x80 + ((c) & 0x3f)); family_name += 2; family_length -= 2; } else { entry->family[family_index++] = (0xe0 + (ch >> 12)); entry->family[family_index++] = (0x80 + ((ch >> 6) & 0x3f)); entry->family[family_index++] = (0x80 + ((ch) & 0x3f)); } family_name += 2; family_length -= 2; } entry->family[family_index] = '\0'; } else { static const stbtt_uint16 MAC_ROMAN_TABLE[256] = { 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, 0x0008, 0x0009, 0x000A, 0x000B, 0x000C, 0x000D, 0x000E, 0x000F, 0x0010, 0x2318, 0x21E7, 0x2325, 0x2303, 0x0015, 0x0016, 0x0017, 0x0018, 0x0019, 0x001A, 0x001B, 0x001C, 0x001D, 0x001E, 0x001F, 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, 0x0028, 0x0029, 0x002A, 0x002B, 0x002C, 0x002D, 0x002E, 0x002F, 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, 0x0038, 0x0039, 0x003A, 0x003B, 0x003C, 0x003D, 0x003E, 0x003F, 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, 0x0048, 0x0049, 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F, 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, 0x0058, 0x0059, 0x005A, 0x005B, 0x005C, 0x005D, 0x005E, 0x005F, 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, 0x0068, 0x0069, 0x006A, 0x006B, 0x006C, 0x006D, 0x006E, 0x006F, 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, 0x0078, 0x0079, 0x007A, 0x007B, 0x007C, 0x007D, 0x007E, 0x007F, 0x00C4, 0x00C5, 0x00C7, 0x00C9, 0x00D1, 0x00D6, 0x00DC, 0x00E1, 0x00E0, 0x00E2, 0x00E4, 0x00E3, 0x00E5, 0x00E7, 0x00E9, 0x00E8, 0x00EA, 0x00EB, 0x00ED, 0x00EC, 0x00EE, 0x00EF, 0x00F1, 0x00F3, 0x00F2, 0x00F4, 0x00F6, 0x00F5, 0x00FA, 0x00F9, 0x00FB, 0x00FC, 0x2020, 0x00B0, 0x00A2, 0x00A3, 0x00A7, 0x2022, 0x00B6, 0x00DF, 0x00AE, 0x00A9, 0x2122, 0x00B4, 0x00A8, 0x2260, 0x00C6, 0x00D8, 0x221E, 0x00B1, 0x2264, 0x2265, 0x00A5, 0x00B5, 0x2202, 0x2211, 0x220F, 0x03C0, 0x222B, 0x00AA, 0x00BA, 0x03A9, 0x00E6, 0x00F8, 0x00BF, 0x00A1, 0x00AC, 0x221A, 0x0192, 0x2248, 0x2206, 0x00AB, 0x00BB, 0x2026, 0x00A0, 0x00C0, 0x00C3, 0x00D5, 0x0152, 0x0153, 0x2013, 0x2014, 0x201C, 0x201D, 0x2018, 0x2019, 0x00F7, 0x25CA, 0x00FF, 0x0178, 0x2044, 0x20AC, 0x2039, 0x203A, 0xFB01, 0xFB02, 0x2021, 0x00B7, 0x201A, 0x201E, 0x2030, 0x00C2, 0x00CA, 0x00C1, 0x00CB, 0x00C8, 0x00CD, 0x00CE, 0x00CF, 0x00CC, 0x00D3, 0x00D4, 0xF8FF, 0x00D2, 0x00DA, 0x00DB, 0x00D9, 0x0131, 0x02C6, 0x02DC, 0x00AF, 0x02D8, 0x02D9, 0x02DA, 0x00B8, 0x02DD, 0x02DB, 0x02C7, }; const stbtt_uint8* family_name = roman_family_name; while(family_length) { stbtt_uint16 ch = MAC_ROMAN_TABLE[family_name[0]]; if(ch < 0x80) { entry->family[family_index++] = ch; } else if(ch < 0x800) { entry->family[family_index++] = (0xc0 + (ch >> 6)); entry->family[family_index++] = (0x80 + (ch & 0x3f)); } else { entry->family[family_index++] = (0xe0 + (ch >> 12)); entry->family[family_index++] = (0x80 + ((ch >> 6) & 0x3f)); entry->family[family_index++] = (0x80 + ((ch) & 0x3f)); } family_name += 1; family_length -= 1; } entry->family[family_index] = '\0'; } entry->face = NULL; entry->bold = false; entry->italic = false; entry->ttcindex = index; stbtt_uint32 hd = stbtt__find_table(data, offset, "head"); stbtt_uint16 style = ttUSHORT(data + hd + 44); if(style & 0x1) entry->bold = true; if(style & 0x2) { entry->italic = true; } plutovg_font_face_cache_add_entry(cache, entry); num_faces++; } plutovg_unmap(data, length); return num_faces; } static bool plutovg_font_face_supports_file(const char* filename) { const char* extension = strrchr(filename, '.'); if(extension) { char ext[5]; size_t length = strlen(extension); if(length == 4) { for(size_t i = 0; i < length; ++i) ext[i] = tolower(extension[i]); ext[length] = '\0'; return strcmp(ext, ".ttf") == 0 || strcmp(ext, ".otf") == 0 || strcmp(ext, ".ttc") == 0 || strcmp(ext, ".otc") == 0; } } return false; } #ifdef _WIN32 int plutovg_font_face_cache_load_dir(plutovg_font_face_cache_t* cache, const char* dirname) { char search_path[MAX_PATH]; snprintf(search_path, sizeof(search_path), "%s\\*", dirname); WIN32_FIND_DATAA find_data; HANDLE handle = FindFirstFileA(search_path, &find_data); if(handle == INVALID_HANDLE_VALUE) { return 0; } int num_faces = 0; do { const char* name = find_data.cFileName; if(strcmp(name, ".") == 0 || strcmp(name, "..") == 0) continue; char path[MAX_PATH * 2]; snprintf(path, sizeof(path), "%s\\%s", dirname, name); if(find_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { num_faces += plutovg_font_face_cache_load_dir(cache, path); } else if(plutovg_font_face_supports_file(path)) { num_faces += plutovg_font_face_cache_load_file(cache, path); } } while(FindNextFileA(handle, &find_data)); FindClose(handle); return num_faces; } #else int plutovg_font_face_cache_load_dir(plutovg_font_face_cache_t* cache, const char* dirname) { DIR* dir = opendir(dirname); if(dir == NULL) { return 0; } int num_faces = 0; struct dirent* entry; while((entry = readdir(dir)) != NULL) { if(strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) continue; char path[PATH_MAX]; snprintf(path, sizeof(path), "%s/%s", dirname, entry->d_name); struct stat st; if(stat(path, &st) == -1) continue; if(S_ISDIR(st.st_mode)) { num_faces += plutovg_font_face_cache_load_dir(cache, path); } else if(S_ISREG(st.st_mode) && plutovg_font_face_supports_file(path)) { num_faces += plutovg_font_face_cache_load_file(cache, path); } } closedir(dir); return num_faces; } #endif // _WIN32 int plutovg_font_face_cache_load_sys(plutovg_font_face_cache_t* cache) { int num_faces = 0; #if defined(_WIN32) num_faces += plutovg_font_face_cache_load_dir(cache, "C:\\Windows\\Fonts"); #elif defined(__APPLE__) num_faces += plutovg_font_face_cache_load_dir(cache, "/Library/Fonts"); num_faces += plutovg_font_face_cache_load_dir(cache, "/System/Library/Fonts"); #elif defined(__linux__) num_faces += plutovg_font_face_cache_load_dir(cache, "/usr/share/fonts"); num_faces += plutovg_font_face_cache_load_dir(cache, "/usr/local/share/fonts"); #endif return num_faces; } #else int plutovg_font_face_cache_load_file(plutovg_font_face_cache_t* cache, const char* filename) { return -1; } int plutovg_font_face_cache_load_dir(plutovg_font_face_cache_t* cache, const char* dirname) { return -1; } int plutovg_font_face_cache_load_sys(plutovg_font_face_cache_t* cache) { return -1; } #endif // PLUTOVG_DISABLE_FONT_FACE_CACHE_LOAD sammycage-plutovg-5695a71/source/plutovg-ft-math.c000066400000000000000000000267231510714322500221550ustar00rootroot00000000000000/***************************************************************************/ /* */ /* fttrigon.c */ /* */ /* FreeType trigonometric functions (body). */ /* */ /* Copyright 2001-2005, 2012-2013 by */ /* David Turner, Robert Wilhelm, and Werner Lemberg. */ /* */ /* This file is part of the FreeType project, and may only be used, */ /* modified, and distributed under the terms of the FreeType project */ /* license, FTL.TXT. By continuing to use, modify, or distribute */ /* this file you indicate that you have read the license and */ /* understand and accept it fully. */ /* */ /***************************************************************************/ #include "plutovg-ft-math.h" #if defined(_MSC_VER) #include static inline int clz(unsigned int x) { unsigned long r = 0; if (_BitScanReverse(&r, x)) return 31 - r; return 32; } #define PVG_FT_MSB(x) (31 - clz(x)) #elif defined(__GNUC__) #define PVG_FT_MSB(x) (31 - __builtin_clz(x)) #else static inline int clz(unsigned int x) { int n = 0; if (x == 0) return 32; if (x <= 0x0000FFFFU) { n += 16; x <<= 16; } if (x <= 0x00FFFFFFU) { n += 8; x <<= 8; } if (x <= 0x0FFFFFFFU) { n += 4; x <<= 4; } if (x <= 0x3FFFFFFFU) { n += 2; x <<= 2; } if (x <= 0x7FFFFFFFU) { n += 1; } return n; } #define PVG_FT_MSB(x) (31 - clz(x)) #endif #define PVG_FT_PAD_FLOOR(x, n) ((x) & ~((n)-1)) #define PVG_FT_PAD_ROUND(x, n) PVG_FT_PAD_FLOOR((x) + ((n) / 2), n) #define PVG_FT_PAD_CEIL(x, n) PVG_FT_PAD_FLOOR((x) + ((n)-1), n) /* transfer sign leaving a positive number */ #define PVG_FT_MOVE_SIGN(x, s) \ PVG_FT_BEGIN_STMNT \ if (x < 0) { \ x = -x; \ s = -s; \ } \ PVG_FT_END_STMNT PVG_FT_Long PVG_FT_MulFix(PVG_FT_Long a, PVG_FT_Long b) { PVG_FT_Int s = 1; PVG_FT_Long c; PVG_FT_MOVE_SIGN(a, s); PVG_FT_MOVE_SIGN(b, s); c = (PVG_FT_Long)(((PVG_FT_Int64)a * b + 0x8000L) >> 16); return (s > 0) ? c : -c; } PVG_FT_Long PVG_FT_MulDiv(PVG_FT_Long a, PVG_FT_Long b, PVG_FT_Long c) { PVG_FT_Int s = 1; PVG_FT_Long d; PVG_FT_MOVE_SIGN(a, s); PVG_FT_MOVE_SIGN(b, s); PVG_FT_MOVE_SIGN(c, s); d = (PVG_FT_Long)(c > 0 ? ((PVG_FT_Int64)a * b + (c >> 1)) / c : 0x7FFFFFFFL); return (s > 0) ? d : -d; } PVG_FT_Long PVG_FT_DivFix(PVG_FT_Long a, PVG_FT_Long b) { PVG_FT_Int s = 1; PVG_FT_Long q; PVG_FT_MOVE_SIGN(a, s); PVG_FT_MOVE_SIGN(b, s); q = (PVG_FT_Long)(b > 0 ? (((PVG_FT_UInt64)a << 16) + (b >> 1)) / b : 0x7FFFFFFFL); return (s < 0 ? -q : q); } /*************************************************************************/ /* */ /* This is a fixed-point CORDIC implementation of trigonometric */ /* functions as well as transformations between Cartesian and polar */ /* coordinates. The angles are represented as 16.16 fixed-point values */ /* in degrees, i.e., the angular resolution is 2^-16 degrees. Note that */ /* only vectors longer than 2^16*180/pi (or at least 22 bits) on a */ /* discrete Cartesian grid can have the same or better angular */ /* resolution. Therefore, to maintain this precision, some functions */ /* require an interim upscaling of the vectors, whereas others operate */ /* with 24-bit long vectors directly. */ /* */ /*************************************************************************/ /* the Cordic shrink factor 0.858785336480436 * 2^32 */ #define PVG_FT_TRIG_SCALE 0xDBD95B16UL /* the highest bit in overflow-safe vector components, */ /* MSB of 0.858785336480436 * sqrt(0.5) * 2^30 */ #define PVG_FT_TRIG_SAFE_MSB 29 /* this table was generated for PVG_FT_PI = 180L << 16, i.e. degrees */ #define PVG_FT_TRIG_MAX_ITERS 23 static const PVG_FT_Fixed ft_trig_arctan_table[] = { 1740967L, 919879L, 466945L, 234379L, 117304L, 58666L, 29335L, 14668L, 7334L, 3667L, 1833L, 917L, 458L, 229L, 115L, 57L, 29L, 14L, 7L, 4L, 2L, 1L}; /* multiply a given value by the CORDIC shrink factor */ static PVG_FT_Fixed ft_trig_downscale(PVG_FT_Fixed val) { PVG_FT_Fixed s; PVG_FT_Int64 v; s = val; val = PVG_FT_ABS(val); v = (val * (PVG_FT_Int64)PVG_FT_TRIG_SCALE) + 0x100000000UL; val = (PVG_FT_Fixed)(v >> 32); return (s >= 0) ? val : -val; } /* undefined and never called for zero vector */ static PVG_FT_Int ft_trig_prenorm(PVG_FT_Vector* vec) { PVG_FT_Pos x, y; PVG_FT_Int shift; x = vec->x; y = vec->y; shift = PVG_FT_MSB(PVG_FT_ABS(x) | PVG_FT_ABS(y)); if (shift <= PVG_FT_TRIG_SAFE_MSB) { shift = PVG_FT_TRIG_SAFE_MSB - shift; vec->x = (PVG_FT_Pos)((PVG_FT_ULong)x << shift); vec->y = (PVG_FT_Pos)((PVG_FT_ULong)y << shift); } else { shift -= PVG_FT_TRIG_SAFE_MSB; vec->x = x >> shift; vec->y = y >> shift; shift = -shift; } return shift; } static void ft_trig_pseudo_rotate(PVG_FT_Vector* vec, PVG_FT_Angle theta) { PVG_FT_Int i; PVG_FT_Fixed x, y, xtemp, b; const PVG_FT_Fixed* arctanptr; x = vec->x; y = vec->y; /* Rotate inside [-PI/4,PI/4] sector */ while (theta < -PVG_FT_ANGLE_PI4) { xtemp = y; y = -x; x = xtemp; theta += PVG_FT_ANGLE_PI2; } while (theta > PVG_FT_ANGLE_PI4) { xtemp = -y; y = x; x = xtemp; theta -= PVG_FT_ANGLE_PI2; } arctanptr = ft_trig_arctan_table; /* Pseudorotations, with right shifts */ for (i = 1, b = 1; i < PVG_FT_TRIG_MAX_ITERS; b <<= 1, i++) { PVG_FT_Fixed v1 = ((y + b) >> i); PVG_FT_Fixed v2 = ((x + b) >> i); if (theta < 0) { xtemp = x + v1; y = y - v2; x = xtemp; theta += *arctanptr++; } else { xtemp = x - v1; y = y + v2; x = xtemp; theta -= *arctanptr++; } } vec->x = x; vec->y = y; } static void ft_trig_pseudo_polarize(PVG_FT_Vector* vec) { PVG_FT_Angle theta; PVG_FT_Int i; PVG_FT_Fixed x, y, xtemp, b; const PVG_FT_Fixed* arctanptr; x = vec->x; y = vec->y; /* Get the vector into [-PI/4,PI/4] sector */ if (y > x) { if (y > -x) { theta = PVG_FT_ANGLE_PI2; xtemp = y; y = -x; x = xtemp; } else { theta = y > 0 ? PVG_FT_ANGLE_PI : -PVG_FT_ANGLE_PI; x = -x; y = -y; } } else { if (y < -x) { theta = -PVG_FT_ANGLE_PI2; xtemp = -y; y = x; x = xtemp; } else { theta = 0; } } arctanptr = ft_trig_arctan_table; /* Pseudorotations, with right shifts */ for (i = 1, b = 1; i < PVG_FT_TRIG_MAX_ITERS; b <<= 1, i++) { PVG_FT_Fixed v1 = ((y + b) >> i); PVG_FT_Fixed v2 = ((x + b) >> i); if (y > 0) { xtemp = x + v1; y = y - v2; x = xtemp; theta += *arctanptr++; } else { xtemp = x - v1; y = y + v2; x = xtemp; theta -= *arctanptr++; } } /* round theta */ if (theta >= 0) theta = PVG_FT_PAD_ROUND(theta, 32); else theta = -PVG_FT_PAD_ROUND(-theta, 32); vec->x = x; vec->y = theta; } /* documentation is in fttrigon.h */ PVG_FT_Fixed PVG_FT_Cos(PVG_FT_Angle angle) { PVG_FT_Vector v; v.x = PVG_FT_TRIG_SCALE >> 8; v.y = 0; ft_trig_pseudo_rotate(&v, angle); return (v.x + 0x80L) >> 8; } /* documentation is in fttrigon.h */ PVG_FT_Fixed PVG_FT_Sin(PVG_FT_Angle angle) { return PVG_FT_Cos(PVG_FT_ANGLE_PI2 - angle); } /* documentation is in fttrigon.h */ PVG_FT_Fixed PVG_FT_Tan(PVG_FT_Angle angle) { PVG_FT_Vector v; v.x = PVG_FT_TRIG_SCALE >> 8; v.y = 0; ft_trig_pseudo_rotate(&v, angle); return PVG_FT_DivFix(v.y, v.x); } /* documentation is in fttrigon.h */ PVG_FT_Angle PVG_FT_Atan2(PVG_FT_Fixed dx, PVG_FT_Fixed dy) { PVG_FT_Vector v; if (dx == 0 && dy == 0) return 0; v.x = dx; v.y = dy; ft_trig_prenorm(&v); ft_trig_pseudo_polarize(&v); return v.y; } /* documentation is in fttrigon.h */ void PVG_FT_Vector_Unit(PVG_FT_Vector* vec, PVG_FT_Angle angle) { vec->x = PVG_FT_TRIG_SCALE >> 8; vec->y = 0; ft_trig_pseudo_rotate(vec, angle); vec->x = (vec->x + 0x80L) >> 8; vec->y = (vec->y + 0x80L) >> 8; } void PVG_FT_Vector_Rotate(PVG_FT_Vector* vec, PVG_FT_Angle angle) { PVG_FT_Int shift; PVG_FT_Vector v = *vec; if ( v.x == 0 && v.y == 0 ) return; shift = ft_trig_prenorm( &v ); ft_trig_pseudo_rotate( &v, angle ); v.x = ft_trig_downscale( v.x ); v.y = ft_trig_downscale( v.y ); if ( shift > 0 ) { PVG_FT_Int32 half = (PVG_FT_Int32)1L << ( shift - 1 ); vec->x = ( v.x + half - ( v.x < 0 ) ) >> shift; vec->y = ( v.y + half - ( v.y < 0 ) ) >> shift; } else { shift = -shift; vec->x = (PVG_FT_Pos)( (PVG_FT_ULong)v.x << shift ); vec->y = (PVG_FT_Pos)( (PVG_FT_ULong)v.y << shift ); } } /* documentation is in fttrigon.h */ PVG_FT_Fixed PVG_FT_Vector_Length(PVG_FT_Vector* vec) { PVG_FT_Int shift; PVG_FT_Vector v; v = *vec; /* handle trivial cases */ if (v.x == 0) { return PVG_FT_ABS(v.y); } else if (v.y == 0) { return PVG_FT_ABS(v.x); } /* general case */ shift = ft_trig_prenorm(&v); ft_trig_pseudo_polarize(&v); v.x = ft_trig_downscale(v.x); if (shift > 0) return (v.x + (1 << (shift - 1))) >> shift; return (PVG_FT_Fixed)((PVG_FT_UInt32)v.x << -shift); } /* documentation is in fttrigon.h */ void PVG_FT_Vector_Polarize(PVG_FT_Vector* vec, PVG_FT_Fixed* length, PVG_FT_Angle* angle) { PVG_FT_Int shift; PVG_FT_Vector v; v = *vec; if (v.x == 0 && v.y == 0) return; shift = ft_trig_prenorm(&v); ft_trig_pseudo_polarize(&v); v.x = ft_trig_downscale(v.x); *length = (shift >= 0) ? (v.x >> shift) : (PVG_FT_Fixed)((PVG_FT_UInt32)v.x << -shift); *angle = v.y; } /* documentation is in fttrigon.h */ void PVG_FT_Vector_From_Polar(PVG_FT_Vector* vec, PVG_FT_Fixed length, PVG_FT_Angle angle) { vec->x = length; vec->y = 0; PVG_FT_Vector_Rotate(vec, angle); } /* documentation is in fttrigon.h */ PVG_FT_Angle PVG_FT_Angle_Diff( PVG_FT_Angle angle1, PVG_FT_Angle angle2 ) { PVG_FT_Angle delta = angle2 - angle1; while ( delta <= -PVG_FT_ANGLE_PI ) delta += PVG_FT_ANGLE_2PI; while ( delta > PVG_FT_ANGLE_PI ) delta -= PVG_FT_ANGLE_2PI; return delta; } /* END */ sammycage-plutovg-5695a71/source/plutovg-ft-math.h000066400000000000000000000334201510714322500221520ustar00rootroot00000000000000/***************************************************************************/ /* */ /* fttrigon.h */ /* */ /* FreeType trigonometric functions (specification). */ /* */ /* Copyright 2001, 2003, 2005, 2007, 2013 by */ /* David Turner, Robert Wilhelm, and Werner Lemberg. */ /* */ /* This file is part of the FreeType project, and may only be used, */ /* modified, and distributed under the terms of the FreeType project */ /* license, FTL.TXT. By continuing to use, modify, or distribute */ /* this file you indicate that you have read the license and */ /* understand and accept it fully. */ /* */ /***************************************************************************/ #ifndef PLUTOVG_FT_MATH_H #define PLUTOVG_FT_MATH_H #include "plutovg-ft-types.h" /*************************************************************************/ /* */ /* The min and max functions missing in C. As usual, be careful not to */ /* write things like PVG_FT_MIN( a++, b++ ) to avoid side effects. */ /* */ #define PVG_FT_MIN( a, b ) ( (a) < (b) ? (a) : (b) ) #define PVG_FT_MAX( a, b ) ( (a) > (b) ? (a) : (b) ) #define PVG_FT_ABS( a ) ( (a) < 0 ? -(a) : (a) ) /* * Approximate sqrt(x*x+y*y) using the `alpha max plus beta min' * algorithm. We use alpha = 1, beta = 3/8, giving us results with a * largest error less than 7% compared to the exact value. */ #define PVG_FT_HYPOT( x, y ) \ ( x = PVG_FT_ABS( x ), \ y = PVG_FT_ABS( y ), \ x > y ? x + ( 3 * y >> 3 ) \ : y + ( 3 * x >> 3 ) ) /*************************************************************************/ /* */ /* */ /* PVG_FT_MulFix */ /* */ /* */ /* A very simple function used to perform the computation */ /* `(a*b)/0x10000' with maximum accuracy. Most of the time this is */ /* used to multiply a given value by a 16.16 fixed-point factor. */ /* */ /* */ /* a :: The first multiplier. */ /* b :: The second multiplier. Use a 16.16 factor here whenever */ /* possible (see note below). */ /* */ /* */ /* The result of `(a*b)/0x10000'. */ /* */ /* */ /* This function has been optimized for the case where the absolute */ /* value of `a' is less than 2048, and `b' is a 16.16 scaling factor. */ /* As this happens mainly when scaling from notional units to */ /* fractional pixels in FreeType, it resulted in noticeable speed */ /* improvements between versions 2.x and 1.x. */ /* */ /* As a conclusion, always try to place a 16.16 factor as the */ /* _second_ argument of this function; this can make a great */ /* difference. */ /* */ PVG_FT_Long PVG_FT_MulFix( PVG_FT_Long a, PVG_FT_Long b ); /*************************************************************************/ /* */ /* */ /* PVG_FT_MulDiv */ /* */ /* */ /* A very simple function used to perform the computation `(a*b)/c' */ /* with maximum accuracy (it uses a 64-bit intermediate integer */ /* whenever necessary). */ /* */ /* This function isn't necessarily as fast as some processor specific */ /* operations, but is at least completely portable. */ /* */ /* */ /* a :: The first multiplier. */ /* b :: The second multiplier. */ /* c :: The divisor. */ /* */ /* */ /* The result of `(a*b)/c'. This function never traps when trying to */ /* divide by zero; it simply returns `MaxInt' or `MinInt' depending */ /* on the signs of `a' and `b'. */ /* */ PVG_FT_Long PVG_FT_MulDiv( PVG_FT_Long a, PVG_FT_Long b, PVG_FT_Long c ); /*************************************************************************/ /* */ /* */ /* PVG_FT_DivFix */ /* */ /* */ /* A very simple function used to perform the computation */ /* `(a*0x10000)/b' with maximum accuracy. Most of the time, this is */ /* used to divide a given value by a 16.16 fixed-point factor. */ /* */ /* */ /* a :: The numerator. */ /* b :: The denominator. Use a 16.16 factor here. */ /* */ /* */ /* The result of `(a*0x10000)/b'. */ /* */ PVG_FT_Long PVG_FT_DivFix( PVG_FT_Long a, PVG_FT_Long b ); /*************************************************************************/ /* */ /*
*/ /* computations */ /* */ /*************************************************************************/ /************************************************************************* * * @type: * PVG_FT_Angle * * @description: * This type is used to model angle values in FreeType. Note that the * angle is a 16.16 fixed-point value expressed in degrees. * */ typedef PVG_FT_Fixed PVG_FT_Angle; /************************************************************************* * * @macro: * PVG_FT_ANGLE_PI * * @description: * The angle pi expressed in @PVG_FT_Angle units. * */ #define PVG_FT_ANGLE_PI ( 180L << 16 ) /************************************************************************* * * @macro: * PVG_FT_ANGLE_2PI * * @description: * The angle 2*pi expressed in @PVG_FT_Angle units. * */ #define PVG_FT_ANGLE_2PI ( PVG_FT_ANGLE_PI * 2 ) /************************************************************************* * * @macro: * PVG_FT_ANGLE_PI2 * * @description: * The angle pi/2 expressed in @PVG_FT_Angle units. * */ #define PVG_FT_ANGLE_PI2 ( PVG_FT_ANGLE_PI / 2 ) /************************************************************************* * * @macro: * PVG_FT_ANGLE_PI4 * * @description: * The angle pi/4 expressed in @PVG_FT_Angle units. * */ #define PVG_FT_ANGLE_PI4 ( PVG_FT_ANGLE_PI / 4 ) /************************************************************************* * * @function: * PVG_FT_Sin * * @description: * Return the sinus of a given angle in fixed-point format. * * @input: * angle :: * The input angle. * * @return: * The sinus value. * * @note: * If you need both the sinus and cosinus for a given angle, use the * function @PVG_FT_Vector_Unit. * */ PVG_FT_Fixed PVG_FT_Sin( PVG_FT_Angle angle ); /************************************************************************* * * @function: * PVG_FT_Cos * * @description: * Return the cosinus of a given angle in fixed-point format. * * @input: * angle :: * The input angle. * * @return: * The cosinus value. * * @note: * If you need both the sinus and cosinus for a given angle, use the * function @PVG_FT_Vector_Unit. * */ PVG_FT_Fixed PVG_FT_Cos( PVG_FT_Angle angle ); /************************************************************************* * * @function: * PVG_FT_Tan * * @description: * Return the tangent of a given angle in fixed-point format. * * @input: * angle :: * The input angle. * * @return: * The tangent value. * */ PVG_FT_Fixed PVG_FT_Tan( PVG_FT_Angle angle ); /************************************************************************* * * @function: * PVG_FT_Atan2 * * @description: * Return the arc-tangent corresponding to a given vector (x,y) in * the 2d plane. * * @input: * x :: * The horizontal vector coordinate. * * y :: * The vertical vector coordinate. * * @return: * The arc-tangent value (i.e. angle). * */ PVG_FT_Angle PVG_FT_Atan2( PVG_FT_Fixed x, PVG_FT_Fixed y ); /************************************************************************* * * @function: * PVG_FT_Angle_Diff * * @description: * Return the difference between two angles. The result is always * constrained to the ]-PI..PI] interval. * * @input: * angle1 :: * First angle. * * angle2 :: * Second angle. * * @return: * Constrained value of `value2-value1'. * */ PVG_FT_Angle PVG_FT_Angle_Diff( PVG_FT_Angle angle1, PVG_FT_Angle angle2 ); /************************************************************************* * * @function: * PVG_FT_Vector_Unit * * @description: * Return the unit vector corresponding to a given angle. After the * call, the value of `vec.x' will be `sin(angle)', and the value of * `vec.y' will be `cos(angle)'. * * This function is useful to retrieve both the sinus and cosinus of a * given angle quickly. * * @output: * vec :: * The address of target vector. * * @input: * angle :: * The input angle. * */ void PVG_FT_Vector_Unit( PVG_FT_Vector* vec, PVG_FT_Angle angle ); /************************************************************************* * * @function: * PVG_FT_Vector_Rotate * * @description: * Rotate a vector by a given angle. * * @inout: * vec :: * The address of target vector. * * @input: * angle :: * The input angle. * */ void PVG_FT_Vector_Rotate( PVG_FT_Vector* vec, PVG_FT_Angle angle ); /************************************************************************* * * @function: * PVG_FT_Vector_Length * * @description: * Return the length of a given vector. * * @input: * vec :: * The address of target vector. * * @return: * The vector length, expressed in the same units that the original * vector coordinates. * */ PVG_FT_Fixed PVG_FT_Vector_Length( PVG_FT_Vector* vec ); /************************************************************************* * * @function: * PVG_FT_Vector_Polarize * * @description: * Compute both the length and angle of a given vector. * * @input: * vec :: * The address of source vector. * * @output: * length :: * The vector length. * * angle :: * The vector angle. * */ void PVG_FT_Vector_Polarize( PVG_FT_Vector* vec, PVG_FT_Fixed *length, PVG_FT_Angle *angle ); /************************************************************************* * * @function: * PVG_FT_Vector_From_Polar * * @description: * Compute vector coordinates from a length and angle. * * @output: * vec :: * The address of source vector. * * @input: * length :: * The vector length. * * angle :: * The vector angle. * */ void PVG_FT_Vector_From_Polar( PVG_FT_Vector* vec, PVG_FT_Fixed length, PVG_FT_Angle angle ); #endif /* PLUTOVG_FT_MATH_H */ sammycage-plutovg-5695a71/source/plutovg-ft-raster.c000066400000000000000000001547751510714322500225350ustar00rootroot00000000000000/***************************************************************************/ /* */ /* ftgrays.c */ /* */ /* A new `perfect' anti-aliasing renderer (body). */ /* */ /* Copyright 2000-2003, 2005-2014 by */ /* David Turner, Robert Wilhelm, and Werner Lemberg. */ /* */ /* This file is part of the FreeType project, and may only be used, */ /* modified, and distributed under the terms of the FreeType project */ /* license, FTL.TXT. By continuing to use, modify, or distribute */ /* this file you indicate that you have read the license and */ /* understand and accept it fully. */ /* */ /***************************************************************************/ /*************************************************************************/ /* */ /* This is a new anti-aliasing scan-converter for FreeType 2. The */ /* algorithm used here is _very_ different from the one in the standard */ /* `ftraster' module. Actually, `ftgrays' computes the _exact_ */ /* coverage of the outline on each pixel cell. */ /* */ /* It is based on ideas that I initially found in Raph Levien's */ /* excellent LibArt graphics library (see http://www.levien.com/libart */ /* for more information, though the web pages do not tell anything */ /* about the renderer; you'll have to dive into the source code to */ /* understand how it works). */ /* */ /* Note, however, that this is a _very_ different implementation */ /* compared to Raph's. Coverage information is stored in a very */ /* different way, and I don't use sorted vector paths. Also, it doesn't */ /* use floating point values. */ /* */ /* This renderer has the following advantages: */ /* */ /* - It doesn't need an intermediate bitmap. Instead, one can supply a */ /* callback function that will be called by the renderer to draw gray */ /* spans on any target surface. You can thus do direct composition on */ /* any kind of bitmap, provided that you give the renderer the right */ /* callback. */ /* */ /* - A perfect anti-aliaser, i.e., it computes the _exact_ coverage on */ /* each pixel cell. */ /* */ /* - It performs a single pass on the outline (the `standard' FT2 */ /* renderer makes two passes). */ /* */ /* - It can easily be modified to render to _any_ number of gray levels */ /* cheaply. */ /* */ /* - For small (< 20) pixel sizes, it is faster than the standard */ /* renderer. */ /* */ /*************************************************************************/ #include "plutovg-ft-raster.h" #include "plutovg-ft-math.h" #include #define pvg_ft_setjmp setjmp #define pvg_ft_longjmp longjmp #define pvg_ft_jmp_buf jmp_buf #include typedef ptrdiff_t PVG_FT_PtrDist; #define ErrRaster_Invalid_Mode -2 #define ErrRaster_Invalid_Outline -1 #define ErrRaster_Invalid_Argument -3 #define ErrRaster_Memory_Overflow -4 #define ErrRaster_OutOfMemory -6 #include #include #define PVG_FT_MINIMUM_POOL_SIZE 8192 #define RAS_ARG PWorker worker #define RAS_ARG_ PWorker worker, #define RAS_VAR worker #define RAS_VAR_ worker, #define ras (*worker) /* must be at least 6 bits! */ #define PIXEL_BITS 8 #define ONE_PIXEL ( 1L << PIXEL_BITS ) #define TRUNC( x ) (TCoord)( (x) >> PIXEL_BITS ) #define FRACT( x ) (TCoord)( (x) & ( ONE_PIXEL - 1 ) ) #if PIXEL_BITS >= 6 #define UPSCALE( x ) ( (x) * ( ONE_PIXEL >> 6 ) ) #define DOWNSCALE( x ) ( (x) >> ( PIXEL_BITS - 6 ) ) #else #define UPSCALE( x ) ( (x) >> ( 6 - PIXEL_BITS ) ) #define DOWNSCALE( x ) ( (x) * ( 64 >> PIXEL_BITS ) ) #endif /* Compute `dividend / divisor' and return both its quotient and */ /* remainder, cast to a specific type. This macro also ensures that */ /* the remainder is always positive. */ #define PVG_FT_DIV_MOD( type, dividend, divisor, quotient, remainder ) \ PVG_FT_BEGIN_STMNT \ (quotient) = (type)( (dividend) / (divisor) ); \ (remainder) = (type)( (dividend) % (divisor) ); \ if ( (remainder) < 0 ) \ { \ (quotient)--; \ (remainder) += (type)(divisor); \ } \ PVG_FT_END_STMNT /* These macros speed up repetitive divisions by replacing them */ /* with multiplications and right shifts. */ #define PVG_FT_UDIVPREP( b ) \ long b ## _r = (long)( ULONG_MAX >> PIXEL_BITS ) / ( b ) #define PVG_FT_UDIV( a, b ) \ ( ( (unsigned long)( a ) * (unsigned long)( b ## _r ) ) >> \ ( sizeof( long ) * CHAR_BIT - PIXEL_BITS ) ) /*************************************************************************/ /* */ /* TYPE DEFINITIONS */ /* */ /* don't change the following types to PVG_FT_Int or PVG_FT_Pos, since we might */ /* need to define them to "float" or "double" when experimenting with */ /* new algorithms */ typedef long TCoord; /* integer scanline/pixel coordinate */ typedef long TPos; /* sub-pixel coordinate */ typedef long TArea ; /* cell areas, coordinate products */ /* maximal number of gray spans in a call to the span callback */ #define PVG_FT_MAX_GRAY_SPANS 256 typedef struct TCell_* PCell; typedef struct TCell_ { int x; int cover; TArea area; PCell next; } TCell; typedef struct TWorker_ { TCoord ex, ey; TPos min_ex, max_ex; TPos min_ey, max_ey; TPos count_ex, count_ey; TArea area; int cover; int invalid; PCell cells; PVG_FT_PtrDist max_cells; PVG_FT_PtrDist num_cells; TPos x, y; PVG_FT_Outline outline; PVG_FT_BBox clip_box; int clip_flags; int clipping; PVG_FT_Span gray_spans[PVG_FT_MAX_GRAY_SPANS]; int num_gray_spans; int skip_spans; PVG_FT_Raster_Span_Func render_span; void* render_span_data; int band_size; int band_shoot; pvg_ft_jmp_buf jump_buffer; void* buffer; long buffer_size; PCell* ycells; TPos ycount; } TWorker, *PWorker; /*************************************************************************/ /* */ /* Initialize the cells table. */ /* */ static void gray_init_cells( RAS_ARG_ void* buffer, long byte_size ) { ras.buffer = buffer; ras.buffer_size = byte_size; ras.ycells = (PCell*) buffer; ras.cells = NULL; ras.max_cells = 0; ras.num_cells = 0; ras.area = 0; ras.cover = 0; ras.invalid = 1; } /*************************************************************************/ /* */ /* Compute the outline bounding box. */ /* */ static void gray_compute_cbox( RAS_ARG ) { PVG_FT_Outline* outline = &ras.outline; PVG_FT_Vector* vec = outline->points; PVG_FT_Vector* limit = vec + outline->n_points; if ( outline->n_points <= 0 ) { ras.min_ex = ras.max_ex = 0; ras.min_ey = ras.max_ey = 0; return; } ras.min_ex = ras.max_ex = vec->x; ras.min_ey = ras.max_ey = vec->y; vec++; for ( ; vec < limit; vec++ ) { TPos x = vec->x; TPos y = vec->y; if ( x < ras.min_ex ) ras.min_ex = x; if ( x > ras.max_ex ) ras.max_ex = x; if ( y < ras.min_ey ) ras.min_ey = y; if ( y > ras.max_ey ) ras.max_ey = y; } /* truncate the bounding box to integer pixels */ ras.min_ex = ras.min_ex >> 6; ras.min_ey = ras.min_ey >> 6; ras.max_ex = ( ras.max_ex + 63 ) >> 6; ras.max_ey = ( ras.max_ey + 63 ) >> 6; } /*************************************************************************/ /* */ /* Record the current cell in the table. */ /* */ static PCell gray_find_cell( RAS_ARG ) { PCell *pcell, cell; TPos x = ras.ex; if ( x > ras.count_ex ) x = ras.count_ex; pcell = &ras.ycells[ras.ey]; for (;;) { cell = *pcell; if ( cell == NULL || cell->x > x ) break; if ( cell->x == x ) goto Exit; pcell = &cell->next; } if ( ras.num_cells >= ras.max_cells ) pvg_ft_longjmp( ras.jump_buffer, 1 ); cell = ras.cells + ras.num_cells++; cell->x = x; cell->area = 0; cell->cover = 0; cell->next = *pcell; *pcell = cell; Exit: return cell; } static void gray_record_cell( RAS_ARG ) { if ( ras.area | ras.cover ) { PCell cell = gray_find_cell( RAS_VAR ); cell->area += ras.area; cell->cover += ras.cover; } } /*************************************************************************/ /* */ /* Set the current cell to a new position. */ /* */ static void gray_set_cell( RAS_ARG_ TCoord ex, TCoord ey ) { /* Move the cell pointer to a new position. We set the `invalid' */ /* flag to indicate that the cell isn't part of those we're interested */ /* in during the render phase. This means that: */ /* */ /* . the new vertical position must be within min_ey..max_ey-1. */ /* . the new horizontal position must be strictly less than max_ex */ /* */ /* Note that if a cell is to the left of the clipping region, it is */ /* actually set to the (min_ex-1) horizontal position. */ /* All cells that are on the left of the clipping region go to the */ /* min_ex - 1 horizontal position. */ ey -= ras.min_ey; if ( ex > ras.max_ex ) ex = ras.max_ex; ex -= ras.min_ex; if ( ex < 0 ) ex = -1; /* are we moving to a different cell ? */ if ( ex != ras.ex || ey != ras.ey ) { /* record the current one if it is valid */ if ( !ras.invalid ) gray_record_cell( RAS_VAR ); ras.area = 0; ras.cover = 0; ras.ex = ex; ras.ey = ey; } ras.invalid = ( (unsigned int)ey >= (unsigned int)ras.count_ey || ex >= ras.count_ex ); } /*************************************************************************/ /* */ /* Start a new contour at a given cell. */ /* */ static void gray_start_cell( RAS_ARG_ TCoord ex, TCoord ey ) { if ( ex > ras.max_ex ) ex = (TCoord)( ras.max_ex ); if ( ex < ras.min_ex ) ex = (TCoord)( ras.min_ex - 1 ); ras.area = 0; ras.cover = 0; ras.ex = ex - ras.min_ex; ras.ey = ey - ras.min_ey; ras.invalid = 0; gray_set_cell( RAS_VAR_ ex, ey ); } // The new render-line implementation is not yet used #if 1 /*************************************************************************/ /* */ /* Render a scanline as one or more cells. */ /* */ static void gray_render_scanline( RAS_ARG_ TCoord ey, TPos x1, TCoord y1, TPos x2, TCoord y2 ) { TCoord ex1, ex2, fx1, fx2, first, dy, delta, mod; TPos p, dx; int incr; ex1 = TRUNC( x1 ); ex2 = TRUNC( x2 ); /* trivial case. Happens often */ if ( y1 == y2 ) { gray_set_cell( RAS_VAR_ ex2, ey ); return; } fx1 = FRACT( x1 ); fx2 = FRACT( x2 ); /* everything is located in a single cell. That is easy! */ /* */ if ( ex1 == ex2 ) goto End; /* ok, we'll have to render a run of adjacent cells on the same */ /* scanline... */ /* */ dx = x2 - x1; dy = y2 - y1; if ( dx > 0 ) { p = ( ONE_PIXEL - fx1 ) * dy; first = ONE_PIXEL; incr = 1; } else { p = fx1 * dy; first = 0; incr = -1; dx = -dx; } PVG_FT_DIV_MOD( TCoord, p, dx, delta, mod ); ras.area += (TArea)( fx1 + first ) * delta; ras.cover += delta; y1 += delta; ex1 += incr; gray_set_cell( RAS_VAR_ ex1, ey ); if ( ex1 != ex2 ) { TCoord lift, rem; p = ONE_PIXEL * dy; PVG_FT_DIV_MOD( TCoord, p, dx, lift, rem ); do { delta = lift; mod += rem; if ( mod >= (TCoord)dx ) { mod -= (TCoord)dx; delta++; } ras.area += (TArea)( ONE_PIXEL * delta ); ras.cover += delta; y1 += delta; ex1 += incr; gray_set_cell( RAS_VAR_ ex1, ey ); } while ( ex1 != ex2 ); } fx1 = ONE_PIXEL - first; End: dy = y2 - y1; ras.area += (TArea)( ( fx1 + fx2 ) * dy ); ras.cover += dy; } /*************************************************************************/ /* */ /* Render a given line as a series of scanlines. */ /* */ static void gray_render_line( RAS_ARG_ TPos from_x, TPos from_y, TPos to_x, TPos to_y ) { TCoord ey1, ey2, fy1, fy2, first, delta, mod; TPos p, dx, dy, x, x2; int incr; ey1 = TRUNC( from_y ); ey2 = TRUNC( to_y ); /* if (ey2 >= ras.max_ey) ey2 = ras.max_ey-1; */ /* perform vertical clipping */ if ( ( ey1 >= ras.max_ey && ey2 >= ras.max_ey ) || ( ey1 < ras.min_ey && ey2 < ras.min_ey ) ) return; fy1 = FRACT( from_y ); fy2 = FRACT( to_y ); /* everything is on a single scanline */ if ( ey1 == ey2 ) { gray_render_scanline( RAS_VAR_ ey1, from_x, fy1, to_x, fy2 ); return; } dx = to_x - from_x; dy = to_y - from_y; /* vertical line - avoid calling gray_render_scanline */ if ( dx == 0 ) { TCoord ex = TRUNC( from_x ); TCoord two_fx = FRACT( from_x ) << 1; TPos area, max_ey1; if ( dy > 0) { first = ONE_PIXEL; } else { first = 0; } delta = first - fy1; ras.area += (TArea)two_fx * delta; ras.cover += delta; delta = first + first - ONE_PIXEL; area = (TArea)two_fx * delta; max_ey1 = ras.count_ey + ras.min_ey; if (dy < 0) { if (ey1 > max_ey1) { ey1 = (max_ey1 > ey2) ? max_ey1 : ey2; gray_set_cell( &ras, ex, ey1 ); } else { ey1--; gray_set_cell( &ras, ex, ey1 ); } while ( ey1 > ey2 && ey1 >= ras.min_ey) { ras.area += area; ras.cover += delta; ey1--; gray_set_cell( &ras, ex, ey1 ); } if (ey1 != ey2) { ey1 = ey2; gray_set_cell( &ras, ex, ey1 ); } } else { if (ey1 < ras.min_ey) { ey1 = (ras.min_ey < ey2) ? ras.min_ey : ey2; gray_set_cell( &ras, ex, ey1 ); } else { ey1++; gray_set_cell( &ras, ex, ey1 ); } while ( ey1 < ey2 && ey1 < max_ey1) { ras.area += area; ras.cover += delta; ey1++; gray_set_cell( &ras, ex, ey1 ); } if (ey1 != ey2) { ey1 = ey2; gray_set_cell( &ras, ex, ey1 ); } } delta = (int)( fy2 - ONE_PIXEL + first ); ras.area += (TArea)two_fx * delta; ras.cover += delta; return; } /* ok, we have to render several scanlines */ if ( dy > 0) { p = ( ONE_PIXEL - fy1 ) * dx; first = ONE_PIXEL; incr = 1; } else { p = fy1 * dx; first = 0; incr = -1; dy = -dy; } /* the fractional part of x-delta is mod/dy. It is essential to */ /* keep track of its accumulation for accurate rendering. */ PVG_FT_DIV_MOD( TCoord, p, dy, delta, mod ); x = from_x + delta; gray_render_scanline( RAS_VAR_ ey1, from_x, fy1, x, (TCoord)first ); ey1 += incr; gray_set_cell( RAS_VAR_ TRUNC( x ), ey1 ); if ( ey1 != ey2 ) { TCoord lift, rem; p = ONE_PIXEL * dx; PVG_FT_DIV_MOD( TCoord, p, dy, lift, rem ); do { delta = lift; mod += rem; if ( mod >= (TCoord)dy ) { mod -= (TCoord)dy; delta++; } x2 = x + delta; gray_render_scanline( RAS_VAR_ ey1, x, ONE_PIXEL - first, x2, first ); x = x2; ey1 += incr; gray_set_cell( RAS_VAR_ TRUNC( x ), ey1 ); } while ( ey1 != ey2 ); } gray_render_scanline( RAS_VAR_ ey1, x, ONE_PIXEL - first, to_x, fy2 ); } #else /*************************************************************************/ /* */ /* Render a straight line across multiple cells in any direction. */ /* */ static void gray_render_line( RAS_ARG_ TPos from_x, TPos from_y, TPos to_x, TPos to_y ) { TPos dx, dy, fx1, fy1, fx2, fy2; TCoord ex1, ex2, ey1, ey2; ex1 = TRUNC( from_x ); ex2 = TRUNC( to_x ); ey1 = TRUNC( from_y ); ey2 = TRUNC( to_y ); /* perform vertical clipping */ if ( ( ey1 >= ras.max_ey && ey2 >= ras.max_ey ) || ( ey1 < ras.min_ey && ey2 < ras.min_ey ) ) return; dx = to_x - from_x; dy = to_y - from_y; fx1 = FRACT( from_x ); fy1 = FRACT( from_y ); if ( ex1 == ex2 && ey1 == ey2 ) /* inside one cell */ ; else if ( dy == 0 ) /* ex1 != ex2 */ /* any horizontal line */ { ex1 = ex2; gray_set_cell( RAS_VAR_ ex1, ey1 ); } else if ( dx == 0 ) { if ( dy > 0 ) /* vertical line up */ do { fy2 = ONE_PIXEL; ras.cover += ( fy2 - fy1 ); ras.area += ( fy2 - fy1 ) * fx1 * 2; fy1 = 0; ey1++; gray_set_cell( RAS_VAR_ ex1, ey1 ); } while ( ey1 != ey2 ); else /* vertical line down */ do { fy2 = 0; ras.cover += ( fy2 - fy1 ); ras.area += ( fy2 - fy1 ) * fx1 * 2; fy1 = ONE_PIXEL; ey1--; gray_set_cell( RAS_VAR_ ex1, ey1 ); } while ( ey1 != ey2 ); } else /* any other line */ { TArea prod = dx * fy1 - dy * fx1; PVG_FT_UDIVPREP( dx ); PVG_FT_UDIVPREP( dy ); /* The fundamental value `prod' determines which side and the */ /* exact coordinate where the line exits current cell. It is */ /* also easily updated when moving from one cell to the next. */ do { if ( prod <= 0 && prod - dx * ONE_PIXEL > 0 ) /* left */ { fx2 = 0; fy2 = (TPos)PVG_FT_UDIV( -prod, -dx ); prod -= dy * ONE_PIXEL; ras.cover += ( fy2 - fy1 ); ras.area += ( fy2 - fy1 ) * ( fx1 + fx2 ); fx1 = ONE_PIXEL; fy1 = fy2; ex1--; } else if ( prod - dx * ONE_PIXEL <= 0 && prod - dx * ONE_PIXEL + dy * ONE_PIXEL > 0 ) /* up */ { prod -= dx * ONE_PIXEL; fx2 = (TPos)PVG_FT_UDIV( -prod, dy ); fy2 = ONE_PIXEL; ras.cover += ( fy2 - fy1 ); ras.area += ( fy2 - fy1 ) * ( fx1 + fx2 ); fx1 = fx2; fy1 = 0; ey1++; } else if ( prod - dx * ONE_PIXEL + dy * ONE_PIXEL <= 0 && prod + dy * ONE_PIXEL >= 0 ) /* right */ { prod += dy * ONE_PIXEL; fx2 = ONE_PIXEL; fy2 = (TPos)PVG_FT_UDIV( prod, dx ); ras.cover += ( fy2 - fy1 ); ras.area += ( fy2 - fy1 ) * ( fx1 + fx2 ); fx1 = 0; fy1 = fy2; ex1++; } else /* ( prod + dy * ONE_PIXEL < 0 && prod > 0 ) down */ { fx2 = (TPos)PVG_FT_UDIV( prod, -dy ); fy2 = 0; prod += dx * ONE_PIXEL; ras.cover += ( fy2 - fy1 ); ras.area += ( fy2 - fy1 ) * ( fx1 + fx2 ); fx1 = fx2; fy1 = ONE_PIXEL; ey1--; } gray_set_cell( RAS_VAR_ ex1, ey1 ); } while ( ex1 != ex2 || ey1 != ey2 ); } fx2 = FRACT( to_x ); fy2 = FRACT( to_y ); ras.cover += ( fy2 - fy1 ); ras.area += ( fy2 - fy1 ) * ( fx1 + fx2 ); } #endif static int gray_clip_flags( RAS_ARG_ TPos x, TPos y ) { return ((x > ras.clip_box.xMax) << 0) | ((y > ras.clip_box.yMax) << 1) | ((x < ras.clip_box.xMin) << 2) | ((y < ras.clip_box.yMin) << 3); } static int gray_clip_vflags( RAS_ARG_ TPos y ) { return ((y > ras.clip_box.yMax) << 1) | ((y < ras.clip_box.yMin) << 3); } static void gray_vline( RAS_ARG_ TPos x1, TPos y1, TPos x2, TPos y2, int f1, int f2 ) { f1 &= 10; f2 &= 10; if((f1 | f2) == 0) /* Fully visible */ { gray_render_line( RAS_VAR_ x1, y1, x2, y2 ); } else if(f1 == f2) /* Invisible by Y */ { return; } else { TPos tx1, ty1, tx2, ty2; TPos clip_y1, clip_y2; tx1 = x1; ty1 = y1; tx2 = x2; ty2 = y2; clip_y1 = ras.clip_box.yMin; clip_y2 = ras.clip_box.yMax; if(f1 & 8) /* y1 < clip_y1 */ { tx1 = x1 + PVG_FT_MulDiv(clip_y1-y1, x2-x1, y2-y1); ty1 = clip_y1; } if(f1 & 2) /* y1 > clip_y2 */ { tx1 = x1 + PVG_FT_MulDiv(clip_y2-y1, x2-x1, y2-y1); ty1 = clip_y2; } if(f2 & 8) /* y2 < clip_y1 */ { tx2 = x1 + PVG_FT_MulDiv(clip_y1-y1, x2-x1, y2-y1); ty2 = clip_y1; } if(f2 & 2) /* y2 > clip_y2 */ { tx2 = x1 + PVG_FT_MulDiv(clip_y2-y1, x2-x1, y2-y1); ty2 = clip_y2; } gray_render_line( RAS_VAR_ tx1, ty1, tx2, ty2 ); } } static void gray_line_to( RAS_ARG_ TPos x2, TPos y2 ) { if ( !ras.clipping ) { gray_render_line( RAS_VAR_ ras.x, ras.y, x2, y2 ); } else { TPos x1, y1, y3, y4; TPos clip_x1, clip_x2; int f1, f2, f3, f4; f1 = ras.clip_flags; f2 = gray_clip_flags( RAS_VAR_ x2, y2 ); if((f1 & 10) == (f2 & 10) && (f1 & 10) != 0) /* Invisible by Y */ { ras.clip_flags = f2; goto End; } x1 = ras.x; y1 = ras.y; clip_x1 = ras.clip_box.xMin; clip_x2 = ras.clip_box.xMax; switch(((f1 & 5) << 1) | (f2 & 5)) { case 0: /* Visible by X */ gray_vline( RAS_VAR_ x1, y1, x2, y2, f1, f2); break; case 1: /* x2 > clip_x2 */ y3 = y1 + PVG_FT_MulDiv(clip_x2-x1, y2-y1, x2-x1); f3 = gray_clip_vflags( RAS_VAR_ y3 ); gray_vline( RAS_VAR_ x1, y1, clip_x2, y3, f1, f3); gray_vline( RAS_VAR_ clip_x2, y3, clip_x2, y2, f3, f2); break; case 2: /* x1 > clip_x2 */ y3 = y1 + PVG_FT_MulDiv(clip_x2-x1, y2-y1, x2-x1); f3 = gray_clip_vflags( RAS_VAR_ y3 ); gray_vline( RAS_VAR_ clip_x2, y1, clip_x2, y3, f1, f3); gray_vline( RAS_VAR_ clip_x2, y3, x2, y2, f3, f2); break; case 3: /* x1 > clip_x2 && x2 > clip_x2 */ gray_vline( RAS_VAR_ clip_x2, y1, clip_x2, y2, f1, f2); break; case 4: /* x2 < clip_x1 */ y3 = y1 + PVG_FT_MulDiv(clip_x1-x1, y2-y1, x2-x1); f3 = gray_clip_vflags( RAS_VAR_ y3 ); gray_vline( RAS_VAR_ x1, y1, clip_x1, y3, f1, f3); gray_vline( RAS_VAR_ clip_x1, y3, clip_x1, y2, f3, f2); break; case 6: /* x1 > clip_x2 && x2 < clip_x1 */ y3 = y1 + PVG_FT_MulDiv(clip_x2-x1, y2-y1, x2-x1); y4 = y1 + PVG_FT_MulDiv(clip_x1-x1, y2-y1, x2-x1); f3 = gray_clip_vflags( RAS_VAR_ y3 ); f4 = gray_clip_vflags( RAS_VAR_ y4 ); gray_vline( RAS_VAR_ clip_x2, y1, clip_x2, y3, f1, f3); gray_vline( RAS_VAR_ clip_x2, y3, clip_x1, y4, f3, f4); gray_vline( RAS_VAR_ clip_x1, y4, clip_x1, y2, f4, f2); break; case 8: /* x1 < clip_x1 */ y3 = y1 + PVG_FT_MulDiv(clip_x1-x1, y2-y1, x2-x1); f3 = gray_clip_vflags( RAS_VAR_ y3 ); gray_vline( RAS_VAR_ clip_x1, y1, clip_x1, y3, f1, f3); gray_vline( RAS_VAR_ clip_x1, y3, x2, y2, f3, f2); break; case 9: /* x1 < clip_x1 && x2 > clip_x2 */ y3 = y1 + PVG_FT_MulDiv(clip_x1-x1, y2-y1, x2-x1); y4 = y1 + PVG_FT_MulDiv(clip_x2-x1, y2-y1, x2-x1); f3 = gray_clip_vflags( RAS_VAR_ y3 ); f4 = gray_clip_vflags( RAS_VAR_ y4 ); gray_vline( RAS_VAR_ clip_x1, y1, clip_x1, y3, f1, f3); gray_vline( RAS_VAR_ clip_x1, y3, clip_x2, y4, f3, f4); gray_vline( RAS_VAR_ clip_x2, y4, clip_x2, y2, f4, f2); break; case 12: /* x1 < clip_x1 && x2 < clip_x1 */ gray_vline( RAS_VAR_ clip_x1, y1, clip_x1, y2, f1, f2); break; } ras.clip_flags = f2; } End: ras.x = x2; ras.y = y2; } static void gray_split_conic( PVG_FT_Vector* base ) { TPos a, b; base[4].x = base[2].x; b = base[1].x; a = base[3].x = ( base[2].x + b ) / 2; b = base[1].x = ( base[0].x + b ) / 2; base[2].x = ( a + b ) / 2; base[4].y = base[2].y; b = base[1].y; a = base[3].y = ( base[2].y + b ) / 2; b = base[1].y = ( base[0].y + b ) / 2; base[2].y = ( a + b ) / 2; } static void gray_render_conic( RAS_ARG_ const PVG_FT_Vector* control, const PVG_FT_Vector* to ) { PVG_FT_Vector bez_stack[16 * 2 + 1]; /* enough to accommodate bisections */ PVG_FT_Vector* arc = bez_stack; TPos dx, dy; int draw, split; arc[0].x = UPSCALE( to->x ); arc[0].y = UPSCALE( to->y ); arc[1].x = UPSCALE( control->x ); arc[1].y = UPSCALE( control->y ); arc[2].x = ras.x; arc[2].y = ras.y; /* short-cut the arc that crosses the current band */ if ( ( TRUNC( arc[0].y ) >= ras.max_ey && TRUNC( arc[1].y ) >= ras.max_ey && TRUNC( arc[2].y ) >= ras.max_ey ) || ( TRUNC( arc[0].y ) < ras.min_ey && TRUNC( arc[1].y ) < ras.min_ey && TRUNC( arc[2].y ) < ras.min_ey ) ) { if ( ras.clipping ) ras.clip_flags = gray_clip_flags ( RAS_VAR_ arc[0].x, arc[0].y ); ras.x = arc[0].x; ras.y = arc[0].y; return; } dx = PVG_FT_ABS( arc[2].x + arc[0].x - 2 * arc[1].x ); dy = PVG_FT_ABS( arc[2].y + arc[0].y - 2 * arc[1].y ); if ( dx < dy ) dx = dy; /* We can calculate the number of necessary bisections because */ /* each bisection predictably reduces deviation exactly 4-fold. */ /* Even 32-bit deviation would vanish after 16 bisections. */ draw = 1; while ( dx > ONE_PIXEL / 4 ) { dx >>= 2; draw <<= 1; } /* We use decrement counter to count the total number of segments */ /* to draw starting from 2^level. Before each draw we split as */ /* many times as there are trailing zeros in the counter. */ do { split = 1; while ( ( draw & split ) == 0 ) { gray_split_conic( arc ); arc += 2; split <<= 1; } gray_line_to( RAS_VAR_ arc[0].x, arc[0].y ); arc -= 2; } while ( --draw ); } static void gray_split_cubic( PVG_FT_Vector* base ) { TPos a, b, c, d; base[6].x = base[3].x; c = base[1].x; d = base[2].x; base[1].x = a = ( base[0].x + c ) / 2; base[5].x = b = ( base[3].x + d ) / 2; c = ( c + d ) / 2; base[2].x = a = ( a + c ) / 2; base[4].x = b = ( b + c ) / 2; base[3].x = ( a + b ) / 2; base[6].y = base[3].y; c = base[1].y; d = base[2].y; base[1].y = a = ( base[0].y + c ) / 2; base[5].y = b = ( base[3].y + d ) / 2; c = ( c + d ) / 2; base[2].y = a = ( a + c ) / 2; base[4].y = b = ( b + c ) / 2; base[3].y = ( a + b ) / 2; } static void gray_render_cubic( RAS_ARG_ const PVG_FT_Vector* control1, const PVG_FT_Vector* control2, const PVG_FT_Vector* to ) { PVG_FT_Vector bez_stack[16 * 3 + 1]; /* enough to accommodate bisections */ PVG_FT_Vector* arc = bez_stack; PVG_FT_Vector* limit = bez_stack + 45; TPos dx, dy, dx_, dy_; TPos dx1, dy1, dx2, dy2; TPos L, s, s_limit; arc[0].x = UPSCALE( to->x ); arc[0].y = UPSCALE( to->y ); arc[1].x = UPSCALE( control2->x ); arc[1].y = UPSCALE( control2->y ); arc[2].x = UPSCALE( control1->x ); arc[2].y = UPSCALE( control1->y ); arc[3].x = ras.x; arc[3].y = ras.y; /* short-cut the arc that crosses the current band */ if ( ( TRUNC( arc[0].y ) >= ras.max_ey && TRUNC( arc[1].y ) >= ras.max_ey && TRUNC( arc[2].y ) >= ras.max_ey && TRUNC( arc[3].y ) >= ras.max_ey ) || ( TRUNC( arc[0].y ) < ras.min_ey && TRUNC( arc[1].y ) < ras.min_ey && TRUNC( arc[2].y ) < ras.min_ey && TRUNC( arc[3].y ) < ras.min_ey ) ) { if ( ras.clipping ) ras.clip_flags = gray_clip_flags ( RAS_VAR_ arc[0].x, arc[0].y ); ras.x = arc[0].x; ras.y = arc[0].y; return; } for (;;) { /* Decide whether to split or draw. See `Rapid Termination */ /* Evaluation for Recursive Subdivision of Bezier Curves' by Thomas */ /* F. Hain, at */ /* http://www.cis.southalabama.edu/~hain/general/Publications/Bezier/Camera-ready%20CISST02%202.pdf */ /* dx and dy are x and y components of the P0-P3 chord vector. */ dx = dx_ = arc[3].x - arc[0].x; dy = dy_ = arc[3].y - arc[0].y; L = PVG_FT_HYPOT( dx_, dy_ ); /* Avoid possible arithmetic overflow below by splitting. */ if ( L >= (1 << 23) ) goto Split; /* Max deviation may be as much as (s/L) * 3/4 (if Hain's v = 1). */ s_limit = L * (TPos)( ONE_PIXEL / 6 ); /* s is L * the perpendicular distance from P1 to the line P0-P3. */ dx1 = arc[1].x - arc[0].x; dy1 = arc[1].y - arc[0].y; s = PVG_FT_ABS( dy * dx1 - dx * dy1 ); if ( s > s_limit ) goto Split; /* s is L * the perpendicular distance from P2 to the line P0-P3. */ dx2 = arc[2].x - arc[0].x; dy2 = arc[2].y - arc[0].y; s = PVG_FT_ABS( dy * dx2 - dx * dy2 ); if ( s > s_limit ) goto Split; /* Split super curvy segments where the off points are so far from the chord that the angles P0-P1-P3 or P0-P2-P3 become acute as detected by appropriate dot products. */ if ( dx1 * ( dx1 - dx ) + dy1 * ( dy1 - dy ) > 0 || dx2 * ( dx2 - dx ) + dy2 * ( dy2 - dy ) > 0 ) goto Split; gray_line_to( RAS_VAR_ arc[0].x, arc[0].y ); if ( arc == bez_stack ) return; arc -= 3; continue; Split: if( arc == limit ) return; gray_split_cubic( arc ); arc += 3; } } static int gray_move_to( const PVG_FT_Vector* to, PWorker worker ) { TPos x, y; /* record current cell, if any */ if ( !ras.invalid ) gray_record_cell( worker ); /* start to a new position */ x = UPSCALE( to->x ); y = UPSCALE( to->y ); gray_start_cell( worker, TRUNC( x ), TRUNC( y ) ); if ( ras.clipping ) ras.clip_flags = gray_clip_flags( worker, x, y ); ras.x = x; ras.y = y; return 0; } static void gray_hline( RAS_ARG_ TCoord x, TCoord y, TPos area, int acount ) { int coverage; /* compute the coverage line's coverage, depending on the */ /* outline fill rule */ /* */ /* the coverage percentage is area/(PIXEL_BITS*PIXEL_BITS*2) */ /* */ coverage = (int)( area >> ( PIXEL_BITS * 2 + 1 - 8 ) ); /* use range 0..256 */ if ( coverage < 0 ) coverage = -coverage; if ( ras.outline.flags & PVG_FT_OUTLINE_EVEN_ODD_FILL ) { coverage &= 511; if ( coverage > 256 ) coverage = 512 - coverage; else if ( coverage == 256 ) coverage = 255; } else { /* normal non-zero winding rule */ if ( coverage >= 256 ) coverage = 255; } y += (TCoord)ras.min_ey; x += (TCoord)ras.min_ex; /* PVG_FT_Span.x is an int, so limit our coordinates appropriately */ if ( x >= (1 << 23) ) x = (1 << 23) - 1; /* PVG_FT_Span.y is an int, so limit our coordinates appropriately */ if ( y >= (1 << 23) ) y = (1 << 23) - 1; if ( coverage ) { PVG_FT_Span* span; int count; int skip; /* see whether we can add this span to the current list */ count = ras.num_gray_spans; span = ras.gray_spans + count - 1; if ( count > 0 && span->y == y && span->x + span->len == x && span->coverage == coverage ) { span->len = span->len + acount; return; } if ( count >= PVG_FT_MAX_GRAY_SPANS ) { if ( ras.render_span && count > ras.skip_spans ) { skip = ras.skip_spans > 0 ? ras.skip_spans : 0; ras.render_span( ras.num_gray_spans - skip, ras.gray_spans + skip, ras.render_span_data ); } ras.skip_spans -= ras.num_gray_spans; /* ras.render_span( span->y, ras.gray_spans, count ); */ ras.num_gray_spans = 0; span = ras.gray_spans; } else span++; /* add a gray span to the current list */ span->x = x; span->len = acount; span->y = y; span->coverage = (unsigned char)coverage; ras.num_gray_spans++; } } static void gray_sweep( RAS_ARG) { int yindex; if ( ras.num_cells == 0 ) return; for ( yindex = 0; yindex < ras.ycount; yindex++ ) { PCell cell = ras.ycells[yindex]; TCoord cover = 0; TCoord x = 0; for ( ; cell != NULL; cell = cell->next ) { TArea area; if ( cell->x > x && cover != 0 ) gray_hline( RAS_VAR_ x, yindex, cover * ( ONE_PIXEL * 2 ), cell->x - x ); cover += cell->cover; area = cover * ( ONE_PIXEL * 2 ) - cell->area; if ( area != 0 && cell->x >= 0 ) gray_hline( RAS_VAR_ cell->x, yindex, area, 1 ); x = cell->x + 1; } if ( ras.count_ex > x && cover != 0 ) gray_hline( RAS_VAR_ x, yindex, cover * ( ONE_PIXEL * 2 ), ras.count_ex - x ); } } PVG_FT_Error PVG_FT_Outline_Check(PVG_FT_Outline* outline) { if (outline) { PVG_FT_Int n_points = outline->n_points; PVG_FT_Int n_contours = outline->n_contours; PVG_FT_Int end0, end; PVG_FT_Int n; /* empty glyph? */ if (n_points == 0 && n_contours == 0) return 0; /* check point and contour counts */ if (n_points <= 0 || n_contours <= 0) goto Bad; end0 = end = -1; for (n = 0; n < n_contours; n++) { end = outline->contours[n]; /* note that we don't accept empty contours */ if (end <= end0 || end >= n_points) goto Bad; end0 = end; } if (end != n_points - 1) goto Bad; /* XXX: check the tags array */ return 0; } Bad: return ErrRaster_Invalid_Outline; } void PVG_FT_Outline_Get_CBox(const PVG_FT_Outline* outline, PVG_FT_BBox* acbox) { PVG_FT_Pos xMin, yMin, xMax, yMax; if (outline && acbox) { if (outline->n_points == 0) { xMin = 0; yMin = 0; xMax = 0; yMax = 0; } else { PVG_FT_Vector* vec = outline->points; PVG_FT_Vector* limit = vec + outline->n_points; xMin = xMax = vec->x; yMin = yMax = vec->y; vec++; for (; vec < limit; vec++) { PVG_FT_Pos x, y; x = vec->x; if (x < xMin) xMin = x; if (x > xMax) xMax = x; y = vec->y; if (y < yMin) yMin = y; if (y > yMax) yMax = y; } } acbox->xMin = xMin; acbox->xMax = xMax; acbox->yMin = yMin; acbox->yMax = yMax; } } /*************************************************************************/ /* */ /* The following function should only compile in stand_alone mode, */ /* i.e., when building this component without the rest of FreeType. */ /* */ /*************************************************************************/ /*************************************************************************/ /* */ /* */ /* PVG_FT_Outline_Decompose */ /* */ /* */ /* Walks over an outline's structure to decompose it into individual */ /* segments and Bezier arcs. This function is also able to emit */ /* `move to' and `close to' operations to indicate the start and end */ /* of new contours in the outline. */ /* */ /* */ /* outline :: A pointer to the source target. */ /* */ /* user :: A typeless pointer which is passed to each */ /* emitter during the decomposition. It can be */ /* used to store the state during the */ /* decomposition. */ /* */ /* */ /* Error code. 0 means success. */ /* */ static int PVG_FT_Outline_Decompose( const PVG_FT_Outline* outline, void* user ) { #undef SCALED #define SCALED( x ) (x) PVG_FT_Vector v_last; PVG_FT_Vector v_control; PVG_FT_Vector v_start; PVG_FT_Vector* point; PVG_FT_Vector* limit; char* tags; int n; /* index of contour in outline */ int first; /* index of first point in contour */ int error; char tag; /* current point's state */ if ( !outline ) return ErrRaster_Invalid_Outline; first = 0; for ( n = 0; n < outline->n_contours; n++ ) { int last; /* index of last point in contour */ last = outline->contours[n]; if ( last < 0 ) goto Invalid_Outline; limit = outline->points + last; v_start = outline->points[first]; v_start.x = SCALED( v_start.x ); v_start.y = SCALED( v_start.y ); v_last = outline->points[last]; v_last.x = SCALED( v_last.x ); v_last.y = SCALED( v_last.y ); v_control = v_start; point = outline->points + first; tags = outline->tags + first; tag = PVG_FT_CURVE_TAG( tags[0] ); /* A contour cannot start with a cubic control point! */ if ( tag == PVG_FT_CURVE_TAG_CUBIC ) goto Invalid_Outline; /* check first point to determine origin */ if ( tag == PVG_FT_CURVE_TAG_CONIC ) { /* first point is conic control. Yes, this happens. */ if ( PVG_FT_CURVE_TAG( outline->tags[last] ) == PVG_FT_CURVE_TAG_ON ) { /* start at last point if it is on the curve */ v_start = v_last; limit--; } else { /* if both first and last points are conic, */ /* start at their middle and record its position */ /* for closure */ v_start.x = ( v_start.x + v_last.x ) / 2; v_start.y = ( v_start.y + v_last.y ) / 2; v_last = v_start; } point--; tags--; } error = gray_move_to( &v_start, user ); if ( error ) goto Exit; while ( point < limit ) { point++; tags++; tag = PVG_FT_CURVE_TAG( tags[0] ); switch ( tag ) { case PVG_FT_CURVE_TAG_ON: /* emit a single line_to */ { PVG_FT_Vector vec; vec.x = SCALED( point->x ); vec.y = SCALED( point->y ); gray_line_to(user, UPSCALE(vec.x), UPSCALE(vec.y)); continue; } case PVG_FT_CURVE_TAG_CONIC: /* consume conic arcs */ { v_control.x = SCALED( point->x ); v_control.y = SCALED( point->y ); Do_Conic: if ( point < limit ) { PVG_FT_Vector vec; PVG_FT_Vector v_middle; point++; tags++; tag = PVG_FT_CURVE_TAG( tags[0] ); vec.x = SCALED( point->x ); vec.y = SCALED( point->y ); if ( tag == PVG_FT_CURVE_TAG_ON ) { gray_render_conic(user, &v_control, &vec); continue; } if ( tag != PVG_FT_CURVE_TAG_CONIC ) goto Invalid_Outline; v_middle.x = ( v_control.x + vec.x ) / 2; v_middle.y = ( v_control.y + vec.y ) / 2; gray_render_conic(user, &v_control, &v_middle); v_control = vec; goto Do_Conic; } gray_render_conic(user, &v_control, &v_start); goto Close; } default: /* PVG_FT_CURVE_TAG_CUBIC */ { PVG_FT_Vector vec1, vec2; if ( point + 1 > limit || PVG_FT_CURVE_TAG( tags[1] ) != PVG_FT_CURVE_TAG_CUBIC ) goto Invalid_Outline; point += 2; tags += 2; vec1.x = SCALED( point[-2].x ); vec1.y = SCALED( point[-2].y ); vec2.x = SCALED( point[-1].x ); vec2.y = SCALED( point[-1].y ); if ( point <= limit ) { PVG_FT_Vector vec; vec.x = SCALED( point->x ); vec.y = SCALED( point->y ); gray_render_cubic(user, &vec1, &vec2, &vec); continue; } gray_render_cubic(user, &vec1, &vec2, &v_start); goto Close; } } } /* close the contour with a line segment */ gray_line_to(user, UPSCALE(v_start.x), UPSCALE(v_start.y)); Close: first = last + 1; } return 0; Exit: return error; Invalid_Outline: return ErrRaster_Invalid_Outline; } typedef struct TBand_ { TPos min, max; } TBand; static int gray_convert_glyph_inner( RAS_ARG ) { volatile int error = 0; if ( pvg_ft_setjmp( ras.jump_buffer ) == 0 ) { error = PVG_FT_Outline_Decompose( &ras.outline, &ras ); if ( !ras.invalid ) gray_record_cell( RAS_VAR ); } else { error = ErrRaster_Memory_Overflow; } return error; } static int gray_convert_glyph( RAS_ARG ) { TBand bands[40]; TBand* volatile band; int volatile n, num_bands; TPos volatile min, max, max_y; PVG_FT_BBox* clip; int skip; ras.num_gray_spans = 0; /* Set up state in the raster object */ gray_compute_cbox( RAS_VAR ); /* clip to target bitmap, exit if nothing to do */ clip = &ras.clip_box; if ( ras.max_ex <= clip->xMin || ras.min_ex >= clip->xMax || ras.max_ey <= clip->yMin || ras.min_ey >= clip->yMax ) return 0; ras.clip_flags = ras.clipping = 0; if ( ras.min_ex < clip->xMin ) { ras.min_ex = clip->xMin; ras.clipping = 1; } if ( ras.min_ey < clip->yMin ) { ras.min_ey = clip->yMin; ras.clipping = 1; } if ( ras.max_ex > clip->xMax ) { ras.max_ex = clip->xMax; ras.clipping = 1; } if ( ras.max_ey > clip->yMax ) { ras.max_ey = clip->yMax; ras.clipping = 1; } clip->xMin = (ras.min_ex - 1) * ONE_PIXEL; clip->yMin = (ras.min_ey - 1) * ONE_PIXEL; clip->xMax = (ras.max_ex + 1) * ONE_PIXEL; clip->yMax = (ras.max_ey + 1) * ONE_PIXEL; ras.count_ex = ras.max_ex - ras.min_ex; ras.count_ey = ras.max_ey - ras.min_ey; /* set up vertical bands */ num_bands = (int)( ( ras.max_ey - ras.min_ey ) / ras.band_size ); if ( num_bands == 0 ) num_bands = 1; if ( num_bands >= 39 ) num_bands = 39; ras.band_shoot = 0; min = ras.min_ey; max_y = ras.max_ey; for ( n = 0; n < num_bands; n++, min = max ) { max = min + ras.band_size; if ( n == num_bands - 1 || max > max_y ) max = max_y; bands[0].min = min; bands[0].max = max; band = bands; while ( band >= bands ) { TPos bottom, top, middle; int error; { PCell cells_max; int yindex; int cell_start, cell_end, cell_mod; ras.ycells = (PCell*)ras.buffer; ras.ycount = band->max - band->min; cell_start = sizeof ( PCell ) * ras.ycount; cell_mod = cell_start % sizeof ( TCell ); if ( cell_mod > 0 ) cell_start += sizeof ( TCell ) - cell_mod; cell_end = ras.buffer_size; cell_end -= cell_end % sizeof( TCell ); cells_max = (PCell)( (char*)ras.buffer + cell_end ); ras.cells = (PCell)( (char*)ras.buffer + cell_start ); if ( ras.cells >= cells_max ) goto ReduceBands; ras.max_cells = (int)(cells_max - ras.cells); if ( ras.max_cells < 2 ) goto ReduceBands; for ( yindex = 0; yindex < ras.ycount; yindex++ ) ras.ycells[yindex] = NULL; } ras.num_cells = 0; ras.invalid = 1; ras.min_ey = band->min; ras.max_ey = band->max; ras.count_ey = band->max - band->min; error = gray_convert_glyph_inner( RAS_VAR ); if ( !error ) { gray_sweep( RAS_VAR); band--; continue; } else if ( error != ErrRaster_Memory_Overflow ) return 1; ReduceBands: /* render pool overflow; we will reduce the render band by half */ bottom = band->min; top = band->max; middle = bottom + ( ( top - bottom ) >> 1 ); /* This is too complex for a single scanline; there must */ /* be some problems. */ if ( middle == bottom ) { return ErrRaster_OutOfMemory; } if ( bottom-top >= ras.band_size ) ras.band_shoot++; band[1].min = bottom; band[1].max = middle; band[0].min = middle; band[0].max = top; band++; } } if ( ras.render_span && ras.num_gray_spans > ras.skip_spans ) { skip = ras.skip_spans > 0 ? ras.skip_spans : 0; ras.render_span( ras.num_gray_spans - skip, ras.gray_spans + skip, ras.render_span_data ); } ras.skip_spans -= ras.num_gray_spans; if ( ras.band_shoot > 8 && ras.band_size > 16 ) ras.band_size = ras.band_size / 2; return 0; } static int gray_raster_render( RAS_ARG_ void* buffer, long buffer_size, const PVG_FT_Raster_Params* params ) { const PVG_FT_Outline* outline = (const PVG_FT_Outline*)params->source; if ( outline == NULL ) return ErrRaster_Invalid_Outline; /* return immediately if the outline is empty */ if ( outline->n_points == 0 || outline->n_contours <= 0 ) return 0; if ( !outline->contours || !outline->points ) return ErrRaster_Invalid_Outline; if ( outline->n_points != outline->contours[outline->n_contours - 1] + 1 ) return ErrRaster_Invalid_Outline; /* this version does not support monochrome rendering */ if ( !( params->flags & PVG_FT_RASTER_FLAG_AA ) ) return ErrRaster_Invalid_Mode; if ( !( params->flags & PVG_FT_RASTER_FLAG_DIRECT ) ) return ErrRaster_Invalid_Mode; /* compute clipping box */ if ( params->flags & PVG_FT_RASTER_FLAG_CLIP ) { ras.clip_box = params->clip_box; } else { ras.clip_box.xMin = -(1 << 23); ras.clip_box.yMin = -(1 << 23); ras.clip_box.xMax = (1 << 23) - 1; ras.clip_box.yMax = (1 << 23) - 1; } gray_init_cells( RAS_VAR_ buffer, buffer_size ); ras.outline = *outline; ras.num_cells = 0; ras.invalid = 1; ras.band_size = (int)(buffer_size / (long)(sizeof(TCell) * 8)); ras.render_span = (PVG_FT_Raster_Span_Func)params->gray_spans; ras.render_span_data = params->user; return gray_convert_glyph( RAS_VAR ); } void PVG_FT_Raster_Render(const PVG_FT_Raster_Params *params) { char stack[PVG_FT_MINIMUM_POOL_SIZE]; size_t length = PVG_FT_MINIMUM_POOL_SIZE; TWorker worker; worker.skip_spans = 0; int rendered_spans = 0; int error = gray_raster_render(&worker, stack, length, params); while(error == ErrRaster_OutOfMemory) { if(worker.skip_spans < 0) rendered_spans += -worker.skip_spans; worker.skip_spans = rendered_spans; length *= 2; void* heap = malloc(length); error = gray_raster_render(&worker, heap, length, params); free(heap); } } /* END */ sammycage-plutovg-5695a71/source/plutovg-ft-raster.h000066400000000000000000000650361510714322500225310ustar00rootroot00000000000000/***************************************************************************/ /* */ /* ftimage.h */ /* */ /* FreeType glyph image formats and default raster interface */ /* (specification). */ /* */ /* Copyright 1996-2010, 2013 by */ /* David Turner, Robert Wilhelm, and Werner Lemberg. */ /* */ /* This file is part of the FreeType project, and may only be used, */ /* modified, and distributed under the terms of the FreeType project */ /* license, FTL.TXT. By continuing to use, modify, or distribute */ /* this file you indicate that you have read the license and */ /* understand and accept it fully. */ /* */ /***************************************************************************/ #ifndef PLUTOVG_FT_RASTER_H #define PLUTOVG_FT_RASTER_H #include "plutovg-ft-types.h" /*************************************************************************/ /* */ /* */ /* FT_BBox */ /* */ /* */ /* A structure used to hold an outline's bounding box, i.e., the */ /* coordinates of its extrema in the horizontal and vertical */ /* directions. */ /* */ /* */ /* xMin :: The horizontal minimum (left-most). */ /* */ /* yMin :: The vertical minimum (bottom-most). */ /* */ /* xMax :: The horizontal maximum (right-most). */ /* */ /* yMax :: The vertical maximum (top-most). */ /* */ /* */ /* The bounding box is specified with the coordinates of the lower */ /* left and the upper right corner. In PostScript, those values are */ /* often called (llx,lly) and (urx,ury), respectively. */ /* */ /* If `yMin' is negative, this value gives the glyph's descender. */ /* Otherwise, the glyph doesn't descend below the baseline. */ /* Similarly, if `ymax' is positive, this value gives the glyph's */ /* ascender. */ /* */ /* `xMin' gives the horizontal distance from the glyph's origin to */ /* the left edge of the glyph's bounding box. If `xMin' is negative, */ /* the glyph extends to the left of the origin. */ /* */ typedef struct PVG_FT_BBox_ { PVG_FT_Pos xMin, yMin; PVG_FT_Pos xMax, yMax; } PVG_FT_BBox; /*************************************************************************/ /* */ /* */ /* PVG_FT_Outline */ /* */ /* */ /* This structure is used to describe an outline to the scan-line */ /* converter. */ /* */ /* */ /* n_contours :: The number of contours in the outline. */ /* */ /* n_points :: The number of points in the outline. */ /* */ /* points :: A pointer to an array of `n_points' @PVG_FT_Vector */ /* elements, giving the outline's point coordinates. */ /* */ /* tags :: A pointer to an array of `n_points' chars, giving */ /* each outline point's type. */ /* */ /* If bit~0 is unset, the point is `off' the curve, */ /* i.e., a Bézier control point, while it is `on' if */ /* set. */ /* */ /* Bit~1 is meaningful for `off' points only. If set, */ /* it indicates a third-order Bézier arc control point; */ /* and a second-order control point if unset. */ /* */ /* If bit~2 is set, bits 5-7 contain the drop-out mode */ /* (as defined in the OpenType specification; the value */ /* is the same as the argument to the SCANMODE */ /* instruction). */ /* */ /* Bits 3 and~4 are reserved for internal purposes. */ /* */ /* contours :: An array of `n_contours' shorts, giving the end */ /* point of each contour within the outline. For */ /* example, the first contour is defined by the points */ /* `0' to `contours[0]', the second one is defined by */ /* the points `contours[0]+1' to `contours[1]', etc. */ /* */ /* flags :: A set of bit flags used to characterize the outline */ /* and give hints to the scan-converter and hinter on */ /* how to convert/grid-fit it. See @PVG_FT_OUTLINE_FLAGS.*/ /* */ typedef struct PVG_FT_Outline_ { int n_contours; /* number of contours in glyph */ int n_points; /* number of points in the glyph */ PVG_FT_Vector* points; /* the outline's points */ char* tags; /* the points flags */ int* contours; /* the contour end points */ char* contours_flag; /* the contour open flags */ int flags; /* outline masks */ } PVG_FT_Outline; /*************************************************************************/ /* */ /* */ /* PVG_FT_OUTLINE_FLAGS */ /* */ /* */ /* A list of bit-field constants use for the flags in an outline's */ /* `flags' field. */ /* */ /* */ /* PVG_FT_OUTLINE_NONE :: */ /* Value~0 is reserved. */ /* */ /* PVG_FT_OUTLINE_OWNER :: */ /* If set, this flag indicates that the outline's field arrays */ /* (i.e., `points', `flags', and `contours') are `owned' by the */ /* outline object, and should thus be freed when it is destroyed. */ /* */ /* PVG_FT_OUTLINE_EVEN_ODD_FILL :: */ /* By default, outlines are filled using the non-zero winding rule. */ /* If set to 1, the outline will be filled using the even-odd fill */ /* rule (only works with the smooth rasterizer). */ /* */ /* PVG_FT_OUTLINE_REVERSE_FILL :: */ /* By default, outside contours of an outline are oriented in */ /* clock-wise direction, as defined in the TrueType specification. */ /* This flag is set if the outline uses the opposite direction */ /* (typically for Type~1 fonts). This flag is ignored by the scan */ /* converter. */ /* */ /* */ /* */ /* There exists a second mechanism to pass the drop-out mode to the */ /* B/W rasterizer; see the `tags' field in @PVG_FT_Outline. */ /* */ /* Please refer to the description of the `SCANTYPE' instruction in */ /* the OpenType specification (in file `ttinst1.doc') how simple */ /* drop-outs, smart drop-outs, and stubs are defined. */ /* */ #define PVG_FT_OUTLINE_NONE 0x0 #define PVG_FT_OUTLINE_OWNER 0x1 #define PVG_FT_OUTLINE_EVEN_ODD_FILL 0x2 #define PVG_FT_OUTLINE_REVERSE_FILL 0x4 /* */ #define PVG_FT_CURVE_TAG( flag ) ( flag & 3 ) #define PVG_FT_CURVE_TAG_ON 1 #define PVG_FT_CURVE_TAG_CONIC 0 #define PVG_FT_CURVE_TAG_CUBIC 2 #define PVG_FT_Curve_Tag_On PVG_FT_CURVE_TAG_ON #define PVG_FT_Curve_Tag_Conic PVG_FT_CURVE_TAG_CONIC #define PVG_FT_Curve_Tag_Cubic PVG_FT_CURVE_TAG_CUBIC /*************************************************************************/ /* */ /* */ /* PVG_FT_Outline_Check */ /* */ /* */ /* Check the contents of an outline descriptor. */ /* */ /* */ /* outline :: A handle to a source outline. */ /* */ /* */ /* FreeType error code. 0~means success. */ /* */ PVG_FT_Error PVG_FT_Outline_Check( PVG_FT_Outline* outline ); /*************************************************************************/ /* */ /* */ /* PVG_FT_Outline_Get_CBox */ /* */ /* */ /* Return an outline's `control box'. The control box encloses all */ /* the outline's points, including Bézier control points. Though it */ /* coincides with the exact bounding box for most glyphs, it can be */ /* slightly larger in some situations (like when rotating an outline */ /* that contains Bézier outside arcs). */ /* */ /* Computing the control box is very fast, while getting the bounding */ /* box can take much more time as it needs to walk over all segments */ /* and arcs in the outline. To get the latter, you can use the */ /* `ftbbox' component, which is dedicated to this single task. */ /* */ /* */ /* outline :: A pointer to the source outline descriptor. */ /* */ /* */ /* acbox :: The outline's control box. */ /* */ /* */ /* See @PVG_FT_Glyph_Get_CBox for a discussion of tricky fonts. */ /* */ void PVG_FT_Outline_Get_CBox( const PVG_FT_Outline* outline, PVG_FT_BBox *acbox ); /*************************************************************************/ /* */ /* */ /* PVG_FT_Span */ /* */ /* */ /* A structure used to model a single span of gray (or black) pixels */ /* when rendering a monochrome or anti-aliased bitmap. */ /* */ /* */ /* x :: The span's horizontal start position. */ /* */ /* len :: The span's length in pixels. */ /* */ /* coverage :: The span color/coverage, ranging from 0 (background) */ /* to 255 (foreground). Only used for anti-aliased */ /* rendering. */ /* */ /* */ /* This structure is used by the span drawing callback type named */ /* @PVG_FT_SpanFunc that takes the y~coordinate of the span as a */ /* parameter. */ /* */ /* The coverage value is always between 0 and 255. If you want less */ /* gray values, the callback function has to reduce them. */ /* */ typedef struct PVG_FT_Span_ { int x; int len; int y; unsigned char coverage; } PVG_FT_Span; /*************************************************************************/ /* */ /* */ /* PVG_FT_SpanFunc */ /* */ /* */ /* A function used as a call-back by the anti-aliased renderer in */ /* order to let client applications draw themselves the gray pixel */ /* spans on each scan line. */ /* */ /* */ /* y :: The scanline's y~coordinate. */ /* */ /* count :: The number of spans to draw on this scanline. */ /* */ /* spans :: A table of `count' spans to draw on the scanline. */ /* */ /* user :: User-supplied data that is passed to the callback. */ /* */ /* */ /* This callback allows client applications to directly render the */ /* gray spans of the anti-aliased bitmap to any kind of surfaces. */ /* */ /* This can be used to write anti-aliased outlines directly to a */ /* given background bitmap, and even perform translucency. */ /* */ /* Note that the `count' field cannot be greater than a fixed value */ /* defined by the `PVG_FT_MAX_GRAY_SPANS' configuration macro in */ /* `ftoption.h'. By default, this value is set to~32, which means */ /* that if there are more than 32~spans on a given scanline, the */ /* callback is called several times with the same `y' parameter in */ /* order to draw all callbacks. */ /* */ /* Otherwise, the callback is only called once per scan-line, and */ /* only for those scanlines that do have `gray' pixels on them. */ /* */ typedef void (*PVG_FT_SpanFunc)( int count, const PVG_FT_Span* spans, void* user ); #define PVG_FT_Raster_Span_Func PVG_FT_SpanFunc /*************************************************************************/ /* */ /* */ /* PVG_FT_RASTER_FLAG_XXX */ /* */ /* */ /* A list of bit flag constants as used in the `flags' field of a */ /* @PVG_FT_Raster_Params structure. */ /* */ /* */ /* PVG_FT_RASTER_FLAG_DEFAULT :: This value is 0. */ /* */ /* PVG_FT_RASTER_FLAG_AA :: This flag is set to indicate that an */ /* anti-aliased glyph image should be */ /* generated. Otherwise, it will be */ /* monochrome (1-bit). */ /* */ /* PVG_FT_RASTER_FLAG_DIRECT :: This flag is set to indicate direct */ /* rendering. In this mode, client */ /* applications must provide their own span */ /* callback. This lets them directly */ /* draw or compose over an existing bitmap. */ /* If this bit is not set, the target */ /* pixmap's buffer _must_ be zeroed before */ /* rendering. */ /* */ /* Note that for now, direct rendering is */ /* only possible with anti-aliased glyphs. */ /* */ /* PVG_FT_RASTER_FLAG_CLIP :: This flag is only used in direct */ /* rendering mode. If set, the output will */ /* be clipped to a box specified in the */ /* `clip_box' field of the */ /* @PVG_FT_Raster_Params structure. */ /* */ /* Note that by default, the glyph bitmap */ /* is clipped to the target pixmap, except */ /* in direct rendering mode where all spans */ /* are generated if no clipping box is set. */ /* */ #define PVG_FT_RASTER_FLAG_DEFAULT 0x0 #define PVG_FT_RASTER_FLAG_AA 0x1 #define PVG_FT_RASTER_FLAG_DIRECT 0x2 #define PVG_FT_RASTER_FLAG_CLIP 0x4 /*************************************************************************/ /* */ /* */ /* PVG_FT_Raster_Params */ /* */ /* */ /* A structure to hold the arguments used by a raster's render */ /* function. */ /* */ /* */ /* target :: The target bitmap. */ /* */ /* source :: A pointer to the source glyph image (e.g., an */ /* @PVG_FT_Outline). */ /* */ /* flags :: The rendering flags. */ /* */ /* gray_spans :: The gray span drawing callback. */ /* */ /* black_spans :: The black span drawing callback. UNIMPLEMENTED! */ /* */ /* bit_test :: The bit test callback. UNIMPLEMENTED! */ /* */ /* bit_set :: The bit set callback. UNIMPLEMENTED! */ /* */ /* user :: User-supplied data that is passed to each drawing */ /* callback. */ /* */ /* clip_box :: An optional clipping box. It is only used in */ /* direct rendering mode. Note that coordinates here */ /* should be expressed in _integer_ pixels (and not in */ /* 26.6 fixed-point units). */ /* */ /* */ /* An anti-aliased glyph bitmap is drawn if the @PVG_FT_RASTER_FLAG_AA */ /* bit flag is set in the `flags' field, otherwise a monochrome */ /* bitmap is generated. */ /* */ /* If the @PVG_FT_RASTER_FLAG_DIRECT bit flag is set in `flags', the */ /* raster will call the `gray_spans' callback to draw gray pixel */ /* spans, in the case of an aa glyph bitmap, it will call */ /* `black_spans', and `bit_test' and `bit_set' in the case of a */ /* monochrome bitmap. This allows direct composition over a */ /* pre-existing bitmap through user-provided callbacks to perform the */ /* span drawing/composition. */ /* */ /* Note that the `bit_test' and `bit_set' callbacks are required when */ /* rendering a monochrome bitmap, as they are crucial to implement */ /* correct drop-out control as defined in the TrueType specification. */ /* */ typedef struct PVG_FT_Raster_Params_ { const void* source; int flags; PVG_FT_SpanFunc gray_spans; void* user; PVG_FT_BBox clip_box; } PVG_FT_Raster_Params; void PVG_FT_Raster_Render(const PVG_FT_Raster_Params *params); #endif // PLUTOVG_FT_RASTER_H sammycage-plutovg-5695a71/source/plutovg-ft-stroker.c000066400000000000000000001702021510714322500227050ustar00rootroot00000000000000 /***************************************************************************/ /* */ /* ftstroke.c */ /* */ /* FreeType path stroker (body). */ /* */ /* Copyright 2002-2006, 2008-2011, 2013 by */ /* David Turner, Robert Wilhelm, and Werner Lemberg. */ /* */ /* This file is part of the FreeType project, and may only be used, */ /* modified, and distributed under the terms of the FreeType project */ /* license, FTL.TXT. By continuing to use, modify, or distribute */ /* this file you indicate that you have read the license and */ /* understand and accept it fully. */ /* */ /***************************************************************************/ #include "plutovg-ft-stroker.h" #include "plutovg-ft-math.h" #include #include #include /*************************************************************************/ /*************************************************************************/ /***** *****/ /***** BEZIER COMPUTATIONS *****/ /***** *****/ /*************************************************************************/ /*************************************************************************/ #define PVG_FT_SMALL_CONIC_THRESHOLD (PVG_FT_ANGLE_PI / 6) #define PVG_FT_SMALL_CUBIC_THRESHOLD (PVG_FT_ANGLE_PI / 8) #define PVG_FT_EPSILON 2 #define PVG_FT_IS_SMALL(x) ((x) > -PVG_FT_EPSILON && (x) < PVG_FT_EPSILON) static PVG_FT_Pos ft_pos_abs(PVG_FT_Pos x) { return x >= 0 ? x : -x; } static void ft_conic_split(PVG_FT_Vector* base) { PVG_FT_Pos a, b; base[4].x = base[2].x; a = base[0].x + base[1].x; b = base[1].x + base[2].x; base[3].x = b >> 1; base[2].x = ( a + b ) >> 2; base[1].x = a >> 1; base[4].y = base[2].y; a = base[0].y + base[1].y; b = base[1].y + base[2].y; base[3].y = b >> 1; base[2].y = ( a + b ) >> 2; base[1].y = a >> 1; } static PVG_FT_Bool ft_conic_is_small_enough(PVG_FT_Vector* base, PVG_FT_Angle* angle_in, PVG_FT_Angle* angle_out) { PVG_FT_Vector d1, d2; PVG_FT_Angle theta; PVG_FT_Int close1, close2; d1.x = base[1].x - base[2].x; d1.y = base[1].y - base[2].y; d2.x = base[0].x - base[1].x; d2.y = base[0].y - base[1].y; close1 = PVG_FT_IS_SMALL(d1.x) && PVG_FT_IS_SMALL(d1.y); close2 = PVG_FT_IS_SMALL(d2.x) && PVG_FT_IS_SMALL(d2.y); if (close1) { if (close2) { /* basically a point; */ /* do nothing to retain original direction */ } else { *angle_in = *angle_out = PVG_FT_Atan2(d2.x, d2.y); } } else /* !close1 */ { if (close2) { *angle_in = *angle_out = PVG_FT_Atan2(d1.x, d1.y); } else { *angle_in = PVG_FT_Atan2(d1.x, d1.y); *angle_out = PVG_FT_Atan2(d2.x, d2.y); } } theta = ft_pos_abs(PVG_FT_Angle_Diff(*angle_in, *angle_out)); return PVG_FT_BOOL(theta < PVG_FT_SMALL_CONIC_THRESHOLD); } static void ft_cubic_split(PVG_FT_Vector* base) { PVG_FT_Pos a, b, c; base[6].x = base[3].x; a = base[0].x + base[1].x; b = base[1].x + base[2].x; c = base[2].x + base[3].x; base[5].x = c >> 1; c += b; base[4].x = c >> 2; base[1].x = a >> 1; a += b; base[2].x = a >> 2; base[3].x = ( a + c ) >> 3; base[6].y = base[3].y; a = base[0].y + base[1].y; b = base[1].y + base[2].y; c = base[2].y + base[3].y; base[5].y = c >> 1; c += b; base[4].y = c >> 2; base[1].y = a >> 1; a += b; base[2].y = a >> 2; base[3].y = ( a + c ) >> 3; } /* Return the average of `angle1' and `angle2'. */ /* This gives correct result even if `angle1' and `angle2' */ /* have opposite signs. */ static PVG_FT_Angle ft_angle_mean(PVG_FT_Angle angle1, PVG_FT_Angle angle2) { return angle1 + PVG_FT_Angle_Diff(angle1, angle2) / 2; } static PVG_FT_Bool ft_cubic_is_small_enough(PVG_FT_Vector* base, PVG_FT_Angle* angle_in, PVG_FT_Angle* angle_mid, PVG_FT_Angle* angle_out) { PVG_FT_Vector d1, d2, d3; PVG_FT_Angle theta1, theta2; PVG_FT_Int close1, close2, close3; d1.x = base[2].x - base[3].x; d1.y = base[2].y - base[3].y; d2.x = base[1].x - base[2].x; d2.y = base[1].y - base[2].y; d3.x = base[0].x - base[1].x; d3.y = base[0].y - base[1].y; close1 = PVG_FT_IS_SMALL(d1.x) && PVG_FT_IS_SMALL(d1.y); close2 = PVG_FT_IS_SMALL(d2.x) && PVG_FT_IS_SMALL(d2.y); close3 = PVG_FT_IS_SMALL(d3.x) && PVG_FT_IS_SMALL(d3.y); if (close1) { if (close2) { if (close3) { /* basically a point; */ /* do nothing to retain original direction */ } else /* !close3 */ { *angle_in = *angle_mid = *angle_out = PVG_FT_Atan2(d3.x, d3.y); } } else /* !close2 */ { if (close3) { *angle_in = *angle_mid = *angle_out = PVG_FT_Atan2(d2.x, d2.y); } else /* !close3 */ { *angle_in = *angle_mid = PVG_FT_Atan2(d2.x, d2.y); *angle_out = PVG_FT_Atan2(d3.x, d3.y); } } } else /* !close1 */ { if (close2) { if (close3) { *angle_in = *angle_mid = *angle_out = PVG_FT_Atan2(d1.x, d1.y); } else /* !close3 */ { *angle_in = PVG_FT_Atan2(d1.x, d1.y); *angle_out = PVG_FT_Atan2(d3.x, d3.y); *angle_mid = ft_angle_mean(*angle_in, *angle_out); } } else /* !close2 */ { if (close3) { *angle_in = PVG_FT_Atan2(d1.x, d1.y); *angle_mid = *angle_out = PVG_FT_Atan2(d2.x, d2.y); } else /* !close3 */ { *angle_in = PVG_FT_Atan2(d1.x, d1.y); *angle_mid = PVG_FT_Atan2(d2.x, d2.y); *angle_out = PVG_FT_Atan2(d3.x, d3.y); } } } theta1 = ft_pos_abs(PVG_FT_Angle_Diff(*angle_in, *angle_mid)); theta2 = ft_pos_abs(PVG_FT_Angle_Diff(*angle_mid, *angle_out)); return PVG_FT_BOOL(theta1 < PVG_FT_SMALL_CUBIC_THRESHOLD && theta2 < PVG_FT_SMALL_CUBIC_THRESHOLD); } /*************************************************************************/ /*************************************************************************/ /***** *****/ /***** STROKE BORDERS *****/ /***** *****/ /*************************************************************************/ /*************************************************************************/ typedef enum PVG_FT_StrokeTags_ { PVG_FT_STROKE_TAG_ON = 1, /* on-curve point */ PVG_FT_STROKE_TAG_CUBIC = 2, /* cubic off-point */ PVG_FT_STROKE_TAG_BEGIN = 4, /* sub-path start */ PVG_FT_STROKE_TAG_END = 8 /* sub-path end */ } PVG_FT_StrokeTags; #define PVG_FT_STROKE_TAG_BEGIN_END \ (PVG_FT_STROKE_TAG_BEGIN | PVG_FT_STROKE_TAG_END) typedef struct PVG_FT_StrokeBorderRec_ { PVG_FT_UInt num_points; PVG_FT_UInt max_points; PVG_FT_Vector* points; PVG_FT_Byte* tags; PVG_FT_Bool movable; /* TRUE for ends of lineto borders */ PVG_FT_Int start; /* index of current sub-path start point */ PVG_FT_Bool valid; } PVG_FT_StrokeBorderRec, *PVG_FT_StrokeBorder; static PVG_FT_Error ft_stroke_border_grow(PVG_FT_StrokeBorder border, PVG_FT_UInt new_points) { PVG_FT_UInt old_max = border->max_points; PVG_FT_UInt new_max = border->num_points + new_points; PVG_FT_Error error = 0; if (new_max > old_max) { PVG_FT_UInt cur_max = old_max; while (cur_max < new_max) cur_max += (cur_max >> 1) + 16; border->points = (PVG_FT_Vector*)realloc(border->points, cur_max * sizeof(PVG_FT_Vector)); border->tags = (PVG_FT_Byte*)realloc(border->tags, cur_max * sizeof(PVG_FT_Byte)); if (!border->points || !border->tags) goto Exit; border->max_points = cur_max; } Exit: return error; } static void ft_stroke_border_close(PVG_FT_StrokeBorder border, PVG_FT_Bool reverse) { PVG_FT_UInt start = border->start; PVG_FT_UInt count = border->num_points; assert(border->start >= 0); /* don't record empty paths! */ if (count <= start + 1U) border->num_points = start; else { /* copy the last point to the start of this sub-path, since */ /* it contains the `adjusted' starting coordinates */ border->num_points = --count; border->points[start] = border->points[count]; border->tags[start] = border->tags[count]; if (reverse) { /* reverse the points */ { PVG_FT_Vector* vec1 = border->points + start + 1; PVG_FT_Vector* vec2 = border->points + count - 1; for (; vec1 < vec2; vec1++, vec2--) { PVG_FT_Vector tmp; tmp = *vec1; *vec1 = *vec2; *vec2 = tmp; } } /* then the tags */ { PVG_FT_Byte* tag1 = border->tags + start + 1; PVG_FT_Byte* tag2 = border->tags + count - 1; for (; tag1 < tag2; tag1++, tag2--) { PVG_FT_Byte tmp; tmp = *tag1; *tag1 = *tag2; *tag2 = tmp; } } } border->tags[start] |= PVG_FT_STROKE_TAG_BEGIN; border->tags[count - 1] |= PVG_FT_STROKE_TAG_END; } border->start = -1; border->movable = FALSE; } static PVG_FT_Error ft_stroke_border_lineto(PVG_FT_StrokeBorder border, PVG_FT_Vector* to, PVG_FT_Bool movable) { PVG_FT_Error error = 0; assert(border->start >= 0); if (border->movable) { /* move last point */ border->points[border->num_points - 1] = *to; } else { /* don't add zero-length lineto, but always add moveto */ if (border->num_points > border->start && PVG_FT_IS_SMALL(border->points[border->num_points - 1].x - to->x) && PVG_FT_IS_SMALL(border->points[border->num_points - 1].y - to->y)) return error; /* add one point */ error = ft_stroke_border_grow(border, 1); if (!error) { PVG_FT_Vector* vec = border->points + border->num_points; PVG_FT_Byte* tag = border->tags + border->num_points; vec[0] = *to; tag[0] = PVG_FT_STROKE_TAG_ON; border->num_points += 1; } } border->movable = movable; return error; } static PVG_FT_Error ft_stroke_border_conicto(PVG_FT_StrokeBorder border, PVG_FT_Vector* control, PVG_FT_Vector* to) { PVG_FT_Error error; assert(border->start >= 0); error = ft_stroke_border_grow(border, 2); if (!error) { PVG_FT_Vector* vec = border->points + border->num_points; PVG_FT_Byte* tag = border->tags + border->num_points; vec[0] = *control; vec[1] = *to; tag[0] = 0; tag[1] = PVG_FT_STROKE_TAG_ON; border->num_points += 2; } border->movable = FALSE; return error; } static PVG_FT_Error ft_stroke_border_cubicto(PVG_FT_StrokeBorder border, PVG_FT_Vector* control1, PVG_FT_Vector* control2, PVG_FT_Vector* to) { PVG_FT_Error error; assert(border->start >= 0); error = ft_stroke_border_grow(border, 3); if (!error) { PVG_FT_Vector* vec = border->points + border->num_points; PVG_FT_Byte* tag = border->tags + border->num_points; vec[0] = *control1; vec[1] = *control2; vec[2] = *to; tag[0] = PVG_FT_STROKE_TAG_CUBIC; tag[1] = PVG_FT_STROKE_TAG_CUBIC; tag[2] = PVG_FT_STROKE_TAG_ON; border->num_points += 3; } border->movable = FALSE; return error; } #define PVG_FT_ARC_CUBIC_ANGLE (PVG_FT_ANGLE_PI / 2) static PVG_FT_Error ft_stroke_border_arcto( PVG_FT_StrokeBorder border, PVG_FT_Vector* center, PVG_FT_Fixed radius, PVG_FT_Angle angle_start, PVG_FT_Angle angle_diff ) { PVG_FT_Fixed coef; PVG_FT_Vector a0, a1, a2, a3; PVG_FT_Int i, arcs = 1; PVG_FT_Error error = 0; /* number of cubic arcs to draw */ while ( angle_diff > PVG_FT_ARC_CUBIC_ANGLE * arcs || -angle_diff > PVG_FT_ARC_CUBIC_ANGLE * arcs ) arcs++; /* control tangents */ coef = PVG_FT_Tan( angle_diff / ( 4 * arcs ) ); coef += coef / 3; /* compute start and first control point */ PVG_FT_Vector_From_Polar( &a0, radius, angle_start ); a1.x = PVG_FT_MulFix( -a0.y, coef ); a1.y = PVG_FT_MulFix( a0.x, coef ); a0.x += center->x; a0.y += center->y; a1.x += a0.x; a1.y += a0.y; for ( i = 1; i <= arcs; i++ ) { /* compute end and second control point */ PVG_FT_Vector_From_Polar( &a3, radius, angle_start + i * angle_diff / arcs ); a2.x = PVG_FT_MulFix( a3.y, coef ); a2.y = PVG_FT_MulFix( -a3.x, coef ); a3.x += center->x; a3.y += center->y; a2.x += a3.x; a2.y += a3.y; /* add cubic arc */ error = ft_stroke_border_cubicto( border, &a1, &a2, &a3 ); if ( error ) break; /* a0 = a3; */ a1.x = a3.x - a2.x + a3.x; a1.y = a3.y - a2.y + a3.y; } return error; } static PVG_FT_Error ft_stroke_border_moveto(PVG_FT_StrokeBorder border, PVG_FT_Vector* to) { /* close current open path if any ? */ if (border->start >= 0) ft_stroke_border_close(border, FALSE); border->start = border->num_points; border->movable = FALSE; return ft_stroke_border_lineto(border, to, FALSE); } static void ft_stroke_border_init(PVG_FT_StrokeBorder border) { border->points = NULL; border->tags = NULL; border->num_points = 0; border->max_points = 0; border->start = -1; border->valid = FALSE; } static void ft_stroke_border_reset(PVG_FT_StrokeBorder border) { border->num_points = 0; border->start = -1; border->valid = FALSE; } static void ft_stroke_border_done(PVG_FT_StrokeBorder border) { free(border->points); free(border->tags); border->num_points = 0; border->max_points = 0; border->start = -1; border->valid = FALSE; } static PVG_FT_Error ft_stroke_border_get_counts(PVG_FT_StrokeBorder border, PVG_FT_UInt* anum_points, PVG_FT_UInt* anum_contours) { PVG_FT_Error error = 0; PVG_FT_UInt num_points = 0; PVG_FT_UInt num_contours = 0; PVG_FT_UInt count = border->num_points; PVG_FT_Vector* point = border->points; PVG_FT_Byte* tags = border->tags; PVG_FT_Int in_contour = 0; for (; count > 0; count--, num_points++, point++, tags++) { if (tags[0] & PVG_FT_STROKE_TAG_BEGIN) { if (in_contour != 0) goto Fail; in_contour = 1; } else if (in_contour == 0) goto Fail; if (tags[0] & PVG_FT_STROKE_TAG_END) { in_contour = 0; num_contours++; } } if (in_contour != 0) goto Fail; border->valid = TRUE; Exit: *anum_points = num_points; *anum_contours = num_contours; return error; Fail: num_points = 0; num_contours = 0; goto Exit; } static void ft_stroke_border_export(PVG_FT_StrokeBorder border, PVG_FT_Outline* outline) { /* copy point locations */ memcpy(outline->points + outline->n_points, border->points, border->num_points * sizeof(PVG_FT_Vector)); /* copy tags */ { PVG_FT_UInt count = border->num_points; PVG_FT_Byte* read = border->tags; PVG_FT_Byte* write = (PVG_FT_Byte*)outline->tags + outline->n_points; for (; count > 0; count--, read++, write++) { if (*read & PVG_FT_STROKE_TAG_ON) *write = PVG_FT_CURVE_TAG_ON; else if (*read & PVG_FT_STROKE_TAG_CUBIC) *write = PVG_FT_CURVE_TAG_CUBIC; else *write = PVG_FT_CURVE_TAG_CONIC; } } /* copy contours */ { PVG_FT_UInt count = border->num_points; PVG_FT_Byte* tags = border->tags; PVG_FT_Int* write = outline->contours + outline->n_contours; PVG_FT_Int idx = (PVG_FT_Int)outline->n_points; for (; count > 0; count--, tags++, idx++) { if (*tags & PVG_FT_STROKE_TAG_END) { *write++ = idx; outline->n_contours++; } } } outline->n_points = (int)(outline->n_points + border->num_points); assert(PVG_FT_Outline_Check(outline) == 0); } /*************************************************************************/ /*************************************************************************/ /***** *****/ /***** STROKER *****/ /***** *****/ /*************************************************************************/ /*************************************************************************/ #define PVG_FT_SIDE_TO_ROTATE(s) (PVG_FT_ANGLE_PI2 - (s)*PVG_FT_ANGLE_PI) typedef struct PVG_FT_StrokerRec_ { PVG_FT_Angle angle_in; /* direction into curr join */ PVG_FT_Angle angle_out; /* direction out of join */ PVG_FT_Vector center; /* current position */ PVG_FT_Fixed line_length; /* length of last lineto */ PVG_FT_Bool first_point; /* is this the start? */ PVG_FT_Bool subpath_open; /* is the subpath open? */ PVG_FT_Angle subpath_angle; /* subpath start direction */ PVG_FT_Vector subpath_start; /* subpath start position */ PVG_FT_Fixed subpath_line_length; /* subpath start lineto len */ PVG_FT_Bool handle_wide_strokes; /* use wide strokes logic? */ PVG_FT_Stroker_LineCap line_cap; PVG_FT_Stroker_LineJoin line_join; PVG_FT_Stroker_LineJoin line_join_saved; PVG_FT_Fixed miter_limit; PVG_FT_Fixed radius; PVG_FT_StrokeBorderRec borders[2]; } PVG_FT_StrokerRec; /* documentation is in ftstroke.h */ PVG_FT_Error PVG_FT_Stroker_New(PVG_FT_Stroker* astroker) { PVG_FT_Error error = 0; /* assigned in PVG_FT_NEW */ PVG_FT_Stroker stroker = NULL; stroker = (PVG_FT_StrokerRec*)calloc(1, sizeof(PVG_FT_StrokerRec)); if (stroker) { ft_stroke_border_init(&stroker->borders[0]); ft_stroke_border_init(&stroker->borders[1]); } *astroker = stroker; return error; } void PVG_FT_Stroker_Rewind(PVG_FT_Stroker stroker) { if (stroker) { ft_stroke_border_reset(&stroker->borders[0]); ft_stroke_border_reset(&stroker->borders[1]); } } /* documentation is in ftstroke.h */ void PVG_FT_Stroker_Set(PVG_FT_Stroker stroker, PVG_FT_Fixed radius, PVG_FT_Stroker_LineCap line_cap, PVG_FT_Stroker_LineJoin line_join, PVG_FT_Fixed miter_limit) { stroker->radius = radius; stroker->line_cap = line_cap; stroker->line_join = line_join; stroker->miter_limit = miter_limit; /* ensure miter limit has sensible value */ if (stroker->miter_limit < 0x10000) stroker->miter_limit = 0x10000; /* save line join style: */ /* line join style can be temporarily changed when stroking curves */ stroker->line_join_saved = line_join; PVG_FT_Stroker_Rewind(stroker); } /* documentation is in ftstroke.h */ void PVG_FT_Stroker_Done(PVG_FT_Stroker stroker) { if (stroker) { ft_stroke_border_done(&stroker->borders[0]); ft_stroke_border_done(&stroker->borders[1]); free(stroker); } } /* create a circular arc at a corner or cap */ static PVG_FT_Error ft_stroker_arcto(PVG_FT_Stroker stroker, PVG_FT_Int side) { PVG_FT_Angle total, rotate; PVG_FT_Fixed radius = stroker->radius; PVG_FT_Error error = 0; PVG_FT_StrokeBorder border = stroker->borders + side; rotate = PVG_FT_SIDE_TO_ROTATE(side); total = PVG_FT_Angle_Diff(stroker->angle_in, stroker->angle_out); if (total == PVG_FT_ANGLE_PI) total = -rotate * 2; error = ft_stroke_border_arcto(border, &stroker->center, radius, stroker->angle_in + rotate, total); border->movable = FALSE; return error; } /* add a cap at the end of an opened path */ static PVG_FT_Error ft_stroker_cap(PVG_FT_Stroker stroker, PVG_FT_Angle angle, PVG_FT_Int side) { PVG_FT_Error error = 0; if (stroker->line_cap == PVG_FT_STROKER_LINECAP_ROUND) { /* add a round cap */ stroker->angle_in = angle; stroker->angle_out = angle + PVG_FT_ANGLE_PI; error = ft_stroker_arcto(stroker, side); } else { /* add a square or butt cap */ PVG_FT_Vector middle, delta; PVG_FT_Fixed radius = stroker->radius; PVG_FT_StrokeBorder border = stroker->borders + side; /* compute middle point and first angle point */ PVG_FT_Vector_From_Polar( &middle, radius, angle ); delta.x = side ? middle.y : -middle.y; delta.y = side ? -middle.x : middle.x; if ( stroker->line_cap == PVG_FT_STROKER_LINECAP_SQUARE ) { middle.x += stroker->center.x; middle.y += stroker->center.y; } else /* PVG_FT_STROKER_LINECAP_BUTT */ { middle.x = stroker->center.x; middle.y = stroker->center.y; } delta.x += middle.x; delta.y += middle.y; error = ft_stroke_border_lineto( border, &delta, FALSE ); if ( error ) goto Exit; /* compute second angle point */ delta.x = middle.x - delta.x + middle.x; delta.y = middle.y - delta.y + middle.y; error = ft_stroke_border_lineto( border, &delta, FALSE ); } Exit: return error; } /* process an inside corner, i.e. compute intersection */ static PVG_FT_Error ft_stroker_inside(PVG_FT_Stroker stroker, PVG_FT_Int side, PVG_FT_Fixed line_length) { PVG_FT_StrokeBorder border = stroker->borders + side; PVG_FT_Angle phi, theta, rotate; PVG_FT_Fixed length; PVG_FT_Vector sigma = {0, 0}; PVG_FT_Vector delta; PVG_FT_Error error = 0; PVG_FT_Bool intersect; /* use intersection of lines? */ rotate = PVG_FT_SIDE_TO_ROTATE(side); theta = PVG_FT_Angle_Diff(stroker->angle_in, stroker->angle_out) / 2; /* Only intersect borders if between two lineto's and both */ /* lines are long enough (line_length is zero for curves). */ if (!border->movable || line_length == 0 || theta > 0x59C000 || theta < -0x59C000 ) intersect = FALSE; else { /* compute minimum required length of lines */ PVG_FT_Fixed min_length; PVG_FT_Vector_Unit( &sigma, theta ); min_length = ft_pos_abs( PVG_FT_MulDiv( stroker->radius, sigma.y, sigma.x ) ); intersect = PVG_FT_BOOL( min_length && stroker->line_length >= min_length && line_length >= min_length ); } if (!intersect) { PVG_FT_Vector_From_Polar(&delta, stroker->radius, stroker->angle_out + rotate); delta.x += stroker->center.x; delta.y += stroker->center.y; border->movable = FALSE; } else { /* compute median angle */ phi = stroker->angle_in + theta + rotate; length = PVG_FT_DivFix( stroker->radius, sigma.x ); PVG_FT_Vector_From_Polar( &delta, length, phi ); delta.x += stroker->center.x; delta.y += stroker->center.y; } error = ft_stroke_border_lineto(border, &delta, FALSE); return error; } /* process an outside corner, i.e. compute bevel/miter/round */ static PVG_FT_Error ft_stroker_outside( PVG_FT_Stroker stroker, PVG_FT_Int side, PVG_FT_Fixed line_length ) { PVG_FT_StrokeBorder border = stroker->borders + side; PVG_FT_Error error; PVG_FT_Angle rotate; if ( stroker->line_join == PVG_FT_STROKER_LINEJOIN_ROUND ) error = ft_stroker_arcto( stroker, side ); else { /* this is a mitered (pointed) or beveled (truncated) corner */ PVG_FT_Fixed radius = stroker->radius; PVG_FT_Vector sigma = {0, 0}; PVG_FT_Angle theta = 0, phi = 0; PVG_FT_Bool bevel, fixed_bevel; rotate = PVG_FT_SIDE_TO_ROTATE( side ); bevel = PVG_FT_BOOL( stroker->line_join == PVG_FT_STROKER_LINEJOIN_BEVEL ); fixed_bevel = PVG_FT_BOOL( stroker->line_join != PVG_FT_STROKER_LINEJOIN_MITER_VARIABLE ); /* check miter limit first */ if ( !bevel ) { theta = PVG_FT_Angle_Diff( stroker->angle_in, stroker->angle_out ) / 2; if ( theta == PVG_FT_ANGLE_PI2 ) theta = -rotate; phi = stroker->angle_in + theta + rotate; PVG_FT_Vector_From_Polar( &sigma, stroker->miter_limit, theta ); /* is miter limit exceeded? */ if ( sigma.x < 0x10000L ) { /* don't create variable bevels for very small deviations; */ /* FT_Sin(x) = 0 for x <= 57 */ if ( fixed_bevel || ft_pos_abs( theta ) > 57 ) bevel = TRUE; } } if ( bevel ) /* this is a bevel (broken angle) */ { if ( fixed_bevel ) { /* the outer corners are simply joined together */ PVG_FT_Vector delta; /* add bevel */ PVG_FT_Vector_From_Polar( &delta, radius, stroker->angle_out + rotate ); delta.x += stroker->center.x; delta.y += stroker->center.y; border->movable = FALSE; error = ft_stroke_border_lineto( border, &delta, FALSE ); } else /* variable bevel or clipped miter */ { /* the miter is truncated */ PVG_FT_Vector middle, delta; PVG_FT_Fixed coef; /* compute middle point and first angle point */ PVG_FT_Vector_From_Polar( &middle, PVG_FT_MulFix( radius, stroker->miter_limit ), phi ); coef = PVG_FT_DivFix( 0x10000L - sigma.x, sigma.y ); delta.x = PVG_FT_MulFix( middle.y, coef ); delta.y = PVG_FT_MulFix( -middle.x, coef ); middle.x += stroker->center.x; middle.y += stroker->center.y; delta.x += middle.x; delta.y += middle.y; error = ft_stroke_border_lineto( border, &delta, FALSE ); if ( error ) goto Exit; /* compute second angle point */ delta.x = middle.x - delta.x + middle.x; delta.y = middle.y - delta.y + middle.y; error = ft_stroke_border_lineto( border, &delta, FALSE ); if ( error ) goto Exit; /* finally, add an end point; only needed if not lineto */ /* (line_length is zero for curves) */ if ( line_length == 0 ) { PVG_FT_Vector_From_Polar( &delta, radius, stroker->angle_out + rotate ); delta.x += stroker->center.x; delta.y += stroker->center.y; error = ft_stroke_border_lineto( border, &delta, FALSE ); } } } else /* this is a miter (intersection) */ { PVG_FT_Fixed length; PVG_FT_Vector delta; length = PVG_FT_MulDiv( stroker->radius, stroker->miter_limit, sigma.x ); PVG_FT_Vector_From_Polar( &delta, length, phi ); delta.x += stroker->center.x; delta.y += stroker->center.y; error = ft_stroke_border_lineto( border, &delta, FALSE ); if ( error ) goto Exit; /* now add an end point; only needed if not lineto */ /* (line_length is zero for curves) */ if ( line_length == 0 ) { PVG_FT_Vector_From_Polar( &delta, stroker->radius, stroker->angle_out + rotate ); delta.x += stroker->center.x; delta.y += stroker->center.y; error = ft_stroke_border_lineto( border, &delta, FALSE ); } } } Exit: return error; } static PVG_FT_Error ft_stroker_process_corner(PVG_FT_Stroker stroker, PVG_FT_Fixed line_length) { PVG_FT_Error error = 0; PVG_FT_Angle turn; PVG_FT_Int inside_side; turn = PVG_FT_Angle_Diff(stroker->angle_in, stroker->angle_out); /* no specific corner processing is required if the turn is 0 */ if (turn == 0) goto Exit; /* when we turn to the right, the inside side is 0 */ inside_side = 0; /* otherwise, the inside side is 1 */ if (turn < 0) inside_side = 1; /* process the inside side */ error = ft_stroker_inside(stroker, inside_side, line_length); if (error) goto Exit; /* process the outside side */ error = ft_stroker_outside(stroker, 1 - inside_side, line_length); Exit: return error; } /* add two points to the left and right borders corresponding to the */ /* start of the subpath */ static PVG_FT_Error ft_stroker_subpath_start(PVG_FT_Stroker stroker, PVG_FT_Angle start_angle, PVG_FT_Fixed line_length) { PVG_FT_Vector delta; PVG_FT_Vector point; PVG_FT_Error error; PVG_FT_StrokeBorder border; PVG_FT_Vector_From_Polar(&delta, stroker->radius, start_angle + PVG_FT_ANGLE_PI2); point.x = stroker->center.x + delta.x; point.y = stroker->center.y + delta.y; border = stroker->borders; error = ft_stroke_border_moveto(border, &point); if (error) goto Exit; point.x = stroker->center.x - delta.x; point.y = stroker->center.y - delta.y; border++; error = ft_stroke_border_moveto(border, &point); /* save angle, position, and line length for last join */ /* (line_length is zero for curves) */ stroker->subpath_angle = start_angle; stroker->first_point = FALSE; stroker->subpath_line_length = line_length; Exit: return error; } /* documentation is in ftstroke.h */ PVG_FT_Error PVG_FT_Stroker_LineTo(PVG_FT_Stroker stroker, PVG_FT_Vector* to) { PVG_FT_Error error = 0; PVG_FT_StrokeBorder border; PVG_FT_Vector delta; PVG_FT_Angle angle; PVG_FT_Int side; PVG_FT_Fixed line_length; delta.x = to->x - stroker->center.x; delta.y = to->y - stroker->center.y; /* a zero-length lineto is a no-op; avoid creating a spurious corner */ if (delta.x == 0 && delta.y == 0) goto Exit; /* compute length of line */ line_length = PVG_FT_Vector_Length(&delta); angle = PVG_FT_Atan2(delta.x, delta.y); PVG_FT_Vector_From_Polar(&delta, stroker->radius, angle + PVG_FT_ANGLE_PI2); /* process corner if necessary */ if (stroker->first_point) { /* This is the first segment of a subpath. We need to */ /* add a point to each border at their respective starting */ /* point locations. */ error = ft_stroker_subpath_start(stroker, angle, line_length); if (error) goto Exit; } else { /* process the current corner */ stroker->angle_out = angle; error = ft_stroker_process_corner(stroker, line_length); if (error) goto Exit; } /* now add a line segment to both the `inside' and `outside' paths */ for (border = stroker->borders, side = 1; side >= 0; side--, border++) { PVG_FT_Vector point; point.x = to->x + delta.x; point.y = to->y + delta.y; /* the ends of lineto borders are movable */ error = ft_stroke_border_lineto(border, &point, TRUE); if (error) goto Exit; delta.x = -delta.x; delta.y = -delta.y; } stroker->angle_in = angle; stroker->center = *to; stroker->line_length = line_length; Exit: return error; } /* documentation is in ftstroke.h */ PVG_FT_Error PVG_FT_Stroker_ConicTo(PVG_FT_Stroker stroker, PVG_FT_Vector* control, PVG_FT_Vector* to) { PVG_FT_Error error = 0; PVG_FT_Vector bez_stack[34]; PVG_FT_Vector* arc; PVG_FT_Vector* limit = bez_stack + 30; PVG_FT_Bool first_arc = TRUE; /* if all control points are coincident, this is a no-op; */ /* avoid creating a spurious corner */ if (PVG_FT_IS_SMALL(stroker->center.x - control->x) && PVG_FT_IS_SMALL(stroker->center.y - control->y) && PVG_FT_IS_SMALL(control->x - to->x) && PVG_FT_IS_SMALL(control->y - to->y)) { stroker->center = *to; goto Exit; } arc = bez_stack; arc[0] = *to; arc[1] = *control; arc[2] = stroker->center; while (arc >= bez_stack) { PVG_FT_Angle angle_in, angle_out; /* initialize with current direction */ angle_in = angle_out = stroker->angle_in; if (arc < limit && !ft_conic_is_small_enough(arc, &angle_in, &angle_out)) { if (stroker->first_point) stroker->angle_in = angle_in; ft_conic_split(arc); arc += 2; continue; } if (first_arc) { first_arc = FALSE; /* process corner if necessary */ if (stroker->first_point) error = ft_stroker_subpath_start(stroker, angle_in, 0); else { stroker->angle_out = angle_in; error = ft_stroker_process_corner(stroker, 0); } } else if (ft_pos_abs(PVG_FT_Angle_Diff(stroker->angle_in, angle_in)) > PVG_FT_SMALL_CONIC_THRESHOLD / 4) { /* if the deviation from one arc to the next is too great, */ /* add a round corner */ stroker->center = arc[2]; stroker->angle_out = angle_in; stroker->line_join = PVG_FT_STROKER_LINEJOIN_ROUND; error = ft_stroker_process_corner(stroker, 0); /* reinstate line join style */ stroker->line_join = stroker->line_join_saved; } if (error) goto Exit; /* the arc's angle is small enough; we can add it directly to each */ /* border */ { PVG_FT_Vector ctrl, end; PVG_FT_Angle theta, phi, rotate, alpha0 = 0; PVG_FT_Fixed length; PVG_FT_StrokeBorder border; PVG_FT_Int side; theta = PVG_FT_Angle_Diff(angle_in, angle_out) / 2; phi = angle_in + theta; length = PVG_FT_DivFix(stroker->radius, PVG_FT_Cos(theta)); /* compute direction of original arc */ if (stroker->handle_wide_strokes) alpha0 = PVG_FT_Atan2(arc[0].x - arc[2].x, arc[0].y - arc[2].y); for (border = stroker->borders, side = 0; side <= 1; side++, border++) { rotate = PVG_FT_SIDE_TO_ROTATE(side); /* compute control point */ PVG_FT_Vector_From_Polar(&ctrl, length, phi + rotate); ctrl.x += arc[1].x; ctrl.y += arc[1].y; /* compute end point */ PVG_FT_Vector_From_Polar(&end, stroker->radius, angle_out + rotate); end.x += arc[0].x; end.y += arc[0].y; if (stroker->handle_wide_strokes) { PVG_FT_Vector start; PVG_FT_Angle alpha1; /* determine whether the border radius is greater than the */ /* radius of curvature of the original arc */ start = border->points[border->num_points - 1]; alpha1 = PVG_FT_Atan2(end.x - start.x, end.y - start.y); /* is the direction of the border arc opposite to */ /* that of the original arc? */ if (ft_pos_abs(PVG_FT_Angle_Diff(alpha0, alpha1)) > PVG_FT_ANGLE_PI / 2) { PVG_FT_Angle beta, gamma; PVG_FT_Vector bvec, delta; PVG_FT_Fixed blen, sinA, sinB, alen; /* use the sine rule to find the intersection point */ beta = PVG_FT_Atan2(arc[2].x - start.x, arc[2].y - start.y); gamma = PVG_FT_Atan2(arc[0].x - end.x, arc[0].y - end.y); bvec.x = end.x - start.x; bvec.y = end.y - start.y; blen = PVG_FT_Vector_Length(&bvec); sinA = ft_pos_abs(PVG_FT_Sin(alpha1 - gamma)); sinB = ft_pos_abs(PVG_FT_Sin(beta - gamma)); alen = PVG_FT_MulDiv(blen, sinA, sinB); PVG_FT_Vector_From_Polar(&delta, alen, beta); delta.x += start.x; delta.y += start.y; /* circumnavigate the negative sector backwards */ border->movable = FALSE; error = ft_stroke_border_lineto(border, &delta, FALSE); if (error) goto Exit; error = ft_stroke_border_lineto(border, &end, FALSE); if (error) goto Exit; error = ft_stroke_border_conicto(border, &ctrl, &start); if (error) goto Exit; /* and then move to the endpoint */ error = ft_stroke_border_lineto(border, &end, FALSE); if (error) goto Exit; continue; } /* else fall through */ } /* simply add an arc */ error = ft_stroke_border_conicto(border, &ctrl, &end); if (error) goto Exit; } } arc -= 2; stroker->angle_in = angle_out; } stroker->center = *to; stroker->line_length = 0; Exit: return error; } /* documentation is in ftstroke.h */ PVG_FT_Error PVG_FT_Stroker_CubicTo(PVG_FT_Stroker stroker, PVG_FT_Vector* control1, PVG_FT_Vector* control2, PVG_FT_Vector* to) { PVG_FT_Error error = 0; PVG_FT_Vector bez_stack[37]; PVG_FT_Vector* arc; PVG_FT_Vector* limit = bez_stack + 32; PVG_FT_Bool first_arc = TRUE; /* if all control points are coincident, this is a no-op; */ /* avoid creating a spurious corner */ if (PVG_FT_IS_SMALL(stroker->center.x - control1->x) && PVG_FT_IS_SMALL(stroker->center.y - control1->y) && PVG_FT_IS_SMALL(control1->x - control2->x) && PVG_FT_IS_SMALL(control1->y - control2->y) && PVG_FT_IS_SMALL(control2->x - to->x) && PVG_FT_IS_SMALL(control2->y - to->y)) { stroker->center = *to; goto Exit; } arc = bez_stack; arc[0] = *to; arc[1] = *control2; arc[2] = *control1; arc[3] = stroker->center; while (arc >= bez_stack) { PVG_FT_Angle angle_in, angle_mid, angle_out; /* initialize with current direction */ angle_in = angle_out = angle_mid = stroker->angle_in; if (arc < limit && !ft_cubic_is_small_enough(arc, &angle_in, &angle_mid, &angle_out)) { if (stroker->first_point) stroker->angle_in = angle_in; ft_cubic_split(arc); arc += 3; continue; } if (first_arc) { first_arc = FALSE; /* process corner if necessary */ if (stroker->first_point) error = ft_stroker_subpath_start(stroker, angle_in, 0); else { stroker->angle_out = angle_in; error = ft_stroker_process_corner(stroker, 0); } } else if (ft_pos_abs(PVG_FT_Angle_Diff(stroker->angle_in, angle_in)) > PVG_FT_SMALL_CUBIC_THRESHOLD / 4) { /* if the deviation from one arc to the next is too great, */ /* add a round corner */ stroker->center = arc[3]; stroker->angle_out = angle_in; stroker->line_join = PVG_FT_STROKER_LINEJOIN_ROUND; error = ft_stroker_process_corner(stroker, 0); /* reinstate line join style */ stroker->line_join = stroker->line_join_saved; } if (error) goto Exit; /* the arc's angle is small enough; we can add it directly to each */ /* border */ { PVG_FT_Vector ctrl1, ctrl2, end; PVG_FT_Angle theta1, phi1, theta2, phi2, rotate, alpha0 = 0; PVG_FT_Fixed length1, length2; PVG_FT_StrokeBorder border; PVG_FT_Int side; theta1 = PVG_FT_Angle_Diff(angle_in, angle_mid) / 2; theta2 = PVG_FT_Angle_Diff(angle_mid, angle_out) / 2; phi1 = ft_angle_mean(angle_in, angle_mid); phi2 = ft_angle_mean(angle_mid, angle_out); length1 = PVG_FT_DivFix(stroker->radius, PVG_FT_Cos(theta1)); length2 = PVG_FT_DivFix(stroker->radius, PVG_FT_Cos(theta2)); /* compute direction of original arc */ if (stroker->handle_wide_strokes) alpha0 = PVG_FT_Atan2(arc[0].x - arc[3].x, arc[0].y - arc[3].y); for (border = stroker->borders, side = 0; side <= 1; side++, border++) { rotate = PVG_FT_SIDE_TO_ROTATE(side); /* compute control points */ PVG_FT_Vector_From_Polar(&ctrl1, length1, phi1 + rotate); ctrl1.x += arc[2].x; ctrl1.y += arc[2].y; PVG_FT_Vector_From_Polar(&ctrl2, length2, phi2 + rotate); ctrl2.x += arc[1].x; ctrl2.y += arc[1].y; /* compute end point */ PVG_FT_Vector_From_Polar(&end, stroker->radius, angle_out + rotate); end.x += arc[0].x; end.y += arc[0].y; if (stroker->handle_wide_strokes) { PVG_FT_Vector start; PVG_FT_Angle alpha1; /* determine whether the border radius is greater than the */ /* radius of curvature of the original arc */ start = border->points[border->num_points - 1]; alpha1 = PVG_FT_Atan2(end.x - start.x, end.y - start.y); /* is the direction of the border arc opposite to */ /* that of the original arc? */ if (ft_pos_abs(PVG_FT_Angle_Diff(alpha0, alpha1)) > PVG_FT_ANGLE_PI / 2) { PVG_FT_Angle beta, gamma; PVG_FT_Vector bvec, delta; PVG_FT_Fixed blen, sinA, sinB, alen; /* use the sine rule to find the intersection point */ beta = PVG_FT_Atan2(arc[3].x - start.x, arc[3].y - start.y); gamma = PVG_FT_Atan2(arc[0].x - end.x, arc[0].y - end.y); bvec.x = end.x - start.x; bvec.y = end.y - start.y; blen = PVG_FT_Vector_Length(&bvec); sinA = ft_pos_abs(PVG_FT_Sin(alpha1 - gamma)); sinB = ft_pos_abs(PVG_FT_Sin(beta - gamma)); alen = PVG_FT_MulDiv(blen, sinA, sinB); PVG_FT_Vector_From_Polar(&delta, alen, beta); delta.x += start.x; delta.y += start.y; /* circumnavigate the negative sector backwards */ border->movable = FALSE; error = ft_stroke_border_lineto(border, &delta, FALSE); if (error) goto Exit; error = ft_stroke_border_lineto(border, &end, FALSE); if (error) goto Exit; error = ft_stroke_border_cubicto(border, &ctrl2, &ctrl1, &start); if (error) goto Exit; /* and then move to the endpoint */ error = ft_stroke_border_lineto(border, &end, FALSE); if (error) goto Exit; continue; } /* else fall through */ } /* simply add an arc */ error = ft_stroke_border_cubicto(border, &ctrl1, &ctrl2, &end); if (error) goto Exit; } } arc -= 3; stroker->angle_in = angle_out; } stroker->center = *to; stroker->line_length = 0; Exit: return error; } /* documentation is in ftstroke.h */ PVG_FT_Error PVG_FT_Stroker_BeginSubPath(PVG_FT_Stroker stroker, PVG_FT_Vector* to, PVG_FT_Bool open) { /* We cannot process the first point, because there is not enough */ /* information regarding its corner/cap. The latter will be processed */ /* in the `PVG_FT_Stroker_EndSubPath' routine. */ /* */ stroker->first_point = TRUE; stroker->center = *to; stroker->subpath_open = open; /* Determine if we need to check whether the border radius is greater */ /* than the radius of curvature of a curve, to handle this case */ /* specially. This is only required if bevel joins or butt caps may */ /* be created, because round & miter joins and round & square caps */ /* cover the negative sector created with wide strokes. */ stroker->handle_wide_strokes = PVG_FT_BOOL(stroker->line_join != PVG_FT_STROKER_LINEJOIN_ROUND || (stroker->subpath_open && stroker->line_cap == PVG_FT_STROKER_LINECAP_BUTT)); /* record the subpath start point for each border */ stroker->subpath_start = *to; stroker->angle_in = 0; return 0; } static PVG_FT_Error ft_stroker_add_reverse_left(PVG_FT_Stroker stroker, PVG_FT_Bool open) { PVG_FT_StrokeBorder right = stroker->borders + 0; PVG_FT_StrokeBorder left = stroker->borders + 1; PVG_FT_Int new_points; PVG_FT_Error error = 0; assert(left->start >= 0); new_points = left->num_points - left->start; if (new_points > 0) { error = ft_stroke_border_grow(right, (PVG_FT_UInt)new_points); if (error) goto Exit; { PVG_FT_Vector* dst_point = right->points + right->num_points; PVG_FT_Byte* dst_tag = right->tags + right->num_points; PVG_FT_Vector* src_point = left->points + left->num_points - 1; PVG_FT_Byte* src_tag = left->tags + left->num_points - 1; while (src_point >= left->points + left->start) { *dst_point = *src_point; *dst_tag = *src_tag; if (open) dst_tag[0] &= ~PVG_FT_STROKE_TAG_BEGIN_END; else { PVG_FT_Byte ttag = (PVG_FT_Byte)(dst_tag[0] & PVG_FT_STROKE_TAG_BEGIN_END); /* switch begin/end tags if necessary */ if (ttag == PVG_FT_STROKE_TAG_BEGIN || ttag == PVG_FT_STROKE_TAG_END) dst_tag[0] ^= PVG_FT_STROKE_TAG_BEGIN_END; } src_point--; src_tag--; dst_point++; dst_tag++; } } left->num_points = left->start; right->num_points += new_points; right->movable = FALSE; left->movable = FALSE; } Exit: return error; } /* documentation is in ftstroke.h */ /* there's a lot of magic in this function! */ PVG_FT_Error PVG_FT_Stroker_EndSubPath(PVG_FT_Stroker stroker) { PVG_FT_Error error = 0; if (stroker->subpath_open) { PVG_FT_StrokeBorder right = stroker->borders; /* All right, this is an opened path, we need to add a cap between */ /* right & left, add the reverse of left, then add a final cap */ /* between left & right. */ error = ft_stroker_cap(stroker, stroker->angle_in, 0); if (error) goto Exit; /* add reversed points from `left' to `right' */ error = ft_stroker_add_reverse_left(stroker, TRUE); if (error) goto Exit; /* now add the final cap */ stroker->center = stroker->subpath_start; error = ft_stroker_cap(stroker, stroker->subpath_angle + PVG_FT_ANGLE_PI, 0); if (error) goto Exit; /* Now end the right subpath accordingly. The left one is */ /* rewind and doesn't need further processing. */ ft_stroke_border_close(right, FALSE); } else { PVG_FT_Angle turn; PVG_FT_Int inside_side; /* close the path if needed */ if (stroker->center.x != stroker->subpath_start.x || stroker->center.y != stroker->subpath_start.y) { error = PVG_FT_Stroker_LineTo(stroker, &stroker->subpath_start); if (error) goto Exit; } /* process the corner */ stroker->angle_out = stroker->subpath_angle; turn = PVG_FT_Angle_Diff(stroker->angle_in, stroker->angle_out); /* no specific corner processing is required if the turn is 0 */ if (turn != 0) { /* when we turn to the right, the inside side is 0 */ inside_side = 0; /* otherwise, the inside side is 1 */ if (turn < 0) inside_side = 1; error = ft_stroker_inside(stroker, inside_side, stroker->subpath_line_length); if (error) goto Exit; /* process the outside side */ error = ft_stroker_outside(stroker, 1 - inside_side, stroker->subpath_line_length); if (error) goto Exit; } /* then end our two subpaths */ ft_stroke_border_close(stroker->borders + 0, FALSE); ft_stroke_border_close(stroker->borders + 1, TRUE); } Exit: return error; } /* documentation is in ftstroke.h */ PVG_FT_Error PVG_FT_Stroker_GetBorderCounts(PVG_FT_Stroker stroker, PVG_FT_StrokerBorder border, PVG_FT_UInt* anum_points, PVG_FT_UInt* anum_contours) { PVG_FT_UInt num_points = 0, num_contours = 0; PVG_FT_Error error; if (!stroker || border > 1) { error = -1; // PVG_FT_THROW( Invalid_Argument ); goto Exit; } error = ft_stroke_border_get_counts(stroker->borders + border, &num_points, &num_contours); Exit: if (anum_points) *anum_points = num_points; if (anum_contours) *anum_contours = num_contours; return error; } /* documentation is in ftstroke.h */ PVG_FT_Error PVG_FT_Stroker_GetCounts(PVG_FT_Stroker stroker, PVG_FT_UInt* anum_points, PVG_FT_UInt* anum_contours) { PVG_FT_UInt count1, count2, num_points = 0; PVG_FT_UInt count3, count4, num_contours = 0; PVG_FT_Error error; error = ft_stroke_border_get_counts(stroker->borders + 0, &count1, &count2); if (error) goto Exit; error = ft_stroke_border_get_counts(stroker->borders + 1, &count3, &count4); if (error) goto Exit; num_points = count1 + count3; num_contours = count2 + count4; Exit: *anum_points = num_points; *anum_contours = num_contours; return error; } /* documentation is in ftstroke.h */ void PVG_FT_Stroker_ExportBorder(PVG_FT_Stroker stroker, PVG_FT_StrokerBorder border, PVG_FT_Outline* outline) { if (border == PVG_FT_STROKER_BORDER_LEFT || border == PVG_FT_STROKER_BORDER_RIGHT) { PVG_FT_StrokeBorder sborder = &stroker->borders[border]; if (sborder->valid) ft_stroke_border_export(sborder, outline); } } /* documentation is in ftstroke.h */ void PVG_FT_Stroker_Export(PVG_FT_Stroker stroker, PVG_FT_Outline* outline) { PVG_FT_Stroker_ExportBorder(stroker, PVG_FT_STROKER_BORDER_LEFT, outline); PVG_FT_Stroker_ExportBorder(stroker, PVG_FT_STROKER_BORDER_RIGHT, outline); } /* documentation is in ftstroke.h */ /* * The following is very similar to PVG_FT_Outline_Decompose, except * that we do support opened paths, and do not scale the outline. */ PVG_FT_Error PVG_FT_Stroker_ParseOutline(PVG_FT_Stroker stroker, const PVG_FT_Outline* outline) { PVG_FT_Vector v_last; PVG_FT_Vector v_control; PVG_FT_Vector v_start; PVG_FT_Vector* point; PVG_FT_Vector* limit; char* tags; PVG_FT_Error error; PVG_FT_Int n; /* index of contour in outline */ PVG_FT_UInt first; /* index of first point in contour */ PVG_FT_Int tag; /* current point's state */ if (!outline || !stroker) return -1; // PVG_FT_THROW( Invalid_Argument ); PVG_FT_Stroker_Rewind(stroker); first = 0; for (n = 0; n < outline->n_contours; n++) { PVG_FT_UInt last; /* index of last point in contour */ last = outline->contours[n]; limit = outline->points + last; /* skip empty points; we don't stroke these */ if (last <= first) { first = last + 1; continue; } v_start = outline->points[first]; v_last = outline->points[last]; v_control = v_start; point = outline->points + first; tags = outline->tags + first; tag = PVG_FT_CURVE_TAG(tags[0]); /* A contour cannot start with a cubic control point! */ if (tag == PVG_FT_CURVE_TAG_CUBIC) goto Invalid_Outline; /* check first point to determine origin */ if (tag == PVG_FT_CURVE_TAG_CONIC) { /* First point is conic control. Yes, this happens. */ if (PVG_FT_CURVE_TAG(outline->tags[last]) == PVG_FT_CURVE_TAG_ON) { /* start at last point if it is on the curve */ v_start = v_last; limit--; } else { /* if both first and last points are conic, */ /* start at their middle */ v_start.x = (v_start.x + v_last.x) / 2; v_start.y = (v_start.y + v_last.y) / 2; } point--; tags--; } error = PVG_FT_Stroker_BeginSubPath(stroker, &v_start, outline->contours_flag[n]); if (error) goto Exit; while (point < limit) { point++; tags++; tag = PVG_FT_CURVE_TAG(tags[0]); switch (tag) { case PVG_FT_CURVE_TAG_ON: /* emit a single line_to */ { PVG_FT_Vector vec; vec.x = point->x; vec.y = point->y; error = PVG_FT_Stroker_LineTo(stroker, &vec); if (error) goto Exit; continue; } case PVG_FT_CURVE_TAG_CONIC: /* consume conic arcs */ v_control.x = point->x; v_control.y = point->y; Do_Conic: if (point < limit) { PVG_FT_Vector vec; PVG_FT_Vector v_middle; point++; tags++; tag = PVG_FT_CURVE_TAG(tags[0]); vec = point[0]; if (tag == PVG_FT_CURVE_TAG_ON) { error = PVG_FT_Stroker_ConicTo(stroker, &v_control, &vec); if (error) goto Exit; continue; } if (tag != PVG_FT_CURVE_TAG_CONIC) goto Invalid_Outline; v_middle.x = (v_control.x + vec.x) / 2; v_middle.y = (v_control.y + vec.y) / 2; error = PVG_FT_Stroker_ConicTo(stroker, &v_control, &v_middle); if (error) goto Exit; v_control = vec; goto Do_Conic; } error = PVG_FT_Stroker_ConicTo(stroker, &v_control, &v_start); goto Close; default: /* PVG_FT_CURVE_TAG_CUBIC */ { PVG_FT_Vector vec1, vec2; if (point + 1 > limit || PVG_FT_CURVE_TAG(tags[1]) != PVG_FT_CURVE_TAG_CUBIC) goto Invalid_Outline; point += 2; tags += 2; vec1 = point[-2]; vec2 = point[-1]; if (point <= limit) { PVG_FT_Vector vec; vec = point[0]; error = PVG_FT_Stroker_CubicTo(stroker, &vec1, &vec2, &vec); if (error) goto Exit; continue; } error = PVG_FT_Stroker_CubicTo(stroker, &vec1, &vec2, &v_start); goto Close; } } } Close: if (error) goto Exit; if (stroker->first_point) { stroker->subpath_open = TRUE; error = ft_stroker_subpath_start(stroker, 0, 0); if (error) goto Exit; } error = PVG_FT_Stroker_EndSubPath(stroker); if (error) goto Exit; first = last + 1; } return 0; Exit: return error; Invalid_Outline: return -2; // PVG_FT_THROW( Invalid_Outline ); } /* END */ sammycage-plutovg-5695a71/source/plutovg-ft-stroker.h000066400000000000000000000225561510714322500227220ustar00rootroot00000000000000/***************************************************************************/ /* */ /* ftstroke.h */ /* */ /* FreeType path stroker (specification). */ /* */ /* Copyright 2002-2006, 2008, 2009, 2011-2012 by */ /* David Turner, Robert Wilhelm, and Werner Lemberg. */ /* */ /* This file is part of the FreeType project, and may only be used, */ /* modified, and distributed under the terms of the FreeType project */ /* license, FTL.TXT. By continuing to use, modify, or distribute */ /* this file you indicate that you have read the license and */ /* understand and accept it fully. */ /* */ /***************************************************************************/ #ifndef PLUTOVG_FT_STROKER_H #define PLUTOVG_FT_STROKER_H #include "plutovg-ft-raster.h" /************************************************************** * * @type: * PVG_FT_Stroker * * @description: * Opaque handler to a path stroker object. */ typedef struct PVG_FT_StrokerRec_* PVG_FT_Stroker; /************************************************************** * * @enum: * PVG_FT_Stroker_LineJoin * * @description: * These values determine how two joining lines are rendered * in a stroker. * * @values: * PVG_FT_STROKER_LINEJOIN_ROUND :: * Used to render rounded line joins. Circular arcs are used * to join two lines smoothly. * * PVG_FT_STROKER_LINEJOIN_BEVEL :: * Used to render beveled line joins. The outer corner of * the joined lines is filled by enclosing the triangular * region of the corner with a straight line between the * outer corners of each stroke. * * PVG_FT_STROKER_LINEJOIN_MITER_FIXED :: * Used to render mitered line joins, with fixed bevels if the * miter limit is exceeded. The outer edges of the strokes * for the two segments are extended until they meet at an * angle. If the segments meet at too sharp an angle (such * that the miter would extend from the intersection of the * segments a distance greater than the product of the miter * limit value and the border radius), then a bevel join (see * above) is used instead. This prevents long spikes being * created. PVG_FT_STROKER_LINEJOIN_MITER_FIXED generates a miter * line join as used in PostScript and PDF. * * PVG_FT_STROKER_LINEJOIN_MITER_VARIABLE :: * PVG_FT_STROKER_LINEJOIN_MITER :: * Used to render mitered line joins, with variable bevels if * the miter limit is exceeded. The intersection of the * strokes is clipped at a line perpendicular to the bisector * of the angle between the strokes, at the distance from the * intersection of the segments equal to the product of the * miter limit value and the border radius. This prevents * long spikes being created. * PVG_FT_STROKER_LINEJOIN_MITER_VARIABLE generates a mitered line * join as used in XPS. PVG_FT_STROKER_LINEJOIN_MITER is an alias * for PVG_FT_STROKER_LINEJOIN_MITER_VARIABLE, retained for * backwards compatibility. */ typedef enum PVG_FT_Stroker_LineJoin_ { PVG_FT_STROKER_LINEJOIN_ROUND = 0, PVG_FT_STROKER_LINEJOIN_BEVEL = 1, PVG_FT_STROKER_LINEJOIN_MITER_VARIABLE = 2, PVG_FT_STROKER_LINEJOIN_MITER = PVG_FT_STROKER_LINEJOIN_MITER_VARIABLE, PVG_FT_STROKER_LINEJOIN_MITER_FIXED = 3 } PVG_FT_Stroker_LineJoin; /************************************************************** * * @enum: * PVG_FT_Stroker_LineCap * * @description: * These values determine how the end of opened sub-paths are * rendered in a stroke. * * @values: * PVG_FT_STROKER_LINECAP_BUTT :: * The end of lines is rendered as a full stop on the last * point itself. * * PVG_FT_STROKER_LINECAP_ROUND :: * The end of lines is rendered as a half-circle around the * last point. * * PVG_FT_STROKER_LINECAP_SQUARE :: * The end of lines is rendered as a square around the * last point. */ typedef enum PVG_FT_Stroker_LineCap_ { PVG_FT_STROKER_LINECAP_BUTT = 0, PVG_FT_STROKER_LINECAP_ROUND, PVG_FT_STROKER_LINECAP_SQUARE } PVG_FT_Stroker_LineCap; /************************************************************** * * @enum: * PVG_FT_StrokerBorder * * @description: * These values are used to select a given stroke border * in @PVG_FT_Stroker_GetBorderCounts and @PVG_FT_Stroker_ExportBorder. * * @values: * PVG_FT_STROKER_BORDER_LEFT :: * Select the left border, relative to the drawing direction. * * PVG_FT_STROKER_BORDER_RIGHT :: * Select the right border, relative to the drawing direction. * * @note: * Applications are generally interested in the `inside' and `outside' * borders. However, there is no direct mapping between these and the * `left' and `right' ones, since this really depends on the glyph's * drawing orientation, which varies between font formats. * * You can however use @PVG_FT_Outline_GetInsideBorder and * @PVG_FT_Outline_GetOutsideBorder to get these. */ typedef enum PVG_FT_StrokerBorder_ { PVG_FT_STROKER_BORDER_LEFT = 0, PVG_FT_STROKER_BORDER_RIGHT } PVG_FT_StrokerBorder; /************************************************************** * * @function: * PVG_FT_Stroker_New * * @description: * Create a new stroker object. * * @input: * library :: * FreeType library handle. * * @output: * astroker :: * A new stroker object handle. NULL in case of error. * * @return: * FreeType error code. 0~means success. */ PVG_FT_Error PVG_FT_Stroker_New( PVG_FT_Stroker *astroker ); /************************************************************** * * @function: * PVG_FT_Stroker_Set * * @description: * Reset a stroker object's attributes. * * @input: * stroker :: * The target stroker handle. * * radius :: * The border radius. * * line_cap :: * The line cap style. * * line_join :: * The line join style. * * miter_limit :: * The miter limit for the PVG_FT_STROKER_LINEJOIN_MITER_FIXED and * PVG_FT_STROKER_LINEJOIN_MITER_VARIABLE line join styles, * expressed as 16.16 fixed-point value. * * @note: * The radius is expressed in the same units as the outline * coordinates. */ void PVG_FT_Stroker_Set( PVG_FT_Stroker stroker, PVG_FT_Fixed radius, PVG_FT_Stroker_LineCap line_cap, PVG_FT_Stroker_LineJoin line_join, PVG_FT_Fixed miter_limit ); /************************************************************** * * @function: * PVG_FT_Stroker_ParseOutline * * @description: * A convenience function used to parse a whole outline with * the stroker. The resulting outline(s) can be retrieved * later by functions like @PVG_FT_Stroker_GetCounts and @PVG_FT_Stroker_Export. * * @input: * stroker :: * The target stroker handle. * * outline :: * The source outline. * * * @return: * FreeType error code. 0~means success. * * @note: * If `opened' is~0 (the default), the outline is treated as a closed * path, and the stroker generates two distinct `border' outlines. * * * This function calls @PVG_FT_Stroker_Rewind automatically. */ PVG_FT_Error PVG_FT_Stroker_ParseOutline( PVG_FT_Stroker stroker, const PVG_FT_Outline* outline); /************************************************************** * * @function: * PVG_FT_Stroker_GetCounts * * @description: * Call this function once you have finished parsing your paths * with the stroker. It returns the number of points and * contours necessary to export all points/borders from the stroked * outline/path. * * @input: * stroker :: * The target stroker handle. * * @output: * anum_points :: * The number of points. * * anum_contours :: * The number of contours. * * @return: * FreeType error code. 0~means success. */ PVG_FT_Error PVG_FT_Stroker_GetCounts( PVG_FT_Stroker stroker, PVG_FT_UInt *anum_points, PVG_FT_UInt *anum_contours ); /************************************************************** * * @function: * PVG_FT_Stroker_Export * * @description: * Call this function after @PVG_FT_Stroker_GetBorderCounts to * export all borders to your own @PVG_FT_Outline structure. * * Note that this function appends the border points and * contours to your outline, but does not try to resize its * arrays. * * @input: * stroker :: * The target stroker handle. * * outline :: * The target outline handle. */ void PVG_FT_Stroker_Export( PVG_FT_Stroker stroker, PVG_FT_Outline* outline ); /************************************************************** * * @function: * PVG_FT_Stroker_Done * * @description: * Destroy a stroker object. * * @input: * stroker :: * A stroker handle. Can be NULL. */ void PVG_FT_Stroker_Done( PVG_FT_Stroker stroker ); #endif // PLUTOVG_FT_STROKER_H sammycage-plutovg-5695a71/source/plutovg-ft-types.h000066400000000000000000000214151510714322500223660ustar00rootroot00000000000000/**************************************************************************** * * fttypes.h * * FreeType simple types definitions (specification only). * * Copyright (C) 1996-2020 by * David Turner, Robert Wilhelm, and Werner Lemberg. * * This file is part of the FreeType project, and may only be used, * modified, and distributed under the terms of the FreeType project * license, FTL.TXT. By continuing to use, modify, or distribute * this file you indicate that you have read the license and * understand and accept it fully. * */ #ifndef PLUTOVG_FT_TYPES_H #define PLUTOVG_FT_TYPES_H /*************************************************************************/ /* */ /* */ /* PVG_FT_Fixed */ /* */ /* */ /* This type is used to store 16.16 fixed-point values, like scaling */ /* values or matrix coefficients. */ /* */ typedef signed long PVG_FT_Fixed; /*************************************************************************/ /* */ /* */ /* PVG_FT_Int */ /* */ /* */ /* A typedef for the int type. */ /* */ typedef signed int PVG_FT_Int; /*************************************************************************/ /* */ /* */ /* PVG_FT_UInt */ /* */ /* */ /* A typedef for the unsigned int type. */ /* */ typedef unsigned int PVG_FT_UInt; /*************************************************************************/ /* */ /* */ /* PVG_FT_Long */ /* */ /* */ /* A typedef for signed long. */ /* */ typedef signed long PVG_FT_Long; /*************************************************************************/ /* */ /* */ /* PVG_FT_ULong */ /* */ /* */ /* A typedef for unsigned long. */ /* */ typedef unsigned long PVG_FT_ULong; /*************************************************************************/ /* */ /* */ /* PVG_FT_Short */ /* */ /* */ /* A typedef for signed short. */ /* */ typedef signed short PVG_FT_Short; /*************************************************************************/ /* */ /* */ /* PVG_FT_Byte */ /* */ /* */ /* A simple typedef for the _unsigned_ char type. */ /* */ typedef unsigned char PVG_FT_Byte; /*************************************************************************/ /* */ /* */ /* PVG_FT_Bool */ /* */ /* */ /* A typedef of unsigned char, used for simple booleans. As usual, */ /* values 1 and~0 represent true and false, respectively. */ /* */ typedef unsigned char PVG_FT_Bool; /*************************************************************************/ /* */ /* */ /* PVG_FT_Error */ /* */ /* */ /* The FreeType error code type. A value of~0 is always interpreted */ /* as a successful operation. */ /* */ typedef int PVG_FT_Error; /*************************************************************************/ /* */ /* */ /* PVG_FT_Pos */ /* */ /* */ /* The type PVG_FT_Pos is used to store vectorial coordinates. Depending */ /* on the context, these can represent distances in integer font */ /* units, or 16.16, or 26.6 fixed-point pixel coordinates. */ /* */ typedef signed long PVG_FT_Pos; /*************************************************************************/ /* */ /* */ /* PVG_FT_Vector */ /* */ /* */ /* A simple structure used to store a 2D vector; coordinates are of */ /* the PVG_FT_Pos type. */ /* */ /* */ /* x :: The horizontal coordinate. */ /* y :: The vertical coordinate. */ /* */ typedef struct PVG_FT_Vector_ { PVG_FT_Pos x; PVG_FT_Pos y; } PVG_FT_Vector; typedef long long int PVG_FT_Int64; typedef unsigned long long int PVG_FT_UInt64; typedef signed int PVG_FT_Int32; typedef unsigned int PVG_FT_UInt32; #define PVG_FT_BOOL( x ) ( (PVG_FT_Bool)( x ) ) #ifndef TRUE #define TRUE 1 #endif #ifndef FALSE #define FALSE 0 #endif #define PVG_FT_BEGIN_STMNT do { #define PVG_FT_END_STMNT } while (0) #endif // PLUTOVG_FT_TYPES_H sammycage-plutovg-5695a71/source/plutovg-matrix.c000066400000000000000000000166301510714322500221150ustar00rootroot00000000000000#include "plutovg.h" #include "plutovg-utils.h" void plutovg_matrix_init(plutovg_matrix_t* matrix, float a, float b, float c, float d, float e, float f) { matrix->a = a; matrix->b = b; matrix->c = c; matrix->d = d; matrix->e = e; matrix->f = f; } void plutovg_matrix_init_identity(plutovg_matrix_t* matrix) { plutovg_matrix_init(matrix, 1, 0, 0, 1, 0, 0); } void plutovg_matrix_init_translate(plutovg_matrix_t* matrix, float tx, float ty) { plutovg_matrix_init(matrix, 1, 0, 0, 1, tx, ty); } void plutovg_matrix_init_scale(plutovg_matrix_t* matrix, float sx, float sy) { plutovg_matrix_init(matrix, sx, 0, 0, sy, 0, 0); } void plutovg_matrix_init_rotate(plutovg_matrix_t* matrix, float angle) { float c = cosf(angle); float s = sinf(angle); plutovg_matrix_init(matrix, c, s, -s, c, 0, 0); } void plutovg_matrix_init_shear(plutovg_matrix_t* matrix, float shx, float shy) { plutovg_matrix_init(matrix, 1, tanf(shy), tanf(shx), 1, 0, 0); } void plutovg_matrix_translate(plutovg_matrix_t* matrix, float tx, float ty) { plutovg_matrix_t m; plutovg_matrix_init_translate(&m, tx, ty); plutovg_matrix_multiply(matrix, &m, matrix); } void plutovg_matrix_scale(plutovg_matrix_t* matrix, float sx, float sy) { plutovg_matrix_t m; plutovg_matrix_init_scale(&m, sx, sy); plutovg_matrix_multiply(matrix, &m, matrix); } void plutovg_matrix_rotate(plutovg_matrix_t* matrix, float angle) { plutovg_matrix_t m; plutovg_matrix_init_rotate(&m, angle); plutovg_matrix_multiply(matrix, &m, matrix); } void plutovg_matrix_shear(plutovg_matrix_t* matrix, float shx, float shy) { plutovg_matrix_t m; plutovg_matrix_init_shear(&m, shx, shy); plutovg_matrix_multiply(matrix, &m, matrix); } void plutovg_matrix_multiply(plutovg_matrix_t* matrix, const plutovg_matrix_t* left, const plutovg_matrix_t* right) { float a = left->a * right->a + left->b * right->c; float b = left->a * right->b + left->b * right->d; float c = left->c * right->a + left->d * right->c; float d = left->c * right->b + left->d * right->d; float e = left->e * right->a + left->f * right->c + right->e; float f = left->e * right->b + left->f * right->d + right->f; plutovg_matrix_init(matrix, a, b, c, d, e, f); } bool plutovg_matrix_invert(const plutovg_matrix_t* matrix, plutovg_matrix_t* inverse) { float det = (matrix->a * matrix->d - matrix->b * matrix->c); if(det == 0.f) return false; if(inverse) { float inv_det = 1.f / det; float a = matrix->a * inv_det; float b = matrix->b * inv_det; float c = matrix->c * inv_det; float d = matrix->d * inv_det; float e = (matrix->c * matrix->f - matrix->d * matrix->e) * inv_det; float f = (matrix->b * matrix->e - matrix->a * matrix->f) * inv_det; plutovg_matrix_init(inverse, d, -b, -c, a, e, f); } return true; } void plutovg_matrix_map(const plutovg_matrix_t* matrix, float x, float y, float* xx, float* yy) { *xx = x * matrix->a + y * matrix->c + matrix->e; *yy = x * matrix->b + y * matrix->d + matrix->f; } void plutovg_matrix_map_point(const plutovg_matrix_t* matrix, const plutovg_point_t* src, plutovg_point_t* dst) { plutovg_matrix_map(matrix, src->x, src->y, &dst->x, &dst->y); } void plutovg_matrix_map_points(const plutovg_matrix_t* matrix, const plutovg_point_t* src, plutovg_point_t* dst, int count) { for(int i = 0; i < count; ++i) { plutovg_matrix_map_point(matrix, &src[i], &dst[i]); } } void plutovg_matrix_map_rect(const plutovg_matrix_t* matrix, const plutovg_rect_t* src, plutovg_rect_t* dst) { plutovg_point_t p[4]; p[0].x = src->x; p[0].y = src->y; p[1].x = src->x + src->w; p[1].y = src->y; p[2].x = src->x + src->w; p[2].y = src->y + src->h; p[3].x = src->x; p[3].y = src->y + src->h; plutovg_matrix_map_points(matrix, p, p, 4); float l = p[0].x; float t = p[0].y; float r = p[0].x; float b = p[0].y; for(int i = 1; i < 4; i++) { if(p[i].x < l) l = p[i].x; if(p[i].x > r) r = p[i].x; if(p[i].y < t) t = p[i].y; if(p[i].y > b) b = p[i].y; } dst->x = l; dst->y = t; dst->w = r - l; dst->h = b - t; } static int parse_matrix_parameters(const char** begin, const char* end, float values[6], int required, int optional) { if(!plutovg_skip_ws_and_delim(begin, end, '(')) return 0; int count = 0; int max_count = required + optional; bool has_trailing_comma = false; for(; count < max_count; ++count) { if(!plutovg_parse_number(begin, end, values + count)) break; plutovg_skip_ws_or_comma(begin, end, &has_trailing_comma); } if(!has_trailing_comma && (count == required || count == max_count) && plutovg_skip_delim(begin, end, ')')) { return count; } return 0; } bool plutovg_matrix_parse(plutovg_matrix_t* matrix, const char* data, int length) { float values[6]; plutovg_matrix_init_identity(matrix); if(length == -1) length = strlen(data); const char* it = data; const char* end = it + length; bool has_trailing_comma = false; plutovg_skip_ws(&it, end); while(it < end) { if(plutovg_skip_string(&it, end, "matrix")) { int count = parse_matrix_parameters(&it, end, values, 6, 0); if(count == 0) return false; plutovg_matrix_t m = { values[0], values[1], values[2], values[3], values[4], values[5] }; plutovg_matrix_multiply(matrix, &m, matrix); } else if(plutovg_skip_string(&it, end, "translate")) { int count = parse_matrix_parameters(&it, end, values, 1, 1); if(count == 0) return false; if(count == 1) { plutovg_matrix_translate(matrix, values[0], 0); } else { plutovg_matrix_translate(matrix, values[0], values[1]); } } else if(plutovg_skip_string(&it, end, "scale")) { int count = parse_matrix_parameters(&it, end, values, 1, 1); if(count == 0) return false; if(count == 1) { plutovg_matrix_scale(matrix, values[0], values[0]); } else { plutovg_matrix_scale(matrix, values[0], values[1]); } } else if(plutovg_skip_string(&it, end, "rotate")) { int count = parse_matrix_parameters(&it, end, values, 1, 2); if(count == 0) return false; if(count == 3) plutovg_matrix_translate(matrix, values[1], values[2]); plutovg_matrix_rotate(matrix, PLUTOVG_DEG2RAD(values[0])); if(count == 3) { plutovg_matrix_translate(matrix, -values[1], -values[2]); } } else if(plutovg_skip_string(&it, end, "skewX")) { int count = parse_matrix_parameters(&it, end, values, 1, 0); if(count == 0) return false; plutovg_matrix_shear(matrix, PLUTOVG_DEG2RAD(values[0]), 0); } else if(plutovg_skip_string(&it, end, "skewY")) { int count = parse_matrix_parameters(&it, end, values, 1, 0); if(count == 0) return false; plutovg_matrix_shear(matrix, 0, PLUTOVG_DEG2RAD(values[0])); } else { return false; } plutovg_skip_ws_or_comma(&it, end, &has_trailing_comma); } return !has_trailing_comma; } sammycage-plutovg-5695a71/source/plutovg-paint.c000066400000000000000000000424451510714322500217270ustar00rootroot00000000000000#include "plutovg-private.h" #include "plutovg-utils.h" #include void plutovg_color_init_rgb(plutovg_color_t* color, float r, float g, float b) { plutovg_color_init_rgba(color, r, g, b, 1.f); } void plutovg_color_init_rgba(plutovg_color_t* color, float r, float g, float b, float a) { color->r = plutovg_clamp(r, 0.f, 1.f); color->g = plutovg_clamp(g, 0.f, 1.f); color->b = plutovg_clamp(b, 0.f, 1.f); color->a = plutovg_clamp(a, 0.f, 1.f); } void plutovg_color_init_rgb8(plutovg_color_t* color, int r, int g, int b) { plutovg_color_init_rgba8(color, r, g, b, 255); } void plutovg_color_init_rgba8(plutovg_color_t* color, int r, int g, int b, int a) { plutovg_color_init_rgba(color, r / 255.f, g / 255.f, b / 255.f, a / 255.f); } void plutovg_color_init_rgba32(plutovg_color_t* color, unsigned int value) { uint8_t r = (value >> 24) & 0xFF; uint8_t g = (value >> 16) & 0xFF; uint8_t b = (value >> 8) & 0xFF; uint8_t a = (value >> 0) & 0xFF; plutovg_color_init_rgba8(color, r, g, b, a); } void plutovg_color_init_argb32(plutovg_color_t* color, unsigned int value) { uint8_t a = (value >> 24) & 0xFF; uint8_t r = (value >> 16) & 0xFF; uint8_t g = (value >> 8) & 0xFF; uint8_t b = (value >> 0) & 0xFF; plutovg_color_init_rgba8(color, r, g, b, a); } void plutovg_color_init_hsl(plutovg_color_t* color, float h, float s, float l) { plutovg_color_init_hsla(color, h, s, l, 1.f); } static inline float hsl_component(float h, float s, float l, float n) { const float k = fmodf(n + h / 30.f, 12.f); const float a = s * plutovg_min(l, 1.f - l); return l - a * plutovg_max(-1.f, plutovg_min(1.f, plutovg_min(k - 3.f, 9.f - k))); } void plutovg_color_init_hsla(plutovg_color_t* color, float h, float s, float l, float a) { h = fmodf(h, 360.f); if(h < 0.f) { h += 360.f; } float r = hsl_component(h, s, l, 0); float g = hsl_component(h, s, l, 8); float b = hsl_component(h, s, l, 4); plutovg_color_init_rgba(color, r, g, b, a); } unsigned int plutovg_color_to_rgba32(const plutovg_color_t* color) { uint32_t r = lroundf(color->r * 255); uint32_t g = lroundf(color->g * 255); uint32_t b = lroundf(color->b * 255); uint32_t a = lroundf(color->a * 255); return (r << 24) | (g << 16) | (b << 8) | (a); } unsigned int plutovg_color_to_argb32(const plutovg_color_t* color) { uint32_t a = lroundf(color->a * 255); uint32_t r = lroundf(color->r * 255); uint32_t g = lroundf(color->g * 255); uint32_t b = lroundf(color->b * 255); return (a << 24) | (r << 16) | (g << 8) | (b); } static inline uint8_t hex_digit(uint8_t c) { if(c >= '0' && c <= '9') return c - '0'; if(c >= 'a' && c <= 'f') return 10 + c - 'a'; return 10 + c - 'A'; } static inline uint8_t hex_byte(uint8_t c1, uint8_t c2) { uint8_t h1 = hex_digit(c1); uint8_t h2 = hex_digit(c2); return (h1 << 4) | h2; } #define MAX_NAME 20 typedef struct { const char* name; uint32_t value; } color_entry_t; static int color_entry_compare(const void* a, const void* b) { const char* name = a; const color_entry_t* entry = b; return strcmp(name, entry->name); } static bool parse_rgb_component(const char** begin, const char* end, float* component) { float value = 0; if(!plutovg_parse_number(begin, end, &value)) return false; if(plutovg_skip_delim(begin, end, '%')) value *= 2.55f; *component = plutovg_clamp(value, 0.f, 255.f) / 255.f; return true; } static bool parse_alpha_component(const char** begin, const char* end, float* component) { float value = 0; if(!plutovg_parse_number(begin, end, &value)) return false; if(plutovg_skip_delim(begin, end, '%')) value /= 100.f; *component = plutovg_clamp(value, 0.f, 1.f); return true; } int plutovg_color_parse(plutovg_color_t* color, const char* data, int length) { if(length == -1) length = strlen(data); const char* it = data; const char* end = it + length; plutovg_skip_ws(&it, end); if(plutovg_skip_delim(&it, end, '#')) { int r, g, b, a = 255; const char* begin = it; while(it < end && isxdigit(*it)) ++it; int count = it - begin; if(count == 3 || count == 4) { r = hex_byte(begin[0], begin[0]); g = hex_byte(begin[1], begin[1]); b = hex_byte(begin[2], begin[2]); if(count == 4) { a = hex_byte(begin[3], begin[3]); } } else if(count == 6 || count == 8) { r = hex_byte(begin[0], begin[1]); g = hex_byte(begin[2], begin[3]); b = hex_byte(begin[4], begin[5]); if(count == 8) { a = hex_byte(begin[6], begin[7]); } } else { return 0; } plutovg_color_init_rgba8(color, r, g, b, a); } else { int name_length = 0; char name[MAX_NAME + 1]; while(it < end && name_length < MAX_NAME && isalpha(*it)) name[name_length++] = tolower(*it++); name[name_length] = '\0'; if(strcmp(name, "transparent") == 0) { plutovg_color_init_rgba(color, 0, 0, 0, 0); } else if(strcmp(name, "rgb") == 0 || strcmp(name, "rgba") == 0) { if(!plutovg_skip_ws_and_delim(&it, end, '(')) return 0; float r, g, b, a = 1.f; if(!parse_rgb_component(&it, end, &r) || !plutovg_skip_ws_and_comma(&it, end) || !parse_rgb_component(&it, end, &g) || !plutovg_skip_ws_and_comma(&it, end) || !parse_rgb_component(&it, end, &b)) { return 0; } if(plutovg_skip_ws_and_comma(&it, end) && !parse_alpha_component(&it, end, &a)) { return 0; } plutovg_skip_ws(&it, end); if(!plutovg_skip_delim(&it, end, ')')) return 0; plutovg_color_init_rgba(color, r, g, b, a); } else if(strcmp(name, "hsl") == 0 || strcmp(name, "hsla") == 0) { if(!plutovg_skip_ws_and_delim(&it, end, '(')) return 0; float h, s, l, a = 1.f; if(!plutovg_parse_number(&it, end, &h) || !plutovg_skip_ws_and_comma(&it, end) || !parse_alpha_component(&it, end, &s) || !plutovg_skip_ws_and_comma(&it, end) || !parse_alpha_component(&it, end, &l)) { return 0; } if(plutovg_skip_ws_and_comma(&it, end) && !parse_alpha_component(&it, end, &a)) { return 0; } plutovg_skip_ws(&it, end); if(!plutovg_skip_delim(&it, end, ')')) return 0; plutovg_color_init_hsla(color, h, s, l, a); } else { static const color_entry_t colormap[] = { {"aliceblue", 0xF0F8FF}, {"antiquewhite", 0xFAEBD7}, {"aqua", 0x00FFFF}, {"aquamarine", 0x7FFFD4}, {"azure", 0xF0FFFF}, {"beige", 0xF5F5DC}, {"bisque", 0xFFE4C4}, {"black", 0x000000}, {"blanchedalmond", 0xFFEBCD}, {"blue", 0x0000FF}, {"blueviolet", 0x8A2BE2}, {"brown", 0xA52A2A}, {"burlywood", 0xDEB887}, {"cadetblue", 0x5F9EA0}, {"chartreuse", 0x7FFF00}, {"chocolate", 0xD2691E}, {"coral", 0xFF7F50}, {"cornflowerblue", 0x6495ED}, {"cornsilk", 0xFFF8DC}, {"crimson", 0xDC143C}, {"cyan", 0x00FFFF}, {"darkblue", 0x00008B}, {"darkcyan", 0x008B8B}, {"darkgoldenrod", 0xB8860B}, {"darkgray", 0xA9A9A9}, {"darkgreen", 0x006400}, {"darkgrey", 0xA9A9A9}, {"darkkhaki", 0xBDB76B}, {"darkmagenta", 0x8B008B}, {"darkolivegreen", 0x556B2F}, {"darkorange", 0xFF8C00}, {"darkorchid", 0x9932CC}, {"darkred", 0x8B0000}, {"darksalmon", 0xE9967A}, {"darkseagreen", 0x8FBC8F}, {"darkslateblue", 0x483D8B}, {"darkslategray", 0x2F4F4F}, {"darkslategrey", 0x2F4F4F}, {"darkturquoise", 0x00CED1}, {"darkviolet", 0x9400D3}, {"deeppink", 0xFF1493}, {"deepskyblue", 0x00BFFF}, {"dimgray", 0x696969}, {"dimgrey", 0x696969}, {"dodgerblue", 0x1E90FF}, {"firebrick", 0xB22222}, {"floralwhite", 0xFFFAF0}, {"forestgreen", 0x228B22}, {"fuchsia", 0xFF00FF}, {"gainsboro", 0xDCDCDC}, {"ghostwhite", 0xF8F8FF}, {"gold", 0xFFD700}, {"goldenrod", 0xDAA520}, {"gray", 0x808080}, {"green", 0x008000}, {"greenyellow", 0xADFF2F}, {"grey", 0x808080}, {"honeydew", 0xF0FFF0}, {"hotpink", 0xFF69B4}, {"indianred", 0xCD5C5C}, {"indigo", 0x4B0082}, {"ivory", 0xFFFFF0}, {"khaki", 0xF0E68C}, {"lavender", 0xE6E6FA}, {"lavenderblush", 0xFFF0F5}, {"lawngreen", 0x7CFC00}, {"lemonchiffon", 0xFFFACD}, {"lightblue", 0xADD8E6}, {"lightcoral", 0xF08080}, {"lightcyan", 0xE0FFFF}, {"lightgoldenrodyellow", 0xFAFAD2}, {"lightgray", 0xD3D3D3}, {"lightgreen", 0x90EE90}, {"lightgrey", 0xD3D3D3}, {"lightpink", 0xFFB6C1}, {"lightsalmon", 0xFFA07A}, {"lightseagreen", 0x20B2AA}, {"lightskyblue", 0x87CEFA}, {"lightslategray", 0x778899}, {"lightslategrey", 0x778899}, {"lightsteelblue", 0xB0C4DE}, {"lightyellow", 0xFFFFE0}, {"lime", 0x00FF00}, {"limegreen", 0x32CD32}, {"linen", 0xFAF0E6}, {"magenta", 0xFF00FF}, {"maroon", 0x800000}, {"mediumaquamarine", 0x66CDAA}, {"mediumblue", 0x0000CD}, {"mediumorchid", 0xBA55D3}, {"mediumpurple", 0x9370DB}, {"mediumseagreen", 0x3CB371}, {"mediumslateblue", 0x7B68EE}, {"mediumspringgreen", 0x00FA9A}, {"mediumturquoise", 0x48D1CC}, {"mediumvioletred", 0xC71585}, {"midnightblue", 0x191970}, {"mintcream", 0xF5FFFA}, {"mistyrose", 0xFFE4E1}, {"moccasin", 0xFFE4B5}, {"navajowhite", 0xFFDEAD}, {"navy", 0x000080}, {"oldlace", 0xFDF5E6}, {"olive", 0x808000}, {"olivedrab", 0x6B8E23}, {"orange", 0xFFA500}, {"orangered", 0xFF4500}, {"orchid", 0xDA70D6}, {"palegoldenrod", 0xEEE8AA}, {"palegreen", 0x98FB98}, {"paleturquoise", 0xAFEEEE}, {"palevioletred", 0xDB7093}, {"papayawhip", 0xFFEFD5}, {"peachpuff", 0xFFDAB9}, {"peru", 0xCD853F}, {"pink", 0xFFC0CB}, {"plum", 0xDDA0DD}, {"powderblue", 0xB0E0E6}, {"purple", 0x800080}, {"rebeccapurple", 0x663399}, {"red", 0xFF0000}, {"rosybrown", 0xBC8F8F}, {"royalblue", 0x4169E1}, {"saddlebrown", 0x8B4513}, {"salmon", 0xFA8072}, {"sandybrown", 0xF4A460}, {"seagreen", 0x2E8B57}, {"seashell", 0xFFF5EE}, {"sienna", 0xA0522D}, {"silver", 0xC0C0C0}, {"skyblue", 0x87CEEB}, {"slateblue", 0x6A5ACD}, {"slategray", 0x708090}, {"slategrey", 0x708090}, {"snow", 0xFFFAFA}, {"springgreen", 0x00FF7F}, {"steelblue", 0x4682B4}, {"tan", 0xD2B48C}, {"teal", 0x008080}, {"thistle", 0xD8BFD8}, {"tomato", 0xFF6347}, {"turquoise", 0x40E0D0}, {"violet", 0xEE82EE}, {"wheat", 0xF5DEB3}, {"white", 0xFFFFFF}, {"whitesmoke", 0xF5F5F5}, {"yellow", 0xFFFF00}, {"yellowgreen", 0x9ACD32} }; const color_entry_t* entry = bsearch(name, colormap, sizeof(colormap) / sizeof(color_entry_t), sizeof(color_entry_t), color_entry_compare); if(entry == NULL) return 0; plutovg_color_init_argb32(color, 0xFF000000 | entry->value); } } plutovg_skip_ws(&it, end); return it - data; } static void* plutovg_paint_create(plutovg_paint_type_t type, size_t size) { plutovg_paint_t* paint = malloc(size); plutovg_init_reference(paint); paint->type = type; return paint; } plutovg_paint_t* plutovg_paint_create_rgb(float r, float g, float b) { return plutovg_paint_create_rgba(r, g, b, 1.f); } plutovg_paint_t* plutovg_paint_create_rgba(float r, float g, float b, float a) { plutovg_solid_paint_t* solid = plutovg_paint_create(PLUTOVG_PAINT_TYPE_COLOR, sizeof(plutovg_solid_paint_t)); solid->color.r = plutovg_clamp(r, 0.f, 1.f); solid->color.g = plutovg_clamp(g, 0.f, 1.f); solid->color.b = plutovg_clamp(b, 0.f, 1.f); solid->color.a = plutovg_clamp(a, 0.f, 1.f); return &solid->base; } plutovg_paint_t* plutovg_paint_create_color(const plutovg_color_t* color) { return plutovg_paint_create_rgba(color->r, color->g, color->b, color->a); } static plutovg_gradient_paint_t* plutovg_gradient_create(plutovg_gradient_type_t type, plutovg_spread_method_t spread, const plutovg_gradient_stop_t* stops, int nstops, const plutovg_matrix_t* matrix) { plutovg_gradient_paint_t* gradient = plutovg_paint_create(PLUTOVG_PAINT_TYPE_GRADIENT, sizeof(plutovg_gradient_paint_t) + nstops * sizeof(plutovg_gradient_stop_t)); gradient->type = type; gradient->spread = spread; gradient->matrix = matrix ? *matrix : PLUTOVG_IDENTITY_MATRIX; gradient->stops = (plutovg_gradient_stop_t*)(gradient + 1); gradient->nstops = nstops; float prev_offset = 0.f; for(int i = 0; i < nstops; ++i) { const plutovg_gradient_stop_t* stop = stops + i; gradient->stops[i].offset = plutovg_max(prev_offset, plutovg_clamp(stop->offset, 0.f, 1.f)); gradient->stops[i].color.r = plutovg_clamp(stop->color.r, 0.f, 1.f); gradient->stops[i].color.g = plutovg_clamp(stop->color.g, 0.f, 1.f); gradient->stops[i].color.b = plutovg_clamp(stop->color.b, 0.f, 1.f); gradient->stops[i].color.a = plutovg_clamp(stop->color.a, 0.f, 1.f); prev_offset = gradient->stops[i].offset; } return gradient; } plutovg_paint_t* plutovg_paint_create_linear_gradient(float x1, float y1, float x2, float y2, plutovg_spread_method_t spread, const plutovg_gradient_stop_t* stops, int nstops, const plutovg_matrix_t* matrix) { plutovg_gradient_paint_t* gradient = plutovg_gradient_create(PLUTOVG_GRADIENT_TYPE_LINEAR, spread, stops, nstops, matrix); gradient->values[0] = x1; gradient->values[1] = y1; gradient->values[2] = x2; gradient->values[3] = y2; return &gradient->base; } plutovg_paint_t* plutovg_paint_create_radial_gradient(float cx, float cy, float cr, float fx, float fy, float fr, plutovg_spread_method_t spread, const plutovg_gradient_stop_t* stops, int nstops, const plutovg_matrix_t* matrix) { plutovg_gradient_paint_t* gradient = plutovg_gradient_create(PLUTOVG_GRADIENT_TYPE_RADIAL, spread, stops, nstops, matrix); gradient->values[0] = cx; gradient->values[1] = cy; gradient->values[2] = cr; gradient->values[3] = fx; gradient->values[4] = fy; gradient->values[5] = fr; return &gradient->base; } plutovg_paint_t* plutovg_paint_create_texture(plutovg_surface_t* surface, plutovg_texture_type_t type, float opacity, const plutovg_matrix_t* matrix) { plutovg_texture_paint_t* texture = plutovg_paint_create(PLUTOVG_PAINT_TYPE_TEXTURE, sizeof(plutovg_texture_paint_t)); texture->type = type; texture->opacity = plutovg_clamp(opacity, 0.f, 1.f); texture->matrix = matrix ? *matrix : PLUTOVG_IDENTITY_MATRIX; texture->surface = plutovg_surface_reference(surface); return &texture->base; } plutovg_paint_t* plutovg_paint_reference(plutovg_paint_t* paint) { plutovg_increment_reference(paint); return paint; } void plutovg_paint_destroy(plutovg_paint_t* paint) { if(plutovg_destroy_reference(paint)) { if(paint->type == PLUTOVG_PAINT_TYPE_TEXTURE) { plutovg_texture_paint_t* texture = (plutovg_texture_paint_t*)(paint); plutovg_surface_destroy(texture->surface); } free(paint); } } int plutovg_paint_get_reference_count(const plutovg_paint_t* paint) { return plutovg_get_reference_count(paint); } sammycage-plutovg-5695a71/source/plutovg-path.c000066400000000000000000000751111510714322500215440ustar00rootroot00000000000000#include "plutovg-private.h" #include "plutovg-utils.h" #include void plutovg_path_iterator_init(plutovg_path_iterator_t* it, const plutovg_path_t* path) { it->elements = path->elements.data; it->size = path->elements.size; it->index = 0; } bool plutovg_path_iterator_has_next(const plutovg_path_iterator_t* it) { return it->index < it->size; } plutovg_path_command_t plutovg_path_iterator_next(plutovg_path_iterator_t* it, plutovg_point_t points[3]) { const plutovg_path_element_t* elements = it->elements + it->index; switch(elements[0].header.command) { case PLUTOVG_PATH_COMMAND_MOVE_TO: case PLUTOVG_PATH_COMMAND_LINE_TO: case PLUTOVG_PATH_COMMAND_CLOSE: points[0] = elements[1].point; break; case PLUTOVG_PATH_COMMAND_CUBIC_TO: points[0] = elements[1].point; points[1] = elements[2].point; points[2] = elements[3].point; break; } it->index += elements[0].header.length; return elements[0].header.command; } plutovg_path_t* plutovg_path_create(void) { plutovg_path_t* path = malloc(sizeof(plutovg_path_t)); plutovg_init_reference(path); path->num_points = 0; path->num_contours = 0; path->num_curves = 0; path->start_point = PLUTOVG_EMPTY_POINT; plutovg_array_init(path->elements); return path; } plutovg_path_t* plutovg_path_reference(plutovg_path_t* path) { plutovg_increment_reference(path); return path; } void plutovg_path_destroy(plutovg_path_t* path) { if(plutovg_destroy_reference(path)) { plutovg_array_destroy(path->elements); free(path); } } int plutovg_path_get_reference_count(const plutovg_path_t* path) { return plutovg_get_reference_count(path); } int plutovg_path_get_elements(const plutovg_path_t* path, const plutovg_path_element_t** elements) { if(elements) *elements = path->elements.data; return path->elements.size; } static plutovg_path_element_t* plutovg_path_add_command(plutovg_path_t* path, plutovg_path_command_t command, int npoints) { const int length = npoints + 1; plutovg_array_ensure(path->elements, length); plutovg_path_element_t* elements = path->elements.data + path->elements.size; elements->header.command = command; elements->header.length = length; path->elements.size += length; path->num_points += npoints; return elements + 1; } void plutovg_path_move_to(plutovg_path_t* path, float x, float y) { plutovg_path_element_t* elements = plutovg_path_add_command(path, PLUTOVG_PATH_COMMAND_MOVE_TO, 1); elements[0].point = PLUTOVG_MAKE_POINT(x, y); path->start_point = PLUTOVG_MAKE_POINT(x, y); path->num_contours += 1; } void plutovg_path_line_to(plutovg_path_t* path, float x, float y) { if(path->elements.size == 0) plutovg_path_move_to(path, 0, 0); plutovg_path_element_t* elements = plutovg_path_add_command(path, PLUTOVG_PATH_COMMAND_LINE_TO, 1); elements[0].point = PLUTOVG_MAKE_POINT(x, y); } void plutovg_path_quad_to(plutovg_path_t* path, float x1, float y1, float x2, float y2) { float current_x, current_y; plutovg_path_get_current_point(path, ¤t_x, ¤t_y); float cp1x = 2.f / 3.f * x1 + 1.f / 3.f * current_x; float cp1y = 2.f / 3.f * y1 + 1.f / 3.f * current_y; float cp2x = 2.f / 3.f * x1 + 1.f / 3.f * x2; float cp2y = 2.f / 3.f * y1 + 1.f / 3.f * y2; plutovg_path_cubic_to(path, cp1x, cp1y, cp2x, cp2y, x2, y2); } void plutovg_path_cubic_to(plutovg_path_t* path, float x1, float y1, float x2, float y2, float x3, float y3) { if(path->elements.size == 0) plutovg_path_move_to(path, 0, 0); plutovg_path_element_t* elements = plutovg_path_add_command(path, PLUTOVG_PATH_COMMAND_CUBIC_TO, 3); elements[0].point = PLUTOVG_MAKE_POINT(x1, y1); elements[1].point = PLUTOVG_MAKE_POINT(x2, y2); elements[2].point = PLUTOVG_MAKE_POINT(x3, y3); path->num_curves += 1; } void plutovg_path_arc_to(plutovg_path_t* path, float rx, float ry, float angle, bool large_arc_flag, bool sweep_flag, float x, float y) { float current_x, current_y; plutovg_path_get_current_point(path, ¤t_x, ¤t_y); if(rx == 0.f || ry == 0.f || (current_x == x && current_y == y)) { plutovg_path_line_to(path, x, y); return; } if(rx < 0.f) rx = -rx; if(ry < 0.f) ry = -ry; float dx = (current_x - x) * 0.5f; float dy = (current_y - y) * 0.5f; plutovg_matrix_t matrix; plutovg_matrix_init_rotate(&matrix, -angle); plutovg_matrix_map(&matrix, dx, dy, &dx, &dy); float rxrx = rx * rx; float ryry = ry * ry; float dxdx = dx * dx; float dydy = dy * dy; float radius = dxdx / rxrx + dydy / ryry; if(radius > 1.f) { rx *= sqrtf(radius); ry *= sqrtf(radius); } plutovg_matrix_init_scale(&matrix, 1.f / rx, 1.f / ry); plutovg_matrix_rotate(&matrix, -angle); float x1, y1; float x2, y2; plutovg_matrix_map(&matrix, current_x, current_y, &x1, &y1); plutovg_matrix_map(&matrix, x, y, &x2, &y2); float dx1 = x2 - x1; float dy1 = y2 - y1; float d = dx1 * dx1 + dy1 * dy1; float scale_sq = 1.f / d - 0.25f; if(scale_sq < 0.f) scale_sq = 0.f; float scale = sqrtf(scale_sq); if(sweep_flag == large_arc_flag) scale = -scale; dx1 *= scale; dy1 *= scale; float cx1 = 0.5f * (x1 + x2) - dy1; float cy1 = 0.5f * (y1 + y2) + dx1; float th1 = atan2f(y1 - cy1, x1 - cx1); float th2 = atan2f(y2 - cy1, x2 - cx1); float th_arc = th2 - th1; if(th_arc < 0.f && sweep_flag) th_arc += PLUTOVG_TWO_PI; else if(th_arc > 0.f && !sweep_flag) th_arc -= PLUTOVG_TWO_PI; plutovg_matrix_init_rotate(&matrix, angle); plutovg_matrix_scale(&matrix, rx, ry); int segments = (int)(ceilf(fabsf(th_arc / (PLUTOVG_HALF_PI + 0.001f)))); for(int i = 0; i < segments; i++) { float th_start = th1 + i * th_arc / segments; float th_end = th1 + (i + 1) * th_arc / segments; float t = (8.f / 6.f) * tanf(0.25f * (th_end - th_start)); float x3 = cosf(th_end) + cx1; float y3 = sinf(th_end) + cy1; float cp2x = x3 + t * sinf(th_end); float cp2y = y3 - t * cosf(th_end); float cp1x = cosf(th_start) - t * sinf(th_start); float cp1y = sinf(th_start) + t * cosf(th_start); cp1x += cx1; cp1y += cy1; plutovg_matrix_map(&matrix, cp1x, cp1y, &cp1x, &cp1y); plutovg_matrix_map(&matrix, cp2x, cp2y, &cp2x, &cp2y); plutovg_matrix_map(&matrix, x3, y3, &x3, &y3); plutovg_path_cubic_to(path, cp1x, cp1y, cp2x, cp2y, x3, y3); } } void plutovg_path_close(plutovg_path_t* path) { if(path->elements.size == 0) return; plutovg_path_element_t* elements = plutovg_path_add_command(path, PLUTOVG_PATH_COMMAND_CLOSE, 1); elements[0].point = path->start_point; } void plutovg_path_get_current_point(const plutovg_path_t* path, float* x, float* y) { float xx = 0.f; float yy = 0.f; if(path->num_points > 0) { xx = path->elements.data[path->elements.size - 1].point.x; yy = path->elements.data[path->elements.size - 1].point.y; } if(x) *x = xx; if(y) *y = yy; } void plutovg_path_reserve(plutovg_path_t* path, int count) { plutovg_array_ensure(path->elements, count); } void plutovg_path_reset(plutovg_path_t* path) { plutovg_array_clear(path->elements); path->start_point = PLUTOVG_EMPTY_POINT; path->num_points = 0; path->num_contours = 0; path->num_curves = 0; } void plutovg_path_add_rect(plutovg_path_t* path, float x, float y, float w, float h) { plutovg_path_reserve(path, 6 * 2); plutovg_path_move_to(path, x, y); plutovg_path_line_to(path, x + w, y); plutovg_path_line_to(path, x + w, y + h); plutovg_path_line_to(path, x, y + h); plutovg_path_line_to(path, x, y); plutovg_path_close(path); } void plutovg_path_add_round_rect(plutovg_path_t* path, float x, float y, float w, float h, float rx, float ry) { rx = plutovg_min(rx, w * 0.5f); ry = plutovg_min(ry, h * 0.5f); if(rx == 0.f && ry == 0.f) { plutovg_path_add_rect(path, x, y, w, h); return; } float right = x + w; float bottom = y + h; float cpx = rx * PLUTOVG_KAPPA; float cpy = ry * PLUTOVG_KAPPA; plutovg_path_reserve(path, 6 * 2 + 4 * 4); plutovg_path_move_to(path, x, y+ry); plutovg_path_cubic_to(path, x, y+ry-cpy, x+rx-cpx, y, x+rx, y); plutovg_path_line_to(path, right-rx, y); plutovg_path_cubic_to(path, right-rx+cpx, y, right, y+ry-cpy, right, y+ry); plutovg_path_line_to(path, right, bottom-ry); plutovg_path_cubic_to(path, right, bottom-ry+cpy, right-rx+cpx, bottom, right-rx, bottom); plutovg_path_line_to(path, x+rx, bottom); plutovg_path_cubic_to(path, x+rx-cpx, bottom, x, bottom-ry+cpy, x, bottom-ry); plutovg_path_line_to(path, x, y+ry); plutovg_path_close(path); } void plutovg_path_add_ellipse(plutovg_path_t* path, float cx, float cy, float rx, float ry) { float left = cx - rx; float top = cy - ry; float right = cx + rx; float bottom = cy + ry; float cpx = rx * PLUTOVG_KAPPA; float cpy = ry * PLUTOVG_KAPPA; plutovg_path_reserve(path, 2 * 2 + 4 * 4); plutovg_path_move_to(path, cx, top); plutovg_path_cubic_to(path, cx+cpx, top, right, cy-cpy, right, cy); plutovg_path_cubic_to(path, right, cy+cpy, cx+cpx, bottom, cx, bottom); plutovg_path_cubic_to(path, cx-cpx, bottom, left, cy+cpy, left, cy); plutovg_path_cubic_to(path, left, cy-cpy, cx-cpx, top, cx, top); plutovg_path_close(path); } void plutovg_path_add_circle(plutovg_path_t* path, float cx, float cy, float r) { plutovg_path_add_ellipse(path, cx, cy, r, r); } void plutovg_path_add_arc(plutovg_path_t* path, float cx, float cy, float r, float a0, float a1, bool ccw) { float da = a1 - a0; if(fabsf(da) > PLUTOVG_TWO_PI) { da = PLUTOVG_TWO_PI; } else if(da != 0.f && ccw != (da < 0.f)) { da += PLUTOVG_TWO_PI * (ccw ? -1 : 1); } int seg_n = (int)(ceilf(fabsf(da) / PLUTOVG_HALF_PI)); if(seg_n == 0) return; float a = a0; float ax = cx + cosf(a) * r; float ay = cy + sinf(a) * r; float seg_a = da / seg_n; float d = (seg_a / PLUTOVG_HALF_PI) * PLUTOVG_KAPPA * r; float dx = -sinf(a) * d; float dy = cosf(a) * d; plutovg_path_reserve(path, 2 + 4 * seg_n); if(path->elements.size == 0) { plutovg_path_move_to(path, ax, ay); } else { plutovg_path_line_to(path, ax, ay); } for(int i = 0; i < seg_n; i++) { float cp1x = ax + dx; float cp1y = ay + dy; a += seg_a; ax = cx + cosf(a) * r; ay = cy + sinf(a) * r; dx = -sinf(a) * d; dy = cosf(a) * d; float cp2x = ax - dx; float cp2y = ay - dy; plutovg_path_cubic_to(path, cp1x, cp1y, cp2x, cp2y, ax, ay); } } void plutovg_path_transform(plutovg_path_t* path, const plutovg_matrix_t* matrix) { plutovg_path_element_t* elements = path->elements.data; for(int i = 0; i < path->elements.size; i += elements[i].header.length) { switch(elements[i].header.command) { case PLUTOVG_PATH_COMMAND_MOVE_TO: case PLUTOVG_PATH_COMMAND_LINE_TO: case PLUTOVG_PATH_COMMAND_CLOSE: plutovg_matrix_map_point(matrix, &elements[i + 1].point, &elements[i + 1].point); break; case PLUTOVG_PATH_COMMAND_CUBIC_TO: plutovg_matrix_map_point(matrix, &elements[i + 1].point, &elements[i + 1].point); plutovg_matrix_map_point(matrix, &elements[i + 2].point, &elements[i + 2].point); plutovg_matrix_map_point(matrix, &elements[i + 3].point, &elements[i + 3].point); break; } } } void plutovg_path_add_path(plutovg_path_t* path, const plutovg_path_t* source, const plutovg_matrix_t* matrix) { if(matrix == NULL) { plutovg_array_append(path->elements, source->elements); path->start_point = source->start_point; path->num_points += source->num_points; path->num_contours += source->num_contours; path->num_curves += source->num_curves; return; } plutovg_path_iterator_t it; plutovg_path_iterator_init(&it, source); plutovg_point_t points[3]; plutovg_array_ensure(path->elements, source->elements.size); while(plutovg_path_iterator_has_next(&it)) { switch(plutovg_path_iterator_next(&it, points)) { case PLUTOVG_PATH_COMMAND_MOVE_TO: plutovg_matrix_map_points(matrix, points, points, 1); plutovg_path_move_to(path, points[0].x, points[0].y); break; case PLUTOVG_PATH_COMMAND_LINE_TO: plutovg_matrix_map_points(matrix, points, points, 1); plutovg_path_line_to(path, points[0].x, points[0].y); break; case PLUTOVG_PATH_COMMAND_CUBIC_TO: plutovg_matrix_map_points(matrix, points, points, 3); plutovg_path_cubic_to(path, points[0].x, points[0].y, points[1].x, points[1].y, points[2].x, points[2].y); break; case PLUTOVG_PATH_COMMAND_CLOSE: plutovg_path_close(path); break; } } } void plutovg_path_traverse(const plutovg_path_t* path, plutovg_path_traverse_func_t traverse_func, void* closure) { plutovg_path_iterator_t it; plutovg_path_iterator_init(&it, path); plutovg_point_t points[3]; while(plutovg_path_iterator_has_next(&it)) { switch(plutovg_path_iterator_next(&it, points)) { case PLUTOVG_PATH_COMMAND_MOVE_TO: traverse_func(closure, PLUTOVG_PATH_COMMAND_MOVE_TO, points, 1); break; case PLUTOVG_PATH_COMMAND_LINE_TO: traverse_func(closure, PLUTOVG_PATH_COMMAND_LINE_TO, points, 1); break; case PLUTOVG_PATH_COMMAND_CUBIC_TO: traverse_func(closure, PLUTOVG_PATH_COMMAND_CUBIC_TO, points, 3); break; case PLUTOVG_PATH_COMMAND_CLOSE: traverse_func(closure, PLUTOVG_PATH_COMMAND_CLOSE, points, 1); break; } } } typedef struct { float x1; float y1; float x2; float y2; float x3; float y3; float x4; float y4; } bezier_t; static inline void split_bezier(const bezier_t* b, bezier_t* first, bezier_t* second) { float c = (b->x2 + b->x3) * 0.5f; first->x2 = (b->x1 + b->x2) * 0.5f; second->x3 = (b->x3 + b->x4) * 0.5f; first->x1 = b->x1; second->x4 = b->x4; first->x3 = (first->x2 + c) * 0.5f; second->x2 = (second->x3 + c) * 0.5f; first->x4 = second->x1 = (first->x3 + second->x2) * 0.5f; c = (b->y2 + b->y3) * 0.5f; first->y2 = (b->y1 + b->y2) * 0.5f; second->y3 = (b->y3 + b->y4) * 0.5f; first->y1 = b->y1; second->y4 = b->y4; first->y3 = (first->y2 + c) * 0.5f; second->y2 = (second->y3 + c) * 0.5f; first->y4 = second->y1 = (first->y3 + second->y2) * 0.5f; } void plutovg_path_traverse_flatten(const plutovg_path_t* path, plutovg_path_traverse_func_t traverse_func, void* closure) { if(path->num_curves == 0) { plutovg_path_traverse(path, traverse_func, closure); return; } const float threshold = 0.25f; plutovg_path_iterator_t it; plutovg_path_iterator_init(&it, path); bezier_t beziers[32]; plutovg_point_t points[3]; plutovg_point_t current_point = {0, 0}; while(plutovg_path_iterator_has_next(&it)) { plutovg_path_command_t command = plutovg_path_iterator_next(&it, points); switch(command) { case PLUTOVG_PATH_COMMAND_MOVE_TO: case PLUTOVG_PATH_COMMAND_LINE_TO: case PLUTOVG_PATH_COMMAND_CLOSE: traverse_func(closure, command, points, 1); current_point = points[0]; break; case PLUTOVG_PATH_COMMAND_CUBIC_TO: beziers[0].x1 = current_point.x; beziers[0].y1 = current_point.y; beziers[0].x2 = points[0].x; beziers[0].y2 = points[0].y; beziers[0].x3 = points[1].x; beziers[0].y3 = points[1].y; beziers[0].x4 = points[2].x; beziers[0].y4 = points[2].y; bezier_t* b = beziers; while(b >= beziers) { float y4y1 = b->y4 - b->y1; float x4x1 = b->x4 - b->x1; float l = fabsf(x4x1) + fabsf(y4y1); float d; if(l > 1.f) { d = fabsf((x4x1)*(b->y1 - b->y2) - (y4y1)*(b->x1 - b->x2)) + fabsf((x4x1)*(b->y1 - b->y3) - (y4y1)*(b->x1 - b->x3)); } else { d = fabsf(b->x1 - b->x2) + fabsf(b->y1 - b->y2) + fabsf(b->x1 - b->x3) + fabsf(b->y1 - b->y3); l = 1.f; } if(d < threshold*l || b == beziers + 31) { plutovg_point_t p = { b->x4, b->y4 }; traverse_func(closure, PLUTOVG_PATH_COMMAND_LINE_TO, &p, 1); --b; } else { split_bezier(b, b + 1, b); ++b; } } current_point = points[2]; break; } } } typedef struct { const float* dashes; int ndashes; float start_phase; float phase; int start_index; int index; bool start_toggle; bool toggle; plutovg_point_t current_point; plutovg_path_traverse_func_t traverse_func; void* closure; } dasher_t; static void dash_traverse_func(void* closure, plutovg_path_command_t command, const plutovg_point_t* points, int npoints) { dasher_t* dasher = (dasher_t*)(closure); if(command == PLUTOVG_PATH_COMMAND_MOVE_TO) { if(dasher->start_toggle) dasher->traverse_func(dasher->closure, PLUTOVG_PATH_COMMAND_MOVE_TO, points, npoints); dasher->current_point = points[0]; dasher->phase = dasher->start_phase; dasher->index = dasher->start_index; dasher->toggle = dasher->start_toggle; return; } assert(command == PLUTOVG_PATH_COMMAND_LINE_TO || command == PLUTOVG_PATH_COMMAND_CLOSE); plutovg_point_t p0 = dasher->current_point; plutovg_point_t p1 = points[0]; float dx = p1.x - p0.x; float dy = p1.y - p0.y; float dist0 = sqrtf(dx*dx + dy*dy); float dist1 = 0.f; while(dist0 - dist1 > dasher->dashes[dasher->index % dasher->ndashes] - dasher->phase) { dist1 += dasher->dashes[dasher->index % dasher->ndashes] - dasher->phase; float a = dist1 / dist0; plutovg_point_t p = { p0.x + a * dx, p0.y + a * dy }; if(dasher->toggle) { dasher->traverse_func(dasher->closure, PLUTOVG_PATH_COMMAND_LINE_TO, &p, 1); } else { dasher->traverse_func(dasher->closure, PLUTOVG_PATH_COMMAND_MOVE_TO, &p, 1); } dasher->phase = 0.f; dasher->toggle = !dasher->toggle; dasher->index++; } if(dasher->toggle) { dasher->traverse_func(dasher->closure, PLUTOVG_PATH_COMMAND_LINE_TO, &p1, 1); } dasher->phase += dist0 - dist1; dasher->current_point = p1; } void plutovg_path_traverse_dashed(const plutovg_path_t* path, float offset, const float* dashes, int ndashes, plutovg_path_traverse_func_t traverse_func, void* closure) { float dash_sum = 0.f; for(int i = 0; i < ndashes; ++i) dash_sum += dashes[i]; if(ndashes % 2 == 1) dash_sum *= 2.f; if(dash_sum <= 0.f) { plutovg_path_traverse(path, traverse_func, closure); return; } dasher_t dasher; dasher.dashes = dashes; dasher.ndashes = ndashes; dasher.start_phase = fmodf(offset, dash_sum); if(dasher.start_phase < 0.f) dasher.start_phase += dash_sum; dasher.start_index = 0; dasher.start_toggle = true; while(dasher.start_phase > 0.f && dasher.start_phase >= dasher.dashes[dasher.start_index % dasher.ndashes]) { dasher.start_phase -= dashes[dasher.start_index % dasher.ndashes]; dasher.start_toggle = !dasher.start_toggle; dasher.start_index++; } dasher.phase = dasher.start_phase; dasher.index = dasher.start_index; dasher.toggle = dasher.start_toggle; dasher.current_point = PLUTOVG_EMPTY_POINT; dasher.traverse_func = traverse_func; dasher.closure = closure; plutovg_path_traverse_flatten(path, dash_traverse_func, &dasher); } plutovg_path_t* plutovg_path_clone(const plutovg_path_t* path) { plutovg_path_t* clone = plutovg_path_create(); plutovg_array_append(clone->elements, path->elements); clone->start_point = path->start_point; clone->num_points = path->num_points; clone->num_contours = path->num_contours; clone->num_curves = path->num_curves; return clone; } static void clone_traverse_func(void* closure, plutovg_path_command_t command, const plutovg_point_t* points, int npoints) { plutovg_path_t* path = (plutovg_path_t*)(closure); switch(command) { case PLUTOVG_PATH_COMMAND_MOVE_TO: plutovg_path_move_to(path, points[0].x, points[0].y); break; case PLUTOVG_PATH_COMMAND_LINE_TO: plutovg_path_line_to(path, points[0].x, points[0].y); break; case PLUTOVG_PATH_COMMAND_CUBIC_TO: plutovg_path_cubic_to(path, points[0].x, points[0].y, points[1].x, points[1].y, points[2].x, points[2].y); break; case PLUTOVG_PATH_COMMAND_CLOSE: plutovg_path_close(path); break; } } plutovg_path_t* plutovg_path_clone_flatten(const plutovg_path_t* path) { plutovg_path_t* clone = plutovg_path_create(); plutovg_path_reserve(clone, path->elements.size + path->num_curves * 32); plutovg_path_traverse_flatten(path, clone_traverse_func, clone); return clone; } plutovg_path_t* plutovg_path_clone_dashed(const plutovg_path_t* path, float offset, const float* dashes, int ndashes) { plutovg_path_t* clone = plutovg_path_create(); plutovg_path_reserve(clone, path->elements.size + path->num_curves * 32); plutovg_path_traverse_dashed(path, offset, dashes, ndashes, clone_traverse_func, clone); return clone; } typedef struct { plutovg_point_t current_point; bool is_first_point; float length; float x1; float y1; float x2; float y2; } extents_calculator_t; static void extents_traverse_func(void* closure, plutovg_path_command_t command, const plutovg_point_t* points, int npoints) { extents_calculator_t* calculator = (extents_calculator_t*)(closure); if(calculator->is_first_point) { assert(command == PLUTOVG_PATH_COMMAND_MOVE_TO); calculator->is_first_point = false; calculator->current_point = points[0]; calculator->x1 = points[0].x; calculator->y1 = points[0].y; calculator->x2 = points[0].x; calculator->y2 = points[0].y; calculator->length = 0; return; } for(int i = 0; i < npoints; ++i) { calculator->x1 = plutovg_min(calculator->x1, points[i].x); calculator->y1 = plutovg_min(calculator->y1, points[i].y); calculator->x2 = plutovg_max(calculator->x2, points[i].x); calculator->y2 = plutovg_max(calculator->y2, points[i].y); if(command != PLUTOVG_PATH_COMMAND_MOVE_TO) calculator->length += hypotf(points[i].x - calculator->current_point.x, points[i].y - calculator->current_point.y); calculator->current_point = points[i]; } } float plutovg_path_extents(const plutovg_path_t* path, plutovg_rect_t* extents, bool tight) { extents_calculator_t calculator = {{0, 0}, true, 0, 0, 0, 0, 0}; if(tight) { plutovg_path_traverse_flatten(path, extents_traverse_func, &calculator); } else { plutovg_path_traverse(path, extents_traverse_func, &calculator); } if(extents) { extents->x = calculator.x1; extents->y = calculator.y1; extents->w = calculator.x2 - calculator.x1; extents->h = calculator.y2 - calculator.y1; } return calculator.length; } float plutovg_path_length(const plutovg_path_t* path) { return plutovg_path_extents(path, NULL, true); } static inline bool parse_arc_flag(const char** begin, const char* end, bool* flag) { if(plutovg_skip_delim(begin, end, '0')) *flag = 0; else if(plutovg_skip_delim(begin, end, '1')) *flag = 1; else return false; plutovg_skip_ws_or_comma(begin, end, NULL); return true; } static inline bool parse_path_coordinates(const char** begin, const char* end, float values[6], int offset, int count) { for(int i = 0; i < count; i++) { if(!plutovg_parse_number(begin, end, values + offset + i)) return false; plutovg_skip_ws_or_comma(begin, end, NULL); } return true; } bool plutovg_path_parse(plutovg_path_t* path, const char* data, int length) { if(length == -1) length = strlen(data); const char* it = data; const char* end = it + length; float values[6]; bool flags[2]; float start_x = 0; float start_y = 0; float current_x = 0; float current_y = 0; float last_control_x = 0; float last_control_y = 0; char command = 0; char last_command = 0; plutovg_skip_ws(&it, end); while(it < end) { if(PLUTOVG_IS_ALPHA(*it)) { command = *it++; plutovg_skip_ws(&it, end); } if(!last_command && !(command == 'M' || command == 'm')) return false; if(command == 'M' || command == 'm') { if(!parse_path_coordinates(&it, end, values, 0, 2)) return false; if(command == 'm') { values[0] += current_x; values[1] += current_y; } plutovg_path_move_to(path, values[0], values[1]); current_x = start_x = values[0]; current_y = start_y = values[1]; command = command == 'm' ? 'l' : 'L'; } else if(command == 'L' || command == 'l') { if(!parse_path_coordinates(&it, end, values, 0, 2)) return false; if(command == 'l') { values[0] += current_x; values[1] += current_y; } plutovg_path_line_to(path, values[0], values[1]); current_x = values[0]; current_y = values[1]; } else if(command == 'H' || command == 'h') { if(!parse_path_coordinates(&it, end, values, 0, 1)) return false; if(command == 'h') { values[0] += current_x; } plutovg_path_line_to(path, values[0], current_y); current_x = values[0]; } else if(command == 'V' || command == 'v') { if(!parse_path_coordinates(&it, end, values, 1, 1)) return false; if(command == 'v') { values[1] += current_y; } plutovg_path_line_to(path, current_x, values[1]); current_y = values[1]; } else if(command == 'Q' || command == 'q') { if(!parse_path_coordinates(&it, end, values, 0, 4)) return false; if(command == 'q') { values[0] += current_x; values[1] += current_y; values[2] += current_x; values[3] += current_y; } plutovg_path_quad_to(path, values[0], values[1], values[2], values[3]); last_control_x = values[0]; last_control_y = values[1]; current_x = values[2]; current_y = values[3]; } else if(command == 'C' || command == 'c') { if(!parse_path_coordinates(&it, end, values, 0, 6)) return false; if(command == 'c') { values[0] += current_x; values[1] += current_y; values[2] += current_x; values[3] += current_y; values[4] += current_x; values[5] += current_y; } plutovg_path_cubic_to(path, values[0], values[1], values[2], values[3], values[4], values[5]); last_control_x = values[2]; last_control_y = values[3]; current_x = values[4]; current_y = values[5]; } else if(command == 'T' || command == 't') { if(last_command != 'Q' && last_command != 'q' && last_command != 'T' && last_command != 't') { values[0] = current_x; values[1] = current_y; } else { values[0] = 2 * current_x - last_control_x; values[1] = 2 * current_y - last_control_y; } if(!parse_path_coordinates(&it, end, values, 2, 2)) return false; if(command == 't') { values[2] += current_x; values[3] += current_y; } plutovg_path_quad_to(path, values[0], values[1], values[2], values[3]); last_control_x = values[0]; last_control_y = values[1]; current_x = values[2]; current_y = values[3]; } else if(command == 'S' || command == 's') { if(last_command != 'C' && last_command != 'c' && last_command != 'S' && last_command != 's') { values[0] = current_x; values[1] = current_y; } else { values[0] = 2 * current_x - last_control_x; values[1] = 2 * current_y - last_control_y; } if(!parse_path_coordinates(&it, end, values, 2, 4)) return false; if(command == 's') { values[2] += current_x; values[3] += current_y; values[4] += current_x; values[5] += current_y; } plutovg_path_cubic_to(path, values[0], values[1], values[2], values[3], values[4], values[5]); last_control_x = values[2]; last_control_y = values[3]; current_x = values[4]; current_y = values[5]; } else if(command == 'A' || command == 'a') { if(!parse_path_coordinates(&it, end, values, 0, 3) || !parse_arc_flag(&it, end, &flags[0]) || !parse_arc_flag(&it, end, &flags[1]) || !parse_path_coordinates(&it, end, values, 3, 2)) { return false; } if(command == 'a') { values[3] += current_x; values[4] += current_y; } plutovg_path_arc_to(path, values[0], values[1], PLUTOVG_DEG2RAD(values[2]), flags[0], flags[1], values[3], values[4]); current_x = values[3]; current_y = values[4]; } else if(command == 'Z' || command == 'z') { if(last_command == 'Z' || last_command == 'z') return false; plutovg_path_close(path); current_x = start_x; current_y = start_y; } else { return false; } last_command = command; } return true; } sammycage-plutovg-5695a71/source/plutovg-private.h000066400000000000000000000120331510714322500222610ustar00rootroot00000000000000#ifndef PLUTOVG_PRIVATE_H #define PLUTOVG_PRIVATE_H #include "plutovg.h" #if defined(_WIN32) #include typedef LONG plutovg_ref_count_t; #define plutovg_init_reference(ob) ((ob)->ref_count = 1) #define plutovg_increment_reference(ob) (void)(ob && InterlockedIncrement(&(ob)->ref_count)) #define plutovg_destroy_reference(ob) (ob && InterlockedDecrement(&(ob)->ref_count) == 0) #define plutovg_get_reference_count(ob) ((ob) ? InterlockedCompareExchange((LONG*)&(ob)->ref_count, 0, 0) : 0) #elif defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L && !defined(__STDC_NO_ATOMICS__) #include typedef atomic_int plutovg_ref_count_t; #define plutovg_init_reference(ob) atomic_init(&(ob)->ref_count, 1) #define plutovg_increment_reference(ob) (void)(ob && atomic_fetch_add(&(ob)->ref_count, 1)) #define plutovg_destroy_reference(ob) (ob && atomic_fetch_sub(&(ob)->ref_count, 1) == 1) #define plutovg_get_reference_count(ob) ((ob) ? atomic_load(&(ob)->ref_count) : 0) #else typedef int plutovg_ref_count_t; #define plutovg_init_reference(ob) ((ob)->ref_count = 1) #define plutovg_increment_reference(ob) (void)(ob && ++(ob)->ref_count) #define plutovg_destroy_reference(ob) (ob && --(ob)->ref_count == 0) #define plutovg_get_reference_count(ob) ((ob) ? (ob)->ref_count : 0) #endif struct plutovg_surface { plutovg_ref_count_t ref_count; int width; int height; int stride; unsigned char* data; }; struct plutovg_path { plutovg_ref_count_t ref_count; int num_points; int num_contours; int num_curves; plutovg_point_t start_point; struct { plutovg_path_element_t* data; int size; int capacity; } elements; }; typedef enum { PLUTOVG_PAINT_TYPE_COLOR, PLUTOVG_PAINT_TYPE_GRADIENT, PLUTOVG_PAINT_TYPE_TEXTURE } plutovg_paint_type_t; struct plutovg_paint { plutovg_ref_count_t ref_count; plutovg_paint_type_t type; }; typedef struct { plutovg_paint_t base; plutovg_color_t color; } plutovg_solid_paint_t; typedef enum { PLUTOVG_GRADIENT_TYPE_LINEAR, PLUTOVG_GRADIENT_TYPE_RADIAL } plutovg_gradient_type_t; typedef struct { plutovg_paint_t base; plutovg_gradient_type_t type; plutovg_spread_method_t spread; plutovg_matrix_t matrix; plutovg_gradient_stop_t* stops; int nstops; float values[6]; } plutovg_gradient_paint_t; typedef struct { plutovg_paint_t base; plutovg_texture_type_t type; float opacity; plutovg_matrix_t matrix; plutovg_surface_t* surface; } plutovg_texture_paint_t; typedef struct { int x; int len; int y; unsigned char coverage; } plutovg_span_t; typedef struct { struct { plutovg_span_t* data; int size; int capacity; } spans; int x; int y; int w; int h; } plutovg_span_buffer_t; typedef struct { float offset; struct { float* data; int size; int capacity; } array; } plutovg_stroke_dash_t; typedef struct { float width; plutovg_line_cap_t cap; plutovg_line_join_t join; float miter_limit; } plutovg_stroke_style_t; typedef struct { plutovg_stroke_style_t style; plutovg_stroke_dash_t dash; } plutovg_stroke_data_t; typedef struct plutovg_state { plutovg_paint_t* paint; plutovg_font_face_t* font_face; plutovg_color_t color; plutovg_matrix_t matrix; plutovg_stroke_data_t stroke; plutovg_span_buffer_t clip_spans; plutovg_fill_rule_t winding; plutovg_operator_t op; float font_size; float opacity; bool clipping; struct plutovg_state* next; } plutovg_state_t; struct plutovg_canvas { plutovg_ref_count_t ref_count; plutovg_surface_t* surface; plutovg_path_t* path; plutovg_state_t* state; plutovg_state_t* freed_state; plutovg_font_face_cache_t* face_cache; plutovg_rect_t clip_rect; plutovg_span_buffer_t clip_spans; plutovg_span_buffer_t fill_spans; }; void plutovg_span_buffer_init(plutovg_span_buffer_t* span_buffer); void plutovg_span_buffer_init_rect(plutovg_span_buffer_t* span_buffer, int x, int y, int width, int height); void plutovg_span_buffer_reset(plutovg_span_buffer_t* span_buffer); void plutovg_span_buffer_destroy(plutovg_span_buffer_t* span_buffer); void plutovg_span_buffer_copy(plutovg_span_buffer_t* span_buffer, const plutovg_span_buffer_t* source); bool plutovg_span_buffer_contains(const plutovg_span_buffer_t* span_buffer, float x, float y); void plutovg_span_buffer_extents(plutovg_span_buffer_t* span_buffer, plutovg_rect_t* extents); void plutovg_span_buffer_intersect(plutovg_span_buffer_t* span_buffer, const plutovg_span_buffer_t* a, const plutovg_span_buffer_t* b); void plutovg_rasterize(plutovg_span_buffer_t* span_buffer, const plutovg_path_t* path, const plutovg_matrix_t* matrix, const plutovg_rect_t* clip_rect, const plutovg_stroke_data_t* stroke_data, plutovg_fill_rule_t winding); void plutovg_blend(plutovg_canvas_t* canvas, const plutovg_span_buffer_t* span_buffer); void plutovg_memfill32(unsigned int* dest, int length, unsigned int value); #endif // PLUTOVG_PRIVATE_H sammycage-plutovg-5695a71/source/plutovg-rasterize.c000066400000000000000000000313051510714322500226150ustar00rootroot00000000000000#include "plutovg-private.h" #include "plutovg-utils.h" #include "plutovg-ft-raster.h" #include "plutovg-ft-stroker.h" #include void plutovg_span_buffer_init(plutovg_span_buffer_t* span_buffer) { plutovg_array_init(span_buffer->spans); plutovg_span_buffer_reset(span_buffer); } void plutovg_span_buffer_init_rect(plutovg_span_buffer_t* span_buffer, int x, int y, int width, int height) { plutovg_array_clear(span_buffer->spans); plutovg_array_ensure(span_buffer->spans, height); plutovg_span_t* spans = span_buffer->spans.data; for(int i = 0; i < height; i++) { spans[i].x = x; spans[i].y = y + i; spans[i].len = width; spans[i].coverage = 255; } span_buffer->x = x; span_buffer->y = y; span_buffer->w = width; span_buffer->h = height; span_buffer->spans.size = height; } void plutovg_span_buffer_reset(plutovg_span_buffer_t* span_buffer) { plutovg_array_clear(span_buffer->spans); span_buffer->x = 0; span_buffer->y = 0; span_buffer->w = -1; span_buffer->h = -1; } void plutovg_span_buffer_destroy(plutovg_span_buffer_t* span_buffer) { plutovg_array_destroy(span_buffer->spans); } void plutovg_span_buffer_copy(plutovg_span_buffer_t* span_buffer, const plutovg_span_buffer_t* source) { plutovg_array_clear(span_buffer->spans); plutovg_array_append(span_buffer->spans, source->spans); span_buffer->x = source->x; span_buffer->y = source->y; span_buffer->w = source->w; span_buffer->h = source->h; } bool plutovg_span_buffer_contains(const plutovg_span_buffer_t* span_buffer, float x, float y) { const int ix = (int)floorf(x); const int iy = (int)floorf(y); for(int i = 0; i < span_buffer->spans.size; i++) { plutovg_span_t* span = &span_buffer->spans.data[i]; if(span->y != iy) continue; if(ix >= span->x && ix < (span->x + span->len)) { return true; } } return false; } static void plutovg_span_buffer_update_extents(plutovg_span_buffer_t* span_buffer) { if(span_buffer->w != -1 && span_buffer->h != -1) return; if(span_buffer->spans.size == 0) { span_buffer->x = 0; span_buffer->y = 0; span_buffer->w = 0; span_buffer->h = 0; return; } plutovg_span_t* spans = span_buffer->spans.data; int x1 = INT_MAX; int y1 = spans[0].y; int x2 = 0; int y2 = spans[span_buffer->spans.size - 1].y; for(int i = 0; i < span_buffer->spans.size; i++) { if(spans[i].x < x1) x1 = spans[i].x; if(spans[i].x + spans[i].len > x2) x2 = spans[i].x + spans[i].len; } span_buffer->x = x1; span_buffer->y = y1; span_buffer->w = x2 - x1; span_buffer->h = y2 - y1 + 1; } void plutovg_span_buffer_extents(plutovg_span_buffer_t* span_buffer, plutovg_rect_t* extents) { plutovg_span_buffer_update_extents(span_buffer); extents->x = span_buffer->x; extents->y = span_buffer->y; extents->w = span_buffer->w; extents->h = span_buffer->h; } void plutovg_span_buffer_intersect(plutovg_span_buffer_t* span_buffer, const plutovg_span_buffer_t* a, const plutovg_span_buffer_t* b) { plutovg_span_buffer_reset(span_buffer); plutovg_array_ensure(span_buffer->spans, plutovg_max(a->spans.size, b->spans.size)); plutovg_span_t* a_spans = a->spans.data; plutovg_span_t* a_end = a_spans + a->spans.size; plutovg_span_t* b_spans = b->spans.data; plutovg_span_t* b_end = b_spans + b->spans.size; while(a_spans < a_end && b_spans < b_end) { if(b_spans->y > a_spans->y) { ++a_spans; continue; } if(a_spans->y != b_spans->y) { ++b_spans; continue; } int ax1 = a_spans->x; int ax2 = ax1 + a_spans->len; int bx1 = b_spans->x; int bx2 = bx1 + b_spans->len; if(bx1 < ax1 && bx2 < ax1) { ++b_spans; continue; } if(ax1 < bx1 && ax2 < bx1) { ++a_spans; continue; } int x = plutovg_max(ax1, bx1); int len = plutovg_min(ax2, bx2) - x; if(len) { plutovg_array_ensure(span_buffer->spans, 1); plutovg_span_t* span = span_buffer->spans.data + span_buffer->spans.size; span->x = x; span->len = len; span->y = a_spans->y; span->coverage = (a_spans->coverage * b_spans->coverage) / 255; span_buffer->spans.size += 1; } if(ax2 < bx2) { ++a_spans; } else { ++b_spans; } } } #define ALIGN_SIZE(size) (((size) + 7ul) & ~7ul) static PVG_FT_Outline* ft_outline_create(int points, int contours) { size_t points_size = ALIGN_SIZE((points + contours) * sizeof(PVG_FT_Vector)); size_t tags_size = ALIGN_SIZE((points + contours) * sizeof(char)); size_t contours_size = ALIGN_SIZE(contours * sizeof(int)); size_t contours_flag_size = ALIGN_SIZE(contours * sizeof(char)); PVG_FT_Outline* outline = malloc(points_size + tags_size + contours_size + contours_flag_size + sizeof(PVG_FT_Outline)); PVG_FT_Byte* outline_data = (PVG_FT_Byte*)(outline + 1); outline->points = (PVG_FT_Vector*)(outline_data); outline->tags = (char*)(outline_data + points_size); outline->contours = (int*)(outline_data + points_size + tags_size); outline->contours_flag = (char*)(outline_data + points_size + tags_size + contours_size); outline->n_points = 0; outline->n_contours = 0; outline->flags = 0x0; return outline; } static void ft_outline_destroy(PVG_FT_Outline* outline) { free(outline); } #define FT_COORD(x) (PVG_FT_Pos)(roundf(x * 64)) static void ft_outline_move_to(PVG_FT_Outline* ft, float x, float y) { ft->points[ft->n_points].x = FT_COORD(x); ft->points[ft->n_points].y = FT_COORD(y); ft->tags[ft->n_points] = PVG_FT_CURVE_TAG_ON; if(ft->n_points) { ft->contours[ft->n_contours] = ft->n_points - 1; ft->n_contours++; } ft->contours_flag[ft->n_contours] = 1; ft->n_points++; } static void ft_outline_line_to(PVG_FT_Outline* ft, float x, float y) { ft->points[ft->n_points].x = FT_COORD(x); ft->points[ft->n_points].y = FT_COORD(y); ft->tags[ft->n_points] = PVG_FT_CURVE_TAG_ON; ft->n_points++; } static void ft_outline_cubic_to(PVG_FT_Outline* ft, float x1, float y1, float x2, float y2, float x3, float y3) { ft->points[ft->n_points].x = FT_COORD(x1); ft->points[ft->n_points].y = FT_COORD(y1); ft->tags[ft->n_points] = PVG_FT_CURVE_TAG_CUBIC; ft->n_points++; ft->points[ft->n_points].x = FT_COORD(x2); ft->points[ft->n_points].y = FT_COORD(y2); ft->tags[ft->n_points] = PVG_FT_CURVE_TAG_CUBIC; ft->n_points++; ft->points[ft->n_points].x = FT_COORD(x3); ft->points[ft->n_points].y = FT_COORD(y3); ft->tags[ft->n_points] = PVG_FT_CURVE_TAG_ON; ft->n_points++; } static void ft_outline_close(PVG_FT_Outline* ft) { ft->contours_flag[ft->n_contours] = 0; int index = ft->n_contours ? ft->contours[ft->n_contours - 1] + 1 : 0; if(index == ft->n_points) return; ft->points[ft->n_points].x = ft->points[index].x; ft->points[ft->n_points].y = ft->points[index].y; ft->tags[ft->n_points] = PVG_FT_CURVE_TAG_ON; ft->n_points++; } static void ft_outline_end(PVG_FT_Outline* ft) { if(ft->n_points) { ft->contours[ft->n_contours] = ft->n_points - 1; ft->n_contours++; } } static PVG_FT_Outline* ft_outline_convert_stroke(const plutovg_path_t* path, const plutovg_matrix_t* matrix, const plutovg_stroke_data_t* stroke_data); static PVG_FT_Outline* ft_outline_convert(const plutovg_path_t* path, const plutovg_matrix_t* matrix, const plutovg_stroke_data_t* stroke_data) { if(stroke_data) { return ft_outline_convert_stroke(path, matrix, stroke_data); } plutovg_path_iterator_t it; plutovg_path_iterator_init(&it, path); plutovg_point_t points[3]; PVG_FT_Outline* outline = ft_outline_create(path->num_points, path->num_contours); while(plutovg_path_iterator_has_next(&it)) { switch(plutovg_path_iterator_next(&it, points)) { case PLUTOVG_PATH_COMMAND_MOVE_TO: plutovg_matrix_map_points(matrix, points, points, 1); ft_outline_move_to(outline, points[0].x, points[0].y); break; case PLUTOVG_PATH_COMMAND_LINE_TO: plutovg_matrix_map_points(matrix, points, points, 1); ft_outline_line_to(outline, points[0].x, points[0].y); break; case PLUTOVG_PATH_COMMAND_CUBIC_TO: plutovg_matrix_map_points(matrix, points, points, 3); ft_outline_cubic_to(outline, points[0].x, points[0].y, points[1].x, points[1].y, points[2].x, points[2].y); break; case PLUTOVG_PATH_COMMAND_CLOSE: ft_outline_close(outline); break; } } ft_outline_end(outline); return outline; } static PVG_FT_Outline* ft_outline_convert_dash(const plutovg_path_t* path, const plutovg_matrix_t* matrix, const plutovg_stroke_dash_t* stroke_dash) { if(stroke_dash->array.size == 0) return ft_outline_convert(path, matrix, NULL); plutovg_path_t* dashed = plutovg_path_clone_dashed(path, stroke_dash->offset, stroke_dash->array.data, stroke_dash->array.size); PVG_FT_Outline* outline = ft_outline_convert(dashed, matrix, NULL); plutovg_path_destroy(dashed); return outline; } static PVG_FT_Outline* ft_outline_convert_stroke(const plutovg_path_t* path, const plutovg_matrix_t* matrix, const plutovg_stroke_data_t* stroke_data) { double scale_x = sqrt(matrix->a * matrix->a + matrix->b * matrix->b); double scale_y = sqrt(matrix->c * matrix->c + matrix->d * matrix->d); double scale = hypot(scale_x, scale_y) / PLUTOVG_SQRT2; double width = stroke_data->style.width * scale; PVG_FT_Fixed ftWidth = (PVG_FT_Fixed)(width * 0.5 * (1 << 6)); PVG_FT_Fixed ftMiterLimit = (PVG_FT_Fixed)(stroke_data->style.miter_limit * (1 << 16)); PVG_FT_Stroker_LineCap ftCap; switch(stroke_data->style.cap) { case PLUTOVG_LINE_CAP_SQUARE: ftCap = PVG_FT_STROKER_LINECAP_SQUARE; break; case PLUTOVG_LINE_CAP_ROUND: ftCap = PVG_FT_STROKER_LINECAP_ROUND; break; default: ftCap = PVG_FT_STROKER_LINECAP_BUTT; break; } PVG_FT_Stroker_LineJoin ftJoin; switch(stroke_data->style.join) { case PLUTOVG_LINE_JOIN_BEVEL: ftJoin = PVG_FT_STROKER_LINEJOIN_BEVEL; break; case PLUTOVG_LINE_JOIN_ROUND: ftJoin = PVG_FT_STROKER_LINEJOIN_ROUND; break; default: ftJoin = PVG_FT_STROKER_LINEJOIN_MITER_FIXED; break; } PVG_FT_Stroker stroker; PVG_FT_Stroker_New(&stroker); PVG_FT_Stroker_Set(stroker, ftWidth, ftCap, ftJoin, ftMiterLimit); PVG_FT_Outline* outline = ft_outline_convert_dash(path, matrix, &stroke_data->dash); PVG_FT_Stroker_ParseOutline(stroker, outline); PVG_FT_UInt points; PVG_FT_UInt contours; PVG_FT_Stroker_GetCounts(stroker, &points, &contours); PVG_FT_Outline* stroke_outline = ft_outline_create(points, contours); PVG_FT_Stroker_Export(stroker, stroke_outline); PVG_FT_Stroker_Done(stroker); ft_outline_destroy(outline); return stroke_outline; } static void spans_generation_callback(int count, const PVG_FT_Span* spans, void* user) { plutovg_span_buffer_t* span_buffer = (plutovg_span_buffer_t*)(user); plutovg_array_append_data(span_buffer->spans, spans, count); } void plutovg_rasterize(plutovg_span_buffer_t* span_buffer, const plutovg_path_t* path, const plutovg_matrix_t* matrix, const plutovg_rect_t* clip_rect, const plutovg_stroke_data_t* stroke_data, plutovg_fill_rule_t winding) { PVG_FT_Outline* outline = ft_outline_convert(path, matrix, stroke_data); if(stroke_data) { outline->flags = PVG_FT_OUTLINE_NONE; } else { switch(winding) { case PLUTOVG_FILL_RULE_EVEN_ODD: outline->flags = PVG_FT_OUTLINE_EVEN_ODD_FILL; break; default: outline->flags = PVG_FT_OUTLINE_NONE; break; } } PVG_FT_Raster_Params params; params.flags = PVG_FT_RASTER_FLAG_DIRECT | PVG_FT_RASTER_FLAG_AA; params.gray_spans = spans_generation_callback; params.user = span_buffer; params.source = outline; if(clip_rect) { params.flags |= PVG_FT_RASTER_FLAG_CLIP; params.clip_box.xMin = (PVG_FT_Pos)clip_rect->x; params.clip_box.yMin = (PVG_FT_Pos)clip_rect->y; params.clip_box.xMax = (PVG_FT_Pos)(clip_rect->x + clip_rect->w); params.clip_box.yMax = (PVG_FT_Pos)(clip_rect->y + clip_rect->h); } plutovg_span_buffer_reset(span_buffer); PVG_FT_Raster_Render(¶ms); ft_outline_destroy(outline); } sammycage-plutovg-5695a71/source/plutovg-stb-image-write.h000066400000000000000000002130651510714322500236170ustar00rootroot00000000000000/* stb_image_write - v1.16 - public domain - http://nothings.org/stb writes out PNG/BMP/TGA/JPEG/HDR images to C stdio - Sean Barrett 2010-2015 no warranty implied; use at your own risk Before #including, #define STB_IMAGE_WRITE_IMPLEMENTATION in the file that you want to have the implementation. Will probably not work correctly with strict-aliasing optimizations. ABOUT: This header file is a library for writing images to C stdio or a callback. The PNG output is not optimal; it is 20-50% larger than the file written by a decent optimizing implementation; though providing a custom zlib compress function (see STBIW_ZLIB_COMPRESS) can mitigate that. This library is designed for source code compactness and simplicity, not optimal image file size or run-time performance. BUILDING: You can #define STBIW_ASSERT(x) before the #include to avoid using assert.h. You can #define STBIW_MALLOC(), STBIW_REALLOC(), and STBIW_FREE() to replace malloc,realloc,free. You can #define STBIW_MEMMOVE() to replace memmove() You can #define STBIW_ZLIB_COMPRESS to use a custom zlib-style compress function for PNG compression (instead of the builtin one), it must have the following signature: unsigned char * my_compress(unsigned char *data, int data_len, int *out_len, int quality); The returned data will be freed with STBIW_FREE() (free() by default), so it must be heap allocated with STBIW_MALLOC() (malloc() by default), UNICODE: If compiling for Windows and you wish to use Unicode filenames, compile with #define STBIW_WINDOWS_UTF8 and pass utf8-encoded filenames. Call stbiw_convert_wchar_to_utf8 to convert Windows wchar_t filenames to utf8. USAGE: There are five functions, one for each image file format: int stbi_write_png(char const *filename, int w, int h, int comp, const void *data, int stride_in_bytes); int stbi_write_bmp(char const *filename, int w, int h, int comp, const void *data); int stbi_write_tga(char const *filename, int w, int h, int comp, const void *data); int stbi_write_jpg(char const *filename, int w, int h, int comp, const void *data, int quality); int stbi_write_hdr(char const *filename, int w, int h, int comp, const float *data); void stbi_flip_vertically_on_write(int flag); // flag is non-zero to flip data vertically There are also five equivalent functions that use an arbitrary write function. You are expected to open/close your file-equivalent before and after calling these: int stbi_write_png_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data, int stride_in_bytes); int stbi_write_bmp_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data); int stbi_write_tga_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data); int stbi_write_hdr_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const float *data); int stbi_write_jpg_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data, int quality); where the callback is: void stbi_write_func(void *context, void *data, int size); You can configure it with these global variables: int stbi_write_tga_with_rle; // defaults to true; set to 0 to disable RLE int stbi_write_png_compression_level; // defaults to 8; set to higher for more compression int stbi_write_force_png_filter; // defaults to -1; set to 0..5 to force a filter mode You can define STBI_WRITE_NO_STDIO to disable the file variant of these functions, so the library will not use stdio.h at all. However, this will also disable HDR writing, because it requires stdio for formatted output. Each function returns 0 on failure and non-0 on success. The functions create an image file defined by the parameters. The image is a rectangle of pixels stored from left-to-right, top-to-bottom. Each pixel contains 'comp' channels of data stored interleaved with 8-bits per channel, in the following order: 1=Y, 2=YA, 3=RGB, 4=RGBA. (Y is monochrome color.) The rectangle is 'w' pixels wide and 'h' pixels tall. The *data pointer points to the first byte of the top-left-most pixel. For PNG, "stride_in_bytes" is the distance in bytes from the first byte of a row of pixels to the first byte of the next row of pixels. PNG creates output files with the same number of components as the input. The BMP format expands Y to RGB in the file format and does not output alpha. PNG supports writing rectangles of data even when the bytes storing rows of data are not consecutive in memory (e.g. sub-rectangles of a larger image), by supplying the stride between the beginning of adjacent rows. The other formats do not. (Thus you cannot write a native-format BMP through the BMP writer, both because it is in BGR order and because it may have padding at the end of the line.) PNG allows you to set the deflate compression level by setting the global variable 'stbi_write_png_compression_level' (it defaults to 8). HDR expects linear float data. Since the format is always 32-bit rgb(e) data, alpha (if provided) is discarded, and for monochrome data it is replicated across all three channels. TGA supports RLE or non-RLE compressed data. To use non-RLE-compressed data, set the global variable 'stbi_write_tga_with_rle' to 0. JPEG does ignore alpha channels in input data; quality is between 1 and 100. Higher quality looks better but results in a bigger image. JPEG baseline (no JPEG progressive). CREDITS: Sean Barrett - PNG/BMP/TGA Baldur Karlsson - HDR Jean-Sebastien Guay - TGA monochrome Tim Kelsey - misc enhancements Alan Hickman - TGA RLE Emmanuel Julien - initial file IO callback implementation Jon Olick - original jo_jpeg.cpp code Daniel Gibson - integrate JPEG, allow external zlib Aarni Koskela - allow choosing PNG filter bugfixes: github:Chribba Guillaume Chereau github:jry2 github:romigrou Sergio Gonzalez Jonas Karlsson Filip Wasil Thatcher Ulrich github:poppolopoppo Patrick Boettcher github:xeekworx Cap Petschulat Simon Rodriguez Ivan Tikhonov github:ignotion Adam Schackart Andrew Kensler LICENSE See end of file for license information. */ #ifndef PLUTOVG_STB_IMAGE_WRITE_H #define PLUTOVG_STB_IMAGE_WRITE_H #include // if STB_IMAGE_WRITE_STATIC causes problems, try defining STBIWDEF to 'inline' or 'static inline' #ifndef STBIWDEF #ifdef STB_IMAGE_WRITE_STATIC #define STBIWDEF static #else #ifdef __cplusplus #define STBIWDEF extern "C" #else #define STBIWDEF extern #endif #endif #endif #ifndef STB_IMAGE_WRITE_STATIC // C++ forbids static forward declarations STBIWDEF int stbi_write_tga_with_rle; STBIWDEF int stbi_write_png_compression_level; STBIWDEF int stbi_write_force_png_filter; #endif #ifndef STBI_WRITE_NO_STDIO STBIWDEF int stbi_write_png(char const *filename, int w, int h, int comp, const void *data, int stride_in_bytes); STBIWDEF int stbi_write_bmp(char const *filename, int w, int h, int comp, const void *data); STBIWDEF int stbi_write_tga(char const *filename, int w, int h, int comp, const void *data); STBIWDEF int stbi_write_hdr(char const *filename, int w, int h, int comp, const float *data); STBIWDEF int stbi_write_jpg(char const *filename, int x, int y, int comp, const void *data, int quality); #ifdef STBIW_WINDOWS_UTF8 STBIWDEF int stbiw_convert_wchar_to_utf8(char *buffer, size_t bufferlen, const wchar_t* input); #endif #endif typedef void stbi_write_func(void *context, void *data, int size); STBIWDEF int stbi_write_png_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data, int stride_in_bytes); STBIWDEF int stbi_write_bmp_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data); STBIWDEF int stbi_write_tga_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data); STBIWDEF int stbi_write_hdr_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const float *data); STBIWDEF int stbi_write_jpg_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data, int quality); STBIWDEF void stbi_flip_vertically_on_write(int flip_boolean); #endif//PLUTOVG_STB_IMAGE_WRITE_H #ifdef STB_IMAGE_WRITE_IMPLEMENTATION #ifdef _WIN32 #ifndef _CRT_SECURE_NO_WARNINGS #define _CRT_SECURE_NO_WARNINGS #endif #ifndef _CRT_NONSTDC_NO_DEPRECATE #define _CRT_NONSTDC_NO_DEPRECATE #endif #endif #ifndef STBI_WRITE_NO_STDIO #include #endif // STBI_WRITE_NO_STDIO #include #include #include #include #if defined(STBIW_MALLOC) && defined(STBIW_FREE) && (defined(STBIW_REALLOC) || defined(STBIW_REALLOC_SIZED)) // ok #elif !defined(STBIW_MALLOC) && !defined(STBIW_FREE) && !defined(STBIW_REALLOC) && !defined(STBIW_REALLOC_SIZED) // ok #else #error "Must define all or none of STBIW_MALLOC, STBIW_FREE, and STBIW_REALLOC (or STBIW_REALLOC_SIZED)." #endif #ifndef STBIW_MALLOC #define STBIW_MALLOC(sz) malloc(sz) #define STBIW_REALLOC(p,newsz) realloc(p,newsz) #define STBIW_FREE(p) free(p) #endif #ifndef STBIW_REALLOC_SIZED #define STBIW_REALLOC_SIZED(p,oldsz,newsz) STBIW_REALLOC(p,newsz) #endif #ifndef STBIW_MEMMOVE #define STBIW_MEMMOVE(a,b,sz) memmove(a,b,sz) #endif #ifndef STBIW_ASSERT #include #define STBIW_ASSERT(x) assert(x) #endif #define STBIW_UCHAR(x) (unsigned char) ((x) & 0xff) #ifdef STB_IMAGE_WRITE_STATIC static int stbi_write_png_compression_level = 8; static int stbi_write_tga_with_rle = 1; static int stbi_write_force_png_filter = -1; #else int stbi_write_png_compression_level = 8; int stbi_write_tga_with_rle = 1; int stbi_write_force_png_filter = -1; #endif static int stbi__flip_vertically_on_write = 0; STBIWDEF void stbi_flip_vertically_on_write(int flag) { stbi__flip_vertically_on_write = flag; } typedef struct { stbi_write_func *func; void *context; unsigned char buffer[64]; int buf_used; } stbi__write_context; // initialize a callback-based context static void stbi__start_write_callbacks(stbi__write_context *s, stbi_write_func *c, void *context) { s->func = c; s->context = context; } #ifndef STBI_WRITE_NO_STDIO static void stbi__stdio_write(void *context, void *data, int size) { fwrite(data,1,size,(FILE*) context); } #if defined(_WIN32) && defined(STBIW_WINDOWS_UTF8) #ifdef __cplusplus #define STBIW_EXTERN extern "C" #else #define STBIW_EXTERN extern #endif STBIW_EXTERN __declspec(dllimport) int __stdcall MultiByteToWideChar(unsigned int cp, unsigned long flags, const char *str, int cbmb, wchar_t *widestr, int cchwide); STBIW_EXTERN __declspec(dllimport) int __stdcall WideCharToMultiByte(unsigned int cp, unsigned long flags, const wchar_t *widestr, int cchwide, char *str, int cbmb, const char *defchar, int *used_default); STBIWDEF int stbiw_convert_wchar_to_utf8(char *buffer, size_t bufferlen, const wchar_t* input) { return WideCharToMultiByte(65001 /* UTF8 */, 0, input, -1, buffer, (int) bufferlen, NULL, NULL); } #endif static FILE *stbiw__fopen(char const *filename, char const *mode) { FILE *f; #if defined(_WIN32) && defined(STBIW_WINDOWS_UTF8) wchar_t wMode[64]; wchar_t wFilename[1024]; if (0 == MultiByteToWideChar(65001 /* UTF8 */, 0, filename, -1, wFilename, sizeof(wFilename)/sizeof(*wFilename))) return 0; if (0 == MultiByteToWideChar(65001 /* UTF8 */, 0, mode, -1, wMode, sizeof(wMode)/sizeof(*wMode))) return 0; #if defined(_MSC_VER) && _MSC_VER >= 1400 if (0 != _wfopen_s(&f, wFilename, wMode)) f = 0; #else f = _wfopen(wFilename, wMode); #endif #elif defined(_MSC_VER) && _MSC_VER >= 1400 if (0 != fopen_s(&f, filename, mode)) f=0; #else f = fopen(filename, mode); #endif return f; } static int stbi__start_write_file(stbi__write_context *s, const char *filename) { FILE *f = stbiw__fopen(filename, "wb"); stbi__start_write_callbacks(s, stbi__stdio_write, (void *) f); return f != NULL; } static void stbi__end_write_file(stbi__write_context *s) { fclose((FILE *)s->context); } #endif // !STBI_WRITE_NO_STDIO typedef unsigned int stbiw_uint32; typedef int stb_image_write_test[sizeof(stbiw_uint32)==4 ? 1 : -1]; static void stbiw__writefv(stbi__write_context *s, const char *fmt, va_list v) { while (*fmt) { switch (*fmt++) { case ' ': break; case '1': { unsigned char x = STBIW_UCHAR(va_arg(v, int)); s->func(s->context,&x,1); break; } case '2': { int x = va_arg(v,int); unsigned char b[2]; b[0] = STBIW_UCHAR(x); b[1] = STBIW_UCHAR(x>>8); s->func(s->context,b,2); break; } case '4': { stbiw_uint32 x = va_arg(v,int); unsigned char b[4]; b[0]=STBIW_UCHAR(x); b[1]=STBIW_UCHAR(x>>8); b[2]=STBIW_UCHAR(x>>16); b[3]=STBIW_UCHAR(x>>24); s->func(s->context,b,4); break; } default: STBIW_ASSERT(0); return; } } } static void stbiw__writef(stbi__write_context *s, const char *fmt, ...) { va_list v; va_start(v, fmt); stbiw__writefv(s, fmt, v); va_end(v); } static void stbiw__write_flush(stbi__write_context *s) { if (s->buf_used) { s->func(s->context, &s->buffer, s->buf_used); s->buf_used = 0; } } static void stbiw__putc(stbi__write_context *s, unsigned char c) { s->func(s->context, &c, 1); } static void stbiw__write1(stbi__write_context *s, unsigned char a) { if ((size_t)s->buf_used + 1 > sizeof(s->buffer)) stbiw__write_flush(s); s->buffer[s->buf_used++] = a; } static void stbiw__write3(stbi__write_context *s, unsigned char a, unsigned char b, unsigned char c) { int n; if ((size_t)s->buf_used + 3 > sizeof(s->buffer)) stbiw__write_flush(s); n = s->buf_used; s->buf_used = n+3; s->buffer[n+0] = a; s->buffer[n+1] = b; s->buffer[n+2] = c; } static void stbiw__write_pixel(stbi__write_context *s, int rgb_dir, int comp, int write_alpha, int expand_mono, unsigned char *d) { unsigned char bg[3] = { 255, 0, 255}, px[3]; int k; if (write_alpha < 0) stbiw__write1(s, d[comp - 1]); switch (comp) { case 2: // 2 pixels = mono + alpha, alpha is written separately, so same as 1-channel case case 1: if (expand_mono) stbiw__write3(s, d[0], d[0], d[0]); // monochrome bmp else stbiw__write1(s, d[0]); // monochrome TGA break; case 4: if (!write_alpha) { // composite against pink background for (k = 0; k < 3; ++k) px[k] = bg[k] + ((d[k] - bg[k]) * d[3]) / 255; stbiw__write3(s, px[1 - rgb_dir], px[1], px[1 + rgb_dir]); break; } /* FALLTHROUGH */ case 3: stbiw__write3(s, d[1 - rgb_dir], d[1], d[1 + rgb_dir]); break; } if (write_alpha > 0) stbiw__write1(s, d[comp - 1]); } static void stbiw__write_pixels(stbi__write_context *s, int rgb_dir, int vdir, int x, int y, int comp, void *data, int write_alpha, int scanline_pad, int expand_mono) { stbiw_uint32 zero = 0; int i,j, j_end; if (y <= 0) return; if (stbi__flip_vertically_on_write) vdir *= -1; if (vdir < 0) { j_end = -1; j = y-1; } else { j_end = y; j = 0; } for (; j != j_end; j += vdir) { for (i=0; i < x; ++i) { unsigned char *d = (unsigned char *) data + (j*x+i)*comp; stbiw__write_pixel(s, rgb_dir, comp, write_alpha, expand_mono, d); } stbiw__write_flush(s); s->func(s->context, &zero, scanline_pad); } } static int stbiw__outfile(stbi__write_context *s, int rgb_dir, int vdir, int x, int y, int comp, int expand_mono, void *data, int alpha, int pad, const char *fmt, ...) { if (y < 0 || x < 0) { return 0; } else { va_list v; va_start(v, fmt); stbiw__writefv(s, fmt, v); va_end(v); stbiw__write_pixels(s,rgb_dir,vdir,x,y,comp,data,alpha,pad, expand_mono); return 1; } } static int stbi_write_bmp_core(stbi__write_context *s, int x, int y, int comp, const void *data) { if (comp != 4) { // write RGB bitmap int pad = (-x*3) & 3; return stbiw__outfile(s,-1,-1,x,y,comp,1,(void *) data,0,pad, "11 4 22 4" "4 44 22 444444", 'B', 'M', 14+40+(x*3+pad)*y, 0,0, 14+40, // file header 40, x,y, 1,24, 0,0,0,0,0,0); // bitmap header } else { // RGBA bitmaps need a v4 header // use BI_BITFIELDS mode with 32bpp and alpha mask // (straight BI_RGB with alpha mask doesn't work in most readers) return stbiw__outfile(s,-1,-1,x,y,comp,1,(void *)data,1,0, "11 4 22 4" "4 44 22 444444 4444 4 444 444 444 444", 'B', 'M', 14+108+x*y*4, 0, 0, 14+108, // file header 108, x,y, 1,32, 3,0,0,0,0,0, 0xff0000,0xff00,0xff,0xff000000u, 0, 0,0,0, 0,0,0, 0,0,0, 0,0,0); // bitmap V4 header } } STBIWDEF int stbi_write_bmp_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data) { stbi__write_context s = { 0 }; stbi__start_write_callbacks(&s, func, context); return stbi_write_bmp_core(&s, x, y, comp, data); } #ifndef STBI_WRITE_NO_STDIO STBIWDEF int stbi_write_bmp(char const *filename, int x, int y, int comp, const void *data) { stbi__write_context s = { 0 }; if (stbi__start_write_file(&s,filename)) { int r = stbi_write_bmp_core(&s, x, y, comp, data); stbi__end_write_file(&s); return r; } else return 0; } #endif //!STBI_WRITE_NO_STDIO static int stbi_write_tga_core(stbi__write_context *s, int x, int y, int comp, void *data) { int has_alpha = (comp == 2 || comp == 4); int colorbytes = has_alpha ? comp-1 : comp; int format = colorbytes < 2 ? 3 : 2; // 3 color channels (RGB/RGBA) = 2, 1 color channel (Y/YA) = 3 if (y < 0 || x < 0) return 0; if (!stbi_write_tga_with_rle) { return stbiw__outfile(s, -1, -1, x, y, comp, 0, (void *) data, has_alpha, 0, "111 221 2222 11", 0, 0, format, 0, 0, 0, 0, 0, x, y, (colorbytes + has_alpha) * 8, has_alpha * 8); } else { int i,j,k; int jend, jdir; stbiw__writef(s, "111 221 2222 11", 0,0,format+8, 0,0,0, 0,0,x,y, (colorbytes + has_alpha) * 8, has_alpha * 8); if (stbi__flip_vertically_on_write) { j = 0; jend = y; jdir = 1; } else { j = y-1; jend = -1; jdir = -1; } for (; j != jend; j += jdir) { unsigned char *row = (unsigned char *) data + j * x * comp; int len; for (i = 0; i < x; i += len) { unsigned char *begin = row + i * comp; int diff = 1; len = 1; if (i < x - 1) { ++len; diff = memcmp(begin, row + (i + 1) * comp, comp); if (diff) { const unsigned char *prev = begin; for (k = i + 2; k < x && len < 128; ++k) { if (memcmp(prev, row + k * comp, comp)) { prev += comp; ++len; } else { --len; break; } } } else { for (k = i + 2; k < x && len < 128; ++k) { if (!memcmp(begin, row + k * comp, comp)) { ++len; } else { break; } } } } if (diff) { unsigned char header = STBIW_UCHAR(len - 1); stbiw__write1(s, header); for (k = 0; k < len; ++k) { stbiw__write_pixel(s, -1, comp, has_alpha, 0, begin + k * comp); } } else { unsigned char header = STBIW_UCHAR(len - 129); stbiw__write1(s, header); stbiw__write_pixel(s, -1, comp, has_alpha, 0, begin); } } } stbiw__write_flush(s); } return 1; } STBIWDEF int stbi_write_tga_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data) { stbi__write_context s = { 0 }; stbi__start_write_callbacks(&s, func, context); return stbi_write_tga_core(&s, x, y, comp, (void *) data); } #ifndef STBI_WRITE_NO_STDIO STBIWDEF int stbi_write_tga(char const *filename, int x, int y, int comp, const void *data) { stbi__write_context s = { 0 }; if (stbi__start_write_file(&s,filename)) { int r = stbi_write_tga_core(&s, x, y, comp, (void *) data); stbi__end_write_file(&s); return r; } else return 0; } #endif // ************************************************************************************************* // Radiance RGBE HDR writer // by Baldur Karlsson #define stbiw__max(a, b) ((a) > (b) ? (a) : (b)) #ifndef STBI_WRITE_NO_STDIO static void stbiw__linear_to_rgbe(unsigned char *rgbe, float *linear) { int exponent; float maxcomp = stbiw__max(linear[0], stbiw__max(linear[1], linear[2])); if (maxcomp < 1e-32f) { rgbe[0] = rgbe[1] = rgbe[2] = rgbe[3] = 0; } else { float normalize = (float) frexp(maxcomp, &exponent) * 256.0f/maxcomp; rgbe[0] = (unsigned char)(linear[0] * normalize); rgbe[1] = (unsigned char)(linear[1] * normalize); rgbe[2] = (unsigned char)(linear[2] * normalize); rgbe[3] = (unsigned char)(exponent + 128); } } static void stbiw__write_run_data(stbi__write_context *s, int length, unsigned char databyte) { unsigned char lengthbyte = STBIW_UCHAR(length+128); STBIW_ASSERT(length+128 <= 255); s->func(s->context, &lengthbyte, 1); s->func(s->context, &databyte, 1); } static void stbiw__write_dump_data(stbi__write_context *s, int length, unsigned char *data) { unsigned char lengthbyte = STBIW_UCHAR(length); STBIW_ASSERT(length <= 128); // inconsistent with spec but consistent with official code s->func(s->context, &lengthbyte, 1); s->func(s->context, data, length); } static void stbiw__write_hdr_scanline(stbi__write_context *s, int width, int ncomp, unsigned char *scratch, float *scanline) { unsigned char scanlineheader[4] = { 2, 2, 0, 0 }; unsigned char rgbe[4]; float linear[3]; int x; scanlineheader[2] = (width&0xff00)>>8; scanlineheader[3] = (width&0x00ff); /* skip RLE for images too small or large */ if (width < 8 || width >= 32768) { for (x=0; x < width; x++) { switch (ncomp) { case 4: /* fallthrough */ case 3: linear[2] = scanline[x*ncomp + 2]; linear[1] = scanline[x*ncomp + 1]; linear[0] = scanline[x*ncomp + 0]; break; default: linear[0] = linear[1] = linear[2] = scanline[x*ncomp + 0]; break; } stbiw__linear_to_rgbe(rgbe, linear); s->func(s->context, rgbe, 4); } } else { int c,r; /* encode into scratch buffer */ for (x=0; x < width; x++) { switch(ncomp) { case 4: /* fallthrough */ case 3: linear[2] = scanline[x*ncomp + 2]; linear[1] = scanline[x*ncomp + 1]; linear[0] = scanline[x*ncomp + 0]; break; default: linear[0] = linear[1] = linear[2] = scanline[x*ncomp + 0]; break; } stbiw__linear_to_rgbe(rgbe, linear); scratch[x + width*0] = rgbe[0]; scratch[x + width*1] = rgbe[1]; scratch[x + width*2] = rgbe[2]; scratch[x + width*3] = rgbe[3]; } s->func(s->context, scanlineheader, 4); /* RLE each component separately */ for (c=0; c < 4; c++) { unsigned char *comp = &scratch[width*c]; x = 0; while (x < width) { // find first run r = x; while (r+2 < width) { if (comp[r] == comp[r+1] && comp[r] == comp[r+2]) break; ++r; } if (r+2 >= width) r = width; // dump up to first run while (x < r) { int len = r-x; if (len > 128) len = 128; stbiw__write_dump_data(s, len, &comp[x]); x += len; } // if there's a run, output it if (r+2 < width) { // same test as what we break out of in search loop, so only true if we break'd // find next byte after run while (r < width && comp[r] == comp[x]) ++r; // output run up to r while (x < r) { int len = r-x; if (len > 127) len = 127; stbiw__write_run_data(s, len, comp[x]); x += len; } } } } } } static int stbi_write_hdr_core(stbi__write_context *s, int x, int y, int comp, float *data) { if (y <= 0 || x <= 0 || data == NULL) return 0; else { // Each component is stored separately. Allocate scratch space for full output scanline. unsigned char *scratch = (unsigned char *) STBIW_MALLOC(x*4); int i, len; char buffer[128]; char header[] = "#?RADIANCE\n# Written by stb_image_write.h\nFORMAT=32-bit_rle_rgbe\n"; s->func(s->context, header, sizeof(header)-1); #ifdef __STDC_LIB_EXT1__ len = sprintf_s(buffer, sizeof(buffer), "EXPOSURE= 1.0000000000000\n\n-Y %d +X %d\n", y, x); #else len = sprintf(buffer, "EXPOSURE= 1.0000000000000\n\n-Y %d +X %d\n", y, x); #endif s->func(s->context, buffer, len); for(i=0; i < y; i++) stbiw__write_hdr_scanline(s, x, comp, scratch, data + comp*x*(stbi__flip_vertically_on_write ? y-1-i : i)); STBIW_FREE(scratch); return 1; } } STBIWDEF int stbi_write_hdr_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const float *data) { stbi__write_context s = { 0 }; stbi__start_write_callbacks(&s, func, context); return stbi_write_hdr_core(&s, x, y, comp, (float *) data); } STBIWDEF int stbi_write_hdr(char const *filename, int x, int y, int comp, const float *data) { stbi__write_context s = { 0 }; if (stbi__start_write_file(&s,filename)) { int r = stbi_write_hdr_core(&s, x, y, comp, (float *) data); stbi__end_write_file(&s); return r; } else return 0; } #endif // STBI_WRITE_NO_STDIO ////////////////////////////////////////////////////////////////////////////// // // PNG writer // #ifndef STBIW_ZLIB_COMPRESS // stretchy buffer; stbiw__sbpush() == vector<>::push_back() -- stbiw__sbcount() == vector<>::size() #define stbiw__sbraw(a) ((int *) (void *) (a) - 2) #define stbiw__sbm(a) stbiw__sbraw(a)[0] #define stbiw__sbn(a) stbiw__sbraw(a)[1] #define stbiw__sbneedgrow(a,n) ((a)==0 || stbiw__sbn(a)+n >= stbiw__sbm(a)) #define stbiw__sbmaybegrow(a,n) (stbiw__sbneedgrow(a,(n)) ? stbiw__sbgrow(a,n) : 0) #define stbiw__sbgrow(a,n) stbiw__sbgrowf((void **) &(a), (n), sizeof(*(a))) #define stbiw__sbpush(a, v) (stbiw__sbmaybegrow(a,1), (a)[stbiw__sbn(a)++] = (v)) #define stbiw__sbcount(a) ((a) ? stbiw__sbn(a) : 0) #define stbiw__sbfree(a) ((a) ? STBIW_FREE(stbiw__sbraw(a)),0 : 0) static void *stbiw__sbgrowf(void **arr, int increment, int itemsize) { int m = *arr ? 2*stbiw__sbm(*arr)+increment : increment+1; void *p = STBIW_REALLOC_SIZED(*arr ? stbiw__sbraw(*arr) : 0, *arr ? (stbiw__sbm(*arr)*itemsize + sizeof(int)*2) : 0, itemsize * m + sizeof(int)*2); STBIW_ASSERT(p); if (p) { if (!*arr) ((int *) p)[1] = 0; *arr = (void *) ((int *) p + 2); stbiw__sbm(*arr) = m; } return *arr; } static unsigned char *stbiw__zlib_flushf(unsigned char *data, unsigned int *bitbuffer, int *bitcount) { while (*bitcount >= 8) { stbiw__sbpush(data, STBIW_UCHAR(*bitbuffer)); *bitbuffer >>= 8; *bitcount -= 8; } return data; } static int stbiw__zlib_bitrev(int code, int codebits) { int res=0; while (codebits--) { res = (res << 1) | (code & 1); code >>= 1; } return res; } static unsigned int stbiw__zlib_countm(unsigned char *a, unsigned char *b, int limit) { int i; for (i=0; i < limit && i < 258; ++i) if (a[i] != b[i]) break; return i; } static unsigned int stbiw__zhash(unsigned char *data) { stbiw_uint32 hash = data[0] + (data[1] << 8) + (data[2] << 16); hash ^= hash << 3; hash += hash >> 5; hash ^= hash << 4; hash += hash >> 17; hash ^= hash << 25; hash += hash >> 6; return hash; } #define stbiw__zlib_flush() (out = stbiw__zlib_flushf(out, &bitbuf, &bitcount)) #define stbiw__zlib_add(code,codebits) \ (bitbuf |= (code) << bitcount, bitcount += (codebits), stbiw__zlib_flush()) #define stbiw__zlib_huffa(b,c) stbiw__zlib_add(stbiw__zlib_bitrev(b,c),c) // default huffman tables #define stbiw__zlib_huff1(n) stbiw__zlib_huffa(0x30 + (n), 8) #define stbiw__zlib_huff2(n) stbiw__zlib_huffa(0x190 + (n)-144, 9) #define stbiw__zlib_huff3(n) stbiw__zlib_huffa(0 + (n)-256,7) #define stbiw__zlib_huff4(n) stbiw__zlib_huffa(0xc0 + (n)-280,8) #define stbiw__zlib_huff(n) ((n) <= 143 ? stbiw__zlib_huff1(n) : (n) <= 255 ? stbiw__zlib_huff2(n) : (n) <= 279 ? stbiw__zlib_huff3(n) : stbiw__zlib_huff4(n)) #define stbiw__zlib_huffb(n) ((n) <= 143 ? stbiw__zlib_huff1(n) : stbiw__zlib_huff2(n)) #define stbiw__ZHASH 16384 #endif // STBIW_ZLIB_COMPRESS STBIWDEF unsigned char * stbi_zlib_compress(unsigned char *data, int data_len, int *out_len, int quality) { #ifdef STBIW_ZLIB_COMPRESS // user provided a zlib compress implementation, use that return STBIW_ZLIB_COMPRESS(data, data_len, out_len, quality); #else // use builtin static unsigned short lengthc[] = { 3,4,5,6,7,8,9,10,11,13,15,17,19,23,27,31,35,43,51,59,67,83,99,115,131,163,195,227,258, 259 }; static unsigned char lengtheb[]= { 0,0,0,0,0,0,0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0 }; static unsigned short distc[] = { 1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193,257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577, 32768 }; static unsigned char disteb[] = { 0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13 }; unsigned int bitbuf=0; int i,j, bitcount=0; unsigned char *out = NULL; unsigned char ***hash_table = (unsigned char***) STBIW_MALLOC(stbiw__ZHASH * sizeof(unsigned char**)); if (hash_table == NULL) return NULL; if (quality < 5) quality = 5; stbiw__sbpush(out, 0x78); // DEFLATE 32K window stbiw__sbpush(out, 0x5e); // FLEVEL = 1 stbiw__zlib_add(1,1); // BFINAL = 1 stbiw__zlib_add(1,2); // BTYPE = 1 -- fixed huffman for (i=0; i < stbiw__ZHASH; ++i) hash_table[i] = NULL; i=0; while (i < data_len-3) { // hash next 3 bytes of data to be compressed int h = stbiw__zhash(data+i)&(stbiw__ZHASH-1), best=3; unsigned char *bestloc = 0; unsigned char **hlist = hash_table[h]; int n = stbiw__sbcount(hlist); for (j=0; j < n; ++j) { if (hlist[j]-data > i-32768) { // if entry lies within window int d = stbiw__zlib_countm(hlist[j], data+i, data_len-i); if (d >= best) { best=d; bestloc=hlist[j]; } } } // when hash table entry is too long, delete half the entries if (hash_table[h] && stbiw__sbn(hash_table[h]) == 2*quality) { STBIW_MEMMOVE(hash_table[h], hash_table[h]+quality, sizeof(hash_table[h][0])*quality); stbiw__sbn(hash_table[h]) = quality; } stbiw__sbpush(hash_table[h],data+i); if (bestloc) { // "lazy matching" - check match at *next* byte, and if it's better, do cur byte as literal h = stbiw__zhash(data+i+1)&(stbiw__ZHASH-1); hlist = hash_table[h]; n = stbiw__sbcount(hlist); for (j=0; j < n; ++j) { if (hlist[j]-data > i-32767) { int e = stbiw__zlib_countm(hlist[j], data+i+1, data_len-i-1); if (e > best) { // if next match is better, bail on current match bestloc = NULL; break; } } } } if (bestloc) { int d = (int) (data+i - bestloc); // distance back STBIW_ASSERT(d <= 32767 && best <= 258); for (j=0; best > lengthc[j+1]-1; ++j); stbiw__zlib_huff(j+257); if (lengtheb[j]) stbiw__zlib_add(best - lengthc[j], lengtheb[j]); for (j=0; d > distc[j+1]-1; ++j); stbiw__zlib_add(stbiw__zlib_bitrev(j,5),5); if (disteb[j]) stbiw__zlib_add(d - distc[j], disteb[j]); i += best; } else { stbiw__zlib_huffb(data[i]); ++i; } } // write out final bytes for (;i < data_len; ++i) stbiw__zlib_huffb(data[i]); stbiw__zlib_huff(256); // end of block // pad with 0 bits to byte boundary while (bitcount) stbiw__zlib_add(0,1); for (i=0; i < stbiw__ZHASH; ++i) (void) stbiw__sbfree(hash_table[i]); STBIW_FREE(hash_table); // store uncompressed instead if compression was worse if (stbiw__sbn(out) > data_len + 2 + ((data_len+32766)/32767)*5) { stbiw__sbn(out) = 2; // truncate to DEFLATE 32K window and FLEVEL = 1 for (j = 0; j < data_len;) { int blocklen = data_len - j; if (blocklen > 32767) blocklen = 32767; stbiw__sbpush(out, data_len - j == blocklen); // BFINAL = ?, BTYPE = 0 -- no compression stbiw__sbpush(out, STBIW_UCHAR(blocklen)); // LEN stbiw__sbpush(out, STBIW_UCHAR(blocklen >> 8)); stbiw__sbpush(out, STBIW_UCHAR(~blocklen)); // NLEN stbiw__sbpush(out, STBIW_UCHAR(~blocklen >> 8)); memcpy(out+stbiw__sbn(out), data+j, blocklen); stbiw__sbn(out) += blocklen; j += blocklen; } } { // compute adler32 on input unsigned int s1=1, s2=0; int blocklen = (int) (data_len % 5552); j=0; while (j < data_len) { for (i=0; i < blocklen; ++i) { s1 += data[j+i]; s2 += s1; } s1 %= 65521; s2 %= 65521; j += blocklen; blocklen = 5552; } stbiw__sbpush(out, STBIW_UCHAR(s2 >> 8)); stbiw__sbpush(out, STBIW_UCHAR(s2)); stbiw__sbpush(out, STBIW_UCHAR(s1 >> 8)); stbiw__sbpush(out, STBIW_UCHAR(s1)); } *out_len = stbiw__sbn(out); // make returned pointer freeable STBIW_MEMMOVE(stbiw__sbraw(out), out, *out_len); return (unsigned char *) stbiw__sbraw(out); #endif // STBIW_ZLIB_COMPRESS } static unsigned int stbiw__crc32(unsigned char *buffer, int len) { #ifdef STBIW_CRC32 return STBIW_CRC32(buffer, len); #else static unsigned int crc_table[256] = { 0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419, 0x706AF48F, 0xE963A535, 0x9E6495A3, 0x0eDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988, 0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91, 0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE, 0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7, 0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, 0x14015C4F, 0x63066CD9, 0xFA0F3D63, 0x8D080DF5, 0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172, 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B, 0x35B5A8FA, 0x42B2986C, 0xDBBBC9D6, 0xACBCF940, 0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59, 0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116, 0x21B4F4B5, 0x56B3C423, 0xCFBA9599, 0xB8BDA50F, 0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924, 0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D, 0x76DC4190, 0x01DB7106, 0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433, 0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818, 0x7F6A0DBB, 0x086D3D2D, 0x91646C97, 0xE6635C01, 0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E, 0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457, 0x65B0D9C6, 0x12B7E950, 0x8BBEB8EA, 0xFCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65, 0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2, 0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB, 0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0, 0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9, 0x5005713C, 0x270241AA, 0xBE0B1010, 0xC90C2086, 0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F, 0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17, 0x2EB40D81, 0xB7BD5C3B, 0xC0BA6CAD, 0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A, 0xEAD54739, 0x9DD277AF, 0x04DB2615, 0x73DC1683, 0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8, 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1, 0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE, 0xF762575D, 0x806567CB, 0x196C3671, 0x6E6B06E7, 0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC, 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5, 0xD6D6A3E8, 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252, 0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B, 0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60, 0xDF60EFC3, 0xA867DF55, 0x316E8EEF, 0x4669BE79, 0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236, 0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F, 0xC5BA3BBE, 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7, 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D, 0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A, 0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713, 0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38, 0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21, 0x86D3D2D4, 0xF1D4E242, 0x68DDB3F8, 0x1FDA836E, 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777, 0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C, 0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45, 0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2, 0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB, 0xAED16A4A, 0xD9D65ADC, 0x40DF0B66, 0x37D83BF0, 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9, 0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, 0xBAD03605, 0xCDD70693, 0x54DE5729, 0x23D967BF, 0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94, 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D }; unsigned int crc = ~0u; int i; for (i=0; i < len; ++i) crc = (crc >> 8) ^ crc_table[buffer[i] ^ (crc & 0xff)]; return ~crc; #endif } #define stbiw__wpng4(o,a,b,c,d) ((o)[0]=STBIW_UCHAR(a),(o)[1]=STBIW_UCHAR(b),(o)[2]=STBIW_UCHAR(c),(o)[3]=STBIW_UCHAR(d),(o)+=4) #define stbiw__wp32(data,v) stbiw__wpng4(data, (v)>>24,(v)>>16,(v)>>8,(v)); #define stbiw__wptag(data,s) stbiw__wpng4(data, s[0],s[1],s[2],s[3]) static void stbiw__wpcrc(unsigned char **data, int len) { unsigned int crc = stbiw__crc32(*data - len - 4, len+4); stbiw__wp32(*data, crc); } static unsigned char stbiw__paeth(int a, int b, int c) { int p = a + b - c, pa = abs(p-a), pb = abs(p-b), pc = abs(p-c); if (pa <= pb && pa <= pc) return STBIW_UCHAR(a); if (pb <= pc) return STBIW_UCHAR(b); return STBIW_UCHAR(c); } // @OPTIMIZE: provide an option that always forces left-predict or paeth predict static void stbiw__encode_png_line(unsigned char *pixels, int stride_bytes, int width, int height, int y, int n, int filter_type, signed char *line_buffer) { static int mapping[] = { 0,1,2,3,4 }; static int firstmap[] = { 0,1,0,5,6 }; int *mymap = (y != 0) ? mapping : firstmap; int i; int type = mymap[filter_type]; unsigned char *z = pixels + stride_bytes * (stbi__flip_vertically_on_write ? height-1-y : y); int signed_stride = stbi__flip_vertically_on_write ? -stride_bytes : stride_bytes; if (type==0) { memcpy(line_buffer, z, width*n); return; } // first loop isn't optimized since it's just one pixel for (i = 0; i < n; ++i) { switch (type) { case 1: line_buffer[i] = z[i]; break; case 2: line_buffer[i] = z[i] - z[i-signed_stride]; break; case 3: line_buffer[i] = z[i] - (z[i-signed_stride]>>1); break; case 4: line_buffer[i] = (signed char) (z[i] - stbiw__paeth(0,z[i-signed_stride],0)); break; case 5: line_buffer[i] = z[i]; break; case 6: line_buffer[i] = z[i]; break; } } switch (type) { case 1: for (i=n; i < width*n; ++i) line_buffer[i] = z[i] - z[i-n]; break; case 2: for (i=n; i < width*n; ++i) line_buffer[i] = z[i] - z[i-signed_stride]; break; case 3: for (i=n; i < width*n; ++i) line_buffer[i] = z[i] - ((z[i-n] + z[i-signed_stride])>>1); break; case 4: for (i=n; i < width*n; ++i) line_buffer[i] = z[i] - stbiw__paeth(z[i-n], z[i-signed_stride], z[i-signed_stride-n]); break; case 5: for (i=n; i < width*n; ++i) line_buffer[i] = z[i] - (z[i-n]>>1); break; case 6: for (i=n; i < width*n; ++i) line_buffer[i] = z[i] - stbiw__paeth(z[i-n], 0,0); break; } } STBIWDEF unsigned char *stbi_write_png_to_mem(const unsigned char *pixels, int stride_bytes, int x, int y, int n, int *out_len) { int force_filter = stbi_write_force_png_filter; int ctype[5] = { -1, 0, 4, 2, 6 }; unsigned char sig[8] = { 137,80,78,71,13,10,26,10 }; unsigned char *out,*o, *filt, *zlib; signed char *line_buffer; int j,zlen; if (stride_bytes == 0) stride_bytes = x * n; if (force_filter >= 5) { force_filter = -1; } filt = (unsigned char *) STBIW_MALLOC((x*n+1) * y); if (!filt) return 0; line_buffer = (signed char *) STBIW_MALLOC(x * n); if (!line_buffer) { STBIW_FREE(filt); return 0; } for (j=0; j < y; ++j) { int filter_type; if (force_filter > -1) { filter_type = force_filter; stbiw__encode_png_line((unsigned char*)(pixels), stride_bytes, x, y, j, n, force_filter, line_buffer); } else { // Estimate the best filter by running through all of them: int best_filter = 0, best_filter_val = 0x7fffffff, est, i; for (filter_type = 0; filter_type < 5; filter_type++) { stbiw__encode_png_line((unsigned char*)(pixels), stride_bytes, x, y, j, n, filter_type, line_buffer); // Estimate the entropy of the line using this filter; the less, the better. est = 0; for (i = 0; i < x*n; ++i) { est += abs((signed char) line_buffer[i]); } if (est < best_filter_val) { best_filter_val = est; best_filter = filter_type; } } if (filter_type != best_filter) { // If the last iteration already got us the best filter, don't redo it stbiw__encode_png_line((unsigned char*)(pixels), stride_bytes, x, y, j, n, best_filter, line_buffer); filter_type = best_filter; } } // when we get here, filter_type contains the filter type, and line_buffer contains the data filt[j*(x*n+1)] = (unsigned char) filter_type; STBIW_MEMMOVE(filt+j*(x*n+1)+1, line_buffer, x*n); } STBIW_FREE(line_buffer); zlib = stbi_zlib_compress(filt, y*( x*n+1), &zlen, stbi_write_png_compression_level); STBIW_FREE(filt); if (!zlib) return 0; // each tag requires 12 bytes of overhead out = (unsigned char *) STBIW_MALLOC(8 + 12+13 + 12+zlen + 12); if (!out) return 0; *out_len = 8 + 12+13 + 12+zlen + 12; o=out; STBIW_MEMMOVE(o,sig,8); o+= 8; stbiw__wp32(o, 13); // header length stbiw__wptag(o, "IHDR"); stbiw__wp32(o, x); stbiw__wp32(o, y); *o++ = 8; *o++ = STBIW_UCHAR(ctype[n]); *o++ = 0; *o++ = 0; *o++ = 0; stbiw__wpcrc(&o,13); stbiw__wp32(o, zlen); stbiw__wptag(o, "IDAT"); STBIW_MEMMOVE(o, zlib, zlen); o += zlen; STBIW_FREE(zlib); stbiw__wpcrc(&o, zlen); stbiw__wp32(o,0); stbiw__wptag(o, "IEND"); stbiw__wpcrc(&o,0); STBIW_ASSERT(o == out + *out_len); return out; } #ifndef STBI_WRITE_NO_STDIO STBIWDEF int stbi_write_png(char const *filename, int x, int y, int comp, const void *data, int stride_bytes) { FILE *f; int len; unsigned char *png = stbi_write_png_to_mem((const unsigned char *) data, stride_bytes, x, y, comp, &len); if (png == NULL) return 0; f = stbiw__fopen(filename, "wb"); if (!f) { STBIW_FREE(png); return 0; } fwrite(png, 1, len, f); fclose(f); STBIW_FREE(png); return 1; } #endif STBIWDEF int stbi_write_png_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data, int stride_bytes) { int len; unsigned char *png = stbi_write_png_to_mem((const unsigned char *) data, stride_bytes, x, y, comp, &len); if (png == NULL) return 0; func(context, png, len); STBIW_FREE(png); return 1; } /* *************************************************************************** * * JPEG writer * * This is based on Jon Olick's jo_jpeg.cpp: * public domain Simple, Minimalistic JPEG writer - http://www.jonolick.com/code.html */ static const unsigned char stbiw__jpg_ZigZag[] = { 0,1,5,6,14,15,27,28,2,4,7,13,16,26,29,42,3,8,12,17,25,30,41,43,9,11,18, 24,31,40,44,53,10,19,23,32,39,45,52,54,20,22,33,38,46,51,55,60,21,34,37,47,50,56,59,61,35,36,48,49,57,58,62,63 }; static void stbiw__jpg_writeBits(stbi__write_context *s, int *bitBufP, int *bitCntP, const unsigned short *bs) { int bitBuf = *bitBufP, bitCnt = *bitCntP; bitCnt += bs[1]; bitBuf |= bs[0] << (24 - bitCnt); while(bitCnt >= 8) { unsigned char c = (bitBuf >> 16) & 255; stbiw__putc(s, c); if(c == 255) { stbiw__putc(s, 0); } bitBuf <<= 8; bitCnt -= 8; } *bitBufP = bitBuf; *bitCntP = bitCnt; } static void stbiw__jpg_DCT(float *d0p, float *d1p, float *d2p, float *d3p, float *d4p, float *d5p, float *d6p, float *d7p) { float d0 = *d0p, d1 = *d1p, d2 = *d2p, d3 = *d3p, d4 = *d4p, d5 = *d5p, d6 = *d6p, d7 = *d7p; float z1, z2, z3, z4, z5, z11, z13; float tmp0 = d0 + d7; float tmp7 = d0 - d7; float tmp1 = d1 + d6; float tmp6 = d1 - d6; float tmp2 = d2 + d5; float tmp5 = d2 - d5; float tmp3 = d3 + d4; float tmp4 = d3 - d4; // Even part float tmp10 = tmp0 + tmp3; // phase 2 float tmp13 = tmp0 - tmp3; float tmp11 = tmp1 + tmp2; float tmp12 = tmp1 - tmp2; d0 = tmp10 + tmp11; // phase 3 d4 = tmp10 - tmp11; z1 = (tmp12 + tmp13) * 0.707106781f; // c4 d2 = tmp13 + z1; // phase 5 d6 = tmp13 - z1; // Odd part tmp10 = tmp4 + tmp5; // phase 2 tmp11 = tmp5 + tmp6; tmp12 = tmp6 + tmp7; // The rotator is modified from fig 4-8 to avoid extra negations. z5 = (tmp10 - tmp12) * 0.382683433f; // c6 z2 = tmp10 * 0.541196100f + z5; // c2-c6 z4 = tmp12 * 1.306562965f + z5; // c2+c6 z3 = tmp11 * 0.707106781f; // c4 z11 = tmp7 + z3; // phase 5 z13 = tmp7 - z3; *d5p = z13 + z2; // phase 6 *d3p = z13 - z2; *d1p = z11 + z4; *d7p = z11 - z4; *d0p = d0; *d2p = d2; *d4p = d4; *d6p = d6; } static void stbiw__jpg_calcBits(int val, unsigned short bits[2]) { int tmp1 = val < 0 ? -val : val; val = val < 0 ? val-1 : val; bits[1] = 1; while(tmp1 >>= 1) { ++bits[1]; } bits[0] = val & ((1<0)&&(DU[end0pos]==0); --end0pos) { } // end0pos = first element in reverse order !=0 if(end0pos == 0) { stbiw__jpg_writeBits(s, bitBuf, bitCnt, EOB); return DU[0]; } for(i = 1; i <= end0pos; ++i) { int startpos = i; int nrzeroes; unsigned short bits[2]; for (; DU[i]==0 && i<=end0pos; ++i) { } nrzeroes = i-startpos; if ( nrzeroes >= 16 ) { int lng = nrzeroes>>4; int nrmarker; for (nrmarker=1; nrmarker <= lng; ++nrmarker) stbiw__jpg_writeBits(s, bitBuf, bitCnt, M16zeroes); nrzeroes &= 15; } stbiw__jpg_calcBits(DU[i], bits); stbiw__jpg_writeBits(s, bitBuf, bitCnt, HTAC[(nrzeroes<<4)+bits[1]]); stbiw__jpg_writeBits(s, bitBuf, bitCnt, bits); } if(end0pos != 63) { stbiw__jpg_writeBits(s, bitBuf, bitCnt, EOB); } return DU[0]; } static int stbi_write_jpg_core(stbi__write_context *s, int width, int height, int comp, const void* data, int quality) { // Constants that don't pollute global namespace static const unsigned char std_dc_luminance_nrcodes[] = {0,0,1,5,1,1,1,1,1,1,0,0,0,0,0,0,0}; static const unsigned char std_dc_luminance_values[] = {0,1,2,3,4,5,6,7,8,9,10,11}; static const unsigned char std_ac_luminance_nrcodes[] = {0,0,2,1,3,3,2,4,3,5,5,4,4,0,0,1,0x7d}; static const unsigned char std_ac_luminance_values[] = { 0x01,0x02,0x03,0x00,0x04,0x11,0x05,0x12,0x21,0x31,0x41,0x06,0x13,0x51,0x61,0x07,0x22,0x71,0x14,0x32,0x81,0x91,0xa1,0x08, 0x23,0x42,0xb1,0xc1,0x15,0x52,0xd1,0xf0,0x24,0x33,0x62,0x72,0x82,0x09,0x0a,0x16,0x17,0x18,0x19,0x1a,0x25,0x26,0x27,0x28, 0x29,0x2a,0x34,0x35,0x36,0x37,0x38,0x39,0x3a,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4a,0x53,0x54,0x55,0x56,0x57,0x58,0x59, 0x5a,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6a,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7a,0x83,0x84,0x85,0x86,0x87,0x88,0x89, 0x8a,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9a,0xa2,0xa3,0xa4,0xa5,0xa6,0xa7,0xa8,0xa9,0xaa,0xb2,0xb3,0xb4,0xb5,0xb6, 0xb7,0xb8,0xb9,0xba,0xc2,0xc3,0xc4,0xc5,0xc6,0xc7,0xc8,0xc9,0xca,0xd2,0xd3,0xd4,0xd5,0xd6,0xd7,0xd8,0xd9,0xda,0xe1,0xe2, 0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,0xea,0xf1,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8,0xf9,0xfa }; static const unsigned char std_dc_chrominance_nrcodes[] = {0,0,3,1,1,1,1,1,1,1,1,1,0,0,0,0,0}; static const unsigned char std_dc_chrominance_values[] = {0,1,2,3,4,5,6,7,8,9,10,11}; static const unsigned char std_ac_chrominance_nrcodes[] = {0,0,2,1,2,4,4,3,4,7,5,4,4,0,1,2,0x77}; static const unsigned char std_ac_chrominance_values[] = { 0x00,0x01,0x02,0x03,0x11,0x04,0x05,0x21,0x31,0x06,0x12,0x41,0x51,0x07,0x61,0x71,0x13,0x22,0x32,0x81,0x08,0x14,0x42,0x91, 0xa1,0xb1,0xc1,0x09,0x23,0x33,0x52,0xf0,0x15,0x62,0x72,0xd1,0x0a,0x16,0x24,0x34,0xe1,0x25,0xf1,0x17,0x18,0x19,0x1a,0x26, 0x27,0x28,0x29,0x2a,0x35,0x36,0x37,0x38,0x39,0x3a,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4a,0x53,0x54,0x55,0x56,0x57,0x58, 0x59,0x5a,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6a,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7a,0x82,0x83,0x84,0x85,0x86,0x87, 0x88,0x89,0x8a,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9a,0xa2,0xa3,0xa4,0xa5,0xa6,0xa7,0xa8,0xa9,0xaa,0xb2,0xb3,0xb4, 0xb5,0xb6,0xb7,0xb8,0xb9,0xba,0xc2,0xc3,0xc4,0xc5,0xc6,0xc7,0xc8,0xc9,0xca,0xd2,0xd3,0xd4,0xd5,0xd6,0xd7,0xd8,0xd9,0xda, 0xe2,0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,0xea,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8,0xf9,0xfa }; // Huffman tables static const unsigned short YDC_HT[256][2] = { {0,2},{2,3},{3,3},{4,3},{5,3},{6,3},{14,4},{30,5},{62,6},{126,7},{254,8},{510,9}}; static const unsigned short UVDC_HT[256][2] = { {0,2},{1,2},{2,2},{6,3},{14,4},{30,5},{62,6},{126,7},{254,8},{510,9},{1022,10},{2046,11}}; static const unsigned short YAC_HT[256][2] = { {10,4},{0,2},{1,2},{4,3},{11,4},{26,5},{120,7},{248,8},{1014,10},{65410,16},{65411,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, {12,4},{27,5},{121,7},{502,9},{2038,11},{65412,16},{65413,16},{65414,16},{65415,16},{65416,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, {28,5},{249,8},{1015,10},{4084,12},{65417,16},{65418,16},{65419,16},{65420,16},{65421,16},{65422,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, {58,6},{503,9},{4085,12},{65423,16},{65424,16},{65425,16},{65426,16},{65427,16},{65428,16},{65429,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, {59,6},{1016,10},{65430,16},{65431,16},{65432,16},{65433,16},{65434,16},{65435,16},{65436,16},{65437,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, {122,7},{2039,11},{65438,16},{65439,16},{65440,16},{65441,16},{65442,16},{65443,16},{65444,16},{65445,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, {123,7},{4086,12},{65446,16},{65447,16},{65448,16},{65449,16},{65450,16},{65451,16},{65452,16},{65453,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, {250,8},{4087,12},{65454,16},{65455,16},{65456,16},{65457,16},{65458,16},{65459,16},{65460,16},{65461,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, {504,9},{32704,15},{65462,16},{65463,16},{65464,16},{65465,16},{65466,16},{65467,16},{65468,16},{65469,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, {505,9},{65470,16},{65471,16},{65472,16},{65473,16},{65474,16},{65475,16},{65476,16},{65477,16},{65478,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, {506,9},{65479,16},{65480,16},{65481,16},{65482,16},{65483,16},{65484,16},{65485,16},{65486,16},{65487,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, {1017,10},{65488,16},{65489,16},{65490,16},{65491,16},{65492,16},{65493,16},{65494,16},{65495,16},{65496,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, {1018,10},{65497,16},{65498,16},{65499,16},{65500,16},{65501,16},{65502,16},{65503,16},{65504,16},{65505,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, {2040,11},{65506,16},{65507,16},{65508,16},{65509,16},{65510,16},{65511,16},{65512,16},{65513,16},{65514,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, {65515,16},{65516,16},{65517,16},{65518,16},{65519,16},{65520,16},{65521,16},{65522,16},{65523,16},{65524,16},{0,0},{0,0},{0,0},{0,0},{0,0}, {2041,11},{65525,16},{65526,16},{65527,16},{65528,16},{65529,16},{65530,16},{65531,16},{65532,16},{65533,16},{65534,16},{0,0},{0,0},{0,0},{0,0},{0,0} }; static const unsigned short UVAC_HT[256][2] = { {0,2},{1,2},{4,3},{10,4},{24,5},{25,5},{56,6},{120,7},{500,9},{1014,10},{4084,12},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, {11,4},{57,6},{246,8},{501,9},{2038,11},{4085,12},{65416,16},{65417,16},{65418,16},{65419,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, {26,5},{247,8},{1015,10},{4086,12},{32706,15},{65420,16},{65421,16},{65422,16},{65423,16},{65424,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, {27,5},{248,8},{1016,10},{4087,12},{65425,16},{65426,16},{65427,16},{65428,16},{65429,16},{65430,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, {58,6},{502,9},{65431,16},{65432,16},{65433,16},{65434,16},{65435,16},{65436,16},{65437,16},{65438,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, {59,6},{1017,10},{65439,16},{65440,16},{65441,16},{65442,16},{65443,16},{65444,16},{65445,16},{65446,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, {121,7},{2039,11},{65447,16},{65448,16},{65449,16},{65450,16},{65451,16},{65452,16},{65453,16},{65454,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, {122,7},{2040,11},{65455,16},{65456,16},{65457,16},{65458,16},{65459,16},{65460,16},{65461,16},{65462,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, {249,8},{65463,16},{65464,16},{65465,16},{65466,16},{65467,16},{65468,16},{65469,16},{65470,16},{65471,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, {503,9},{65472,16},{65473,16},{65474,16},{65475,16},{65476,16},{65477,16},{65478,16},{65479,16},{65480,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, {504,9},{65481,16},{65482,16},{65483,16},{65484,16},{65485,16},{65486,16},{65487,16},{65488,16},{65489,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, {505,9},{65490,16},{65491,16},{65492,16},{65493,16},{65494,16},{65495,16},{65496,16},{65497,16},{65498,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, {506,9},{65499,16},{65500,16},{65501,16},{65502,16},{65503,16},{65504,16},{65505,16},{65506,16},{65507,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, {2041,11},{65508,16},{65509,16},{65510,16},{65511,16},{65512,16},{65513,16},{65514,16},{65515,16},{65516,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, {16352,14},{65517,16},{65518,16},{65519,16},{65520,16},{65521,16},{65522,16},{65523,16},{65524,16},{65525,16},{0,0},{0,0},{0,0},{0,0},{0,0}, {1018,10},{32707,15},{65526,16},{65527,16},{65528,16},{65529,16},{65530,16},{65531,16},{65532,16},{65533,16},{65534,16},{0,0},{0,0},{0,0},{0,0},{0,0} }; static const int YQT[] = {16,11,10,16,24,40,51,61,12,12,14,19,26,58,60,55,14,13,16,24,40,57,69,56,14,17,22,29,51,87,80,62,18,22, 37,56,68,109,103,77,24,35,55,64,81,104,113,92,49,64,78,87,103,121,120,101,72,92,95,98,112,100,103,99}; static const int UVQT[] = {17,18,24,47,99,99,99,99,18,21,26,66,99,99,99,99,24,26,56,99,99,99,99,99,47,66,99,99,99,99,99,99, 99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99}; static const float aasf[] = { 1.0f * 2.828427125f, 1.387039845f * 2.828427125f, 1.306562965f * 2.828427125f, 1.175875602f * 2.828427125f, 1.0f * 2.828427125f, 0.785694958f * 2.828427125f, 0.541196100f * 2.828427125f, 0.275899379f * 2.828427125f }; int row, col, i, k, subsample; float fdtbl_Y[64], fdtbl_UV[64]; unsigned char YTable[64], UVTable[64]; if(!data || !width || !height || comp > 4 || comp < 1) { return 0; } quality = quality ? quality : 90; subsample = quality <= 90 ? 1 : 0; quality = quality < 1 ? 1 : quality > 100 ? 100 : quality; quality = quality < 50 ? 5000 / quality : 200 - quality * 2; for(i = 0; i < 64; ++i) { int uvti, yti = (YQT[i]*quality+50)/100; YTable[stbiw__jpg_ZigZag[i]] = (unsigned char) (yti < 1 ? 1 : yti > 255 ? 255 : yti); uvti = (UVQT[i]*quality+50)/100; UVTable[stbiw__jpg_ZigZag[i]] = (unsigned char) (uvti < 1 ? 1 : uvti > 255 ? 255 : uvti); } for(row = 0, k = 0; row < 8; ++row) { for(col = 0; col < 8; ++col, ++k) { fdtbl_Y[k] = 1 / (YTable [stbiw__jpg_ZigZag[k]] * aasf[row] * aasf[col]); fdtbl_UV[k] = 1 / (UVTable[stbiw__jpg_ZigZag[k]] * aasf[row] * aasf[col]); } } // Write Headers { static const unsigned char head0[] = { 0xFF,0xD8,0xFF,0xE0,0,0x10,'J','F','I','F',0,1,1,0,0,1,0,1,0,0,0xFF,0xDB,0,0x84,0 }; static const unsigned char head2[] = { 0xFF,0xDA,0,0xC,3,1,0,2,0x11,3,0x11,0,0x3F,0 }; const unsigned char head1[] = { 0xFF,0xC0,0,0x11,8,(unsigned char)(height>>8),STBIW_UCHAR(height),(unsigned char)(width>>8),STBIW_UCHAR(width), 3,1,(unsigned char)(subsample?0x22:0x11),0,2,0x11,1,3,0x11,1,0xFF,0xC4,0x01,0xA2,0 }; s->func(s->context, (void*)head0, sizeof(head0)); s->func(s->context, (void*)YTable, sizeof(YTable)); stbiw__putc(s, 1); s->func(s->context, UVTable, sizeof(UVTable)); s->func(s->context, (void*)head1, sizeof(head1)); s->func(s->context, (void*)(std_dc_luminance_nrcodes+1), sizeof(std_dc_luminance_nrcodes)-1); s->func(s->context, (void*)std_dc_luminance_values, sizeof(std_dc_luminance_values)); stbiw__putc(s, 0x10); // HTYACinfo s->func(s->context, (void*)(std_ac_luminance_nrcodes+1), sizeof(std_ac_luminance_nrcodes)-1); s->func(s->context, (void*)std_ac_luminance_values, sizeof(std_ac_luminance_values)); stbiw__putc(s, 1); // HTUDCinfo s->func(s->context, (void*)(std_dc_chrominance_nrcodes+1), sizeof(std_dc_chrominance_nrcodes)-1); s->func(s->context, (void*)std_dc_chrominance_values, sizeof(std_dc_chrominance_values)); stbiw__putc(s, 0x11); // HTUACinfo s->func(s->context, (void*)(std_ac_chrominance_nrcodes+1), sizeof(std_ac_chrominance_nrcodes)-1); s->func(s->context, (void*)std_ac_chrominance_values, sizeof(std_ac_chrominance_values)); s->func(s->context, (void*)head2, sizeof(head2)); } // Encode 8x8 macroblocks { static const unsigned short fillBits[] = {0x7F, 7}; int DCY=0, DCU=0, DCV=0; int bitBuf=0, bitCnt=0; // comp == 2 is grey+alpha (alpha is ignored) int ofsG = comp > 2 ? 1 : 0, ofsB = comp > 2 ? 2 : 0; const unsigned char *dataR = (const unsigned char *)data; const unsigned char *dataG = dataR + ofsG; const unsigned char *dataB = dataR + ofsB; int x, y, pos; if(subsample) { for(y = 0; y < height; y += 16) { for(x = 0; x < width; x += 16) { float Y[256], U[256], V[256]; for(row = y, pos = 0; row < y+16; ++row) { // row >= height => use last input row int clamped_row = (row < height) ? row : height - 1; int base_p = (stbi__flip_vertically_on_write ? (height-1-clamped_row) : clamped_row)*width*comp; for(col = x; col < x+16; ++col, ++pos) { // if col >= width => use pixel from last input column int p = base_p + ((col < width) ? col : (width-1))*comp; float r = dataR[p], g = dataG[p], b = dataB[p]; Y[pos]= +0.29900f*r + 0.58700f*g + 0.11400f*b - 128; U[pos]= -0.16874f*r - 0.33126f*g + 0.50000f*b; V[pos]= +0.50000f*r - 0.41869f*g - 0.08131f*b; } } DCY = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, Y+0, 16, fdtbl_Y, DCY, YDC_HT, YAC_HT); DCY = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, Y+8, 16, fdtbl_Y, DCY, YDC_HT, YAC_HT); DCY = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, Y+128, 16, fdtbl_Y, DCY, YDC_HT, YAC_HT); DCY = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, Y+136, 16, fdtbl_Y, DCY, YDC_HT, YAC_HT); // subsample U,V { float subU[64], subV[64]; int yy, xx; for(yy = 0, pos = 0; yy < 8; ++yy) { for(xx = 0; xx < 8; ++xx, ++pos) { int j = yy*32+xx*2; subU[pos] = (U[j+0] + U[j+1] + U[j+16] + U[j+17]) * 0.25f; subV[pos] = (V[j+0] + V[j+1] + V[j+16] + V[j+17]) * 0.25f; } } DCU = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, subU, 8, fdtbl_UV, DCU, UVDC_HT, UVAC_HT); DCV = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, subV, 8, fdtbl_UV, DCV, UVDC_HT, UVAC_HT); } } } } else { for(y = 0; y < height; y += 8) { for(x = 0; x < width; x += 8) { float Y[64], U[64], V[64]; for(row = y, pos = 0; row < y+8; ++row) { // row >= height => use last input row int clamped_row = (row < height) ? row : height - 1; int base_p = (stbi__flip_vertically_on_write ? (height-1-clamped_row) : clamped_row)*width*comp; for(col = x; col < x+8; ++col, ++pos) { // if col >= width => use pixel from last input column int p = base_p + ((col < width) ? col : (width-1))*comp; float r = dataR[p], g = dataG[p], b = dataB[p]; Y[pos]= +0.29900f*r + 0.58700f*g + 0.11400f*b - 128; U[pos]= -0.16874f*r - 0.33126f*g + 0.50000f*b; V[pos]= +0.50000f*r - 0.41869f*g - 0.08131f*b; } } DCY = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, Y, 8, fdtbl_Y, DCY, YDC_HT, YAC_HT); DCU = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, U, 8, fdtbl_UV, DCU, UVDC_HT, UVAC_HT); DCV = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, V, 8, fdtbl_UV, DCV, UVDC_HT, UVAC_HT); } } } // Do the bit alignment of the EOI marker stbiw__jpg_writeBits(s, &bitBuf, &bitCnt, fillBits); } // EOI stbiw__putc(s, 0xFF); stbiw__putc(s, 0xD9); return 1; } STBIWDEF int stbi_write_jpg_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data, int quality) { stbi__write_context s = { 0 }; stbi__start_write_callbacks(&s, func, context); return stbi_write_jpg_core(&s, x, y, comp, (void *) data, quality); } #ifndef STBI_WRITE_NO_STDIO STBIWDEF int stbi_write_jpg(char const *filename, int x, int y, int comp, const void *data, int quality) { stbi__write_context s = { 0 }; if (stbi__start_write_file(&s,filename)) { int r = stbi_write_jpg_core(&s, x, y, comp, data, quality); stbi__end_write_file(&s); return r; } else return 0; } #endif #endif // STB_IMAGE_WRITE_IMPLEMENTATION /* Revision history 1.16 (2021-07-11) make Deflate code emit uncompressed blocks when it would otherwise expand support writing BMPs with alpha channel 1.15 (2020-07-13) unknown 1.14 (2020-02-02) updated JPEG writer to downsample chroma channels 1.13 1.12 1.11 (2019-08-11) 1.10 (2019-02-07) support utf8 filenames in Windows; fix warnings and platform ifdefs 1.09 (2018-02-11) fix typo in zlib quality API, improve STB_I_W_STATIC in C++ 1.08 (2018-01-29) add stbi__flip_vertically_on_write, external zlib, zlib quality, choose PNG filter 1.07 (2017-07-24) doc fix 1.06 (2017-07-23) writing JPEG (using Jon Olick's code) 1.05 ??? 1.04 (2017-03-03) monochrome BMP expansion 1.03 ??? 1.02 (2016-04-02) avoid allocating large structures on the stack 1.01 (2016-01-16) STBIW_REALLOC_SIZED: support allocators with no realloc support avoid race-condition in crc initialization minor compile issues 1.00 (2015-09-14) installable file IO function 0.99 (2015-09-13) warning fixes; TGA rle support 0.98 (2015-04-08) added STBIW_MALLOC, STBIW_ASSERT etc 0.97 (2015-01-18) fixed HDR asserts, rewrote HDR rle logic 0.96 (2015-01-17) add HDR output fix monochrome BMP 0.95 (2014-08-17) add monochrome TGA output 0.94 (2014-05-31) rename private functions to avoid conflicts with stb_image.h 0.93 (2014-05-27) warning fixes 0.92 (2010-08-01) casts to unsigned char to fix warnings 0.91 (2010-07-17) first public release 0.90 first internal release */ /* ------------------------------------------------------------------------------ This software is available under 2 licenses -- choose whichever you prefer. ------------------------------------------------------------------------------ ALTERNATIVE A - MIT License Copyright (c) 2017 Sean Barrett Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ------------------------------------------------------------------------------ ALTERNATIVE B - Public Domain (www.unlicense.org) This is free and unencumbered software released into the public domain. Anyone is free to copy, modify, publish, use, compile, sell, or distribute this software, either in source code form or as a compiled binary, for any purpose, commercial or non-commercial, and by any means. In jurisdictions that recognize copyright laws, the author or authors of this software dedicate any and all copyright interest in the software to the public domain. We make this dedication for the benefit of the public at large and to the detriment of our heirs and successors. We intend this dedication to be an overt act of relinquishment in perpetuity of all present and future rights to this software under copyright law. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ------------------------------------------------------------------------------ */ sammycage-plutovg-5695a71/source/plutovg-stb-image.h000066400000000000000000010505631510714322500224720ustar00rootroot00000000000000/* stb_image - v2.30 - public domain image loader - http://nothings.org/stb no warranty implied; use at your own risk Do this: #define STB_IMAGE_IMPLEMENTATION before you include this file in *one* C or C++ file to create the implementation. // i.e. it should look like this: #include ... #include ... #include ... #define STB_IMAGE_IMPLEMENTATION #include "stb_image.h" You can #define STBI_ASSERT(x) before the #include to avoid using assert.h. And #define STBI_MALLOC, STBI_REALLOC, and STBI_FREE to avoid using malloc,realloc,free QUICK NOTES: Primarily of interest to game developers and other people who can avoid problematic images and only need the trivial interface JPEG baseline & progressive (12 bpc/arithmetic not supported, same as stock IJG lib) PNG 1/2/4/8/16-bit-per-channel TGA (not sure what subset, if a subset) BMP non-1bpp, non-RLE PSD (composited view only, no extra channels, 8/16 bit-per-channel) GIF (*comp always reports as 4-channel) HDR (radiance rgbE format) PIC (Softimage PIC) PNM (PPM and PGM binary only) Animated GIF still needs a proper API, but here's one way to do it: http://gist.github.com/urraka/685d9a6340b26b830d49 - decode from memory or through FILE (define STBI_NO_STDIO to remove code) - decode from arbitrary I/O callbacks - SIMD acceleration on x86/x64 (SSE2) and ARM (NEON) Full documentation under "DOCUMENTATION" below. LICENSE See end of file for license information. RECENT REVISION HISTORY: 2.30 (2024-05-31) avoid erroneous gcc warning 2.29 (2023-05-xx) optimizations 2.28 (2023-01-29) many error fixes, security errors, just tons of stuff 2.27 (2021-07-11) document stbi_info better, 16-bit PNM support, bug fixes 2.26 (2020-07-13) many minor fixes 2.25 (2020-02-02) fix warnings 2.24 (2020-02-02) fix warnings; thread-local failure_reason and flip_vertically 2.23 (2019-08-11) fix clang static analysis warning 2.22 (2019-03-04) gif fixes, fix warnings 2.21 (2019-02-25) fix typo in comment 2.20 (2019-02-07) support utf8 filenames in Windows; fix warnings and platform ifdefs 2.19 (2018-02-11) fix warning 2.18 (2018-01-30) fix warnings 2.17 (2018-01-29) bugfix, 1-bit BMP, 16-bitness query, fix warnings 2.16 (2017-07-23) all functions have 16-bit variants; optimizations; bugfixes 2.15 (2017-03-18) fix png-1,2,4; all Imagenet JPGs; no runtime SSE detection on GCC 2.14 (2017-03-03) remove deprecated STBI_JPEG_OLD; fixes for Imagenet JPGs 2.13 (2016-12-04) experimental 16-bit API, only for PNG so far; fixes 2.12 (2016-04-02) fix typo in 2.11 PSD fix that caused crashes 2.11 (2016-04-02) 16-bit PNGS; enable SSE2 in non-gcc x64 RGB-format JPEG; remove white matting in PSD; allocate large structures on the stack; correct channel count for PNG & BMP 2.10 (2016-01-22) avoid warning introduced in 2.09 2.09 (2016-01-16) 16-bit TGA; comments in PNM files; STBI_REALLOC_SIZED See end of file for full revision history. ============================ Contributors ========================= Image formats Extensions, features Sean Barrett (jpeg, png, bmp) Jetro Lauha (stbi_info) Nicolas Schulz (hdr, psd) Martin "SpartanJ" Golini (stbi_info) Jonathan Dummer (tga) James "moose2000" Brown (iPhone PNG) Jean-Marc Lienher (gif) Ben "Disch" Wenger (io callbacks) Tom Seddon (pic) Omar Cornut (1/2/4-bit PNG) Thatcher Ulrich (psd) Nicolas Guillemot (vertical flip) Ken Miller (pgm, ppm) Richard Mitton (16-bit PSD) github:urraka (animated gif) Junggon Kim (PNM comments) Christopher Forseth (animated gif) Daniel Gibson (16-bit TGA) socks-the-fox (16-bit PNG) Jeremy Sawicki (handle all ImageNet JPGs) Optimizations & bugfixes Mikhail Morozov (1-bit BMP) Fabian "ryg" Giesen Anael Seghezzi (is-16-bit query) Arseny Kapoulkine Simon Breuss (16-bit PNM) John-Mark Allen Carmelo J Fdez-Aguera Bug & warning fixes Marc LeBlanc David Woo Guillaume George Martins Mozeiko Christpher Lloyd Jerry Jansson Joseph Thomson Blazej Dariusz Roszkowski Phil Jordan Dave Moore Roy Eltham Hayaki Saito Nathan Reed Won Chun Luke Graham Johan Duparc Nick Verigakis the Horde3D community Thomas Ruf Ronny Chevalier github:rlyeh Janez Zemva John Bartholomew Michal Cichon github:romigrou Jonathan Blow Ken Hamada Tero Hanninen github:svdijk Eugene Golushkov Laurent Gomila Cort Stratton github:snagar Aruelien Pocheville Sergio Gonzalez Thibault Reuille github:Zelex Cass Everitt Ryamond Barbiero github:grim210 Paul Du Bois Engin Manap Aldo Culquicondor github:sammyhw Philipp Wiesemann Dale Weiler Oriol Ferrer Mesia github:phprus Josh Tobin Neil Bickford Matthew Gregan github:poppolopoppo Julian Raschke Gregory Mullen Christian Floisand github:darealshinji Baldur Karlsson Kevin Schmidt JR Smith github:Michaelangel007 Brad Weinberger Matvey Cherevko github:mosra Luca Sas Alexander Veselov Zack Middleton [reserved] Ryan C. Gordon [reserved] [reserved] DO NOT ADD YOUR NAME HERE Jacko Dirks To add your name to the credits, pick a random blank space in the middle and fill it. 80% of merge conflicts on stb PRs are due to people adding their name at the end of the credits. */ #ifndef PLUTOVG_STB_IMAGE_H #define PLUTOVG_STB_IMAGE_H // DOCUMENTATION // // Limitations: // - no 12-bit-per-channel JPEG // - no JPEGs with arithmetic coding // - GIF always returns *comp=4 // // Basic usage (see HDR discussion below for HDR usage): // int x,y,n; // unsigned char *data = stbi_load(filename, &x, &y, &n, 0); // // ... process data if not NULL ... // // ... x = width, y = height, n = # 8-bit components per pixel ... // // ... replace '0' with '1'..'4' to force that many components per pixel // // ... but 'n' will always be the number that it would have been if you said 0 // stbi_image_free(data); // // Standard parameters: // int *x -- outputs image width in pixels // int *y -- outputs image height in pixels // int *channels_in_file -- outputs # of image components in image file // int desired_channels -- if non-zero, # of image components requested in result // // The return value from an image loader is an 'unsigned char *' which points // to the pixel data, or NULL on an allocation failure or if the image is // corrupt or invalid. The pixel data consists of *y scanlines of *x pixels, // with each pixel consisting of N interleaved 8-bit components; the first // pixel pointed to is top-left-most in the image. There is no padding between // image scanlines or between pixels, regardless of format. The number of // components N is 'desired_channels' if desired_channels is non-zero, or // *channels_in_file otherwise. If desired_channels is non-zero, // *channels_in_file has the number of components that _would_ have been // output otherwise. E.g. if you set desired_channels to 4, you will always // get RGBA output, but you can check *channels_in_file to see if it's trivially // opaque because e.g. there were only 3 channels in the source image. // // An output image with N components has the following components interleaved // in this order in each pixel: // // N=#comp components // 1 grey // 2 grey, alpha // 3 red, green, blue // 4 red, green, blue, alpha // // If image loading fails for any reason, the return value will be NULL, // and *x, *y, *channels_in_file will be unchanged. The function // stbi_failure_reason() can be queried for an extremely brief, end-user // unfriendly explanation of why the load failed. Define STBI_NO_FAILURE_STRINGS // to avoid compiling these strings at all, and STBI_FAILURE_USERMSG to get slightly // more user-friendly ones. // // Paletted PNG, BMP, GIF, and PIC images are automatically depalettized. // // To query the width, height and component count of an image without having to // decode the full file, you can use the stbi_info family of functions: // // int x,y,n,ok; // ok = stbi_info(filename, &x, &y, &n); // // returns ok=1 and sets x, y, n if image is a supported format, // // 0 otherwise. // // Note that stb_image pervasively uses ints in its public API for sizes, // including sizes of memory buffers. This is now part of the API and thus // hard to change without causing breakage. As a result, the various image // loaders all have certain limits on image size; these differ somewhat // by format but generally boil down to either just under 2GB or just under // 1GB. When the decoded image would be larger than this, stb_image decoding // will fail. // // Additionally, stb_image will reject image files that have any of their // dimensions set to a larger value than the configurable STBI_MAX_DIMENSIONS, // which defaults to 2**24 = 16777216 pixels. Due to the above memory limit, // the only way to have an image with such dimensions load correctly // is for it to have a rather extreme aspect ratio. Either way, the // assumption here is that such larger images are likely to be malformed // or malicious. If you do need to load an image with individual dimensions // larger than that, and it still fits in the overall size limit, you can // #define STBI_MAX_DIMENSIONS on your own to be something larger. // // =========================================================================== // // UNICODE: // // If compiling for Windows and you wish to use Unicode filenames, compile // with // #define STBI_WINDOWS_UTF8 // and pass utf8-encoded filenames. Call stbi_convert_wchar_to_utf8 to convert // Windows wchar_t filenames to utf8. // // =========================================================================== // // Philosophy // // stb libraries are designed with the following priorities: // // 1. easy to use // 2. easy to maintain // 3. good performance // // Sometimes I let "good performance" creep up in priority over "easy to maintain", // and for best performance I may provide less-easy-to-use APIs that give higher // performance, in addition to the easy-to-use ones. Nevertheless, it's important // to keep in mind that from the standpoint of you, a client of this library, // all you care about is #1 and #3, and stb libraries DO NOT emphasize #3 above all. // // Some secondary priorities arise directly from the first two, some of which // provide more explicit reasons why performance can't be emphasized. // // - Portable ("ease of use") // - Small source code footprint ("easy to maintain") // - No dependencies ("ease of use") // // =========================================================================== // // I/O callbacks // // I/O callbacks allow you to read from arbitrary sources, like packaged // files or some other source. Data read from callbacks are processed // through a small internal buffer (currently 128 bytes) to try to reduce // overhead. // // The three functions you must define are "read" (reads some bytes of data), // "skip" (skips some bytes of data), "eof" (reports if the stream is at the end). // // =========================================================================== // // SIMD support // // The JPEG decoder will try to automatically use SIMD kernels on x86 when // supported by the compiler. For ARM Neon support, you must explicitly // request it. // // (The old do-it-yourself SIMD API is no longer supported in the current // code.) // // On x86, SSE2 will automatically be used when available based on a run-time // test; if not, the generic C versions are used as a fall-back. On ARM targets, // the typical path is to have separate builds for NEON and non-NEON devices // (at least this is true for iOS and Android). Therefore, the NEON support is // toggled by a build flag: define STBI_NEON to get NEON loops. // // If for some reason you do not want to use any of SIMD code, or if // you have issues compiling it, you can disable it entirely by // defining STBI_NO_SIMD. // // =========================================================================== // // HDR image support (disable by defining STBI_NO_HDR) // // stb_image supports loading HDR images in general, and currently the Radiance // .HDR file format specifically. You can still load any file through the existing // interface; if you attempt to load an HDR file, it will be automatically remapped // to LDR, assuming gamma 2.2 and an arbitrary scale factor defaulting to 1; // both of these constants can be reconfigured through this interface: // // stbi_hdr_to_ldr_gamma(2.2f); // stbi_hdr_to_ldr_scale(1.0f); // // (note, do not use _inverse_ constants; stbi_image will invert them // appropriately). // // Additionally, there is a new, parallel interface for loading files as // (linear) floats to preserve the full dynamic range: // // float *data = stbi_loadf(filename, &x, &y, &n, 0); // // If you load LDR images through this interface, those images will // be promoted to floating point values, run through the inverse of // constants corresponding to the above: // // stbi_ldr_to_hdr_scale(1.0f); // stbi_ldr_to_hdr_gamma(2.2f); // // Finally, given a filename (or an open file or memory block--see header // file for details) containing image data, you can query for the "most // appropriate" interface to use (that is, whether the image is HDR or // not), using: // // stbi_is_hdr(char *filename); // // =========================================================================== // // iPhone PNG support: // // We optionally support converting iPhone-formatted PNGs (which store // premultiplied BGRA) back to RGB, even though they're internally encoded // differently. To enable this conversion, call // stbi_convert_iphone_png_to_rgb(1). // // Call stbi_set_unpremultiply_on_load(1) as well to force a divide per // pixel to remove any premultiplied alpha *only* if the image file explicitly // says there's premultiplied data (currently only happens in iPhone images, // and only if iPhone convert-to-rgb processing is on). // // =========================================================================== // // ADDITIONAL CONFIGURATION // // - You can suppress implementation of any of the decoders to reduce // your code footprint by #defining one or more of the following // symbols before creating the implementation. // // STBI_NO_JPEG // STBI_NO_PNG // STBI_NO_BMP // STBI_NO_PSD // STBI_NO_TGA // STBI_NO_GIF // STBI_NO_HDR // STBI_NO_PIC // STBI_NO_PNM (.ppm and .pgm) // // - You can request *only* certain decoders and suppress all other ones // (this will be more forward-compatible, as addition of new decoders // doesn't require you to disable them explicitly): // // STBI_ONLY_JPEG // STBI_ONLY_PNG // STBI_ONLY_BMP // STBI_ONLY_PSD // STBI_ONLY_TGA // STBI_ONLY_GIF // STBI_ONLY_HDR // STBI_ONLY_PIC // STBI_ONLY_PNM (.ppm and .pgm) // // - If you use STBI_NO_PNG (or _ONLY_ without PNG), and you still // want the zlib decoder to be available, #define STBI_SUPPORT_ZLIB // // - If you define STBI_MAX_DIMENSIONS, stb_image will reject images greater // than that size (in either width or height) without further processing. // This is to let programs in the wild set an upper bound to prevent // denial-of-service attacks on untrusted data, as one could generate a // valid image of gigantic dimensions and force stb_image to allocate a // huge block of memory and spend disproportionate time decoding it. By // default this is set to (1 << 24), which is 16777216, but that's still // very big. #ifndef STBI_NO_STDIO #include #endif // STBI_NO_STDIO #define STBI_VERSION 1 enum { STBI_default = 0, // only used for desired_channels STBI_grey = 1, STBI_grey_alpha = 2, STBI_rgb = 3, STBI_rgb_alpha = 4 }; #include typedef unsigned char stbi_uc; typedef unsigned short stbi_us; #ifdef __cplusplus extern "C" { #endif #ifndef STBIDEF #ifdef STB_IMAGE_STATIC #define STBIDEF static #else #define STBIDEF extern #endif #endif ////////////////////////////////////////////////////////////////////////////// // // PRIMARY API - works on images of any type // // // load image by filename, open file, or memory buffer // typedef struct { int (*read) (void *user,char *data,int size); // fill 'data' with 'size' bytes. return number of bytes actually read void (*skip) (void *user,int n); // skip the next 'n' bytes, or 'unget' the last -n bytes if negative int (*eof) (void *user); // returns nonzero if we are at end of file/data } stbi_io_callbacks; //////////////////////////////////// // // 8-bits-per-channel interface // STBIDEF stbi_uc *stbi_load_from_memory (stbi_uc const *buffer, int len , int *x, int *y, int *channels_in_file, int desired_channels); STBIDEF stbi_uc *stbi_load_from_callbacks(stbi_io_callbacks const *clbk , void *user, int *x, int *y, int *channels_in_file, int desired_channels); #ifndef STBI_NO_STDIO STBIDEF stbi_uc *stbi_load (char const *filename, int *x, int *y, int *channels_in_file, int desired_channels); STBIDEF stbi_uc *stbi_load_from_file (FILE *f, int *x, int *y, int *channels_in_file, int desired_channels); // for stbi_load_from_file, file pointer is left pointing immediately after image #endif #ifndef STBI_NO_GIF STBIDEF stbi_uc *stbi_load_gif_from_memory(stbi_uc const *buffer, int len, int **delays, int *x, int *y, int *z, int *comp, int req_comp); #endif #ifdef STBI_WINDOWS_UTF8 STBIDEF int stbi_convert_wchar_to_utf8(char *buffer, size_t bufferlen, const wchar_t* input); #endif //////////////////////////////////// // // 16-bits-per-channel interface // STBIDEF stbi_us *stbi_load_16_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *channels_in_file, int desired_channels); STBIDEF stbi_us *stbi_load_16_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *channels_in_file, int desired_channels); #ifndef STBI_NO_STDIO STBIDEF stbi_us *stbi_load_16 (char const *filename, int *x, int *y, int *channels_in_file, int desired_channels); STBIDEF stbi_us *stbi_load_from_file_16(FILE *f, int *x, int *y, int *channels_in_file, int desired_channels); #endif //////////////////////////////////// // // float-per-channel interface // #ifndef STBI_NO_LINEAR STBIDEF float *stbi_loadf_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *channels_in_file, int desired_channels); STBIDEF float *stbi_loadf_from_callbacks (stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *channels_in_file, int desired_channels); #ifndef STBI_NO_STDIO STBIDEF float *stbi_loadf (char const *filename, int *x, int *y, int *channels_in_file, int desired_channels); STBIDEF float *stbi_loadf_from_file (FILE *f, int *x, int *y, int *channels_in_file, int desired_channels); #endif #endif #ifndef STBI_NO_HDR STBIDEF void stbi_hdr_to_ldr_gamma(float gamma); STBIDEF void stbi_hdr_to_ldr_scale(float scale); #endif // STBI_NO_HDR #ifndef STBI_NO_LINEAR STBIDEF void stbi_ldr_to_hdr_gamma(float gamma); STBIDEF void stbi_ldr_to_hdr_scale(float scale); #endif // STBI_NO_LINEAR // stbi_is_hdr is always defined, but always returns false if STBI_NO_HDR STBIDEF int stbi_is_hdr_from_callbacks(stbi_io_callbacks const *clbk, void *user); STBIDEF int stbi_is_hdr_from_memory(stbi_uc const *buffer, int len); #ifndef STBI_NO_STDIO STBIDEF int stbi_is_hdr (char const *filename); STBIDEF int stbi_is_hdr_from_file(FILE *f); #endif // STBI_NO_STDIO // get a VERY brief reason for failure // on most compilers (and ALL modern mainstream compilers) this is threadsafe STBIDEF const char *stbi_failure_reason (void); // free the loaded image -- this is just free() STBIDEF void stbi_image_free (void *retval_from_stbi_load); // get image dimensions & components without fully decoding STBIDEF int stbi_info_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp); STBIDEF int stbi_info_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp); STBIDEF int stbi_is_16_bit_from_memory(stbi_uc const *buffer, int len); STBIDEF int stbi_is_16_bit_from_callbacks(stbi_io_callbacks const *clbk, void *user); #ifndef STBI_NO_STDIO STBIDEF int stbi_info (char const *filename, int *x, int *y, int *comp); STBIDEF int stbi_info_from_file (FILE *f, int *x, int *y, int *comp); STBIDEF int stbi_is_16_bit (char const *filename); STBIDEF int stbi_is_16_bit_from_file(FILE *f); #endif // for image formats that explicitly notate that they have premultiplied alpha, // we just return the colors as stored in the file. set this flag to force // unpremultiplication. results are undefined if the unpremultiply overflow. STBIDEF void stbi_set_unpremultiply_on_load(int flag_true_if_should_unpremultiply); // indicate whether we should process iphone images back to canonical format, // or just pass them through "as-is" STBIDEF void stbi_convert_iphone_png_to_rgb(int flag_true_if_should_convert); // flip the image vertically, so the first pixel in the output array is the bottom left STBIDEF void stbi_set_flip_vertically_on_load(int flag_true_if_should_flip); // as above, but only applies to images loaded on the thread that calls the function // this function is only available if your compiler supports thread-local variables; // calling it will fail to link if your compiler doesn't STBIDEF void stbi_set_unpremultiply_on_load_thread(int flag_true_if_should_unpremultiply); STBIDEF void stbi_convert_iphone_png_to_rgb_thread(int flag_true_if_should_convert); STBIDEF void stbi_set_flip_vertically_on_load_thread(int flag_true_if_should_flip); // ZLIB client - used by PNG, available for other purposes STBIDEF char *stbi_zlib_decode_malloc_guesssize(const char *buffer, int len, int initial_size, int *outlen); STBIDEF char *stbi_zlib_decode_malloc_guesssize_headerflag(const char *buffer, int len, int initial_size, int *outlen, int parse_header); STBIDEF char *stbi_zlib_decode_malloc(const char *buffer, int len, int *outlen); STBIDEF int stbi_zlib_decode_buffer(char *obuffer, int olen, const char *ibuffer, int ilen); STBIDEF char *stbi_zlib_decode_noheader_malloc(const char *buffer, int len, int *outlen); STBIDEF int stbi_zlib_decode_noheader_buffer(char *obuffer, int olen, const char *ibuffer, int ilen); #ifdef __cplusplus } #endif // // //// end header file ///////////////////////////////////////////////////// #endif // PLUTOVG_STB_IMAGE_H #ifdef STB_IMAGE_IMPLEMENTATION #if defined(STBI_ONLY_JPEG) || defined(STBI_ONLY_PNG) || defined(STBI_ONLY_BMP) \ || defined(STBI_ONLY_TGA) || defined(STBI_ONLY_GIF) || defined(STBI_ONLY_PSD) \ || defined(STBI_ONLY_HDR) || defined(STBI_ONLY_PIC) || defined(STBI_ONLY_PNM) \ || defined(STBI_ONLY_ZLIB) #ifndef STBI_ONLY_JPEG #define STBI_NO_JPEG #endif #ifndef STBI_ONLY_PNG #define STBI_NO_PNG #endif #ifndef STBI_ONLY_BMP #define STBI_NO_BMP #endif #ifndef STBI_ONLY_PSD #define STBI_NO_PSD #endif #ifndef STBI_ONLY_TGA #define STBI_NO_TGA #endif #ifndef STBI_ONLY_GIF #define STBI_NO_GIF #endif #ifndef STBI_ONLY_HDR #define STBI_NO_HDR #endif #ifndef STBI_ONLY_PIC #define STBI_NO_PIC #endif #ifndef STBI_ONLY_PNM #define STBI_NO_PNM #endif #endif #if defined(STBI_NO_PNG) && !defined(STBI_SUPPORT_ZLIB) && !defined(STBI_NO_ZLIB) #define STBI_NO_ZLIB #endif #include #include // ptrdiff_t on osx #include #include #include #if !defined(STBI_NO_LINEAR) || !defined(STBI_NO_HDR) #include // ldexp, pow #endif #ifndef STBI_NO_STDIO #include #endif #ifndef STBI_ASSERT #include #define STBI_ASSERT(x) assert(x) #endif #ifdef __cplusplus #define STBI_EXTERN extern "C" #else #define STBI_EXTERN extern #endif #ifndef _MSC_VER #ifdef __cplusplus #define stbi_inline inline #else #define stbi_inline #endif #else #define stbi_inline __forceinline #endif #ifndef STBI_NO_THREAD_LOCALS #if defined(__cplusplus) && __cplusplus >= 201103L #define STBI_THREAD_LOCAL thread_local #elif defined(__GNUC__) && __GNUC__ < 5 #define STBI_THREAD_LOCAL __thread #elif defined(_MSC_VER) #define STBI_THREAD_LOCAL __declspec(thread) #elif defined (__STDC_VERSION__) && __STDC_VERSION__ >= 201112L && !defined(__STDC_NO_THREADS__) #define STBI_THREAD_LOCAL _Thread_local #endif #ifndef STBI_THREAD_LOCAL #if defined(__GNUC__) #define STBI_THREAD_LOCAL __thread #endif #endif #endif #if defined(_MSC_VER) || defined(__SYMBIAN32__) typedef unsigned short stbi__uint16; typedef signed short stbi__int16; typedef unsigned int stbi__uint32; typedef signed int stbi__int32; #else #include typedef uint16_t stbi__uint16; typedef int16_t stbi__int16; typedef uint32_t stbi__uint32; typedef int32_t stbi__int32; #endif // should produce compiler error if size is wrong typedef unsigned char validate_uint32[sizeof(stbi__uint32)==4 ? 1 : -1]; #ifdef _MSC_VER #define STBI_NOTUSED(v) (void)(v) #else #define STBI_NOTUSED(v) (void)sizeof(v) #endif #ifdef _MSC_VER #define STBI_HAS_LROTL #endif #ifdef STBI_HAS_LROTL #define stbi_lrot(x,y) _lrotl(x,y) #else #define stbi_lrot(x,y) (((x) << (y)) | ((x) >> (-(y) & 31))) #endif #if defined(STBI_MALLOC) && defined(STBI_FREE) && (defined(STBI_REALLOC) || defined(STBI_REALLOC_SIZED)) // ok #elif !defined(STBI_MALLOC) && !defined(STBI_FREE) && !defined(STBI_REALLOC) && !defined(STBI_REALLOC_SIZED) // ok #else #error "Must define all or none of STBI_MALLOC, STBI_FREE, and STBI_REALLOC (or STBI_REALLOC_SIZED)." #endif #ifndef STBI_MALLOC #define STBI_MALLOC(sz) malloc(sz) #define STBI_REALLOC(p,newsz) realloc(p,newsz) #define STBI_FREE(p) free(p) #endif #ifndef STBI_REALLOC_SIZED #define STBI_REALLOC_SIZED(p,oldsz,newsz) STBI_REALLOC(p,newsz) #endif // x86/x64 detection #if defined(__x86_64__) || defined(_M_X64) #define STBI__X64_TARGET #elif defined(__i386) || defined(_M_IX86) #define STBI__X86_TARGET #endif #if defined(__GNUC__) && defined(STBI__X86_TARGET) && !defined(__SSE2__) && !defined(STBI_NO_SIMD) // gcc doesn't support sse2 intrinsics unless you compile with -msse2, // which in turn means it gets to use SSE2 everywhere. This is unfortunate, // but previous attempts to provide the SSE2 functions with runtime // detection caused numerous issues. The way architecture extensions are // exposed in GCC/Clang is, sadly, not really suited for one-file libs. // New behavior: if compiled with -msse2, we use SSE2 without any // detection; if not, we don't use it at all. #define STBI_NO_SIMD #endif #if defined(__MINGW32__) && defined(STBI__X86_TARGET) && !defined(STBI_MINGW_ENABLE_SSE2) && !defined(STBI_NO_SIMD) // Note that __MINGW32__ doesn't actually mean 32-bit, so we have to avoid STBI__X64_TARGET // // 32-bit MinGW wants ESP to be 16-byte aligned, but this is not in the // Windows ABI and VC++ as well as Windows DLLs don't maintain that invariant. // As a result, enabling SSE2 on 32-bit MinGW is dangerous when not // simultaneously enabling "-mstackrealign". // // See https://github.com/nothings/stb/issues/81 for more information. // // So default to no SSE2 on 32-bit MinGW. If you've read this far and added // -mstackrealign to your build settings, feel free to #define STBI_MINGW_ENABLE_SSE2. #define STBI_NO_SIMD #endif #if !defined(STBI_NO_SIMD) && (defined(STBI__X86_TARGET) || defined(STBI__X64_TARGET)) #define STBI_SSE2 #include #ifdef _MSC_VER #if _MSC_VER >= 1400 // not VC6 #include // __cpuid static int stbi__cpuid3(void) { int info[4]; __cpuid(info,1); return info[3]; } #else static int stbi__cpuid3(void) { int res; __asm { mov eax,1 cpuid mov res,edx } return res; } #endif #define STBI_SIMD_ALIGN(type, name) __declspec(align(16)) type name #if !defined(STBI_NO_JPEG) && defined(STBI_SSE2) static int stbi__sse2_available(void) { int info3 = stbi__cpuid3(); return ((info3 >> 26) & 1) != 0; } #endif #else // assume GCC-style if not VC++ #define STBI_SIMD_ALIGN(type, name) type name __attribute__((aligned(16))) #if !defined(STBI_NO_JPEG) && defined(STBI_SSE2) static int stbi__sse2_available(void) { // If we're even attempting to compile this on GCC/Clang, that means // -msse2 is on, which means the compiler is allowed to use SSE2 // instructions at will, and so are we. return 1; } #endif #endif #endif // ARM NEON #if defined(STBI_NO_SIMD) && defined(STBI_NEON) #undef STBI_NEON #endif #ifdef STBI_NEON #include #ifdef _MSC_VER #define STBI_SIMD_ALIGN(type, name) __declspec(align(16)) type name #else #define STBI_SIMD_ALIGN(type, name) type name __attribute__((aligned(16))) #endif #endif #ifndef STBI_SIMD_ALIGN #define STBI_SIMD_ALIGN(type, name) type name #endif #ifndef STBI_MAX_DIMENSIONS #define STBI_MAX_DIMENSIONS (1 << 24) #endif /////////////////////////////////////////////// // // stbi__context struct and start_xxx functions // stbi__context structure is our basic context used by all images, so it // contains all the IO context, plus some basic image information typedef struct { stbi__uint32 img_x, img_y; int img_n, img_out_n; stbi_io_callbacks io; void *io_user_data; int read_from_callbacks; int buflen; stbi_uc buffer_start[128]; int callback_already_read; stbi_uc *img_buffer, *img_buffer_end; stbi_uc *img_buffer_original, *img_buffer_original_end; } stbi__context; static void stbi__refill_buffer(stbi__context *s); // initialize a memory-decode context static void stbi__start_mem(stbi__context *s, stbi_uc const *buffer, int len) { s->io.read = NULL; s->read_from_callbacks = 0; s->callback_already_read = 0; s->img_buffer = s->img_buffer_original = (stbi_uc *) buffer; s->img_buffer_end = s->img_buffer_original_end = (stbi_uc *) buffer+len; } // initialize a callback-based context static void stbi__start_callbacks(stbi__context *s, stbi_io_callbacks *c, void *user) { s->io = *c; s->io_user_data = user; s->buflen = sizeof(s->buffer_start); s->read_from_callbacks = 1; s->callback_already_read = 0; s->img_buffer = s->img_buffer_original = s->buffer_start; stbi__refill_buffer(s); s->img_buffer_original_end = s->img_buffer_end; } #ifndef STBI_NO_STDIO static int stbi__stdio_read(void *user, char *data, int size) { return (int) fread(data,1,size,(FILE*) user); } static void stbi__stdio_skip(void *user, int n) { int ch; fseek((FILE*) user, n, SEEK_CUR); ch = fgetc((FILE*) user); /* have to read a byte to reset feof()'s flag */ if (ch != EOF) { ungetc(ch, (FILE *) user); /* push byte back onto stream if valid. */ } } static int stbi__stdio_eof(void *user) { return feof((FILE*) user) || ferror((FILE *) user); } static stbi_io_callbacks stbi__stdio_callbacks = { stbi__stdio_read, stbi__stdio_skip, stbi__stdio_eof, }; static void stbi__start_file(stbi__context *s, FILE *f) { stbi__start_callbacks(s, &stbi__stdio_callbacks, (void *) f); } //static void stop_file(stbi__context *s) { } #endif // !STBI_NO_STDIO static void stbi__rewind(stbi__context *s) { // conceptually rewind SHOULD rewind to the beginning of the stream, // but we just rewind to the beginning of the initial buffer, because // we only use it after doing 'test', which only ever looks at at most 92 bytes s->img_buffer = s->img_buffer_original; s->img_buffer_end = s->img_buffer_original_end; } enum { STBI_ORDER_RGB, STBI_ORDER_BGR }; typedef struct { int bits_per_channel; int num_channels; int channel_order; } stbi__result_info; #ifndef STBI_NO_JPEG static int stbi__jpeg_test(stbi__context *s); static void *stbi__jpeg_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); static int stbi__jpeg_info(stbi__context *s, int *x, int *y, int *comp); #endif #ifndef STBI_NO_PNG static int stbi__png_test(stbi__context *s); static void *stbi__png_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); static int stbi__png_info(stbi__context *s, int *x, int *y, int *comp); static int stbi__png_is16(stbi__context *s); #endif #ifndef STBI_NO_BMP static int stbi__bmp_test(stbi__context *s); static void *stbi__bmp_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); static int stbi__bmp_info(stbi__context *s, int *x, int *y, int *comp); #endif #ifndef STBI_NO_TGA static int stbi__tga_test(stbi__context *s); static void *stbi__tga_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); static int stbi__tga_info(stbi__context *s, int *x, int *y, int *comp); #endif #ifndef STBI_NO_PSD static int stbi__psd_test(stbi__context *s); static void *stbi__psd_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri, int bpc); static int stbi__psd_info(stbi__context *s, int *x, int *y, int *comp); static int stbi__psd_is16(stbi__context *s); #endif #ifndef STBI_NO_HDR static int stbi__hdr_test(stbi__context *s); static float *stbi__hdr_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); static int stbi__hdr_info(stbi__context *s, int *x, int *y, int *comp); #endif #ifndef STBI_NO_PIC static int stbi__pic_test(stbi__context *s); static void *stbi__pic_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); static int stbi__pic_info(stbi__context *s, int *x, int *y, int *comp); #endif #ifndef STBI_NO_GIF static int stbi__gif_test(stbi__context *s); static void *stbi__gif_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); static void *stbi__load_gif_main(stbi__context *s, int **delays, int *x, int *y, int *z, int *comp, int req_comp); static int stbi__gif_info(stbi__context *s, int *x, int *y, int *comp); #endif #ifndef STBI_NO_PNM static int stbi__pnm_test(stbi__context *s); static void *stbi__pnm_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); static int stbi__pnm_info(stbi__context *s, int *x, int *y, int *comp); static int stbi__pnm_is16(stbi__context *s); #endif static #ifdef STBI_THREAD_LOCAL STBI_THREAD_LOCAL #endif const char *stbi__g_failure_reason; STBIDEF const char *stbi_failure_reason(void) { return stbi__g_failure_reason; } #ifndef STBI_NO_FAILURE_STRINGS static int stbi__err(const char *str) { stbi__g_failure_reason = str; return 0; } #endif static void *stbi__malloc(size_t size) { return STBI_MALLOC(size); } // stb_image uses ints pervasively, including for offset calculations. // therefore the largest decoded image size we can support with the // current code, even on 64-bit targets, is INT_MAX. this is not a // significant limitation for the intended use case. // // we do, however, need to make sure our size calculations don't // overflow. hence a few helper functions for size calculations that // multiply integers together, making sure that they're non-negative // and no overflow occurs. // return 1 if the sum is valid, 0 on overflow. // negative terms are considered invalid. static int stbi__addsizes_valid(int a, int b) { if (b < 0) return 0; // now 0 <= b <= INT_MAX, hence also // 0 <= INT_MAX - b <= INTMAX. // And "a + b <= INT_MAX" (which might overflow) is the // same as a <= INT_MAX - b (no overflow) return a <= INT_MAX - b; } // returns 1 if the product is valid, 0 on overflow. // negative factors are considered invalid. static int stbi__mul2sizes_valid(int a, int b) { if (a < 0 || b < 0) return 0; if (b == 0) return 1; // mul-by-0 is always safe // portable way to check for no overflows in a*b return a <= INT_MAX/b; } #if !defined(STBI_NO_JPEG) || !defined(STBI_NO_PNG) || !defined(STBI_NO_TGA) || !defined(STBI_NO_HDR) // returns 1 if "a*b + add" has no negative terms/factors and doesn't overflow static int stbi__mad2sizes_valid(int a, int b, int add) { return stbi__mul2sizes_valid(a, b) && stbi__addsizes_valid(a*b, add); } #endif // returns 1 if "a*b*c + add" has no negative terms/factors and doesn't overflow static int stbi__mad3sizes_valid(int a, int b, int c, int add) { return stbi__mul2sizes_valid(a, b) && stbi__mul2sizes_valid(a*b, c) && stbi__addsizes_valid(a*b*c, add); } // returns 1 if "a*b*c*d + add" has no negative terms/factors and doesn't overflow #if !defined(STBI_NO_LINEAR) || !defined(STBI_NO_HDR) || !defined(STBI_NO_PNM) static int stbi__mad4sizes_valid(int a, int b, int c, int d, int add) { return stbi__mul2sizes_valid(a, b) && stbi__mul2sizes_valid(a*b, c) && stbi__mul2sizes_valid(a*b*c, d) && stbi__addsizes_valid(a*b*c*d, add); } #endif #if !defined(STBI_NO_JPEG) || !defined(STBI_NO_PNG) || !defined(STBI_NO_TGA) || !defined(STBI_NO_HDR) // mallocs with size overflow checking static void *stbi__malloc_mad2(int a, int b, int add) { if (!stbi__mad2sizes_valid(a, b, add)) return NULL; return stbi__malloc(a*b + add); } #endif static void *stbi__malloc_mad3(int a, int b, int c, int add) { if (!stbi__mad3sizes_valid(a, b, c, add)) return NULL; return stbi__malloc(a*b*c + add); } #if !defined(STBI_NO_LINEAR) || !defined(STBI_NO_HDR) || !defined(STBI_NO_PNM) static void *stbi__malloc_mad4(int a, int b, int c, int d, int add) { if (!stbi__mad4sizes_valid(a, b, c, d, add)) return NULL; return stbi__malloc(a*b*c*d + add); } #endif // returns 1 if the sum of two signed ints is valid (between -2^31 and 2^31-1 inclusive), 0 on overflow. static int stbi__addints_valid(int a, int b) { if ((a >= 0) != (b >= 0)) return 1; // a and b have different signs, so no overflow if (a < 0 && b < 0) return a >= INT_MIN - b; // same as a + b >= INT_MIN; INT_MIN - b cannot overflow since b < 0. return a <= INT_MAX - b; } // returns 1 if the product of two ints fits in a signed short, 0 on overflow. static int stbi__mul2shorts_valid(int a, int b) { if (b == 0 || b == -1) return 1; // multiplication by 0 is always 0; check for -1 so SHRT_MIN/b doesn't overflow if ((a >= 0) == (b >= 0)) return a <= SHRT_MAX/b; // product is positive, so similar to mul2sizes_valid if (b < 0) return a <= SHRT_MIN / b; // same as a * b >= SHRT_MIN return a >= SHRT_MIN / b; } // stbi__err - error // stbi__errpf - error returning pointer to float // stbi__errpuc - error returning pointer to unsigned char #ifdef STBI_NO_FAILURE_STRINGS #define stbi__err(x,y) 0 #elif defined(STBI_FAILURE_USERMSG) #define stbi__err(x,y) stbi__err(y) #else #define stbi__err(x,y) stbi__err(x) #endif #define stbi__errpf(x,y) ((float *)(size_t) (stbi__err(x,y)?NULL:NULL)) #define stbi__errpuc(x,y) ((unsigned char *)(size_t) (stbi__err(x,y)?NULL:NULL)) STBIDEF void stbi_image_free(void *retval_from_stbi_load) { STBI_FREE(retval_from_stbi_load); } #ifndef STBI_NO_LINEAR static float *stbi__ldr_to_hdr(stbi_uc *data, int x, int y, int comp); #endif #ifndef STBI_NO_HDR static stbi_uc *stbi__hdr_to_ldr(float *data, int x, int y, int comp); #endif static int stbi__vertically_flip_on_load_global = 0; STBIDEF void stbi_set_flip_vertically_on_load(int flag_true_if_should_flip) { stbi__vertically_flip_on_load_global = flag_true_if_should_flip; } #ifndef STBI_THREAD_LOCAL #define stbi__vertically_flip_on_load stbi__vertically_flip_on_load_global #else static STBI_THREAD_LOCAL int stbi__vertically_flip_on_load_local, stbi__vertically_flip_on_load_set; STBIDEF void stbi_set_flip_vertically_on_load_thread(int flag_true_if_should_flip) { stbi__vertically_flip_on_load_local = flag_true_if_should_flip; stbi__vertically_flip_on_load_set = 1; } #define stbi__vertically_flip_on_load (stbi__vertically_flip_on_load_set \ ? stbi__vertically_flip_on_load_local \ : stbi__vertically_flip_on_load_global) #endif // STBI_THREAD_LOCAL static void *stbi__load_main(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri, int bpc) { memset(ri, 0, sizeof(*ri)); // make sure it's initialized if we add new fields ri->bits_per_channel = 8; // default is 8 so most paths don't have to be changed ri->channel_order = STBI_ORDER_RGB; // all current input & output are this, but this is here so we can add BGR order ri->num_channels = 0; // test the formats with a very explicit header first (at least a FOURCC // or distinctive magic number first) #ifndef STBI_NO_PNG if (stbi__png_test(s)) return stbi__png_load(s,x,y,comp,req_comp, ri); #endif #ifndef STBI_NO_BMP if (stbi__bmp_test(s)) return stbi__bmp_load(s,x,y,comp,req_comp, ri); #endif #ifndef STBI_NO_GIF if (stbi__gif_test(s)) return stbi__gif_load(s,x,y,comp,req_comp, ri); #endif #ifndef STBI_NO_PSD if (stbi__psd_test(s)) return stbi__psd_load(s,x,y,comp,req_comp, ri, bpc); #else STBI_NOTUSED(bpc); #endif #ifndef STBI_NO_PIC if (stbi__pic_test(s)) return stbi__pic_load(s,x,y,comp,req_comp, ri); #endif // then the formats that can end up attempting to load with just 1 or 2 // bytes matching expectations; these are prone to false positives, so // try them later #ifndef STBI_NO_JPEG if (stbi__jpeg_test(s)) return stbi__jpeg_load(s,x,y,comp,req_comp, ri); #endif #ifndef STBI_NO_PNM if (stbi__pnm_test(s)) return stbi__pnm_load(s,x,y,comp,req_comp, ri); #endif #ifndef STBI_NO_HDR if (stbi__hdr_test(s)) { float *hdr = stbi__hdr_load(s, x,y,comp,req_comp, ri); return stbi__hdr_to_ldr(hdr, *x, *y, req_comp ? req_comp : *comp); } #endif #ifndef STBI_NO_TGA // test tga last because it's a crappy test! if (stbi__tga_test(s)) return stbi__tga_load(s,x,y,comp,req_comp, ri); #endif return stbi__errpuc("unknown image type", "Image not of any known type, or corrupt"); } static stbi_uc *stbi__convert_16_to_8(stbi__uint16 *orig, int w, int h, int channels) { int i; int img_len = w * h * channels; stbi_uc *reduced; reduced = (stbi_uc *) stbi__malloc(img_len); if (reduced == NULL) return stbi__errpuc("outofmem", "Out of memory"); for (i = 0; i < img_len; ++i) reduced[i] = (stbi_uc)((orig[i] >> 8) & 0xFF); // top half of each byte is sufficient approx of 16->8 bit scaling STBI_FREE(orig); return reduced; } static stbi__uint16 *stbi__convert_8_to_16(stbi_uc *orig, int w, int h, int channels) { int i; int img_len = w * h * channels; stbi__uint16 *enlarged; enlarged = (stbi__uint16 *) stbi__malloc(img_len*2); if (enlarged == NULL) return (stbi__uint16 *) stbi__errpuc("outofmem", "Out of memory"); for (i = 0; i < img_len; ++i) enlarged[i] = (stbi__uint16)((orig[i] << 8) + orig[i]); // replicate to high and low byte, maps 0->0, 255->0xffff STBI_FREE(orig); return enlarged; } static void stbi__vertical_flip(void *image, int w, int h, int bytes_per_pixel) { int row; size_t bytes_per_row = (size_t)w * bytes_per_pixel; stbi_uc temp[2048]; stbi_uc *bytes = (stbi_uc *)image; for (row = 0; row < (h>>1); row++) { stbi_uc *row0 = bytes + row*bytes_per_row; stbi_uc *row1 = bytes + (h - row - 1)*bytes_per_row; // swap row0 with row1 size_t bytes_left = bytes_per_row; while (bytes_left) { size_t bytes_copy = (bytes_left < sizeof(temp)) ? bytes_left : sizeof(temp); memcpy(temp, row0, bytes_copy); memcpy(row0, row1, bytes_copy); memcpy(row1, temp, bytes_copy); row0 += bytes_copy; row1 += bytes_copy; bytes_left -= bytes_copy; } } } #ifndef STBI_NO_GIF static void stbi__vertical_flip_slices(void *image, int w, int h, int z, int bytes_per_pixel) { int slice; int slice_size = w * h * bytes_per_pixel; stbi_uc *bytes = (stbi_uc *)image; for (slice = 0; slice < z; ++slice) { stbi__vertical_flip(bytes, w, h, bytes_per_pixel); bytes += slice_size; } } #endif static unsigned char *stbi__load_and_postprocess_8bit(stbi__context *s, int *x, int *y, int *comp, int req_comp) { stbi__result_info ri; void *result = stbi__load_main(s, x, y, comp, req_comp, &ri, 8); if (result == NULL) return NULL; // it is the responsibility of the loaders to make sure we get either 8 or 16 bit. STBI_ASSERT(ri.bits_per_channel == 8 || ri.bits_per_channel == 16); if (ri.bits_per_channel != 8) { result = stbi__convert_16_to_8((stbi__uint16 *) result, *x, *y, req_comp == 0 ? *comp : req_comp); ri.bits_per_channel = 8; } // @TODO: move stbi__convert_format to here if (stbi__vertically_flip_on_load) { int channels = req_comp ? req_comp : *comp; stbi__vertical_flip(result, *x, *y, channels * sizeof(stbi_uc)); } return (unsigned char *) result; } static stbi__uint16 *stbi__load_and_postprocess_16bit(stbi__context *s, int *x, int *y, int *comp, int req_comp) { stbi__result_info ri; void *result = stbi__load_main(s, x, y, comp, req_comp, &ri, 16); if (result == NULL) return NULL; // it is the responsibility of the loaders to make sure we get either 8 or 16 bit. STBI_ASSERT(ri.bits_per_channel == 8 || ri.bits_per_channel == 16); if (ri.bits_per_channel != 16) { result = stbi__convert_8_to_16((stbi_uc *) result, *x, *y, req_comp == 0 ? *comp : req_comp); ri.bits_per_channel = 16; } // @TODO: move stbi__convert_format16 to here // @TODO: special case RGB-to-Y (and RGBA-to-YA) for 8-bit-to-16-bit case to keep more precision if (stbi__vertically_flip_on_load) { int channels = req_comp ? req_comp : *comp; stbi__vertical_flip(result, *x, *y, channels * sizeof(stbi__uint16)); } return (stbi__uint16 *) result; } #if !defined(STBI_NO_HDR) && !defined(STBI_NO_LINEAR) static void stbi__float_postprocess(float *result, int *x, int *y, int *comp, int req_comp) { if (stbi__vertically_flip_on_load && result != NULL) { int channels = req_comp ? req_comp : *comp; stbi__vertical_flip(result, *x, *y, channels * sizeof(float)); } } #endif #ifndef STBI_NO_STDIO #if defined(_WIN32) && defined(STBI_WINDOWS_UTF8) STBI_EXTERN __declspec(dllimport) int __stdcall MultiByteToWideChar(unsigned int cp, unsigned long flags, const char *str, int cbmb, wchar_t *widestr, int cchwide); STBI_EXTERN __declspec(dllimport) int __stdcall WideCharToMultiByte(unsigned int cp, unsigned long flags, const wchar_t *widestr, int cchwide, char *str, int cbmb, const char *defchar, int *used_default); #endif #if defined(_WIN32) && defined(STBI_WINDOWS_UTF8) STBIDEF int stbi_convert_wchar_to_utf8(char *buffer, size_t bufferlen, const wchar_t* input) { return WideCharToMultiByte(65001 /* UTF8 */, 0, input, -1, buffer, (int) bufferlen, NULL, NULL); } #endif static FILE *stbi__fopen(char const *filename, char const *mode) { FILE *f; #if defined(_WIN32) && defined(STBI_WINDOWS_UTF8) wchar_t wMode[64]; wchar_t wFilename[1024]; if (0 == MultiByteToWideChar(65001 /* UTF8 */, 0, filename, -1, wFilename, sizeof(wFilename)/sizeof(*wFilename))) return 0; if (0 == MultiByteToWideChar(65001 /* UTF8 */, 0, mode, -1, wMode, sizeof(wMode)/sizeof(*wMode))) return 0; #if defined(_MSC_VER) && _MSC_VER >= 1400 if (0 != _wfopen_s(&f, wFilename, wMode)) f = 0; #else f = _wfopen(wFilename, wMode); #endif #elif defined(_MSC_VER) && _MSC_VER >= 1400 if (0 != fopen_s(&f, filename, mode)) f=0; #else f = fopen(filename, mode); #endif return f; } STBIDEF stbi_uc *stbi_load(char const *filename, int *x, int *y, int *comp, int req_comp) { FILE *f = stbi__fopen(filename, "rb"); unsigned char *result; if (!f) return stbi__errpuc("can't fopen", "Unable to open file"); result = stbi_load_from_file(f,x,y,comp,req_comp); fclose(f); return result; } STBIDEF stbi_uc *stbi_load_from_file(FILE *f, int *x, int *y, int *comp, int req_comp) { unsigned char *result; stbi__context s; stbi__start_file(&s,f); result = stbi__load_and_postprocess_8bit(&s,x,y,comp,req_comp); if (result) { // need to 'unget' all the characters in the IO buffer fseek(f, - (int) (s.img_buffer_end - s.img_buffer), SEEK_CUR); } return result; } STBIDEF stbi__uint16 *stbi_load_from_file_16(FILE *f, int *x, int *y, int *comp, int req_comp) { stbi__uint16 *result; stbi__context s; stbi__start_file(&s,f); result = stbi__load_and_postprocess_16bit(&s,x,y,comp,req_comp); if (result) { // need to 'unget' all the characters in the IO buffer fseek(f, - (int) (s.img_buffer_end - s.img_buffer), SEEK_CUR); } return result; } STBIDEF stbi_us *stbi_load_16(char const *filename, int *x, int *y, int *comp, int req_comp) { FILE *f = stbi__fopen(filename, "rb"); stbi__uint16 *result; if (!f) return (stbi_us *) stbi__errpuc("can't fopen", "Unable to open file"); result = stbi_load_from_file_16(f,x,y,comp,req_comp); fclose(f); return result; } #endif //!STBI_NO_STDIO STBIDEF stbi_us *stbi_load_16_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *channels_in_file, int desired_channels) { stbi__context s; stbi__start_mem(&s,buffer,len); return stbi__load_and_postprocess_16bit(&s,x,y,channels_in_file,desired_channels); } STBIDEF stbi_us *stbi_load_16_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *channels_in_file, int desired_channels) { stbi__context s; stbi__start_callbacks(&s, (stbi_io_callbacks *)clbk, user); return stbi__load_and_postprocess_16bit(&s,x,y,channels_in_file,desired_channels); } STBIDEF stbi_uc *stbi_load_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp) { stbi__context s; stbi__start_mem(&s,buffer,len); return stbi__load_and_postprocess_8bit(&s,x,y,comp,req_comp); } STBIDEF stbi_uc *stbi_load_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp, int req_comp) { stbi__context s; stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user); return stbi__load_and_postprocess_8bit(&s,x,y,comp,req_comp); } #ifndef STBI_NO_GIF STBIDEF stbi_uc *stbi_load_gif_from_memory(stbi_uc const *buffer, int len, int **delays, int *x, int *y, int *z, int *comp, int req_comp) { unsigned char *result; stbi__context s; stbi__start_mem(&s,buffer,len); result = (unsigned char*) stbi__load_gif_main(&s, delays, x, y, z, comp, req_comp); if (stbi__vertically_flip_on_load) { stbi__vertical_flip_slices( result, *x, *y, *z, *comp ); } return result; } #endif #ifndef STBI_NO_LINEAR static float *stbi__loadf_main(stbi__context *s, int *x, int *y, int *comp, int req_comp) { unsigned char *data; #ifndef STBI_NO_HDR if (stbi__hdr_test(s)) { stbi__result_info ri; float *hdr_data = stbi__hdr_load(s,x,y,comp,req_comp, &ri); if (hdr_data) stbi__float_postprocess(hdr_data,x,y,comp,req_comp); return hdr_data; } #endif data = stbi__load_and_postprocess_8bit(s, x, y, comp, req_comp); if (data) return stbi__ldr_to_hdr(data, *x, *y, req_comp ? req_comp : *comp); return stbi__errpf("unknown image type", "Image not of any known type, or corrupt"); } STBIDEF float *stbi_loadf_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp) { stbi__context s; stbi__start_mem(&s,buffer,len); return stbi__loadf_main(&s,x,y,comp,req_comp); } STBIDEF float *stbi_loadf_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp, int req_comp) { stbi__context s; stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user); return stbi__loadf_main(&s,x,y,comp,req_comp); } #ifndef STBI_NO_STDIO STBIDEF float *stbi_loadf(char const *filename, int *x, int *y, int *comp, int req_comp) { float *result; FILE *f = stbi__fopen(filename, "rb"); if (!f) return stbi__errpf("can't fopen", "Unable to open file"); result = stbi_loadf_from_file(f,x,y,comp,req_comp); fclose(f); return result; } STBIDEF float *stbi_loadf_from_file(FILE *f, int *x, int *y, int *comp, int req_comp) { stbi__context s; stbi__start_file(&s,f); return stbi__loadf_main(&s,x,y,comp,req_comp); } #endif // !STBI_NO_STDIO #endif // !STBI_NO_LINEAR // these is-hdr-or-not is defined independent of whether STBI_NO_LINEAR is // defined, for API simplicity; if STBI_NO_LINEAR is defined, it always // reports false! STBIDEF int stbi_is_hdr_from_memory(stbi_uc const *buffer, int len) { #ifndef STBI_NO_HDR stbi__context s; stbi__start_mem(&s,buffer,len); return stbi__hdr_test(&s); #else STBI_NOTUSED(buffer); STBI_NOTUSED(len); return 0; #endif } #ifndef STBI_NO_STDIO STBIDEF int stbi_is_hdr (char const *filename) { FILE *f = stbi__fopen(filename, "rb"); int result=0; if (f) { result = stbi_is_hdr_from_file(f); fclose(f); } return result; } STBIDEF int stbi_is_hdr_from_file(FILE *f) { #ifndef STBI_NO_HDR long pos = ftell(f); int res; stbi__context s; stbi__start_file(&s,f); res = stbi__hdr_test(&s); fseek(f, pos, SEEK_SET); return res; #else STBI_NOTUSED(f); return 0; #endif } #endif // !STBI_NO_STDIO STBIDEF int stbi_is_hdr_from_callbacks(stbi_io_callbacks const *clbk, void *user) { #ifndef STBI_NO_HDR stbi__context s; stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user); return stbi__hdr_test(&s); #else STBI_NOTUSED(clbk); STBI_NOTUSED(user); return 0; #endif } #ifndef STBI_NO_LINEAR static float stbi__l2h_gamma=2.2f, stbi__l2h_scale=1.0f; STBIDEF void stbi_ldr_to_hdr_gamma(float gamma) { stbi__l2h_gamma = gamma; } STBIDEF void stbi_ldr_to_hdr_scale(float scale) { stbi__l2h_scale = scale; } #endif static float stbi__h2l_gamma_i=1.0f/2.2f, stbi__h2l_scale_i=1.0f; STBIDEF void stbi_hdr_to_ldr_gamma(float gamma) { stbi__h2l_gamma_i = 1/gamma; } STBIDEF void stbi_hdr_to_ldr_scale(float scale) { stbi__h2l_scale_i = 1/scale; } ////////////////////////////////////////////////////////////////////////////// // // Common code used by all image loaders // enum { STBI__SCAN_load=0, STBI__SCAN_type, STBI__SCAN_header }; static void stbi__refill_buffer(stbi__context *s) { int n = (s->io.read)(s->io_user_data,(char*)s->buffer_start,s->buflen); s->callback_already_read += (int) (s->img_buffer - s->img_buffer_original); if (n == 0) { // at end of file, treat same as if from memory, but need to handle case // where s->img_buffer isn't pointing to safe memory, e.g. 0-byte file s->read_from_callbacks = 0; s->img_buffer = s->buffer_start; s->img_buffer_end = s->buffer_start+1; *s->img_buffer = 0; } else { s->img_buffer = s->buffer_start; s->img_buffer_end = s->buffer_start + n; } } stbi_inline static stbi_uc stbi__get8(stbi__context *s) { if (s->img_buffer < s->img_buffer_end) return *s->img_buffer++; if (s->read_from_callbacks) { stbi__refill_buffer(s); return *s->img_buffer++; } return 0; } #if defined(STBI_NO_JPEG) && defined(STBI_NO_HDR) && defined(STBI_NO_PIC) && defined(STBI_NO_PNM) // nothing #else stbi_inline static int stbi__at_eof(stbi__context *s) { if (s->io.read) { if (!(s->io.eof)(s->io_user_data)) return 0; // if feof() is true, check if buffer = end // special case: we've only got the special 0 character at the end if (s->read_from_callbacks == 0) return 1; } return s->img_buffer >= s->img_buffer_end; } #endif #if defined(STBI_NO_JPEG) && defined(STBI_NO_PNG) && defined(STBI_NO_BMP) && defined(STBI_NO_PSD) && defined(STBI_NO_TGA) && defined(STBI_NO_GIF) && defined(STBI_NO_PIC) // nothing #else static void stbi__skip(stbi__context *s, int n) { if (n == 0) return; // already there! if (n < 0) { s->img_buffer = s->img_buffer_end; return; } if (s->io.read) { int blen = (int) (s->img_buffer_end - s->img_buffer); if (blen < n) { s->img_buffer = s->img_buffer_end; (s->io.skip)(s->io_user_data, n - blen); return; } } s->img_buffer += n; } #endif #if defined(STBI_NO_PNG) && defined(STBI_NO_TGA) && defined(STBI_NO_HDR) && defined(STBI_NO_PNM) // nothing #else static int stbi__getn(stbi__context *s, stbi_uc *buffer, int n) { if (s->io.read) { int blen = (int) (s->img_buffer_end - s->img_buffer); if (blen < n) { int res, count; memcpy(buffer, s->img_buffer, blen); count = (s->io.read)(s->io_user_data, (char*) buffer + blen, n - blen); res = (count == (n-blen)); s->img_buffer = s->img_buffer_end; return res; } } if (s->img_buffer+n <= s->img_buffer_end) { memcpy(buffer, s->img_buffer, n); s->img_buffer += n; return 1; } else return 0; } #endif #if defined(STBI_NO_JPEG) && defined(STBI_NO_PNG) && defined(STBI_NO_PSD) && defined(STBI_NO_PIC) // nothing #else static int stbi__get16be(stbi__context *s) { int z = stbi__get8(s); return (z << 8) + stbi__get8(s); } #endif #if defined(STBI_NO_PNG) && defined(STBI_NO_PSD) && defined(STBI_NO_PIC) // nothing #else static stbi__uint32 stbi__get32be(stbi__context *s) { stbi__uint32 z = stbi__get16be(s); return (z << 16) + stbi__get16be(s); } #endif #if defined(STBI_NO_BMP) && defined(STBI_NO_TGA) && defined(STBI_NO_GIF) // nothing #else static int stbi__get16le(stbi__context *s) { int z = stbi__get8(s); return z + (stbi__get8(s) << 8); } #endif #ifndef STBI_NO_BMP static stbi__uint32 stbi__get32le(stbi__context *s) { stbi__uint32 z = stbi__get16le(s); z += (stbi__uint32)stbi__get16le(s) << 16; return z; } #endif #define STBI__BYTECAST(x) ((stbi_uc) ((x) & 255)) // truncate int to byte without warnings #if defined(STBI_NO_JPEG) && defined(STBI_NO_PNG) && defined(STBI_NO_BMP) && defined(STBI_NO_PSD) && defined(STBI_NO_TGA) && defined(STBI_NO_GIF) && defined(STBI_NO_PIC) && defined(STBI_NO_PNM) // nothing #else ////////////////////////////////////////////////////////////////////////////// // // generic converter from built-in img_n to req_comp // individual types do this automatically as much as possible (e.g. jpeg // does all cases internally since it needs to colorspace convert anyway, // and it never has alpha, so very few cases ). png can automatically // interleave an alpha=255 channel, but falls back to this for other cases // // assume data buffer is malloced, so malloc a new one and free that one // only failure mode is malloc failing static stbi_uc stbi__compute_y(int r, int g, int b) { return (stbi_uc) (((r*77) + (g*150) + (29*b)) >> 8); } #endif #if defined(STBI_NO_PNG) && defined(STBI_NO_BMP) && defined(STBI_NO_PSD) && defined(STBI_NO_TGA) && defined(STBI_NO_GIF) && defined(STBI_NO_PIC) && defined(STBI_NO_PNM) // nothing #else static unsigned char *stbi__convert_format(unsigned char *data, int img_n, int req_comp, unsigned int x, unsigned int y) { int i,j; unsigned char *good; if (req_comp == img_n) return data; STBI_ASSERT(req_comp >= 1 && req_comp <= 4); good = (unsigned char *) stbi__malloc_mad3(req_comp, x, y, 0); if (good == NULL) { STBI_FREE(data); return stbi__errpuc("outofmem", "Out of memory"); } for (j=0; j < (int) y; ++j) { unsigned char *src = data + j * x * img_n ; unsigned char *dest = good + j * x * req_comp; #define STBI__COMBO(a,b) ((a)*8+(b)) #define STBI__CASE(a,b) case STBI__COMBO(a,b): for(i=x-1; i >= 0; --i, src += a, dest += b) // convert source image with img_n components to one with req_comp components; // avoid switch per pixel, so use switch per scanline and massive macros switch (STBI__COMBO(img_n, req_comp)) { STBI__CASE(1,2) { dest[0]=src[0]; dest[1]=255; } break; STBI__CASE(1,3) { dest[0]=dest[1]=dest[2]=src[0]; } break; STBI__CASE(1,4) { dest[0]=dest[1]=dest[2]=src[0]; dest[3]=255; } break; STBI__CASE(2,1) { dest[0]=src[0]; } break; STBI__CASE(2,3) { dest[0]=dest[1]=dest[2]=src[0]; } break; STBI__CASE(2,4) { dest[0]=dest[1]=dest[2]=src[0]; dest[3]=src[1]; } break; STBI__CASE(3,4) { dest[0]=src[0];dest[1]=src[1];dest[2]=src[2];dest[3]=255; } break; STBI__CASE(3,1) { dest[0]=stbi__compute_y(src[0],src[1],src[2]); } break; STBI__CASE(3,2) { dest[0]=stbi__compute_y(src[0],src[1],src[2]); dest[1] = 255; } break; STBI__CASE(4,1) { dest[0]=stbi__compute_y(src[0],src[1],src[2]); } break; STBI__CASE(4,2) { dest[0]=stbi__compute_y(src[0],src[1],src[2]); dest[1] = src[3]; } break; STBI__CASE(4,3) { dest[0]=src[0];dest[1]=src[1];dest[2]=src[2]; } break; default: STBI_ASSERT(0); STBI_FREE(data); STBI_FREE(good); return stbi__errpuc("unsupported", "Unsupported format conversion"); } #undef STBI__CASE } STBI_FREE(data); return good; } #endif #if defined(STBI_NO_PNG) && defined(STBI_NO_PSD) // nothing #else static stbi__uint16 stbi__compute_y_16(int r, int g, int b) { return (stbi__uint16) (((r*77) + (g*150) + (29*b)) >> 8); } #endif #if defined(STBI_NO_PNG) && defined(STBI_NO_PSD) // nothing #else static stbi__uint16 *stbi__convert_format16(stbi__uint16 *data, int img_n, int req_comp, unsigned int x, unsigned int y) { int i,j; stbi__uint16 *good; if (req_comp == img_n) return data; STBI_ASSERT(req_comp >= 1 && req_comp <= 4); good = (stbi__uint16 *) stbi__malloc(req_comp * x * y * 2); if (good == NULL) { STBI_FREE(data); return (stbi__uint16 *) stbi__errpuc("outofmem", "Out of memory"); } for (j=0; j < (int) y; ++j) { stbi__uint16 *src = data + j * x * img_n ; stbi__uint16 *dest = good + j * x * req_comp; #define STBI__COMBO(a,b) ((a)*8+(b)) #define STBI__CASE(a,b) case STBI__COMBO(a,b): for(i=x-1; i >= 0; --i, src += a, dest += b) // convert source image with img_n components to one with req_comp components; // avoid switch per pixel, so use switch per scanline and massive macros switch (STBI__COMBO(img_n, req_comp)) { STBI__CASE(1,2) { dest[0]=src[0]; dest[1]=0xffff; } break; STBI__CASE(1,3) { dest[0]=dest[1]=dest[2]=src[0]; } break; STBI__CASE(1,4) { dest[0]=dest[1]=dest[2]=src[0]; dest[3]=0xffff; } break; STBI__CASE(2,1) { dest[0]=src[0]; } break; STBI__CASE(2,3) { dest[0]=dest[1]=dest[2]=src[0]; } break; STBI__CASE(2,4) { dest[0]=dest[1]=dest[2]=src[0]; dest[3]=src[1]; } break; STBI__CASE(3,4) { dest[0]=src[0];dest[1]=src[1];dest[2]=src[2];dest[3]=0xffff; } break; STBI__CASE(3,1) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]); } break; STBI__CASE(3,2) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]); dest[1] = 0xffff; } break; STBI__CASE(4,1) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]); } break; STBI__CASE(4,2) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]); dest[1] = src[3]; } break; STBI__CASE(4,3) { dest[0]=src[0];dest[1]=src[1];dest[2]=src[2]; } break; default: STBI_ASSERT(0); STBI_FREE(data); STBI_FREE(good); return (stbi__uint16*) stbi__errpuc("unsupported", "Unsupported format conversion"); } #undef STBI__CASE } STBI_FREE(data); return good; } #endif #ifndef STBI_NO_LINEAR static float *stbi__ldr_to_hdr(stbi_uc *data, int x, int y, int comp) { int i,k,n; float *output; if (!data) return NULL; output = (float *) stbi__malloc_mad4(x, y, comp, sizeof(float), 0); if (output == NULL) { STBI_FREE(data); return stbi__errpf("outofmem", "Out of memory"); } // compute number of non-alpha components if (comp & 1) n = comp; else n = comp-1; for (i=0; i < x*y; ++i) { for (k=0; k < n; ++k) { output[i*comp + k] = (float) (pow(data[i*comp+k]/255.0f, stbi__l2h_gamma) * stbi__l2h_scale); } } if (n < comp) { for (i=0; i < x*y; ++i) { output[i*comp + n] = data[i*comp + n]/255.0f; } } STBI_FREE(data); return output; } #endif #ifndef STBI_NO_HDR #define stbi__float2int(x) ((int) (x)) static stbi_uc *stbi__hdr_to_ldr(float *data, int x, int y, int comp) { int i,k,n; stbi_uc *output; if (!data) return NULL; output = (stbi_uc *) stbi__malloc_mad3(x, y, comp, 0); if (output == NULL) { STBI_FREE(data); return stbi__errpuc("outofmem", "Out of memory"); } // compute number of non-alpha components if (comp & 1) n = comp; else n = comp-1; for (i=0; i < x*y; ++i) { for (k=0; k < n; ++k) { float z = (float) pow(data[i*comp+k]*stbi__h2l_scale_i, stbi__h2l_gamma_i) * 255 + 0.5f; if (z < 0) z = 0; if (z > 255) z = 255; output[i*comp + k] = (stbi_uc) stbi__float2int(z); } if (k < comp) { float z = data[i*comp+k] * 255 + 0.5f; if (z < 0) z = 0; if (z > 255) z = 255; output[i*comp + k] = (stbi_uc) stbi__float2int(z); } } STBI_FREE(data); return output; } #endif ////////////////////////////////////////////////////////////////////////////// // // "baseline" JPEG/JFIF decoder // // simple implementation // - doesn't support delayed output of y-dimension // - simple interface (only one output format: 8-bit interleaved RGB) // - doesn't try to recover corrupt jpegs // - doesn't allow partial loading, loading multiple at once // - still fast on x86 (copying globals into locals doesn't help x86) // - allocates lots of intermediate memory (full size of all components) // - non-interleaved case requires this anyway // - allows good upsampling (see next) // high-quality // - upsampled channels are bilinearly interpolated, even across blocks // - quality integer IDCT derived from IJG's 'slow' // performance // - fast huffman; reasonable integer IDCT // - some SIMD kernels for common paths on targets with SSE2/NEON // - uses a lot of intermediate memory, could cache poorly #ifndef STBI_NO_JPEG // huffman decoding acceleration #define FAST_BITS 9 // larger handles more cases; smaller stomps less cache typedef struct { stbi_uc fast[1 << FAST_BITS]; // weirdly, repacking this into AoS is a 10% speed loss, instead of a win stbi__uint16 code[256]; stbi_uc values[256]; stbi_uc size[257]; unsigned int maxcode[18]; int delta[17]; // old 'firstsymbol' - old 'firstcode' } stbi__huffman; typedef struct { stbi__context *s; stbi__huffman huff_dc[4]; stbi__huffman huff_ac[4]; stbi__uint16 dequant[4][64]; stbi__int16 fast_ac[4][1 << FAST_BITS]; // sizes for components, interleaved MCUs int img_h_max, img_v_max; int img_mcu_x, img_mcu_y; int img_mcu_w, img_mcu_h; // definition of jpeg image component struct { int id; int h,v; int tq; int hd,ha; int dc_pred; int x,y,w2,h2; stbi_uc *data; void *raw_data, *raw_coeff; stbi_uc *linebuf; short *coeff; // progressive only int coeff_w, coeff_h; // number of 8x8 coefficient blocks } img_comp[4]; stbi__uint32 code_buffer; // jpeg entropy-coded buffer int code_bits; // number of valid bits unsigned char marker; // marker seen while filling entropy buffer int nomore; // flag if we saw a marker so must stop int progressive; int spec_start; int spec_end; int succ_high; int succ_low; int eob_run; int jfif; int app14_color_transform; // Adobe APP14 tag int rgb; int scan_n, order[4]; int restart_interval, todo; // kernels void (*idct_block_kernel)(stbi_uc *out, int out_stride, short data[64]); void (*YCbCr_to_RGB_kernel)(stbi_uc *out, const stbi_uc *y, const stbi_uc *pcb, const stbi_uc *pcr, int count, int step); stbi_uc *(*resample_row_hv_2_kernel)(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs); } stbi__jpeg; static int stbi__build_huffman(stbi__huffman *h, int *count) { int i,j,k=0; unsigned int code; // build size list for each symbol (from JPEG spec) for (i=0; i < 16; ++i) { for (j=0; j < count[i]; ++j) { h->size[k++] = (stbi_uc) (i+1); if(k >= 257) return stbi__err("bad size list","Corrupt JPEG"); } } h->size[k] = 0; // compute actual symbols (from jpeg spec) code = 0; k = 0; for(j=1; j <= 16; ++j) { // compute delta to add to code to compute symbol id h->delta[j] = k - code; if (h->size[k] == j) { while (h->size[k] == j) h->code[k++] = (stbi__uint16) (code++); if (code-1 >= (1u << j)) return stbi__err("bad code lengths","Corrupt JPEG"); } // compute largest code + 1 for this size, preshifted as needed later h->maxcode[j] = code << (16-j); code <<= 1; } h->maxcode[j] = 0xffffffff; // build non-spec acceleration table; 255 is flag for not-accelerated memset(h->fast, 255, 1 << FAST_BITS); for (i=0; i < k; ++i) { int s = h->size[i]; if (s <= FAST_BITS) { int c = h->code[i] << (FAST_BITS-s); int m = 1 << (FAST_BITS-s); for (j=0; j < m; ++j) { h->fast[c+j] = (stbi_uc) i; } } } return 1; } // build a table that decodes both magnitude and value of small ACs in // one go. static void stbi__build_fast_ac(stbi__int16 *fast_ac, stbi__huffman *h) { int i; for (i=0; i < (1 << FAST_BITS); ++i) { stbi_uc fast = h->fast[i]; fast_ac[i] = 0; if (fast < 255) { int rs = h->values[fast]; int run = (rs >> 4) & 15; int magbits = rs & 15; int len = h->size[fast]; if (magbits && len + magbits <= FAST_BITS) { // magnitude code followed by receive_extend code int k = ((i << len) & ((1 << FAST_BITS) - 1)) >> (FAST_BITS - magbits); int m = 1 << (magbits - 1); if (k < m) k += (~0U << magbits) + 1; // if the result is small enough, we can fit it in fast_ac table if (k >= -128 && k <= 127) fast_ac[i] = (stbi__int16) ((k * 256) + (run * 16) + (len + magbits)); } } } } static void stbi__grow_buffer_unsafe(stbi__jpeg *j) { do { unsigned int b = j->nomore ? 0 : stbi__get8(j->s); if (b == 0xff) { int c = stbi__get8(j->s); while (c == 0xff) c = stbi__get8(j->s); // consume fill bytes if (c != 0) { j->marker = (unsigned char) c; j->nomore = 1; return; } } j->code_buffer |= b << (24 - j->code_bits); j->code_bits += 8; } while (j->code_bits <= 24); } // (1 << n) - 1 static const stbi__uint32 stbi__bmask[17]={0,1,3,7,15,31,63,127,255,511,1023,2047,4095,8191,16383,32767,65535}; // decode a jpeg huffman value from the bitstream stbi_inline static int stbi__jpeg_huff_decode(stbi__jpeg *j, stbi__huffman *h) { unsigned int temp; int c,k; if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); // look at the top FAST_BITS and determine what symbol ID it is, // if the code is <= FAST_BITS c = (j->code_buffer >> (32 - FAST_BITS)) & ((1 << FAST_BITS)-1); k = h->fast[c]; if (k < 255) { int s = h->size[k]; if (s > j->code_bits) return -1; j->code_buffer <<= s; j->code_bits -= s; return h->values[k]; } // naive test is to shift the code_buffer down so k bits are // valid, then test against maxcode. To speed this up, we've // preshifted maxcode left so that it has (16-k) 0s at the // end; in other words, regardless of the number of bits, it // wants to be compared against something shifted to have 16; // that way we don't need to shift inside the loop. temp = j->code_buffer >> 16; for (k=FAST_BITS+1 ; ; ++k) if (temp < h->maxcode[k]) break; if (k == 17) { // error! code not found j->code_bits -= 16; return -1; } if (k > j->code_bits) return -1; // convert the huffman code to the symbol id c = ((j->code_buffer >> (32 - k)) & stbi__bmask[k]) + h->delta[k]; if(c < 0 || c >= 256) // symbol id out of bounds! return -1; STBI_ASSERT((((j->code_buffer) >> (32 - h->size[c])) & stbi__bmask[h->size[c]]) == h->code[c]); // convert the id to a symbol j->code_bits -= k; j->code_buffer <<= k; return h->values[c]; } // bias[n] = (-1<code_bits < n) stbi__grow_buffer_unsafe(j); if (j->code_bits < n) return 0; // ran out of bits from stream, return 0s intead of continuing sgn = j->code_buffer >> 31; // sign bit always in MSB; 0 if MSB clear (positive), 1 if MSB set (negative) k = stbi_lrot(j->code_buffer, n); j->code_buffer = k & ~stbi__bmask[n]; k &= stbi__bmask[n]; j->code_bits -= n; return k + (stbi__jbias[n] & (sgn - 1)); } // get some unsigned bits stbi_inline static int stbi__jpeg_get_bits(stbi__jpeg *j, int n) { unsigned int k; if (j->code_bits < n) stbi__grow_buffer_unsafe(j); if (j->code_bits < n) return 0; // ran out of bits from stream, return 0s intead of continuing k = stbi_lrot(j->code_buffer, n); j->code_buffer = k & ~stbi__bmask[n]; k &= stbi__bmask[n]; j->code_bits -= n; return k; } stbi_inline static int stbi__jpeg_get_bit(stbi__jpeg *j) { unsigned int k; if (j->code_bits < 1) stbi__grow_buffer_unsafe(j); if (j->code_bits < 1) return 0; // ran out of bits from stream, return 0s intead of continuing k = j->code_buffer; j->code_buffer <<= 1; --j->code_bits; return k & 0x80000000; } // given a value that's at position X in the zigzag stream, // where does it appear in the 8x8 matrix coded as row-major? static const stbi_uc stbi__jpeg_dezigzag[64+15] = { 0, 1, 8, 16, 9, 2, 3, 10, 17, 24, 32, 25, 18, 11, 4, 5, 12, 19, 26, 33, 40, 48, 41, 34, 27, 20, 13, 6, 7, 14, 21, 28, 35, 42, 49, 56, 57, 50, 43, 36, 29, 22, 15, 23, 30, 37, 44, 51, 58, 59, 52, 45, 38, 31, 39, 46, 53, 60, 61, 54, 47, 55, 62, 63, // let corrupt input sample past end 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63 }; // decode one 64-entry block-- static int stbi__jpeg_decode_block(stbi__jpeg *j, short data[64], stbi__huffman *hdc, stbi__huffman *hac, stbi__int16 *fac, int b, stbi__uint16 *dequant) { int diff,dc,k; int t; if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); t = stbi__jpeg_huff_decode(j, hdc); if (t < 0 || t > 15) return stbi__err("bad huffman code","Corrupt JPEG"); // 0 all the ac values now so we can do it 32-bits at a time memset(data,0,64*sizeof(data[0])); diff = t ? stbi__extend_receive(j, t) : 0; if (!stbi__addints_valid(j->img_comp[b].dc_pred, diff)) return stbi__err("bad delta","Corrupt JPEG"); dc = j->img_comp[b].dc_pred + diff; j->img_comp[b].dc_pred = dc; if (!stbi__mul2shorts_valid(dc, dequant[0])) return stbi__err("can't merge dc and ac", "Corrupt JPEG"); data[0] = (short) (dc * dequant[0]); // decode AC components, see JPEG spec k = 1; do { unsigned int zig; int c,r,s; if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); c = (j->code_buffer >> (32 - FAST_BITS)) & ((1 << FAST_BITS)-1); r = fac[c]; if (r) { // fast-AC path k += (r >> 4) & 15; // run s = r & 15; // combined length if (s > j->code_bits) return stbi__err("bad huffman code", "Combined length longer than code bits available"); j->code_buffer <<= s; j->code_bits -= s; // decode into unzigzag'd location zig = stbi__jpeg_dezigzag[k++]; data[zig] = (short) ((r >> 8) * dequant[zig]); } else { int rs = stbi__jpeg_huff_decode(j, hac); if (rs < 0) return stbi__err("bad huffman code","Corrupt JPEG"); s = rs & 15; r = rs >> 4; if (s == 0) { if (rs != 0xf0) break; // end block k += 16; } else { k += r; // decode into unzigzag'd location zig = stbi__jpeg_dezigzag[k++]; data[zig] = (short) (stbi__extend_receive(j,s) * dequant[zig]); } } } while (k < 64); return 1; } static int stbi__jpeg_decode_block_prog_dc(stbi__jpeg *j, short data[64], stbi__huffman *hdc, int b) { int diff,dc; int t; if (j->spec_end != 0) return stbi__err("can't merge dc and ac", "Corrupt JPEG"); if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); if (j->succ_high == 0) { // first scan for DC coefficient, must be first memset(data,0,64*sizeof(data[0])); // 0 all the ac values now t = stbi__jpeg_huff_decode(j, hdc); if (t < 0 || t > 15) return stbi__err("can't merge dc and ac", "Corrupt JPEG"); diff = t ? stbi__extend_receive(j, t) : 0; if (!stbi__addints_valid(j->img_comp[b].dc_pred, diff)) return stbi__err("bad delta", "Corrupt JPEG"); dc = j->img_comp[b].dc_pred + diff; j->img_comp[b].dc_pred = dc; if (!stbi__mul2shorts_valid(dc, 1 << j->succ_low)) return stbi__err("can't merge dc and ac", "Corrupt JPEG"); data[0] = (short) (dc * (1 << j->succ_low)); } else { // refinement scan for DC coefficient if (stbi__jpeg_get_bit(j)) data[0] += (short) (1 << j->succ_low); } return 1; } // @OPTIMIZE: store non-zigzagged during the decode passes, // and only de-zigzag when dequantizing static int stbi__jpeg_decode_block_prog_ac(stbi__jpeg *j, short data[64], stbi__huffman *hac, stbi__int16 *fac) { int k; if (j->spec_start == 0) return stbi__err("can't merge dc and ac", "Corrupt JPEG"); if (j->succ_high == 0) { int shift = j->succ_low; if (j->eob_run) { --j->eob_run; return 1; } k = j->spec_start; do { unsigned int zig; int c,r,s; if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); c = (j->code_buffer >> (32 - FAST_BITS)) & ((1 << FAST_BITS)-1); r = fac[c]; if (r) { // fast-AC path k += (r >> 4) & 15; // run s = r & 15; // combined length if (s > j->code_bits) return stbi__err("bad huffman code", "Combined length longer than code bits available"); j->code_buffer <<= s; j->code_bits -= s; zig = stbi__jpeg_dezigzag[k++]; data[zig] = (short) ((r >> 8) * (1 << shift)); } else { int rs = stbi__jpeg_huff_decode(j, hac); if (rs < 0) return stbi__err("bad huffman code","Corrupt JPEG"); s = rs & 15; r = rs >> 4; if (s == 0) { if (r < 15) { j->eob_run = (1 << r); if (r) j->eob_run += stbi__jpeg_get_bits(j, r); --j->eob_run; break; } k += 16; } else { k += r; zig = stbi__jpeg_dezigzag[k++]; data[zig] = (short) (stbi__extend_receive(j,s) * (1 << shift)); } } } while (k <= j->spec_end); } else { // refinement scan for these AC coefficients short bit = (short) (1 << j->succ_low); if (j->eob_run) { --j->eob_run; for (k = j->spec_start; k <= j->spec_end; ++k) { short *p = &data[stbi__jpeg_dezigzag[k]]; if (*p != 0) if (stbi__jpeg_get_bit(j)) if ((*p & bit)==0) { if (*p > 0) *p += bit; else *p -= bit; } } } else { k = j->spec_start; do { int r,s; int rs = stbi__jpeg_huff_decode(j, hac); // @OPTIMIZE see if we can use the fast path here, advance-by-r is so slow, eh if (rs < 0) return stbi__err("bad huffman code","Corrupt JPEG"); s = rs & 15; r = rs >> 4; if (s == 0) { if (r < 15) { j->eob_run = (1 << r) - 1; if (r) j->eob_run += stbi__jpeg_get_bits(j, r); r = 64; // force end of block } else { // r=15 s=0 should write 16 0s, so we just do // a run of 15 0s and then write s (which is 0), // so we don't have to do anything special here } } else { if (s != 1) return stbi__err("bad huffman code", "Corrupt JPEG"); // sign bit if (stbi__jpeg_get_bit(j)) s = bit; else s = -bit; } // advance by r while (k <= j->spec_end) { short *p = &data[stbi__jpeg_dezigzag[k++]]; if (*p != 0) { if (stbi__jpeg_get_bit(j)) if ((*p & bit)==0) { if (*p > 0) *p += bit; else *p -= bit; } } else { if (r == 0) { *p = (short) s; break; } --r; } } } while (k <= j->spec_end); } } return 1; } // take a -128..127 value and stbi__clamp it and convert to 0..255 stbi_inline static stbi_uc stbi__clamp(int x) { // trick to use a single test to catch both cases if ((unsigned int) x > 255) { if (x < 0) return 0; if (x > 255) return 255; } return (stbi_uc) x; } #define stbi__f2f(x) ((int) (((x) * 4096 + 0.5))) #define stbi__fsh(x) ((x) * 4096) // derived from jidctint -- DCT_ISLOW #define STBI__IDCT_1D(s0,s1,s2,s3,s4,s5,s6,s7) \ int t0,t1,t2,t3,p1,p2,p3,p4,p5,x0,x1,x2,x3; \ p2 = s2; \ p3 = s6; \ p1 = (p2+p3) * stbi__f2f(0.5411961f); \ t2 = p1 + p3*stbi__f2f(-1.847759065f); \ t3 = p1 + p2*stbi__f2f( 0.765366865f); \ p2 = s0; \ p3 = s4; \ t0 = stbi__fsh(p2+p3); \ t1 = stbi__fsh(p2-p3); \ x0 = t0+t3; \ x3 = t0-t3; \ x1 = t1+t2; \ x2 = t1-t2; \ t0 = s7; \ t1 = s5; \ t2 = s3; \ t3 = s1; \ p3 = t0+t2; \ p4 = t1+t3; \ p1 = t0+t3; \ p2 = t1+t2; \ p5 = (p3+p4)*stbi__f2f( 1.175875602f); \ t0 = t0*stbi__f2f( 0.298631336f); \ t1 = t1*stbi__f2f( 2.053119869f); \ t2 = t2*stbi__f2f( 3.072711026f); \ t3 = t3*stbi__f2f( 1.501321110f); \ p1 = p5 + p1*stbi__f2f(-0.899976223f); \ p2 = p5 + p2*stbi__f2f(-2.562915447f); \ p3 = p3*stbi__f2f(-1.961570560f); \ p4 = p4*stbi__f2f(-0.390180644f); \ t3 += p1+p4; \ t2 += p2+p3; \ t1 += p2+p4; \ t0 += p1+p3; static void stbi__idct_block(stbi_uc *out, int out_stride, short data[64]) { int i,val[64],*v=val; stbi_uc *o; short *d = data; // columns for (i=0; i < 8; ++i,++d, ++v) { // if all zeroes, shortcut -- this avoids dequantizing 0s and IDCTing if (d[ 8]==0 && d[16]==0 && d[24]==0 && d[32]==0 && d[40]==0 && d[48]==0 && d[56]==0) { // no shortcut 0 seconds // (1|2|3|4|5|6|7)==0 0 seconds // all separate -0.047 seconds // 1 && 2|3 && 4|5 && 6|7: -0.047 seconds int dcterm = d[0]*4; v[0] = v[8] = v[16] = v[24] = v[32] = v[40] = v[48] = v[56] = dcterm; } else { STBI__IDCT_1D(d[ 0],d[ 8],d[16],d[24],d[32],d[40],d[48],d[56]) // constants scaled things up by 1<<12; let's bring them back // down, but keep 2 extra bits of precision x0 += 512; x1 += 512; x2 += 512; x3 += 512; v[ 0] = (x0+t3) >> 10; v[56] = (x0-t3) >> 10; v[ 8] = (x1+t2) >> 10; v[48] = (x1-t2) >> 10; v[16] = (x2+t1) >> 10; v[40] = (x2-t1) >> 10; v[24] = (x3+t0) >> 10; v[32] = (x3-t0) >> 10; } } for (i=0, v=val, o=out; i < 8; ++i,v+=8,o+=out_stride) { // no fast case since the first 1D IDCT spread components out STBI__IDCT_1D(v[0],v[1],v[2],v[3],v[4],v[5],v[6],v[7]) // constants scaled things up by 1<<12, plus we had 1<<2 from first // loop, plus horizontal and vertical each scale by sqrt(8) so together // we've got an extra 1<<3, so 1<<17 total we need to remove. // so we want to round that, which means adding 0.5 * 1<<17, // aka 65536. Also, we'll end up with -128 to 127 that we want // to encode as 0..255 by adding 128, so we'll add that before the shift x0 += 65536 + (128<<17); x1 += 65536 + (128<<17); x2 += 65536 + (128<<17); x3 += 65536 + (128<<17); // tried computing the shifts into temps, or'ing the temps to see // if any were out of range, but that was slower o[0] = stbi__clamp((x0+t3) >> 17); o[7] = stbi__clamp((x0-t3) >> 17); o[1] = stbi__clamp((x1+t2) >> 17); o[6] = stbi__clamp((x1-t2) >> 17); o[2] = stbi__clamp((x2+t1) >> 17); o[5] = stbi__clamp((x2-t1) >> 17); o[3] = stbi__clamp((x3+t0) >> 17); o[4] = stbi__clamp((x3-t0) >> 17); } } #ifdef STBI_SSE2 // sse2 integer IDCT. not the fastest possible implementation but it // produces bit-identical results to the generic C version so it's // fully "transparent". static void stbi__idct_simd(stbi_uc *out, int out_stride, short data[64]) { // This is constructed to match our regular (generic) integer IDCT exactly. __m128i row0, row1, row2, row3, row4, row5, row6, row7; __m128i tmp; // dot product constant: even elems=x, odd elems=y #define dct_const(x,y) _mm_setr_epi16((x),(y),(x),(y),(x),(y),(x),(y)) // out(0) = c0[even]*x + c0[odd]*y (c0, x, y 16-bit, out 32-bit) // out(1) = c1[even]*x + c1[odd]*y #define dct_rot(out0,out1, x,y,c0,c1) \ __m128i c0##lo = _mm_unpacklo_epi16((x),(y)); \ __m128i c0##hi = _mm_unpackhi_epi16((x),(y)); \ __m128i out0##_l = _mm_madd_epi16(c0##lo, c0); \ __m128i out0##_h = _mm_madd_epi16(c0##hi, c0); \ __m128i out1##_l = _mm_madd_epi16(c0##lo, c1); \ __m128i out1##_h = _mm_madd_epi16(c0##hi, c1) // out = in << 12 (in 16-bit, out 32-bit) #define dct_widen(out, in) \ __m128i out##_l = _mm_srai_epi32(_mm_unpacklo_epi16(_mm_setzero_si128(), (in)), 4); \ __m128i out##_h = _mm_srai_epi32(_mm_unpackhi_epi16(_mm_setzero_si128(), (in)), 4) // wide add #define dct_wadd(out, a, b) \ __m128i out##_l = _mm_add_epi32(a##_l, b##_l); \ __m128i out##_h = _mm_add_epi32(a##_h, b##_h) // wide sub #define dct_wsub(out, a, b) \ __m128i out##_l = _mm_sub_epi32(a##_l, b##_l); \ __m128i out##_h = _mm_sub_epi32(a##_h, b##_h) // butterfly a/b, add bias, then shift by "s" and pack #define dct_bfly32o(out0, out1, a,b,bias,s) \ { \ __m128i abiased_l = _mm_add_epi32(a##_l, bias); \ __m128i abiased_h = _mm_add_epi32(a##_h, bias); \ dct_wadd(sum, abiased, b); \ dct_wsub(dif, abiased, b); \ out0 = _mm_packs_epi32(_mm_srai_epi32(sum_l, s), _mm_srai_epi32(sum_h, s)); \ out1 = _mm_packs_epi32(_mm_srai_epi32(dif_l, s), _mm_srai_epi32(dif_h, s)); \ } // 8-bit interleave step (for transposes) #define dct_interleave8(a, b) \ tmp = a; \ a = _mm_unpacklo_epi8(a, b); \ b = _mm_unpackhi_epi8(tmp, b) // 16-bit interleave step (for transposes) #define dct_interleave16(a, b) \ tmp = a; \ a = _mm_unpacklo_epi16(a, b); \ b = _mm_unpackhi_epi16(tmp, b) #define dct_pass(bias,shift) \ { \ /* even part */ \ dct_rot(t2e,t3e, row2,row6, rot0_0,rot0_1); \ __m128i sum04 = _mm_add_epi16(row0, row4); \ __m128i dif04 = _mm_sub_epi16(row0, row4); \ dct_widen(t0e, sum04); \ dct_widen(t1e, dif04); \ dct_wadd(x0, t0e, t3e); \ dct_wsub(x3, t0e, t3e); \ dct_wadd(x1, t1e, t2e); \ dct_wsub(x2, t1e, t2e); \ /* odd part */ \ dct_rot(y0o,y2o, row7,row3, rot2_0,rot2_1); \ dct_rot(y1o,y3o, row5,row1, rot3_0,rot3_1); \ __m128i sum17 = _mm_add_epi16(row1, row7); \ __m128i sum35 = _mm_add_epi16(row3, row5); \ dct_rot(y4o,y5o, sum17,sum35, rot1_0,rot1_1); \ dct_wadd(x4, y0o, y4o); \ dct_wadd(x5, y1o, y5o); \ dct_wadd(x6, y2o, y5o); \ dct_wadd(x7, y3o, y4o); \ dct_bfly32o(row0,row7, x0,x7,bias,shift); \ dct_bfly32o(row1,row6, x1,x6,bias,shift); \ dct_bfly32o(row2,row5, x2,x5,bias,shift); \ dct_bfly32o(row3,row4, x3,x4,bias,shift); \ } __m128i rot0_0 = dct_const(stbi__f2f(0.5411961f), stbi__f2f(0.5411961f) + stbi__f2f(-1.847759065f)); __m128i rot0_1 = dct_const(stbi__f2f(0.5411961f) + stbi__f2f( 0.765366865f), stbi__f2f(0.5411961f)); __m128i rot1_0 = dct_const(stbi__f2f(1.175875602f) + stbi__f2f(-0.899976223f), stbi__f2f(1.175875602f)); __m128i rot1_1 = dct_const(stbi__f2f(1.175875602f), stbi__f2f(1.175875602f) + stbi__f2f(-2.562915447f)); __m128i rot2_0 = dct_const(stbi__f2f(-1.961570560f) + stbi__f2f( 0.298631336f), stbi__f2f(-1.961570560f)); __m128i rot2_1 = dct_const(stbi__f2f(-1.961570560f), stbi__f2f(-1.961570560f) + stbi__f2f( 3.072711026f)); __m128i rot3_0 = dct_const(stbi__f2f(-0.390180644f) + stbi__f2f( 2.053119869f), stbi__f2f(-0.390180644f)); __m128i rot3_1 = dct_const(stbi__f2f(-0.390180644f), stbi__f2f(-0.390180644f) + stbi__f2f( 1.501321110f)); // rounding biases in column/row passes, see stbi__idct_block for explanation. __m128i bias_0 = _mm_set1_epi32(512); __m128i bias_1 = _mm_set1_epi32(65536 + (128<<17)); // load row0 = _mm_load_si128((const __m128i *) (data + 0*8)); row1 = _mm_load_si128((const __m128i *) (data + 1*8)); row2 = _mm_load_si128((const __m128i *) (data + 2*8)); row3 = _mm_load_si128((const __m128i *) (data + 3*8)); row4 = _mm_load_si128((const __m128i *) (data + 4*8)); row5 = _mm_load_si128((const __m128i *) (data + 5*8)); row6 = _mm_load_si128((const __m128i *) (data + 6*8)); row7 = _mm_load_si128((const __m128i *) (data + 7*8)); // column pass dct_pass(bias_0, 10); { // 16bit 8x8 transpose pass 1 dct_interleave16(row0, row4); dct_interleave16(row1, row5); dct_interleave16(row2, row6); dct_interleave16(row3, row7); // transpose pass 2 dct_interleave16(row0, row2); dct_interleave16(row1, row3); dct_interleave16(row4, row6); dct_interleave16(row5, row7); // transpose pass 3 dct_interleave16(row0, row1); dct_interleave16(row2, row3); dct_interleave16(row4, row5); dct_interleave16(row6, row7); } // row pass dct_pass(bias_1, 17); { // pack __m128i p0 = _mm_packus_epi16(row0, row1); // a0a1a2a3...a7b0b1b2b3...b7 __m128i p1 = _mm_packus_epi16(row2, row3); __m128i p2 = _mm_packus_epi16(row4, row5); __m128i p3 = _mm_packus_epi16(row6, row7); // 8bit 8x8 transpose pass 1 dct_interleave8(p0, p2); // a0e0a1e1... dct_interleave8(p1, p3); // c0g0c1g1... // transpose pass 2 dct_interleave8(p0, p1); // a0c0e0g0... dct_interleave8(p2, p3); // b0d0f0h0... // transpose pass 3 dct_interleave8(p0, p2); // a0b0c0d0... dct_interleave8(p1, p3); // a4b4c4d4... // store _mm_storel_epi64((__m128i *) out, p0); out += out_stride; _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p0, 0x4e)); out += out_stride; _mm_storel_epi64((__m128i *) out, p2); out += out_stride; _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p2, 0x4e)); out += out_stride; _mm_storel_epi64((__m128i *) out, p1); out += out_stride; _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p1, 0x4e)); out += out_stride; _mm_storel_epi64((__m128i *) out, p3); out += out_stride; _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p3, 0x4e)); } #undef dct_const #undef dct_rot #undef dct_widen #undef dct_wadd #undef dct_wsub #undef dct_bfly32o #undef dct_interleave8 #undef dct_interleave16 #undef dct_pass } #endif // STBI_SSE2 #ifdef STBI_NEON // NEON integer IDCT. should produce bit-identical // results to the generic C version. static void stbi__idct_simd(stbi_uc *out, int out_stride, short data[64]) { int16x8_t row0, row1, row2, row3, row4, row5, row6, row7; int16x4_t rot0_0 = vdup_n_s16(stbi__f2f(0.5411961f)); int16x4_t rot0_1 = vdup_n_s16(stbi__f2f(-1.847759065f)); int16x4_t rot0_2 = vdup_n_s16(stbi__f2f( 0.765366865f)); int16x4_t rot1_0 = vdup_n_s16(stbi__f2f( 1.175875602f)); int16x4_t rot1_1 = vdup_n_s16(stbi__f2f(-0.899976223f)); int16x4_t rot1_2 = vdup_n_s16(stbi__f2f(-2.562915447f)); int16x4_t rot2_0 = vdup_n_s16(stbi__f2f(-1.961570560f)); int16x4_t rot2_1 = vdup_n_s16(stbi__f2f(-0.390180644f)); int16x4_t rot3_0 = vdup_n_s16(stbi__f2f( 0.298631336f)); int16x4_t rot3_1 = vdup_n_s16(stbi__f2f( 2.053119869f)); int16x4_t rot3_2 = vdup_n_s16(stbi__f2f( 3.072711026f)); int16x4_t rot3_3 = vdup_n_s16(stbi__f2f( 1.501321110f)); #define dct_long_mul(out, inq, coeff) \ int32x4_t out##_l = vmull_s16(vget_low_s16(inq), coeff); \ int32x4_t out##_h = vmull_s16(vget_high_s16(inq), coeff) #define dct_long_mac(out, acc, inq, coeff) \ int32x4_t out##_l = vmlal_s16(acc##_l, vget_low_s16(inq), coeff); \ int32x4_t out##_h = vmlal_s16(acc##_h, vget_high_s16(inq), coeff) #define dct_widen(out, inq) \ int32x4_t out##_l = vshll_n_s16(vget_low_s16(inq), 12); \ int32x4_t out##_h = vshll_n_s16(vget_high_s16(inq), 12) // wide add #define dct_wadd(out, a, b) \ int32x4_t out##_l = vaddq_s32(a##_l, b##_l); \ int32x4_t out##_h = vaddq_s32(a##_h, b##_h) // wide sub #define dct_wsub(out, a, b) \ int32x4_t out##_l = vsubq_s32(a##_l, b##_l); \ int32x4_t out##_h = vsubq_s32(a##_h, b##_h) // butterfly a/b, then shift using "shiftop" by "s" and pack #define dct_bfly32o(out0,out1, a,b,shiftop,s) \ { \ dct_wadd(sum, a, b); \ dct_wsub(dif, a, b); \ out0 = vcombine_s16(shiftop(sum_l, s), shiftop(sum_h, s)); \ out1 = vcombine_s16(shiftop(dif_l, s), shiftop(dif_h, s)); \ } #define dct_pass(shiftop, shift) \ { \ /* even part */ \ int16x8_t sum26 = vaddq_s16(row2, row6); \ dct_long_mul(p1e, sum26, rot0_0); \ dct_long_mac(t2e, p1e, row6, rot0_1); \ dct_long_mac(t3e, p1e, row2, rot0_2); \ int16x8_t sum04 = vaddq_s16(row0, row4); \ int16x8_t dif04 = vsubq_s16(row0, row4); \ dct_widen(t0e, sum04); \ dct_widen(t1e, dif04); \ dct_wadd(x0, t0e, t3e); \ dct_wsub(x3, t0e, t3e); \ dct_wadd(x1, t1e, t2e); \ dct_wsub(x2, t1e, t2e); \ /* odd part */ \ int16x8_t sum15 = vaddq_s16(row1, row5); \ int16x8_t sum17 = vaddq_s16(row1, row7); \ int16x8_t sum35 = vaddq_s16(row3, row5); \ int16x8_t sum37 = vaddq_s16(row3, row7); \ int16x8_t sumodd = vaddq_s16(sum17, sum35); \ dct_long_mul(p5o, sumodd, rot1_0); \ dct_long_mac(p1o, p5o, sum17, rot1_1); \ dct_long_mac(p2o, p5o, sum35, rot1_2); \ dct_long_mul(p3o, sum37, rot2_0); \ dct_long_mul(p4o, sum15, rot2_1); \ dct_wadd(sump13o, p1o, p3o); \ dct_wadd(sump24o, p2o, p4o); \ dct_wadd(sump23o, p2o, p3o); \ dct_wadd(sump14o, p1o, p4o); \ dct_long_mac(x4, sump13o, row7, rot3_0); \ dct_long_mac(x5, sump24o, row5, rot3_1); \ dct_long_mac(x6, sump23o, row3, rot3_2); \ dct_long_mac(x7, sump14o, row1, rot3_3); \ dct_bfly32o(row0,row7, x0,x7,shiftop,shift); \ dct_bfly32o(row1,row6, x1,x6,shiftop,shift); \ dct_bfly32o(row2,row5, x2,x5,shiftop,shift); \ dct_bfly32o(row3,row4, x3,x4,shiftop,shift); \ } // load row0 = vld1q_s16(data + 0*8); row1 = vld1q_s16(data + 1*8); row2 = vld1q_s16(data + 2*8); row3 = vld1q_s16(data + 3*8); row4 = vld1q_s16(data + 4*8); row5 = vld1q_s16(data + 5*8); row6 = vld1q_s16(data + 6*8); row7 = vld1q_s16(data + 7*8); // add DC bias row0 = vaddq_s16(row0, vsetq_lane_s16(1024, vdupq_n_s16(0), 0)); // column pass dct_pass(vrshrn_n_s32, 10); // 16bit 8x8 transpose { // these three map to a single VTRN.16, VTRN.32, and VSWP, respectively. // whether compilers actually get this is another story, sadly. #define dct_trn16(x, y) { int16x8x2_t t = vtrnq_s16(x, y); x = t.val[0]; y = t.val[1]; } #define dct_trn32(x, y) { int32x4x2_t t = vtrnq_s32(vreinterpretq_s32_s16(x), vreinterpretq_s32_s16(y)); x = vreinterpretq_s16_s32(t.val[0]); y = vreinterpretq_s16_s32(t.val[1]); } #define dct_trn64(x, y) { int16x8_t x0 = x; int16x8_t y0 = y; x = vcombine_s16(vget_low_s16(x0), vget_low_s16(y0)); y = vcombine_s16(vget_high_s16(x0), vget_high_s16(y0)); } // pass 1 dct_trn16(row0, row1); // a0b0a2b2a4b4a6b6 dct_trn16(row2, row3); dct_trn16(row4, row5); dct_trn16(row6, row7); // pass 2 dct_trn32(row0, row2); // a0b0c0d0a4b4c4d4 dct_trn32(row1, row3); dct_trn32(row4, row6); dct_trn32(row5, row7); // pass 3 dct_trn64(row0, row4); // a0b0c0d0e0f0g0h0 dct_trn64(row1, row5); dct_trn64(row2, row6); dct_trn64(row3, row7); #undef dct_trn16 #undef dct_trn32 #undef dct_trn64 } // row pass // vrshrn_n_s32 only supports shifts up to 16, we need // 17. so do a non-rounding shift of 16 first then follow // up with a rounding shift by 1. dct_pass(vshrn_n_s32, 16); { // pack and round uint8x8_t p0 = vqrshrun_n_s16(row0, 1); uint8x8_t p1 = vqrshrun_n_s16(row1, 1); uint8x8_t p2 = vqrshrun_n_s16(row2, 1); uint8x8_t p3 = vqrshrun_n_s16(row3, 1); uint8x8_t p4 = vqrshrun_n_s16(row4, 1); uint8x8_t p5 = vqrshrun_n_s16(row5, 1); uint8x8_t p6 = vqrshrun_n_s16(row6, 1); uint8x8_t p7 = vqrshrun_n_s16(row7, 1); // again, these can translate into one instruction, but often don't. #define dct_trn8_8(x, y) { uint8x8x2_t t = vtrn_u8(x, y); x = t.val[0]; y = t.val[1]; } #define dct_trn8_16(x, y) { uint16x4x2_t t = vtrn_u16(vreinterpret_u16_u8(x), vreinterpret_u16_u8(y)); x = vreinterpret_u8_u16(t.val[0]); y = vreinterpret_u8_u16(t.val[1]); } #define dct_trn8_32(x, y) { uint32x2x2_t t = vtrn_u32(vreinterpret_u32_u8(x), vreinterpret_u32_u8(y)); x = vreinterpret_u8_u32(t.val[0]); y = vreinterpret_u8_u32(t.val[1]); } // sadly can't use interleaved stores here since we only write // 8 bytes to each scan line! // 8x8 8-bit transpose pass 1 dct_trn8_8(p0, p1); dct_trn8_8(p2, p3); dct_trn8_8(p4, p5); dct_trn8_8(p6, p7); // pass 2 dct_trn8_16(p0, p2); dct_trn8_16(p1, p3); dct_trn8_16(p4, p6); dct_trn8_16(p5, p7); // pass 3 dct_trn8_32(p0, p4); dct_trn8_32(p1, p5); dct_trn8_32(p2, p6); dct_trn8_32(p3, p7); // store vst1_u8(out, p0); out += out_stride; vst1_u8(out, p1); out += out_stride; vst1_u8(out, p2); out += out_stride; vst1_u8(out, p3); out += out_stride; vst1_u8(out, p4); out += out_stride; vst1_u8(out, p5); out += out_stride; vst1_u8(out, p6); out += out_stride; vst1_u8(out, p7); #undef dct_trn8_8 #undef dct_trn8_16 #undef dct_trn8_32 } #undef dct_long_mul #undef dct_long_mac #undef dct_widen #undef dct_wadd #undef dct_wsub #undef dct_bfly32o #undef dct_pass } #endif // STBI_NEON #define STBI__MARKER_none 0xff // if there's a pending marker from the entropy stream, return that // otherwise, fetch from the stream and get a marker. if there's no // marker, return 0xff, which is never a valid marker value static stbi_uc stbi__get_marker(stbi__jpeg *j) { stbi_uc x; if (j->marker != STBI__MARKER_none) { x = j->marker; j->marker = STBI__MARKER_none; return x; } x = stbi__get8(j->s); if (x != 0xff) return STBI__MARKER_none; while (x == 0xff) x = stbi__get8(j->s); // consume repeated 0xff fill bytes return x; } // in each scan, we'll have scan_n components, and the order // of the components is specified by order[] #define STBI__RESTART(x) ((x) >= 0xd0 && (x) <= 0xd7) // after a restart interval, stbi__jpeg_reset the entropy decoder and // the dc prediction static void stbi__jpeg_reset(stbi__jpeg *j) { j->code_bits = 0; j->code_buffer = 0; j->nomore = 0; j->img_comp[0].dc_pred = j->img_comp[1].dc_pred = j->img_comp[2].dc_pred = j->img_comp[3].dc_pred = 0; j->marker = STBI__MARKER_none; j->todo = j->restart_interval ? j->restart_interval : 0x7fffffff; j->eob_run = 0; // no more than 1<<31 MCUs if no restart_interal? that's plenty safe, // since we don't even allow 1<<30 pixels } static int stbi__parse_entropy_coded_data(stbi__jpeg *z) { stbi__jpeg_reset(z); if (!z->progressive) { if (z->scan_n == 1) { int i,j; STBI_SIMD_ALIGN(short, data[64]); int n = z->order[0]; // non-interleaved data, we just need to process one block at a time, // in trivial scanline order // number of blocks to do just depends on how many actual "pixels" this // component has, independent of interleaved MCU blocking and such int w = (z->img_comp[n].x+7) >> 3; int h = (z->img_comp[n].y+7) >> 3; for (j=0; j < h; ++j) { for (i=0; i < w; ++i) { int ha = z->img_comp[n].ha; if (!stbi__jpeg_decode_block(z, data, z->huff_dc+z->img_comp[n].hd, z->huff_ac+ha, z->fast_ac[ha], n, z->dequant[z->img_comp[n].tq])) return 0; z->idct_block_kernel(z->img_comp[n].data+z->img_comp[n].w2*j*8+i*8, z->img_comp[n].w2, data); // every data block is an MCU, so countdown the restart interval if (--z->todo <= 0) { if (z->code_bits < 24) stbi__grow_buffer_unsafe(z); // if it's NOT a restart, then just bail, so we get corrupt data // rather than no data if (!STBI__RESTART(z->marker)) return 1; stbi__jpeg_reset(z); } } } return 1; } else { // interleaved int i,j,k,x,y; STBI_SIMD_ALIGN(short, data[64]); for (j=0; j < z->img_mcu_y; ++j) { for (i=0; i < z->img_mcu_x; ++i) { // scan an interleaved mcu... process scan_n components in order for (k=0; k < z->scan_n; ++k) { int n = z->order[k]; // scan out an mcu's worth of this component; that's just determined // by the basic H and V specified for the component for (y=0; y < z->img_comp[n].v; ++y) { for (x=0; x < z->img_comp[n].h; ++x) { int x2 = (i*z->img_comp[n].h + x)*8; int y2 = (j*z->img_comp[n].v + y)*8; int ha = z->img_comp[n].ha; if (!stbi__jpeg_decode_block(z, data, z->huff_dc+z->img_comp[n].hd, z->huff_ac+ha, z->fast_ac[ha], n, z->dequant[z->img_comp[n].tq])) return 0; z->idct_block_kernel(z->img_comp[n].data+z->img_comp[n].w2*y2+x2, z->img_comp[n].w2, data); } } } // after all interleaved components, that's an interleaved MCU, // so now count down the restart interval if (--z->todo <= 0) { if (z->code_bits < 24) stbi__grow_buffer_unsafe(z); if (!STBI__RESTART(z->marker)) return 1; stbi__jpeg_reset(z); } } } return 1; } } else { if (z->scan_n == 1) { int i,j; int n = z->order[0]; // non-interleaved data, we just need to process one block at a time, // in trivial scanline order // number of blocks to do just depends on how many actual "pixels" this // component has, independent of interleaved MCU blocking and such int w = (z->img_comp[n].x+7) >> 3; int h = (z->img_comp[n].y+7) >> 3; for (j=0; j < h; ++j) { for (i=0; i < w; ++i) { short *data = z->img_comp[n].coeff + 64 * (i + j * z->img_comp[n].coeff_w); if (z->spec_start == 0) { if (!stbi__jpeg_decode_block_prog_dc(z, data, &z->huff_dc[z->img_comp[n].hd], n)) return 0; } else { int ha = z->img_comp[n].ha; if (!stbi__jpeg_decode_block_prog_ac(z, data, &z->huff_ac[ha], z->fast_ac[ha])) return 0; } // every data block is an MCU, so countdown the restart interval if (--z->todo <= 0) { if (z->code_bits < 24) stbi__grow_buffer_unsafe(z); if (!STBI__RESTART(z->marker)) return 1; stbi__jpeg_reset(z); } } } return 1; } else { // interleaved int i,j,k,x,y; for (j=0; j < z->img_mcu_y; ++j) { for (i=0; i < z->img_mcu_x; ++i) { // scan an interleaved mcu... process scan_n components in order for (k=0; k < z->scan_n; ++k) { int n = z->order[k]; // scan out an mcu's worth of this component; that's just determined // by the basic H and V specified for the component for (y=0; y < z->img_comp[n].v; ++y) { for (x=0; x < z->img_comp[n].h; ++x) { int x2 = (i*z->img_comp[n].h + x); int y2 = (j*z->img_comp[n].v + y); short *data = z->img_comp[n].coeff + 64 * (x2 + y2 * z->img_comp[n].coeff_w); if (!stbi__jpeg_decode_block_prog_dc(z, data, &z->huff_dc[z->img_comp[n].hd], n)) return 0; } } } // after all interleaved components, that's an interleaved MCU, // so now count down the restart interval if (--z->todo <= 0) { if (z->code_bits < 24) stbi__grow_buffer_unsafe(z); if (!STBI__RESTART(z->marker)) return 1; stbi__jpeg_reset(z); } } } return 1; } } } static void stbi__jpeg_dequantize(short *data, stbi__uint16 *dequant) { int i; for (i=0; i < 64; ++i) data[i] *= dequant[i]; } static void stbi__jpeg_finish(stbi__jpeg *z) { if (z->progressive) { // dequantize and idct the data int i,j,n; for (n=0; n < z->s->img_n; ++n) { int w = (z->img_comp[n].x+7) >> 3; int h = (z->img_comp[n].y+7) >> 3; for (j=0; j < h; ++j) { for (i=0; i < w; ++i) { short *data = z->img_comp[n].coeff + 64 * (i + j * z->img_comp[n].coeff_w); stbi__jpeg_dequantize(data, z->dequant[z->img_comp[n].tq]); z->idct_block_kernel(z->img_comp[n].data+z->img_comp[n].w2*j*8+i*8, z->img_comp[n].w2, data); } } } } } static int stbi__process_marker(stbi__jpeg *z, int m) { int L; switch (m) { case STBI__MARKER_none: // no marker found return stbi__err("expected marker","Corrupt JPEG"); case 0xDD: // DRI - specify restart interval if (stbi__get16be(z->s) != 4) return stbi__err("bad DRI len","Corrupt JPEG"); z->restart_interval = stbi__get16be(z->s); return 1; case 0xDB: // DQT - define quantization table L = stbi__get16be(z->s)-2; while (L > 0) { int q = stbi__get8(z->s); int p = q >> 4, sixteen = (p != 0); int t = q & 15,i; if (p != 0 && p != 1) return stbi__err("bad DQT type","Corrupt JPEG"); if (t > 3) return stbi__err("bad DQT table","Corrupt JPEG"); for (i=0; i < 64; ++i) z->dequant[t][stbi__jpeg_dezigzag[i]] = (stbi__uint16)(sixteen ? stbi__get16be(z->s) : stbi__get8(z->s)); L -= (sixteen ? 129 : 65); } return L==0; case 0xC4: // DHT - define huffman table L = stbi__get16be(z->s)-2; while (L > 0) { stbi_uc *v; int sizes[16],i,n=0; int q = stbi__get8(z->s); int tc = q >> 4; int th = q & 15; if (tc > 1 || th > 3) return stbi__err("bad DHT header","Corrupt JPEG"); for (i=0; i < 16; ++i) { sizes[i] = stbi__get8(z->s); n += sizes[i]; } if(n > 256) return stbi__err("bad DHT header","Corrupt JPEG"); // Loop over i < n would write past end of values! L -= 17; if (tc == 0) { if (!stbi__build_huffman(z->huff_dc+th, sizes)) return 0; v = z->huff_dc[th].values; } else { if (!stbi__build_huffman(z->huff_ac+th, sizes)) return 0; v = z->huff_ac[th].values; } for (i=0; i < n; ++i) v[i] = stbi__get8(z->s); if (tc != 0) stbi__build_fast_ac(z->fast_ac[th], z->huff_ac + th); L -= n; } return L==0; } // check for comment block or APP blocks if ((m >= 0xE0 && m <= 0xEF) || m == 0xFE) { L = stbi__get16be(z->s); if (L < 2) { if (m == 0xFE) return stbi__err("bad COM len","Corrupt JPEG"); else return stbi__err("bad APP len","Corrupt JPEG"); } L -= 2; if (m == 0xE0 && L >= 5) { // JFIF APP0 segment static const unsigned char tag[5] = {'J','F','I','F','\0'}; int ok = 1; int i; for (i=0; i < 5; ++i) if (stbi__get8(z->s) != tag[i]) ok = 0; L -= 5; if (ok) z->jfif = 1; } else if (m == 0xEE && L >= 12) { // Adobe APP14 segment static const unsigned char tag[6] = {'A','d','o','b','e','\0'}; int ok = 1; int i; for (i=0; i < 6; ++i) if (stbi__get8(z->s) != tag[i]) ok = 0; L -= 6; if (ok) { stbi__get8(z->s); // version stbi__get16be(z->s); // flags0 stbi__get16be(z->s); // flags1 z->app14_color_transform = stbi__get8(z->s); // color transform L -= 6; } } stbi__skip(z->s, L); return 1; } return stbi__err("unknown marker","Corrupt JPEG"); } // after we see SOS static int stbi__process_scan_header(stbi__jpeg *z) { int i; int Ls = stbi__get16be(z->s); z->scan_n = stbi__get8(z->s); if (z->scan_n < 1 || z->scan_n > 4 || z->scan_n > (int) z->s->img_n) return stbi__err("bad SOS component count","Corrupt JPEG"); if (Ls != 6+2*z->scan_n) return stbi__err("bad SOS len","Corrupt JPEG"); for (i=0; i < z->scan_n; ++i) { int id = stbi__get8(z->s), which; int q = stbi__get8(z->s); for (which = 0; which < z->s->img_n; ++which) if (z->img_comp[which].id == id) break; if (which == z->s->img_n) return 0; // no match z->img_comp[which].hd = q >> 4; if (z->img_comp[which].hd > 3) return stbi__err("bad DC huff","Corrupt JPEG"); z->img_comp[which].ha = q & 15; if (z->img_comp[which].ha > 3) return stbi__err("bad AC huff","Corrupt JPEG"); z->order[i] = which; } { int aa; z->spec_start = stbi__get8(z->s); z->spec_end = stbi__get8(z->s); // should be 63, but might be 0 aa = stbi__get8(z->s); z->succ_high = (aa >> 4); z->succ_low = (aa & 15); if (z->progressive) { if (z->spec_start > 63 || z->spec_end > 63 || z->spec_start > z->spec_end || z->succ_high > 13 || z->succ_low > 13) return stbi__err("bad SOS", "Corrupt JPEG"); } else { if (z->spec_start != 0) return stbi__err("bad SOS","Corrupt JPEG"); if (z->succ_high != 0 || z->succ_low != 0) return stbi__err("bad SOS","Corrupt JPEG"); z->spec_end = 63; } } return 1; } static int stbi__free_jpeg_components(stbi__jpeg *z, int ncomp, int why) { int i; for (i=0; i < ncomp; ++i) { if (z->img_comp[i].raw_data) { STBI_FREE(z->img_comp[i].raw_data); z->img_comp[i].raw_data = NULL; z->img_comp[i].data = NULL; } if (z->img_comp[i].raw_coeff) { STBI_FREE(z->img_comp[i].raw_coeff); z->img_comp[i].raw_coeff = 0; z->img_comp[i].coeff = 0; } if (z->img_comp[i].linebuf) { STBI_FREE(z->img_comp[i].linebuf); z->img_comp[i].linebuf = NULL; } } return why; } static int stbi__process_frame_header(stbi__jpeg *z, int scan) { stbi__context *s = z->s; int Lf,p,i,q, h_max=1,v_max=1,c; Lf = stbi__get16be(s); if (Lf < 11) return stbi__err("bad SOF len","Corrupt JPEG"); // JPEG p = stbi__get8(s); if (p != 8) return stbi__err("only 8-bit","JPEG format not supported: 8-bit only"); // JPEG baseline s->img_y = stbi__get16be(s); if (s->img_y == 0) return stbi__err("no header height", "JPEG format not supported: delayed height"); // Legal, but we don't handle it--but neither does IJG s->img_x = stbi__get16be(s); if (s->img_x == 0) return stbi__err("0 width","Corrupt JPEG"); // JPEG requires if (s->img_y > STBI_MAX_DIMENSIONS) return stbi__err("too large","Very large image (corrupt?)"); if (s->img_x > STBI_MAX_DIMENSIONS) return stbi__err("too large","Very large image (corrupt?)"); c = stbi__get8(s); if (c != 3 && c != 1 && c != 4) return stbi__err("bad component count","Corrupt JPEG"); s->img_n = c; for (i=0; i < c; ++i) { z->img_comp[i].data = NULL; z->img_comp[i].linebuf = NULL; } if (Lf != 8+3*s->img_n) return stbi__err("bad SOF len","Corrupt JPEG"); z->rgb = 0; for (i=0; i < s->img_n; ++i) { static const unsigned char rgb[3] = { 'R', 'G', 'B' }; z->img_comp[i].id = stbi__get8(s); if (s->img_n == 3 && z->img_comp[i].id == rgb[i]) ++z->rgb; q = stbi__get8(s); z->img_comp[i].h = (q >> 4); if (!z->img_comp[i].h || z->img_comp[i].h > 4) return stbi__err("bad H","Corrupt JPEG"); z->img_comp[i].v = q & 15; if (!z->img_comp[i].v || z->img_comp[i].v > 4) return stbi__err("bad V","Corrupt JPEG"); z->img_comp[i].tq = stbi__get8(s); if (z->img_comp[i].tq > 3) return stbi__err("bad TQ","Corrupt JPEG"); } if (scan != STBI__SCAN_load) return 1; if (!stbi__mad3sizes_valid(s->img_x, s->img_y, s->img_n, 0)) return stbi__err("too large", "Image too large to decode"); for (i=0; i < s->img_n; ++i) { if (z->img_comp[i].h > h_max) h_max = z->img_comp[i].h; if (z->img_comp[i].v > v_max) v_max = z->img_comp[i].v; } // check that plane subsampling factors are integer ratios; our resamplers can't deal with fractional ratios // and I've never seen a non-corrupted JPEG file actually use them for (i=0; i < s->img_n; ++i) { if (h_max % z->img_comp[i].h != 0) return stbi__err("bad H","Corrupt JPEG"); if (v_max % z->img_comp[i].v != 0) return stbi__err("bad V","Corrupt JPEG"); } // compute interleaved mcu info z->img_h_max = h_max; z->img_v_max = v_max; z->img_mcu_w = h_max * 8; z->img_mcu_h = v_max * 8; // these sizes can't be more than 17 bits z->img_mcu_x = (s->img_x + z->img_mcu_w-1) / z->img_mcu_w; z->img_mcu_y = (s->img_y + z->img_mcu_h-1) / z->img_mcu_h; for (i=0; i < s->img_n; ++i) { // number of effective pixels (e.g. for non-interleaved MCU) z->img_comp[i].x = (s->img_x * z->img_comp[i].h + h_max-1) / h_max; z->img_comp[i].y = (s->img_y * z->img_comp[i].v + v_max-1) / v_max; // to simplify generation, we'll allocate enough memory to decode // the bogus oversized data from using interleaved MCUs and their // big blocks (e.g. a 16x16 iMCU on an image of width 33); we won't // discard the extra data until colorspace conversion // // img_mcu_x, img_mcu_y: <=17 bits; comp[i].h and .v are <=4 (checked earlier) // so these muls can't overflow with 32-bit ints (which we require) z->img_comp[i].w2 = z->img_mcu_x * z->img_comp[i].h * 8; z->img_comp[i].h2 = z->img_mcu_y * z->img_comp[i].v * 8; z->img_comp[i].coeff = 0; z->img_comp[i].raw_coeff = 0; z->img_comp[i].linebuf = NULL; z->img_comp[i].raw_data = stbi__malloc_mad2(z->img_comp[i].w2, z->img_comp[i].h2, 15); if (z->img_comp[i].raw_data == NULL) return stbi__free_jpeg_components(z, i+1, stbi__err("outofmem", "Out of memory")); // align blocks for idct using mmx/sse z->img_comp[i].data = (stbi_uc*) (((size_t) z->img_comp[i].raw_data + 15) & ~15); if (z->progressive) { // w2, h2 are multiples of 8 (see above) z->img_comp[i].coeff_w = z->img_comp[i].w2 / 8; z->img_comp[i].coeff_h = z->img_comp[i].h2 / 8; z->img_comp[i].raw_coeff = stbi__malloc_mad3(z->img_comp[i].w2, z->img_comp[i].h2, sizeof(short), 15); if (z->img_comp[i].raw_coeff == NULL) return stbi__free_jpeg_components(z, i+1, stbi__err("outofmem", "Out of memory")); z->img_comp[i].coeff = (short*) (((size_t) z->img_comp[i].raw_coeff + 15) & ~15); } } return 1; } // use comparisons since in some cases we handle more than one case (e.g. SOF) #define stbi__DNL(x) ((x) == 0xdc) #define stbi__SOI(x) ((x) == 0xd8) #define stbi__EOI(x) ((x) == 0xd9) #define stbi__SOF(x) ((x) == 0xc0 || (x) == 0xc1 || (x) == 0xc2) #define stbi__SOS(x) ((x) == 0xda) #define stbi__SOF_progressive(x) ((x) == 0xc2) static int stbi__decode_jpeg_header(stbi__jpeg *z, int scan) { int m; z->jfif = 0; z->app14_color_transform = -1; // valid values are 0,1,2 z->marker = STBI__MARKER_none; // initialize cached marker to empty m = stbi__get_marker(z); if (!stbi__SOI(m)) return stbi__err("no SOI","Corrupt JPEG"); if (scan == STBI__SCAN_type) return 1; m = stbi__get_marker(z); while (!stbi__SOF(m)) { if (!stbi__process_marker(z,m)) return 0; m = stbi__get_marker(z); while (m == STBI__MARKER_none) { // some files have extra padding after their blocks, so ok, we'll scan if (stbi__at_eof(z->s)) return stbi__err("no SOF", "Corrupt JPEG"); m = stbi__get_marker(z); } } z->progressive = stbi__SOF_progressive(m); if (!stbi__process_frame_header(z, scan)) return 0; return 1; } static stbi_uc stbi__skip_jpeg_junk_at_end(stbi__jpeg *j) { // some JPEGs have junk at end, skip over it but if we find what looks // like a valid marker, resume there while (!stbi__at_eof(j->s)) { stbi_uc x = stbi__get8(j->s); while (x == 0xff) { // might be a marker if (stbi__at_eof(j->s)) return STBI__MARKER_none; x = stbi__get8(j->s); if (x != 0x00 && x != 0xff) { // not a stuffed zero or lead-in to another marker, looks // like an actual marker, return it return x; } // stuffed zero has x=0 now which ends the loop, meaning we go // back to regular scan loop. // repeated 0xff keeps trying to read the next byte of the marker. } } return STBI__MARKER_none; } // decode image to YCbCr format static int stbi__decode_jpeg_image(stbi__jpeg *j) { int m; for (m = 0; m < 4; m++) { j->img_comp[m].raw_data = NULL; j->img_comp[m].raw_coeff = NULL; } j->restart_interval = 0; if (!stbi__decode_jpeg_header(j, STBI__SCAN_load)) return 0; m = stbi__get_marker(j); while (!stbi__EOI(m)) { if (stbi__SOS(m)) { if (!stbi__process_scan_header(j)) return 0; if (!stbi__parse_entropy_coded_data(j)) return 0; if (j->marker == STBI__MARKER_none ) { j->marker = stbi__skip_jpeg_junk_at_end(j); // if we reach eof without hitting a marker, stbi__get_marker() below will fail and we'll eventually return 0 } m = stbi__get_marker(j); if (STBI__RESTART(m)) m = stbi__get_marker(j); } else if (stbi__DNL(m)) { int Ld = stbi__get16be(j->s); stbi__uint32 NL = stbi__get16be(j->s); if (Ld != 4) return stbi__err("bad DNL len", "Corrupt JPEG"); if (NL != j->s->img_y) return stbi__err("bad DNL height", "Corrupt JPEG"); m = stbi__get_marker(j); } else { if (!stbi__process_marker(j, m)) return 1; m = stbi__get_marker(j); } } if (j->progressive) stbi__jpeg_finish(j); return 1; } // static jfif-centered resampling (across block boundaries) typedef stbi_uc *(*resample_row_func)(stbi_uc *out, stbi_uc *in0, stbi_uc *in1, int w, int hs); #define stbi__div4(x) ((stbi_uc) ((x) >> 2)) static stbi_uc *resample_row_1(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) { STBI_NOTUSED(out); STBI_NOTUSED(in_far); STBI_NOTUSED(w); STBI_NOTUSED(hs); return in_near; } static stbi_uc* stbi__resample_row_v_2(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) { // need to generate two samples vertically for every one in input int i; STBI_NOTUSED(hs); for (i=0; i < w; ++i) out[i] = stbi__div4(3*in_near[i] + in_far[i] + 2); return out; } static stbi_uc* stbi__resample_row_h_2(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) { // need to generate two samples horizontally for every one in input int i; stbi_uc *input = in_near; if (w == 1) { // if only one sample, can't do any interpolation out[0] = out[1] = input[0]; return out; } out[0] = input[0]; out[1] = stbi__div4(input[0]*3 + input[1] + 2); for (i=1; i < w-1; ++i) { int n = 3*input[i]+2; out[i*2+0] = stbi__div4(n+input[i-1]); out[i*2+1] = stbi__div4(n+input[i+1]); } out[i*2+0] = stbi__div4(input[w-2]*3 + input[w-1] + 2); out[i*2+1] = input[w-1]; STBI_NOTUSED(in_far); STBI_NOTUSED(hs); return out; } #define stbi__div16(x) ((stbi_uc) ((x) >> 4)) static stbi_uc *stbi__resample_row_hv_2(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) { // need to generate 2x2 samples for every one in input int i,t0,t1; if (w == 1) { out[0] = out[1] = stbi__div4(3*in_near[0] + in_far[0] + 2); return out; } t1 = 3*in_near[0] + in_far[0]; out[0] = stbi__div4(t1+2); for (i=1; i < w; ++i) { t0 = t1; t1 = 3*in_near[i]+in_far[i]; out[i*2-1] = stbi__div16(3*t0 + t1 + 8); out[i*2 ] = stbi__div16(3*t1 + t0 + 8); } out[w*2-1] = stbi__div4(t1+2); STBI_NOTUSED(hs); return out; } #if defined(STBI_SSE2) || defined(STBI_NEON) static stbi_uc *stbi__resample_row_hv_2_simd(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) { // need to generate 2x2 samples for every one in input int i=0,t0,t1; if (w == 1) { out[0] = out[1] = stbi__div4(3*in_near[0] + in_far[0] + 2); return out; } t1 = 3*in_near[0] + in_far[0]; // process groups of 8 pixels for as long as we can. // note we can't handle the last pixel in a row in this loop // because we need to handle the filter boundary conditions. for (; i < ((w-1) & ~7); i += 8) { #if defined(STBI_SSE2) // load and perform the vertical filtering pass // this uses 3*x + y = 4*x + (y - x) __m128i zero = _mm_setzero_si128(); __m128i farb = _mm_loadl_epi64((__m128i *) (in_far + i)); __m128i nearb = _mm_loadl_epi64((__m128i *) (in_near + i)); __m128i farw = _mm_unpacklo_epi8(farb, zero); __m128i nearw = _mm_unpacklo_epi8(nearb, zero); __m128i diff = _mm_sub_epi16(farw, nearw); __m128i nears = _mm_slli_epi16(nearw, 2); __m128i curr = _mm_add_epi16(nears, diff); // current row // horizontal filter works the same based on shifted vers of current // row. "prev" is current row shifted right by 1 pixel; we need to // insert the previous pixel value (from t1). // "next" is current row shifted left by 1 pixel, with first pixel // of next block of 8 pixels added in. __m128i prv0 = _mm_slli_si128(curr, 2); __m128i nxt0 = _mm_srli_si128(curr, 2); __m128i prev = _mm_insert_epi16(prv0, t1, 0); __m128i next = _mm_insert_epi16(nxt0, 3*in_near[i+8] + in_far[i+8], 7); // horizontal filter, polyphase implementation since it's convenient: // even pixels = 3*cur + prev = cur*4 + (prev - cur) // odd pixels = 3*cur + next = cur*4 + (next - cur) // note the shared term. __m128i bias = _mm_set1_epi16(8); __m128i curs = _mm_slli_epi16(curr, 2); __m128i prvd = _mm_sub_epi16(prev, curr); __m128i nxtd = _mm_sub_epi16(next, curr); __m128i curb = _mm_add_epi16(curs, bias); __m128i even = _mm_add_epi16(prvd, curb); __m128i odd = _mm_add_epi16(nxtd, curb); // interleave even and odd pixels, then undo scaling. __m128i int0 = _mm_unpacklo_epi16(even, odd); __m128i int1 = _mm_unpackhi_epi16(even, odd); __m128i de0 = _mm_srli_epi16(int0, 4); __m128i de1 = _mm_srli_epi16(int1, 4); // pack and write output __m128i outv = _mm_packus_epi16(de0, de1); _mm_storeu_si128((__m128i *) (out + i*2), outv); #elif defined(STBI_NEON) // load and perform the vertical filtering pass // this uses 3*x + y = 4*x + (y - x) uint8x8_t farb = vld1_u8(in_far + i); uint8x8_t nearb = vld1_u8(in_near + i); int16x8_t diff = vreinterpretq_s16_u16(vsubl_u8(farb, nearb)); int16x8_t nears = vreinterpretq_s16_u16(vshll_n_u8(nearb, 2)); int16x8_t curr = vaddq_s16(nears, diff); // current row // horizontal filter works the same based on shifted vers of current // row. "prev" is current row shifted right by 1 pixel; we need to // insert the previous pixel value (from t1). // "next" is current row shifted left by 1 pixel, with first pixel // of next block of 8 pixels added in. int16x8_t prv0 = vextq_s16(curr, curr, 7); int16x8_t nxt0 = vextq_s16(curr, curr, 1); int16x8_t prev = vsetq_lane_s16(t1, prv0, 0); int16x8_t next = vsetq_lane_s16(3*in_near[i+8] + in_far[i+8], nxt0, 7); // horizontal filter, polyphase implementation since it's convenient: // even pixels = 3*cur + prev = cur*4 + (prev - cur) // odd pixels = 3*cur + next = cur*4 + (next - cur) // note the shared term. int16x8_t curs = vshlq_n_s16(curr, 2); int16x8_t prvd = vsubq_s16(prev, curr); int16x8_t nxtd = vsubq_s16(next, curr); int16x8_t even = vaddq_s16(curs, prvd); int16x8_t odd = vaddq_s16(curs, nxtd); // undo scaling and round, then store with even/odd phases interleaved uint8x8x2_t o; o.val[0] = vqrshrun_n_s16(even, 4); o.val[1] = vqrshrun_n_s16(odd, 4); vst2_u8(out + i*2, o); #endif // "previous" value for next iter t1 = 3*in_near[i+7] + in_far[i+7]; } t0 = t1; t1 = 3*in_near[i] + in_far[i]; out[i*2] = stbi__div16(3*t1 + t0 + 8); for (++i; i < w; ++i) { t0 = t1; t1 = 3*in_near[i]+in_far[i]; out[i*2-1] = stbi__div16(3*t0 + t1 + 8); out[i*2 ] = stbi__div16(3*t1 + t0 + 8); } out[w*2-1] = stbi__div4(t1+2); STBI_NOTUSED(hs); return out; } #endif static stbi_uc *stbi__resample_row_generic(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) { // resample with nearest-neighbor int i,j; STBI_NOTUSED(in_far); for (i=0; i < w; ++i) for (j=0; j < hs; ++j) out[i*hs+j] = in_near[i]; return out; } // this is a reduced-precision calculation of YCbCr-to-RGB introduced // to make sure the code produces the same results in both SIMD and scalar #define stbi__float2fixed(x) (((int) ((x) * 4096.0f + 0.5f)) << 8) static void stbi__YCbCr_to_RGB_row(stbi_uc *out, const stbi_uc *y, const stbi_uc *pcb, const stbi_uc *pcr, int count, int step) { int i; for (i=0; i < count; ++i) { int y_fixed = (y[i] << 20) + (1<<19); // rounding int r,g,b; int cr = pcr[i] - 128; int cb = pcb[i] - 128; r = y_fixed + cr* stbi__float2fixed(1.40200f); g = y_fixed + (cr*-stbi__float2fixed(0.71414f)) + ((cb*-stbi__float2fixed(0.34414f)) & 0xffff0000); b = y_fixed + cb* stbi__float2fixed(1.77200f); r >>= 20; g >>= 20; b >>= 20; if ((unsigned) r > 255) { if (r < 0) r = 0; else r = 255; } if ((unsigned) g > 255) { if (g < 0) g = 0; else g = 255; } if ((unsigned) b > 255) { if (b < 0) b = 0; else b = 255; } out[0] = (stbi_uc)r; out[1] = (stbi_uc)g; out[2] = (stbi_uc)b; out[3] = 255; out += step; } } #if defined(STBI_SSE2) || defined(STBI_NEON) static void stbi__YCbCr_to_RGB_simd(stbi_uc *out, stbi_uc const *y, stbi_uc const *pcb, stbi_uc const *pcr, int count, int step) { int i = 0; #ifdef STBI_SSE2 // step == 3 is pretty ugly on the final interleave, and i'm not convinced // it's useful in practice (you wouldn't use it for textures, for example). // so just accelerate step == 4 case. if (step == 4) { // this is a fairly straightforward implementation and not super-optimized. __m128i signflip = _mm_set1_epi8(-0x80); __m128i cr_const0 = _mm_set1_epi16( (short) ( 1.40200f*4096.0f+0.5f)); __m128i cr_const1 = _mm_set1_epi16( - (short) ( 0.71414f*4096.0f+0.5f)); __m128i cb_const0 = _mm_set1_epi16( - (short) ( 0.34414f*4096.0f+0.5f)); __m128i cb_const1 = _mm_set1_epi16( (short) ( 1.77200f*4096.0f+0.5f)); __m128i y_bias = _mm_set1_epi8((char) (unsigned char) 128); __m128i xw = _mm_set1_epi16(255); // alpha channel for (; i+7 < count; i += 8) { // load __m128i y_bytes = _mm_loadl_epi64((__m128i *) (y+i)); __m128i cr_bytes = _mm_loadl_epi64((__m128i *) (pcr+i)); __m128i cb_bytes = _mm_loadl_epi64((__m128i *) (pcb+i)); __m128i cr_biased = _mm_xor_si128(cr_bytes, signflip); // -128 __m128i cb_biased = _mm_xor_si128(cb_bytes, signflip); // -128 // unpack to short (and left-shift cr, cb by 8) __m128i yw = _mm_unpacklo_epi8(y_bias, y_bytes); __m128i crw = _mm_unpacklo_epi8(_mm_setzero_si128(), cr_biased); __m128i cbw = _mm_unpacklo_epi8(_mm_setzero_si128(), cb_biased); // color transform __m128i yws = _mm_srli_epi16(yw, 4); __m128i cr0 = _mm_mulhi_epi16(cr_const0, crw); __m128i cb0 = _mm_mulhi_epi16(cb_const0, cbw); __m128i cb1 = _mm_mulhi_epi16(cbw, cb_const1); __m128i cr1 = _mm_mulhi_epi16(crw, cr_const1); __m128i rws = _mm_add_epi16(cr0, yws); __m128i gwt = _mm_add_epi16(cb0, yws); __m128i bws = _mm_add_epi16(yws, cb1); __m128i gws = _mm_add_epi16(gwt, cr1); // descale __m128i rw = _mm_srai_epi16(rws, 4); __m128i bw = _mm_srai_epi16(bws, 4); __m128i gw = _mm_srai_epi16(gws, 4); // back to byte, set up for transpose __m128i brb = _mm_packus_epi16(rw, bw); __m128i gxb = _mm_packus_epi16(gw, xw); // transpose to interleave channels __m128i t0 = _mm_unpacklo_epi8(brb, gxb); __m128i t1 = _mm_unpackhi_epi8(brb, gxb); __m128i o0 = _mm_unpacklo_epi16(t0, t1); __m128i o1 = _mm_unpackhi_epi16(t0, t1); // store _mm_storeu_si128((__m128i *) (out + 0), o0); _mm_storeu_si128((__m128i *) (out + 16), o1); out += 32; } } #endif #ifdef STBI_NEON // in this version, step=3 support would be easy to add. but is there demand? if (step == 4) { // this is a fairly straightforward implementation and not super-optimized. uint8x8_t signflip = vdup_n_u8(0x80); int16x8_t cr_const0 = vdupq_n_s16( (short) ( 1.40200f*4096.0f+0.5f)); int16x8_t cr_const1 = vdupq_n_s16( - (short) ( 0.71414f*4096.0f+0.5f)); int16x8_t cb_const0 = vdupq_n_s16( - (short) ( 0.34414f*4096.0f+0.5f)); int16x8_t cb_const1 = vdupq_n_s16( (short) ( 1.77200f*4096.0f+0.5f)); for (; i+7 < count; i += 8) { // load uint8x8_t y_bytes = vld1_u8(y + i); uint8x8_t cr_bytes = vld1_u8(pcr + i); uint8x8_t cb_bytes = vld1_u8(pcb + i); int8x8_t cr_biased = vreinterpret_s8_u8(vsub_u8(cr_bytes, signflip)); int8x8_t cb_biased = vreinterpret_s8_u8(vsub_u8(cb_bytes, signflip)); // expand to s16 int16x8_t yws = vreinterpretq_s16_u16(vshll_n_u8(y_bytes, 4)); int16x8_t crw = vshll_n_s8(cr_biased, 7); int16x8_t cbw = vshll_n_s8(cb_biased, 7); // color transform int16x8_t cr0 = vqdmulhq_s16(crw, cr_const0); int16x8_t cb0 = vqdmulhq_s16(cbw, cb_const0); int16x8_t cr1 = vqdmulhq_s16(crw, cr_const1); int16x8_t cb1 = vqdmulhq_s16(cbw, cb_const1); int16x8_t rws = vaddq_s16(yws, cr0); int16x8_t gws = vaddq_s16(vaddq_s16(yws, cb0), cr1); int16x8_t bws = vaddq_s16(yws, cb1); // undo scaling, round, convert to byte uint8x8x4_t o; o.val[0] = vqrshrun_n_s16(rws, 4); o.val[1] = vqrshrun_n_s16(gws, 4); o.val[2] = vqrshrun_n_s16(bws, 4); o.val[3] = vdup_n_u8(255); // store, interleaving r/g/b/a vst4_u8(out, o); out += 8*4; } } #endif for (; i < count; ++i) { int y_fixed = (y[i] << 20) + (1<<19); // rounding int r,g,b; int cr = pcr[i] - 128; int cb = pcb[i] - 128; r = y_fixed + cr* stbi__float2fixed(1.40200f); g = y_fixed + cr*-stbi__float2fixed(0.71414f) + ((cb*-stbi__float2fixed(0.34414f)) & 0xffff0000); b = y_fixed + cb* stbi__float2fixed(1.77200f); r >>= 20; g >>= 20; b >>= 20; if ((unsigned) r > 255) { if (r < 0) r = 0; else r = 255; } if ((unsigned) g > 255) { if (g < 0) g = 0; else g = 255; } if ((unsigned) b > 255) { if (b < 0) b = 0; else b = 255; } out[0] = (stbi_uc)r; out[1] = (stbi_uc)g; out[2] = (stbi_uc)b; out[3] = 255; out += step; } } #endif // set up the kernels static void stbi__setup_jpeg(stbi__jpeg *j) { j->idct_block_kernel = stbi__idct_block; j->YCbCr_to_RGB_kernel = stbi__YCbCr_to_RGB_row; j->resample_row_hv_2_kernel = stbi__resample_row_hv_2; #ifdef STBI_SSE2 if (stbi__sse2_available()) { j->idct_block_kernel = stbi__idct_simd; j->YCbCr_to_RGB_kernel = stbi__YCbCr_to_RGB_simd; j->resample_row_hv_2_kernel = stbi__resample_row_hv_2_simd; } #endif #ifdef STBI_NEON j->idct_block_kernel = stbi__idct_simd; j->YCbCr_to_RGB_kernel = stbi__YCbCr_to_RGB_simd; j->resample_row_hv_2_kernel = stbi__resample_row_hv_2_simd; #endif } // clean up the temporary component buffers static void stbi__cleanup_jpeg(stbi__jpeg *j) { stbi__free_jpeg_components(j, j->s->img_n, 0); } typedef struct { resample_row_func resample; stbi_uc *line0,*line1; int hs,vs; // expansion factor in each axis int w_lores; // horizontal pixels pre-expansion int ystep; // how far through vertical expansion we are int ypos; // which pre-expansion row we're on } stbi__resample; // fast 0..255 * 0..255 => 0..255 rounded multiplication static stbi_uc stbi__blinn_8x8(stbi_uc x, stbi_uc y) { unsigned int t = x*y + 128; return (stbi_uc) ((t + (t >>8)) >> 8); } static stbi_uc *load_jpeg_image(stbi__jpeg *z, int *out_x, int *out_y, int *comp, int req_comp) { int n, decode_n, is_rgb; z->s->img_n = 0; // make stbi__cleanup_jpeg safe // validate req_comp if (req_comp < 0 || req_comp > 4) return stbi__errpuc("bad req_comp", "Internal error"); // load a jpeg image from whichever source, but leave in YCbCr format if (!stbi__decode_jpeg_image(z)) { stbi__cleanup_jpeg(z); return NULL; } // determine actual number of components to generate n = req_comp ? req_comp : z->s->img_n >= 3 ? 3 : 1; is_rgb = z->s->img_n == 3 && (z->rgb == 3 || (z->app14_color_transform == 0 && !z->jfif)); if (z->s->img_n == 3 && n < 3 && !is_rgb) decode_n = 1; else decode_n = z->s->img_n; // nothing to do if no components requested; check this now to avoid // accessing uninitialized coutput[0] later if (decode_n <= 0) { stbi__cleanup_jpeg(z); return NULL; } // resample and color-convert { int k; unsigned int i,j; stbi_uc *output; stbi_uc *coutput[4] = { NULL, NULL, NULL, NULL }; stbi__resample res_comp[4]; for (k=0; k < decode_n; ++k) { stbi__resample *r = &res_comp[k]; // allocate line buffer big enough for upsampling off the edges // with upsample factor of 4 z->img_comp[k].linebuf = (stbi_uc *) stbi__malloc(z->s->img_x + 3); if (!z->img_comp[k].linebuf) { stbi__cleanup_jpeg(z); return stbi__errpuc("outofmem", "Out of memory"); } r->hs = z->img_h_max / z->img_comp[k].h; r->vs = z->img_v_max / z->img_comp[k].v; r->ystep = r->vs >> 1; r->w_lores = (z->s->img_x + r->hs-1) / r->hs; r->ypos = 0; r->line0 = r->line1 = z->img_comp[k].data; if (r->hs == 1 && r->vs == 1) r->resample = resample_row_1; else if (r->hs == 1 && r->vs == 2) r->resample = stbi__resample_row_v_2; else if (r->hs == 2 && r->vs == 1) r->resample = stbi__resample_row_h_2; else if (r->hs == 2 && r->vs == 2) r->resample = z->resample_row_hv_2_kernel; else r->resample = stbi__resample_row_generic; } // can't error after this so, this is safe output = (stbi_uc *) stbi__malloc_mad3(n, z->s->img_x, z->s->img_y, 1); if (!output) { stbi__cleanup_jpeg(z); return stbi__errpuc("outofmem", "Out of memory"); } // now go ahead and resample for (j=0; j < z->s->img_y; ++j) { stbi_uc *out = output + n * z->s->img_x * j; for (k=0; k < decode_n; ++k) { stbi__resample *r = &res_comp[k]; int y_bot = r->ystep >= (r->vs >> 1); coutput[k] = r->resample(z->img_comp[k].linebuf, y_bot ? r->line1 : r->line0, y_bot ? r->line0 : r->line1, r->w_lores, r->hs); if (++r->ystep >= r->vs) { r->ystep = 0; r->line0 = r->line1; if (++r->ypos < z->img_comp[k].y) r->line1 += z->img_comp[k].w2; } } if (n >= 3) { stbi_uc *y = coutput[0]; if (z->s->img_n == 3) { if (is_rgb) { for (i=0; i < z->s->img_x; ++i) { out[0] = y[i]; out[1] = coutput[1][i]; out[2] = coutput[2][i]; out[3] = 255; out += n; } } else { z->YCbCr_to_RGB_kernel(out, y, coutput[1], coutput[2], z->s->img_x, n); } } else if (z->s->img_n == 4) { if (z->app14_color_transform == 0) { // CMYK for (i=0; i < z->s->img_x; ++i) { stbi_uc m = coutput[3][i]; out[0] = stbi__blinn_8x8(coutput[0][i], m); out[1] = stbi__blinn_8x8(coutput[1][i], m); out[2] = stbi__blinn_8x8(coutput[2][i], m); out[3] = 255; out += n; } } else if (z->app14_color_transform == 2) { // YCCK z->YCbCr_to_RGB_kernel(out, y, coutput[1], coutput[2], z->s->img_x, n); for (i=0; i < z->s->img_x; ++i) { stbi_uc m = coutput[3][i]; out[0] = stbi__blinn_8x8(255 - out[0], m); out[1] = stbi__blinn_8x8(255 - out[1], m); out[2] = stbi__blinn_8x8(255 - out[2], m); out += n; } } else { // YCbCr + alpha? Ignore the fourth channel for now z->YCbCr_to_RGB_kernel(out, y, coutput[1], coutput[2], z->s->img_x, n); } } else for (i=0; i < z->s->img_x; ++i) { out[0] = out[1] = out[2] = y[i]; out[3] = 255; // not used if n==3 out += n; } } else { if (is_rgb) { if (n == 1) for (i=0; i < z->s->img_x; ++i) *out++ = stbi__compute_y(coutput[0][i], coutput[1][i], coutput[2][i]); else { for (i=0; i < z->s->img_x; ++i, out += 2) { out[0] = stbi__compute_y(coutput[0][i], coutput[1][i], coutput[2][i]); out[1] = 255; } } } else if (z->s->img_n == 4 && z->app14_color_transform == 0) { for (i=0; i < z->s->img_x; ++i) { stbi_uc m = coutput[3][i]; stbi_uc r = stbi__blinn_8x8(coutput[0][i], m); stbi_uc g = stbi__blinn_8x8(coutput[1][i], m); stbi_uc b = stbi__blinn_8x8(coutput[2][i], m); out[0] = stbi__compute_y(r, g, b); out[1] = 255; out += n; } } else if (z->s->img_n == 4 && z->app14_color_transform == 2) { for (i=0; i < z->s->img_x; ++i) { out[0] = stbi__blinn_8x8(255 - coutput[0][i], coutput[3][i]); out[1] = 255; out += n; } } else { stbi_uc *y = coutput[0]; if (n == 1) for (i=0; i < z->s->img_x; ++i) out[i] = y[i]; else for (i=0; i < z->s->img_x; ++i) { *out++ = y[i]; *out++ = 255; } } } } stbi__cleanup_jpeg(z); *out_x = z->s->img_x; *out_y = z->s->img_y; if (comp) *comp = z->s->img_n >= 3 ? 3 : 1; // report original components, not output return output; } } static void *stbi__jpeg_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) { unsigned char* result; stbi__jpeg* j = (stbi__jpeg*) stbi__malloc(sizeof(stbi__jpeg)); if (!j) return stbi__errpuc("outofmem", "Out of memory"); memset(j, 0, sizeof(stbi__jpeg)); STBI_NOTUSED(ri); j->s = s; stbi__setup_jpeg(j); result = load_jpeg_image(j, x,y,comp,req_comp); STBI_FREE(j); return result; } static int stbi__jpeg_test(stbi__context *s) { int r; stbi__jpeg* j = (stbi__jpeg*)stbi__malloc(sizeof(stbi__jpeg)); if (!j) return stbi__err("outofmem", "Out of memory"); memset(j, 0, sizeof(stbi__jpeg)); j->s = s; stbi__setup_jpeg(j); r = stbi__decode_jpeg_header(j, STBI__SCAN_type); stbi__rewind(s); STBI_FREE(j); return r; } static int stbi__jpeg_info_raw(stbi__jpeg *j, int *x, int *y, int *comp) { if (!stbi__decode_jpeg_header(j, STBI__SCAN_header)) { stbi__rewind( j->s ); return 0; } if (x) *x = j->s->img_x; if (y) *y = j->s->img_y; if (comp) *comp = j->s->img_n >= 3 ? 3 : 1; return 1; } static int stbi__jpeg_info(stbi__context *s, int *x, int *y, int *comp) { int result; stbi__jpeg* j = (stbi__jpeg*) (stbi__malloc(sizeof(stbi__jpeg))); if (!j) return stbi__err("outofmem", "Out of memory"); memset(j, 0, sizeof(stbi__jpeg)); j->s = s; result = stbi__jpeg_info_raw(j, x, y, comp); STBI_FREE(j); return result; } #endif // public domain zlib decode v0.2 Sean Barrett 2006-11-18 // simple implementation // - all input must be provided in an upfront buffer // - all output is written to a single output buffer (can malloc/realloc) // performance // - fast huffman #ifndef STBI_NO_ZLIB // fast-way is faster to check than jpeg huffman, but slow way is slower #define STBI__ZFAST_BITS 9 // accelerate all cases in default tables #define STBI__ZFAST_MASK ((1 << STBI__ZFAST_BITS) - 1) #define STBI__ZNSYMS 288 // number of symbols in literal/length alphabet // zlib-style huffman encoding // (jpegs packs from left, zlib from right, so can't share code) typedef struct { stbi__uint16 fast[1 << STBI__ZFAST_BITS]; stbi__uint16 firstcode[16]; int maxcode[17]; stbi__uint16 firstsymbol[16]; stbi_uc size[STBI__ZNSYMS]; stbi__uint16 value[STBI__ZNSYMS]; } stbi__zhuffman; stbi_inline static int stbi__bitreverse16(int n) { n = ((n & 0xAAAA) >> 1) | ((n & 0x5555) << 1); n = ((n & 0xCCCC) >> 2) | ((n & 0x3333) << 2); n = ((n & 0xF0F0) >> 4) | ((n & 0x0F0F) << 4); n = ((n & 0xFF00) >> 8) | ((n & 0x00FF) << 8); return n; } stbi_inline static int stbi__bit_reverse(int v, int bits) { STBI_ASSERT(bits <= 16); // to bit reverse n bits, reverse 16 and shift // e.g. 11 bits, bit reverse and shift away 5 return stbi__bitreverse16(v) >> (16-bits); } static int stbi__zbuild_huffman(stbi__zhuffman *z, const stbi_uc *sizelist, int num) { int i,k=0; int code, next_code[16], sizes[17]; // DEFLATE spec for generating codes memset(sizes, 0, sizeof(sizes)); memset(z->fast, 0, sizeof(z->fast)); for (i=0; i < num; ++i) ++sizes[sizelist[i]]; sizes[0] = 0; for (i=1; i < 16; ++i) if (sizes[i] > (1 << i)) return stbi__err("bad sizes", "Corrupt PNG"); code = 0; for (i=1; i < 16; ++i) { next_code[i] = code; z->firstcode[i] = (stbi__uint16) code; z->firstsymbol[i] = (stbi__uint16) k; code = (code + sizes[i]); if (sizes[i]) if (code-1 >= (1 << i)) return stbi__err("bad codelengths","Corrupt PNG"); z->maxcode[i] = code << (16-i); // preshift for inner loop code <<= 1; k += sizes[i]; } z->maxcode[16] = 0x10000; // sentinel for (i=0; i < num; ++i) { int s = sizelist[i]; if (s) { int c = next_code[s] - z->firstcode[s] + z->firstsymbol[s]; stbi__uint16 fastv = (stbi__uint16) ((s << 9) | i); z->size [c] = (stbi_uc ) s; z->value[c] = (stbi__uint16) i; if (s <= STBI__ZFAST_BITS) { int j = stbi__bit_reverse(next_code[s],s); while (j < (1 << STBI__ZFAST_BITS)) { z->fast[j] = fastv; j += (1 << s); } } ++next_code[s]; } } return 1; } // zlib-from-memory implementation for PNG reading // because PNG allows splitting the zlib stream arbitrarily, // and it's annoying structurally to have PNG call ZLIB call PNG, // we require PNG read all the IDATs and combine them into a single // memory buffer typedef struct { stbi_uc *zbuffer, *zbuffer_end; int num_bits; int hit_zeof_once; stbi__uint32 code_buffer; char *zout; char *zout_start; char *zout_end; int z_expandable; stbi__zhuffman z_length, z_distance; } stbi__zbuf; stbi_inline static int stbi__zeof(stbi__zbuf *z) { return (z->zbuffer >= z->zbuffer_end); } stbi_inline static stbi_uc stbi__zget8(stbi__zbuf *z) { return stbi__zeof(z) ? 0 : *z->zbuffer++; } static void stbi__fill_bits(stbi__zbuf *z) { do { if (z->code_buffer >= (1U << z->num_bits)) { z->zbuffer = z->zbuffer_end; /* treat this as EOF so we fail. */ return; } z->code_buffer |= (unsigned int) stbi__zget8(z) << z->num_bits; z->num_bits += 8; } while (z->num_bits <= 24); } stbi_inline static unsigned int stbi__zreceive(stbi__zbuf *z, int n) { unsigned int k; if (z->num_bits < n) stbi__fill_bits(z); k = z->code_buffer & ((1 << n) - 1); z->code_buffer >>= n; z->num_bits -= n; return k; } static int stbi__zhuffman_decode_slowpath(stbi__zbuf *a, stbi__zhuffman *z) { int b,s,k; // not resolved by fast table, so compute it the slow way // use jpeg approach, which requires MSbits at top k = stbi__bit_reverse(a->code_buffer, 16); for (s=STBI__ZFAST_BITS+1; ; ++s) if (k < z->maxcode[s]) break; if (s >= 16) return -1; // invalid code! // code size is s, so: b = (k >> (16-s)) - z->firstcode[s] + z->firstsymbol[s]; if (b >= STBI__ZNSYMS) return -1; // some data was corrupt somewhere! if (z->size[b] != s) return -1; // was originally an assert, but report failure instead. a->code_buffer >>= s; a->num_bits -= s; return z->value[b]; } stbi_inline static int stbi__zhuffman_decode(stbi__zbuf *a, stbi__zhuffman *z) { int b,s; if (a->num_bits < 16) { if (stbi__zeof(a)) { if (!a->hit_zeof_once) { // This is the first time we hit eof, insert 16 extra padding btis // to allow us to keep going; if we actually consume any of them // though, that is invalid data. This is caught later. a->hit_zeof_once = 1; a->num_bits += 16; // add 16 implicit zero bits } else { // We already inserted our extra 16 padding bits and are again // out, this stream is actually prematurely terminated. return -1; } } else { stbi__fill_bits(a); } } b = z->fast[a->code_buffer & STBI__ZFAST_MASK]; if (b) { s = b >> 9; a->code_buffer >>= s; a->num_bits -= s; return b & 511; } return stbi__zhuffman_decode_slowpath(a, z); } static int stbi__zexpand(stbi__zbuf *z, char *zout, int n) // need to make room for n bytes { char *q; unsigned int cur, limit, old_limit; z->zout = zout; if (!z->z_expandable) return stbi__err("output buffer limit","Corrupt PNG"); cur = (unsigned int) (z->zout - z->zout_start); limit = old_limit = (unsigned) (z->zout_end - z->zout_start); if (UINT_MAX - cur < (unsigned) n) return stbi__err("outofmem", "Out of memory"); while (cur + n > limit) { if(limit > UINT_MAX / 2) return stbi__err("outofmem", "Out of memory"); limit *= 2; } q = (char *) STBI_REALLOC_SIZED(z->zout_start, old_limit, limit); STBI_NOTUSED(old_limit); if (q == NULL) return stbi__err("outofmem", "Out of memory"); z->zout_start = q; z->zout = q + cur; z->zout_end = q + limit; return 1; } static const int stbi__zlength_base[31] = { 3,4,5,6,7,8,9,10,11,13, 15,17,19,23,27,31,35,43,51,59, 67,83,99,115,131,163,195,227,258,0,0 }; static const int stbi__zlength_extra[31]= { 0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0,0,0 }; static const int stbi__zdist_base[32] = { 1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193, 257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577,0,0}; static const int stbi__zdist_extra[32] = { 0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13}; static int stbi__parse_huffman_block(stbi__zbuf *a) { char *zout = a->zout; for(;;) { int z = stbi__zhuffman_decode(a, &a->z_length); if (z < 256) { if (z < 0) return stbi__err("bad huffman code","Corrupt PNG"); // error in huffman codes if (zout >= a->zout_end) { if (!stbi__zexpand(a, zout, 1)) return 0; zout = a->zout; } *zout++ = (char) z; } else { stbi_uc *p; int len,dist; if (z == 256) { a->zout = zout; if (a->hit_zeof_once && a->num_bits < 16) { // The first time we hit zeof, we inserted 16 extra zero bits into our bit // buffer so the decoder can just do its speculative decoding. But if we // actually consumed any of those bits (which is the case when num_bits < 16), // the stream actually read past the end so it is malformed. return stbi__err("unexpected end","Corrupt PNG"); } return 1; } if (z >= 286) return stbi__err("bad huffman code","Corrupt PNG"); // per DEFLATE, length codes 286 and 287 must not appear in compressed data z -= 257; len = stbi__zlength_base[z]; if (stbi__zlength_extra[z]) len += stbi__zreceive(a, stbi__zlength_extra[z]); z = stbi__zhuffman_decode(a, &a->z_distance); if (z < 0 || z >= 30) return stbi__err("bad huffman code","Corrupt PNG"); // per DEFLATE, distance codes 30 and 31 must not appear in compressed data dist = stbi__zdist_base[z]; if (stbi__zdist_extra[z]) dist += stbi__zreceive(a, stbi__zdist_extra[z]); if (zout - a->zout_start < dist) return stbi__err("bad dist","Corrupt PNG"); if (len > a->zout_end - zout) { if (!stbi__zexpand(a, zout, len)) return 0; zout = a->zout; } p = (stbi_uc *) (zout - dist); if (dist == 1) { // run of one byte; common in images. stbi_uc v = *p; if (len) { do *zout++ = v; while (--len); } } else { if (len) { do *zout++ = *p++; while (--len); } } } } } static int stbi__compute_huffman_codes(stbi__zbuf *a) { static const stbi_uc length_dezigzag[19] = { 16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15 }; stbi__zhuffman z_codelength; stbi_uc lencodes[286+32+137];//padding for maximum single op stbi_uc codelength_sizes[19]; int i,n; int hlit = stbi__zreceive(a,5) + 257; int hdist = stbi__zreceive(a,5) + 1; int hclen = stbi__zreceive(a,4) + 4; int ntot = hlit + hdist; memset(codelength_sizes, 0, sizeof(codelength_sizes)); for (i=0; i < hclen; ++i) { int s = stbi__zreceive(a,3); codelength_sizes[length_dezigzag[i]] = (stbi_uc) s; } if (!stbi__zbuild_huffman(&z_codelength, codelength_sizes, 19)) return 0; n = 0; while (n < ntot) { int c = stbi__zhuffman_decode(a, &z_codelength); if (c < 0 || c >= 19) return stbi__err("bad codelengths", "Corrupt PNG"); if (c < 16) lencodes[n++] = (stbi_uc) c; else { stbi_uc fill = 0; if (c == 16) { c = stbi__zreceive(a,2)+3; if (n == 0) return stbi__err("bad codelengths", "Corrupt PNG"); fill = lencodes[n-1]; } else if (c == 17) { c = stbi__zreceive(a,3)+3; } else if (c == 18) { c = stbi__zreceive(a,7)+11; } else { return stbi__err("bad codelengths", "Corrupt PNG"); } if (ntot - n < c) return stbi__err("bad codelengths", "Corrupt PNG"); memset(lencodes+n, fill, c); n += c; } } if (n != ntot) return stbi__err("bad codelengths","Corrupt PNG"); if (!stbi__zbuild_huffman(&a->z_length, lencodes, hlit)) return 0; if (!stbi__zbuild_huffman(&a->z_distance, lencodes+hlit, hdist)) return 0; return 1; } static int stbi__parse_uncompressed_block(stbi__zbuf *a) { stbi_uc header[4]; int len,nlen,k; if (a->num_bits & 7) stbi__zreceive(a, a->num_bits & 7); // discard // drain the bit-packed data into header k = 0; while (a->num_bits > 0) { header[k++] = (stbi_uc) (a->code_buffer & 255); // suppress MSVC run-time check a->code_buffer >>= 8; a->num_bits -= 8; } if (a->num_bits < 0) return stbi__err("zlib corrupt","Corrupt PNG"); // now fill header the normal way while (k < 4) header[k++] = stbi__zget8(a); len = header[1] * 256 + header[0]; nlen = header[3] * 256 + header[2]; if (nlen != (len ^ 0xffff)) return stbi__err("zlib corrupt","Corrupt PNG"); if (a->zbuffer + len > a->zbuffer_end) return stbi__err("read past buffer","Corrupt PNG"); if (a->zout + len > a->zout_end) if (!stbi__zexpand(a, a->zout, len)) return 0; memcpy(a->zout, a->zbuffer, len); a->zbuffer += len; a->zout += len; return 1; } static int stbi__parse_zlib_header(stbi__zbuf *a) { int cmf = stbi__zget8(a); int cm = cmf & 15; /* int cinfo = cmf >> 4; */ int flg = stbi__zget8(a); if (stbi__zeof(a)) return stbi__err("bad zlib header","Corrupt PNG"); // zlib spec if ((cmf*256+flg) % 31 != 0) return stbi__err("bad zlib header","Corrupt PNG"); // zlib spec if (flg & 32) return stbi__err("no preset dict","Corrupt PNG"); // preset dictionary not allowed in png if (cm != 8) return stbi__err("bad compression","Corrupt PNG"); // DEFLATE required for png // window = 1 << (8 + cinfo)... but who cares, we fully buffer output return 1; } static const stbi_uc stbi__zdefault_length[STBI__ZNSYMS] = { 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, 7,7,7,7,7,7,7,7,8,8,8,8,8,8,8,8 }; static const stbi_uc stbi__zdefault_distance[32] = { 5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5 }; /* Init algorithm: { int i; // use <= to match clearly with spec for (i=0; i <= 143; ++i) stbi__zdefault_length[i] = 8; for ( ; i <= 255; ++i) stbi__zdefault_length[i] = 9; for ( ; i <= 279; ++i) stbi__zdefault_length[i] = 7; for ( ; i <= 287; ++i) stbi__zdefault_length[i] = 8; for (i=0; i <= 31; ++i) stbi__zdefault_distance[i] = 5; } */ static int stbi__parse_zlib(stbi__zbuf *a, int parse_header) { int final, type; if (parse_header) if (!stbi__parse_zlib_header(a)) return 0; a->num_bits = 0; a->code_buffer = 0; a->hit_zeof_once = 0; do { final = stbi__zreceive(a,1); type = stbi__zreceive(a,2); if (type == 0) { if (!stbi__parse_uncompressed_block(a)) return 0; } else if (type == 3) { return 0; } else { if (type == 1) { // use fixed code lengths if (!stbi__zbuild_huffman(&a->z_length , stbi__zdefault_length , STBI__ZNSYMS)) return 0; if (!stbi__zbuild_huffman(&a->z_distance, stbi__zdefault_distance, 32)) return 0; } else { if (!stbi__compute_huffman_codes(a)) return 0; } if (!stbi__parse_huffman_block(a)) return 0; } } while (!final); return 1; } static int stbi__do_zlib(stbi__zbuf *a, char *obuf, int olen, int exp, int parse_header) { a->zout_start = obuf; a->zout = obuf; a->zout_end = obuf + olen; a->z_expandable = exp; return stbi__parse_zlib(a, parse_header); } STBIDEF char *stbi_zlib_decode_malloc_guesssize(const char *buffer, int len, int initial_size, int *outlen) { stbi__zbuf a; char *p = (char *) stbi__malloc(initial_size); if (p == NULL) return NULL; a.zbuffer = (stbi_uc *) buffer; a.zbuffer_end = (stbi_uc *) buffer + len; if (stbi__do_zlib(&a, p, initial_size, 1, 1)) { if (outlen) *outlen = (int) (a.zout - a.zout_start); return a.zout_start; } else { STBI_FREE(a.zout_start); return NULL; } } STBIDEF char *stbi_zlib_decode_malloc(char const *buffer, int len, int *outlen) { return stbi_zlib_decode_malloc_guesssize(buffer, len, 16384, outlen); } STBIDEF char *stbi_zlib_decode_malloc_guesssize_headerflag(const char *buffer, int len, int initial_size, int *outlen, int parse_header) { stbi__zbuf a; char *p = (char *) stbi__malloc(initial_size); if (p == NULL) return NULL; a.zbuffer = (stbi_uc *) buffer; a.zbuffer_end = (stbi_uc *) buffer + len; if (stbi__do_zlib(&a, p, initial_size, 1, parse_header)) { if (outlen) *outlen = (int) (a.zout - a.zout_start); return a.zout_start; } else { STBI_FREE(a.zout_start); return NULL; } } STBIDEF int stbi_zlib_decode_buffer(char *obuffer, int olen, char const *ibuffer, int ilen) { stbi__zbuf a; a.zbuffer = (stbi_uc *) ibuffer; a.zbuffer_end = (stbi_uc *) ibuffer + ilen; if (stbi__do_zlib(&a, obuffer, olen, 0, 1)) return (int) (a.zout - a.zout_start); else return -1; } STBIDEF char *stbi_zlib_decode_noheader_malloc(char const *buffer, int len, int *outlen) { stbi__zbuf a; char *p = (char *) stbi__malloc(16384); if (p == NULL) return NULL; a.zbuffer = (stbi_uc *) buffer; a.zbuffer_end = (stbi_uc *) buffer+len; if (stbi__do_zlib(&a, p, 16384, 1, 0)) { if (outlen) *outlen = (int) (a.zout - a.zout_start); return a.zout_start; } else { STBI_FREE(a.zout_start); return NULL; } } STBIDEF int stbi_zlib_decode_noheader_buffer(char *obuffer, int olen, const char *ibuffer, int ilen) { stbi__zbuf a; a.zbuffer = (stbi_uc *) ibuffer; a.zbuffer_end = (stbi_uc *) ibuffer + ilen; if (stbi__do_zlib(&a, obuffer, olen, 0, 0)) return (int) (a.zout - a.zout_start); else return -1; } #endif // public domain "baseline" PNG decoder v0.10 Sean Barrett 2006-11-18 // simple implementation // - only 8-bit samples // - no CRC checking // - allocates lots of intermediate memory // - avoids problem of streaming data between subsystems // - avoids explicit window management // performance // - uses stb_zlib, a PD zlib implementation with fast huffman decoding #ifndef STBI_NO_PNG typedef struct { stbi__uint32 length; stbi__uint32 type; } stbi__pngchunk; static stbi__pngchunk stbi__get_chunk_header(stbi__context *s) { stbi__pngchunk c; c.length = stbi__get32be(s); c.type = stbi__get32be(s); return c; } static int stbi__check_png_header(stbi__context *s) { static const stbi_uc png_sig[8] = { 137,80,78,71,13,10,26,10 }; int i; for (i=0; i < 8; ++i) if (stbi__get8(s) != png_sig[i]) return stbi__err("bad png sig","Not a PNG"); return 1; } typedef struct { stbi__context *s; stbi_uc *idata, *expanded, *out; int depth; } stbi__png; enum { STBI__F_none=0, STBI__F_sub=1, STBI__F_up=2, STBI__F_avg=3, STBI__F_paeth=4, // synthetic filter used for first scanline to avoid needing a dummy row of 0s STBI__F_avg_first }; static stbi_uc first_row_filter[5] = { STBI__F_none, STBI__F_sub, STBI__F_none, STBI__F_avg_first, STBI__F_sub // Paeth with b=c=0 turns out to be equivalent to sub }; static int stbi__paeth(int a, int b, int c) { // This formulation looks very different from the reference in the PNG spec, but is // actually equivalent and has favorable data dependencies and admits straightforward // generation of branch-free code, which helps performance significantly. int thresh = c*3 - (a + b); int lo = a < b ? a : b; int hi = a < b ? b : a; int t0 = (hi <= thresh) ? lo : c; int t1 = (thresh <= lo) ? hi : t0; return t1; } static const stbi_uc stbi__depth_scale_table[9] = { 0, 0xff, 0x55, 0, 0x11, 0,0,0, 0x01 }; // adds an extra all-255 alpha channel // dest == src is legal // img_n must be 1 or 3 static void stbi__create_png_alpha_expand8(stbi_uc *dest, stbi_uc *src, stbi__uint32 x, int img_n) { int i; // must process data backwards since we allow dest==src if (img_n == 1) { for (i=x-1; i >= 0; --i) { dest[i*2+1] = 255; dest[i*2+0] = src[i]; } } else { STBI_ASSERT(img_n == 3); for (i=x-1; i >= 0; --i) { dest[i*4+3] = 255; dest[i*4+2] = src[i*3+2]; dest[i*4+1] = src[i*3+1]; dest[i*4+0] = src[i*3+0]; } } } // create the png data from post-deflated data static int stbi__create_png_image_raw(stbi__png *a, stbi_uc *raw, stbi__uint32 raw_len, int out_n, stbi__uint32 x, stbi__uint32 y, int depth, int color) { int bytes = (depth == 16 ? 2 : 1); stbi__context *s = a->s; stbi__uint32 i,j,stride = x*out_n*bytes; stbi__uint32 img_len, img_width_bytes; stbi_uc *filter_buf; int all_ok = 1; int k; int img_n = s->img_n; // copy it into a local for later int output_bytes = out_n*bytes; int filter_bytes = img_n*bytes; int width = x; STBI_ASSERT(out_n == s->img_n || out_n == s->img_n+1); a->out = (stbi_uc *) stbi__malloc_mad3(x, y, output_bytes, 0); // extra bytes to write off the end into if (!a->out) return stbi__err("outofmem", "Out of memory"); // note: error exits here don't need to clean up a->out individually, // stbi__do_png always does on error. if (!stbi__mad3sizes_valid(img_n, x, depth, 7)) return stbi__err("too large", "Corrupt PNG"); img_width_bytes = (((img_n * x * depth) + 7) >> 3); if (!stbi__mad2sizes_valid(img_width_bytes, y, img_width_bytes)) return stbi__err("too large", "Corrupt PNG"); img_len = (img_width_bytes + 1) * y; // we used to check for exact match between raw_len and img_len on non-interlaced PNGs, // but issue #276 reported a PNG in the wild that had extra data at the end (all zeros), // so just check for raw_len < img_len always. if (raw_len < img_len) return stbi__err("not enough pixels","Corrupt PNG"); // Allocate two scan lines worth of filter workspace buffer. filter_buf = (stbi_uc *) stbi__malloc_mad2(img_width_bytes, 2, 0); if (!filter_buf) return stbi__err("outofmem", "Out of memory"); // Filtering for low-bit-depth images if (depth < 8) { filter_bytes = 1; width = img_width_bytes; } for (j=0; j < y; ++j) { // cur/prior filter buffers alternate stbi_uc *cur = filter_buf + (j & 1)*img_width_bytes; stbi_uc *prior = filter_buf + (~j & 1)*img_width_bytes; stbi_uc *dest = a->out + stride*j; int nk = width * filter_bytes; int filter = *raw++; // check filter type if (filter > 4) { all_ok = stbi__err("invalid filter","Corrupt PNG"); break; } // if first row, use special filter that doesn't sample previous row if (j == 0) filter = first_row_filter[filter]; // perform actual filtering switch (filter) { case STBI__F_none: memcpy(cur, raw, nk); break; case STBI__F_sub: memcpy(cur, raw, filter_bytes); for (k = filter_bytes; k < nk; ++k) cur[k] = STBI__BYTECAST(raw[k] + cur[k-filter_bytes]); break; case STBI__F_up: for (k = 0; k < nk; ++k) cur[k] = STBI__BYTECAST(raw[k] + prior[k]); break; case STBI__F_avg: for (k = 0; k < filter_bytes; ++k) cur[k] = STBI__BYTECAST(raw[k] + (prior[k]>>1)); for (k = filter_bytes; k < nk; ++k) cur[k] = STBI__BYTECAST(raw[k] + ((prior[k] + cur[k-filter_bytes])>>1)); break; case STBI__F_paeth: for (k = 0; k < filter_bytes; ++k) cur[k] = STBI__BYTECAST(raw[k] + prior[k]); // prior[k] == stbi__paeth(0,prior[k],0) for (k = filter_bytes; k < nk; ++k) cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k-filter_bytes], prior[k], prior[k-filter_bytes])); break; case STBI__F_avg_first: memcpy(cur, raw, filter_bytes); for (k = filter_bytes; k < nk; ++k) cur[k] = STBI__BYTECAST(raw[k] + (cur[k-filter_bytes] >> 1)); break; } raw += nk; // expand decoded bits in cur to dest, also adding an extra alpha channel if desired if (depth < 8) { stbi_uc scale = (color == 0) ? stbi__depth_scale_table[depth] : 1; // scale grayscale values to 0..255 range stbi_uc *in = cur; stbi_uc *out = dest; stbi_uc inb = 0; stbi__uint32 nsmp = x*img_n; // expand bits to bytes first if (depth == 4) { for (i=0; i < nsmp; ++i) { if ((i & 1) == 0) inb = *in++; *out++ = scale * (inb >> 4); inb <<= 4; } } else if (depth == 2) { for (i=0; i < nsmp; ++i) { if ((i & 3) == 0) inb = *in++; *out++ = scale * (inb >> 6); inb <<= 2; } } else { STBI_ASSERT(depth == 1); for (i=0; i < nsmp; ++i) { if ((i & 7) == 0) inb = *in++; *out++ = scale * (inb >> 7); inb <<= 1; } } // insert alpha=255 values if desired if (img_n != out_n) stbi__create_png_alpha_expand8(dest, dest, x, img_n); } else if (depth == 8) { if (img_n == out_n) memcpy(dest, cur, x*img_n); else stbi__create_png_alpha_expand8(dest, cur, x, img_n); } else if (depth == 16) { // convert the image data from big-endian to platform-native stbi__uint16 *dest16 = (stbi__uint16*)dest; stbi__uint32 nsmp = x*img_n; if (img_n == out_n) { for (i = 0; i < nsmp; ++i, ++dest16, cur += 2) *dest16 = (cur[0] << 8) | cur[1]; } else { STBI_ASSERT(img_n+1 == out_n); if (img_n == 1) { for (i = 0; i < x; ++i, dest16 += 2, cur += 2) { dest16[0] = (cur[0] << 8) | cur[1]; dest16[1] = 0xffff; } } else { STBI_ASSERT(img_n == 3); for (i = 0; i < x; ++i, dest16 += 4, cur += 6) { dest16[0] = (cur[0] << 8) | cur[1]; dest16[1] = (cur[2] << 8) | cur[3]; dest16[2] = (cur[4] << 8) | cur[5]; dest16[3] = 0xffff; } } } } } STBI_FREE(filter_buf); if (!all_ok) return 0; return 1; } static int stbi__create_png_image(stbi__png *a, stbi_uc *image_data, stbi__uint32 image_data_len, int out_n, int depth, int color, int interlaced) { int bytes = (depth == 16 ? 2 : 1); int out_bytes = out_n * bytes; stbi_uc *final; int p; if (!interlaced) return stbi__create_png_image_raw(a, image_data, image_data_len, out_n, a->s->img_x, a->s->img_y, depth, color); // de-interlacing final = (stbi_uc *) stbi__malloc_mad3(a->s->img_x, a->s->img_y, out_bytes, 0); if (!final) return stbi__err("outofmem", "Out of memory"); for (p=0; p < 7; ++p) { int xorig[] = { 0,4,0,2,0,1,0 }; int yorig[] = { 0,0,4,0,2,0,1 }; int xspc[] = { 8,8,4,4,2,2,1 }; int yspc[] = { 8,8,8,4,4,2,2 }; int i,j,x,y; // pass1_x[4] = 0, pass1_x[5] = 1, pass1_x[12] = 1 x = (a->s->img_x - xorig[p] + xspc[p]-1) / xspc[p]; y = (a->s->img_y - yorig[p] + yspc[p]-1) / yspc[p]; if (x && y) { stbi__uint32 img_len = ((((a->s->img_n * x * depth) + 7) >> 3) + 1) * y; if (!stbi__create_png_image_raw(a, image_data, image_data_len, out_n, x, y, depth, color)) { STBI_FREE(final); return 0; } for (j=0; j < y; ++j) { for (i=0; i < x; ++i) { int out_y = j*yspc[p]+yorig[p]; int out_x = i*xspc[p]+xorig[p]; memcpy(final + out_y*a->s->img_x*out_bytes + out_x*out_bytes, a->out + (j*x+i)*out_bytes, out_bytes); } } STBI_FREE(a->out); image_data += img_len; image_data_len -= img_len; } } a->out = final; return 1; } static int stbi__compute_transparency(stbi__png *z, stbi_uc tc[3], int out_n) { stbi__context *s = z->s; stbi__uint32 i, pixel_count = s->img_x * s->img_y; stbi_uc *p = z->out; // compute color-based transparency, assuming we've // already got 255 as the alpha value in the output STBI_ASSERT(out_n == 2 || out_n == 4); if (out_n == 2) { for (i=0; i < pixel_count; ++i) { p[1] = (p[0] == tc[0] ? 0 : 255); p += 2; } } else { for (i=0; i < pixel_count; ++i) { if (p[0] == tc[0] && p[1] == tc[1] && p[2] == tc[2]) p[3] = 0; p += 4; } } return 1; } static int stbi__compute_transparency16(stbi__png *z, stbi__uint16 tc[3], int out_n) { stbi__context *s = z->s; stbi__uint32 i, pixel_count = s->img_x * s->img_y; stbi__uint16 *p = (stbi__uint16*) z->out; // compute color-based transparency, assuming we've // already got 65535 as the alpha value in the output STBI_ASSERT(out_n == 2 || out_n == 4); if (out_n == 2) { for (i = 0; i < pixel_count; ++i) { p[1] = (p[0] == tc[0] ? 0 : 65535); p += 2; } } else { for (i = 0; i < pixel_count; ++i) { if (p[0] == tc[0] && p[1] == tc[1] && p[2] == tc[2]) p[3] = 0; p += 4; } } return 1; } static int stbi__expand_png_palette(stbi__png *a, stbi_uc *palette, int len, int pal_img_n) { stbi__uint32 i, pixel_count = a->s->img_x * a->s->img_y; stbi_uc *p, *temp_out, *orig = a->out; p = (stbi_uc *) stbi__malloc_mad2(pixel_count, pal_img_n, 0); if (p == NULL) return stbi__err("outofmem", "Out of memory"); // between here and free(out) below, exitting would leak temp_out = p; if (pal_img_n == 3) { for (i=0; i < pixel_count; ++i) { int n = orig[i]*4; p[0] = palette[n ]; p[1] = palette[n+1]; p[2] = palette[n+2]; p += 3; } } else { for (i=0; i < pixel_count; ++i) { int n = orig[i]*4; p[0] = palette[n ]; p[1] = palette[n+1]; p[2] = palette[n+2]; p[3] = palette[n+3]; p += 4; } } STBI_FREE(a->out); a->out = temp_out; STBI_NOTUSED(len); return 1; } static int stbi__unpremultiply_on_load_global = 0; static int stbi__de_iphone_flag_global = 0; STBIDEF void stbi_set_unpremultiply_on_load(int flag_true_if_should_unpremultiply) { stbi__unpremultiply_on_load_global = flag_true_if_should_unpremultiply; } STBIDEF void stbi_convert_iphone_png_to_rgb(int flag_true_if_should_convert) { stbi__de_iphone_flag_global = flag_true_if_should_convert; } #ifndef STBI_THREAD_LOCAL #define stbi__unpremultiply_on_load stbi__unpremultiply_on_load_global #define stbi__de_iphone_flag stbi__de_iphone_flag_global #else static STBI_THREAD_LOCAL int stbi__unpremultiply_on_load_local, stbi__unpremultiply_on_load_set; static STBI_THREAD_LOCAL int stbi__de_iphone_flag_local, stbi__de_iphone_flag_set; STBIDEF void stbi_set_unpremultiply_on_load_thread(int flag_true_if_should_unpremultiply) { stbi__unpremultiply_on_load_local = flag_true_if_should_unpremultiply; stbi__unpremultiply_on_load_set = 1; } STBIDEF void stbi_convert_iphone_png_to_rgb_thread(int flag_true_if_should_convert) { stbi__de_iphone_flag_local = flag_true_if_should_convert; stbi__de_iphone_flag_set = 1; } #define stbi__unpremultiply_on_load (stbi__unpremultiply_on_load_set \ ? stbi__unpremultiply_on_load_local \ : stbi__unpremultiply_on_load_global) #define stbi__de_iphone_flag (stbi__de_iphone_flag_set \ ? stbi__de_iphone_flag_local \ : stbi__de_iphone_flag_global) #endif // STBI_THREAD_LOCAL static void stbi__de_iphone(stbi__png *z) { stbi__context *s = z->s; stbi__uint32 i, pixel_count = s->img_x * s->img_y; stbi_uc *p = z->out; if (s->img_out_n == 3) { // convert bgr to rgb for (i=0; i < pixel_count; ++i) { stbi_uc t = p[0]; p[0] = p[2]; p[2] = t; p += 3; } } else { STBI_ASSERT(s->img_out_n == 4); if (stbi__unpremultiply_on_load) { // convert bgr to rgb and unpremultiply for (i=0; i < pixel_count; ++i) { stbi_uc a = p[3]; stbi_uc t = p[0]; if (a) { stbi_uc half = a / 2; p[0] = (p[2] * 255 + half) / a; p[1] = (p[1] * 255 + half) / a; p[2] = ( t * 255 + half) / a; } else { p[0] = p[2]; p[2] = t; } p += 4; } } else { // convert bgr to rgb for (i=0; i < pixel_count; ++i) { stbi_uc t = p[0]; p[0] = p[2]; p[2] = t; p += 4; } } } } #define STBI__PNG_TYPE(a,b,c,d) (((unsigned) (a) << 24) + ((unsigned) (b) << 16) + ((unsigned) (c) << 8) + (unsigned) (d)) static int stbi__parse_png_file(stbi__png *z, int scan, int req_comp) { stbi_uc palette[1024], pal_img_n=0; stbi_uc has_trans=0, tc[3]={0}; stbi__uint16 tc16[3]; stbi__uint32 ioff=0, idata_limit=0, i, pal_len=0; int first=1,k,interlace=0, color=0, is_iphone=0; stbi__context *s = z->s; z->expanded = NULL; z->idata = NULL; z->out = NULL; if (!stbi__check_png_header(s)) return 0; if (scan == STBI__SCAN_type) return 1; for (;;) { stbi__pngchunk c = stbi__get_chunk_header(s); switch (c.type) { case STBI__PNG_TYPE('C','g','B','I'): is_iphone = 1; stbi__skip(s, c.length); break; case STBI__PNG_TYPE('I','H','D','R'): { int comp,filter; if (!first) return stbi__err("multiple IHDR","Corrupt PNG"); first = 0; if (c.length != 13) return stbi__err("bad IHDR len","Corrupt PNG"); s->img_x = stbi__get32be(s); s->img_y = stbi__get32be(s); if (s->img_y > STBI_MAX_DIMENSIONS) return stbi__err("too large","Very large image (corrupt?)"); if (s->img_x > STBI_MAX_DIMENSIONS) return stbi__err("too large","Very large image (corrupt?)"); z->depth = stbi__get8(s); if (z->depth != 1 && z->depth != 2 && z->depth != 4 && z->depth != 8 && z->depth != 16) return stbi__err("1/2/4/8/16-bit only","PNG not supported: 1/2/4/8/16-bit only"); color = stbi__get8(s); if (color > 6) return stbi__err("bad ctype","Corrupt PNG"); if (color == 3 && z->depth == 16) return stbi__err("bad ctype","Corrupt PNG"); if (color == 3) pal_img_n = 3; else if (color & 1) return stbi__err("bad ctype","Corrupt PNG"); comp = stbi__get8(s); if (comp) return stbi__err("bad comp method","Corrupt PNG"); filter= stbi__get8(s); if (filter) return stbi__err("bad filter method","Corrupt PNG"); interlace = stbi__get8(s); if (interlace>1) return stbi__err("bad interlace method","Corrupt PNG"); if (!s->img_x || !s->img_y) return stbi__err("0-pixel image","Corrupt PNG"); if (!pal_img_n) { s->img_n = (color & 2 ? 3 : 1) + (color & 4 ? 1 : 0); if ((1 << 30) / s->img_x / s->img_n < s->img_y) return stbi__err("too large", "Image too large to decode"); } else { // if paletted, then pal_n is our final components, and // img_n is # components to decompress/filter. s->img_n = 1; if ((1 << 30) / s->img_x / 4 < s->img_y) return stbi__err("too large","Corrupt PNG"); } // even with SCAN_header, have to scan to see if we have a tRNS break; } case STBI__PNG_TYPE('P','L','T','E'): { if (first) return stbi__err("first not IHDR", "Corrupt PNG"); if (c.length > 256*3) return stbi__err("invalid PLTE","Corrupt PNG"); pal_len = c.length / 3; if (pal_len * 3 != c.length) return stbi__err("invalid PLTE","Corrupt PNG"); for (i=0; i < pal_len; ++i) { palette[i*4+0] = stbi__get8(s); palette[i*4+1] = stbi__get8(s); palette[i*4+2] = stbi__get8(s); palette[i*4+3] = 255; } break; } case STBI__PNG_TYPE('t','R','N','S'): { if (first) return stbi__err("first not IHDR", "Corrupt PNG"); if (z->idata) return stbi__err("tRNS after IDAT","Corrupt PNG"); if (pal_img_n) { if (scan == STBI__SCAN_header) { s->img_n = 4; return 1; } if (pal_len == 0) return stbi__err("tRNS before PLTE","Corrupt PNG"); if (c.length > pal_len) return stbi__err("bad tRNS len","Corrupt PNG"); pal_img_n = 4; for (i=0; i < c.length; ++i) palette[i*4+3] = stbi__get8(s); } else { if (!(s->img_n & 1)) return stbi__err("tRNS with alpha","Corrupt PNG"); if (c.length != (stbi__uint32) s->img_n*2) return stbi__err("bad tRNS len","Corrupt PNG"); has_trans = 1; // non-paletted with tRNS = constant alpha. if header-scanning, we can stop now. if (scan == STBI__SCAN_header) { ++s->img_n; return 1; } if (z->depth == 16) { for (k = 0; k < s->img_n && k < 3; ++k) // extra loop test to suppress false GCC warning tc16[k] = (stbi__uint16)stbi__get16be(s); // copy the values as-is } else { for (k = 0; k < s->img_n && k < 3; ++k) tc[k] = (stbi_uc)(stbi__get16be(s) & 255) * stbi__depth_scale_table[z->depth]; // non 8-bit images will be larger } } break; } case STBI__PNG_TYPE('I','D','A','T'): { if (first) return stbi__err("first not IHDR", "Corrupt PNG"); if (pal_img_n && !pal_len) return stbi__err("no PLTE","Corrupt PNG"); if (scan == STBI__SCAN_header) { // header scan definitely stops at first IDAT if (pal_img_n) s->img_n = pal_img_n; return 1; } if (c.length > (1u << 30)) return stbi__err("IDAT size limit", "IDAT section larger than 2^30 bytes"); if ((int)(ioff + c.length) < (int)ioff) return 0; if (ioff + c.length > idata_limit) { stbi__uint32 idata_limit_old = idata_limit; stbi_uc *p; if (idata_limit == 0) idata_limit = c.length > 4096 ? c.length : 4096; while (ioff + c.length > idata_limit) idata_limit *= 2; STBI_NOTUSED(idata_limit_old); p = (stbi_uc *) STBI_REALLOC_SIZED(z->idata, idata_limit_old, idata_limit); if (p == NULL) return stbi__err("outofmem", "Out of memory"); z->idata = p; } if (!stbi__getn(s, z->idata+ioff,c.length)) return stbi__err("outofdata","Corrupt PNG"); ioff += c.length; break; } case STBI__PNG_TYPE('I','E','N','D'): { stbi__uint32 raw_len, bpl; if (first) return stbi__err("first not IHDR", "Corrupt PNG"); if (scan != STBI__SCAN_load) return 1; if (z->idata == NULL) return stbi__err("no IDAT","Corrupt PNG"); // initial guess for decoded data size to avoid unnecessary reallocs bpl = (s->img_x * z->depth + 7) / 8; // bytes per line, per component raw_len = bpl * s->img_y * s->img_n /* pixels */ + s->img_y /* filter mode per row */; z->expanded = (stbi_uc *) stbi_zlib_decode_malloc_guesssize_headerflag((char *) z->idata, ioff, raw_len, (int *) &raw_len, !is_iphone); if (z->expanded == NULL) return 0; // zlib should set error STBI_FREE(z->idata); z->idata = NULL; if ((req_comp == s->img_n+1 && req_comp != 3 && !pal_img_n) || has_trans) s->img_out_n = s->img_n+1; else s->img_out_n = s->img_n; if (!stbi__create_png_image(z, z->expanded, raw_len, s->img_out_n, z->depth, color, interlace)) return 0; if (has_trans) { if (z->depth == 16) { if (!stbi__compute_transparency16(z, tc16, s->img_out_n)) return 0; } else { if (!stbi__compute_transparency(z, tc, s->img_out_n)) return 0; } } if (is_iphone && stbi__de_iphone_flag && s->img_out_n > 2) stbi__de_iphone(z); if (pal_img_n) { // pal_img_n == 3 or 4 s->img_n = pal_img_n; // record the actual colors we had s->img_out_n = pal_img_n; if (req_comp >= 3) s->img_out_n = req_comp; if (!stbi__expand_png_palette(z, palette, pal_len, s->img_out_n)) return 0; } else if (has_trans) { // non-paletted image with tRNS -> source image has (constant) alpha ++s->img_n; } STBI_FREE(z->expanded); z->expanded = NULL; // end of PNG chunk, read and skip CRC stbi__get32be(s); return 1; } default: // if critical, fail if (first) return stbi__err("first not IHDR", "Corrupt PNG"); if ((c.type & (1 << 29)) == 0) { #ifndef STBI_NO_FAILURE_STRINGS // not threadsafe static char invalid_chunk[] = "XXXX PNG chunk not known"; invalid_chunk[0] = STBI__BYTECAST(c.type >> 24); invalid_chunk[1] = STBI__BYTECAST(c.type >> 16); invalid_chunk[2] = STBI__BYTECAST(c.type >> 8); invalid_chunk[3] = STBI__BYTECAST(c.type >> 0); #endif return stbi__err(invalid_chunk, "PNG not supported: unknown PNG chunk type"); } stbi__skip(s, c.length); break; } // end of PNG chunk, read and skip CRC stbi__get32be(s); } } static void *stbi__do_png(stbi__png *p, int *x, int *y, int *n, int req_comp, stbi__result_info *ri) { void *result=NULL; if (req_comp < 0 || req_comp > 4) return stbi__errpuc("bad req_comp", "Internal error"); if (stbi__parse_png_file(p, STBI__SCAN_load, req_comp)) { if (p->depth <= 8) ri->bits_per_channel = 8; else if (p->depth == 16) ri->bits_per_channel = 16; else return stbi__errpuc("bad bits_per_channel", "PNG not supported: unsupported color depth"); result = p->out; p->out = NULL; if (req_comp && req_comp != p->s->img_out_n) { if (ri->bits_per_channel == 8) result = stbi__convert_format((unsigned char *) result, p->s->img_out_n, req_comp, p->s->img_x, p->s->img_y); else result = stbi__convert_format16((stbi__uint16 *) result, p->s->img_out_n, req_comp, p->s->img_x, p->s->img_y); p->s->img_out_n = req_comp; if (result == NULL) return result; } *x = p->s->img_x; *y = p->s->img_y; if (n) *n = p->s->img_n; } STBI_FREE(p->out); p->out = NULL; STBI_FREE(p->expanded); p->expanded = NULL; STBI_FREE(p->idata); p->idata = NULL; return result; } static void *stbi__png_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) { stbi__png p; p.s = s; return stbi__do_png(&p, x,y,comp,req_comp, ri); } static int stbi__png_test(stbi__context *s) { int r; r = stbi__check_png_header(s); stbi__rewind(s); return r; } static int stbi__png_info_raw(stbi__png *p, int *x, int *y, int *comp) { if (!stbi__parse_png_file(p, STBI__SCAN_header, 0)) { stbi__rewind( p->s ); return 0; } if (x) *x = p->s->img_x; if (y) *y = p->s->img_y; if (comp) *comp = p->s->img_n; return 1; } static int stbi__png_info(stbi__context *s, int *x, int *y, int *comp) { stbi__png p; p.s = s; return stbi__png_info_raw(&p, x, y, comp); } static int stbi__png_is16(stbi__context *s) { stbi__png p; p.s = s; if (!stbi__png_info_raw(&p, NULL, NULL, NULL)) return 0; if (p.depth != 16) { stbi__rewind(p.s); return 0; } return 1; } #endif // Microsoft/Windows BMP image #ifndef STBI_NO_BMP static int stbi__bmp_test_raw(stbi__context *s) { int r; int sz; if (stbi__get8(s) != 'B') return 0; if (stbi__get8(s) != 'M') return 0; stbi__get32le(s); // discard filesize stbi__get16le(s); // discard reserved stbi__get16le(s); // discard reserved stbi__get32le(s); // discard data offset sz = stbi__get32le(s); r = (sz == 12 || sz == 40 || sz == 56 || sz == 108 || sz == 124); return r; } static int stbi__bmp_test(stbi__context *s) { int r = stbi__bmp_test_raw(s); stbi__rewind(s); return r; } // returns 0..31 for the highest set bit static int stbi__high_bit(unsigned int z) { int n=0; if (z == 0) return -1; if (z >= 0x10000) { n += 16; z >>= 16; } if (z >= 0x00100) { n += 8; z >>= 8; } if (z >= 0x00010) { n += 4; z >>= 4; } if (z >= 0x00004) { n += 2; z >>= 2; } if (z >= 0x00002) { n += 1;/* >>= 1;*/ } return n; } static int stbi__bitcount(unsigned int a) { a = (a & 0x55555555) + ((a >> 1) & 0x55555555); // max 2 a = (a & 0x33333333) + ((a >> 2) & 0x33333333); // max 4 a = (a + (a >> 4)) & 0x0f0f0f0f; // max 8 per 4, now 8 bits a = (a + (a >> 8)); // max 16 per 8 bits a = (a + (a >> 16)); // max 32 per 8 bits return a & 0xff; } // extract an arbitrarily-aligned N-bit value (N=bits) // from v, and then make it 8-bits long and fractionally // extend it to full full range. static int stbi__shiftsigned(unsigned int v, int shift, int bits) { static unsigned int mul_table[9] = { 0, 0xff/*0b11111111*/, 0x55/*0b01010101*/, 0x49/*0b01001001*/, 0x11/*0b00010001*/, 0x21/*0b00100001*/, 0x41/*0b01000001*/, 0x81/*0b10000001*/, 0x01/*0b00000001*/, }; static unsigned int shift_table[9] = { 0, 0,0,1,0,2,4,6,0, }; if (shift < 0) v <<= -shift; else v >>= shift; STBI_ASSERT(v < 256); v >>= (8-bits); STBI_ASSERT(bits >= 0 && bits <= 8); return (int) ((unsigned) v * mul_table[bits]) >> shift_table[bits]; } typedef struct { int bpp, offset, hsz; unsigned int mr,mg,mb,ma, all_a; int extra_read; } stbi__bmp_data; static int stbi__bmp_set_mask_defaults(stbi__bmp_data *info, int compress) { // BI_BITFIELDS specifies masks explicitly, don't override if (compress == 3) return 1; if (compress == 0) { if (info->bpp == 16) { info->mr = 31u << 10; info->mg = 31u << 5; info->mb = 31u << 0; } else if (info->bpp == 32) { info->mr = 0xffu << 16; info->mg = 0xffu << 8; info->mb = 0xffu << 0; info->ma = 0xffu << 24; info->all_a = 0; // if all_a is 0 at end, then we loaded alpha channel but it was all 0 } else { // otherwise, use defaults, which is all-0 info->mr = info->mg = info->mb = info->ma = 0; } return 1; } return 0; // error } static void *stbi__bmp_parse_header(stbi__context *s, stbi__bmp_data *info) { int hsz; if (stbi__get8(s) != 'B' || stbi__get8(s) != 'M') return stbi__errpuc("not BMP", "Corrupt BMP"); stbi__get32le(s); // discard filesize stbi__get16le(s); // discard reserved stbi__get16le(s); // discard reserved info->offset = stbi__get32le(s); info->hsz = hsz = stbi__get32le(s); info->mr = info->mg = info->mb = info->ma = 0; info->extra_read = 14; if (info->offset < 0) return stbi__errpuc("bad BMP", "bad BMP"); if (hsz != 12 && hsz != 40 && hsz != 56 && hsz != 108 && hsz != 124) return stbi__errpuc("unknown BMP", "BMP type not supported: unknown"); if (hsz == 12) { s->img_x = stbi__get16le(s); s->img_y = stbi__get16le(s); } else { s->img_x = stbi__get32le(s); s->img_y = stbi__get32le(s); } if (stbi__get16le(s) != 1) return stbi__errpuc("bad BMP", "bad BMP"); info->bpp = stbi__get16le(s); if (hsz != 12) { int compress = stbi__get32le(s); if (compress == 1 || compress == 2) return stbi__errpuc("BMP RLE", "BMP type not supported: RLE"); if (compress >= 4) return stbi__errpuc("BMP JPEG/PNG", "BMP type not supported: unsupported compression"); // this includes PNG/JPEG modes if (compress == 3 && info->bpp != 16 && info->bpp != 32) return stbi__errpuc("bad BMP", "bad BMP"); // bitfields requires 16 or 32 bits/pixel stbi__get32le(s); // discard sizeof stbi__get32le(s); // discard hres stbi__get32le(s); // discard vres stbi__get32le(s); // discard colorsused stbi__get32le(s); // discard max important if (hsz == 40 || hsz == 56) { if (hsz == 56) { stbi__get32le(s); stbi__get32le(s); stbi__get32le(s); stbi__get32le(s); } if (info->bpp == 16 || info->bpp == 32) { if (compress == 0) { stbi__bmp_set_mask_defaults(info, compress); } else if (compress == 3) { info->mr = stbi__get32le(s); info->mg = stbi__get32le(s); info->mb = stbi__get32le(s); info->extra_read += 12; // not documented, but generated by photoshop and handled by mspaint if (info->mr == info->mg && info->mg == info->mb) { // ?!?!? return stbi__errpuc("bad BMP", "bad BMP"); } } else return stbi__errpuc("bad BMP", "bad BMP"); } } else { // V4/V5 header int i; if (hsz != 108 && hsz != 124) return stbi__errpuc("bad BMP", "bad BMP"); info->mr = stbi__get32le(s); info->mg = stbi__get32le(s); info->mb = stbi__get32le(s); info->ma = stbi__get32le(s); if (compress != 3) // override mr/mg/mb unless in BI_BITFIELDS mode, as per docs stbi__bmp_set_mask_defaults(info, compress); stbi__get32le(s); // discard color space for (i=0; i < 12; ++i) stbi__get32le(s); // discard color space parameters if (hsz == 124) { stbi__get32le(s); // discard rendering intent stbi__get32le(s); // discard offset of profile data stbi__get32le(s); // discard size of profile data stbi__get32le(s); // discard reserved } } } return (void *) 1; } static void *stbi__bmp_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) { stbi_uc *out; unsigned int mr=0,mg=0,mb=0,ma=0, all_a; stbi_uc pal[256][4]; int psize=0,i,j,width; int flip_vertically, pad, target; stbi__bmp_data info; STBI_NOTUSED(ri); info.all_a = 255; if (stbi__bmp_parse_header(s, &info) == NULL) return NULL; // error code already set flip_vertically = ((int) s->img_y) > 0; s->img_y = abs((int) s->img_y); if (s->img_y > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); if (s->img_x > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); mr = info.mr; mg = info.mg; mb = info.mb; ma = info.ma; all_a = info.all_a; if (info.hsz == 12) { if (info.bpp < 24) psize = (info.offset - info.extra_read - 24) / 3; } else { if (info.bpp < 16) psize = (info.offset - info.extra_read - info.hsz) >> 2; } if (psize == 0) { // accept some number of extra bytes after the header, but if the offset points either to before // the header ends or implies a large amount of extra data, reject the file as malformed int bytes_read_so_far = s->callback_already_read + (int)(s->img_buffer - s->img_buffer_original); int header_limit = 1024; // max we actually read is below 256 bytes currently. int extra_data_limit = 256*4; // what ordinarily goes here is a palette; 256 entries*4 bytes is its max size. if (bytes_read_so_far <= 0 || bytes_read_so_far > header_limit) { return stbi__errpuc("bad header", "Corrupt BMP"); } // we established that bytes_read_so_far is positive and sensible. // the first half of this test rejects offsets that are either too small positives, or // negative, and guarantees that info.offset >= bytes_read_so_far > 0. this in turn // ensures the number computed in the second half of the test can't overflow. if (info.offset < bytes_read_so_far || info.offset - bytes_read_so_far > extra_data_limit) { return stbi__errpuc("bad offset", "Corrupt BMP"); } else { stbi__skip(s, info.offset - bytes_read_so_far); } } if (info.bpp == 24 && ma == 0xff000000) s->img_n = 3; else s->img_n = ma ? 4 : 3; if (req_comp && req_comp >= 3) // we can directly decode 3 or 4 target = req_comp; else target = s->img_n; // if they want monochrome, we'll post-convert // sanity-check size if (!stbi__mad3sizes_valid(target, s->img_x, s->img_y, 0)) return stbi__errpuc("too large", "Corrupt BMP"); out = (stbi_uc *) stbi__malloc_mad3(target, s->img_x, s->img_y, 0); if (!out) return stbi__errpuc("outofmem", "Out of memory"); if (info.bpp < 16) { int z=0; if (psize == 0 || psize > 256) { STBI_FREE(out); return stbi__errpuc("invalid", "Corrupt BMP"); } for (i=0; i < psize; ++i) { pal[i][2] = stbi__get8(s); pal[i][1] = stbi__get8(s); pal[i][0] = stbi__get8(s); if (info.hsz != 12) stbi__get8(s); pal[i][3] = 255; } stbi__skip(s, info.offset - info.extra_read - info.hsz - psize * (info.hsz == 12 ? 3 : 4)); if (info.bpp == 1) width = (s->img_x + 7) >> 3; else if (info.bpp == 4) width = (s->img_x + 1) >> 1; else if (info.bpp == 8) width = s->img_x; else { STBI_FREE(out); return stbi__errpuc("bad bpp", "Corrupt BMP"); } pad = (-width)&3; if (info.bpp == 1) { for (j=0; j < (int) s->img_y; ++j) { int bit_offset = 7, v = stbi__get8(s); for (i=0; i < (int) s->img_x; ++i) { int color = (v>>bit_offset)&0x1; out[z++] = pal[color][0]; out[z++] = pal[color][1]; out[z++] = pal[color][2]; if (target == 4) out[z++] = 255; if (i+1 == (int) s->img_x) break; if((--bit_offset) < 0) { bit_offset = 7; v = stbi__get8(s); } } stbi__skip(s, pad); } } else { for (j=0; j < (int) s->img_y; ++j) { for (i=0; i < (int) s->img_x; i += 2) { int v=stbi__get8(s),v2=0; if (info.bpp == 4) { v2 = v & 15; v >>= 4; } out[z++] = pal[v][0]; out[z++] = pal[v][1]; out[z++] = pal[v][2]; if (target == 4) out[z++] = 255; if (i+1 == (int) s->img_x) break; v = (info.bpp == 8) ? stbi__get8(s) : v2; out[z++] = pal[v][0]; out[z++] = pal[v][1]; out[z++] = pal[v][2]; if (target == 4) out[z++] = 255; } stbi__skip(s, pad); } } } else { int rshift=0,gshift=0,bshift=0,ashift=0,rcount=0,gcount=0,bcount=0,acount=0; int z = 0; int easy=0; stbi__skip(s, info.offset - info.extra_read - info.hsz); if (info.bpp == 24) width = 3 * s->img_x; else if (info.bpp == 16) width = 2*s->img_x; else /* bpp = 32 and pad = 0 */ width=0; pad = (-width) & 3; if (info.bpp == 24) { easy = 1; } else if (info.bpp == 32) { if (mb == 0xff && mg == 0xff00 && mr == 0x00ff0000 && ma == 0xff000000) easy = 2; } if (!easy) { if (!mr || !mg || !mb) { STBI_FREE(out); return stbi__errpuc("bad masks", "Corrupt BMP"); } // right shift amt to put high bit in position #7 rshift = stbi__high_bit(mr)-7; rcount = stbi__bitcount(mr); gshift = stbi__high_bit(mg)-7; gcount = stbi__bitcount(mg); bshift = stbi__high_bit(mb)-7; bcount = stbi__bitcount(mb); ashift = stbi__high_bit(ma)-7; acount = stbi__bitcount(ma); if (rcount > 8 || gcount > 8 || bcount > 8 || acount > 8) { STBI_FREE(out); return stbi__errpuc("bad masks", "Corrupt BMP"); } } for (j=0; j < (int) s->img_y; ++j) { if (easy) { for (i=0; i < (int) s->img_x; ++i) { unsigned char a; out[z+2] = stbi__get8(s); out[z+1] = stbi__get8(s); out[z+0] = stbi__get8(s); z += 3; a = (easy == 2 ? stbi__get8(s) : 255); all_a |= a; if (target == 4) out[z++] = a; } } else { int bpp = info.bpp; for (i=0; i < (int) s->img_x; ++i) { stbi__uint32 v = (bpp == 16 ? (stbi__uint32) stbi__get16le(s) : stbi__get32le(s)); unsigned int a; out[z++] = STBI__BYTECAST(stbi__shiftsigned(v & mr, rshift, rcount)); out[z++] = STBI__BYTECAST(stbi__shiftsigned(v & mg, gshift, gcount)); out[z++] = STBI__BYTECAST(stbi__shiftsigned(v & mb, bshift, bcount)); a = (ma ? stbi__shiftsigned(v & ma, ashift, acount) : 255); all_a |= a; if (target == 4) out[z++] = STBI__BYTECAST(a); } } stbi__skip(s, pad); } } // if alpha channel is all 0s, replace with all 255s if (target == 4 && all_a == 0) for (i=4*s->img_x*s->img_y-1; i >= 0; i -= 4) out[i] = 255; if (flip_vertically) { stbi_uc t; for (j=0; j < (int) s->img_y>>1; ++j) { stbi_uc *p1 = out + j *s->img_x*target; stbi_uc *p2 = out + (s->img_y-1-j)*s->img_x*target; for (i=0; i < (int) s->img_x*target; ++i) { t = p1[i]; p1[i] = p2[i]; p2[i] = t; } } } if (req_comp && req_comp != target) { out = stbi__convert_format(out, target, req_comp, s->img_x, s->img_y); if (out == NULL) return out; // stbi__convert_format frees input on failure } *x = s->img_x; *y = s->img_y; if (comp) *comp = s->img_n; return out; } #endif // Targa Truevision - TGA // by Jonathan Dummer #ifndef STBI_NO_TGA // returns STBI_rgb or whatever, 0 on error static int stbi__tga_get_comp(int bits_per_pixel, int is_grey, int* is_rgb16) { // only RGB or RGBA (incl. 16bit) or grey allowed if (is_rgb16) *is_rgb16 = 0; switch(bits_per_pixel) { case 8: return STBI_grey; case 16: if(is_grey) return STBI_grey_alpha; // fallthrough case 15: if(is_rgb16) *is_rgb16 = 1; return STBI_rgb; case 24: // fallthrough case 32: return bits_per_pixel/8; default: return 0; } } static int stbi__tga_info(stbi__context *s, int *x, int *y, int *comp) { int tga_w, tga_h, tga_comp, tga_image_type, tga_bits_per_pixel, tga_colormap_bpp; int sz, tga_colormap_type; stbi__get8(s); // discard Offset tga_colormap_type = stbi__get8(s); // colormap type if( tga_colormap_type > 1 ) { stbi__rewind(s); return 0; // only RGB or indexed allowed } tga_image_type = stbi__get8(s); // image type if ( tga_colormap_type == 1 ) { // colormapped (paletted) image if (tga_image_type != 1 && tga_image_type != 9) { stbi__rewind(s); return 0; } stbi__skip(s,4); // skip index of first colormap entry and number of entries sz = stbi__get8(s); // check bits per palette color entry if ( (sz != 8) && (sz != 15) && (sz != 16) && (sz != 24) && (sz != 32) ) { stbi__rewind(s); return 0; } stbi__skip(s,4); // skip image x and y origin tga_colormap_bpp = sz; } else { // "normal" image w/o colormap - only RGB or grey allowed, +/- RLE if ( (tga_image_type != 2) && (tga_image_type != 3) && (tga_image_type != 10) && (tga_image_type != 11) ) { stbi__rewind(s); return 0; // only RGB or grey allowed, +/- RLE } stbi__skip(s,9); // skip colormap specification and image x/y origin tga_colormap_bpp = 0; } tga_w = stbi__get16le(s); if( tga_w < 1 ) { stbi__rewind(s); return 0; // test width } tga_h = stbi__get16le(s); if( tga_h < 1 ) { stbi__rewind(s); return 0; // test height } tga_bits_per_pixel = stbi__get8(s); // bits per pixel stbi__get8(s); // ignore alpha bits if (tga_colormap_bpp != 0) { if((tga_bits_per_pixel != 8) && (tga_bits_per_pixel != 16)) { // when using a colormap, tga_bits_per_pixel is the size of the indexes // I don't think anything but 8 or 16bit indexes makes sense stbi__rewind(s); return 0; } tga_comp = stbi__tga_get_comp(tga_colormap_bpp, 0, NULL); } else { tga_comp = stbi__tga_get_comp(tga_bits_per_pixel, (tga_image_type == 3) || (tga_image_type == 11), NULL); } if(!tga_comp) { stbi__rewind(s); return 0; } if (x) *x = tga_w; if (y) *y = tga_h; if (comp) *comp = tga_comp; return 1; // seems to have passed everything } static int stbi__tga_test(stbi__context *s) { int res = 0; int sz, tga_color_type; stbi__get8(s); // discard Offset tga_color_type = stbi__get8(s); // color type if ( tga_color_type > 1 ) goto errorEnd; // only RGB or indexed allowed sz = stbi__get8(s); // image type if ( tga_color_type == 1 ) { // colormapped (paletted) image if (sz != 1 && sz != 9) goto errorEnd; // colortype 1 demands image type 1 or 9 stbi__skip(s,4); // skip index of first colormap entry and number of entries sz = stbi__get8(s); // check bits per palette color entry if ( (sz != 8) && (sz != 15) && (sz != 16) && (sz != 24) && (sz != 32) ) goto errorEnd; stbi__skip(s,4); // skip image x and y origin } else { // "normal" image w/o colormap if ( (sz != 2) && (sz != 3) && (sz != 10) && (sz != 11) ) goto errorEnd; // only RGB or grey allowed, +/- RLE stbi__skip(s,9); // skip colormap specification and image x/y origin } if ( stbi__get16le(s) < 1 ) goto errorEnd; // test width if ( stbi__get16le(s) < 1 ) goto errorEnd; // test height sz = stbi__get8(s); // bits per pixel if ( (tga_color_type == 1) && (sz != 8) && (sz != 16) ) goto errorEnd; // for colormapped images, bpp is size of an index if ( (sz != 8) && (sz != 15) && (sz != 16) && (sz != 24) && (sz != 32) ) goto errorEnd; res = 1; // if we got this far, everything's good and we can return 1 instead of 0 errorEnd: stbi__rewind(s); return res; } // read 16bit value and convert to 24bit RGB static void stbi__tga_read_rgb16(stbi__context *s, stbi_uc* out) { stbi__uint16 px = (stbi__uint16)stbi__get16le(s); stbi__uint16 fiveBitMask = 31; // we have 3 channels with 5bits each int r = (px >> 10) & fiveBitMask; int g = (px >> 5) & fiveBitMask; int b = px & fiveBitMask; // Note that this saves the data in RGB(A) order, so it doesn't need to be swapped later out[0] = (stbi_uc)((r * 255)/31); out[1] = (stbi_uc)((g * 255)/31); out[2] = (stbi_uc)((b * 255)/31); // some people claim that the most significant bit might be used for alpha // (possibly if an alpha-bit is set in the "image descriptor byte") // but that only made 16bit test images completely translucent.. // so let's treat all 15 and 16bit TGAs as RGB with no alpha. } static void *stbi__tga_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) { // read in the TGA header stuff int tga_offset = stbi__get8(s); int tga_indexed = stbi__get8(s); int tga_image_type = stbi__get8(s); int tga_is_RLE = 0; int tga_palette_start = stbi__get16le(s); int tga_palette_len = stbi__get16le(s); int tga_palette_bits = stbi__get8(s); int tga_x_origin = stbi__get16le(s); int tga_y_origin = stbi__get16le(s); int tga_width = stbi__get16le(s); int tga_height = stbi__get16le(s); int tga_bits_per_pixel = stbi__get8(s); int tga_comp, tga_rgb16=0; int tga_inverted = stbi__get8(s); // int tga_alpha_bits = tga_inverted & 15; // the 4 lowest bits - unused (useless?) // image data unsigned char *tga_data; unsigned char *tga_palette = NULL; int i, j; unsigned char raw_data[4] = {0}; int RLE_count = 0; int RLE_repeating = 0; int read_next_pixel = 1; STBI_NOTUSED(ri); STBI_NOTUSED(tga_x_origin); // @TODO STBI_NOTUSED(tga_y_origin); // @TODO if (tga_height > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); if (tga_width > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); // do a tiny bit of precessing if ( tga_image_type >= 8 ) { tga_image_type -= 8; tga_is_RLE = 1; } tga_inverted = 1 - ((tga_inverted >> 5) & 1); // If I'm paletted, then I'll use the number of bits from the palette if ( tga_indexed ) tga_comp = stbi__tga_get_comp(tga_palette_bits, 0, &tga_rgb16); else tga_comp = stbi__tga_get_comp(tga_bits_per_pixel, (tga_image_type == 3), &tga_rgb16); if(!tga_comp) // shouldn't really happen, stbi__tga_test() should have ensured basic consistency return stbi__errpuc("bad format", "Can't find out TGA pixelformat"); // tga info *x = tga_width; *y = tga_height; if (comp) *comp = tga_comp; if (!stbi__mad3sizes_valid(tga_width, tga_height, tga_comp, 0)) return stbi__errpuc("too large", "Corrupt TGA"); tga_data = (unsigned char*)stbi__malloc_mad3(tga_width, tga_height, tga_comp, 0); if (!tga_data) return stbi__errpuc("outofmem", "Out of memory"); // skip to the data's starting position (offset usually = 0) stbi__skip(s, tga_offset ); if ( !tga_indexed && !tga_is_RLE && !tga_rgb16 ) { for (i=0; i < tga_height; ++i) { int row = tga_inverted ? tga_height -i - 1 : i; stbi_uc *tga_row = tga_data + row*tga_width*tga_comp; stbi__getn(s, tga_row, tga_width * tga_comp); } } else { // do I need to load a palette? if ( tga_indexed) { if (tga_palette_len == 0) { /* you have to have at least one entry! */ STBI_FREE(tga_data); return stbi__errpuc("bad palette", "Corrupt TGA"); } // any data to skip? (offset usually = 0) stbi__skip(s, tga_palette_start ); // load the palette tga_palette = (unsigned char*)stbi__malloc_mad2(tga_palette_len, tga_comp, 0); if (!tga_palette) { STBI_FREE(tga_data); return stbi__errpuc("outofmem", "Out of memory"); } if (tga_rgb16) { stbi_uc *pal_entry = tga_palette; STBI_ASSERT(tga_comp == STBI_rgb); for (i=0; i < tga_palette_len; ++i) { stbi__tga_read_rgb16(s, pal_entry); pal_entry += tga_comp; } } else if (!stbi__getn(s, tga_palette, tga_palette_len * tga_comp)) { STBI_FREE(tga_data); STBI_FREE(tga_palette); return stbi__errpuc("bad palette", "Corrupt TGA"); } } // load the data for (i=0; i < tga_width * tga_height; ++i) { // if I'm in RLE mode, do I need to get a RLE stbi__pngchunk? if ( tga_is_RLE ) { if ( RLE_count == 0 ) { // yep, get the next byte as a RLE command int RLE_cmd = stbi__get8(s); RLE_count = 1 + (RLE_cmd & 127); RLE_repeating = RLE_cmd >> 7; read_next_pixel = 1; } else if ( !RLE_repeating ) { read_next_pixel = 1; } } else { read_next_pixel = 1; } // OK, if I need to read a pixel, do it now if ( read_next_pixel ) { // load however much data we did have if ( tga_indexed ) { // read in index, then perform the lookup int pal_idx = (tga_bits_per_pixel == 8) ? stbi__get8(s) : stbi__get16le(s); if ( pal_idx >= tga_palette_len ) { // invalid index pal_idx = 0; } pal_idx *= tga_comp; for (j = 0; j < tga_comp; ++j) { raw_data[j] = tga_palette[pal_idx+j]; } } else if(tga_rgb16) { STBI_ASSERT(tga_comp == STBI_rgb); stbi__tga_read_rgb16(s, raw_data); } else { // read in the data raw for (j = 0; j < tga_comp; ++j) { raw_data[j] = stbi__get8(s); } } // clear the reading flag for the next pixel read_next_pixel = 0; } // end of reading a pixel // copy data for (j = 0; j < tga_comp; ++j) tga_data[i*tga_comp+j] = raw_data[j]; // in case we're in RLE mode, keep counting down --RLE_count; } // do I need to invert the image? if ( tga_inverted ) { for (j = 0; j*2 < tga_height; ++j) { int index1 = j * tga_width * tga_comp; int index2 = (tga_height - 1 - j) * tga_width * tga_comp; for (i = tga_width * tga_comp; i > 0; --i) { unsigned char temp = tga_data[index1]; tga_data[index1] = tga_data[index2]; tga_data[index2] = temp; ++index1; ++index2; } } } // clear my palette, if I had one if ( tga_palette != NULL ) { STBI_FREE( tga_palette ); } } // swap RGB - if the source data was RGB16, it already is in the right order if (tga_comp >= 3 && !tga_rgb16) { unsigned char* tga_pixel = tga_data; for (i=0; i < tga_width * tga_height; ++i) { unsigned char temp = tga_pixel[0]; tga_pixel[0] = tga_pixel[2]; tga_pixel[2] = temp; tga_pixel += tga_comp; } } // convert to target component count if (req_comp && req_comp != tga_comp) tga_data = stbi__convert_format(tga_data, tga_comp, req_comp, tga_width, tga_height); // the things I do to get rid of an error message, and yet keep // Microsoft's C compilers happy... [8^( tga_palette_start = tga_palette_len = tga_palette_bits = tga_x_origin = tga_y_origin = 0; STBI_NOTUSED(tga_palette_start); // OK, done return tga_data; } #endif // ************************************************************************************************* // Photoshop PSD loader -- PD by Thatcher Ulrich, integration by Nicolas Schulz, tweaked by STB #ifndef STBI_NO_PSD static int stbi__psd_test(stbi__context *s) { int r = (stbi__get32be(s) == 0x38425053); stbi__rewind(s); return r; } static int stbi__psd_decode_rle(stbi__context *s, stbi_uc *p, int pixelCount) { int count, nleft, len; count = 0; while ((nleft = pixelCount - count) > 0) { len = stbi__get8(s); if (len == 128) { // No-op. } else if (len < 128) { // Copy next len+1 bytes literally. len++; if (len > nleft) return 0; // corrupt data count += len; while (len) { *p = stbi__get8(s); p += 4; len--; } } else if (len > 128) { stbi_uc val; // Next -len+1 bytes in the dest are replicated from next source byte. // (Interpret len as a negative 8-bit int.) len = 257 - len; if (len > nleft) return 0; // corrupt data val = stbi__get8(s); count += len; while (len) { *p = val; p += 4; len--; } } } return 1; } static void *stbi__psd_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri, int bpc) { int pixelCount; int channelCount, compression; int channel, i; int bitdepth; int w,h; stbi_uc *out; STBI_NOTUSED(ri); // Check identifier if (stbi__get32be(s) != 0x38425053) // "8BPS" return stbi__errpuc("not PSD", "Corrupt PSD image"); // Check file type version. if (stbi__get16be(s) != 1) return stbi__errpuc("wrong version", "Unsupported version of PSD image"); // Skip 6 reserved bytes. stbi__skip(s, 6 ); // Read the number of channels (R, G, B, A, etc). channelCount = stbi__get16be(s); if (channelCount < 0 || channelCount > 16) return stbi__errpuc("wrong channel count", "Unsupported number of channels in PSD image"); // Read the rows and columns of the image. h = stbi__get32be(s); w = stbi__get32be(s); if (h > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); if (w > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); // Make sure the depth is 8 bits. bitdepth = stbi__get16be(s); if (bitdepth != 8 && bitdepth != 16) return stbi__errpuc("unsupported bit depth", "PSD bit depth is not 8 or 16 bit"); // Make sure the color mode is RGB. // Valid options are: // 0: Bitmap // 1: Grayscale // 2: Indexed color // 3: RGB color // 4: CMYK color // 7: Multichannel // 8: Duotone // 9: Lab color if (stbi__get16be(s) != 3) return stbi__errpuc("wrong color format", "PSD is not in RGB color format"); // Skip the Mode Data. (It's the palette for indexed color; other info for other modes.) stbi__skip(s,stbi__get32be(s) ); // Skip the image resources. (resolution, pen tool paths, etc) stbi__skip(s, stbi__get32be(s) ); // Skip the reserved data. stbi__skip(s, stbi__get32be(s) ); // Find out if the data is compressed. // Known values: // 0: no compression // 1: RLE compressed compression = stbi__get16be(s); if (compression > 1) return stbi__errpuc("bad compression", "PSD has an unknown compression format"); // Check size if (!stbi__mad3sizes_valid(4, w, h, 0)) return stbi__errpuc("too large", "Corrupt PSD"); // Create the destination image. if (!compression && bitdepth == 16 && bpc == 16) { out = (stbi_uc *) stbi__malloc_mad3(8, w, h, 0); ri->bits_per_channel = 16; } else out = (stbi_uc *) stbi__malloc(4 * w*h); if (!out) return stbi__errpuc("outofmem", "Out of memory"); pixelCount = w*h; // Initialize the data to zero. //memset( out, 0, pixelCount * 4 ); // Finally, the image data. if (compression) { // RLE as used by .PSD and .TIFF // Loop until you get the number of unpacked bytes you are expecting: // Read the next source byte into n. // If n is between 0 and 127 inclusive, copy the next n+1 bytes literally. // Else if n is between -127 and -1 inclusive, copy the next byte -n+1 times. // Else if n is 128, noop. // Endloop // The RLE-compressed data is preceded by a 2-byte data count for each row in the data, // which we're going to just skip. stbi__skip(s, h * channelCount * 2 ); // Read the RLE data by channel. for (channel = 0; channel < 4; channel++) { stbi_uc *p; p = out+channel; if (channel >= channelCount) { // Fill this channel with default data. for (i = 0; i < pixelCount; i++, p += 4) *p = (channel == 3 ? 255 : 0); } else { // Read the RLE data. if (!stbi__psd_decode_rle(s, p, pixelCount)) { STBI_FREE(out); return stbi__errpuc("corrupt", "bad RLE data"); } } } } else { // We're at the raw image data. It's each channel in order (Red, Green, Blue, Alpha, ...) // where each channel consists of an 8-bit (or 16-bit) value for each pixel in the image. // Read the data by channel. for (channel = 0; channel < 4; channel++) { if (channel >= channelCount) { // Fill this channel with default data. if (bitdepth == 16 && bpc == 16) { stbi__uint16 *q = ((stbi__uint16 *) out) + channel; stbi__uint16 val = channel == 3 ? 65535 : 0; for (i = 0; i < pixelCount; i++, q += 4) *q = val; } else { stbi_uc *p = out+channel; stbi_uc val = channel == 3 ? 255 : 0; for (i = 0; i < pixelCount; i++, p += 4) *p = val; } } else { if (ri->bits_per_channel == 16) { // output bpc stbi__uint16 *q = ((stbi__uint16 *) out) + channel; for (i = 0; i < pixelCount; i++, q += 4) *q = (stbi__uint16) stbi__get16be(s); } else { stbi_uc *p = out+channel; if (bitdepth == 16) { // input bpc for (i = 0; i < pixelCount; i++, p += 4) *p = (stbi_uc) (stbi__get16be(s) >> 8); } else { for (i = 0; i < pixelCount; i++, p += 4) *p = stbi__get8(s); } } } } } // remove weird white matte from PSD if (channelCount >= 4) { if (ri->bits_per_channel == 16) { for (i=0; i < w*h; ++i) { stbi__uint16 *pixel = (stbi__uint16 *) out + 4*i; if (pixel[3] != 0 && pixel[3] != 65535) { float a = pixel[3] / 65535.0f; float ra = 1.0f / a; float inv_a = 65535.0f * (1 - ra); pixel[0] = (stbi__uint16) (pixel[0]*ra + inv_a); pixel[1] = (stbi__uint16) (pixel[1]*ra + inv_a); pixel[2] = (stbi__uint16) (pixel[2]*ra + inv_a); } } } else { for (i=0; i < w*h; ++i) { unsigned char *pixel = out + 4*i; if (pixel[3] != 0 && pixel[3] != 255) { float a = pixel[3] / 255.0f; float ra = 1.0f / a; float inv_a = 255.0f * (1 - ra); pixel[0] = (unsigned char) (pixel[0]*ra + inv_a); pixel[1] = (unsigned char) (pixel[1]*ra + inv_a); pixel[2] = (unsigned char) (pixel[2]*ra + inv_a); } } } } // convert to desired output format if (req_comp && req_comp != 4) { if (ri->bits_per_channel == 16) out = (stbi_uc *) stbi__convert_format16((stbi__uint16 *) out, 4, req_comp, w, h); else out = stbi__convert_format(out, 4, req_comp, w, h); if (out == NULL) return out; // stbi__convert_format frees input on failure } if (comp) *comp = 4; *y = h; *x = w; return out; } #endif // ************************************************************************************************* // Softimage PIC loader // by Tom Seddon // // See http://softimage.wiki.softimage.com/index.php/INFO:_PIC_file_format // See http://ozviz.wasp.uwa.edu.au/~pbourke/dataformats/softimagepic/ #ifndef STBI_NO_PIC static int stbi__pic_is4(stbi__context *s,const char *str) { int i; for (i=0; i<4; ++i) if (stbi__get8(s) != (stbi_uc)str[i]) return 0; return 1; } static int stbi__pic_test_core(stbi__context *s) { int i; if (!stbi__pic_is4(s,"\x53\x80\xF6\x34")) return 0; for(i=0;i<84;++i) stbi__get8(s); if (!stbi__pic_is4(s,"PICT")) return 0; return 1; } typedef struct { stbi_uc size,type,channel; } stbi__pic_packet; static stbi_uc *stbi__readval(stbi__context *s, int channel, stbi_uc *dest) { int mask=0x80, i; for (i=0; i<4; ++i, mask>>=1) { if (channel & mask) { if (stbi__at_eof(s)) return stbi__errpuc("bad file","PIC file too short"); dest[i]=stbi__get8(s); } } return dest; } static void stbi__copyval(int channel,stbi_uc *dest,const stbi_uc *src) { int mask=0x80,i; for (i=0;i<4; ++i, mask>>=1) if (channel&mask) dest[i]=src[i]; } static stbi_uc *stbi__pic_load_core(stbi__context *s,int width,int height,int *comp, stbi_uc *result) { int act_comp=0,num_packets=0,y,chained; stbi__pic_packet packets[10]; // this will (should...) cater for even some bizarre stuff like having data // for the same channel in multiple packets. do { stbi__pic_packet *packet; if (num_packets==sizeof(packets)/sizeof(packets[0])) return stbi__errpuc("bad format","too many packets"); packet = &packets[num_packets++]; chained = stbi__get8(s); packet->size = stbi__get8(s); packet->type = stbi__get8(s); packet->channel = stbi__get8(s); act_comp |= packet->channel; if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (reading packets)"); if (packet->size != 8) return stbi__errpuc("bad format","packet isn't 8bpp"); } while (chained); *comp = (act_comp & 0x10 ? 4 : 3); // has alpha channel? for(y=0; ytype) { default: return stbi__errpuc("bad format","packet has bad compression type"); case 0: {//uncompressed int x; for(x=0;xchannel,dest)) return 0; break; } case 1://Pure RLE { int left=width, i; while (left>0) { stbi_uc count,value[4]; count=stbi__get8(s); if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (pure read count)"); if (count > left) count = (stbi_uc) left; if (!stbi__readval(s,packet->channel,value)) return 0; for(i=0; ichannel,dest,value); left -= count; } } break; case 2: {//Mixed RLE int left=width; while (left>0) { int count = stbi__get8(s), i; if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (mixed read count)"); if (count >= 128) { // Repeated stbi_uc value[4]; if (count==128) count = stbi__get16be(s); else count -= 127; if (count > left) return stbi__errpuc("bad file","scanline overrun"); if (!stbi__readval(s,packet->channel,value)) return 0; for(i=0;ichannel,dest,value); } else { // Raw ++count; if (count>left) return stbi__errpuc("bad file","scanline overrun"); for(i=0;ichannel,dest)) return 0; } left-=count; } break; } } } } return result; } static void *stbi__pic_load(stbi__context *s,int *px,int *py,int *comp,int req_comp, stbi__result_info *ri) { stbi_uc *result; int i, x,y, internal_comp; STBI_NOTUSED(ri); if (!comp) comp = &internal_comp; for (i=0; i<92; ++i) stbi__get8(s); x = stbi__get16be(s); y = stbi__get16be(s); if (y > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); if (x > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (pic header)"); if (!stbi__mad3sizes_valid(x, y, 4, 0)) return stbi__errpuc("too large", "PIC image too large to decode"); stbi__get32be(s); //skip `ratio' stbi__get16be(s); //skip `fields' stbi__get16be(s); //skip `pad' // intermediate buffer is RGBA result = (stbi_uc *) stbi__malloc_mad3(x, y, 4, 0); if (!result) return stbi__errpuc("outofmem", "Out of memory"); memset(result, 0xff, x*y*4); if (!stbi__pic_load_core(s,x,y,comp, result)) { STBI_FREE(result); result=0; } *px = x; *py = y; if (req_comp == 0) req_comp = *comp; result=stbi__convert_format(result,4,req_comp,x,y); return result; } static int stbi__pic_test(stbi__context *s) { int r = stbi__pic_test_core(s); stbi__rewind(s); return r; } #endif // ************************************************************************************************* // GIF loader -- public domain by Jean-Marc Lienher -- simplified/shrunk by stb #ifndef STBI_NO_GIF typedef struct { stbi__int16 prefix; stbi_uc first; stbi_uc suffix; } stbi__gif_lzw; typedef struct { int w,h; stbi_uc *out; // output buffer (always 4 components) stbi_uc *background; // The current "background" as far as a gif is concerned stbi_uc *history; int flags, bgindex, ratio, transparent, eflags; stbi_uc pal[256][4]; stbi_uc lpal[256][4]; stbi__gif_lzw codes[8192]; stbi_uc *color_table; int parse, step; int lflags; int start_x, start_y; int max_x, max_y; int cur_x, cur_y; int line_size; int delay; } stbi__gif; static int stbi__gif_test_raw(stbi__context *s) { int sz; if (stbi__get8(s) != 'G' || stbi__get8(s) != 'I' || stbi__get8(s) != 'F' || stbi__get8(s) != '8') return 0; sz = stbi__get8(s); if (sz != '9' && sz != '7') return 0; if (stbi__get8(s) != 'a') return 0; return 1; } static int stbi__gif_test(stbi__context *s) { int r = stbi__gif_test_raw(s); stbi__rewind(s); return r; } static void stbi__gif_parse_colortable(stbi__context *s, stbi_uc pal[256][4], int num_entries, int transp) { int i; for (i=0; i < num_entries; ++i) { pal[i][2] = stbi__get8(s); pal[i][1] = stbi__get8(s); pal[i][0] = stbi__get8(s); pal[i][3] = transp == i ? 0 : 255; } } static int stbi__gif_header(stbi__context *s, stbi__gif *g, int *comp, int is_info) { stbi_uc version; if (stbi__get8(s) != 'G' || stbi__get8(s) != 'I' || stbi__get8(s) != 'F' || stbi__get8(s) != '8') return stbi__err("not GIF", "Corrupt GIF"); version = stbi__get8(s); if (version != '7' && version != '9') return stbi__err("not GIF", "Corrupt GIF"); if (stbi__get8(s) != 'a') return stbi__err("not GIF", "Corrupt GIF"); stbi__g_failure_reason = ""; g->w = stbi__get16le(s); g->h = stbi__get16le(s); g->flags = stbi__get8(s); g->bgindex = stbi__get8(s); g->ratio = stbi__get8(s); g->transparent = -1; if (g->w > STBI_MAX_DIMENSIONS) return stbi__err("too large","Very large image (corrupt?)"); if (g->h > STBI_MAX_DIMENSIONS) return stbi__err("too large","Very large image (corrupt?)"); if (comp != 0) *comp = 4; // can't actually tell whether it's 3 or 4 until we parse the comments if (is_info) return 1; if (g->flags & 0x80) stbi__gif_parse_colortable(s,g->pal, 2 << (g->flags & 7), -1); return 1; } static int stbi__gif_info_raw(stbi__context *s, int *x, int *y, int *comp) { stbi__gif* g = (stbi__gif*) stbi__malloc(sizeof(stbi__gif)); if (!g) return stbi__err("outofmem", "Out of memory"); if (!stbi__gif_header(s, g, comp, 1)) { STBI_FREE(g); stbi__rewind( s ); return 0; } if (x) *x = g->w; if (y) *y = g->h; STBI_FREE(g); return 1; } static void stbi__out_gif_code(stbi__gif *g, stbi__uint16 code) { stbi_uc *p, *c; int idx; // recurse to decode the prefixes, since the linked-list is backwards, // and working backwards through an interleaved image would be nasty if (g->codes[code].prefix >= 0) stbi__out_gif_code(g, g->codes[code].prefix); if (g->cur_y >= g->max_y) return; idx = g->cur_x + g->cur_y; p = &g->out[idx]; g->history[idx / 4] = 1; c = &g->color_table[g->codes[code].suffix * 4]; if (c[3] > 128) { // don't render transparent pixels; p[0] = c[2]; p[1] = c[1]; p[2] = c[0]; p[3] = c[3]; } g->cur_x += 4; if (g->cur_x >= g->max_x) { g->cur_x = g->start_x; g->cur_y += g->step; while (g->cur_y >= g->max_y && g->parse > 0) { g->step = (1 << g->parse) * g->line_size; g->cur_y = g->start_y + (g->step >> 1); --g->parse; } } } static stbi_uc *stbi__process_gif_raster(stbi__context *s, stbi__gif *g) { stbi_uc lzw_cs; stbi__int32 len, init_code; stbi__uint32 first; stbi__int32 codesize, codemask, avail, oldcode, bits, valid_bits, clear; stbi__gif_lzw *p; lzw_cs = stbi__get8(s); if (lzw_cs > 12) return NULL; clear = 1 << lzw_cs; first = 1; codesize = lzw_cs + 1; codemask = (1 << codesize) - 1; bits = 0; valid_bits = 0; for (init_code = 0; init_code < clear; init_code++) { g->codes[init_code].prefix = -1; g->codes[init_code].first = (stbi_uc) init_code; g->codes[init_code].suffix = (stbi_uc) init_code; } // support no starting clear code avail = clear+2; oldcode = -1; len = 0; for(;;) { if (valid_bits < codesize) { if (len == 0) { len = stbi__get8(s); // start new block if (len == 0) return g->out; } --len; bits |= (stbi__int32) stbi__get8(s) << valid_bits; valid_bits += 8; } else { stbi__int32 code = bits & codemask; bits >>= codesize; valid_bits -= codesize; // @OPTIMIZE: is there some way we can accelerate the non-clear path? if (code == clear) { // clear code codesize = lzw_cs + 1; codemask = (1 << codesize) - 1; avail = clear + 2; oldcode = -1; first = 0; } else if (code == clear + 1) { // end of stream code stbi__skip(s, len); while ((len = stbi__get8(s)) > 0) stbi__skip(s,len); return g->out; } else if (code <= avail) { if (first) { return stbi__errpuc("no clear code", "Corrupt GIF"); } if (oldcode >= 0) { p = &g->codes[avail++]; if (avail > 8192) { return stbi__errpuc("too many codes", "Corrupt GIF"); } p->prefix = (stbi__int16) oldcode; p->first = g->codes[oldcode].first; p->suffix = (code == avail) ? p->first : g->codes[code].first; } else if (code == avail) return stbi__errpuc("illegal code in raster", "Corrupt GIF"); stbi__out_gif_code(g, (stbi__uint16) code); if ((avail & codemask) == 0 && avail <= 0x0FFF) { codesize++; codemask = (1 << codesize) - 1; } oldcode = code; } else { return stbi__errpuc("illegal code in raster", "Corrupt GIF"); } } } } // this function is designed to support animated gifs, although stb_image doesn't support it // two back is the image from two frames ago, used for a very specific disposal format static stbi_uc *stbi__gif_load_next(stbi__context *s, stbi__gif *g, int *comp, int req_comp, stbi_uc *two_back) { int dispose; int first_frame; int pi; int pcount; STBI_NOTUSED(req_comp); // on first frame, any non-written pixels get the background colour (non-transparent) first_frame = 0; if (g->out == 0) { if (!stbi__gif_header(s, g, comp,0)) return 0; // stbi__g_failure_reason set by stbi__gif_header if (!stbi__mad3sizes_valid(4, g->w, g->h, 0)) return stbi__errpuc("too large", "GIF image is too large"); pcount = g->w * g->h; g->out = (stbi_uc *) stbi__malloc(4 * pcount); g->background = (stbi_uc *) stbi__malloc(4 * pcount); g->history = (stbi_uc *) stbi__malloc(pcount); if (!g->out || !g->background || !g->history) return stbi__errpuc("outofmem", "Out of memory"); // image is treated as "transparent" at the start - ie, nothing overwrites the current background; // background colour is only used for pixels that are not rendered first frame, after that "background" // color refers to the color that was there the previous frame. memset(g->out, 0x00, 4 * pcount); memset(g->background, 0x00, 4 * pcount); // state of the background (starts transparent) memset(g->history, 0x00, pcount); // pixels that were affected previous frame first_frame = 1; } else { // second frame - how do we dispose of the previous one? dispose = (g->eflags & 0x1C) >> 2; pcount = g->w * g->h; if ((dispose == 3) && (two_back == 0)) { dispose = 2; // if I don't have an image to revert back to, default to the old background } if (dispose == 3) { // use previous graphic for (pi = 0; pi < pcount; ++pi) { if (g->history[pi]) { memcpy( &g->out[pi * 4], &two_back[pi * 4], 4 ); } } } else if (dispose == 2) { // restore what was changed last frame to background before that frame; for (pi = 0; pi < pcount; ++pi) { if (g->history[pi]) { memcpy( &g->out[pi * 4], &g->background[pi * 4], 4 ); } } } else { // This is a non-disposal case eithe way, so just // leave the pixels as is, and they will become the new background // 1: do not dispose // 0: not specified. } // background is what out is after the undoing of the previou frame; memcpy( g->background, g->out, 4 * g->w * g->h ); } // clear my history; memset( g->history, 0x00, g->w * g->h ); // pixels that were affected previous frame for (;;) { int tag = stbi__get8(s); switch (tag) { case 0x2C: /* Image Descriptor */ { stbi__int32 x, y, w, h; stbi_uc *o; x = stbi__get16le(s); y = stbi__get16le(s); w = stbi__get16le(s); h = stbi__get16le(s); if (((x + w) > (g->w)) || ((y + h) > (g->h))) return stbi__errpuc("bad Image Descriptor", "Corrupt GIF"); g->line_size = g->w * 4; g->start_x = x * 4; g->start_y = y * g->line_size; g->max_x = g->start_x + w * 4; g->max_y = g->start_y + h * g->line_size; g->cur_x = g->start_x; g->cur_y = g->start_y; // if the width of the specified rectangle is 0, that means // we may not see *any* pixels or the image is malformed; // to make sure this is caught, move the current y down to // max_y (which is what out_gif_code checks). if (w == 0) g->cur_y = g->max_y; g->lflags = stbi__get8(s); if (g->lflags & 0x40) { g->step = 8 * g->line_size; // first interlaced spacing g->parse = 3; } else { g->step = g->line_size; g->parse = 0; } if (g->lflags & 0x80) { stbi__gif_parse_colortable(s,g->lpal, 2 << (g->lflags & 7), g->eflags & 0x01 ? g->transparent : -1); g->color_table = (stbi_uc *) g->lpal; } else if (g->flags & 0x80) { g->color_table = (stbi_uc *) g->pal; } else return stbi__errpuc("missing color table", "Corrupt GIF"); o = stbi__process_gif_raster(s, g); if (!o) return NULL; // if this was the first frame, pcount = g->w * g->h; if (first_frame && (g->bgindex > 0)) { // if first frame, any pixel not drawn to gets the background color for (pi = 0; pi < pcount; ++pi) { if (g->history[pi] == 0) { g->pal[g->bgindex][3] = 255; // just in case it was made transparent, undo that; It will be reset next frame if need be; memcpy( &g->out[pi * 4], &g->pal[g->bgindex], 4 ); } } } return o; } case 0x21: // Comment Extension. { int len; int ext = stbi__get8(s); if (ext == 0xF9) { // Graphic Control Extension. len = stbi__get8(s); if (len == 4) { g->eflags = stbi__get8(s); g->delay = 10 * stbi__get16le(s); // delay - 1/100th of a second, saving as 1/1000ths. // unset old transparent if (g->transparent >= 0) { g->pal[g->transparent][3] = 255; } if (g->eflags & 0x01) { g->transparent = stbi__get8(s); if (g->transparent >= 0) { g->pal[g->transparent][3] = 0; } } else { // don't need transparent stbi__skip(s, 1); g->transparent = -1; } } else { stbi__skip(s, len); break; } } while ((len = stbi__get8(s)) != 0) { stbi__skip(s, len); } break; } case 0x3B: // gif stream termination code return (stbi_uc *) s; // using '1' causes warning on some compilers default: return stbi__errpuc("unknown code", "Corrupt GIF"); } } } static void *stbi__load_gif_main_outofmem(stbi__gif *g, stbi_uc *out, int **delays) { STBI_FREE(g->out); STBI_FREE(g->history); STBI_FREE(g->background); if (out) STBI_FREE(out); if (delays && *delays) STBI_FREE(*delays); return stbi__errpuc("outofmem", "Out of memory"); } static void *stbi__load_gif_main(stbi__context *s, int **delays, int *x, int *y, int *z, int *comp, int req_comp) { if (stbi__gif_test(s)) { int layers = 0; stbi_uc *u = 0; stbi_uc *out = 0; stbi_uc *two_back = 0; stbi__gif g; int stride; int out_size = 0; int delays_size = 0; STBI_NOTUSED(out_size); STBI_NOTUSED(delays_size); memset(&g, 0, sizeof(g)); if (delays) { *delays = 0; } do { u = stbi__gif_load_next(s, &g, comp, req_comp, two_back); if (u == (stbi_uc *) s) u = 0; // end of animated gif marker if (u) { *x = g.w; *y = g.h; ++layers; stride = g.w * g.h * 4; if (out) { void *tmp = (stbi_uc*) STBI_REALLOC_SIZED( out, out_size, layers * stride ); if (!tmp) return stbi__load_gif_main_outofmem(&g, out, delays); else { out = (stbi_uc*) tmp; out_size = layers * stride; } if (delays) { int *new_delays = (int*) STBI_REALLOC_SIZED( *delays, delays_size, sizeof(int) * layers ); if (!new_delays) return stbi__load_gif_main_outofmem(&g, out, delays); *delays = new_delays; delays_size = layers * sizeof(int); } } else { out = (stbi_uc*)stbi__malloc( layers * stride ); if (!out) return stbi__load_gif_main_outofmem(&g, out, delays); out_size = layers * stride; if (delays) { *delays = (int*) stbi__malloc( layers * sizeof(int) ); if (!*delays) return stbi__load_gif_main_outofmem(&g, out, delays); delays_size = layers * sizeof(int); } } memcpy( out + ((layers - 1) * stride), u, stride ); if (layers >= 2) { two_back = out - 2 * stride; } if (delays) { (*delays)[layers - 1U] = g.delay; } } } while (u != 0); // free temp buffer; STBI_FREE(g.out); STBI_FREE(g.history); STBI_FREE(g.background); // do the final conversion after loading everything; if (req_comp && req_comp != 4) out = stbi__convert_format(out, 4, req_comp, layers * g.w, g.h); *z = layers; return out; } else { return stbi__errpuc("not GIF", "Image was not as a gif type."); } } static void *stbi__gif_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) { stbi_uc *u = 0; stbi__gif g; memset(&g, 0, sizeof(g)); STBI_NOTUSED(ri); u = stbi__gif_load_next(s, &g, comp, req_comp, 0); if (u == (stbi_uc *) s) u = 0; // end of animated gif marker if (u) { *x = g.w; *y = g.h; // moved conversion to after successful load so that the same // can be done for multiple frames. if (req_comp && req_comp != 4) u = stbi__convert_format(u, 4, req_comp, g.w, g.h); } else if (g.out) { // if there was an error and we allocated an image buffer, free it! STBI_FREE(g.out); } // free buffers needed for multiple frame loading; STBI_FREE(g.history); STBI_FREE(g.background); return u; } static int stbi__gif_info(stbi__context *s, int *x, int *y, int *comp) { return stbi__gif_info_raw(s,x,y,comp); } #endif // ************************************************************************************************* // Radiance RGBE HDR loader // originally by Nicolas Schulz #ifndef STBI_NO_HDR static int stbi__hdr_test_core(stbi__context *s, const char *signature) { int i; for (i=0; signature[i]; ++i) if (stbi__get8(s) != signature[i]) return 0; stbi__rewind(s); return 1; } static int stbi__hdr_test(stbi__context* s) { int r = stbi__hdr_test_core(s, "#?RADIANCE\n"); stbi__rewind(s); if(!r) { r = stbi__hdr_test_core(s, "#?RGBE\n"); stbi__rewind(s); } return r; } #define STBI__HDR_BUFLEN 1024 static char *stbi__hdr_gettoken(stbi__context *z, char *buffer) { int len=0; char c = '\0'; c = (char) stbi__get8(z); while (!stbi__at_eof(z) && c != '\n') { buffer[len++] = c; if (len == STBI__HDR_BUFLEN-1) { // flush to end of line while (!stbi__at_eof(z) && stbi__get8(z) != '\n') ; break; } c = (char) stbi__get8(z); } buffer[len] = 0; return buffer; } static void stbi__hdr_convert(float *output, stbi_uc *input, int req_comp) { if ( input[3] != 0 ) { float f1; // Exponent f1 = (float) ldexp(1.0f, input[3] - (int)(128 + 8)); if (req_comp <= 2) output[0] = (input[0] + input[1] + input[2]) * f1 / 3; else { output[0] = input[0] * f1; output[1] = input[1] * f1; output[2] = input[2] * f1; } if (req_comp == 2) output[1] = 1; if (req_comp == 4) output[3] = 1; } else { switch (req_comp) { case 4: output[3] = 1; /* fallthrough */ case 3: output[0] = output[1] = output[2] = 0; break; case 2: output[1] = 1; /* fallthrough */ case 1: output[0] = 0; break; } } } static float *stbi__hdr_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) { char buffer[STBI__HDR_BUFLEN]; char *token; int valid = 0; int width, height; stbi_uc *scanline; float *hdr_data; int len; unsigned char count, value; int i, j, k, c1,c2, z; const char *headerToken; STBI_NOTUSED(ri); // Check identifier headerToken = stbi__hdr_gettoken(s,buffer); if (strcmp(headerToken, "#?RADIANCE") != 0 && strcmp(headerToken, "#?RGBE") != 0) return stbi__errpf("not HDR", "Corrupt HDR image"); // Parse header for(;;) { token = stbi__hdr_gettoken(s,buffer); if (token[0] == 0) break; if (strcmp(token, "FORMAT=32-bit_rle_rgbe") == 0) valid = 1; } if (!valid) return stbi__errpf("unsupported format", "Unsupported HDR format"); // Parse width and height // can't use sscanf() if we're not using stdio! token = stbi__hdr_gettoken(s,buffer); if (strncmp(token, "-Y ", 3)) return stbi__errpf("unsupported data layout", "Unsupported HDR format"); token += 3; height = (int) strtol(token, &token, 10); while (*token == ' ') ++token; if (strncmp(token, "+X ", 3)) return stbi__errpf("unsupported data layout", "Unsupported HDR format"); token += 3; width = (int) strtol(token, NULL, 10); if (height > STBI_MAX_DIMENSIONS) return stbi__errpf("too large","Very large image (corrupt?)"); if (width > STBI_MAX_DIMENSIONS) return stbi__errpf("too large","Very large image (corrupt?)"); *x = width; *y = height; if (comp) *comp = 3; if (req_comp == 0) req_comp = 3; if (!stbi__mad4sizes_valid(width, height, req_comp, sizeof(float), 0)) return stbi__errpf("too large", "HDR image is too large"); // Read data hdr_data = (float *) stbi__malloc_mad4(width, height, req_comp, sizeof(float), 0); if (!hdr_data) return stbi__errpf("outofmem", "Out of memory"); // Load image data // image data is stored as some number of sca if ( width < 8 || width >= 32768) { // Read flat data for (j=0; j < height; ++j) { for (i=0; i < width; ++i) { stbi_uc rgbe[4]; main_decode_loop: stbi__getn(s, rgbe, 4); stbi__hdr_convert(hdr_data + j * width * req_comp + i * req_comp, rgbe, req_comp); } } } else { // Read RLE-encoded data scanline = NULL; for (j = 0; j < height; ++j) { c1 = stbi__get8(s); c2 = stbi__get8(s); len = stbi__get8(s); if (c1 != 2 || c2 != 2 || (len & 0x80)) { // not run-length encoded, so we have to actually use THIS data as a decoded // pixel (note this can't be a valid pixel--one of RGB must be >= 128) stbi_uc rgbe[4]; rgbe[0] = (stbi_uc) c1; rgbe[1] = (stbi_uc) c2; rgbe[2] = (stbi_uc) len; rgbe[3] = (stbi_uc) stbi__get8(s); stbi__hdr_convert(hdr_data, rgbe, req_comp); i = 1; j = 0; STBI_FREE(scanline); goto main_decode_loop; // yes, this makes no sense } len <<= 8; len |= stbi__get8(s); if (len != width) { STBI_FREE(hdr_data); STBI_FREE(scanline); return stbi__errpf("invalid decoded scanline length", "corrupt HDR"); } if (scanline == NULL) { scanline = (stbi_uc *) stbi__malloc_mad2(width, 4, 0); if (!scanline) { STBI_FREE(hdr_data); return stbi__errpf("outofmem", "Out of memory"); } } for (k = 0; k < 4; ++k) { int nleft; i = 0; while ((nleft = width - i) > 0) { count = stbi__get8(s); if (count > 128) { // Run value = stbi__get8(s); count -= 128; if ((count == 0) || (count > nleft)) { STBI_FREE(hdr_data); STBI_FREE(scanline); return stbi__errpf("corrupt", "bad RLE data in HDR"); } for (z = 0; z < count; ++z) scanline[i++ * 4 + k] = value; } else { // Dump if ((count == 0) || (count > nleft)) { STBI_FREE(hdr_data); STBI_FREE(scanline); return stbi__errpf("corrupt", "bad RLE data in HDR"); } for (z = 0; z < count; ++z) scanline[i++ * 4 + k] = stbi__get8(s); } } } for (i=0; i < width; ++i) stbi__hdr_convert(hdr_data+(j*width + i)*req_comp, scanline + i*4, req_comp); } if (scanline) STBI_FREE(scanline); } return hdr_data; } static int stbi__hdr_info(stbi__context *s, int *x, int *y, int *comp) { char buffer[STBI__HDR_BUFLEN]; char *token; int valid = 0; int dummy; if (!x) x = &dummy; if (!y) y = &dummy; if (!comp) comp = &dummy; if (stbi__hdr_test(s) == 0) { stbi__rewind( s ); return 0; } for(;;) { token = stbi__hdr_gettoken(s,buffer); if (token[0] == 0) break; if (strcmp(token, "FORMAT=32-bit_rle_rgbe") == 0) valid = 1; } if (!valid) { stbi__rewind( s ); return 0; } token = stbi__hdr_gettoken(s,buffer); if (strncmp(token, "-Y ", 3)) { stbi__rewind( s ); return 0; } token += 3; *y = (int) strtol(token, &token, 10); while (*token == ' ') ++token; if (strncmp(token, "+X ", 3)) { stbi__rewind( s ); return 0; } token += 3; *x = (int) strtol(token, NULL, 10); *comp = 3; return 1; } #endif // STBI_NO_HDR #ifndef STBI_NO_BMP static int stbi__bmp_info(stbi__context *s, int *x, int *y, int *comp) { void *p; stbi__bmp_data info; info.all_a = 255; p = stbi__bmp_parse_header(s, &info); if (p == NULL) { stbi__rewind( s ); return 0; } if (x) *x = s->img_x; if (y) *y = s->img_y; if (comp) { if (info.bpp == 24 && info.ma == 0xff000000) *comp = 3; else *comp = info.ma ? 4 : 3; } return 1; } #endif #ifndef STBI_NO_PSD static int stbi__psd_info(stbi__context *s, int *x, int *y, int *comp) { int channelCount, dummy, depth; if (!x) x = &dummy; if (!y) y = &dummy; if (!comp) comp = &dummy; if (stbi__get32be(s) != 0x38425053) { stbi__rewind( s ); return 0; } if (stbi__get16be(s) != 1) { stbi__rewind( s ); return 0; } stbi__skip(s, 6); channelCount = stbi__get16be(s); if (channelCount < 0 || channelCount > 16) { stbi__rewind( s ); return 0; } *y = stbi__get32be(s); *x = stbi__get32be(s); depth = stbi__get16be(s); if (depth != 8 && depth != 16) { stbi__rewind( s ); return 0; } if (stbi__get16be(s) != 3) { stbi__rewind( s ); return 0; } *comp = 4; return 1; } static int stbi__psd_is16(stbi__context *s) { int channelCount, depth; if (stbi__get32be(s) != 0x38425053) { stbi__rewind( s ); return 0; } if (stbi__get16be(s) != 1) { stbi__rewind( s ); return 0; } stbi__skip(s, 6); channelCount = stbi__get16be(s); if (channelCount < 0 || channelCount > 16) { stbi__rewind( s ); return 0; } STBI_NOTUSED(stbi__get32be(s)); STBI_NOTUSED(stbi__get32be(s)); depth = stbi__get16be(s); if (depth != 16) { stbi__rewind( s ); return 0; } return 1; } #endif #ifndef STBI_NO_PIC static int stbi__pic_info(stbi__context *s, int *x, int *y, int *comp) { int act_comp=0,num_packets=0,chained,dummy; stbi__pic_packet packets[10]; if (!x) x = &dummy; if (!y) y = &dummy; if (!comp) comp = &dummy; if (!stbi__pic_is4(s,"\x53\x80\xF6\x34")) { stbi__rewind(s); return 0; } stbi__skip(s, 88); *x = stbi__get16be(s); *y = stbi__get16be(s); if (stbi__at_eof(s)) { stbi__rewind( s); return 0; } if ( (*x) != 0 && (1 << 28) / (*x) < (*y)) { stbi__rewind( s ); return 0; } stbi__skip(s, 8); do { stbi__pic_packet *packet; if (num_packets==sizeof(packets)/sizeof(packets[0])) return 0; packet = &packets[num_packets++]; chained = stbi__get8(s); packet->size = stbi__get8(s); packet->type = stbi__get8(s); packet->channel = stbi__get8(s); act_comp |= packet->channel; if (stbi__at_eof(s)) { stbi__rewind( s ); return 0; } if (packet->size != 8) { stbi__rewind( s ); return 0; } } while (chained); *comp = (act_comp & 0x10 ? 4 : 3); return 1; } #endif // ************************************************************************************************* // Portable Gray Map and Portable Pixel Map loader // by Ken Miller // // PGM: http://netpbm.sourceforge.net/doc/pgm.html // PPM: http://netpbm.sourceforge.net/doc/ppm.html // // Known limitations: // Does not support comments in the header section // Does not support ASCII image data (formats P2 and P3) #ifndef STBI_NO_PNM static int stbi__pnm_test(stbi__context *s) { char p, t; p = (char) stbi__get8(s); t = (char) stbi__get8(s); if (p != 'P' || (t != '5' && t != '6')) { stbi__rewind( s ); return 0; } return 1; } static void *stbi__pnm_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) { stbi_uc *out; STBI_NOTUSED(ri); ri->bits_per_channel = stbi__pnm_info(s, (int *)&s->img_x, (int *)&s->img_y, (int *)&s->img_n); if (ri->bits_per_channel == 0) return 0; if (s->img_y > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); if (s->img_x > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); *x = s->img_x; *y = s->img_y; if (comp) *comp = s->img_n; if (!stbi__mad4sizes_valid(s->img_n, s->img_x, s->img_y, ri->bits_per_channel / 8, 0)) return stbi__errpuc("too large", "PNM too large"); out = (stbi_uc *) stbi__malloc_mad4(s->img_n, s->img_x, s->img_y, ri->bits_per_channel / 8, 0); if (!out) return stbi__errpuc("outofmem", "Out of memory"); if (!stbi__getn(s, out, s->img_n * s->img_x * s->img_y * (ri->bits_per_channel / 8))) { STBI_FREE(out); return stbi__errpuc("bad PNM", "PNM file truncated"); } if (req_comp && req_comp != s->img_n) { if (ri->bits_per_channel == 16) { out = (stbi_uc *) stbi__convert_format16((stbi__uint16 *) out, s->img_n, req_comp, s->img_x, s->img_y); } else { out = stbi__convert_format(out, s->img_n, req_comp, s->img_x, s->img_y); } if (out == NULL) return out; // stbi__convert_format frees input on failure } return out; } static int stbi__pnm_isspace(char c) { return c == ' ' || c == '\t' || c == '\n' || c == '\v' || c == '\f' || c == '\r'; } static void stbi__pnm_skip_whitespace(stbi__context *s, char *c) { for (;;) { while (!stbi__at_eof(s) && stbi__pnm_isspace(*c)) *c = (char) stbi__get8(s); if (stbi__at_eof(s) || *c != '#') break; while (!stbi__at_eof(s) && *c != '\n' && *c != '\r' ) *c = (char) stbi__get8(s); } } static int stbi__pnm_isdigit(char c) { return c >= '0' && c <= '9'; } static int stbi__pnm_getinteger(stbi__context *s, char *c) { int value = 0; while (!stbi__at_eof(s) && stbi__pnm_isdigit(*c)) { value = value*10 + (*c - '0'); *c = (char) stbi__get8(s); if((value > 214748364) || (value == 214748364 && *c > '7')) return stbi__err("integer parse overflow", "Parsing an integer in the PPM header overflowed a 32-bit int"); } return value; } static int stbi__pnm_info(stbi__context *s, int *x, int *y, int *comp) { int maxv, dummy; char c, p, t; if (!x) x = &dummy; if (!y) y = &dummy; if (!comp) comp = &dummy; stbi__rewind(s); // Get identifier p = (char) stbi__get8(s); t = (char) stbi__get8(s); if (p != 'P' || (t != '5' && t != '6')) { stbi__rewind(s); return 0; } *comp = (t == '6') ? 3 : 1; // '5' is 1-component .pgm; '6' is 3-component .ppm c = (char) stbi__get8(s); stbi__pnm_skip_whitespace(s, &c); *x = stbi__pnm_getinteger(s, &c); // read width if(*x == 0) return stbi__err("invalid width", "PPM image header had zero or overflowing width"); stbi__pnm_skip_whitespace(s, &c); *y = stbi__pnm_getinteger(s, &c); // read height if (*y == 0) return stbi__err("invalid width", "PPM image header had zero or overflowing width"); stbi__pnm_skip_whitespace(s, &c); maxv = stbi__pnm_getinteger(s, &c); // read max value if (maxv > 65535) return stbi__err("max value > 65535", "PPM image supports only 8-bit and 16-bit images"); else if (maxv > 255) return 16; else return 8; } static int stbi__pnm_is16(stbi__context *s) { if (stbi__pnm_info(s, NULL, NULL, NULL) == 16) return 1; return 0; } #endif static int stbi__info_main(stbi__context *s, int *x, int *y, int *comp) { #ifndef STBI_NO_JPEG if (stbi__jpeg_info(s, x, y, comp)) return 1; #endif #ifndef STBI_NO_PNG if (stbi__png_info(s, x, y, comp)) return 1; #endif #ifndef STBI_NO_GIF if (stbi__gif_info(s, x, y, comp)) return 1; #endif #ifndef STBI_NO_BMP if (stbi__bmp_info(s, x, y, comp)) return 1; #endif #ifndef STBI_NO_PSD if (stbi__psd_info(s, x, y, comp)) return 1; #endif #ifndef STBI_NO_PIC if (stbi__pic_info(s, x, y, comp)) return 1; #endif #ifndef STBI_NO_PNM if (stbi__pnm_info(s, x, y, comp)) return 1; #endif #ifndef STBI_NO_HDR if (stbi__hdr_info(s, x, y, comp)) return 1; #endif // test tga last because it's a crappy test! #ifndef STBI_NO_TGA if (stbi__tga_info(s, x, y, comp)) return 1; #endif return stbi__err("unknown image type", "Image not of any known type, or corrupt"); } static int stbi__is_16_main(stbi__context *s) { #ifndef STBI_NO_PNG if (stbi__png_is16(s)) return 1; #endif #ifndef STBI_NO_PSD if (stbi__psd_is16(s)) return 1; #endif #ifndef STBI_NO_PNM if (stbi__pnm_is16(s)) return 1; #endif return 0; } #ifndef STBI_NO_STDIO STBIDEF int stbi_info(char const *filename, int *x, int *y, int *comp) { FILE *f = stbi__fopen(filename, "rb"); int result; if (!f) return stbi__err("can't fopen", "Unable to open file"); result = stbi_info_from_file(f, x, y, comp); fclose(f); return result; } STBIDEF int stbi_info_from_file(FILE *f, int *x, int *y, int *comp) { int r; stbi__context s; long pos = ftell(f); stbi__start_file(&s, f); r = stbi__info_main(&s,x,y,comp); fseek(f,pos,SEEK_SET); return r; } STBIDEF int stbi_is_16_bit(char const *filename) { FILE *f = stbi__fopen(filename, "rb"); int result; if (!f) return stbi__err("can't fopen", "Unable to open file"); result = stbi_is_16_bit_from_file(f); fclose(f); return result; } STBIDEF int stbi_is_16_bit_from_file(FILE *f) { int r; stbi__context s; long pos = ftell(f); stbi__start_file(&s, f); r = stbi__is_16_main(&s); fseek(f,pos,SEEK_SET); return r; } #endif // !STBI_NO_STDIO STBIDEF int stbi_info_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp) { stbi__context s; stbi__start_mem(&s,buffer,len); return stbi__info_main(&s,x,y,comp); } STBIDEF int stbi_info_from_callbacks(stbi_io_callbacks const *c, void *user, int *x, int *y, int *comp) { stbi__context s; stbi__start_callbacks(&s, (stbi_io_callbacks *) c, user); return stbi__info_main(&s,x,y,comp); } STBIDEF int stbi_is_16_bit_from_memory(stbi_uc const *buffer, int len) { stbi__context s; stbi__start_mem(&s,buffer,len); return stbi__is_16_main(&s); } STBIDEF int stbi_is_16_bit_from_callbacks(stbi_io_callbacks const *c, void *user) { stbi__context s; stbi__start_callbacks(&s, (stbi_io_callbacks *) c, user); return stbi__is_16_main(&s); } #endif // STB_IMAGE_IMPLEMENTATION /* revision history: 2.20 (2019-02-07) support utf8 filenames in Windows; fix warnings and platform ifdefs 2.19 (2018-02-11) fix warning 2.18 (2018-01-30) fix warnings 2.17 (2018-01-29) change sbti__shiftsigned to avoid clang -O2 bug 1-bit BMP *_is_16_bit api avoid warnings 2.16 (2017-07-23) all functions have 16-bit variants; STBI_NO_STDIO works again; compilation fixes; fix rounding in unpremultiply; optimize vertical flip; disable raw_len validation; documentation fixes 2.15 (2017-03-18) fix png-1,2,4 bug; now all Imagenet JPGs decode; warning fixes; disable run-time SSE detection on gcc; uniform handling of optional "return" values; thread-safe initialization of zlib tables 2.14 (2017-03-03) remove deprecated STBI_JPEG_OLD; fixes for Imagenet JPGs 2.13 (2016-11-29) add 16-bit API, only supported for PNG right now 2.12 (2016-04-02) fix typo in 2.11 PSD fix that caused crashes 2.11 (2016-04-02) allocate large structures on the stack remove white matting for transparent PSD fix reported channel count for PNG & BMP re-enable SSE2 in non-gcc 64-bit support RGB-formatted JPEG read 16-bit PNGs (only as 8-bit) 2.10 (2016-01-22) avoid warning introduced in 2.09 by STBI_REALLOC_SIZED 2.09 (2016-01-16) allow comments in PNM files 16-bit-per-pixel TGA (not bit-per-component) info() for TGA could break due to .hdr handling info() for BMP to shares code instead of sloppy parse can use STBI_REALLOC_SIZED if allocator doesn't support realloc code cleanup 2.08 (2015-09-13) fix to 2.07 cleanup, reading RGB PSD as RGBA 2.07 (2015-09-13) fix compiler warnings partial animated GIF support limited 16-bpc PSD support #ifdef unused functions bug with < 92 byte PIC,PNM,HDR,TGA 2.06 (2015-04-19) fix bug where PSD returns wrong '*comp' value 2.05 (2015-04-19) fix bug in progressive JPEG handling, fix warning 2.04 (2015-04-15) try to re-enable SIMD on MinGW 64-bit 2.03 (2015-04-12) extra corruption checking (mmozeiko) stbi_set_flip_vertically_on_load (nguillemot) fix NEON support; fix mingw support 2.02 (2015-01-19) fix incorrect assert, fix warning 2.01 (2015-01-17) fix various warnings; suppress SIMD on gcc 32-bit without -msse2 2.00b (2014-12-25) fix STBI_MALLOC in progressive JPEG 2.00 (2014-12-25) optimize JPG, including x86 SSE2 & NEON SIMD (ryg) progressive JPEG (stb) PGM/PPM support (Ken Miller) STBI_MALLOC,STBI_REALLOC,STBI_FREE GIF bugfix -- seemingly never worked STBI_NO_*, STBI_ONLY_* 1.48 (2014-12-14) fix incorrectly-named assert() 1.47 (2014-12-14) 1/2/4-bit PNG support, both direct and paletted (Omar Cornut & stb) optimize PNG (ryg) fix bug in interlaced PNG with user-specified channel count (stb) 1.46 (2014-08-26) fix broken tRNS chunk (colorkey-style transparency) in non-paletted PNG 1.45 (2014-08-16) fix MSVC-ARM internal compiler error by wrapping malloc 1.44 (2014-08-07) various warning fixes from Ronny Chevalier 1.43 (2014-07-15) fix MSVC-only compiler problem in code changed in 1.42 1.42 (2014-07-09) don't define _CRT_SECURE_NO_WARNINGS (affects user code) fixes to stbi__cleanup_jpeg path added STBI_ASSERT to avoid requiring assert.h 1.41 (2014-06-25) fix search&replace from 1.36 that messed up comments/error messages 1.40 (2014-06-22) fix gcc struct-initialization warning 1.39 (2014-06-15) fix to TGA optimization when req_comp != number of components in TGA; fix to GIF loading because BMP wasn't rewinding (whoops, no GIFs in my test suite) add support for BMP version 5 (more ignored fields) 1.38 (2014-06-06) suppress MSVC warnings on integer casts truncating values fix accidental rename of 'skip' field of I/O 1.37 (2014-06-04) remove duplicate typedef 1.36 (2014-06-03) convert to header file single-file library if de-iphone isn't set, load iphone images color-swapped instead of returning NULL 1.35 (2014-05-27) various warnings fix broken STBI_SIMD path fix bug where stbi_load_from_file no longer left file pointer in correct place fix broken non-easy path for 32-bit BMP (possibly never used) TGA optimization by Arseny Kapoulkine 1.34 (unknown) use STBI_NOTUSED in stbi__resample_row_generic(), fix one more leak in tga failure case 1.33 (2011-07-14) make stbi_is_hdr work in STBI_NO_HDR (as specified), minor compiler-friendly improvements 1.32 (2011-07-13) support for "info" function for all supported filetypes (SpartanJ) 1.31 (2011-06-20) a few more leak fixes, bug in PNG handling (SpartanJ) 1.30 (2011-06-11) added ability to load files via callbacks to accomidate custom input streams (Ben Wenger) removed deprecated format-specific test/load functions removed support for installable file formats (stbi_loader) -- would have been broken for IO callbacks anyway error cases in bmp and tga give messages and don't leak (Raymond Barbiero, grisha) fix inefficiency in decoding 32-bit BMP (David Woo) 1.29 (2010-08-16) various warning fixes from Aurelien Pocheville 1.28 (2010-08-01) fix bug in GIF palette transparency (SpartanJ) 1.27 (2010-08-01) cast-to-stbi_uc to fix warnings 1.26 (2010-07-24) fix bug in file buffering for PNG reported by SpartanJ 1.25 (2010-07-17) refix trans_data warning (Won Chun) 1.24 (2010-07-12) perf improvements reading from files on platforms with lock-heavy fgetc() minor perf improvements for jpeg deprecated type-specific functions so we'll get feedback if they're needed attempt to fix trans_data warning (Won Chun) 1.23 fixed bug in iPhone support 1.22 (2010-07-10) removed image *writing* support stbi_info support from Jetro Lauha GIF support from Jean-Marc Lienher iPhone PNG-extensions from James Brown warning-fixes from Nicolas Schulz and Janez Zemva (i.stbi__err. Janez (U+017D)emva) 1.21 fix use of 'stbi_uc' in header (reported by jon blow) 1.20 added support for Softimage PIC, by Tom Seddon 1.19 bug in interlaced PNG corruption check (found by ryg) 1.18 (2008-08-02) fix a threading bug (local mutable static) 1.17 support interlaced PNG 1.16 major bugfix - stbi__convert_format converted one too many pixels 1.15 initialize some fields for thread safety 1.14 fix threadsafe conversion bug header-file-only version (#define STBI_HEADER_FILE_ONLY before including) 1.13 threadsafe 1.12 const qualifiers in the API 1.11 Support installable IDCT, colorspace conversion routines 1.10 Fixes for 64-bit (don't use "unsigned long") optimized upsampling by Fabian "ryg" Giesen 1.09 Fix format-conversion for PSD code (bad global variables!) 1.08 Thatcher Ulrich's PSD code integrated by Nicolas Schulz 1.07 attempt to fix C++ warning/errors again 1.06 attempt to fix C++ warning/errors again 1.05 fix TGA loading to return correct *comp and use good luminance calc 1.04 default float alpha is 1, not 255; use 'void *' for stbi_image_free 1.03 bugfixes to STBI_NO_STDIO, STBI_NO_HDR 1.02 support for (subset of) HDR files, float interface for preferred access to them 1.01 fix bug: possible bug in handling right-side up bmps... not sure fix bug: the stbi__bmp_load() and stbi__tga_load() functions didn't work at all 1.00 interface to zlib that skips zlib header 0.99 correct handling of alpha in palette 0.98 TGA loader by lonesock; dynamically add loaders (untested) 0.97 jpeg errors on too large a file; also catch another malloc failure 0.96 fix detection of invalid v value - particleman@mollyrocket forum 0.95 during header scan, seek to markers in case of padding 0.94 STBI_NO_STDIO to disable stdio usage; rename all #defines the same 0.93 handle jpegtran output; verbose errors 0.92 read 4,8,16,24,32-bit BMP files of several formats 0.91 output 24-bit Windows 3.0 BMP files 0.90 fix a few more warnings; bump version number to approach 1.0 0.61 bugfixes due to Marc LeBlanc, Christopher Lloyd 0.60 fix compiling as c++ 0.59 fix warnings: merge Dave Moore's -Wall fixes 0.58 fix bug: zlib uncompressed mode len/nlen was wrong endian 0.57 fix bug: jpg last huffman symbol before marker was >9 bits but less than 16 available 0.56 fix bug: zlib uncompressed mode len vs. nlen 0.55 fix bug: restart_interval not initialized to 0 0.54 allow NULL for 'int *comp' 0.53 fix bug in png 3->4; speedup png decoding 0.52 png handles req_comp=3,4 directly; minor cleanup; jpeg comments 0.51 obey req_comp requests, 1-component jpegs return as 1-component, on 'test' only check type, not whether we support this variant 0.50 (2006-11-19) first released version */ /* ------------------------------------------------------------------------------ This software is available under 2 licenses -- choose whichever you prefer. ------------------------------------------------------------------------------ ALTERNATIVE A - MIT License Copyright (c) 2017 Sean Barrett Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ------------------------------------------------------------------------------ ALTERNATIVE B - Public Domain (www.unlicense.org) This is free and unencumbered software released into the public domain. Anyone is free to copy, modify, publish, use, compile, sell, or distribute this software, either in source code form or as a compiled binary, for any purpose, commercial or non-commercial, and by any means. In jurisdictions that recognize copyright laws, the author or authors of this software dedicate any and all copyright interest in the software to the public domain. We make this dedication for the benefit of the public at large and to the detriment of our heirs and successors. We intend this dedication to be an overt act of relinquishment in perpetuity of all present and future rights to this software under copyright law. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ------------------------------------------------------------------------------ */ sammycage-plutovg-5695a71/source/plutovg-stb-truetype.h000066400000000000000000006045411510714322500232710ustar00rootroot00000000000000// stb_truetype.h - v1.26 - public domain // authored from 2009-2021 by Sean Barrett / RAD Game Tools // // ======================================================================= // // NO SECURITY GUARANTEE -- DO NOT USE THIS ON UNTRUSTED FONT FILES // // This library does no range checking of the offsets found in the file, // meaning an attacker can use it to read arbitrary memory. // // ======================================================================= // // This library processes TrueType files: // parse files // extract glyph metrics // extract glyph shapes // render glyphs to one-channel bitmaps with antialiasing (box filter) // render glyphs to one-channel SDF bitmaps (signed-distance field/function) // // Todo: // non-MS cmaps // crashproof on bad data // hinting? (no longer patented) // cleartype-style AA? // optimize: use simple memory allocator for intermediates // optimize: build edge-list directly from curves // optimize: rasterize directly from curves? // // ADDITIONAL CONTRIBUTORS // // Mikko Mononen: compound shape support, more cmap formats // Tor Andersson: kerning, subpixel rendering // Dougall Johnson: OpenType / Type 2 font handling // Daniel Ribeiro Maciel: basic GPOS-based kerning // // Misc other: // Ryan Gordon // Simon Glass // github:IntellectualKitty // Imanol Celaya // Daniel Ribeiro Maciel // // Bug/warning reports/fixes: // "Zer" on mollyrocket Fabian "ryg" Giesen github:NiLuJe // Cass Everitt Martins Mozeiko github:aloucks // stoiko (Haemimont Games) Cap Petschulat github:oyvindjam // Brian Hook Omar Cornut github:vassvik // Walter van Niftrik Ryan Griege // David Gow Peter LaValle // David Given Sergey Popov // Ivan-Assen Ivanov Giumo X. Clanjor // Anthony Pesch Higor Euripedes // Johan Duparc Thomas Fields // Hou Qiming Derek Vinyard // Rob Loach Cort Stratton // Kenney Phillis Jr. Brian Costabile // Ken Voskuil (kaesve) // // VERSION HISTORY // // 1.26 (2021-08-28) fix broken rasterizer // 1.25 (2021-07-11) many fixes // 1.24 (2020-02-05) fix warning // 1.23 (2020-02-02) query SVG data for glyphs; query whole kerning table (but only kern not GPOS) // 1.22 (2019-08-11) minimize missing-glyph duplication; fix kerning if both 'GPOS' and 'kern' are defined // 1.21 (2019-02-25) fix warning // 1.20 (2019-02-07) PackFontRange skips missing codepoints; GetScaleFontVMetrics() // 1.19 (2018-02-11) GPOS kerning, STBTT_fmod // 1.18 (2018-01-29) add missing function // 1.17 (2017-07-23) make more arguments const; doc fix // 1.16 (2017-07-12) SDF support // 1.15 (2017-03-03) make more arguments const // 1.14 (2017-01-16) num-fonts-in-TTC function // 1.13 (2017-01-02) support OpenType fonts, certain Apple fonts // 1.12 (2016-10-25) suppress warnings about casting away const with -Wcast-qual // 1.11 (2016-04-02) fix unused-variable warning // 1.10 (2016-04-02) user-defined fabs(); rare memory leak; remove duplicate typedef // 1.09 (2016-01-16) warning fix; avoid crash on outofmem; use allocation userdata properly // 1.08 (2015-09-13) document stbtt_Rasterize(); fixes for vertical & horizontal edges // 1.07 (2015-08-01) allow PackFontRanges to accept arrays of sparse codepoints; // variant PackFontRanges to pack and render in separate phases; // fix stbtt_GetFontOFfsetForIndex (never worked for non-0 input?); // fixed an assert() bug in the new rasterizer // replace assert() with STBTT_assert() in new rasterizer // // Full history can be found at the end of this file. // // LICENSE // // See end of file for license information. // // USAGE // // Include this file in whatever places need to refer to it. In ONE C/C++ // file, write: // #define STB_TRUETYPE_IMPLEMENTATION // before the #include of this file. This expands out the actual // implementation into that C/C++ file. // // To make the implementation private to the file that generates the implementation, // #define STBTT_STATIC // // Simple 3D API (don't ship this, but it's fine for tools and quick start) // stbtt_BakeFontBitmap() -- bake a font to a bitmap for use as texture // stbtt_GetBakedQuad() -- compute quad to draw for a given char // // Improved 3D API (more shippable): // #include "stb_rect_pack.h" -- optional, but you really want it // stbtt_PackBegin() // stbtt_PackSetOversampling() -- for improved quality on small fonts // stbtt_PackFontRanges() -- pack and renders // stbtt_PackEnd() // stbtt_GetPackedQuad() // // "Load" a font file from a memory buffer (you have to keep the buffer loaded) // stbtt_InitFont() // stbtt_GetFontOffsetForIndex() -- indexing for TTC font collections // stbtt_GetNumberOfFonts() -- number of fonts for TTC font collections // // Render a unicode codepoint to a bitmap // stbtt_GetCodepointBitmap() -- allocates and returns a bitmap // stbtt_MakeCodepointBitmap() -- renders into bitmap you provide // stbtt_GetCodepointBitmapBox() -- how big the bitmap must be // // Character advance/positioning // stbtt_GetCodepointHMetrics() // stbtt_GetFontVMetrics() // stbtt_GetFontVMetricsOS2() // stbtt_GetCodepointKernAdvance() // // Starting with version 1.06, the rasterizer was replaced with a new, // faster and generally-more-precise rasterizer. The new rasterizer more // accurately measures pixel coverage for anti-aliasing, except in the case // where multiple shapes overlap, in which case it overestimates the AA pixel // coverage. Thus, anti-aliasing of intersecting shapes may look wrong. If // this turns out to be a problem, you can re-enable the old rasterizer with // #define STBTT_RASTERIZER_VERSION 1 // which will incur about a 15% speed hit. // // ADDITIONAL DOCUMENTATION // // Immediately after this block comment are a series of sample programs. // // After the sample programs is the "header file" section. This section // includes documentation for each API function. // // Some important concepts to understand to use this library: // // Codepoint // Characters are defined by unicode codepoints, e.g. 65 is // uppercase A, 231 is lowercase c with a cedilla, 0x7e30 is // the hiragana for "ma". // // Glyph // A visual character shape (every codepoint is rendered as // some glyph) // // Glyph index // A font-specific integer ID representing a glyph // // Baseline // Glyph shapes are defined relative to a baseline, which is the // bottom of uppercase characters. Characters extend both above // and below the baseline. // // Current Point // As you draw text to the screen, you keep track of a "current point" // which is the origin of each character. The current point's vertical // position is the baseline. Even "baked fonts" use this model. // // Vertical Font Metrics // The vertical qualities of the font, used to vertically position // and space the characters. See docs for stbtt_GetFontVMetrics. // // Font Size in Pixels or Points // The preferred interface for specifying font sizes in stb_truetype // is to specify how tall the font's vertical extent should be in pixels. // If that sounds good enough, skip the next paragraph. // // Most font APIs instead use "points", which are a common typographic // measurement for describing font size, defined as 72 points per inch. // stb_truetype provides a point API for compatibility. However, true // "per inch" conventions don't make much sense on computer displays // since different monitors have different number of pixels per // inch. For example, Windows traditionally uses a convention that // there are 96 pixels per inch, thus making 'inch' measurements have // nothing to do with inches, and thus effectively defining a point to // be 1.333 pixels. Additionally, the TrueType font data provides // an explicit scale factor to scale a given font's glyphs to points, // but the author has observed that this scale factor is often wrong // for non-commercial fonts, thus making fonts scaled in points // according to the TrueType spec incoherently sized in practice. // // DETAILED USAGE: // // Scale: // Select how high you want the font to be, in points or pixels. // Call ScaleForPixelHeight or ScaleForMappingEmToPixels to compute // a scale factor SF that will be used by all other functions. // // Baseline: // You need to select a y-coordinate that is the baseline of where // your text will appear. Call GetFontBoundingBox to get the baseline-relative // bounding box for all characters. SF*-y0 will be the distance in pixels // that the worst-case character could extend above the baseline, so if // you want the top edge of characters to appear at the top of the // screen where y=0, then you would set the baseline to SF*-y0. // // Current point: // Set the current point where the first character will appear. The // first character could extend left of the current point; this is font // dependent. You can either choose a current point that is the leftmost // point and hope, or add some padding, or check the bounding box or // left-side-bearing of the first character to be displayed and set // the current point based on that. // // Displaying a character: // Compute the bounding box of the character. It will contain signed values // relative to . I.e. if it returns x0,y0,x1,y1, // then the character should be displayed in the rectangle from // to = 32 && *text < 128) { stbtt_aligned_quad q; stbtt_GetBakedQuad(cdata, 512,512, *text-32, &x,&y,&q,1);//1=opengl & d3d10+,0=d3d9 glTexCoord2f(q.s0,q.t0); glVertex2f(q.x0,q.y0); glTexCoord2f(q.s1,q.t0); glVertex2f(q.x1,q.y0); glTexCoord2f(q.s1,q.t1); glVertex2f(q.x1,q.y1); glTexCoord2f(q.s0,q.t1); glVertex2f(q.x0,q.y1); } ++text; } glEnd(); } #endif // // ////////////////////////////////////////////////////////////////////////////// // // Complete program (this compiles): get a single bitmap, print as ASCII art // #if 0 #include #define STB_TRUETYPE_IMPLEMENTATION // force following include to generate implementation #include "stb_truetype.h" char ttf_buffer[1<<25]; int main(int argc, char **argv) { stbtt_fontinfo font; unsigned char *bitmap; int w,h,i,j,c = (argc > 1 ? atoi(argv[1]) : 'a'), s = (argc > 2 ? atoi(argv[2]) : 20); fread(ttf_buffer, 1, 1<<25, fopen(argc > 3 ? argv[3] : "c:/windows/fonts/arialbd.ttf", "rb")); stbtt_InitFont(&font, ttf_buffer, stbtt_GetFontOffsetForIndex(ttf_buffer,0)); bitmap = stbtt_GetCodepointBitmap(&font, 0,stbtt_ScaleForPixelHeight(&font, s), c, &w, &h, 0,0); for (j=0; j < h; ++j) { for (i=0; i < w; ++i) putchar(" .:ioVM@"[bitmap[j*w+i]>>5]); putchar('\n'); } return 0; } #endif // // Output: // // .ii. // @@@@@@. // V@Mio@@o // :i. V@V // :oM@@M // :@@@MM@M // @@o o@M // :@@. M@M // @@@o@@@@ // :M@@V:@@. // ////////////////////////////////////////////////////////////////////////////// // // Complete program: print "Hello World!" banner, with bugs // #if 0 char buffer[24<<20]; unsigned char screen[20][79]; int main(int arg, char **argv) { stbtt_fontinfo font; int i,j,ascent,baseline,ch=0; float scale, xpos=2; // leave a little padding in case the character extends left char *text = "Heljo World!"; // intentionally misspelled to show 'lj' brokenness fread(buffer, 1, 1000000, fopen("c:/windows/fonts/arialbd.ttf", "rb")); stbtt_InitFont(&font, buffer, 0); scale = stbtt_ScaleForPixelHeight(&font, 15); stbtt_GetFontVMetrics(&font, &ascent,0,0); baseline = (int) (ascent*scale); while (text[ch]) { int advance,lsb,x0,y0,x1,y1; float x_shift = xpos - (float) floor(xpos); stbtt_GetCodepointHMetrics(&font, text[ch], &advance, &lsb); stbtt_GetCodepointBitmapBoxSubpixel(&font, text[ch], scale,scale,x_shift,0, &x0,&y0,&x1,&y1); stbtt_MakeCodepointBitmapSubpixel(&font, &screen[baseline + y0][(int) xpos + x0], x1-x0,y1-y0, 79, scale,scale,x_shift,0, text[ch]); // note that this stomps the old data, so where character boxes overlap (e.g. 'lj') it's wrong // because this API is really for baking character bitmaps into textures. if you want to render // a sequence of characters, you really need to render each bitmap to a temp buffer, then // "alpha blend" that into the working buffer xpos += (advance * scale); if (text[ch+1]) xpos += scale*stbtt_GetCodepointKernAdvance(&font, text[ch],text[ch+1]); ++ch; } for (j=0; j < 20; ++j) { for (i=0; i < 78; ++i) putchar(" .:ioVM@"[screen[j][i]>>5]); putchar('\n'); } return 0; } #endif ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// //// //// INTEGRATION WITH YOUR CODEBASE //// //// The following sections allow you to supply alternate definitions //// of C library functions used by stb_truetype, e.g. if you don't //// link with the C runtime library. #ifdef STB_TRUETYPE_IMPLEMENTATION // #define your own (u)stbtt_int8/16/32 before including to override this #ifndef stbtt_uint8 typedef unsigned char stbtt_uint8; typedef signed char stbtt_int8; typedef unsigned short stbtt_uint16; typedef signed short stbtt_int16; typedef unsigned int stbtt_uint32; typedef signed int stbtt_int32; #endif typedef char stbtt__check_size32[sizeof(stbtt_int32)==4 ? 1 : -1]; typedef char stbtt__check_size16[sizeof(stbtt_int16)==2 ? 1 : -1]; // e.g. #define your own STBTT_ifloor/STBTT_iceil() to avoid math.h #ifndef STBTT_ifloor #include #define STBTT_ifloor(x) ((int) floor(x)) #define STBTT_iceil(x) ((int) ceil(x)) #endif #ifndef STBTT_sqrt #include #define STBTT_sqrt(x) sqrt(x) #define STBTT_pow(x,y) pow(x,y) #endif #ifndef STBTT_fmod #include #define STBTT_fmod(x,y) fmod(x,y) #endif #ifndef STBTT_cos #include #define STBTT_cos(x) cos(x) #define STBTT_acos(x) acos(x) #endif #ifndef STBTT_fabs #include #define STBTT_fabs(x) fabs(x) #endif // #define your own functions "STBTT_malloc" / "STBTT_free" to avoid malloc.h #ifndef STBTT_malloc #include #define STBTT_malloc(x,u) ((void)(u),malloc(x)) #define STBTT_free(x,u) ((void)(u),free(x)) #endif #ifndef STBTT_assert #include #define STBTT_assert(x) assert(x) #endif #ifndef STBTT_strlen #include #define STBTT_strlen(x) strlen(x) #endif #ifndef STBTT_memcpy #include #define STBTT_memcpy memcpy #define STBTT_memset memset #endif #endif /////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// //// //// INTERFACE //// //// #ifndef PLUTOVG_STB_TRUETYPE_H #define PLUTOVG_STB_TRUETYPE_H #ifdef STBTT_STATIC #define STBTT_DEF static #else #define STBTT_DEF extern #endif #ifdef __cplusplus extern "C" { #endif // private structure typedef struct { unsigned char *data; int cursor; int size; } stbtt__buf; ////////////////////////////////////////////////////////////////////////////// // // TEXTURE BAKING API // // If you use this API, you only have to call two functions ever. // typedef struct { unsigned short x0,y0,x1,y1; // coordinates of bbox in bitmap float xoff,yoff,xadvance; } stbtt_bakedchar; STBTT_DEF int stbtt_BakeFontBitmap(const unsigned char *data, int offset, // font location (use offset=0 for plain .ttf) float pixel_height, // height of font in pixels unsigned char *pixels, int pw, int ph, // bitmap to be filled in int first_char, int num_chars, // characters to bake stbtt_bakedchar *chardata); // you allocate this, it's num_chars long // if return is positive, the first unused row of the bitmap // if return is negative, returns the negative of the number of characters that fit // if return is 0, no characters fit and no rows were used // This uses a very crappy packing. typedef struct { float x0,y0,s0,t0; // top-left float x1,y1,s1,t1; // bottom-right } stbtt_aligned_quad; STBTT_DEF void stbtt_GetBakedQuad(const stbtt_bakedchar *chardata, int pw, int ph, // same data as above int char_index, // character to display float *xpos, float *ypos, // pointers to current position in screen pixel space stbtt_aligned_quad *q, // output: quad to draw int opengl_fillrule); // true if opengl fill rule; false if DX9 or earlier // Call GetBakedQuad with char_index = 'character - first_char', and it // creates the quad you need to draw and advances the current position. // // The coordinate system used assumes y increases downwards. // // Characters will extend both above and below the current position; // see discussion of "BASELINE" above. // // It's inefficient; you might want to c&p it and optimize it. STBTT_DEF void stbtt_GetScaledFontVMetrics(const unsigned char *fontdata, int index, float size, float *ascent, float *descent, float *lineGap); // Query the font vertical metrics without having to create a font first. ////////////////////////////////////////////////////////////////////////////// // // NEW TEXTURE BAKING API // // This provides options for packing multiple fonts into one atlas, not // perfectly but better than nothing. typedef struct { unsigned short x0,y0,x1,y1; // coordinates of bbox in bitmap float xoff,yoff,xadvance; float xoff2,yoff2; } stbtt_packedchar; typedef struct stbtt_pack_context stbtt_pack_context; typedef struct stbtt_fontinfo stbtt_fontinfo; #ifndef STB_RECT_PACK_VERSION typedef struct stbrp_rect stbrp_rect; #endif STBTT_DEF int stbtt_PackBegin(stbtt_pack_context *spc, unsigned char *pixels, int width, int height, int stride_in_bytes, int padding, void *alloc_context); // Initializes a packing context stored in the passed-in stbtt_pack_context. // Future calls using this context will pack characters into the bitmap passed // in here: a 1-channel bitmap that is width * height. stride_in_bytes is // the distance from one row to the next (or 0 to mean they are packed tightly // together). "padding" is the amount of padding to leave between each // character (normally you want '1' for bitmaps you'll use as textures with // bilinear filtering). // // Returns 0 on failure, 1 on success. STBTT_DEF void stbtt_PackEnd (stbtt_pack_context *spc); // Cleans up the packing context and frees all memory. #define STBTT_POINT_SIZE(x) (-(x)) STBTT_DEF int stbtt_PackFontRange(stbtt_pack_context *spc, const unsigned char *fontdata, int font_index, float font_size, int first_unicode_char_in_range, int num_chars_in_range, stbtt_packedchar *chardata_for_range); // Creates character bitmaps from the font_index'th font found in fontdata (use // font_index=0 if you don't know what that is). It creates num_chars_in_range // bitmaps for characters with unicode values starting at first_unicode_char_in_range // and increasing. Data for how to render them is stored in chardata_for_range; // pass these to stbtt_GetPackedQuad to get back renderable quads. // // font_size is the full height of the character from ascender to descender, // as computed by stbtt_ScaleForPixelHeight. To use a point size as computed // by stbtt_ScaleForMappingEmToPixels, wrap the point size in STBTT_POINT_SIZE() // and pass that result as 'font_size': // ..., 20 , ... // font max minus min y is 20 pixels tall // ..., STBTT_POINT_SIZE(20), ... // 'M' is 20 pixels tall typedef struct { float font_size; int first_unicode_codepoint_in_range; // if non-zero, then the chars are continuous, and this is the first codepoint int *array_of_unicode_codepoints; // if non-zero, then this is an array of unicode codepoints int num_chars; stbtt_packedchar *chardata_for_range; // output unsigned char h_oversample, v_oversample; // don't set these, they're used internally } stbtt_pack_range; STBTT_DEF int stbtt_PackFontRanges(stbtt_pack_context *spc, const unsigned char *fontdata, int font_index, stbtt_pack_range *ranges, int num_ranges); // Creates character bitmaps from multiple ranges of characters stored in // ranges. This will usually create a better-packed bitmap than multiple // calls to stbtt_PackFontRange. Note that you can call this multiple // times within a single PackBegin/PackEnd. STBTT_DEF void stbtt_PackSetOversampling(stbtt_pack_context *spc, unsigned int h_oversample, unsigned int v_oversample); // Oversampling a font increases the quality by allowing higher-quality subpixel // positioning, and is especially valuable at smaller text sizes. // // This function sets the amount of oversampling for all following calls to // stbtt_PackFontRange(s) or stbtt_PackFontRangesGatherRects for a given // pack context. The default (no oversampling) is achieved by h_oversample=1 // and v_oversample=1. The total number of pixels required is // h_oversample*v_oversample larger than the default; for example, 2x2 // oversampling requires 4x the storage of 1x1. For best results, render // oversampled textures with bilinear filtering. Look at the readme in // stb/tests/oversample for information about oversampled fonts // // To use with PackFontRangesGather etc., you must set it before calls // call to PackFontRangesGatherRects. STBTT_DEF void stbtt_PackSetSkipMissingCodepoints(stbtt_pack_context *spc, int skip); // If skip != 0, this tells stb_truetype to skip any codepoints for which // there is no corresponding glyph. If skip=0, which is the default, then // codepoints without a glyph recived the font's "missing character" glyph, // typically an empty box by convention. STBTT_DEF void stbtt_GetPackedQuad(const stbtt_packedchar *chardata, int pw, int ph, // same data as above int char_index, // character to display float *xpos, float *ypos, // pointers to current position in screen pixel space stbtt_aligned_quad *q, // output: quad to draw int align_to_integer); STBTT_DEF int stbtt_PackFontRangesGatherRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects); STBTT_DEF void stbtt_PackFontRangesPackRects(stbtt_pack_context *spc, stbrp_rect *rects, int num_rects); STBTT_DEF int stbtt_PackFontRangesRenderIntoRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects); // Calling these functions in sequence is roughly equivalent to calling // stbtt_PackFontRanges(). If you more control over the packing of multiple // fonts, or if you want to pack custom data into a font texture, take a look // at the source to of stbtt_PackFontRanges() and create a custom version // using these functions, e.g. call GatherRects multiple times, // building up a single array of rects, then call PackRects once, // then call RenderIntoRects repeatedly. This may result in a // better packing than calling PackFontRanges multiple times // (or it may not). // this is an opaque structure that you shouldn't mess with which holds // all the context needed from PackBegin to PackEnd. struct stbtt_pack_context { void *user_allocator_context; void *pack_info; int width; int height; int stride_in_bytes; int padding; int skip_missing; unsigned int h_oversample, v_oversample; unsigned char *pixels; void *nodes; }; ////////////////////////////////////////////////////////////////////////////// // // FONT LOADING // // STBTT_DEF int stbtt_GetNumberOfFonts(const unsigned char *data); // This function will determine the number of fonts in a font file. TrueType // collection (.ttc) files may contain multiple fonts, while TrueType font // (.ttf) files only contain one font. The number of fonts can be used for // indexing with the previous function where the index is between zero and one // less than the total fonts. If an error occurs, -1 is returned. STBTT_DEF int stbtt_GetFontOffsetForIndex(const unsigned char *data, int index); // Each .ttf/.ttc file may have more than one font. Each font has a sequential // index number starting from 0. Call this function to get the font offset for // a given index; it returns -1 if the index is out of range. A regular .ttf // file will only define one font and it always be at offset 0, so it will // return '0' for index 0, and -1 for all other indices. // The following structure is defined publicly so you can declare one on // the stack or as a global or etc, but you should treat it as opaque. struct stbtt_fontinfo { void * userdata; unsigned char * data; // pointer to .ttf file int fontstart; // offset of start of font int numGlyphs; // number of glyphs, needed for range checking int loca,head,glyf,hhea,hmtx,kern,gpos,svg; // table locations as offset from start of .ttf int index_map; // a cmap mapping for our chosen character encoding int indexToLocFormat; // format needed to map from glyph index to glyph stbtt__buf cff; // cff font data stbtt__buf charstrings; // the charstring index stbtt__buf gsubrs; // global charstring subroutines index stbtt__buf subrs; // private charstring subroutines index stbtt__buf fontdicts; // array of font dicts stbtt__buf fdselect; // map from glyph to fontdict }; STBTT_DEF int stbtt_InitFont(stbtt_fontinfo *info, const unsigned char *data, int offset); // Given an offset into the file that defines a font, this function builds // the necessary cached info for the rest of the system. You must allocate // the stbtt_fontinfo yourself, and stbtt_InitFont will fill it out. You don't // need to do anything special to free it, because the contents are pure // value data with no additional data structures. Returns 0 on failure. ////////////////////////////////////////////////////////////////////////////// // // CHARACTER TO GLYPH-INDEX CONVERSIOn STBTT_DEF int stbtt_FindGlyphIndex(const stbtt_fontinfo *info, int unicode_codepoint); // If you're going to perform multiple operations on the same character // and you want a speed-up, call this function with the character you're // going to process, then use glyph-based functions instead of the // codepoint-based functions. // Returns 0 if the character codepoint is not defined in the font. ////////////////////////////////////////////////////////////////////////////// // // CHARACTER PROPERTIES // STBTT_DEF float stbtt_ScaleForPixelHeight(const stbtt_fontinfo *info, float pixels); // computes a scale factor to produce a font whose "height" is 'pixels' tall. // Height is measured as the distance from the highest ascender to the lowest // descender; in other words, it's equivalent to calling stbtt_GetFontVMetrics // and computing: // scale = pixels / (ascent - descent) // so if you prefer to measure height by the ascent only, use a similar calculation. STBTT_DEF float stbtt_ScaleForMappingEmToPixels(const stbtt_fontinfo *info, float pixels); // computes a scale factor to produce a font whose EM size is mapped to // 'pixels' tall. This is probably what traditional APIs compute, but // I'm not positive. STBTT_DEF void stbtt_GetFontVMetrics(const stbtt_fontinfo *info, int *ascent, int *descent, int *lineGap); // ascent is the coordinate above the baseline the font extends; descent // is the coordinate below the baseline the font extends (i.e. it is typically negative) // lineGap is the spacing between one row's descent and the next row's ascent... // so you should advance the vertical position by "*ascent - *descent + *lineGap" // these are expressed in unscaled coordinates, so you must multiply by // the scale factor for a given size STBTT_DEF int stbtt_GetFontVMetricsOS2(const stbtt_fontinfo *info, int *typoAscent, int *typoDescent, int *typoLineGap); // analogous to GetFontVMetrics, but returns the "typographic" values from the OS/2 // table (specific to MS/Windows TTF files). // // Returns 1 on success (table present), 0 on failure. STBTT_DEF void stbtt_GetFontBoundingBox(const stbtt_fontinfo *info, int *x0, int *y0, int *x1, int *y1); // the bounding box around all possible characters STBTT_DEF void stbtt_GetCodepointHMetrics(const stbtt_fontinfo *info, int codepoint, int *advanceWidth, int *leftSideBearing); // leftSideBearing is the offset from the current horizontal position to the left edge of the character // advanceWidth is the offset from the current horizontal position to the next horizontal position // these are expressed in unscaled coordinates STBTT_DEF int stbtt_GetCodepointKernAdvance(const stbtt_fontinfo *info, int ch1, int ch2); // an additional amount to add to the 'advance' value between ch1 and ch2 STBTT_DEF int stbtt_GetCodepointBox(const stbtt_fontinfo *info, int codepoint, int *x0, int *y0, int *x1, int *y1); // Gets the bounding box of the visible part of the glyph, in unscaled coordinates STBTT_DEF void stbtt_GetGlyphHMetrics(const stbtt_fontinfo *info, int glyph_index, int *advanceWidth, int *leftSideBearing); STBTT_DEF int stbtt_GetGlyphKernAdvance(const stbtt_fontinfo *info, int glyph1, int glyph2); STBTT_DEF int stbtt_GetGlyphBox(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1); // as above, but takes one or more glyph indices for greater efficiency typedef struct stbtt_kerningentry { int glyph1; // use stbtt_FindGlyphIndex int glyph2; int advance; } stbtt_kerningentry; STBTT_DEF int stbtt_GetKerningTableLength(const stbtt_fontinfo *info); STBTT_DEF int stbtt_GetKerningTable(const stbtt_fontinfo *info, stbtt_kerningentry* table, int table_length); // Retrieves a complete list of all of the kerning pairs provided by the font // stbtt_GetKerningTable never writes more than table_length entries and returns how many entries it did write. // The table will be sorted by (a.glyph1 == b.glyph1)?(a.glyph2 < b.glyph2):(a.glyph1 < b.glyph1) ////////////////////////////////////////////////////////////////////////////// // // GLYPH SHAPES (you probably don't need these, but they have to go before // the bitmaps for C declaration-order reasons) // #ifndef STBTT_vmove // you can predefine these to use different values (but why?) enum { STBTT_vmove=1, STBTT_vline, STBTT_vcurve, STBTT_vcubic }; #endif #ifndef stbtt_vertex // you can predefine this to use different values // (we share this with other code at RAD) #define stbtt_vertex_type short // can't use stbtt_int16 because that's not visible in the header file typedef struct { stbtt_vertex_type x,y,cx,cy,cx1,cy1; unsigned char type,padding; } stbtt_vertex; #endif STBTT_DEF int stbtt_IsGlyphEmpty(const stbtt_fontinfo *info, int glyph_index); // returns non-zero if nothing is drawn for this glyph STBTT_DEF int stbtt_GetCodepointShape(const stbtt_fontinfo *info, int unicode_codepoint, stbtt_vertex **vertices); STBTT_DEF int stbtt_GetGlyphShape(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **vertices); // returns # of vertices and fills *vertices with the pointer to them // these are expressed in "unscaled" coordinates // // The shape is a series of contours. Each one starts with // a STBTT_moveto, then consists of a series of mixed // STBTT_lineto and STBTT_curveto segments. A lineto // draws a line from previous endpoint to its x,y; a curveto // draws a quadratic bezier from previous endpoint to // its x,y, using cx,cy as the bezier control point. STBTT_DEF void stbtt_FreeShape(const stbtt_fontinfo *info, stbtt_vertex *vertices); // frees the data allocated above STBTT_DEF unsigned char *stbtt_FindSVGDoc(const stbtt_fontinfo *info, int gl); STBTT_DEF int stbtt_GetCodepointSVG(const stbtt_fontinfo *info, int unicode_codepoint, const char **svg); STBTT_DEF int stbtt_GetGlyphSVG(const stbtt_fontinfo *info, int gl, const char **svg); // fills svg with the character's SVG data. // returns data size or 0 if SVG not found. ////////////////////////////////////////////////////////////////////////////// // // BITMAP RENDERING // STBTT_DEF void stbtt_FreeBitmap(unsigned char *bitmap, void *userdata); // frees the bitmap allocated below STBTT_DEF unsigned char *stbtt_GetCodepointBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int codepoint, int *width, int *height, int *xoff, int *yoff); // allocates a large-enough single-channel 8bpp bitmap and renders the // specified character/glyph at the specified scale into it, with // antialiasing. 0 is no coverage (transparent), 255 is fully covered (opaque). // *width & *height are filled out with the width & height of the bitmap, // which is stored left-to-right, top-to-bottom. // // xoff/yoff are the offset it pixel space from the glyph origin to the top-left of the bitmap STBTT_DEF unsigned char *stbtt_GetCodepointBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint, int *width, int *height, int *xoff, int *yoff); // the same as stbtt_GetCodepoitnBitmap, but you can specify a subpixel // shift for the character STBTT_DEF void stbtt_MakeCodepointBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int codepoint); // the same as stbtt_GetCodepointBitmap, but you pass in storage for the bitmap // in the form of 'output', with row spacing of 'out_stride' bytes. the bitmap // is clipped to out_w/out_h bytes. Call stbtt_GetCodepointBitmapBox to get the // width and height and positioning info for it first. STBTT_DEF void stbtt_MakeCodepointBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint); // same as stbtt_MakeCodepointBitmap, but you can specify a subpixel // shift for the character STBTT_DEF void stbtt_MakeCodepointBitmapSubpixelPrefilter(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int oversample_x, int oversample_y, float *sub_x, float *sub_y, int codepoint); // same as stbtt_MakeCodepointBitmapSubpixel, but prefiltering // is performed (see stbtt_PackSetOversampling) STBTT_DEF void stbtt_GetCodepointBitmapBox(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1); // get the bbox of the bitmap centered around the glyph origin; so the // bitmap width is ix1-ix0, height is iy1-iy0, and location to place // the bitmap top left is (leftSideBearing*scale,iy0). // (Note that the bitmap uses y-increases-down, but the shape uses // y-increases-up, so CodepointBitmapBox and CodepointBox are inverted.) STBTT_DEF void stbtt_GetCodepointBitmapBoxSubpixel(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1); // same as stbtt_GetCodepointBitmapBox, but you can specify a subpixel // shift for the character // the following functions are equivalent to the above functions, but operate // on glyph indices instead of Unicode codepoints (for efficiency) STBTT_DEF unsigned char *stbtt_GetGlyphBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int glyph, int *width, int *height, int *xoff, int *yoff); STBTT_DEF unsigned char *stbtt_GetGlyphBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int glyph, int *width, int *height, int *xoff, int *yoff); STBTT_DEF void stbtt_MakeGlyphBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int glyph); STBTT_DEF void stbtt_MakeGlyphBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int glyph); STBTT_DEF void stbtt_MakeGlyphBitmapSubpixelPrefilter(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int oversample_x, int oversample_y, float *sub_x, float *sub_y, int glyph); STBTT_DEF void stbtt_GetGlyphBitmapBox(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1); STBTT_DEF void stbtt_GetGlyphBitmapBoxSubpixel(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y,float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1); // @TODO: don't expose this structure typedef struct { int w,h,stride; unsigned char *pixels; } stbtt__bitmap; // rasterize a shape with quadratic beziers into a bitmap STBTT_DEF void stbtt_Rasterize(stbtt__bitmap *result, // 1-channel bitmap to draw into float flatness_in_pixels, // allowable error of curve in pixels stbtt_vertex *vertices, // array of vertices defining shape int num_verts, // number of vertices in above array float scale_x, float scale_y, // scale applied to input vertices float shift_x, float shift_y, // translation applied to input vertices int x_off, int y_off, // another translation applied to input int invert, // if non-zero, vertically flip shape void *userdata); // context for to STBTT_MALLOC ////////////////////////////////////////////////////////////////////////////// // // Signed Distance Function (or Field) rendering STBTT_DEF void stbtt_FreeSDF(unsigned char *bitmap, void *userdata); // frees the SDF bitmap allocated below STBTT_DEF unsigned char * stbtt_GetGlyphSDF(const stbtt_fontinfo *info, float scale, int glyph, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff); STBTT_DEF unsigned char * stbtt_GetCodepointSDF(const stbtt_fontinfo *info, float scale, int codepoint, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff); // These functions compute a discretized SDF field for a single character, suitable for storing // in a single-channel texture, sampling with bilinear filtering, and testing against // larger than some threshold to produce scalable fonts. // info -- the font // scale -- controls the size of the resulting SDF bitmap, same as it would be creating a regular bitmap // glyph/codepoint -- the character to generate the SDF for // padding -- extra "pixels" around the character which are filled with the distance to the character (not 0), // which allows effects like bit outlines // onedge_value -- value 0-255 to test the SDF against to reconstruct the character (i.e. the isocontour of the character) // pixel_dist_scale -- what value the SDF should increase by when moving one SDF "pixel" away from the edge (on the 0..255 scale) // if positive, > onedge_value is inside; if negative, < onedge_value is inside // width,height -- output height & width of the SDF bitmap (including padding) // xoff,yoff -- output origin of the character // return value -- a 2D array of bytes 0..255, width*height in size // // pixel_dist_scale & onedge_value are a scale & bias that allows you to make // optimal use of the limited 0..255 for your application, trading off precision // and special effects. SDF values outside the range 0..255 are clamped to 0..255. // // Example: // scale = stbtt_ScaleForPixelHeight(22) // padding = 5 // onedge_value = 180 // pixel_dist_scale = 180/5.0 = 36.0 // // This will create an SDF bitmap in which the character is about 22 pixels // high but the whole bitmap is about 22+5+5=32 pixels high. To produce a filled // shape, sample the SDF at each pixel and fill the pixel if the SDF value // is greater than or equal to 180/255. (You'll actually want to antialias, // which is beyond the scope of this example.) Additionally, you can compute // offset outlines (e.g. to stroke the character border inside & outside, // or only outside). For example, to fill outside the character up to 3 SDF // pixels, you would compare against (180-36.0*3)/255 = 72/255. The above // choice of variables maps a range from 5 pixels outside the shape to // 2 pixels inside the shape to 0..255; this is intended primarily for apply // outside effects only (the interior range is needed to allow proper // antialiasing of the font at *smaller* sizes) // // The function computes the SDF analytically at each SDF pixel, not by e.g. // building a higher-res bitmap and approximating it. In theory the quality // should be as high as possible for an SDF of this size & representation, but // unclear if this is true in practice (perhaps building a higher-res bitmap // and computing from that can allow drop-out prevention). // // The algorithm has not been optimized at all, so expect it to be slow // if computing lots of characters or very large sizes. ////////////////////////////////////////////////////////////////////////////// // // Finding the right font... // // You should really just solve this offline, keep your own tables // of what font is what, and don't try to get it out of the .ttf file. // That's because getting it out of the .ttf file is really hard, because // the names in the file can appear in many possible encodings, in many // possible languages, and e.g. if you need a case-insensitive comparison, // the details of that depend on the encoding & language in a complex way // (actually underspecified in truetype, but also gigantic). // // But you can use the provided functions in two possible ways: // stbtt_FindMatchingFont() will use *case-sensitive* comparisons on // unicode-encoded names to try to find the font you want; // you can run this before calling stbtt_InitFont() // // stbtt_GetFontNameString() lets you get any of the various strings // from the file yourself and do your own comparisons on them. // You have to have called stbtt_InitFont() first. STBTT_DEF int stbtt_FindMatchingFont(const unsigned char *fontdata, const char *name, int flags); // returns the offset (not index) of the font that matches, or -1 if none // if you use STBTT_MACSTYLE_DONTCARE, use a font name like "Arial Bold". // if you use any other flag, use a font name like "Arial"; this checks // the 'macStyle' header field; i don't know if fonts set this consistently #define STBTT_MACSTYLE_DONTCARE 0 #define STBTT_MACSTYLE_BOLD 1 #define STBTT_MACSTYLE_ITALIC 2 #define STBTT_MACSTYLE_UNDERSCORE 4 #define STBTT_MACSTYLE_NONE 8 // <= not same as 0, this makes us check the bitfield is 0 STBTT_DEF int stbtt_CompareUTF8toUTF16_bigendian(const char *s1, int len1, const char *s2, int len2); // returns 1/0 whether the first string interpreted as utf8 is identical to // the second string interpreted as big-endian utf16... useful for strings from next func STBTT_DEF const char *stbtt_GetFontNameString(const stbtt_fontinfo *font, int *length, int platformID, int encodingID, int languageID, int nameID); // returns the string (which may be big-endian double byte, e.g. for unicode) // and puts the length in bytes in *length. // // some of the values for the IDs are below; for more see the truetype spec: // http://developer.apple.com/textfonts/TTRefMan/RM06/Chap6name.html // http://www.microsoft.com/typography/otspec/name.htm enum { // platformID STBTT_PLATFORM_ID_UNICODE =0, STBTT_PLATFORM_ID_MAC =1, STBTT_PLATFORM_ID_ISO =2, STBTT_PLATFORM_ID_MICROSOFT =3 }; enum { // encodingID for STBTT_PLATFORM_ID_UNICODE STBTT_UNICODE_EID_UNICODE_1_0 =0, STBTT_UNICODE_EID_UNICODE_1_1 =1, STBTT_UNICODE_EID_ISO_10646 =2, STBTT_UNICODE_EID_UNICODE_2_0_BMP=3, STBTT_UNICODE_EID_UNICODE_2_0_FULL=4 }; enum { // encodingID for STBTT_PLATFORM_ID_MICROSOFT STBTT_MS_EID_SYMBOL =0, STBTT_MS_EID_UNICODE_BMP =1, STBTT_MS_EID_SHIFTJIS =2, STBTT_MS_EID_UNICODE_FULL =10 }; enum { // encodingID for STBTT_PLATFORM_ID_MAC; same as Script Manager codes STBTT_MAC_EID_ROMAN =0, STBTT_MAC_EID_ARABIC =4, STBTT_MAC_EID_JAPANESE =1, STBTT_MAC_EID_HEBREW =5, STBTT_MAC_EID_CHINESE_TRAD =2, STBTT_MAC_EID_GREEK =6, STBTT_MAC_EID_KOREAN =3, STBTT_MAC_EID_RUSSIAN =7 }; enum { // languageID for STBTT_PLATFORM_ID_MICROSOFT; same as LCID... // problematic because there are e.g. 16 english LCIDs and 16 arabic LCIDs STBTT_MS_LANG_ENGLISH =0x0409, STBTT_MS_LANG_ITALIAN =0x0410, STBTT_MS_LANG_CHINESE =0x0804, STBTT_MS_LANG_JAPANESE =0x0411, STBTT_MS_LANG_DUTCH =0x0413, STBTT_MS_LANG_KOREAN =0x0412, STBTT_MS_LANG_FRENCH =0x040c, STBTT_MS_LANG_RUSSIAN =0x0419, STBTT_MS_LANG_GERMAN =0x0407, STBTT_MS_LANG_SPANISH =0x0409, STBTT_MS_LANG_HEBREW =0x040d, STBTT_MS_LANG_SWEDISH =0x041D }; enum { // languageID for STBTT_PLATFORM_ID_MAC STBTT_MAC_LANG_ENGLISH =0 , STBTT_MAC_LANG_JAPANESE =11, STBTT_MAC_LANG_ARABIC =12, STBTT_MAC_LANG_KOREAN =23, STBTT_MAC_LANG_DUTCH =4 , STBTT_MAC_LANG_RUSSIAN =32, STBTT_MAC_LANG_FRENCH =1 , STBTT_MAC_LANG_SPANISH =6 , STBTT_MAC_LANG_GERMAN =2 , STBTT_MAC_LANG_SWEDISH =5 , STBTT_MAC_LANG_HEBREW =10, STBTT_MAC_LANG_CHINESE_SIMPLIFIED =33, STBTT_MAC_LANG_ITALIAN =3 , STBTT_MAC_LANG_CHINESE_TRAD =19 }; #ifdef __cplusplus } #endif #endif // PLUTOVG_STB_TRUETYPE_H /////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// //// //// IMPLEMENTATION //// //// #ifdef STB_TRUETYPE_IMPLEMENTATION #ifndef STBTT_MAX_OVERSAMPLE #define STBTT_MAX_OVERSAMPLE 8 #endif #if STBTT_MAX_OVERSAMPLE > 255 #error "STBTT_MAX_OVERSAMPLE cannot be > 255" #endif typedef int stbtt__test_oversample_pow2[(STBTT_MAX_OVERSAMPLE & (STBTT_MAX_OVERSAMPLE-1)) == 0 ? 1 : -1]; #ifndef STBTT_RASTERIZER_VERSION #define STBTT_RASTERIZER_VERSION 2 #endif #ifdef _MSC_VER #define STBTT__NOTUSED(v) (void)(v) #else #define STBTT__NOTUSED(v) (void)sizeof(v) #endif ////////////////////////////////////////////////////////////////////////// // // stbtt__buf helpers to parse data from file // static stbtt_uint8 stbtt__buf_get8(stbtt__buf *b) { if (b->cursor >= b->size) return 0; return b->data[b->cursor++]; } static stbtt_uint8 stbtt__buf_peek8(stbtt__buf *b) { if (b->cursor >= b->size) return 0; return b->data[b->cursor]; } static void stbtt__buf_seek(stbtt__buf *b, int o) { STBTT_assert(!(o > b->size || o < 0)); b->cursor = (o > b->size || o < 0) ? b->size : o; } static void stbtt__buf_skip(stbtt__buf *b, int o) { stbtt__buf_seek(b, b->cursor + o); } static stbtt_uint32 stbtt__buf_get(stbtt__buf *b, int n) { stbtt_uint32 v = 0; int i; STBTT_assert(n >= 1 && n <= 4); for (i = 0; i < n; i++) v = (v << 8) | stbtt__buf_get8(b); return v; } static stbtt__buf stbtt__new_buf(const void *p, size_t size) { stbtt__buf r; STBTT_assert(size < 0x40000000); r.data = (stbtt_uint8*) p; r.size = (int) size; r.cursor = 0; return r; } #define stbtt__buf_get16(b) stbtt__buf_get((b), 2) #define stbtt__buf_get32(b) stbtt__buf_get((b), 4) static stbtt__buf stbtt__buf_range(const stbtt__buf *b, int o, int s) { stbtt__buf r = stbtt__new_buf(NULL, 0); if (o < 0 || s < 0 || o > b->size || s > b->size - o) return r; r.data = b->data + o; r.size = s; return r; } static stbtt__buf stbtt__cff_get_index(stbtt__buf *b) { int count, start, offsize; start = b->cursor; count = stbtt__buf_get16(b); if (count) { offsize = stbtt__buf_get8(b); STBTT_assert(offsize >= 1 && offsize <= 4); stbtt__buf_skip(b, offsize * count); stbtt__buf_skip(b, stbtt__buf_get(b, offsize) - 1); } return stbtt__buf_range(b, start, b->cursor - start); } static stbtt_uint32 stbtt__cff_int(stbtt__buf *b) { int b0 = stbtt__buf_get8(b); if (b0 >= 32 && b0 <= 246) return b0 - 139; else if (b0 >= 247 && b0 <= 250) return (b0 - 247)*256 + stbtt__buf_get8(b) + 108; else if (b0 >= 251 && b0 <= 254) return -(b0 - 251)*256 - stbtt__buf_get8(b) - 108; else if (b0 == 28) return stbtt__buf_get16(b); else if (b0 == 29) return stbtt__buf_get32(b); STBTT_assert(0); return 0; } static void stbtt__cff_skip_operand(stbtt__buf *b) { int v, b0 = stbtt__buf_peek8(b); STBTT_assert(b0 >= 28); if (b0 == 30) { stbtt__buf_skip(b, 1); while (b->cursor < b->size) { v = stbtt__buf_get8(b); if ((v & 0xF) == 0xF || (v >> 4) == 0xF) break; } } else { stbtt__cff_int(b); } } static stbtt__buf stbtt__dict_get(stbtt__buf *b, int key) { stbtt__buf_seek(b, 0); while (b->cursor < b->size) { int start = b->cursor, end, op; while (stbtt__buf_peek8(b) >= 28) stbtt__cff_skip_operand(b); end = b->cursor; op = stbtt__buf_get8(b); if (op == 12) op = stbtt__buf_get8(b) | 0x100; if (op == key) return stbtt__buf_range(b, start, end-start); } return stbtt__buf_range(b, 0, 0); } static void stbtt__dict_get_ints(stbtt__buf *b, int key, int outcount, stbtt_uint32 *out) { int i; stbtt__buf operands = stbtt__dict_get(b, key); for (i = 0; i < outcount && operands.cursor < operands.size; i++) out[i] = stbtt__cff_int(&operands); } static int stbtt__cff_index_count(stbtt__buf *b) { stbtt__buf_seek(b, 0); return stbtt__buf_get16(b); } static stbtt__buf stbtt__cff_index_get(stbtt__buf b, int i) { int count, offsize, start, end; stbtt__buf_seek(&b, 0); count = stbtt__buf_get16(&b); offsize = stbtt__buf_get8(&b); STBTT_assert(i >= 0 && i < count); STBTT_assert(offsize >= 1 && offsize <= 4); stbtt__buf_skip(&b, i*offsize); start = stbtt__buf_get(&b, offsize); end = stbtt__buf_get(&b, offsize); return stbtt__buf_range(&b, 2+(count+1)*offsize+start, end - start); } ////////////////////////////////////////////////////////////////////////// // // accessors to parse data from file // // on platforms that don't allow misaligned reads, if we want to allow // truetype fonts that aren't padded to alignment, define ALLOW_UNALIGNED_TRUETYPE #define ttBYTE(p) (* (stbtt_uint8 *) (p)) #define ttCHAR(p) (* (stbtt_int8 *) (p)) #define ttFixed(p) ttLONG(p) static stbtt_uint16 ttUSHORT(stbtt_uint8 *p) { return p[0]*256 + p[1]; } static stbtt_int16 ttSHORT(stbtt_uint8 *p) { return p[0]*256 + p[1]; } static stbtt_uint32 ttULONG(stbtt_uint8 *p) { return (p[0]<<24) + (p[1]<<16) + (p[2]<<8) + p[3]; } static stbtt_int32 ttLONG(stbtt_uint8 *p) { return (p[0]<<24) + (p[1]<<16) + (p[2]<<8) + p[3]; } #define stbtt_tag4(p,c0,c1,c2,c3) ((p)[0] == (c0) && (p)[1] == (c1) && (p)[2] == (c2) && (p)[3] == (c3)) #define stbtt_tag(p,str) stbtt_tag4(p,str[0],str[1],str[2],str[3]) static int stbtt__isfont(stbtt_uint8 *font) { // check the version number if (stbtt_tag4(font, '1',0,0,0)) return 1; // TrueType 1 if (stbtt_tag(font, "typ1")) return 1; // TrueType with type 1 font -- we don't support this! if (stbtt_tag(font, "OTTO")) return 1; // OpenType with CFF if (stbtt_tag4(font, 0,1,0,0)) return 1; // OpenType 1.0 if (stbtt_tag(font, "true")) return 1; // Apple specification for TrueType fonts return 0; } // @OPTIMIZE: binary search static stbtt_uint32 stbtt__find_table(stbtt_uint8 *data, stbtt_uint32 fontstart, const char *tag) { stbtt_int32 num_tables = ttUSHORT(data+fontstart+4); stbtt_uint32 tabledir = fontstart + 12; stbtt_int32 i; for (i=0; i < num_tables; ++i) { stbtt_uint32 loc = tabledir + 16*i; if (stbtt_tag(data+loc+0, tag)) return ttULONG(data+loc+8); } return 0; } static int stbtt_GetFontOffsetForIndex_internal(unsigned char *font_collection, int index) { // if it's just a font, there's only one valid index if (stbtt__isfont(font_collection)) return index == 0 ? 0 : -1; // check if it's a TTC if (stbtt_tag(font_collection, "ttcf")) { // version 1? if (ttULONG(font_collection+4) == 0x00010000 || ttULONG(font_collection+4) == 0x00020000) { stbtt_int32 n = ttLONG(font_collection+8); if (index >= n) return -1; return ttULONG(font_collection+12+index*4); } } return -1; } static int stbtt_GetNumberOfFonts_internal(unsigned char *font_collection) { // if it's just a font, there's only one valid font if (stbtt__isfont(font_collection)) return 1; // check if it's a TTC if (stbtt_tag(font_collection, "ttcf")) { // version 1? if (ttULONG(font_collection+4) == 0x00010000 || ttULONG(font_collection+4) == 0x00020000) { return ttLONG(font_collection+8); } } return 0; } static stbtt__buf stbtt__get_subrs(stbtt__buf cff, stbtt__buf fontdict) { stbtt_uint32 subrsoff = 0, private_loc[2] = { 0, 0 }; stbtt__buf pdict; stbtt__dict_get_ints(&fontdict, 18, 2, private_loc); if (!private_loc[1] || !private_loc[0]) return stbtt__new_buf(NULL, 0); pdict = stbtt__buf_range(&cff, private_loc[1], private_loc[0]); stbtt__dict_get_ints(&pdict, 19, 1, &subrsoff); if (!subrsoff) return stbtt__new_buf(NULL, 0); stbtt__buf_seek(&cff, private_loc[1]+subrsoff); return stbtt__cff_get_index(&cff); } // since most people won't use this, find this table the first time it's needed static int stbtt__get_svg(stbtt_fontinfo *info) { stbtt_uint32 t; if (info->svg < 0) { t = stbtt__find_table(info->data, info->fontstart, "SVG "); if (t) { stbtt_uint32 offset = ttULONG(info->data + t + 2); info->svg = t + offset; } else { info->svg = 0; } } return info->svg; } static int stbtt_InitFont_internal(stbtt_fontinfo *info, unsigned char *data, int fontstart) { stbtt_uint32 cmap, t; stbtt_int32 i,numTables; info->data = data; info->fontstart = fontstart; info->cff = stbtt__new_buf(NULL, 0); cmap = stbtt__find_table(data, fontstart, "cmap"); // required info->loca = stbtt__find_table(data, fontstart, "loca"); // required info->head = stbtt__find_table(data, fontstart, "head"); // required info->glyf = stbtt__find_table(data, fontstart, "glyf"); // required info->hhea = stbtt__find_table(data, fontstart, "hhea"); // required info->hmtx = stbtt__find_table(data, fontstart, "hmtx"); // required info->kern = stbtt__find_table(data, fontstart, "kern"); // not required info->gpos = stbtt__find_table(data, fontstart, "GPOS"); // not required if (!cmap || !info->head || !info->hhea || !info->hmtx) return 0; if (info->glyf) { // required for truetype if (!info->loca) return 0; } else { // initialization for CFF / Type2 fonts (OTF) stbtt__buf b, topdict, topdictidx; stbtt_uint32 cstype = 2, charstrings = 0, fdarrayoff = 0, fdselectoff = 0; stbtt_uint32 cff; cff = stbtt__find_table(data, fontstart, "CFF "); if (!cff) return 0; info->fontdicts = stbtt__new_buf(NULL, 0); info->fdselect = stbtt__new_buf(NULL, 0); // @TODO this should use size from table (not 512MB) info->cff = stbtt__new_buf(data+cff, 512*1024*1024); b = info->cff; // read the header stbtt__buf_skip(&b, 2); stbtt__buf_seek(&b, stbtt__buf_get8(&b)); // hdrsize // @TODO the name INDEX could list multiple fonts, // but we just use the first one. stbtt__cff_get_index(&b); // name INDEX topdictidx = stbtt__cff_get_index(&b); topdict = stbtt__cff_index_get(topdictidx, 0); stbtt__cff_get_index(&b); // string INDEX info->gsubrs = stbtt__cff_get_index(&b); stbtt__dict_get_ints(&topdict, 17, 1, &charstrings); stbtt__dict_get_ints(&topdict, 0x100 | 6, 1, &cstype); stbtt__dict_get_ints(&topdict, 0x100 | 36, 1, &fdarrayoff); stbtt__dict_get_ints(&topdict, 0x100 | 37, 1, &fdselectoff); info->subrs = stbtt__get_subrs(b, topdict); // we only support Type 2 charstrings if (cstype != 2) return 0; if (charstrings == 0) return 0; if (fdarrayoff) { // looks like a CID font if (!fdselectoff) return 0; stbtt__buf_seek(&b, fdarrayoff); info->fontdicts = stbtt__cff_get_index(&b); info->fdselect = stbtt__buf_range(&b, fdselectoff, b.size-fdselectoff); } stbtt__buf_seek(&b, charstrings); info->charstrings = stbtt__cff_get_index(&b); } t = stbtt__find_table(data, fontstart, "maxp"); if (t) info->numGlyphs = ttUSHORT(data+t+4); else info->numGlyphs = 0xffff; info->svg = -1; // find a cmap encoding table we understand *now* to avoid searching // later. (todo: could make this installable) // the same regardless of glyph. numTables = ttUSHORT(data + cmap + 2); info->index_map = 0; for (i=0; i < numTables; ++i) { stbtt_uint32 encoding_record = cmap + 4 + 8 * i; // find an encoding we understand: switch(ttUSHORT(data+encoding_record)) { case STBTT_PLATFORM_ID_MICROSOFT: switch (ttUSHORT(data+encoding_record+2)) { case STBTT_MS_EID_UNICODE_BMP: case STBTT_MS_EID_UNICODE_FULL: // MS/Unicode info->index_map = cmap + ttULONG(data+encoding_record+4); break; } break; case STBTT_PLATFORM_ID_UNICODE: // Mac/iOS has these // all the encodingIDs are unicode, so we don't bother to check it info->index_map = cmap + ttULONG(data+encoding_record+4); break; } } if (info->index_map == 0) return 0; info->indexToLocFormat = ttUSHORT(data+info->head + 50); return 1; } STBTT_DEF int stbtt_FindGlyphIndex(const stbtt_fontinfo *info, int unicode_codepoint) { stbtt_uint8 *data = info->data; stbtt_uint32 index_map = info->index_map; stbtt_uint16 format = ttUSHORT(data + index_map + 0); if (format == 0) { // apple byte encoding stbtt_int32 bytes = ttUSHORT(data + index_map + 2); if (unicode_codepoint < bytes-6) return ttBYTE(data + index_map + 6 + unicode_codepoint); return 0; } else if (format == 6) { stbtt_uint32 first = ttUSHORT(data + index_map + 6); stbtt_uint32 count = ttUSHORT(data + index_map + 8); if ((stbtt_uint32) unicode_codepoint >= first && (stbtt_uint32) unicode_codepoint < first+count) return ttUSHORT(data + index_map + 10 + (unicode_codepoint - first)*2); return 0; } else if (format == 2) { STBTT_assert(0); // @TODO: high-byte mapping for japanese/chinese/korean return 0; } else if (format == 4) { // standard mapping for windows fonts: binary search collection of ranges stbtt_uint16 segcount = ttUSHORT(data+index_map+6) >> 1; stbtt_uint16 searchRange = ttUSHORT(data+index_map+8) >> 1; stbtt_uint16 entrySelector = ttUSHORT(data+index_map+10); stbtt_uint16 rangeShift = ttUSHORT(data+index_map+12) >> 1; // do a binary search of the segments stbtt_uint32 endCount = index_map + 14; stbtt_uint32 search = endCount; if (unicode_codepoint > 0xffff) return 0; // they lie from endCount .. endCount + segCount // but searchRange is the nearest power of two, so... if (unicode_codepoint >= ttUSHORT(data + search + rangeShift*2)) search += rangeShift*2; // now decrement to bias correctly to find smallest search -= 2; while (entrySelector) { stbtt_uint16 end; searchRange >>= 1; end = ttUSHORT(data + search + searchRange*2); if (unicode_codepoint > end) search += searchRange*2; --entrySelector; } search += 2; { stbtt_uint16 offset, start, last; stbtt_uint16 item = (stbtt_uint16) ((search - endCount) >> 1); start = ttUSHORT(data + index_map + 14 + segcount*2 + 2 + 2*item); last = ttUSHORT(data + endCount + 2*item); if (unicode_codepoint < start || unicode_codepoint > last) return 0; offset = ttUSHORT(data + index_map + 14 + segcount*6 + 2 + 2*item); if (offset == 0) return (stbtt_uint16) (unicode_codepoint + ttSHORT(data + index_map + 14 + segcount*4 + 2 + 2*item)); return ttUSHORT(data + offset + (unicode_codepoint-start)*2 + index_map + 14 + segcount*6 + 2 + 2*item); } } else if (format == 12 || format == 13) { stbtt_uint32 ngroups = ttULONG(data+index_map+12); stbtt_int32 low,high; low = 0; high = (stbtt_int32)ngroups; // Binary search the right group. while (low < high) { stbtt_int32 mid = low + ((high-low) >> 1); // rounds down, so low <= mid < high stbtt_uint32 start_char = ttULONG(data+index_map+16+mid*12); stbtt_uint32 end_char = ttULONG(data+index_map+16+mid*12+4); if ((stbtt_uint32) unicode_codepoint < start_char) high = mid; else if ((stbtt_uint32) unicode_codepoint > end_char) low = mid+1; else { stbtt_uint32 start_glyph = ttULONG(data+index_map+16+mid*12+8); if (format == 12) return start_glyph + unicode_codepoint-start_char; else // format == 13 return start_glyph; } } return 0; // not found } // @TODO STBTT_assert(0); return 0; } STBTT_DEF int stbtt_GetCodepointShape(const stbtt_fontinfo *info, int unicode_codepoint, stbtt_vertex **vertices) { return stbtt_GetGlyphShape(info, stbtt_FindGlyphIndex(info, unicode_codepoint), vertices); } static void stbtt_setvertex(stbtt_vertex *v, stbtt_uint8 type, stbtt_int32 x, stbtt_int32 y, stbtt_int32 cx, stbtt_int32 cy) { v->type = type; v->x = (stbtt_int16) x; v->y = (stbtt_int16) y; v->cx = (stbtt_int16) cx; v->cy = (stbtt_int16) cy; } static int stbtt__GetGlyfOffset(const stbtt_fontinfo *info, int glyph_index) { int g1,g2; STBTT_assert(!info->cff.size); if (glyph_index >= info->numGlyphs) return -1; // glyph index out of range if (info->indexToLocFormat >= 2) return -1; // unknown index->glyph map format if (info->indexToLocFormat == 0) { g1 = info->glyf + ttUSHORT(info->data + info->loca + glyph_index * 2) * 2; g2 = info->glyf + ttUSHORT(info->data + info->loca + glyph_index * 2 + 2) * 2; } else { g1 = info->glyf + ttULONG (info->data + info->loca + glyph_index * 4); g2 = info->glyf + ttULONG (info->data + info->loca + glyph_index * 4 + 4); } return g1==g2 ? -1 : g1; // if length is 0, return -1 } static int stbtt__GetGlyphInfoT2(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1); STBTT_DEF int stbtt_GetGlyphBox(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1) { if (info->cff.size) { stbtt__GetGlyphInfoT2(info, glyph_index, x0, y0, x1, y1); } else { int g = stbtt__GetGlyfOffset(info, glyph_index); if (g < 0) return 0; if (x0) *x0 = ttSHORT(info->data + g + 2); if (y0) *y0 = ttSHORT(info->data + g + 4); if (x1) *x1 = ttSHORT(info->data + g + 6); if (y1) *y1 = ttSHORT(info->data + g + 8); } return 1; } STBTT_DEF int stbtt_GetCodepointBox(const stbtt_fontinfo *info, int codepoint, int *x0, int *y0, int *x1, int *y1) { return stbtt_GetGlyphBox(info, stbtt_FindGlyphIndex(info,codepoint), x0,y0,x1,y1); } STBTT_DEF int stbtt_IsGlyphEmpty(const stbtt_fontinfo *info, int glyph_index) { stbtt_int16 numberOfContours; int g; if (info->cff.size) return stbtt__GetGlyphInfoT2(info, glyph_index, NULL, NULL, NULL, NULL) == 0; g = stbtt__GetGlyfOffset(info, glyph_index); if (g < 0) return 1; numberOfContours = ttSHORT(info->data + g); return numberOfContours == 0; } static int stbtt__close_shape(stbtt_vertex *vertices, int num_vertices, int was_off, int start_off, stbtt_int32 sx, stbtt_int32 sy, stbtt_int32 scx, stbtt_int32 scy, stbtt_int32 cx, stbtt_int32 cy) { if (start_off) { if (was_off) stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, (cx+scx)>>1, (cy+scy)>>1, cx,cy); stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, sx,sy,scx,scy); } else { if (was_off) stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve,sx,sy,cx,cy); else stbtt_setvertex(&vertices[num_vertices++], STBTT_vline,sx,sy,0,0); } return num_vertices; } static int stbtt__GetGlyphShapeTT(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **pvertices) { stbtt_int16 numberOfContours; stbtt_uint8 *endPtsOfContours; stbtt_uint8 *data = info->data; stbtt_vertex *vertices=0; int num_vertices=0; int g = stbtt__GetGlyfOffset(info, glyph_index); *pvertices = NULL; if (g < 0) return 0; numberOfContours = ttSHORT(data + g); if (numberOfContours > 0) { stbtt_uint8 flags=0,flagcount; stbtt_int32 ins, i,j=0,m,n, next_move, was_off=0, off, start_off=0; stbtt_int32 x,y,cx,cy,sx,sy, scx,scy; stbtt_uint8 *points; endPtsOfContours = (data + g + 10); ins = ttUSHORT(data + g + 10 + numberOfContours * 2); points = data + g + 10 + numberOfContours * 2 + 2 + ins; n = 1+ttUSHORT(endPtsOfContours + numberOfContours*2-2); m = n + 2*numberOfContours; // a loose bound on how many vertices we might need vertices = (stbtt_vertex *) STBTT_malloc(m * sizeof(vertices[0]), info->userdata); if (vertices == 0) return 0; next_move = 0; flagcount=0; // in first pass, we load uninterpreted data into the allocated array // above, shifted to the end of the array so we won't overwrite it when // we create our final data starting from the front off = m - n; // starting offset for uninterpreted data, regardless of how m ends up being calculated // first load flags for (i=0; i < n; ++i) { if (flagcount == 0) { flags = *points++; if (flags & 8) flagcount = *points++; } else --flagcount; vertices[off+i].type = flags; } // now load x coordinates x=0; for (i=0; i < n; ++i) { flags = vertices[off+i].type; if (flags & 2) { stbtt_int16 dx = *points++; x += (flags & 16) ? dx : -dx; // ??? } else { if (!(flags & 16)) { x = x + (stbtt_int16) (points[0]*256 + points[1]); points += 2; } } vertices[off+i].x = (stbtt_int16) x; } // now load y coordinates y=0; for (i=0; i < n; ++i) { flags = vertices[off+i].type; if (flags & 4) { stbtt_int16 dy = *points++; y += (flags & 32) ? dy : -dy; // ??? } else { if (!(flags & 32)) { y = y + (stbtt_int16) (points[0]*256 + points[1]); points += 2; } } vertices[off+i].y = (stbtt_int16) y; } // now convert them to our format num_vertices=0; sx = sy = cx = cy = scx = scy = 0; for (i=0; i < n; ++i) { flags = vertices[off+i].type; x = (stbtt_int16) vertices[off+i].x; y = (stbtt_int16) vertices[off+i].y; if (next_move == i) { if (i != 0) num_vertices = stbtt__close_shape(vertices, num_vertices, was_off, start_off, sx,sy,scx,scy,cx,cy); // now start the new one start_off = !(flags & 1); if (start_off) { // if we start off with an off-curve point, then when we need to find a point on the curve // where we can start, and we need to save some state for when we wraparound. scx = x; scy = y; if (!(vertices[off+i+1].type & 1)) { // next point is also a curve point, so interpolate an on-point curve sx = (x + (stbtt_int32) vertices[off+i+1].x) >> 1; sy = (y + (stbtt_int32) vertices[off+i+1].y) >> 1; } else { // otherwise just use the next point as our start point sx = (stbtt_int32) vertices[off+i+1].x; sy = (stbtt_int32) vertices[off+i+1].y; ++i; // we're using point i+1 as the starting point, so skip it } } else { sx = x; sy = y; } stbtt_setvertex(&vertices[num_vertices++], STBTT_vmove,sx,sy,0,0); was_off = 0; next_move = 1 + ttUSHORT(endPtsOfContours+j*2); ++j; } else { if (!(flags & 1)) { // if it's a curve if (was_off) // two off-curve control points in a row means interpolate an on-curve midpoint stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, (cx+x)>>1, (cy+y)>>1, cx, cy); cx = x; cy = y; was_off = 1; } else { if (was_off) stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, x,y, cx, cy); else stbtt_setvertex(&vertices[num_vertices++], STBTT_vline, x,y,0,0); was_off = 0; } } } num_vertices = stbtt__close_shape(vertices, num_vertices, was_off, start_off, sx,sy,scx,scy,cx,cy); } else if (numberOfContours < 0) { // Compound shapes. int more = 1; stbtt_uint8 *comp = data + g + 10; num_vertices = 0; vertices = 0; while (more) { stbtt_uint16 flags, gidx; int comp_num_verts = 0, i; stbtt_vertex *comp_verts = 0, *tmp = 0; float mtx[6] = {1,0,0,1,0,0}, m, n; flags = ttSHORT(comp); comp+=2; gidx = ttSHORT(comp); comp+=2; if (flags & 2) { // XY values if (flags & 1) { // shorts mtx[4] = ttSHORT(comp); comp+=2; mtx[5] = ttSHORT(comp); comp+=2; } else { mtx[4] = ttCHAR(comp); comp+=1; mtx[5] = ttCHAR(comp); comp+=1; } } else { // @TODO handle matching point STBTT_assert(0); } if (flags & (1<<3)) { // WE_HAVE_A_SCALE mtx[0] = mtx[3] = ttSHORT(comp)/16384.0f; comp+=2; mtx[1] = mtx[2] = 0; } else if (flags & (1<<6)) { // WE_HAVE_AN_X_AND_YSCALE mtx[0] = ttSHORT(comp)/16384.0f; comp+=2; mtx[1] = mtx[2] = 0; mtx[3] = ttSHORT(comp)/16384.0f; comp+=2; } else if (flags & (1<<7)) { // WE_HAVE_A_TWO_BY_TWO mtx[0] = ttSHORT(comp)/16384.0f; comp+=2; mtx[1] = ttSHORT(comp)/16384.0f; comp+=2; mtx[2] = ttSHORT(comp)/16384.0f; comp+=2; mtx[3] = ttSHORT(comp)/16384.0f; comp+=2; } // Find transformation scales. m = (float) STBTT_sqrt(mtx[0]*mtx[0] + mtx[1]*mtx[1]); n = (float) STBTT_sqrt(mtx[2]*mtx[2] + mtx[3]*mtx[3]); // Get indexed glyph. comp_num_verts = stbtt_GetGlyphShape(info, gidx, &comp_verts); if (comp_num_verts > 0) { // Transform vertices. for (i = 0; i < comp_num_verts; ++i) { stbtt_vertex* v = &comp_verts[i]; stbtt_vertex_type x,y; x=v->x; y=v->y; v->x = (stbtt_vertex_type)(m * (mtx[0]*x + mtx[2]*y + mtx[4])); v->y = (stbtt_vertex_type)(n * (mtx[1]*x + mtx[3]*y + mtx[5])); x=v->cx; y=v->cy; v->cx = (stbtt_vertex_type)(m * (mtx[0]*x + mtx[2]*y + mtx[4])); v->cy = (stbtt_vertex_type)(n * (mtx[1]*x + mtx[3]*y + mtx[5])); } // Append vertices. tmp = (stbtt_vertex*)STBTT_malloc((num_vertices+comp_num_verts)*sizeof(stbtt_vertex), info->userdata); if (!tmp) { if (vertices) STBTT_free(vertices, info->userdata); if (comp_verts) STBTT_free(comp_verts, info->userdata); return 0; } if (num_vertices > 0 && vertices) STBTT_memcpy(tmp, vertices, num_vertices*sizeof(stbtt_vertex)); STBTT_memcpy(tmp+num_vertices, comp_verts, comp_num_verts*sizeof(stbtt_vertex)); if (vertices) STBTT_free(vertices, info->userdata); vertices = tmp; STBTT_free(comp_verts, info->userdata); num_vertices += comp_num_verts; } // More components ? more = flags & (1<<5); } } else { // numberOfCounters == 0, do nothing } *pvertices = vertices; return num_vertices; } typedef struct { int bounds; int started; float first_x, first_y; float x, y; stbtt_int32 min_x, max_x, min_y, max_y; stbtt_vertex *pvertices; int num_vertices; } stbtt__csctx; #define STBTT__CSCTX_INIT(bounds) {bounds,0, 0,0, 0,0, 0,0,0,0, NULL, 0} static void stbtt__track_vertex(stbtt__csctx *c, stbtt_int32 x, stbtt_int32 y) { if (x > c->max_x || !c->started) c->max_x = x; if (y > c->max_y || !c->started) c->max_y = y; if (x < c->min_x || !c->started) c->min_x = x; if (y < c->min_y || !c->started) c->min_y = y; c->started = 1; } static void stbtt__csctx_v(stbtt__csctx *c, stbtt_uint8 type, stbtt_int32 x, stbtt_int32 y, stbtt_int32 cx, stbtt_int32 cy, stbtt_int32 cx1, stbtt_int32 cy1) { if (c->bounds) { stbtt__track_vertex(c, x, y); if (type == STBTT_vcubic) { stbtt__track_vertex(c, cx, cy); stbtt__track_vertex(c, cx1, cy1); } } else { stbtt_setvertex(&c->pvertices[c->num_vertices], type, x, y, cx, cy); c->pvertices[c->num_vertices].cx1 = (stbtt_int16) cx1; c->pvertices[c->num_vertices].cy1 = (stbtt_int16) cy1; } c->num_vertices++; } static void stbtt__csctx_close_shape(stbtt__csctx *ctx) { if (ctx->first_x != ctx->x || ctx->first_y != ctx->y) stbtt__csctx_v(ctx, STBTT_vline, (int)ctx->first_x, (int)ctx->first_y, 0, 0, 0, 0); } static void stbtt__csctx_rmove_to(stbtt__csctx *ctx, float dx, float dy) { stbtt__csctx_close_shape(ctx); ctx->first_x = ctx->x = ctx->x + dx; ctx->first_y = ctx->y = ctx->y + dy; stbtt__csctx_v(ctx, STBTT_vmove, (int)ctx->x, (int)ctx->y, 0, 0, 0, 0); } static void stbtt__csctx_rline_to(stbtt__csctx *ctx, float dx, float dy) { ctx->x += dx; ctx->y += dy; stbtt__csctx_v(ctx, STBTT_vline, (int)ctx->x, (int)ctx->y, 0, 0, 0, 0); } static void stbtt__csctx_rccurve_to(stbtt__csctx *ctx, float dx1, float dy1, float dx2, float dy2, float dx3, float dy3) { float cx1 = ctx->x + dx1; float cy1 = ctx->y + dy1; float cx2 = cx1 + dx2; float cy2 = cy1 + dy2; ctx->x = cx2 + dx3; ctx->y = cy2 + dy3; stbtt__csctx_v(ctx, STBTT_vcubic, (int)ctx->x, (int)ctx->y, (int)cx1, (int)cy1, (int)cx2, (int)cy2); } static stbtt__buf stbtt__get_subr(stbtt__buf idx, int n) { int count = stbtt__cff_index_count(&idx); int bias = 107; if (count >= 33900) bias = 32768; else if (count >= 1240) bias = 1131; n += bias; if (n < 0 || n >= count) return stbtt__new_buf(NULL, 0); return stbtt__cff_index_get(idx, n); } static stbtt__buf stbtt__cid_get_glyph_subrs(const stbtt_fontinfo *info, int glyph_index) { stbtt__buf fdselect = info->fdselect; int nranges, start, end, v, fmt, fdselector = -1, i; stbtt__buf_seek(&fdselect, 0); fmt = stbtt__buf_get8(&fdselect); if (fmt == 0) { // untested stbtt__buf_skip(&fdselect, glyph_index); fdselector = stbtt__buf_get8(&fdselect); } else if (fmt == 3) { nranges = stbtt__buf_get16(&fdselect); start = stbtt__buf_get16(&fdselect); for (i = 0; i < nranges; i++) { v = stbtt__buf_get8(&fdselect); end = stbtt__buf_get16(&fdselect); if (glyph_index >= start && glyph_index < end) { fdselector = v; break; } start = end; } } if (fdselector == -1) stbtt__new_buf(NULL, 0); return stbtt__get_subrs(info->cff, stbtt__cff_index_get(info->fontdicts, fdselector)); } static int stbtt__run_charstring(const stbtt_fontinfo *info, int glyph_index, stbtt__csctx *c) { int in_header = 1, maskbits = 0, subr_stack_height = 0, sp = 0, v, i, b0; int has_subrs = 0, clear_stack; float s[48]; stbtt__buf subr_stack[10], subrs = info->subrs, b; float f; #define STBTT__CSERR(s) (0) // this currently ignores the initial width value, which isn't needed if we have hmtx b = stbtt__cff_index_get(info->charstrings, glyph_index); while (b.cursor < b.size) { i = 0; clear_stack = 1; b0 = stbtt__buf_get8(&b); switch (b0) { // @TODO implement hinting case 0x13: // hintmask case 0x14: // cntrmask if (in_header) maskbits += (sp / 2); // implicit "vstem" in_header = 0; stbtt__buf_skip(&b, (maskbits + 7) / 8); break; case 0x01: // hstem case 0x03: // vstem case 0x12: // hstemhm case 0x17: // vstemhm maskbits += (sp / 2); break; case 0x15: // rmoveto in_header = 0; if (sp < 2) return STBTT__CSERR("rmoveto stack"); stbtt__csctx_rmove_to(c, s[sp-2], s[sp-1]); break; case 0x04: // vmoveto in_header = 0; if (sp < 1) return STBTT__CSERR("vmoveto stack"); stbtt__csctx_rmove_to(c, 0, s[sp-1]); break; case 0x16: // hmoveto in_header = 0; if (sp < 1) return STBTT__CSERR("hmoveto stack"); stbtt__csctx_rmove_to(c, s[sp-1], 0); break; case 0x05: // rlineto if (sp < 2) return STBTT__CSERR("rlineto stack"); for (; i + 1 < sp; i += 2) stbtt__csctx_rline_to(c, s[i], s[i+1]); break; // hlineto/vlineto and vhcurveto/hvcurveto alternate horizontal and vertical // starting from a different place. case 0x07: // vlineto if (sp < 1) return STBTT__CSERR("vlineto stack"); goto vlineto; case 0x06: // hlineto if (sp < 1) return STBTT__CSERR("hlineto stack"); for (;;) { if (i >= sp) break; stbtt__csctx_rline_to(c, s[i], 0); i++; vlineto: if (i >= sp) break; stbtt__csctx_rline_to(c, 0, s[i]); i++; } break; case 0x1F: // hvcurveto if (sp < 4) return STBTT__CSERR("hvcurveto stack"); goto hvcurveto; case 0x1E: // vhcurveto if (sp < 4) return STBTT__CSERR("vhcurveto stack"); for (;;) { if (i + 3 >= sp) break; stbtt__csctx_rccurve_to(c, 0, s[i], s[i+1], s[i+2], s[i+3], (sp - i == 5) ? s[i + 4] : 0.0f); i += 4; hvcurveto: if (i + 3 >= sp) break; stbtt__csctx_rccurve_to(c, s[i], 0, s[i+1], s[i+2], (sp - i == 5) ? s[i+4] : 0.0f, s[i+3]); i += 4; } break; case 0x08: // rrcurveto if (sp < 6) return STBTT__CSERR("rcurveline stack"); for (; i + 5 < sp; i += 6) stbtt__csctx_rccurve_to(c, s[i], s[i+1], s[i+2], s[i+3], s[i+4], s[i+5]); break; case 0x18: // rcurveline if (sp < 8) return STBTT__CSERR("rcurveline stack"); for (; i + 5 < sp - 2; i += 6) stbtt__csctx_rccurve_to(c, s[i], s[i+1], s[i+2], s[i+3], s[i+4], s[i+5]); if (i + 1 >= sp) return STBTT__CSERR("rcurveline stack"); stbtt__csctx_rline_to(c, s[i], s[i+1]); break; case 0x19: // rlinecurve if (sp < 8) return STBTT__CSERR("rlinecurve stack"); for (; i + 1 < sp - 6; i += 2) stbtt__csctx_rline_to(c, s[i], s[i+1]); if (i + 5 >= sp) return STBTT__CSERR("rlinecurve stack"); stbtt__csctx_rccurve_to(c, s[i], s[i+1], s[i+2], s[i+3], s[i+4], s[i+5]); break; case 0x1A: // vvcurveto case 0x1B: // hhcurveto if (sp < 4) return STBTT__CSERR("(vv|hh)curveto stack"); f = 0.0; if (sp & 1) { f = s[i]; i++; } for (; i + 3 < sp; i += 4) { if (b0 == 0x1B) stbtt__csctx_rccurve_to(c, s[i], f, s[i+1], s[i+2], s[i+3], 0.0); else stbtt__csctx_rccurve_to(c, f, s[i], s[i+1], s[i+2], 0.0, s[i+3]); f = 0.0; } break; case 0x0A: // callsubr if (!has_subrs) { if (info->fdselect.size) subrs = stbtt__cid_get_glyph_subrs(info, glyph_index); has_subrs = 1; } // FALLTHROUGH case 0x1D: // callgsubr if (sp < 1) return STBTT__CSERR("call(g|)subr stack"); v = (int) s[--sp]; if (subr_stack_height >= 10) return STBTT__CSERR("recursion limit"); subr_stack[subr_stack_height++] = b; b = stbtt__get_subr(b0 == 0x0A ? subrs : info->gsubrs, v); if (b.size == 0) return STBTT__CSERR("subr not found"); b.cursor = 0; clear_stack = 0; break; case 0x0B: // return if (subr_stack_height <= 0) return STBTT__CSERR("return outside subr"); b = subr_stack[--subr_stack_height]; clear_stack = 0; break; case 0x0E: // endchar stbtt__csctx_close_shape(c); return 1; case 0x0C: { // two-byte escape float dx1, dx2, dx3, dx4, dx5, dx6, dy1, dy2, dy3, dy4, dy5, dy6; float dx, dy; int b1 = stbtt__buf_get8(&b); switch (b1) { // @TODO These "flex" implementations ignore the flex-depth and resolution, // and always draw beziers. case 0x22: // hflex if (sp < 7) return STBTT__CSERR("hflex stack"); dx1 = s[0]; dx2 = s[1]; dy2 = s[2]; dx3 = s[3]; dx4 = s[4]; dx5 = s[5]; dx6 = s[6]; stbtt__csctx_rccurve_to(c, dx1, 0, dx2, dy2, dx3, 0); stbtt__csctx_rccurve_to(c, dx4, 0, dx5, -dy2, dx6, 0); break; case 0x23: // flex if (sp < 13) return STBTT__CSERR("flex stack"); dx1 = s[0]; dy1 = s[1]; dx2 = s[2]; dy2 = s[3]; dx3 = s[4]; dy3 = s[5]; dx4 = s[6]; dy4 = s[7]; dx5 = s[8]; dy5 = s[9]; dx6 = s[10]; dy6 = s[11]; //fd is s[12] stbtt__csctx_rccurve_to(c, dx1, dy1, dx2, dy2, dx3, dy3); stbtt__csctx_rccurve_to(c, dx4, dy4, dx5, dy5, dx6, dy6); break; case 0x24: // hflex1 if (sp < 9) return STBTT__CSERR("hflex1 stack"); dx1 = s[0]; dy1 = s[1]; dx2 = s[2]; dy2 = s[3]; dx3 = s[4]; dx4 = s[5]; dx5 = s[6]; dy5 = s[7]; dx6 = s[8]; stbtt__csctx_rccurve_to(c, dx1, dy1, dx2, dy2, dx3, 0); stbtt__csctx_rccurve_to(c, dx4, 0, dx5, dy5, dx6, -(dy1+dy2+dy5)); break; case 0x25: // flex1 if (sp < 11) return STBTT__CSERR("flex1 stack"); dx1 = s[0]; dy1 = s[1]; dx2 = s[2]; dy2 = s[3]; dx3 = s[4]; dy3 = s[5]; dx4 = s[6]; dy4 = s[7]; dx5 = s[8]; dy5 = s[9]; dx6 = dy6 = s[10]; dx = dx1+dx2+dx3+dx4+dx5; dy = dy1+dy2+dy3+dy4+dy5; if (STBTT_fabs(dx) > STBTT_fabs(dy)) dy6 = -dy; else dx6 = -dx; stbtt__csctx_rccurve_to(c, dx1, dy1, dx2, dy2, dx3, dy3); stbtt__csctx_rccurve_to(c, dx4, dy4, dx5, dy5, dx6, dy6); break; default: return STBTT__CSERR("unimplemented"); } } break; default: if (b0 != 255 && b0 != 28 && b0 < 32) return STBTT__CSERR("reserved operator"); // push immediate if (b0 == 255) { f = (float)(stbtt_int32)stbtt__buf_get32(&b) / 0x10000; } else { stbtt__buf_skip(&b, -1); f = (float)(stbtt_int16)stbtt__cff_int(&b); } if (sp >= 48) return STBTT__CSERR("push stack overflow"); s[sp++] = f; clear_stack = 0; break; } if (clear_stack) sp = 0; } return STBTT__CSERR("no endchar"); #undef STBTT__CSERR } static int stbtt__GetGlyphShapeT2(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **pvertices) { // runs the charstring twice, once to count and once to output (to avoid realloc) stbtt__csctx count_ctx = STBTT__CSCTX_INIT(1); stbtt__csctx output_ctx = STBTT__CSCTX_INIT(0); if (stbtt__run_charstring(info, glyph_index, &count_ctx)) { *pvertices = (stbtt_vertex*)STBTT_malloc(count_ctx.num_vertices*sizeof(stbtt_vertex), info->userdata); output_ctx.pvertices = *pvertices; if (stbtt__run_charstring(info, glyph_index, &output_ctx)) { STBTT_assert(output_ctx.num_vertices == count_ctx.num_vertices); return output_ctx.num_vertices; } } *pvertices = NULL; return 0; } static int stbtt__GetGlyphInfoT2(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1) { stbtt__csctx c = STBTT__CSCTX_INIT(1); int r = stbtt__run_charstring(info, glyph_index, &c); if (x0) *x0 = r ? c.min_x : 0; if (y0) *y0 = r ? c.min_y : 0; if (x1) *x1 = r ? c.max_x : 0; if (y1) *y1 = r ? c.max_y : 0; return r ? c.num_vertices : 0; } STBTT_DEF int stbtt_GetGlyphShape(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **pvertices) { if (!info->cff.size) return stbtt__GetGlyphShapeTT(info, glyph_index, pvertices); else return stbtt__GetGlyphShapeT2(info, glyph_index, pvertices); } STBTT_DEF void stbtt_GetGlyphHMetrics(const stbtt_fontinfo *info, int glyph_index, int *advanceWidth, int *leftSideBearing) { stbtt_uint16 numOfLongHorMetrics = ttUSHORT(info->data+info->hhea + 34); if (glyph_index < numOfLongHorMetrics) { if (advanceWidth) *advanceWidth = ttSHORT(info->data + info->hmtx + 4*glyph_index); if (leftSideBearing) *leftSideBearing = ttSHORT(info->data + info->hmtx + 4*glyph_index + 2); } else { if (advanceWidth) *advanceWidth = ttSHORT(info->data + info->hmtx + 4*(numOfLongHorMetrics-1)); if (leftSideBearing) *leftSideBearing = ttSHORT(info->data + info->hmtx + 4*numOfLongHorMetrics + 2*(glyph_index - numOfLongHorMetrics)); } } STBTT_DEF int stbtt_GetKerningTableLength(const stbtt_fontinfo *info) { stbtt_uint8 *data = info->data + info->kern; // we only look at the first table. it must be 'horizontal' and format 0. if (!info->kern) return 0; if (ttUSHORT(data+2) < 1) // number of tables, need at least 1 return 0; if (ttUSHORT(data+8) != 1) // horizontal flag must be set in format return 0; return ttUSHORT(data+10); } STBTT_DEF int stbtt_GetKerningTable(const stbtt_fontinfo *info, stbtt_kerningentry* table, int table_length) { stbtt_uint8 *data = info->data + info->kern; int k, length; // we only look at the first table. it must be 'horizontal' and format 0. if (!info->kern) return 0; if (ttUSHORT(data+2) < 1) // number of tables, need at least 1 return 0; if (ttUSHORT(data+8) != 1) // horizontal flag must be set in format return 0; length = ttUSHORT(data+10); if (table_length < length) length = table_length; for (k = 0; k < length; k++) { table[k].glyph1 = ttUSHORT(data+18+(k*6)); table[k].glyph2 = ttUSHORT(data+20+(k*6)); table[k].advance = ttSHORT(data+22+(k*6)); } return length; } static int stbtt__GetGlyphKernInfoAdvance(const stbtt_fontinfo *info, int glyph1, int glyph2) { stbtt_uint8 *data = info->data + info->kern; stbtt_uint32 needle, straw; int l, r, m; // we only look at the first table. it must be 'horizontal' and format 0. if (!info->kern) return 0; if (ttUSHORT(data+2) < 1) // number of tables, need at least 1 return 0; if (ttUSHORT(data+8) != 1) // horizontal flag must be set in format return 0; l = 0; r = ttUSHORT(data+10) - 1; needle = glyph1 << 16 | glyph2; while (l <= r) { m = (l + r) >> 1; straw = ttULONG(data+18+(m*6)); // note: unaligned read if (needle < straw) r = m - 1; else if (needle > straw) l = m + 1; else return ttSHORT(data+22+(m*6)); } return 0; } static stbtt_int32 stbtt__GetCoverageIndex(stbtt_uint8 *coverageTable, int glyph) { stbtt_uint16 coverageFormat = ttUSHORT(coverageTable); switch (coverageFormat) { case 1: { stbtt_uint16 glyphCount = ttUSHORT(coverageTable + 2); // Binary search. stbtt_int32 l=0, r=glyphCount-1, m; int straw, needle=glyph; while (l <= r) { stbtt_uint8 *glyphArray = coverageTable + 4; stbtt_uint16 glyphID; m = (l + r) >> 1; glyphID = ttUSHORT(glyphArray + 2 * m); straw = glyphID; if (needle < straw) r = m - 1; else if (needle > straw) l = m + 1; else { return m; } } break; } case 2: { stbtt_uint16 rangeCount = ttUSHORT(coverageTable + 2); stbtt_uint8 *rangeArray = coverageTable + 4; // Binary search. stbtt_int32 l=0, r=rangeCount-1, m; int strawStart, strawEnd, needle=glyph; while (l <= r) { stbtt_uint8 *rangeRecord; m = (l + r) >> 1; rangeRecord = rangeArray + 6 * m; strawStart = ttUSHORT(rangeRecord); strawEnd = ttUSHORT(rangeRecord + 2); if (needle < strawStart) r = m - 1; else if (needle > strawEnd) l = m + 1; else { stbtt_uint16 startCoverageIndex = ttUSHORT(rangeRecord + 4); return startCoverageIndex + glyph - strawStart; } } break; } default: return -1; // unsupported } return -1; } static stbtt_int32 stbtt__GetGlyphClass(stbtt_uint8 *classDefTable, int glyph) { stbtt_uint16 classDefFormat = ttUSHORT(classDefTable); switch (classDefFormat) { case 1: { stbtt_uint16 startGlyphID = ttUSHORT(classDefTable + 2); stbtt_uint16 glyphCount = ttUSHORT(classDefTable + 4); stbtt_uint8 *classDef1ValueArray = classDefTable + 6; if (glyph >= startGlyphID && glyph < startGlyphID + glyphCount) return (stbtt_int32)ttUSHORT(classDef1ValueArray + 2 * (glyph - startGlyphID)); break; } case 2: { stbtt_uint16 classRangeCount = ttUSHORT(classDefTable + 2); stbtt_uint8 *classRangeRecords = classDefTable + 4; // Binary search. stbtt_int32 l=0, r=classRangeCount-1, m; int strawStart, strawEnd, needle=glyph; while (l <= r) { stbtt_uint8 *classRangeRecord; m = (l + r) >> 1; classRangeRecord = classRangeRecords + 6 * m; strawStart = ttUSHORT(classRangeRecord); strawEnd = ttUSHORT(classRangeRecord + 2); if (needle < strawStart) r = m - 1; else if (needle > strawEnd) l = m + 1; else return (stbtt_int32)ttUSHORT(classRangeRecord + 4); } break; } default: return -1; // Unsupported definition type, return an error. } // "All glyphs not assigned to a class fall into class 0". (OpenType spec) return 0; } // Define to STBTT_assert(x) if you want to break on unimplemented formats. #define STBTT_GPOS_TODO_assert(x) static stbtt_int32 stbtt__GetGlyphGPOSInfoAdvance(const stbtt_fontinfo *info, int glyph1, int glyph2) { stbtt_uint16 lookupListOffset; stbtt_uint8 *lookupList; stbtt_uint16 lookupCount; stbtt_uint8 *data; stbtt_int32 i, sti; if (!info->gpos) return 0; data = info->data + info->gpos; if (ttUSHORT(data+0) != 1) return 0; // Major version 1 if (ttUSHORT(data+2) != 0) return 0; // Minor version 0 lookupListOffset = ttUSHORT(data+8); lookupList = data + lookupListOffset; lookupCount = ttUSHORT(lookupList); for (i=0; i= pairSetCount) return 0; needle=glyph2; r=pairValueCount-1; l=0; // Binary search. while (l <= r) { stbtt_uint16 secondGlyph; stbtt_uint8 *pairValue; m = (l + r) >> 1; pairValue = pairValueArray + (2 + valueRecordPairSizeInBytes) * m; secondGlyph = ttUSHORT(pairValue); straw = secondGlyph; if (needle < straw) r = m - 1; else if (needle > straw) l = m + 1; else { stbtt_int16 xAdvance = ttSHORT(pairValue + 2); return xAdvance; } } } else return 0; break; } case 2: { stbtt_uint16 valueFormat1 = ttUSHORT(table + 4); stbtt_uint16 valueFormat2 = ttUSHORT(table + 6); if (valueFormat1 == 4 && valueFormat2 == 0) { // Support more formats? stbtt_uint16 classDef1Offset = ttUSHORT(table + 8); stbtt_uint16 classDef2Offset = ttUSHORT(table + 10); int glyph1class = stbtt__GetGlyphClass(table + classDef1Offset, glyph1); int glyph2class = stbtt__GetGlyphClass(table + classDef2Offset, glyph2); stbtt_uint16 class1Count = ttUSHORT(table + 12); stbtt_uint16 class2Count = ttUSHORT(table + 14); stbtt_uint8 *class1Records, *class2Records; stbtt_int16 xAdvance; if (glyph1class < 0 || glyph1class >= class1Count) return 0; // malformed if (glyph2class < 0 || glyph2class >= class2Count) return 0; // malformed class1Records = table + 16; class2Records = class1Records + 2 * (glyph1class * class2Count); xAdvance = ttSHORT(class2Records + 2 * glyph2class); return xAdvance; } else return 0; break; } default: return 0; // Unsupported position format } } } return 0; } STBTT_DEF int stbtt_GetGlyphKernAdvance(const stbtt_fontinfo *info, int g1, int g2) { int xAdvance = 0; if (info->gpos) xAdvance += stbtt__GetGlyphGPOSInfoAdvance(info, g1, g2); else if (info->kern) xAdvance += stbtt__GetGlyphKernInfoAdvance(info, g1, g2); return xAdvance; } STBTT_DEF int stbtt_GetCodepointKernAdvance(const stbtt_fontinfo *info, int ch1, int ch2) { if (!info->kern && !info->gpos) // if no kerning table, don't waste time looking up both codepoint->glyphs return 0; return stbtt_GetGlyphKernAdvance(info, stbtt_FindGlyphIndex(info,ch1), stbtt_FindGlyphIndex(info,ch2)); } STBTT_DEF void stbtt_GetCodepointHMetrics(const stbtt_fontinfo *info, int codepoint, int *advanceWidth, int *leftSideBearing) { stbtt_GetGlyphHMetrics(info, stbtt_FindGlyphIndex(info,codepoint), advanceWidth, leftSideBearing); } STBTT_DEF void stbtt_GetFontVMetrics(const stbtt_fontinfo *info, int *ascent, int *descent, int *lineGap) { if (ascent ) *ascent = ttSHORT(info->data+info->hhea + 4); if (descent) *descent = ttSHORT(info->data+info->hhea + 6); if (lineGap) *lineGap = ttSHORT(info->data+info->hhea + 8); } STBTT_DEF int stbtt_GetFontVMetricsOS2(const stbtt_fontinfo *info, int *typoAscent, int *typoDescent, int *typoLineGap) { int tab = stbtt__find_table(info->data, info->fontstart, "OS/2"); if (!tab) return 0; if (typoAscent ) *typoAscent = ttSHORT(info->data+tab + 68); if (typoDescent) *typoDescent = ttSHORT(info->data+tab + 70); if (typoLineGap) *typoLineGap = ttSHORT(info->data+tab + 72); return 1; } STBTT_DEF void stbtt_GetFontBoundingBox(const stbtt_fontinfo *info, int *x0, int *y0, int *x1, int *y1) { *x0 = ttSHORT(info->data + info->head + 36); *y0 = ttSHORT(info->data + info->head + 38); *x1 = ttSHORT(info->data + info->head + 40); *y1 = ttSHORT(info->data + info->head + 42); } STBTT_DEF float stbtt_ScaleForPixelHeight(const stbtt_fontinfo *info, float height) { int fheight = ttSHORT(info->data + info->hhea + 4) - ttSHORT(info->data + info->hhea + 6); return (float) height / fheight; } STBTT_DEF float stbtt_ScaleForMappingEmToPixels(const stbtt_fontinfo *info, float pixels) { int unitsPerEm = ttUSHORT(info->data + info->head + 18); return pixels / unitsPerEm; } STBTT_DEF void stbtt_FreeShape(const stbtt_fontinfo *info, stbtt_vertex *v) { STBTT_free(v, info->userdata); } STBTT_DEF stbtt_uint8 *stbtt_FindSVGDoc(const stbtt_fontinfo *info, int gl) { int i; stbtt_uint8 *data = info->data; stbtt_uint8 *svg_doc_list = data + stbtt__get_svg((stbtt_fontinfo *) info); int numEntries = ttUSHORT(svg_doc_list); stbtt_uint8 *svg_docs = svg_doc_list + 2; for(i=0; i= ttUSHORT(svg_doc)) && (gl <= ttUSHORT(svg_doc + 2))) return svg_doc; } return 0; } STBTT_DEF int stbtt_GetGlyphSVG(const stbtt_fontinfo *info, int gl, const char **svg) { stbtt_uint8 *data = info->data; stbtt_uint8 *svg_doc; if (info->svg == 0) return 0; svg_doc = stbtt_FindSVGDoc(info, gl); if (svg_doc != NULL) { *svg = (char *) data + info->svg + ttULONG(svg_doc + 4); return ttULONG(svg_doc + 8); } else { return 0; } } STBTT_DEF int stbtt_GetCodepointSVG(const stbtt_fontinfo *info, int unicode_codepoint, const char **svg) { return stbtt_GetGlyphSVG(info, stbtt_FindGlyphIndex(info, unicode_codepoint), svg); } ////////////////////////////////////////////////////////////////////////////// // // antialiasing software rasterizer // STBTT_DEF void stbtt_GetGlyphBitmapBoxSubpixel(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y,float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1) { int x0=0,y0=0,x1,y1; // =0 suppresses compiler warning if (!stbtt_GetGlyphBox(font, glyph, &x0,&y0,&x1,&y1)) { // e.g. space character if (ix0) *ix0 = 0; if (iy0) *iy0 = 0; if (ix1) *ix1 = 0; if (iy1) *iy1 = 0; } else { // move to integral bboxes (treating pixels as little squares, what pixels get touched)? if (ix0) *ix0 = STBTT_ifloor( x0 * scale_x + shift_x); if (iy0) *iy0 = STBTT_ifloor(-y1 * scale_y + shift_y); if (ix1) *ix1 = STBTT_iceil ( x1 * scale_x + shift_x); if (iy1) *iy1 = STBTT_iceil (-y0 * scale_y + shift_y); } } STBTT_DEF void stbtt_GetGlyphBitmapBox(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1) { stbtt_GetGlyphBitmapBoxSubpixel(font, glyph, scale_x, scale_y,0.0f,0.0f, ix0, iy0, ix1, iy1); } STBTT_DEF void stbtt_GetCodepointBitmapBoxSubpixel(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1) { stbtt_GetGlyphBitmapBoxSubpixel(font, stbtt_FindGlyphIndex(font,codepoint), scale_x, scale_y,shift_x,shift_y, ix0,iy0,ix1,iy1); } STBTT_DEF void stbtt_GetCodepointBitmapBox(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1) { stbtt_GetCodepointBitmapBoxSubpixel(font, codepoint, scale_x, scale_y,0.0f,0.0f, ix0,iy0,ix1,iy1); } ////////////////////////////////////////////////////////////////////////////// // // Rasterizer typedef struct stbtt__hheap_chunk { struct stbtt__hheap_chunk *next; } stbtt__hheap_chunk; typedef struct stbtt__hheap { struct stbtt__hheap_chunk *head; void *first_free; int num_remaining_in_head_chunk; } stbtt__hheap; static void *stbtt__hheap_alloc(stbtt__hheap *hh, size_t size, void *userdata) { if (hh->first_free) { void *p = hh->first_free; hh->first_free = * (void **) p; return p; } else { if (hh->num_remaining_in_head_chunk == 0) { int count = (size < 32 ? 2000 : size < 128 ? 800 : 100); stbtt__hheap_chunk *c = (stbtt__hheap_chunk *) STBTT_malloc(sizeof(stbtt__hheap_chunk) + size * count, userdata); if (c == NULL) return NULL; c->next = hh->head; hh->head = c; hh->num_remaining_in_head_chunk = count; } --hh->num_remaining_in_head_chunk; return (char *) (hh->head) + sizeof(stbtt__hheap_chunk) + size * hh->num_remaining_in_head_chunk; } } static void stbtt__hheap_free(stbtt__hheap *hh, void *p) { *(void **) p = hh->first_free; hh->first_free = p; } static void stbtt__hheap_cleanup(stbtt__hheap *hh, void *userdata) { stbtt__hheap_chunk *c = hh->head; while (c) { stbtt__hheap_chunk *n = c->next; STBTT_free(c, userdata); c = n; } } typedef struct stbtt__edge { float x0,y0, x1,y1; int invert; } stbtt__edge; typedef struct stbtt__active_edge { struct stbtt__active_edge *next; #if STBTT_RASTERIZER_VERSION==1 int x,dx; float ey; int direction; #elif STBTT_RASTERIZER_VERSION==2 float fx,fdx,fdy; float direction; float sy; float ey; #else #error "Unrecognized value of STBTT_RASTERIZER_VERSION" #endif } stbtt__active_edge; #if STBTT_RASTERIZER_VERSION == 1 #define STBTT_FIXSHIFT 10 #define STBTT_FIX (1 << STBTT_FIXSHIFT) #define STBTT_FIXMASK (STBTT_FIX-1) static stbtt__active_edge *stbtt__new_active(stbtt__hheap *hh, stbtt__edge *e, int off_x, float start_point, void *userdata) { stbtt__active_edge *z = (stbtt__active_edge *) stbtt__hheap_alloc(hh, sizeof(*z), userdata); float dxdy = (e->x1 - e->x0) / (e->y1 - e->y0); STBTT_assert(z != NULL); if (!z) return z; // round dx down to avoid overshooting if (dxdy < 0) z->dx = -STBTT_ifloor(STBTT_FIX * -dxdy); else z->dx = STBTT_ifloor(STBTT_FIX * dxdy); z->x = STBTT_ifloor(STBTT_FIX * e->x0 + z->dx * (start_point - e->y0)); // use z->dx so when we offset later it's by the same amount z->x -= off_x * STBTT_FIX; z->ey = e->y1; z->next = 0; z->direction = e->invert ? 1 : -1; return z; } #elif STBTT_RASTERIZER_VERSION == 2 static stbtt__active_edge *stbtt__new_active(stbtt__hheap *hh, stbtt__edge *e, int off_x, float start_point, void *userdata) { stbtt__active_edge *z = (stbtt__active_edge *) stbtt__hheap_alloc(hh, sizeof(*z), userdata); float dxdy = (e->x1 - e->x0) / (e->y1 - e->y0); STBTT_assert(z != NULL); //STBTT_assert(e->y0 <= start_point); if (!z) return z; z->fdx = dxdy; z->fdy = dxdy != 0.0f ? (1.0f/dxdy) : 0.0f; z->fx = e->x0 + dxdy * (start_point - e->y0); z->fx -= off_x; z->direction = e->invert ? 1.0f : -1.0f; z->sy = e->y0; z->ey = e->y1; z->next = 0; return z; } #else #error "Unrecognized value of STBTT_RASTERIZER_VERSION" #endif #if STBTT_RASTERIZER_VERSION == 1 // note: this routine clips fills that extend off the edges... ideally this // wouldn't happen, but it could happen if the truetype glyph bounding boxes // are wrong, or if the user supplies a too-small bitmap static void stbtt__fill_active_edges(unsigned char *scanline, int len, stbtt__active_edge *e, int max_weight) { // non-zero winding fill int x0=0, w=0; while (e) { if (w == 0) { // if we're currently at zero, we need to record the edge start point x0 = e->x; w += e->direction; } else { int x1 = e->x; w += e->direction; // if we went to zero, we need to draw if (w == 0) { int i = x0 >> STBTT_FIXSHIFT; int j = x1 >> STBTT_FIXSHIFT; if (i < len && j >= 0) { if (i == j) { // x0,x1 are the same pixel, so compute combined coverage scanline[i] = scanline[i] + (stbtt_uint8) ((x1 - x0) * max_weight >> STBTT_FIXSHIFT); } else { if (i >= 0) // add antialiasing for x0 scanline[i] = scanline[i] + (stbtt_uint8) (((STBTT_FIX - (x0 & STBTT_FIXMASK)) * max_weight) >> STBTT_FIXSHIFT); else i = -1; // clip if (j < len) // add antialiasing for x1 scanline[j] = scanline[j] + (stbtt_uint8) (((x1 & STBTT_FIXMASK) * max_weight) >> STBTT_FIXSHIFT); else j = len; // clip for (++i; i < j; ++i) // fill pixels between x0 and x1 scanline[i] = scanline[i] + (stbtt_uint8) max_weight; } } } } e = e->next; } } static void stbtt__rasterize_sorted_edges(stbtt__bitmap *result, stbtt__edge *e, int n, int vsubsample, int off_x, int off_y, void *userdata) { stbtt__hheap hh = { 0, 0, 0 }; stbtt__active_edge *active = NULL; int y,j=0; int max_weight = (255 / vsubsample); // weight per vertical scanline int s; // vertical subsample index unsigned char scanline_data[512], *scanline; if (result->w > 512) scanline = (unsigned char *) STBTT_malloc(result->w, userdata); else scanline = scanline_data; y = off_y * vsubsample; e[n].y0 = (off_y + result->h) * (float) vsubsample + 1; while (j < result->h) { STBTT_memset(scanline, 0, result->w); for (s=0; s < vsubsample; ++s) { // find center of pixel for this scanline float scan_y = y + 0.5f; stbtt__active_edge **step = &active; // update all active edges; // remove all active edges that terminate before the center of this scanline while (*step) { stbtt__active_edge * z = *step; if (z->ey <= scan_y) { *step = z->next; // delete from list STBTT_assert(z->direction); z->direction = 0; stbtt__hheap_free(&hh, z); } else { z->x += z->dx; // advance to position for current scanline step = &((*step)->next); // advance through list } } // resort the list if needed for(;;) { int changed=0; step = &active; while (*step && (*step)->next) { if ((*step)->x > (*step)->next->x) { stbtt__active_edge *t = *step; stbtt__active_edge *q = t->next; t->next = q->next; q->next = t; *step = q; changed = 1; } step = &(*step)->next; } if (!changed) break; } // insert all edges that start before the center of this scanline -- omit ones that also end on this scanline while (e->y0 <= scan_y) { if (e->y1 > scan_y) { stbtt__active_edge *z = stbtt__new_active(&hh, e, off_x, scan_y, userdata); if (z != NULL) { // find insertion point if (active == NULL) active = z; else if (z->x < active->x) { // insert at front z->next = active; active = z; } else { // find thing to insert AFTER stbtt__active_edge *p = active; while (p->next && p->next->x < z->x) p = p->next; // at this point, p->next->x is NOT < z->x z->next = p->next; p->next = z; } } } ++e; } // now process all active edges in XOR fashion if (active) stbtt__fill_active_edges(scanline, result->w, active, max_weight); ++y; } STBTT_memcpy(result->pixels + j * result->stride, scanline, result->w); ++j; } stbtt__hheap_cleanup(&hh, userdata); if (scanline != scanline_data) STBTT_free(scanline, userdata); } #elif STBTT_RASTERIZER_VERSION == 2 // the edge passed in here does not cross the vertical line at x or the vertical line at x+1 // (i.e. it has already been clipped to those) static void stbtt__handle_clipped_edge(float *scanline, int x, stbtt__active_edge *e, float x0, float y0, float x1, float y1) { if (y0 == y1) return; STBTT_assert(y0 < y1); STBTT_assert(e->sy <= e->ey); if (y0 > e->ey) return; if (y1 < e->sy) return; if (y0 < e->sy) { x0 += (x1-x0) * (e->sy - y0) / (y1-y0); y0 = e->sy; } if (y1 > e->ey) { x1 += (x1-x0) * (e->ey - y1) / (y1-y0); y1 = e->ey; } if (x0 == x) STBTT_assert(x1 <= x+1); else if (x0 == x+1) STBTT_assert(x1 >= x); else if (x0 <= x) STBTT_assert(x1 <= x); else if (x0 >= x+1) STBTT_assert(x1 >= x+1); else STBTT_assert(x1 >= x && x1 <= x+1); if (x0 <= x && x1 <= x) scanline[x] += e->direction * (y1-y0); else if (x0 >= x+1 && x1 >= x+1) ; else { STBTT_assert(x0 >= x && x0 <= x+1 && x1 >= x && x1 <= x+1); scanline[x] += e->direction * (y1-y0) * (1-((x0-x)+(x1-x))/2); // coverage = 1 - average x position } } static float stbtt__sized_trapezoid_area(float height, float top_width, float bottom_width) { STBTT_assert(top_width >= 0); STBTT_assert(bottom_width >= 0); return (top_width + bottom_width) / 2.0f * height; } static float stbtt__position_trapezoid_area(float height, float tx0, float tx1, float bx0, float bx1) { return stbtt__sized_trapezoid_area(height, tx1 - tx0, bx1 - bx0); } static float stbtt__sized_triangle_area(float height, float width) { return height * width / 2; } static void stbtt__fill_active_edges_new(float *scanline, float *scanline_fill, int len, stbtt__active_edge *e, float y_top) { float y_bottom = y_top+1; while (e) { // brute force every pixel // compute intersection points with top & bottom STBTT_assert(e->ey >= y_top); if (e->fdx == 0) { float x0 = e->fx; if (x0 < len) { if (x0 >= 0) { stbtt__handle_clipped_edge(scanline,(int) x0,e, x0,y_top, x0,y_bottom); stbtt__handle_clipped_edge(scanline_fill-1,(int) x0+1,e, x0,y_top, x0,y_bottom); } else { stbtt__handle_clipped_edge(scanline_fill-1,0,e, x0,y_top, x0,y_bottom); } } } else { float x0 = e->fx; float dx = e->fdx; float xb = x0 + dx; float x_top, x_bottom; float sy0,sy1; float dy = e->fdy; STBTT_assert(e->sy <= y_bottom && e->ey >= y_top); // compute endpoints of line segment clipped to this scanline (if the // line segment starts on this scanline. x0 is the intersection of the // line with y_top, but that may be off the line segment. if (e->sy > y_top) { x_top = x0 + dx * (e->sy - y_top); sy0 = e->sy; } else { x_top = x0; sy0 = y_top; } if (e->ey < y_bottom) { x_bottom = x0 + dx * (e->ey - y_top); sy1 = e->ey; } else { x_bottom = xb; sy1 = y_bottom; } if (x_top >= 0 && x_bottom >= 0 && x_top < len && x_bottom < len) { // from here on, we don't have to range check x values if ((int) x_top == (int) x_bottom) { float height; // simple case, only spans one pixel int x = (int) x_top; height = (sy1 - sy0) * e->direction; STBTT_assert(x >= 0 && x < len); scanline[x] += stbtt__position_trapezoid_area(height, x_top, x+1.0f, x_bottom, x+1.0f); scanline_fill[x] += height; // everything right of this pixel is filled } else { int x,x1,x2; float y_crossing, y_final, step, sign, area; // covers 2+ pixels if (x_top > x_bottom) { // flip scanline vertically; signed area is the same float t; sy0 = y_bottom - (sy0 - y_top); sy1 = y_bottom - (sy1 - y_top); t = sy0, sy0 = sy1, sy1 = t; t = x_bottom, x_bottom = x_top, x_top = t; dx = -dx; dy = -dy; t = x0, x0 = xb, xb = t; } STBTT_assert(dy >= 0); STBTT_assert(dx >= 0); x1 = (int) x_top; x2 = (int) x_bottom; // compute intersection with y axis at x1+1 y_crossing = y_top + dy * (x1+1 - x0); // compute intersection with y axis at x2 y_final = y_top + dy * (x2 - x0); // x1 x_top x2 x_bottom // y_top +------|-----+------------+------------+--------|---+------------+ // | | | | | | // | | | | | | // sy0 | Txxxxx|............|............|............|............| // y_crossing | *xxxxx.......|............|............|............| // | | xxxxx..|............|............|............| // | | /- xx*xxxx........|............|............| // | | dy < | xxxxxx..|............|............| // y_final | | \- | xx*xxx.........|............| // sy1 | | | | xxxxxB...|............| // | | | | | | // | | | | | | // y_bottom +------------+------------+------------+------------+------------+ // // goal is to measure the area covered by '.' in each pixel // if x2 is right at the right edge of x1, y_crossing can blow up, github #1057 // @TODO: maybe test against sy1 rather than y_bottom? if (y_crossing > y_bottom) y_crossing = y_bottom; sign = e->direction; // area of the rectangle covered from sy0..y_crossing area = sign * (y_crossing-sy0); // area of the triangle (x_top,sy0), (x1+1,sy0), (x1+1,y_crossing) scanline[x1] += stbtt__sized_triangle_area(area, x1+1 - x_top); // check if final y_crossing is blown up; no test case for this if (y_final > y_bottom) { y_final = y_bottom; dy = (y_final - y_crossing ) / (x2 - (x1+1)); // if denom=0, y_final = y_crossing, so y_final <= y_bottom } // in second pixel, area covered by line segment found in first pixel // is always a rectangle 1 wide * the height of that line segment; this // is exactly what the variable 'area' stores. it also gets a contribution // from the line segment within it. the THIRD pixel will get the first // pixel's rectangle contribution, the second pixel's rectangle contribution, // and its own contribution. the 'own contribution' is the same in every pixel except // the leftmost and rightmost, a trapezoid that slides down in each pixel. // the second pixel's contribution to the third pixel will be the // rectangle 1 wide times the height change in the second pixel, which is dy. step = sign * dy * 1; // dy is dy/dx, change in y for every 1 change in x, // which multiplied by 1-pixel-width is how much pixel area changes for each step in x // so the area advances by 'step' every time for (x = x1+1; x < x2; ++x) { scanline[x] += area + step/2; // area of trapezoid is 1*step/2 area += step; } STBTT_assert(STBTT_fabs(area) <= 1.01f); // accumulated error from area += step unless we round step down STBTT_assert(sy1 > y_final-0.01f); // area covered in the last pixel is the rectangle from all the pixels to the left, // plus the trapezoid filled by the line segment in this pixel all the way to the right edge scanline[x2] += area + sign * stbtt__position_trapezoid_area(sy1-y_final, (float) x2, x2+1.0f, x_bottom, x2+1.0f); // the rest of the line is filled based on the total height of the line segment in this pixel scanline_fill[x2] += sign * (sy1-sy0); } } else { // if edge goes outside of box we're drawing, we require // clipping logic. since this does not match the intended use // of this library, we use a different, very slow brute // force implementation // note though that this does happen some of the time because // x_top and x_bottom can be extrapolated at the top & bottom of // the shape and actually lie outside the bounding box int x; for (x=0; x < len; ++x) { // cases: // // there can be up to two intersections with the pixel. any intersection // with left or right edges can be handled by splitting into two (or three) // regions. intersections with top & bottom do not necessitate case-wise logic. // // the old way of doing this found the intersections with the left & right edges, // then used some simple logic to produce up to three segments in sorted order // from top-to-bottom. however, this had a problem: if an x edge was epsilon // across the x border, then the corresponding y position might not be distinct // from the other y segment, and it might ignored as an empty segment. to avoid // that, we need to explicitly produce segments based on x positions. // rename variables to clearly-defined pairs float y0 = y_top; float x1 = (float) (x); float x2 = (float) (x+1); float x3 = xb; float y3 = y_bottom; // x = e->x + e->dx * (y-y_top) // (y-y_top) = (x - e->x) / e->dx // y = (x - e->x) / e->dx + y_top float y1 = (x - x0) / dx + y_top; float y2 = (x+1 - x0) / dx + y_top; if (x0 < x1 && x3 > x2) { // three segments descending down-right stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x1,y1); stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x2,y2); stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x3,y3); } else if (x3 < x1 && x0 > x2) { // three segments descending down-left stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x2,y2); stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x1,y1); stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x3,y3); } else if (x0 < x1 && x3 > x1) { // two segments across x, down-right stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x1,y1); stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x3,y3); } else if (x3 < x1 && x0 > x1) { // two segments across x, down-left stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x1,y1); stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x3,y3); } else if (x0 < x2 && x3 > x2) { // two segments across x+1, down-right stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x2,y2); stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x3,y3); } else if (x3 < x2 && x0 > x2) { // two segments across x+1, down-left stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x2,y2); stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x3,y3); } else { // one segment stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x3,y3); } } } } e = e->next; } } // directly AA rasterize edges w/o supersampling static void stbtt__rasterize_sorted_edges(stbtt__bitmap *result, stbtt__edge *e, int n, int vsubsample, int off_x, int off_y, void *userdata) { stbtt__hheap hh = { 0, 0, 0 }; stbtt__active_edge *active = NULL; int y,j=0, i; float scanline_data[129], *scanline, *scanline2; STBTT__NOTUSED(vsubsample); if (result->w > 64) scanline = (float *) STBTT_malloc((result->w*2+1) * sizeof(float), userdata); else scanline = scanline_data; scanline2 = scanline + result->w; y = off_y; e[n].y0 = (float) (off_y + result->h) + 1; while (j < result->h) { // find center of pixel for this scanline float scan_y_top = y + 0.0f; float scan_y_bottom = y + 1.0f; stbtt__active_edge **step = &active; STBTT_memset(scanline , 0, result->w*sizeof(scanline[0])); STBTT_memset(scanline2, 0, (result->w+1)*sizeof(scanline[0])); // update all active edges; // remove all active edges that terminate before the top of this scanline while (*step) { stbtt__active_edge * z = *step; if (z->ey <= scan_y_top) { *step = z->next; // delete from list STBTT_assert(z->direction); z->direction = 0; stbtt__hheap_free(&hh, z); } else { step = &((*step)->next); // advance through list } } // insert all edges that start before the bottom of this scanline while (e->y0 <= scan_y_bottom) { if (e->y0 != e->y1) { stbtt__active_edge *z = stbtt__new_active(&hh, e, off_x, scan_y_top, userdata); if (z != NULL) { if (j == 0 && off_y != 0) { if (z->ey < scan_y_top) { // this can happen due to subpixel positioning and some kind of fp rounding error i think z->ey = scan_y_top; } } STBTT_assert(z->ey >= scan_y_top); // if we get really unlucky a tiny bit of an edge can be out of bounds // insert at front z->next = active; active = z; } } ++e; } // now process all active edges if (active) stbtt__fill_active_edges_new(scanline, scanline2+1, result->w, active, scan_y_top); { float sum = 0; for (i=0; i < result->w; ++i) { float k; int m; sum += scanline2[i]; k = scanline[i] + sum; k = (float) STBTT_fabs(k)*255 + 0.5f; m = (int) k; if (m > 255) m = 255; result->pixels[j*result->stride + i] = (unsigned char) m; } } // advance all the edges step = &active; while (*step) { stbtt__active_edge *z = *step; z->fx += z->fdx; // advance to position for current scanline step = &((*step)->next); // advance through list } ++y; ++j; } stbtt__hheap_cleanup(&hh, userdata); if (scanline != scanline_data) STBTT_free(scanline, userdata); } #else #error "Unrecognized value of STBTT_RASTERIZER_VERSION" #endif #define STBTT__COMPARE(a,b) ((a)->y0 < (b)->y0) static void stbtt__sort_edges_ins_sort(stbtt__edge *p, int n) { int i,j; for (i=1; i < n; ++i) { stbtt__edge t = p[i], *a = &t; j = i; while (j > 0) { stbtt__edge *b = &p[j-1]; int c = STBTT__COMPARE(a,b); if (!c) break; p[j] = p[j-1]; --j; } if (i != j) p[j] = t; } } static void stbtt__sort_edges_quicksort(stbtt__edge *p, int n) { /* threshold for transitioning to insertion sort */ while (n > 12) { stbtt__edge t; int c01,c12,c,m,i,j; /* compute median of three */ m = n >> 1; c01 = STBTT__COMPARE(&p[0],&p[m]); c12 = STBTT__COMPARE(&p[m],&p[n-1]); /* if 0 >= mid >= end, or 0 < mid < end, then use mid */ if (c01 != c12) { /* otherwise, we'll need to swap something else to middle */ int z; c = STBTT__COMPARE(&p[0],&p[n-1]); /* 0>mid && midn => n; 0 0 */ /* 0n: 0>n => 0; 0 n */ z = (c == c12) ? 0 : n-1; t = p[z]; p[z] = p[m]; p[m] = t; } /* now p[m] is the median-of-three */ /* swap it to the beginning so it won't move around */ t = p[0]; p[0] = p[m]; p[m] = t; /* partition loop */ i=1; j=n-1; for(;;) { /* handling of equality is crucial here */ /* for sentinels & efficiency with duplicates */ for (;;++i) { if (!STBTT__COMPARE(&p[i], &p[0])) break; } for (;;--j) { if (!STBTT__COMPARE(&p[0], &p[j])) break; } /* make sure we haven't crossed */ if (i >= j) break; t = p[i]; p[i] = p[j]; p[j] = t; ++i; --j; } /* recurse on smaller side, iterate on larger */ if (j < (n-i)) { stbtt__sort_edges_quicksort(p,j); p = p+i; n = n-i; } else { stbtt__sort_edges_quicksort(p+i, n-i); n = j; } } } static void stbtt__sort_edges(stbtt__edge *p, int n) { stbtt__sort_edges_quicksort(p, n); stbtt__sort_edges_ins_sort(p, n); } typedef struct { float x,y; } stbtt__point; static void stbtt__rasterize(stbtt__bitmap *result, stbtt__point *pts, int *wcount, int windings, float scale_x, float scale_y, float shift_x, float shift_y, int off_x, int off_y, int invert, void *userdata) { float y_scale_inv = invert ? -scale_y : scale_y; stbtt__edge *e; int n,i,j,k,m; #if STBTT_RASTERIZER_VERSION == 1 int vsubsample = result->h < 8 ? 15 : 5; #elif STBTT_RASTERIZER_VERSION == 2 int vsubsample = 1; #else #error "Unrecognized value of STBTT_RASTERIZER_VERSION" #endif // vsubsample should divide 255 evenly; otherwise we won't reach full opacity // now we have to blow out the windings into explicit edge lists n = 0; for (i=0; i < windings; ++i) n += wcount[i]; e = (stbtt__edge *) STBTT_malloc(sizeof(*e) * (n+1), userdata); // add an extra one as a sentinel if (e == 0) return; n = 0; m=0; for (i=0; i < windings; ++i) { stbtt__point *p = pts + m; m += wcount[i]; j = wcount[i]-1; for (k=0; k < wcount[i]; j=k++) { int a=k,b=j; // skip the edge if horizontal if (p[j].y == p[k].y) continue; // add edge from j to k to the list e[n].invert = 0; if (invert ? p[j].y > p[k].y : p[j].y < p[k].y) { e[n].invert = 1; a=j,b=k; } e[n].x0 = p[a].x * scale_x + shift_x; e[n].y0 = (p[a].y * y_scale_inv + shift_y) * vsubsample; e[n].x1 = p[b].x * scale_x + shift_x; e[n].y1 = (p[b].y * y_scale_inv + shift_y) * vsubsample; ++n; } } // now sort the edges by their highest point (should snap to integer, and then by x) //STBTT_sort(e, n, sizeof(e[0]), stbtt__edge_compare); stbtt__sort_edges(e, n); // now, traverse the scanlines and find the intersections on each scanline, use xor winding rule stbtt__rasterize_sorted_edges(result, e, n, vsubsample, off_x, off_y, userdata); STBTT_free(e, userdata); } static void stbtt__add_point(stbtt__point *points, int n, float x, float y) { if (!points) return; // during first pass, it's unallocated points[n].x = x; points[n].y = y; } // tessellate until threshold p is happy... @TODO warped to compensate for non-linear stretching static int stbtt__tesselate_curve(stbtt__point *points, int *num_points, float x0, float y0, float x1, float y1, float x2, float y2, float objspace_flatness_squared, int n) { // midpoint float mx = (x0 + 2*x1 + x2)/4; float my = (y0 + 2*y1 + y2)/4; // versus directly drawn line float dx = (x0+x2)/2 - mx; float dy = (y0+y2)/2 - my; if (n > 16) // 65536 segments on one curve better be enough! return 1; if (dx*dx+dy*dy > objspace_flatness_squared) { // half-pixel error allowed... need to be smaller if AA stbtt__tesselate_curve(points, num_points, x0,y0, (x0+x1)/2.0f,(y0+y1)/2.0f, mx,my, objspace_flatness_squared,n+1); stbtt__tesselate_curve(points, num_points, mx,my, (x1+x2)/2.0f,(y1+y2)/2.0f, x2,y2, objspace_flatness_squared,n+1); } else { stbtt__add_point(points, *num_points,x2,y2); *num_points = *num_points+1; } return 1; } static void stbtt__tesselate_cubic(stbtt__point *points, int *num_points, float x0, float y0, float x1, float y1, float x2, float y2, float x3, float y3, float objspace_flatness_squared, int n) { // @TODO this "flatness" calculation is just made-up nonsense that seems to work well enough float dx0 = x1-x0; float dy0 = y1-y0; float dx1 = x2-x1; float dy1 = y2-y1; float dx2 = x3-x2; float dy2 = y3-y2; float dx = x3-x0; float dy = y3-y0; float longlen = (float) (STBTT_sqrt(dx0*dx0+dy0*dy0)+STBTT_sqrt(dx1*dx1+dy1*dy1)+STBTT_sqrt(dx2*dx2+dy2*dy2)); float shortlen = (float) STBTT_sqrt(dx*dx+dy*dy); float flatness_squared = longlen*longlen-shortlen*shortlen; if (n > 16) // 65536 segments on one curve better be enough! return; if (flatness_squared > objspace_flatness_squared) { float x01 = (x0+x1)/2; float y01 = (y0+y1)/2; float x12 = (x1+x2)/2; float y12 = (y1+y2)/2; float x23 = (x2+x3)/2; float y23 = (y2+y3)/2; float xa = (x01+x12)/2; float ya = (y01+y12)/2; float xb = (x12+x23)/2; float yb = (y12+y23)/2; float mx = (xa+xb)/2; float my = (ya+yb)/2; stbtt__tesselate_cubic(points, num_points, x0,y0, x01,y01, xa,ya, mx,my, objspace_flatness_squared,n+1); stbtt__tesselate_cubic(points, num_points, mx,my, xb,yb, x23,y23, x3,y3, objspace_flatness_squared,n+1); } else { stbtt__add_point(points, *num_points,x3,y3); *num_points = *num_points+1; } } // returns number of contours static stbtt__point *stbtt_FlattenCurves(stbtt_vertex *vertices, int num_verts, float objspace_flatness, int **contour_lengths, int *num_contours, void *userdata) { stbtt__point *points=0; int num_points=0; float objspace_flatness_squared = objspace_flatness * objspace_flatness; int i,n=0,start=0, pass; // count how many "moves" there are to get the contour count for (i=0; i < num_verts; ++i) if (vertices[i].type == STBTT_vmove) ++n; *num_contours = n; if (n == 0) return 0; *contour_lengths = (int *) STBTT_malloc(sizeof(**contour_lengths) * n, userdata); if (*contour_lengths == 0) { *num_contours = 0; return 0; } // make two passes through the points so we don't need to realloc for (pass=0; pass < 2; ++pass) { float x=0,y=0; if (pass == 1) { points = (stbtt__point *) STBTT_malloc(num_points * sizeof(points[0]), userdata); if (points == NULL) goto error; } num_points = 0; n= -1; for (i=0; i < num_verts; ++i) { switch (vertices[i].type) { case STBTT_vmove: // start the next contour if (n >= 0) (*contour_lengths)[n] = num_points - start; ++n; start = num_points; x = vertices[i].x, y = vertices[i].y; stbtt__add_point(points, num_points++, x,y); break; case STBTT_vline: x = vertices[i].x, y = vertices[i].y; stbtt__add_point(points, num_points++, x, y); break; case STBTT_vcurve: stbtt__tesselate_curve(points, &num_points, x,y, vertices[i].cx, vertices[i].cy, vertices[i].x, vertices[i].y, objspace_flatness_squared, 0); x = vertices[i].x, y = vertices[i].y; break; case STBTT_vcubic: stbtt__tesselate_cubic(points, &num_points, x,y, vertices[i].cx, vertices[i].cy, vertices[i].cx1, vertices[i].cy1, vertices[i].x, vertices[i].y, objspace_flatness_squared, 0); x = vertices[i].x, y = vertices[i].y; break; } } (*contour_lengths)[n] = num_points - start; } return points; error: STBTT_free(points, userdata); STBTT_free(*contour_lengths, userdata); *contour_lengths = 0; *num_contours = 0; return NULL; } STBTT_DEF void stbtt_Rasterize(stbtt__bitmap *result, float flatness_in_pixels, stbtt_vertex *vertices, int num_verts, float scale_x, float scale_y, float shift_x, float shift_y, int x_off, int y_off, int invert, void *userdata) { float scale = scale_x > scale_y ? scale_y : scale_x; int winding_count = 0; int *winding_lengths = NULL; stbtt__point *windings = stbtt_FlattenCurves(vertices, num_verts, flatness_in_pixels / scale, &winding_lengths, &winding_count, userdata); if (windings) { stbtt__rasterize(result, windings, winding_lengths, winding_count, scale_x, scale_y, shift_x, shift_y, x_off, y_off, invert, userdata); STBTT_free(winding_lengths, userdata); STBTT_free(windings, userdata); } } STBTT_DEF void stbtt_FreeBitmap(unsigned char *bitmap, void *userdata) { STBTT_free(bitmap, userdata); } STBTT_DEF unsigned char *stbtt_GetGlyphBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int glyph, int *width, int *height, int *xoff, int *yoff) { int ix0,iy0,ix1,iy1; stbtt__bitmap gbm; stbtt_vertex *vertices; int num_verts = stbtt_GetGlyphShape(info, glyph, &vertices); if (scale_x == 0) scale_x = scale_y; if (scale_y == 0) { if (scale_x == 0) { STBTT_free(vertices, info->userdata); return NULL; } scale_y = scale_x; } stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale_x, scale_y, shift_x, shift_y, &ix0,&iy0,&ix1,&iy1); // now we get the size gbm.w = (ix1 - ix0); gbm.h = (iy1 - iy0); gbm.pixels = NULL; // in case we error if (width ) *width = gbm.w; if (height) *height = gbm.h; if (xoff ) *xoff = ix0; if (yoff ) *yoff = iy0; if (gbm.w && gbm.h) { gbm.pixels = (unsigned char *) STBTT_malloc(gbm.w * gbm.h, info->userdata); if (gbm.pixels) { gbm.stride = gbm.w; stbtt_Rasterize(&gbm, 0.35f, vertices, num_verts, scale_x, scale_y, shift_x, shift_y, ix0, iy0, 1, info->userdata); } } STBTT_free(vertices, info->userdata); return gbm.pixels; } STBTT_DEF unsigned char *stbtt_GetGlyphBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int glyph, int *width, int *height, int *xoff, int *yoff) { return stbtt_GetGlyphBitmapSubpixel(info, scale_x, scale_y, 0.0f, 0.0f, glyph, width, height, xoff, yoff); } STBTT_DEF void stbtt_MakeGlyphBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int glyph) { int ix0,iy0; stbtt_vertex *vertices; int num_verts = stbtt_GetGlyphShape(info, glyph, &vertices); stbtt__bitmap gbm; stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale_x, scale_y, shift_x, shift_y, &ix0,&iy0,0,0); gbm.pixels = output; gbm.w = out_w; gbm.h = out_h; gbm.stride = out_stride; if (gbm.w && gbm.h) stbtt_Rasterize(&gbm, 0.35f, vertices, num_verts, scale_x, scale_y, shift_x, shift_y, ix0,iy0, 1, info->userdata); STBTT_free(vertices, info->userdata); } STBTT_DEF void stbtt_MakeGlyphBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int glyph) { stbtt_MakeGlyphBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, 0.0f,0.0f, glyph); } STBTT_DEF unsigned char *stbtt_GetCodepointBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint, int *width, int *height, int *xoff, int *yoff) { return stbtt_GetGlyphBitmapSubpixel(info, scale_x, scale_y,shift_x,shift_y, stbtt_FindGlyphIndex(info,codepoint), width,height,xoff,yoff); } STBTT_DEF void stbtt_MakeCodepointBitmapSubpixelPrefilter(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int oversample_x, int oversample_y, float *sub_x, float *sub_y, int codepoint) { stbtt_MakeGlyphBitmapSubpixelPrefilter(info, output, out_w, out_h, out_stride, scale_x, scale_y, shift_x, shift_y, oversample_x, oversample_y, sub_x, sub_y, stbtt_FindGlyphIndex(info,codepoint)); } STBTT_DEF void stbtt_MakeCodepointBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint) { stbtt_MakeGlyphBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, shift_x, shift_y, stbtt_FindGlyphIndex(info,codepoint)); } STBTT_DEF unsigned char *stbtt_GetCodepointBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int codepoint, int *width, int *height, int *xoff, int *yoff) { return stbtt_GetCodepointBitmapSubpixel(info, scale_x, scale_y, 0.0f,0.0f, codepoint, width,height,xoff,yoff); } STBTT_DEF void stbtt_MakeCodepointBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int codepoint) { stbtt_MakeCodepointBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, 0.0f,0.0f, codepoint); } ////////////////////////////////////////////////////////////////////////////// // // bitmap baking // // This is SUPER-CRAPPY packing to keep source code small static int stbtt_BakeFontBitmap_internal(unsigned char *data, int offset, // font location (use offset=0 for plain .ttf) float pixel_height, // height of font in pixels unsigned char *pixels, int pw, int ph, // bitmap to be filled in int first_char, int num_chars, // characters to bake stbtt_bakedchar *chardata) { float scale; int x,y,bottom_y, i; stbtt_fontinfo f; f.userdata = NULL; if (!stbtt_InitFont(&f, data, offset)) return -1; STBTT_memset(pixels, 0, pw*ph); // background of 0 around pixels x=y=1; bottom_y = 1; scale = stbtt_ScaleForPixelHeight(&f, pixel_height); for (i=0; i < num_chars; ++i) { int advance, lsb, x0,y0,x1,y1,gw,gh; int g = stbtt_FindGlyphIndex(&f, first_char + i); stbtt_GetGlyphHMetrics(&f, g, &advance, &lsb); stbtt_GetGlyphBitmapBox(&f, g, scale,scale, &x0,&y0,&x1,&y1); gw = x1-x0; gh = y1-y0; if (x + gw + 1 >= pw) y = bottom_y, x = 1; // advance to next row if (y + gh + 1 >= ph) // check if it fits vertically AFTER potentially moving to next row return -i; STBTT_assert(x+gw < pw); STBTT_assert(y+gh < ph); stbtt_MakeGlyphBitmap(&f, pixels+x+y*pw, gw,gh,pw, scale,scale, g); chardata[i].x0 = (stbtt_int16) x; chardata[i].y0 = (stbtt_int16) y; chardata[i].x1 = (stbtt_int16) (x + gw); chardata[i].y1 = (stbtt_int16) (y + gh); chardata[i].xadvance = scale * advance; chardata[i].xoff = (float) x0; chardata[i].yoff = (float) y0; x = x + gw + 1; if (y+gh+1 > bottom_y) bottom_y = y+gh+1; } return bottom_y; } STBTT_DEF void stbtt_GetBakedQuad(const stbtt_bakedchar *chardata, int pw, int ph, int char_index, float *xpos, float *ypos, stbtt_aligned_quad *q, int opengl_fillrule) { float d3d_bias = opengl_fillrule ? 0 : -0.5f; float ipw = 1.0f / pw, iph = 1.0f / ph; const stbtt_bakedchar *b = chardata + char_index; int round_x = STBTT_ifloor((*xpos + b->xoff) + 0.5f); int round_y = STBTT_ifloor((*ypos + b->yoff) + 0.5f); q->x0 = round_x + d3d_bias; q->y0 = round_y + d3d_bias; q->x1 = round_x + b->x1 - b->x0 + d3d_bias; q->y1 = round_y + b->y1 - b->y0 + d3d_bias; q->s0 = b->x0 * ipw; q->t0 = b->y0 * iph; q->s1 = b->x1 * ipw; q->t1 = b->y1 * iph; *xpos += b->xadvance; } ////////////////////////////////////////////////////////////////////////////// // // rectangle packing replacement routines if you don't have stb_rect_pack.h // #ifndef STB_RECT_PACK_VERSION typedef int stbrp_coord; //////////////////////////////////////////////////////////////////////////////////// // // // // // COMPILER WARNING ?!?!? // // // // // // if you get a compile warning due to these symbols being defined more than // // once, move #include "stb_rect_pack.h" before #include "stb_truetype.h" // // // //////////////////////////////////////////////////////////////////////////////////// typedef struct { int width,height; int x,y,bottom_y; } stbrp_context; typedef struct { unsigned char x; } stbrp_node; struct stbrp_rect { stbrp_coord x,y; int id,w,h,was_packed; }; static void stbrp_init_target(stbrp_context *con, int pw, int ph, stbrp_node *nodes, int num_nodes) { con->width = pw; con->height = ph; con->x = 0; con->y = 0; con->bottom_y = 0; STBTT__NOTUSED(nodes); STBTT__NOTUSED(num_nodes); } static void stbrp_pack_rects(stbrp_context *con, stbrp_rect *rects, int num_rects) { int i; for (i=0; i < num_rects; ++i) { if (con->x + rects[i].w > con->width) { con->x = 0; con->y = con->bottom_y; } if (con->y + rects[i].h > con->height) break; rects[i].x = con->x; rects[i].y = con->y; rects[i].was_packed = 1; con->x += rects[i].w; if (con->y + rects[i].h > con->bottom_y) con->bottom_y = con->y + rects[i].h; } for ( ; i < num_rects; ++i) rects[i].was_packed = 0; } #endif ////////////////////////////////////////////////////////////////////////////// // // bitmap baking // // This is SUPER-AWESOME (tm Ryan Gordon) packing using stb_rect_pack.h. If // stb_rect_pack.h isn't available, it uses the BakeFontBitmap strategy. STBTT_DEF int stbtt_PackBegin(stbtt_pack_context *spc, unsigned char *pixels, int pw, int ph, int stride_in_bytes, int padding, void *alloc_context) { stbrp_context *context = (stbrp_context *) STBTT_malloc(sizeof(*context) ,alloc_context); int num_nodes = pw - padding; stbrp_node *nodes = (stbrp_node *) STBTT_malloc(sizeof(*nodes ) * num_nodes,alloc_context); if (context == NULL || nodes == NULL) { if (context != NULL) STBTT_free(context, alloc_context); if (nodes != NULL) STBTT_free(nodes , alloc_context); return 0; } spc->user_allocator_context = alloc_context; spc->width = pw; spc->height = ph; spc->pixels = pixels; spc->pack_info = context; spc->nodes = nodes; spc->padding = padding; spc->stride_in_bytes = stride_in_bytes != 0 ? stride_in_bytes : pw; spc->h_oversample = 1; spc->v_oversample = 1; spc->skip_missing = 0; stbrp_init_target(context, pw-padding, ph-padding, nodes, num_nodes); if (pixels) STBTT_memset(pixels, 0, pw*ph); // background of 0 around pixels return 1; } STBTT_DEF void stbtt_PackEnd (stbtt_pack_context *spc) { STBTT_free(spc->nodes , spc->user_allocator_context); STBTT_free(spc->pack_info, spc->user_allocator_context); } STBTT_DEF void stbtt_PackSetOversampling(stbtt_pack_context *spc, unsigned int h_oversample, unsigned int v_oversample) { STBTT_assert(h_oversample <= STBTT_MAX_OVERSAMPLE); STBTT_assert(v_oversample <= STBTT_MAX_OVERSAMPLE); if (h_oversample <= STBTT_MAX_OVERSAMPLE) spc->h_oversample = h_oversample; if (v_oversample <= STBTT_MAX_OVERSAMPLE) spc->v_oversample = v_oversample; } STBTT_DEF void stbtt_PackSetSkipMissingCodepoints(stbtt_pack_context *spc, int skip) { spc->skip_missing = skip; } #define STBTT__OVER_MASK (STBTT_MAX_OVERSAMPLE-1) static void stbtt__h_prefilter(unsigned char *pixels, int w, int h, int stride_in_bytes, unsigned int kernel_width) { unsigned char buffer[STBTT_MAX_OVERSAMPLE]; int safe_w = w - kernel_width; int j; STBTT_memset(buffer, 0, STBTT_MAX_OVERSAMPLE); // suppress bogus warning from VS2013 -analyze for (j=0; j < h; ++j) { int i; unsigned int total; STBTT_memset(buffer, 0, kernel_width); total = 0; // make kernel_width a constant in common cases so compiler can optimize out the divide switch (kernel_width) { case 2: for (i=0; i <= safe_w; ++i) { total += pixels[i] - buffer[i & STBTT__OVER_MASK]; buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; pixels[i] = (unsigned char) (total / 2); } break; case 3: for (i=0; i <= safe_w; ++i) { total += pixels[i] - buffer[i & STBTT__OVER_MASK]; buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; pixels[i] = (unsigned char) (total / 3); } break; case 4: for (i=0; i <= safe_w; ++i) { total += pixels[i] - buffer[i & STBTT__OVER_MASK]; buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; pixels[i] = (unsigned char) (total / 4); } break; case 5: for (i=0; i <= safe_w; ++i) { total += pixels[i] - buffer[i & STBTT__OVER_MASK]; buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; pixels[i] = (unsigned char) (total / 5); } break; default: for (i=0; i <= safe_w; ++i) { total += pixels[i] - buffer[i & STBTT__OVER_MASK]; buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; pixels[i] = (unsigned char) (total / kernel_width); } break; } for (; i < w; ++i) { STBTT_assert(pixels[i] == 0); total -= buffer[i & STBTT__OVER_MASK]; pixels[i] = (unsigned char) (total / kernel_width); } pixels += stride_in_bytes; } } static void stbtt__v_prefilter(unsigned char *pixels, int w, int h, int stride_in_bytes, unsigned int kernel_width) { unsigned char buffer[STBTT_MAX_OVERSAMPLE]; int safe_h = h - kernel_width; int j; STBTT_memset(buffer, 0, STBTT_MAX_OVERSAMPLE); // suppress bogus warning from VS2013 -analyze for (j=0; j < w; ++j) { int i; unsigned int total; STBTT_memset(buffer, 0, kernel_width); total = 0; // make kernel_width a constant in common cases so compiler can optimize out the divide switch (kernel_width) { case 2: for (i=0; i <= safe_h; ++i) { total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; pixels[i*stride_in_bytes] = (unsigned char) (total / 2); } break; case 3: for (i=0; i <= safe_h; ++i) { total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; pixels[i*stride_in_bytes] = (unsigned char) (total / 3); } break; case 4: for (i=0; i <= safe_h; ++i) { total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; pixels[i*stride_in_bytes] = (unsigned char) (total / 4); } break; case 5: for (i=0; i <= safe_h; ++i) { total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; pixels[i*stride_in_bytes] = (unsigned char) (total / 5); } break; default: for (i=0; i <= safe_h; ++i) { total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; pixels[i*stride_in_bytes] = (unsigned char) (total / kernel_width); } break; } for (; i < h; ++i) { STBTT_assert(pixels[i*stride_in_bytes] == 0); total -= buffer[i & STBTT__OVER_MASK]; pixels[i*stride_in_bytes] = (unsigned char) (total / kernel_width); } pixels += 1; } } static float stbtt__oversample_shift(int oversample) { if (!oversample) return 0.0f; // The prefilter is a box filter of width "oversample", // which shifts phase by (oversample - 1)/2 pixels in // oversampled space. We want to shift in the opposite // direction to counter this. return (float)-(oversample - 1) / (2.0f * (float)oversample); } // rects array must be big enough to accommodate all characters in the given ranges STBTT_DEF int stbtt_PackFontRangesGatherRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects) { int i,j,k; int missing_glyph_added = 0; k=0; for (i=0; i < num_ranges; ++i) { float fh = ranges[i].font_size; float scale = fh > 0 ? stbtt_ScaleForPixelHeight(info, fh) : stbtt_ScaleForMappingEmToPixels(info, -fh); ranges[i].h_oversample = (unsigned char) spc->h_oversample; ranges[i].v_oversample = (unsigned char) spc->v_oversample; for (j=0; j < ranges[i].num_chars; ++j) { int x0,y0,x1,y1; int codepoint = ranges[i].array_of_unicode_codepoints == NULL ? ranges[i].first_unicode_codepoint_in_range + j : ranges[i].array_of_unicode_codepoints[j]; int glyph = stbtt_FindGlyphIndex(info, codepoint); if (glyph == 0 && (spc->skip_missing || missing_glyph_added)) { rects[k].w = rects[k].h = 0; } else { stbtt_GetGlyphBitmapBoxSubpixel(info,glyph, scale * spc->h_oversample, scale * spc->v_oversample, 0,0, &x0,&y0,&x1,&y1); rects[k].w = (stbrp_coord) (x1-x0 + spc->padding + spc->h_oversample-1); rects[k].h = (stbrp_coord) (y1-y0 + spc->padding + spc->v_oversample-1); if (glyph == 0) missing_glyph_added = 1; } ++k; } } return k; } STBTT_DEF void stbtt_MakeGlyphBitmapSubpixelPrefilter(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int prefilter_x, int prefilter_y, float *sub_x, float *sub_y, int glyph) { stbtt_MakeGlyphBitmapSubpixel(info, output, out_w - (prefilter_x - 1), out_h - (prefilter_y - 1), out_stride, scale_x, scale_y, shift_x, shift_y, glyph); if (prefilter_x > 1) stbtt__h_prefilter(output, out_w, out_h, out_stride, prefilter_x); if (prefilter_y > 1) stbtt__v_prefilter(output, out_w, out_h, out_stride, prefilter_y); *sub_x = stbtt__oversample_shift(prefilter_x); *sub_y = stbtt__oversample_shift(prefilter_y); } // rects array must be big enough to accommodate all characters in the given ranges STBTT_DEF int stbtt_PackFontRangesRenderIntoRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects) { int i,j,k, missing_glyph = -1, return_value = 1; // save current values int old_h_over = spc->h_oversample; int old_v_over = spc->v_oversample; k = 0; for (i=0; i < num_ranges; ++i) { float fh = ranges[i].font_size; float scale = fh > 0 ? stbtt_ScaleForPixelHeight(info, fh) : stbtt_ScaleForMappingEmToPixels(info, -fh); float recip_h,recip_v,sub_x,sub_y; spc->h_oversample = ranges[i].h_oversample; spc->v_oversample = ranges[i].v_oversample; recip_h = 1.0f / spc->h_oversample; recip_v = 1.0f / spc->v_oversample; sub_x = stbtt__oversample_shift(spc->h_oversample); sub_y = stbtt__oversample_shift(spc->v_oversample); for (j=0; j < ranges[i].num_chars; ++j) { stbrp_rect *r = &rects[k]; if (r->was_packed && r->w != 0 && r->h != 0) { stbtt_packedchar *bc = &ranges[i].chardata_for_range[j]; int advance, lsb, x0,y0,x1,y1; int codepoint = ranges[i].array_of_unicode_codepoints == NULL ? ranges[i].first_unicode_codepoint_in_range + j : ranges[i].array_of_unicode_codepoints[j]; int glyph = stbtt_FindGlyphIndex(info, codepoint); stbrp_coord pad = (stbrp_coord) spc->padding; // pad on left and top r->x += pad; r->y += pad; r->w -= pad; r->h -= pad; stbtt_GetGlyphHMetrics(info, glyph, &advance, &lsb); stbtt_GetGlyphBitmapBox(info, glyph, scale * spc->h_oversample, scale * spc->v_oversample, &x0,&y0,&x1,&y1); stbtt_MakeGlyphBitmapSubpixel(info, spc->pixels + r->x + r->y*spc->stride_in_bytes, r->w - spc->h_oversample+1, r->h - spc->v_oversample+1, spc->stride_in_bytes, scale * spc->h_oversample, scale * spc->v_oversample, 0,0, glyph); if (spc->h_oversample > 1) stbtt__h_prefilter(spc->pixels + r->x + r->y*spc->stride_in_bytes, r->w, r->h, spc->stride_in_bytes, spc->h_oversample); if (spc->v_oversample > 1) stbtt__v_prefilter(spc->pixels + r->x + r->y*spc->stride_in_bytes, r->w, r->h, spc->stride_in_bytes, spc->v_oversample); bc->x0 = (stbtt_int16) r->x; bc->y0 = (stbtt_int16) r->y; bc->x1 = (stbtt_int16) (r->x + r->w); bc->y1 = (stbtt_int16) (r->y + r->h); bc->xadvance = scale * advance; bc->xoff = (float) x0 * recip_h + sub_x; bc->yoff = (float) y0 * recip_v + sub_y; bc->xoff2 = (x0 + r->w) * recip_h + sub_x; bc->yoff2 = (y0 + r->h) * recip_v + sub_y; if (glyph == 0) missing_glyph = j; } else if (spc->skip_missing) { return_value = 0; } else if (r->was_packed && r->w == 0 && r->h == 0 && missing_glyph >= 0) { ranges[i].chardata_for_range[j] = ranges[i].chardata_for_range[missing_glyph]; } else { return_value = 0; // if any fail, report failure } ++k; } } // restore original values spc->h_oversample = old_h_over; spc->v_oversample = old_v_over; return return_value; } STBTT_DEF void stbtt_PackFontRangesPackRects(stbtt_pack_context *spc, stbrp_rect *rects, int num_rects) { stbrp_pack_rects((stbrp_context *) spc->pack_info, rects, num_rects); } STBTT_DEF int stbtt_PackFontRanges(stbtt_pack_context *spc, const unsigned char *fontdata, int font_index, stbtt_pack_range *ranges, int num_ranges) { stbtt_fontinfo info; int i,j,n, return_value = 1; //stbrp_context *context = (stbrp_context *) spc->pack_info; stbrp_rect *rects; // flag all characters as NOT packed for (i=0; i < num_ranges; ++i) for (j=0; j < ranges[i].num_chars; ++j) ranges[i].chardata_for_range[j].x0 = ranges[i].chardata_for_range[j].y0 = ranges[i].chardata_for_range[j].x1 = ranges[i].chardata_for_range[j].y1 = 0; n = 0; for (i=0; i < num_ranges; ++i) n += ranges[i].num_chars; rects = (stbrp_rect *) STBTT_malloc(sizeof(*rects) * n, spc->user_allocator_context); if (rects == NULL) return 0; info.userdata = spc->user_allocator_context; stbtt_InitFont(&info, fontdata, stbtt_GetFontOffsetForIndex(fontdata,font_index)); n = stbtt_PackFontRangesGatherRects(spc, &info, ranges, num_ranges, rects); stbtt_PackFontRangesPackRects(spc, rects, n); return_value = stbtt_PackFontRangesRenderIntoRects(spc, &info, ranges, num_ranges, rects); STBTT_free(rects, spc->user_allocator_context); return return_value; } STBTT_DEF int stbtt_PackFontRange(stbtt_pack_context *spc, const unsigned char *fontdata, int font_index, float font_size, int first_unicode_codepoint_in_range, int num_chars_in_range, stbtt_packedchar *chardata_for_range) { stbtt_pack_range range; range.first_unicode_codepoint_in_range = first_unicode_codepoint_in_range; range.array_of_unicode_codepoints = NULL; range.num_chars = num_chars_in_range; range.chardata_for_range = chardata_for_range; range.font_size = font_size; return stbtt_PackFontRanges(spc, fontdata, font_index, &range, 1); } STBTT_DEF void stbtt_GetScaledFontVMetrics(const unsigned char *fontdata, int index, float size, float *ascent, float *descent, float *lineGap) { int i_ascent, i_descent, i_lineGap; float scale; stbtt_fontinfo info; stbtt_InitFont(&info, fontdata, stbtt_GetFontOffsetForIndex(fontdata, index)); scale = size > 0 ? stbtt_ScaleForPixelHeight(&info, size) : stbtt_ScaleForMappingEmToPixels(&info, -size); stbtt_GetFontVMetrics(&info, &i_ascent, &i_descent, &i_lineGap); *ascent = (float) i_ascent * scale; *descent = (float) i_descent * scale; *lineGap = (float) i_lineGap * scale; } STBTT_DEF void stbtt_GetPackedQuad(const stbtt_packedchar *chardata, int pw, int ph, int char_index, float *xpos, float *ypos, stbtt_aligned_quad *q, int align_to_integer) { float ipw = 1.0f / pw, iph = 1.0f / ph; const stbtt_packedchar *b = chardata + char_index; if (align_to_integer) { float x = (float) STBTT_ifloor((*xpos + b->xoff) + 0.5f); float y = (float) STBTT_ifloor((*ypos + b->yoff) + 0.5f); q->x0 = x; q->y0 = y; q->x1 = x + b->xoff2 - b->xoff; q->y1 = y + b->yoff2 - b->yoff; } else { q->x0 = *xpos + b->xoff; q->y0 = *ypos + b->yoff; q->x1 = *xpos + b->xoff2; q->y1 = *ypos + b->yoff2; } q->s0 = b->x0 * ipw; q->t0 = b->y0 * iph; q->s1 = b->x1 * ipw; q->t1 = b->y1 * iph; *xpos += b->xadvance; } ////////////////////////////////////////////////////////////////////////////// // // sdf computation // #define STBTT_min(a,b) ((a) < (b) ? (a) : (b)) #define STBTT_max(a,b) ((a) < (b) ? (b) : (a)) static int stbtt__ray_intersect_bezier(float orig[2], float ray[2], float q0[2], float q1[2], float q2[2], float hits[2][2]) { float q0perp = q0[1]*ray[0] - q0[0]*ray[1]; float q1perp = q1[1]*ray[0] - q1[0]*ray[1]; float q2perp = q2[1]*ray[0] - q2[0]*ray[1]; float roperp = orig[1]*ray[0] - orig[0]*ray[1]; float a = q0perp - 2*q1perp + q2perp; float b = q1perp - q0perp; float c = q0perp - roperp; float s0 = 0., s1 = 0.; int num_s = 0; if (a != 0.0) { float discr = b*b - a*c; if (discr > 0.0) { float rcpna = -1 / a; float d = (float) STBTT_sqrt(discr); s0 = (b+d) * rcpna; s1 = (b-d) * rcpna; if (s0 >= 0.0 && s0 <= 1.0) num_s = 1; if (d > 0.0 && s1 >= 0.0 && s1 <= 1.0) { if (num_s == 0) s0 = s1; ++num_s; } } } else { // 2*b*s + c = 0 // s = -c / (2*b) s0 = c / (-2 * b); if (s0 >= 0.0 && s0 <= 1.0) num_s = 1; } if (num_s == 0) return 0; else { float rcp_len2 = 1 / (ray[0]*ray[0] + ray[1]*ray[1]); float rayn_x = ray[0] * rcp_len2, rayn_y = ray[1] * rcp_len2; float q0d = q0[0]*rayn_x + q0[1]*rayn_y; float q1d = q1[0]*rayn_x + q1[1]*rayn_y; float q2d = q2[0]*rayn_x + q2[1]*rayn_y; float rod = orig[0]*rayn_x + orig[1]*rayn_y; float q10d = q1d - q0d; float q20d = q2d - q0d; float q0rd = q0d - rod; hits[0][0] = q0rd + s0*(2.0f - 2.0f*s0)*q10d + s0*s0*q20d; hits[0][1] = a*s0+b; if (num_s > 1) { hits[1][0] = q0rd + s1*(2.0f - 2.0f*s1)*q10d + s1*s1*q20d; hits[1][1] = a*s1+b; return 2; } else { return 1; } } } static int equal(float *a, float *b) { return (a[0] == b[0] && a[1] == b[1]); } static int stbtt__compute_crossings_x(float x, float y, int nverts, stbtt_vertex *verts) { int i; float orig[2], ray[2] = { 1, 0 }; float y_frac; int winding = 0; // make sure y never passes through a vertex of the shape y_frac = (float) STBTT_fmod(y, 1.0f); if (y_frac < 0.01f) y += 0.01f; else if (y_frac > 0.99f) y -= 0.01f; orig[0] = x; orig[1] = y; // test a ray from (-infinity,y) to (x,y) for (i=0; i < nverts; ++i) { if (verts[i].type == STBTT_vline) { int x0 = (int) verts[i-1].x, y0 = (int) verts[i-1].y; int x1 = (int) verts[i ].x, y1 = (int) verts[i ].y; if (y > STBTT_min(y0,y1) && y < STBTT_max(y0,y1) && x > STBTT_min(x0,x1)) { float x_inter = (y - y0) / (y1 - y0) * (x1-x0) + x0; if (x_inter < x) winding += (y0 < y1) ? 1 : -1; } } if (verts[i].type == STBTT_vcurve) { int x0 = (int) verts[i-1].x , y0 = (int) verts[i-1].y ; int x1 = (int) verts[i ].cx, y1 = (int) verts[i ].cy; int x2 = (int) verts[i ].x , y2 = (int) verts[i ].y ; int ax = STBTT_min(x0,STBTT_min(x1,x2)), ay = STBTT_min(y0,STBTT_min(y1,y2)); int by = STBTT_max(y0,STBTT_max(y1,y2)); if (y > ay && y < by && x > ax) { float q0[2],q1[2],q2[2]; float hits[2][2]; q0[0] = (float)x0; q0[1] = (float)y0; q1[0] = (float)x1; q1[1] = (float)y1; q2[0] = (float)x2; q2[1] = (float)y2; if (equal(q0,q1) || equal(q1,q2)) { x0 = (int)verts[i-1].x; y0 = (int)verts[i-1].y; x1 = (int)verts[i ].x; y1 = (int)verts[i ].y; if (y > STBTT_min(y0,y1) && y < STBTT_max(y0,y1) && x > STBTT_min(x0,x1)) { float x_inter = (y - y0) / (y1 - y0) * (x1-x0) + x0; if (x_inter < x) winding += (y0 < y1) ? 1 : -1; } } else { int num_hits = stbtt__ray_intersect_bezier(orig, ray, q0, q1, q2, hits); if (num_hits >= 1) if (hits[0][0] < 0) winding += (hits[0][1] < 0 ? -1 : 1); if (num_hits >= 2) if (hits[1][0] < 0) winding += (hits[1][1] < 0 ? -1 : 1); } } } } return winding; } static float stbtt__cuberoot( float x ) { if (x<0) return -(float) STBTT_pow(-x,1.0f/3.0f); else return (float) STBTT_pow( x,1.0f/3.0f); } // x^3 + a*x^2 + b*x + c = 0 static int stbtt__solve_cubic(float a, float b, float c, float* r) { float s = -a / 3; float p = b - a*a / 3; float q = a * (2*a*a - 9*b) / 27 + c; float p3 = p*p*p; float d = q*q + 4*p3 / 27; if (d >= 0) { float z = (float) STBTT_sqrt(d); float u = (-q + z) / 2; float v = (-q - z) / 2; u = stbtt__cuberoot(u); v = stbtt__cuberoot(v); r[0] = s + u + v; return 1; } else { float u = (float) STBTT_sqrt(-p/3); float v = (float) STBTT_acos(-STBTT_sqrt(-27/p3) * q / 2) / 3; // p3 must be negative, since d is negative float m = (float) STBTT_cos(v); float n = (float) STBTT_cos(v-3.141592/2)*1.732050808f; r[0] = s + u * 2 * m; r[1] = s - u * (m + n); r[2] = s - u * (m - n); //STBTT_assert( STBTT_fabs(((r[0]+a)*r[0]+b)*r[0]+c) < 0.05f); // these asserts may not be safe at all scales, though they're in bezier t parameter units so maybe? //STBTT_assert( STBTT_fabs(((r[1]+a)*r[1]+b)*r[1]+c) < 0.05f); //STBTT_assert( STBTT_fabs(((r[2]+a)*r[2]+b)*r[2]+c) < 0.05f); return 3; } } STBTT_DEF unsigned char * stbtt_GetGlyphSDF(const stbtt_fontinfo *info, float scale, int glyph, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff) { float scale_x = scale, scale_y = scale; int ix0,iy0,ix1,iy1; int w,h; unsigned char *data; if (scale == 0) return NULL; stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale, scale, 0.0f,0.0f, &ix0,&iy0,&ix1,&iy1); // if empty, return NULL if (ix0 == ix1 || iy0 == iy1) return NULL; ix0 -= padding; iy0 -= padding; ix1 += padding; iy1 += padding; w = (ix1 - ix0); h = (iy1 - iy0); if (width ) *width = w; if (height) *height = h; if (xoff ) *xoff = ix0; if (yoff ) *yoff = iy0; // invert for y-downwards bitmaps scale_y = -scale_y; { int x,y,i,j; float *precompute; stbtt_vertex *verts; int num_verts = stbtt_GetGlyphShape(info, glyph, &verts); data = (unsigned char *) STBTT_malloc(w * h, info->userdata); precompute = (float *) STBTT_malloc(num_verts * sizeof(float), info->userdata); for (i=0,j=num_verts-1; i < num_verts; j=i++) { if (verts[i].type == STBTT_vline) { float x0 = verts[i].x*scale_x, y0 = verts[i].y*scale_y; float x1 = verts[j].x*scale_x, y1 = verts[j].y*scale_y; float dist = (float) STBTT_sqrt((x1-x0)*(x1-x0) + (y1-y0)*(y1-y0)); precompute[i] = (dist == 0) ? 0.0f : 1.0f / dist; } else if (verts[i].type == STBTT_vcurve) { float x2 = verts[j].x *scale_x, y2 = verts[j].y *scale_y; float x1 = verts[i].cx*scale_x, y1 = verts[i].cy*scale_y; float x0 = verts[i].x *scale_x, y0 = verts[i].y *scale_y; float bx = x0 - 2*x1 + x2, by = y0 - 2*y1 + y2; float len2 = bx*bx + by*by; if (len2 != 0.0f) precompute[i] = 1.0f / (bx*bx + by*by); else precompute[i] = 0.0f; } else precompute[i] = 0.0f; } for (y=iy0; y < iy1; ++y) { for (x=ix0; x < ix1; ++x) { float val; float min_dist = 999999.0f; float sx = (float) x + 0.5f; float sy = (float) y + 0.5f; float x_gspace = (sx / scale_x); float y_gspace = (sy / scale_y); int winding = stbtt__compute_crossings_x(x_gspace, y_gspace, num_verts, verts); // @OPTIMIZE: this could just be a rasterization, but needs to be line vs. non-tesselated curves so a new path for (i=0; i < num_verts; ++i) { float x0 = verts[i].x*scale_x, y0 = verts[i].y*scale_y; if (verts[i].type == STBTT_vline && precompute[i] != 0.0f) { float x1 = verts[i-1].x*scale_x, y1 = verts[i-1].y*scale_y; float dist,dist2 = (x0-sx)*(x0-sx) + (y0-sy)*(y0-sy); if (dist2 < min_dist*min_dist) min_dist = (float) STBTT_sqrt(dist2); // coarse culling against bbox //if (sx > STBTT_min(x0,x1)-min_dist && sx < STBTT_max(x0,x1)+min_dist && // sy > STBTT_min(y0,y1)-min_dist && sy < STBTT_max(y0,y1)+min_dist) dist = (float) STBTT_fabs((x1-x0)*(y0-sy) - (y1-y0)*(x0-sx)) * precompute[i]; STBTT_assert(i != 0); if (dist < min_dist) { // check position along line // x' = x0 + t*(x1-x0), y' = y0 + t*(y1-y0) // minimize (x'-sx)*(x'-sx)+(y'-sy)*(y'-sy) float dx = x1-x0, dy = y1-y0; float px = x0-sx, py = y0-sy; // minimize (px+t*dx)^2 + (py+t*dy)^2 = px*px + 2*px*dx*t + t^2*dx*dx + py*py + 2*py*dy*t + t^2*dy*dy // derivative: 2*px*dx + 2*py*dy + (2*dx*dx+2*dy*dy)*t, set to 0 and solve float t = -(px*dx + py*dy) / (dx*dx + dy*dy); if (t >= 0.0f && t <= 1.0f) min_dist = dist; } } else if (verts[i].type == STBTT_vcurve) { float x2 = verts[i-1].x *scale_x, y2 = verts[i-1].y *scale_y; float x1 = verts[i ].cx*scale_x, y1 = verts[i ].cy*scale_y; float box_x0 = STBTT_min(STBTT_min(x0,x1),x2); float box_y0 = STBTT_min(STBTT_min(y0,y1),y2); float box_x1 = STBTT_max(STBTT_max(x0,x1),x2); float box_y1 = STBTT_max(STBTT_max(y0,y1),y2); // coarse culling against bbox to avoid computing cubic unnecessarily if (sx > box_x0-min_dist && sx < box_x1+min_dist && sy > box_y0-min_dist && sy < box_y1+min_dist) { int num=0; float ax = x1-x0, ay = y1-y0; float bx = x0 - 2*x1 + x2, by = y0 - 2*y1 + y2; float mx = x0 - sx, my = y0 - sy; float res[3] = {0.f,0.f,0.f}; float px,py,t,it,dist2; float a_inv = precompute[i]; if (a_inv == 0.0) { // if a_inv is 0, it's 2nd degree so use quadratic formula float a = 3*(ax*bx + ay*by); float b = 2*(ax*ax + ay*ay) + (mx*bx+my*by); float c = mx*ax+my*ay; if (a == 0.0) { // if a is 0, it's linear if (b != 0.0) { res[num++] = -c/b; } } else { float discriminant = b*b - 4*a*c; if (discriminant < 0) num = 0; else { float root = (float) STBTT_sqrt(discriminant); res[0] = (-b - root)/(2*a); res[1] = (-b + root)/(2*a); num = 2; // don't bother distinguishing 1-solution case, as code below will still work } } } else { float b = 3*(ax*bx + ay*by) * a_inv; // could precompute this as it doesn't depend on sample point float c = (2*(ax*ax + ay*ay) + (mx*bx+my*by)) * a_inv; float d = (mx*ax+my*ay) * a_inv; num = stbtt__solve_cubic(b, c, d, res); } dist2 = (x0-sx)*(x0-sx) + (y0-sy)*(y0-sy); if (dist2 < min_dist*min_dist) min_dist = (float) STBTT_sqrt(dist2); if (num >= 1 && res[0] >= 0.0f && res[0] <= 1.0f) { t = res[0], it = 1.0f - t; px = it*it*x0 + 2*t*it*x1 + t*t*x2; py = it*it*y0 + 2*t*it*y1 + t*t*y2; dist2 = (px-sx)*(px-sx) + (py-sy)*(py-sy); if (dist2 < min_dist * min_dist) min_dist = (float) STBTT_sqrt(dist2); } if (num >= 2 && res[1] >= 0.0f && res[1] <= 1.0f) { t = res[1], it = 1.0f - t; px = it*it*x0 + 2*t*it*x1 + t*t*x2; py = it*it*y0 + 2*t*it*y1 + t*t*y2; dist2 = (px-sx)*(px-sx) + (py-sy)*(py-sy); if (dist2 < min_dist * min_dist) min_dist = (float) STBTT_sqrt(dist2); } if (num >= 3 && res[2] >= 0.0f && res[2] <= 1.0f) { t = res[2], it = 1.0f - t; px = it*it*x0 + 2*t*it*x1 + t*t*x2; py = it*it*y0 + 2*t*it*y1 + t*t*y2; dist2 = (px-sx)*(px-sx) + (py-sy)*(py-sy); if (dist2 < min_dist * min_dist) min_dist = (float) STBTT_sqrt(dist2); } } } } if (winding == 0) min_dist = -min_dist; // if outside the shape, value is negative val = onedge_value + pixel_dist_scale * min_dist; if (val < 0) val = 0; else if (val > 255) val = 255; data[(y-iy0)*w+(x-ix0)] = (unsigned char) val; } } STBTT_free(precompute, info->userdata); STBTT_free(verts, info->userdata); } return data; } STBTT_DEF unsigned char * stbtt_GetCodepointSDF(const stbtt_fontinfo *info, float scale, int codepoint, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff) { return stbtt_GetGlyphSDF(info, scale, stbtt_FindGlyphIndex(info, codepoint), padding, onedge_value, pixel_dist_scale, width, height, xoff, yoff); } STBTT_DEF void stbtt_FreeSDF(unsigned char *bitmap, void *userdata) { STBTT_free(bitmap, userdata); } ////////////////////////////////////////////////////////////////////////////// // // font name matching -- recommended not to use this // // check if a utf8 string contains a prefix which is the utf16 string; if so return length of matching utf8 string static stbtt_int32 stbtt__CompareUTF8toUTF16_bigendian_prefix(stbtt_uint8 *s1, stbtt_int32 len1, stbtt_uint8 *s2, stbtt_int32 len2) { stbtt_int32 i=0; // convert utf16 to utf8 and compare the results while converting while (len2) { stbtt_uint16 ch = s2[0]*256 + s2[1]; if (ch < 0x80) { if (i >= len1) return -1; if (s1[i++] != ch) return -1; } else if (ch < 0x800) { if (i+1 >= len1) return -1; if (s1[i++] != 0xc0 + (ch >> 6)) return -1; if (s1[i++] != 0x80 + (ch & 0x3f)) return -1; } else if (ch >= 0xd800 && ch < 0xdc00) { stbtt_uint32 c; stbtt_uint16 ch2 = s2[2]*256 + s2[3]; if (i+3 >= len1) return -1; c = ((ch - 0xd800) << 10) + (ch2 - 0xdc00) + 0x10000; if (s1[i++] != 0xf0 + (c >> 18)) return -1; if (s1[i++] != 0x80 + ((c >> 12) & 0x3f)) return -1; if (s1[i++] != 0x80 + ((c >> 6) & 0x3f)) return -1; if (s1[i++] != 0x80 + ((c ) & 0x3f)) return -1; s2 += 2; // plus another 2 below len2 -= 2; } else if (ch >= 0xdc00 && ch < 0xe000) { return -1; } else { if (i+2 >= len1) return -1; if (s1[i++] != 0xe0 + (ch >> 12)) return -1; if (s1[i++] != 0x80 + ((ch >> 6) & 0x3f)) return -1; if (s1[i++] != 0x80 + ((ch ) & 0x3f)) return -1; } s2 += 2; len2 -= 2; } return i; } static int stbtt_CompareUTF8toUTF16_bigendian_internal(char *s1, int len1, char *s2, int len2) { return len1 == stbtt__CompareUTF8toUTF16_bigendian_prefix((stbtt_uint8*) s1, len1, (stbtt_uint8*) s2, len2); } // returns results in whatever encoding you request... but note that 2-byte encodings // will be BIG-ENDIAN... use stbtt_CompareUTF8toUTF16_bigendian() to compare STBTT_DEF const char *stbtt_GetFontNameString(const stbtt_fontinfo *font, int *length, int platformID, int encodingID, int languageID, int nameID) { stbtt_int32 i,count,stringOffset; stbtt_uint8 *fc = font->data; stbtt_uint32 offset = font->fontstart; stbtt_uint32 nm = stbtt__find_table(fc, offset, "name"); if (!nm) return NULL; count = ttUSHORT(fc+nm+2); stringOffset = nm + ttUSHORT(fc+nm+4); for (i=0; i < count; ++i) { stbtt_uint32 loc = nm + 6 + 12 * i; if (platformID == ttUSHORT(fc+loc+0) && encodingID == ttUSHORT(fc+loc+2) && languageID == ttUSHORT(fc+loc+4) && nameID == ttUSHORT(fc+loc+6)) { *length = ttUSHORT(fc+loc+8); return (const char *) (fc+stringOffset+ttUSHORT(fc+loc+10)); } } return NULL; } static int stbtt__matchpair(stbtt_uint8 *fc, stbtt_uint32 nm, stbtt_uint8 *name, stbtt_int32 nlen, stbtt_int32 target_id, stbtt_int32 next_id) { stbtt_int32 i; stbtt_int32 count = ttUSHORT(fc+nm+2); stbtt_int32 stringOffset = nm + ttUSHORT(fc+nm+4); for (i=0; i < count; ++i) { stbtt_uint32 loc = nm + 6 + 12 * i; stbtt_int32 id = ttUSHORT(fc+loc+6); if (id == target_id) { // find the encoding stbtt_int32 platform = ttUSHORT(fc+loc+0), encoding = ttUSHORT(fc+loc+2), language = ttUSHORT(fc+loc+4); // is this a Unicode encoding? if (platform == 0 || (platform == 3 && encoding == 1) || (platform == 3 && encoding == 10)) { stbtt_int32 slen = ttUSHORT(fc+loc+8); stbtt_int32 off = ttUSHORT(fc+loc+10); // check if there's a prefix match stbtt_int32 matchlen = stbtt__CompareUTF8toUTF16_bigendian_prefix(name, nlen, fc+stringOffset+off,slen); if (matchlen >= 0) { // check for target_id+1 immediately following, with same encoding & language if (i+1 < count && ttUSHORT(fc+loc+12+6) == next_id && ttUSHORT(fc+loc+12) == platform && ttUSHORT(fc+loc+12+2) == encoding && ttUSHORT(fc+loc+12+4) == language) { slen = ttUSHORT(fc+loc+12+8); off = ttUSHORT(fc+loc+12+10); if (slen == 0) { if (matchlen == nlen) return 1; } else if (matchlen < nlen && name[matchlen] == ' ') { ++matchlen; if (stbtt_CompareUTF8toUTF16_bigendian_internal((char*) (name+matchlen), nlen-matchlen, (char*)(fc+stringOffset+off),slen)) return 1; } } else { // if nothing immediately following if (matchlen == nlen) return 1; } } } // @TODO handle other encodings } } return 0; } static int stbtt__matches(stbtt_uint8 *fc, stbtt_uint32 offset, stbtt_uint8 *name, stbtt_int32 flags) { stbtt_int32 nlen = (stbtt_int32) STBTT_strlen((char *) name); stbtt_uint32 nm,hd; if (!stbtt__isfont(fc+offset)) return 0; // check italics/bold/underline flags in macStyle... if (flags) { hd = stbtt__find_table(fc, offset, "head"); if ((ttUSHORT(fc+hd+44) & 7) != (flags & 7)) return 0; } nm = stbtt__find_table(fc, offset, "name"); if (!nm) return 0; if (flags) { // if we checked the macStyle flags, then just check the family and ignore the subfamily if (stbtt__matchpair(fc, nm, name, nlen, 16, -1)) return 1; if (stbtt__matchpair(fc, nm, name, nlen, 1, -1)) return 1; if (stbtt__matchpair(fc, nm, name, nlen, 3, -1)) return 1; } else { if (stbtt__matchpair(fc, nm, name, nlen, 16, 17)) return 1; if (stbtt__matchpair(fc, nm, name, nlen, 1, 2)) return 1; if (stbtt__matchpair(fc, nm, name, nlen, 3, -1)) return 1; } return 0; } static int stbtt_FindMatchingFont_internal(unsigned char *font_collection, char *name_utf8, stbtt_int32 flags) { stbtt_int32 i; for (i=0;;++i) { stbtt_int32 off = stbtt_GetFontOffsetForIndex(font_collection, i); if (off < 0) return off; if (stbtt__matches((stbtt_uint8 *) font_collection, off, (stbtt_uint8*) name_utf8, flags)) return off; } } #if defined(__GNUC__) || defined(__clang__) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wcast-qual" #endif STBTT_DEF int stbtt_BakeFontBitmap(const unsigned char *data, int offset, float pixel_height, unsigned char *pixels, int pw, int ph, int first_char, int num_chars, stbtt_bakedchar *chardata) { return stbtt_BakeFontBitmap_internal((unsigned char *) data, offset, pixel_height, pixels, pw, ph, first_char, num_chars, chardata); } STBTT_DEF int stbtt_GetFontOffsetForIndex(const unsigned char *data, int index) { return stbtt_GetFontOffsetForIndex_internal((unsigned char *) data, index); } STBTT_DEF int stbtt_GetNumberOfFonts(const unsigned char *data) { return stbtt_GetNumberOfFonts_internal((unsigned char *) data); } STBTT_DEF int stbtt_InitFont(stbtt_fontinfo *info, const unsigned char *data, int offset) { return stbtt_InitFont_internal(info, (unsigned char *) data, offset); } STBTT_DEF int stbtt_FindMatchingFont(const unsigned char *fontdata, const char *name, int flags) { return stbtt_FindMatchingFont_internal((unsigned char *) fontdata, (char *) name, flags); } STBTT_DEF int stbtt_CompareUTF8toUTF16_bigendian(const char *s1, int len1, const char *s2, int len2) { return stbtt_CompareUTF8toUTF16_bigendian_internal((char *) s1, len1, (char *) s2, len2); } #if defined(__GNUC__) || defined(__clang__) #pragma GCC diagnostic pop #endif #endif // STB_TRUETYPE_IMPLEMENTATION // FULL VERSION HISTORY // // 1.25 (2021-07-11) many fixes // 1.24 (2020-02-05) fix warning // 1.23 (2020-02-02) query SVG data for glyphs; query whole kerning table (but only kern not GPOS) // 1.22 (2019-08-11) minimize missing-glyph duplication; fix kerning if both 'GPOS' and 'kern' are defined // 1.21 (2019-02-25) fix warning // 1.20 (2019-02-07) PackFontRange skips missing codepoints; GetScaleFontVMetrics() // 1.19 (2018-02-11) OpenType GPOS kerning (horizontal only), STBTT_fmod // 1.18 (2018-01-29) add missing function // 1.17 (2017-07-23) make more arguments const; doc fix // 1.16 (2017-07-12) SDF support // 1.15 (2017-03-03) make more arguments const // 1.14 (2017-01-16) num-fonts-in-TTC function // 1.13 (2017-01-02) support OpenType fonts, certain Apple fonts // 1.12 (2016-10-25) suppress warnings about casting away const with -Wcast-qual // 1.11 (2016-04-02) fix unused-variable warning // 1.10 (2016-04-02) allow user-defined fabs() replacement // fix memory leak if fontsize=0.0 // fix warning from duplicate typedef // 1.09 (2016-01-16) warning fix; avoid crash on outofmem; use alloc userdata for PackFontRanges // 1.08 (2015-09-13) document stbtt_Rasterize(); fixes for vertical & horizontal edges // 1.07 (2015-08-01) allow PackFontRanges to accept arrays of sparse codepoints; // allow PackFontRanges to pack and render in separate phases; // fix stbtt_GetFontOFfsetForIndex (never worked for non-0 input?); // fixed an assert() bug in the new rasterizer // replace assert() with STBTT_assert() in new rasterizer // 1.06 (2015-07-14) performance improvements (~35% faster on x86 and x64 on test machine) // also more precise AA rasterizer, except if shapes overlap // remove need for STBTT_sort // 1.05 (2015-04-15) fix misplaced definitions for STBTT_STATIC // 1.04 (2015-04-15) typo in example // 1.03 (2015-04-12) STBTT_STATIC, fix memory leak in new packing, various fixes // 1.02 (2014-12-10) fix various warnings & compile issues w/ stb_rect_pack, C++ // 1.01 (2014-12-08) fix subpixel position when oversampling to exactly match // non-oversampled; STBTT_POINT_SIZE for packed case only // 1.00 (2014-12-06) add new PackBegin etc. API, w/ support for oversampling // 0.99 (2014-09-18) fix multiple bugs with subpixel rendering (ryg) // 0.9 (2014-08-07) support certain mac/iOS fonts without an MS platformID // 0.8b (2014-07-07) fix a warning // 0.8 (2014-05-25) fix a few more warnings // 0.7 (2013-09-25) bugfix: subpixel glyph bug fixed in 0.5 had come back // 0.6c (2012-07-24) improve documentation // 0.6b (2012-07-20) fix a few more warnings // 0.6 (2012-07-17) fix warnings; added stbtt_ScaleForMappingEmToPixels, // stbtt_GetFontBoundingBox, stbtt_IsGlyphEmpty // 0.5 (2011-12-09) bugfixes: // subpixel glyph renderer computed wrong bounding box // first vertex of shape can be off-curve (FreeSans) // 0.4b (2011-12-03) fixed an error in the font baking example // 0.4 (2011-12-01) kerning, subpixel rendering (tor) // bugfixes for: // codepoint-to-glyph conversion using table fmt=12 // codepoint-to-glyph conversion using table fmt=4 // stbtt_GetBakedQuad with non-square texture (Zer) // updated Hello World! sample to use kerning and subpixel // fixed some warnings // 0.3 (2009-06-24) cmap fmt=12, compound shapes (MM) // userdata, malloc-from-userdata, non-zero fill (stb) // 0.2 (2009-03-11) Fix unsigned/signed char warnings // 0.1 (2009-03-09) First public release // /* ------------------------------------------------------------------------------ This software is available under 2 licenses -- choose whichever you prefer. ------------------------------------------------------------------------------ ALTERNATIVE A - MIT License Copyright (c) 2017 Sean Barrett Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ------------------------------------------------------------------------------ ALTERNATIVE B - Public Domain (www.unlicense.org) This is free and unencumbered software released into the public domain. Anyone is free to copy, modify, publish, use, compile, sell, or distribute this software, either in source code form or as a compiled binary, for any purpose, commercial or non-commercial, and by any means. In jurisdictions that recognize copyright laws, the author or authors of this software dedicate any and all copyright interest in the software to the public domain. We make this dedication for the benefit of the public at large and to the detriment of our heirs and successors. We intend this dedication to be an overt act of relinquishment in perpetuity of all present and future rights to this software under copyright law. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ------------------------------------------------------------------------------ */ sammycage-plutovg-5695a71/source/plutovg-surface.c000066400000000000000000000235141510714322500222400ustar00rootroot00000000000000#include "plutovg-private.h" #include "plutovg-utils.h" #define STB_IMAGE_WRITE_STATIC #define STB_IMAGE_WRITE_IMPLEMENTATION #include "plutovg-stb-image-write.h" #define STB_IMAGE_STATIC #define STB_IMAGE_IMPLEMENTATION #include "plutovg-stb-image.h" static plutovg_surface_t* plutovg_surface_create_uninitialized(int width, int height) { static const int kMaxSize = 1 << 15; if(width <= 0 || height <= 0 || width >= kMaxSize || height >= kMaxSize) return NULL; const size_t size = width * height * 4; plutovg_surface_t* surface = malloc(size + sizeof(plutovg_surface_t)); if(surface == NULL) return NULL; plutovg_init_reference(surface); surface->width = width; surface->height = height; surface->stride = width * 4; surface->data = (uint8_t*)(surface + 1); return surface; } plutovg_surface_t* plutovg_surface_create(int width, int height) { plutovg_surface_t* surface = plutovg_surface_create_uninitialized(width, height); if(surface) memset(surface->data, 0, surface->height * surface->stride); return surface; } plutovg_surface_t* plutovg_surface_create_for_data(unsigned char* data, int width, int height, int stride) { plutovg_surface_t* surface = malloc(sizeof(plutovg_surface_t)); plutovg_init_reference(surface); surface->width = width; surface->height = height; surface->stride = stride; surface->data = data; return surface; } static plutovg_surface_t* plutovg_surface_load_from_image(stbi_uc* image, int width, int height) { plutovg_surface_t* surface = plutovg_surface_create_uninitialized(width, height); if(surface) plutovg_convert_rgba_to_argb(surface->data, image, surface->width, surface->height, surface->stride); stbi_image_free(image); return surface; } plutovg_surface_t* plutovg_surface_load_from_image_file(const char* filename) { int width, height, channels; stbi_uc* image = stbi_load(filename, &width, &height, &channels, STBI_rgb_alpha); if(image == NULL) return NULL; return plutovg_surface_load_from_image(image, width, height); } plutovg_surface_t* plutovg_surface_load_from_image_data(const void* data, int length) { int width, height, channels; stbi_uc* image = stbi_load_from_memory(data, length, &width, &height, &channels, STBI_rgb_alpha); if(image == NULL) return NULL; return plutovg_surface_load_from_image(image, width, height); } static const uint8_t base64_table[128] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x00, 0x3F, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, 0x30, 0x31, 0x32, 0x33, 0x00, 0x00, 0x00, 0x00, 0x00 }; plutovg_surface_t* plutovg_surface_load_from_image_base64(const char* data, int length) { plutovg_surface_t* surface = NULL; uint8_t* output_data = NULL; size_t output_length = 0; size_t equals_sign_count = 0; size_t sidx = 0; size_t didx = 0; if(length == -1) length = strlen(data); output_data = malloc(length); if(output_data == NULL) return NULL; for(int i = 0; i < length; ++i) { uint8_t cc = data[i]; if(cc == '=') { ++equals_sign_count; } else if(cc == '+' || cc == '/' || PLUTOVG_IS_ALNUM(cc)) { if(equals_sign_count > 0) goto cleanup; output_data[output_length++] = base64_table[cc]; } else if(!PLUTOVG_IS_WS(cc)) { goto cleanup; } } if(output_length == 0 || equals_sign_count > 2 || (output_length % 4) == 1) goto cleanup; output_length -= (output_length + 3) / 4; if(output_length == 0) { goto cleanup; } if(output_length > 1) { while(didx < output_length - 2) { output_data[didx + 0] = (((output_data[sidx + 0] << 2) & 255) | ((output_data[sidx + 1] >> 4) & 003)); output_data[didx + 1] = (((output_data[sidx + 1] << 4) & 255) | ((output_data[sidx + 2] >> 2) & 017)); output_data[didx + 2] = (((output_data[sidx + 2] << 6) & 255) | ((output_data[sidx + 3] >> 0) & 077)); sidx += 4; didx += 3; } } if(didx < output_length) output_data[didx] = (((output_data[sidx + 0] << 2) & 255) | ((output_data[sidx + 1] >> 4) & 003)); if(++didx < output_length) { output_data[didx] = (((output_data[sidx + 1] << 4) & 255) | ((output_data[sidx + 2] >> 2) & 017)); } surface = plutovg_surface_load_from_image_data(output_data, output_length); cleanup: free(output_data); return surface; } plutovg_surface_t* plutovg_surface_reference(plutovg_surface_t* surface) { plutovg_increment_reference(surface); return surface; } void plutovg_surface_destroy(plutovg_surface_t* surface) { if(plutovg_destroy_reference(surface)) { free(surface); } } int plutovg_surface_get_reference_count(const plutovg_surface_t* surface) { return plutovg_get_reference_count(surface); } unsigned char* plutovg_surface_get_data(const plutovg_surface_t* surface) { return surface->data; } int plutovg_surface_get_width(const plutovg_surface_t* surface) { return surface->width; } int plutovg_surface_get_height(const plutovg_surface_t* surface) { return surface->height; } int plutovg_surface_get_stride(const plutovg_surface_t* surface) { return surface->stride; } void plutovg_surface_clear(plutovg_surface_t* surface, const plutovg_color_t* color) { uint32_t pixel = plutovg_premultiply_argb(plutovg_color_to_argb32(color)); for(int y = 0; y < surface->height; y++) { uint32_t* pixels = (uint32_t*)(surface->data + surface->stride * y); plutovg_memfill32(pixels, surface->width, pixel); } } static void plutovg_surface_write_begin(const plutovg_surface_t* surface) { plutovg_convert_argb_to_rgba(surface->data, surface->data, surface->width, surface->height, surface->stride); } static void plutovg_surface_write_end(const plutovg_surface_t* surface) { plutovg_convert_rgba_to_argb(surface->data, surface->data, surface->width, surface->height, surface->stride); } bool plutovg_surface_write_to_png(const plutovg_surface_t* surface, const char* filename) { plutovg_surface_write_begin(surface); int success = stbi_write_png(filename, surface->width, surface->height, 4, surface->data, surface->stride); plutovg_surface_write_end(surface); return success; } bool plutovg_surface_write_to_jpg(const plutovg_surface_t* surface, const char* filename, int quality) { plutovg_surface_write_begin(surface); int success = stbi_write_jpg(filename, surface->width, surface->height, 4, surface->data, quality); plutovg_surface_write_end(surface); return success; } bool plutovg_surface_write_to_png_stream(const plutovg_surface_t* surface, plutovg_write_func_t write_func, void* closure) { plutovg_surface_write_begin(surface); int success = stbi_write_png_to_func(write_func, closure, surface->width, surface->height, 4, surface->data, surface->stride); plutovg_surface_write_end(surface); return success; } bool plutovg_surface_write_to_jpg_stream(const plutovg_surface_t* surface, plutovg_write_func_t write_func, void* closure, int quality) { plutovg_surface_write_begin(surface); int success = stbi_write_jpg_to_func(write_func, closure, surface->width, surface->height, 4, surface->data, quality); plutovg_surface_write_end(surface); return success; } void plutovg_convert_argb_to_rgba(unsigned char* dst, const unsigned char* src, int width, int height, int stride) { for(int y = 0; y < height; y++) { const uint32_t* src_row = (const uint32_t*)(src + stride * y); unsigned char* dst_row = dst + stride * y; for(int x = 0; x < width; x++) { uint32_t pixel = src_row[x]; uint32_t a = (pixel >> 24) & 0xFF; if(a == 0) { *dst_row++ = 0; *dst_row++ = 0; *dst_row++ = 0; *dst_row++ = 0; } else { uint32_t r = (pixel >> 16) & 0xFF; uint32_t g = (pixel >> 8) & 0xFF; uint32_t b = (pixel >> 0) & 0xFF; if(a != 255) { r = (r * 255) / a; g = (g * 255) / a; b = (b * 255) / a; } *dst_row++ = r; *dst_row++ = g; *dst_row++ = b; *dst_row++ = a; } } } } void plutovg_convert_rgba_to_argb(unsigned char* dst, const unsigned char* src, int width, int height, int stride) { for(int y = 0; y < height; y++) { const unsigned char* src_row = src + stride * y; uint32_t* dst_row = (uint32_t*)(dst + stride * y); for(int x = 0; x < width; x++) { uint32_t a = src_row[4 * x + 3]; if(a == 0) { dst_row[x] = 0x00000000; } else { uint32_t r = src_row[4 * x + 0]; uint32_t g = src_row[4 * x + 1]; uint32_t b = src_row[4 * x + 2]; if(a != 255) { r = (r * a) / 255; g = (g * a) / 255; b = (b * a) / 255; } dst_row[x] = (a << 24) | (r << 16) | (g << 8) | b; } } } } sammycage-plutovg-5695a71/source/plutovg-utils.h000066400000000000000000000132611510714322500217530ustar00rootroot00000000000000#ifndef PLUTOVG_UTILS_H #define PLUTOVG_UTILS_H #include #include #include #include #include #include #include #define PLUTOVG_IS_NUM(c) ((c) >= '0' && (c) <= '9') #define PLUTOVG_IS_ALPHA(c) (((c) >= 'a' && (c) <= 'z') || ((c) >= 'A' && (c) <= 'Z')) #define PLUTOVG_IS_ALNUM(c) (PLUTOVG_IS_ALPHA(c) || PLUTOVG_IS_NUM(c)) #define PLUTOVG_IS_WS(c) ((c) == ' ' || (c) == '\t' || (c) == '\n' || (c) == '\r') #define plutovg_min(a, b) ((a) < (b) ? (a) : (b)) #define plutovg_max(a, b) ((a) > (b) ? (a) : (b)) #define plutovg_clamp(v, lo, hi) ((v) < (lo) ? (lo) : ((v) > (hi) ? (hi) : (v))) #define plutovg_alpha(c) (((c) >> 24) & 0xff) #define plutovg_red(c) (((c) >> 16) & 0xff) #define plutovg_green(c) (((c) >> 8) & 0xff) #define plutovg_blue(c) (((c) >> 0) & 0xff) #define plutovg_array_init(array) \ do { \ (array).data = NULL; \ (array).size = 0; \ (array).capacity = 0; \ } while(0) #define plutovg_array_ensure(array, count) \ do { \ if((array).size + (count) > (array).capacity) { \ int capacity = (array).size + (count); \ int newcapacity = (array).capacity == 0 ? 8 : (array).capacity; \ while(newcapacity < capacity) { newcapacity *= 2; } \ (array).data = realloc((array).data, newcapacity * sizeof((array).data[0])); \ (array).capacity = newcapacity; \ } \ } while(0) #define plutovg_array_append_data(array, newdata, count) \ do { \ if(newdata && count > 0) { \ plutovg_array_ensure(array, count); \ memcpy((array).data + (array).size, newdata, (count) * sizeof((newdata)[0])); \ (array).size += count; \ } \ } while(0) #define plutovg_array_append(array, other) plutovg_array_append_data(array, (other).data, (other).size) #define plutovg_array_clear(array) ((array).size = 0) #define plutovg_array_destroy(array) free((array).data) static inline uint32_t plutovg_premultiply_argb(uint32_t color) { uint32_t a = plutovg_alpha(color); uint32_t r = plutovg_red(color); uint32_t g = plutovg_green(color); uint32_t b = plutovg_blue(color); if(a != 255) { r = (r * a) / 255; g = (g * a) / 255; b = (b * a) / 255; } return (a << 24) | (r << 16) | (g << 8) | (b); } static inline bool plutovg_parse_number(const char** begin, const char* end, float* number) { const char* it = *begin; float integer = 0; float fraction = 0; float exponent = 0; int sign = 1; int expsign = 1; if(it < end && *it == '+') { ++it; } else if(it < end && *it == '-') { ++it; sign = -1; } if(it >= end || (*it != '.' && !PLUTOVG_IS_NUM(*it))) return false; if(PLUTOVG_IS_NUM(*it)) { do { integer = 10.f * integer + (*it++ - '0'); } while(it < end && PLUTOVG_IS_NUM(*it)); } if(it < end && *it == '.') { ++it; if(it >= end || !PLUTOVG_IS_NUM(*it)) return false; float divisor = 1.f; do { fraction = 10.f * fraction + (*it++ - '0'); divisor *= 10.f; } while(it < end && PLUTOVG_IS_NUM(*it)); fraction /= divisor; } if(it < end && (*it == 'e' || *it == 'E')) { ++it; if(it < end && *it == '+') { ++it; } else if(it < end && *it == '-') { ++it; expsign = -1; } if(it >= end || !PLUTOVG_IS_NUM(*it)) return false; do { exponent = 10 * exponent + (*it++ - '0'); } while(it < end && PLUTOVG_IS_NUM(*it)); } *begin = it; *number = sign * (integer + fraction); if(exponent) *number *= powf(10.f, expsign * exponent); return *number >= -FLT_MAX && *number <= FLT_MAX; } static inline bool plutovg_skip_delim(const char** begin, const char* end, const char delim) { const char* it = *begin; if(it < end && *it == delim) { *begin = it + 1; return true; } return false; } static inline bool plutovg_skip_string(const char** begin, const char* end, const char* data) { const char* it = *begin; while(it < end && *data && *it == *data) { ++data; ++it; } if(*data == '\0') { *begin = it; return true; } return false; } static inline bool plutovg_skip_ws(const char** begin, const char* end) { const char* it = *begin; while(it < end && PLUTOVG_IS_WS(*it)) ++it; *begin = it; return it < end; } static inline bool plutovg_skip_ws_and_delim(const char** begin, const char* end, char delim) { const char* it = *begin; if(plutovg_skip_ws(&it, end)) { if(!plutovg_skip_delim(&it, end, delim)) return false; plutovg_skip_ws(&it, end); } else { return false; } *begin = it; return it < end; } static inline bool plutovg_skip_ws_and_comma(const char** begin, const char* end) { return plutovg_skip_ws_and_delim(begin, end, ','); } static inline bool plutovg_skip_ws_or_delim(const char** begin, const char* end, char delim, bool* has_delim) { const char* it = *begin; if(has_delim) *has_delim = false; if(plutovg_skip_ws(&it, end)) { if(plutovg_skip_delim(&it, end, delim)) { if(has_delim) *has_delim = true; plutovg_skip_ws(&it, end); } } if(it == *begin) return false; *begin = it; return it < end; } static inline bool plutovg_skip_ws_or_comma(const char** begin, const char* end, bool* has_comma) { return plutovg_skip_ws_or_delim(begin, end, ',', has_comma); } #endif // PLUTOVG_UTILS_H