├── .editorconfig ├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── README.md ├── examples ├── CMakeLists.txt ├── raylib-aseprite-aseprite.png ├── raylib-aseprite-example.c ├── raylib-aseprite-example.gif ├── raylib-aseprite-example.png ├── raylib-aseprite-numbers.c ├── raylib-aseprite-numbers.png ├── raylib-aseprite-walk.c └── resources │ ├── george.aseprite │ ├── george.md │ └── numbers.aseprite ├── include ├── CMakeLists.txt ├── cute_aseprite.h └── raylib-aseprite.h └── test ├── CMakeLists.txt ├── raylib-aseprite-test.c └── resources └── numbers.aseprite /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | charset = utf-8 6 | end_of_line = lf 7 | indent_size = 4 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | .vscode 3 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.11) 2 | project (raylib_aseprite 3 | VERSION 5.5.0 4 | DESCRIPTION "raylib_aseprite: Use Aseprite files in raylib" 5 | HOMEPAGE_URL "https://github.com/robloach/raylib-aseprite" 6 | LANGUAGES C) 7 | 8 | # Include Directory 9 | add_subdirectory(include) 10 | 11 | # Options 12 | if ("${CMAKE_SOURCE_DIR}" STREQUAL "${CMAKE_CURRENT_SOURCE_DIR}") 13 | set(RAYLIB_ASEPRITE_IS_MAIN TRUE) 14 | else() 15 | set(RAYLIB_ASEPRITE_IS_MAIN FALSE) 16 | endif() 17 | option(RAYLIB_ASEPRITE_BUILD_EXAMPLES "Examples" ${RAYLIB_ASEPRITE_IS_MAIN}) 18 | 19 | # Examples 20 | if(RAYLIB_ASEPRITE_BUILD_EXAMPLES) 21 | add_subdirectory(examples) 22 | 23 | # Testing 24 | include(CTest) 25 | enable_testing() 26 | if(BUILD_TESTING) 27 | # set(CTEST_CUSTOM_TESTS_IGNORE 28 | # pkg-config--static 29 | # ) 30 | add_subdirectory(test) 31 | endif() 32 | endif() 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2021 Rob Loach (@RobLoach) 2 | 3 | This software is provided "as-is", without any express or implied warranty. In no event 4 | will the authors be held liable for any damages arising from the use of this software. 5 | 6 | Permission is granted to anyone to use this software for any purpose, including commercial 7 | applications, and to alter it and redistribute it freely, subject to the following restrictions: 8 | 9 | 1. The origin of this software must not be misrepresented; you must not claim that you 10 | wrote the original software. If you use this software in a product, an acknowledgment 11 | in the product documentation would be appreciated but is not required. 12 | 13 | 2. Altered source versions must be plainly marked as such, and must not be misrepresented 14 | as being the original software. 15 | 16 | 3. This notice may not be removed or altered from any source distribution. 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # raylib-aseprite 2 | 3 | Load [Aseprite](https://www.aseprite.org) `.aseprite` and `.ase` files for animated sprites in [raylib](https://www.raylib.com). 4 | 5 | ![examples/raylib-aseprite-aseprite.png](examples/raylib-aseprite-aseprite.png) 6 | ![examples/raylib-aseprite-example.gif](examples/raylib-aseprite-example.gif) 7 | 8 | ## Features 9 | 10 | - Load [Aseprite](https://www.aseprite.org/) files directly for use in raylib 11 | - Draw individual frames 12 | - Load and draw [Aseprite tags](https://www.aseprite.org/docs/tags/) as sprite animations 13 | - Support for Forwards, Backwards, and Ping-Pong animations 14 | - Adjust tag animation speed by using `tag.speed` 15 | - Pause tag animations by using `tag.pause` 16 | - Toggle whether animations will continue when they finish with `tag.loop` 17 | - Load [Aseprite slice](https://www.aseprite.org/docs/slices/) rectangles for collisions and bounds 18 | 19 | ## Usage 20 | 21 | This is a header-only library. To use it, define `RAYLIB_ASEPRITE_IMPLEMENTATION` in one .c source file before including [*raylib-aseprite.h*](include). You will also have to link the raylib dependency. 22 | 23 | ### Example 24 | 25 | The below is a basic example, see the [examples](examples) folder for more. 26 | 27 | ``` c 28 | #include "raylib.h" 29 | 30 | #define RAYLIB_ASEPRITE_IMPLEMENTATION 31 | #include "raylib-aseprite.h" 32 | 33 | int main() { 34 | InitWindow(640, 480, "Aseprite Example"); 35 | 36 | // Load the George Aseprite file. 37 | Aseprite george = LoadAseprite("resources/george.aseprite"); 38 | 39 | // Load the Walk Down tag. 40 | AsepriteTag walkdown = LoadAsepriteTag(george, "Walk-Down"); 41 | walkdown.speed = 2; // Double the animation speed. 42 | 43 | while(!WindowShouldClose()) { 44 | // Update the animation frame. 45 | UpdateAsperiteTag(&walkdown); 46 | 47 | BeginDrawing(); 48 | { 49 | ClearBackground(RAYWHITE); 50 | 51 | // Draw the first frame from the George sprite. 52 | DrawAseprite(george, 0, 100, 100, WHITE); 53 | 54 | // Draw the Walk Down animation. 55 | DrawAsepriteTag(walkdown, 200, 100, WHITE); 56 | } 57 | EndDrawing(); 58 | } 59 | 60 | // Clean up the George aseprite. 61 | UnloadAseprite(george); 62 | 63 | CloseWindow(); 64 | return 0; 65 | } 66 | ``` 67 | 68 | See the [examples directory](examples) for more demonstrations of how to use *raylib-aseprite*. 69 | 70 | ### API 71 | 72 | ``` c 73 | // Aseprite functions 74 | Aseprite LoadAseprite(const char* fileName); // Load an .aseprite file 75 | Aseprite LoadAsepriteFromMemory(unsigned char* fileData, int size); // Load an aseprite file from memory 76 | bool IsAsepriteValid(Aseprite aseprite); // Check if the given Aseprite was loaded successfully 77 | void UnloadAseprite(Aseprite aseprite); // Unloads the aseprite file 78 | void TraceAseprite(Aseprite aseprite); // Display all information associated with the aseprite 79 | Texture GetAsepriteTexture(Aseprite aseprite); // Retrieve the raylib texture associated with the aseprite 80 | int GetAsepriteWidth(Aseprite aseprite); // Get the width of the sprite 81 | int GetAsepriteHeight(Aseprite aseprite); // Get the height of the sprite 82 | void DrawAseprite(Aseprite aseprite, int frame, int posX, int posY, Color tint); 83 | void DrawAsepriteV(Aseprite aseprite, int frame, Vector2 position, Color tint); 84 | void DrawAsepriteEx(Aseprite aseprite, int frame, Vector2 position, float rotation, float scale, Color tint); 85 | void DrawAsepritePro(Aseprite aseprite, int frame, Rectangle dest, Vector2 origin, float rotation, Color tint); 86 | 87 | // Aseprite Tag functions 88 | AsepriteTag LoadAsepriteTag(Aseprite aseprite, const char* name); // Load an Aseprite tag animation sequence 89 | AsepriteTag LoadAsepriteTagFromIndex(Aseprite aseprite, int index); // Load an Aseprite tag animation sequence from its index 90 | int GetAsepriteTagCount(Aseprite aseprite); // Get the total amount of available tags 91 | bool IsAsepriteTagValid(AsepriteTag tag); // Check if the given Aseprite tag was loaded successfully 92 | void UpdateAsepriteTag(AsepriteTag* tag); // Update the tag animation frame 93 | AsepriteTag GenAsepriteTagDefault(); // Generate an empty Tag with sane defaults 94 | void DrawAsepriteTag(AsepriteTag tag, int posX, int posY, Color tint); 95 | void DrawAsepriteTagV(AsepriteTag tag, Vector2 position, Color tint); 96 | void DrawAsepriteTagEx(AsepriteTag tag, Vector2 position, float rotation, float scale, Color tint); 97 | void DrawAsepriteTagPro(AsepriteTag tag, Rectangle dest, Vector2 origin, float rotation, Color tint); 98 | 99 | // Aseprite Slice functions 100 | AsepriteSlice LoadAsepriteSlice(Aseprite aseprite, const char* name); // Load a slice from an Aseprite based on its name. 101 | AsepriteSlice LoadAsperiteSliceFromIndex(Aseprite aseprite, int index); // Load a slice from an Aseprite based on its index. 102 | int GetAsepriteSliceCount(Aseprite aseprite); // Get the amount of slices that are defined in the Aseprite. 103 | bool IsAsepriteSliceValid(AsepriteSlice slice); // Return whether or not the given slice was found. 104 | AsepriteSlice GenAsepriteSliceDefault(); // Generate empty Aseprite slice data. 105 | ``` 106 | 107 | ## Development 108 | 109 | To build the example locally, and run tests, use [cmake](https://cmake.org/). 110 | 111 | ``` bash 112 | git submodule update --init 113 | mkdir build 114 | cd build 115 | cmake .. 116 | make 117 | cd examples 118 | ./raylib-aseprite-example 119 | ``` 120 | 121 | This uses [cute_aseprite.h](https://github.com/RandyGaul/cute_headers/blob/master/cute_aseprite.h) to handle loading the aseprite file. Thank you to [Randy Gaul's cute_headers](https://github.com/RandyGaul/cute_headers) for making this all possible. 122 | 123 | ## License 124 | 125 | *raylib-aseprite* is licensed under an unmodified zlib/libpng license, which is an OSI-certified, BSD-like license that allows static linking with closed source software. Check [LICENSE](LICENSE) for further details. 126 | -------------------------------------------------------------------------------- /examples/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # raylib 2 | find_package(raylib QUIET) 3 | if (NOT raylib_FOUND) 4 | include(FetchContent) 5 | FetchContent_Declare( 6 | raylib 7 | GIT_REPOSITORY https://github.com/raysan5/raylib.git 8 | GIT_TAG 5.5 9 | ) 10 | FetchContent_GetProperties(raylib) 11 | if (NOT raylib_POPULATED) # Have we downloaded raylib yet? 12 | set(FETCHCONTENT_QUIET NO) 13 | FetchContent_Populate(raylib) 14 | set(BUILD_EXAMPLES OFF CACHE BOOL "" FORCE) 15 | set(BUILD_GAMES OFF CACHE BOOL "" FORCE) 16 | add_subdirectory(${raylib_SOURCE_DIR} ${raylib_BINARY_DIR}) 17 | endif() 18 | endif() 19 | 20 | # raylib-aseprite-example 21 | add_executable(raylib-aseprite-example raylib-aseprite-example.c) 22 | target_link_libraries(raylib-aseprite-example PUBLIC 23 | raylib 24 | raylib_aseprite 25 | ) 26 | set_property(TARGET raylib-aseprite-example PROPERTY C_STANDARD 99) 27 | 28 | # raylib-aseprite-numbers 29 | add_executable(raylib-aseprite-numbers raylib-aseprite-numbers.c) 30 | target_link_libraries(raylib-aseprite-numbers PUBLIC 31 | raylib 32 | raylib_aseprite 33 | ) 34 | set_property(TARGET raylib-aseprite-numbers PROPERTY C_STANDARD 99) 35 | 36 | # raylib-aseprite-walk 37 | add_executable(raylib-aseprite-walk raylib-aseprite-walk.c) 38 | target_link_libraries(raylib-aseprite-walk PUBLIC 39 | raylib 40 | raylib_aseprite 41 | ) 42 | set_property(TARGET raylib-aseprite-walk PROPERTY C_STANDARD 99) 43 | 44 | # Copy the resources 45 | file(GLOB resources resources/*) 46 | set(test_resources) 47 | list(APPEND test_resources ${resources}) 48 | file(COPY ${test_resources} DESTINATION "resources/") 49 | -------------------------------------------------------------------------------- /examples/raylib-aseprite-aseprite.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RobLoach/raylib-aseprite/e268a17c6e320a7443d476e28ecfc8023fc64be7/examples/raylib-aseprite-aseprite.png -------------------------------------------------------------------------------- /examples/raylib-aseprite-example.c: -------------------------------------------------------------------------------- 1 | /******************************************************************************************* 2 | * 3 | * [raylib-aseprite] example - Load a Aseprite file, and display the animated sprite. 4 | * 5 | * This example has been created using raylib 3.5 (www.raylib.com) 6 | * raylib is licensed under an unmodified zlib/libpng license (View raylib.h for details) 7 | * 8 | * Example by Rob Loach (@RobLoach) 9 | * 10 | * Copyright (c) 2021 Rob Loach (@RobLoach) 11 | * 12 | ********************************************************************************************/ 13 | 14 | #include "raylib.h" 15 | 16 | #define RAYLIB_ASEPRITE_IMPLEMENTATION 17 | #include "raylib-aseprite.h" 18 | 19 | int main() { 20 | // Initialization 21 | //-------------------------------------------------------------------------------------- 22 | const int screenWidth = 800; 23 | const int screenHeight = 450; 24 | InitWindow(screenWidth, screenHeight, "[raylib-aseprite] example"); 25 | SetTargetFPS(60); 26 | 27 | // Load the Aseprite file. 28 | Aseprite george = LoadAseprite("resources/george.aseprite"); 29 | 30 | // Load the standing animations from the Aseprite tags. 31 | AsepriteTag walking = LoadAsepriteTag(george, "Walk-Down"); 32 | 33 | // Center George on the screen. 34 | const float scale = 4; 35 | Vector2 position = { 36 | GetScreenWidth() / 2 - GetAsepriteWidth(george) / 2 * scale, 37 | GetScreenHeight() / 2 - GetAsepriteHeight(george) / 2 * scale 38 | }; 39 | //-------------------------------------------------------------------------------------- 40 | 41 | while(!WindowShouldClose()) { 42 | 43 | // Update 44 | //---------------------------------------------------------------------------------- 45 | // Update the active moving animation. 46 | UpdateAsepriteTag(&walking); 47 | //---------------------------------------------------------------------------------- 48 | 49 | // Draw 50 | //---------------------------------------------------------------------------------- 51 | BeginDrawing(); 52 | { 53 | ClearBackground(RAYWHITE); 54 | 55 | // Draw one frame of George. 56 | DrawAseprite(george, 0, 100, 100, WHITE); 57 | DrawAseprite(george, 4, 100, 150, WHITE); 58 | DrawAseprite(george, 8, 100, 200, WHITE); 59 | DrawAsepriteFlipped(george, 12, 100, 250, false, true, WHITE); 60 | 61 | // Draw the walking animation. 62 | DrawAsepriteTagEx(walking, position, 0, scale, WHITE); 63 | } 64 | EndDrawing(); 65 | //---------------------------------------------------------------------------------- 66 | } 67 | 68 | // De-Initialization 69 | //-------------------------------------------------------------------------------------- 70 | 71 | UnloadAseprite(george); // Unload the Aseprite data. 72 | 73 | CloseWindow(); 74 | //-------------------------------------------------------------------------------------- 75 | 76 | return 0; 77 | } 78 | -------------------------------------------------------------------------------- /examples/raylib-aseprite-example.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RobLoach/raylib-aseprite/e268a17c6e320a7443d476e28ecfc8023fc64be7/examples/raylib-aseprite-example.gif -------------------------------------------------------------------------------- /examples/raylib-aseprite-example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RobLoach/raylib-aseprite/e268a17c6e320a7443d476e28ecfc8023fc64be7/examples/raylib-aseprite-example.png -------------------------------------------------------------------------------- /examples/raylib-aseprite-numbers.c: -------------------------------------------------------------------------------- 1 | /******************************************************************************************* 2 | * 3 | * [raylib-aseprite] numbers - Load the numbers Aseprite file, and display the different animation directions. 4 | * 5 | * This example has been created using raylib 3.5 (www.raylib.com) 6 | * raylib is licensed under an unmodified zlib/libpng license (View raylib.h for details) 7 | * 8 | * Example by Rob Loach (@RobLoach) 9 | * 10 | * Copyright (c) 2021 Rob Loach (@RobLoach) 11 | * 12 | ********************************************************************************************/ 13 | 14 | #include "raylib.h" 15 | 16 | #define RAYLIB_ASEPRITE_IMPLEMENTATION 17 | #include "raylib-aseprite.h" 18 | 19 | int main() { 20 | // Initialization 21 | //-------------------------------------------------------------------------------------- 22 | 23 | const int screenWidth = 800; 24 | const int screenHeight = 450; 25 | const int textTop = 140; 26 | const int numberTop = 200; 27 | InitWindow(screenWidth, screenHeight, "[raylib-aseprite] numbers"); 28 | 29 | // Load the Aseprite file. 30 | Aseprite numbers = LoadAseprite("resources/numbers.aseprite"); 31 | 32 | // Load the tag animations from the numbers aseprite. 33 | AsepriteTag forwards = LoadAsepriteTag(numbers, "Forwards"); 34 | AsepriteTag backwards = LoadAsepriteTag(numbers, "Backwards"); 35 | AsepriteTag pingpong = LoadAsepriteTag(numbers, "Ping-Pong"); 36 | 37 | //-------------------------------------------------------------------------------------- 38 | 39 | while(!WindowShouldClose()) { 40 | 41 | // Update 42 | //---------------------------------------------------------------------------------- 43 | 44 | // Update the active animation of the walk down tag. 45 | UpdateAsepriteTag(&forwards); 46 | UpdateAsepriteTag(&backwards); 47 | UpdateAsepriteTag(&pingpong); 48 | //---------------------------------------------------------------------------------- 49 | 50 | // Draw 51 | //---------------------------------------------------------------------------------- 52 | 53 | BeginDrawing(); 54 | { 55 | ClearBackground(RAYWHITE); 56 | const int width = GetAsepriteWidth(numbers); 57 | 58 | // Forwards 59 | DrawText(forwards.name, screenWidth / 4 - width, textTop, 20, forwards.color); 60 | DrawAsepriteTag(forwards, screenWidth / 4 - width, numberTop, WHITE); 61 | 62 | // Backwards 63 | DrawText(backwards.name, screenWidth / 2 - width, textTop, 20, backwards.color); 64 | DrawAsepriteTag(backwards, screenWidth / 2 - width, numberTop, WHITE); 65 | 66 | // Ping-Pong 67 | DrawText(pingpong.name, screenWidth - screenWidth / 4 - width, textTop, 20, pingpong.color); 68 | DrawAsepriteTag(pingpong, screenWidth - screenWidth / 4 - width, numberTop, WHITE); 69 | } 70 | EndDrawing(); 71 | //---------------------------------------------------------------------------------- 72 | } 73 | 74 | // De-Initialization 75 | //-------------------------------------------------------------------------------------- 76 | 77 | UnloadAseprite(numbers); // Unload the Aseprite data. 78 | 79 | CloseWindow(); 80 | //-------------------------------------------------------------------------------------- 81 | 82 | return 0; 83 | } 84 | -------------------------------------------------------------------------------- /examples/raylib-aseprite-numbers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RobLoach/raylib-aseprite/e268a17c6e320a7443d476e28ecfc8023fc64be7/examples/raylib-aseprite-numbers.png -------------------------------------------------------------------------------- /examples/raylib-aseprite-walk.c: -------------------------------------------------------------------------------- 1 | /******************************************************************************************* 2 | * 3 | * [raylib-aseprite] example - Load a Aseprite file, and display the animated sprite. 4 | * 5 | * This example has been created using raylib 3.5 (www.raylib.com) 6 | * raylib is licensed under an unmodified zlib/libpng license (View raylib.h for details) 7 | * 8 | * Example by Rob Loach (@RobLoach) 9 | * 10 | * Copyright (c) 2021 Rob Loach (@RobLoach) 11 | * 12 | ********************************************************************************************/ 13 | 14 | #include "raylib.h" 15 | 16 | #define RAYLIB_ASEPRITE_IMPLEMENTATION 17 | #include "raylib-aseprite.h" 18 | 19 | int main() { 20 | // Initialization 21 | //-------------------------------------------------------------------------------------- 22 | const int screenWidth = 800; 23 | const int screenHeight = 450; 24 | InitWindow(screenWidth, screenHeight, "[raylib-aseprite] example"); 25 | SetTargetFPS(60); 26 | 27 | // Load the Aseprite file. 28 | Aseprite george = LoadAseprite("resources/george.aseprite"); 29 | 30 | // Load the standing animations from the Aseprite tags. 31 | AsepriteTag down = LoadAsepriteTag(george, "Walk-Down"); 32 | AsepriteTag left = LoadAsepriteTag(george, "Walk-Left"); 33 | AsepriteTag right = LoadAsepriteTag(george, "Walk-Right"); 34 | AsepriteTag up = LoadAsepriteTag(george, "Walk-Up"); 35 | AsepriteTag* current = &down; 36 | 37 | // Center George on the screen. 38 | const float scale = 6; 39 | const int speed = 4; 40 | Vector2 position = { 41 | GetScreenWidth() / 2 - GetAsepriteWidth(george) / 2 * scale, 42 | GetScreenHeight() / 2 - GetAsepriteHeight(george) / 2 * scale 43 | }; 44 | //-------------------------------------------------------------------------------------- 45 | 46 | while(!WindowShouldClose()) { 47 | 48 | // Update 49 | //---------------------------------------------------------------------------------- 50 | 51 | // Update the active moving animation. 52 | if (IsKeyDown(KEY_UP)) { 53 | current = &up; 54 | position.y -= speed; 55 | current->paused = false; 56 | } 57 | else if (IsKeyDown(KEY_RIGHT)) { 58 | current = &right; 59 | position.x += speed; 60 | current->paused = false; 61 | } 62 | else if (IsKeyDown(KEY_DOWN)) { 63 | current = &down; 64 | position.y += speed; 65 | current->paused = false; 66 | } 67 | else if (IsKeyDown(KEY_LEFT)) { 68 | current = &left; 69 | position.x -= speed; 70 | current->paused = false; 71 | } 72 | else { 73 | current->paused = true; 74 | } 75 | 76 | // Have George stop walking with two feet on the ground. 77 | if (current->paused) { 78 | SetAsepriteTagFrame(current, 1); 79 | } 80 | 81 | // Update the animation. 82 | UpdateAsepriteTag(current); 83 | 84 | //---------------------------------------------------------------------------------- 85 | 86 | // Draw 87 | //---------------------------------------------------------------------------------- 88 | BeginDrawing(); 89 | { 90 | ClearBackground(RAYWHITE); 91 | 92 | // Draw the current walking animation. 93 | DrawAsepriteTagEx(*current, position, 0, scale, WHITE); 94 | 95 | const char* text = "Use arrow keys to walk"; 96 | DrawText(text, GetScreenWidth() / 2 - MeasureText(text, 20) / 2, GetScreenHeight() - 80, 20, GRAY); 97 | } 98 | EndDrawing(); 99 | //---------------------------------------------------------------------------------- 100 | } 101 | 102 | // De-Initialization 103 | //-------------------------------------------------------------------------------------- 104 | 105 | UnloadAseprite(george); // Unload the Aseprite data. 106 | 107 | CloseWindow(); 108 | //-------------------------------------------------------------------------------------- 109 | 110 | return 0; 111 | } 112 | -------------------------------------------------------------------------------- /examples/resources/george.aseprite: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RobLoach/raylib-aseprite/e268a17c6e320a7443d476e28ecfc8023fc64be7/examples/resources/george.aseprite -------------------------------------------------------------------------------- /examples/resources/george.md: -------------------------------------------------------------------------------- 1 | # George 2 | 3 | George is an alternate LCP character by [sheep](https://opengameart.org/users/sheep), from [OpenGameArt.org](https://opengameart.org/content/alternate-lpc-character-sprites-george). 4 | 5 | ## License 6 | 7 | - CC-BY 3.0 8 | - CC-BY-SA 3.0 9 | - GPL 3.0 10 | -------------------------------------------------------------------------------- /examples/resources/numbers.aseprite: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RobLoach/raylib-aseprite/e268a17c6e320a7443d476e28ecfc8023fc64be7/examples/resources/numbers.aseprite -------------------------------------------------------------------------------- /include/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(raylib_aseprite INTERFACE) 2 | 3 | # Include Directory 4 | target_include_directories(raylib_aseprite INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/) 5 | 6 | # Set the header files as install files. 7 | install(FILES 8 | raylib-aseprite.h 9 | cute_aseprite.h 10 | DESTINATION include 11 | ) 12 | -------------------------------------------------------------------------------- /include/cute_aseprite.h: -------------------------------------------------------------------------------- 1 | /* 2 | ------------------------------------------------------------------------------ 3 | Licensing information can be found at the end of the file. 4 | ------------------------------------------------------------------------------ 5 | 6 | cute_aseprite.h - v1.04 7 | 8 | To create implementation (the function definitions) 9 | #define CUTE_ASEPRITE_IMPLEMENTATION 10 | in *one* C/CPP file (translation unit) that includes this file 11 | 12 | 13 | SUMMARY 14 | 15 | cute_aseprite.h is a single-file header that implements some functions to 16 | parse .ase/.aseprite files. The entire file is parsed all at once and some 17 | structs are filled out then handed back to you. 18 | 19 | 20 | LIMITATIONS 21 | 22 | Only the "normal" blend mode for layers is supported. As a workaround try 23 | using the "merge down" function in Aseprite to create a normal layer. 24 | Supporting all blend modes would take too much code to be worth it. 25 | 26 | Does not support very old versions of Aseprite (with old palette chunks 27 | 0x0004 or 0x0011). Also does not support deprecrated mask chunk. 28 | 29 | sRGB and ICC profiles are parsed but completely ignored when blending 30 | frames together. If you want these to be used when composing frames you 31 | have to do this yourself. 32 | 33 | 34 | SPECIAL THANKS 35 | 36 | Special thanks to Noel Berry for the blend code in his reference C++ 37 | implementation (https://github.com/NoelFB/blah). 38 | 39 | Special thanks to Richard Mitton for the initial implementation of the 40 | zlib inflater. 41 | 42 | 43 | Revision history: 44 | 1.00 (08/25/2020) initial release 45 | 1.01 (08/31/2020) fixed memleaks, tag parsing bug (crash), blend bugs 46 | 1.02 (02/05/2022) fixed icc profile parse bug, support transparent pal- 47 | ette index, can parse 1.3 files (no tileset support) 48 | 1.03 (11/27/2023) fixed slice pivot parse bug 49 | 1.04 (02/20/2024) chunck 0x0004 support 50 | */ 51 | 52 | /* 53 | DOCUMENTATION 54 | 55 | Simply load an .ase or .aseprite file from disk or from memory like so. 56 | 57 | ase_t* ase = cute_aseprite_load_from_file("data/player.aseprite", NULL); 58 | 59 | 60 | Then access the fields directly, assuming you have your own `Animation` type. 61 | 62 | int w = ase->w; 63 | int h = ase->h; 64 | Animation anim = { 0 }; // Your custom animation data type. 65 | 66 | for (int i = 0; i < ase->frame_count; ++i) { 67 | ase_frame_t* frame = ase->frames + i; 68 | anim.add_frame(frame->duration_milliseconds, frame->pixels); 69 | } 70 | 71 | 72 | Then free it up when done. 73 | 74 | cute_aseprite_free(ase); 75 | 76 | 77 | DATA STRUCTURES 78 | 79 | Aseprite files have frames, layers, and cels. A single frame is one frame of an 80 | animation, formed by blending all the cels of an animation together. There is 81 | one cel per layer per frame. Each cel contains its own pixel data. 82 | 83 | The frame's pixels are automatically assumed to have been blended by the `normal` 84 | blend mode. A warning is emit if any other blend mode is encountered. Feel free 85 | to update the pixels of each frame with your own implementation of blending 86 | functions. The frame's pixels are merely provided like this for convenience. 87 | 88 | 89 | BUGS AND CRASHES 90 | 91 | This header is quite new and it takes time to test all the parse paths. Don't be 92 | shy about opening a GitHub issue if there's a crash! It's quite easy to update 93 | the parser as long as you upload your .ase file that shows the bug. 94 | 95 | https://github.com/RandyGaul/cute_headers/issues 96 | */ 97 | 98 | #ifndef CUTE_ASEPRITE_H 99 | #define CUTE_ASEPRITE_H 100 | 101 | typedef struct ase_t ase_t; 102 | 103 | ase_t* cute_aseprite_load_from_file(const char* path, void* mem_ctx); 104 | ase_t* cute_aseprite_load_from_memory(const void* memory, int size, void* mem_ctx); 105 | void cute_aseprite_free(ase_t* aseprite); 106 | 107 | #define CUTE_ASEPRITE_MAX_LAYERS (64) 108 | #define CUTE_ASEPRITE_MAX_SLICES (128) 109 | #define CUTE_ASEPRITE_MAX_PALETTE_ENTRIES (1024) 110 | #define CUTE_ASEPRITE_MAX_TAGS (256) 111 | 112 | #include 113 | 114 | typedef struct ase_color_t ase_color_t; 115 | typedef struct ase_frame_t ase_frame_t; 116 | typedef struct ase_layer_t ase_layer_t; 117 | typedef struct ase_cel_t ase_cel_t; 118 | typedef struct ase_tag_t ase_tag_t; 119 | typedef struct ase_slice_t ase_slice_t; 120 | typedef struct ase_palette_entry_t ase_palette_entry_t; 121 | typedef struct ase_palette_t ase_palette_t; 122 | typedef struct ase_udata_t ase_udata_t; 123 | typedef struct ase_cel_extra_chunk_t ase_cel_extra_chunk_t; 124 | typedef struct ase_color_profile_t ase_color_profile_t; 125 | typedef struct ase_fixed_t ase_fixed_t; 126 | 127 | struct ase_color_t 128 | { 129 | uint8_t r, g, b, a; 130 | }; 131 | 132 | struct ase_fixed_t 133 | { 134 | uint16_t a; 135 | uint16_t b; 136 | }; 137 | 138 | struct ase_udata_t 139 | { 140 | int has_color; 141 | ase_color_t color; 142 | int has_text; 143 | const char* text; 144 | }; 145 | 146 | typedef enum ase_layer_flags_t 147 | { 148 | ASE_LAYER_FLAGS_VISIBLE = 0x01, 149 | ASE_LAYER_FLAGS_EDITABLE = 0x02, 150 | ASE_LAYER_FLAGS_LOCK_MOVEMENT = 0x04, 151 | ASE_LAYER_FLAGS_BACKGROUND = 0x08, 152 | ASE_LAYER_FLAGS_PREFER_LINKED_CELS = 0x10, 153 | ASE_LAYER_FLAGS_COLLAPSED = 0x20, 154 | ASE_LAYER_FLAGS_REFERENCE = 0x40, 155 | } ase_layer_flags_t; 156 | 157 | typedef enum ase_layer_type_t 158 | { 159 | ASE_LAYER_TYPE_NORMAL, 160 | ASE_LAYER_TYPE_GROUP, 161 | } ase_layer_type_t; 162 | 163 | struct ase_layer_t 164 | { 165 | ase_layer_flags_t flags; 166 | ase_layer_type_t type; 167 | const char* name; 168 | ase_layer_t* parent; 169 | float opacity; 170 | ase_udata_t udata; 171 | }; 172 | 173 | struct ase_cel_extra_chunk_t 174 | { 175 | int precise_bounds_are_set; 176 | ase_fixed_t precise_x; 177 | ase_fixed_t precise_y; 178 | ase_fixed_t w, h; 179 | }; 180 | 181 | struct ase_cel_t 182 | { 183 | ase_layer_t* layer; 184 | void* pixels; 185 | int w, h; 186 | int x, y; 187 | float opacity; 188 | int is_linked; 189 | uint16_t linked_frame_index; 190 | int has_extra; 191 | ase_cel_extra_chunk_t extra; 192 | ase_udata_t udata; 193 | }; 194 | 195 | struct ase_frame_t 196 | { 197 | ase_t* ase; 198 | int duration_milliseconds; 199 | ase_color_t* pixels; 200 | int cel_count; 201 | ase_cel_t cels[CUTE_ASEPRITE_MAX_LAYERS]; 202 | }; 203 | 204 | typedef enum ase_animation_direction_t 205 | { 206 | ASE_ANIMATION_DIRECTION_FORWARDS, 207 | ASE_ANIMATION_DIRECTION_BACKWORDS, 208 | ASE_ANIMATION_DIRECTION_PINGPONG, 209 | } ase_animation_direction_t; 210 | 211 | struct ase_tag_t 212 | { 213 | int from_frame; 214 | int to_frame; 215 | ase_animation_direction_t loop_animation_direction; 216 | int repeat; 217 | uint8_t r, g, b; 218 | const char* name; 219 | ase_udata_t udata; 220 | }; 221 | 222 | struct ase_slice_t 223 | { 224 | const char* name; 225 | int frame_number; 226 | int origin_x; 227 | int origin_y; 228 | int w, h; 229 | 230 | int has_center_as_9_slice; 231 | int center_x; 232 | int center_y; 233 | int center_w; 234 | int center_h; 235 | 236 | int has_pivot; 237 | int pivot_x; 238 | int pivot_y; 239 | 240 | ase_udata_t udata; 241 | }; 242 | 243 | struct ase_palette_entry_t 244 | { 245 | ase_color_t color; 246 | const char* color_name; 247 | }; 248 | 249 | struct ase_palette_t 250 | { 251 | int entry_count; 252 | ase_palette_entry_t entries[CUTE_ASEPRITE_MAX_PALETTE_ENTRIES]; 253 | }; 254 | 255 | typedef enum ase_color_profile_type_t 256 | { 257 | ASE_COLOR_PROFILE_TYPE_NONE, 258 | ASE_COLOR_PROFILE_TYPE_SRGB, 259 | ASE_COLOR_PROFILE_TYPE_EMBEDDED_ICC, 260 | } ase_color_profile_type_t; 261 | 262 | struct ase_color_profile_t 263 | { 264 | ase_color_profile_type_t type; 265 | int use_fixed_gamma; 266 | ase_fixed_t gamma; 267 | uint32_t icc_profile_data_length; 268 | void* icc_profile_data; 269 | }; 270 | 271 | typedef enum ase_mode_t 272 | { 273 | ASE_MODE_RGBA, 274 | ASE_MODE_GRAYSCALE, 275 | ASE_MODE_INDEXED 276 | } ase_mode_t; 277 | 278 | struct ase_t 279 | { 280 | ase_mode_t mode; 281 | int w, h; 282 | int transparent_palette_entry_index; 283 | int number_of_colors; 284 | int pixel_w; 285 | int pixel_h; 286 | int grid_x; 287 | int grid_y; 288 | int grid_w; 289 | int grid_h; 290 | int has_color_profile; 291 | ase_color_profile_t color_profile; 292 | ase_palette_t palette; 293 | 294 | int layer_count; 295 | ase_layer_t layers[CUTE_ASEPRITE_MAX_LAYERS]; 296 | 297 | int frame_count; 298 | ase_frame_t* frames; 299 | 300 | int tag_count; 301 | ase_tag_t tags[CUTE_ASEPRITE_MAX_TAGS]; 302 | 303 | int slice_count; 304 | ase_slice_t slices[CUTE_ASEPRITE_MAX_SLICES]; 305 | 306 | void* mem_ctx; 307 | }; 308 | 309 | #endif // CUTE_ASEPRITE_H 310 | 311 | #ifdef CUTE_ASEPRITE_IMPLEMENTATION 312 | #ifndef CUTE_ASEPRITE_IMPLEMENTATION_ONCE 313 | #define CUTE_ASEPRITE_IMPLEMENTATION_ONCE 314 | 315 | #ifndef _CRT_SECURE_NO_WARNINGS 316 | #define _CRT_SECURE_NO_WARNINGS 317 | #endif 318 | 319 | #ifndef _CRT_NONSTDC_NO_DEPRECATE 320 | #define _CRT_NONSTDC_NO_DEPRECATE 321 | #endif 322 | 323 | #if !defined(CUTE_ASEPRITE_ALLOC) 324 | #include 325 | #define CUTE_ASEPRITE_ALLOC(size, ctx) malloc(size) 326 | #define CUTE_ASEPRITE_FREE(mem, ctx) free(mem) 327 | #endif 328 | 329 | #if !defined(CUTE_ASEPRITE_UNUSED) 330 | #if defined(_MSC_VER) 331 | #define CUTE_ASEPRITE_UNUSED(x) (void)x 332 | #else 333 | #define CUTE_ASEPRITE_UNUSED(x) (void)(sizeof(x)) 334 | #endif 335 | #endif 336 | 337 | #if !defined(CUTE_ASEPRITE_MEMCPY) 338 | #include // memcpy 339 | #define CUTE_ASEPRITE_MEMCPY memcpy 340 | #endif 341 | 342 | #if !defined(CUTE_ASEPRITE_MEMSET) 343 | #include // memset 344 | #define CUTE_ASEPRITE_MEMSET memset 345 | #endif 346 | 347 | #if !defined(CUTE_ASEPRITE_ASSERT) 348 | #include 349 | #define CUTE_ASEPRITE_ASSERT assert 350 | #endif 351 | 352 | #if !defined(CUTE_ASEPRITE_SEEK_SET) 353 | #include // SEEK_SET 354 | #define CUTE_ASEPRITE_SEEK_SET SEEK_SET 355 | #endif 356 | 357 | #if !defined(CUTE_ASEPRITE_SEEK_END) 358 | #include // SEEK_END 359 | #define CUTE_ASEPRITE_SEEK_END SEEK_END 360 | #endif 361 | 362 | #if !defined(CUTE_ASEPRITE_FILE) 363 | #include // FILE 364 | #define CUTE_ASEPRITE_FILE FILE 365 | #endif 366 | 367 | #if !defined(CUTE_ASEPRITE_FOPEN) 368 | #include // fopen 369 | #define CUTE_ASEPRITE_FOPEN fopen 370 | #endif 371 | 372 | #if !defined(CUTE_ASEPRITE_FSEEK) 373 | #include // fseek 374 | #define CUTE_ASEPRITE_FSEEK fseek 375 | #endif 376 | 377 | #if !defined(CUTE_ASEPRITE_FREAD) 378 | #include // fread 379 | #define CUTE_ASEPRITE_FREAD fread 380 | #endif 381 | 382 | #if !defined(CUTE_ASEPRITE_FTELL) 383 | #include // ftell 384 | #define CUTE_ASEPRITE_FTELL ftell 385 | #endif 386 | 387 | #if !defined(CUTE_ASEPRITE_FCLOSE) 388 | #include // fclose 389 | #define CUTE_ASEPRITE_FCLOSE fclose 390 | #endif 391 | 392 | static const char* s_error_file = NULL; // The filepath of the file being parsed. NULL if from memory. 393 | static const char* s_error_reason; // Used to capture errors during DEFLATE parsing. 394 | 395 | #if !defined(CUTE_ASEPRITE_WARNING) 396 | #define CUTE_ASEPRITE_WARNING(msg) cute_aseprite_warning(msg, __LINE__) 397 | 398 | static int s_error_cline; // The line in cute_aseprite.h where the error was triggered. 399 | void cute_aseprite_warning(const char* warning, int line) 400 | { 401 | s_error_cline = line; 402 | const char *error_file = s_error_file ? s_error_file : "MEMORY"; 403 | printf("WARNING (cute_aseprite.h:%i): %s (%s)\n", s_error_cline, warning, error_file); 404 | } 405 | #endif 406 | 407 | #define CUTE_ASEPRITE_FAIL() do { goto ase_err; } while (0) 408 | #define CUTE_ASEPRITE_CHECK(X, Y) do { if (!(X)) { s_error_reason = Y; CUTE_ASEPRITE_FAIL(); } } while (0) 409 | #define CUTE_ASEPRITE_CALL(X) do { if (!(X)) goto ase_err; } while (0) 410 | #define CUTE_ASEPRITE_DEFLATE_MAX_BITLEN 15 411 | 412 | // DEFLATE tables from RFC 1951 413 | static uint8_t s_fixed_table[288 + 32] = { 414 | 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, 415 | 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, 416 | 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, 417 | 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, 418 | 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,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, 419 | }; // 3.2.6 420 | static uint8_t s_permutation_order[19] = { 16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15 }; // 3.2.7 421 | static uint8_t s_len_extra_bits[29 + 2] = { 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 }; // 3.2.5 422 | static uint32_t s_len_base[29 + 2] = { 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 }; // 3.2.5 423 | static uint8_t s_dist_extra_bits[30 + 2] = { 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, 0,0 }; // 3.2.5 424 | static uint32_t s_dist_base[30 + 2] = { 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 }; // 3.2.5 425 | 426 | typedef struct deflate_t 427 | { 428 | uint64_t bits; 429 | int count; 430 | uint32_t* words; 431 | int word_count; 432 | int word_index; 433 | int bits_left; 434 | 435 | int final_word_available; 436 | uint32_t final_word; 437 | 438 | char* out; 439 | char* out_end; 440 | char* begin; 441 | 442 | uint32_t lit[288]; 443 | uint32_t dst[32]; 444 | uint32_t len[19]; 445 | uint32_t nlit; 446 | uint32_t ndst; 447 | uint32_t nlen; 448 | } deflate_t; 449 | 450 | static int s_would_overflow(deflate_t* s, int num_bits) 451 | { 452 | return (s->bits_left + s->count) - num_bits < 0; 453 | } 454 | 455 | static char* s_ptr(deflate_t* s) 456 | { 457 | CUTE_ASEPRITE_ASSERT(!(s->bits_left & 7)); 458 | return (char*)(s->words + s->word_index) - (s->count / 8); 459 | } 460 | 461 | static uint64_t s_peak_bits(deflate_t* s, int num_bits_to_read) 462 | { 463 | if (s->count < num_bits_to_read) 464 | { 465 | if (s->word_index < s->word_count) 466 | { 467 | uint32_t word = s->words[s->word_index++]; 468 | s->bits |= (uint64_t)word << s->count; 469 | s->count += 32; 470 | CUTE_ASEPRITE_ASSERT(s->word_index <= s->word_count); 471 | } 472 | 473 | else if (s->final_word_available) 474 | { 475 | uint32_t word = s->final_word; 476 | s->bits |= (uint64_t)word << s->count; 477 | s->count += s->bits_left; 478 | s->final_word_available = 0; 479 | } 480 | } 481 | 482 | return s->bits; 483 | } 484 | 485 | static uint32_t s_consume_bits(deflate_t* s, int num_bits_to_read) 486 | { 487 | CUTE_ASEPRITE_ASSERT(s->count >= num_bits_to_read); 488 | uint32_t bits = (uint32_t)(s->bits & (((uint64_t)1 << num_bits_to_read) - 1)); 489 | s->bits >>= num_bits_to_read; 490 | s->count -= num_bits_to_read; 491 | s->bits_left -= num_bits_to_read; 492 | return bits; 493 | } 494 | 495 | static uint32_t s_read_bits(deflate_t* s, int num_bits_to_read) 496 | { 497 | CUTE_ASEPRITE_ASSERT(num_bits_to_read <= 32); 498 | CUTE_ASEPRITE_ASSERT(num_bits_to_read >= 0); 499 | CUTE_ASEPRITE_ASSERT(s->bits_left > 0); 500 | CUTE_ASEPRITE_ASSERT(s->count <= 64); 501 | CUTE_ASEPRITE_ASSERT(!s_would_overflow(s, num_bits_to_read)); 502 | s_peak_bits(s, num_bits_to_read); 503 | uint32_t bits = s_consume_bits(s, num_bits_to_read); 504 | return bits; 505 | } 506 | 507 | static uint32_t s_rev16(uint32_t a) 508 | { 509 | a = ((a & 0xAAAA) >> 1) | ((a & 0x5555) << 1); 510 | a = ((a & 0xCCCC) >> 2) | ((a & 0x3333) << 2); 511 | a = ((a & 0xF0F0) >> 4) | ((a & 0x0F0F) << 4); 512 | a = ((a & 0xFF00) >> 8) | ((a & 0x00FF) << 8); 513 | return a; 514 | } 515 | 516 | // RFC 1951 section 3.2.2 517 | static uint32_t s_build(deflate_t* s, uint32_t* tree, uint8_t* lens, int sym_count) 518 | { 519 | int n, codes[16], first[16], counts[16] = { 0 }; 520 | CUTE_ASEPRITE_UNUSED(s); 521 | 522 | // Frequency count 523 | for (n = 0; n < sym_count; n++) counts[lens[n]]++; 524 | 525 | // Distribute codes 526 | counts[0] = codes[0] = first[0] = 0; 527 | for (n = 1; n <= 15; ++n) 528 | { 529 | codes[n] = (codes[n - 1] + counts[n - 1]) << 1; 530 | first[n] = first[n - 1] + counts[n - 1]; 531 | } 532 | 533 | for (uint32_t i = 0; i < (uint32_t)sym_count; ++i) 534 | { 535 | uint8_t len = lens[i]; 536 | 537 | if (len != 0) 538 | { 539 | CUTE_ASEPRITE_ASSERT(len < 16); 540 | uint32_t code = (uint32_t)codes[len]++; 541 | uint32_t slot = (uint32_t)first[len]++; 542 | tree[slot] = (code << (32 - (uint32_t)len)) | (i << 4) | len; 543 | } 544 | } 545 | 546 | return (uint32_t)first[15]; 547 | } 548 | 549 | static int s_stored(deflate_t* s) 550 | { 551 | char* p; 552 | 553 | // 3.2.3 554 | // skip any remaining bits in current partially processed byte 555 | s_read_bits(s, s->count & 7); 556 | 557 | // 3.2.4 558 | // read LEN and NLEN, should complement each other 559 | uint16_t LEN = (uint16_t)s_read_bits(s, 16); 560 | uint16_t NLEN = (uint16_t)s_read_bits(s, 16); 561 | uint16_t TILDE_NLEN = ~NLEN; 562 | CUTE_ASEPRITE_CHECK(LEN == TILDE_NLEN, "Failed to find LEN and NLEN as complements within stored (uncompressed) stream."); 563 | CUTE_ASEPRITE_CHECK(s->bits_left / 8 <= (int)LEN, "Stored block extends beyond end of input stream."); 564 | p = s_ptr(s); 565 | CUTE_ASEPRITE_MEMCPY(s->out, p, LEN); 566 | s->out += LEN; 567 | return 1; 568 | 569 | ase_err: 570 | return 0; 571 | } 572 | 573 | // 3.2.6 574 | static int s_fixed(deflate_t* s) 575 | { 576 | s->nlit = s_build(s, s->lit, s_fixed_table, 288); 577 | s->ndst = s_build(0, s->dst, s_fixed_table + 288, 32); 578 | return 1; 579 | } 580 | 581 | static int s_decode(deflate_t* s, uint32_t* tree, int hi) 582 | { 583 | uint64_t bits = s_peak_bits(s, 16); 584 | uint32_t search = (s_rev16((uint32_t)bits) << 16) | 0xFFFF; 585 | int lo = 0; 586 | while (lo < hi) 587 | { 588 | int guess = (lo + hi) >> 1; 589 | if (search < tree[guess]) hi = guess; 590 | else lo = guess + 1; 591 | } 592 | 593 | uint32_t key = tree[lo - 1]; 594 | uint32_t len = (32 - (key & 0xF)); 595 | CUTE_ASEPRITE_ASSERT((search >> len) == (key >> len)); 596 | 597 | s_consume_bits(s, key & 0xF); 598 | return (key >> 4) & 0xFFF; 599 | } 600 | 601 | // 3.2.7 602 | static int s_dynamic(deflate_t* s) 603 | { 604 | uint8_t lenlens[19] = { 0 }; 605 | 606 | uint32_t nlit = 257 + s_read_bits(s, 5); 607 | uint32_t ndst = 1 + s_read_bits(s, 5); 608 | uint32_t nlen = 4 + s_read_bits(s, 4); 609 | 610 | for (uint32_t i = 0 ; i < nlen; ++i) 611 | lenlens[s_permutation_order[i]] = (uint8_t)s_read_bits(s, 3); 612 | 613 | // Build the tree for decoding code lengths 614 | s->nlen = s_build(0, s->len, lenlens, 19); 615 | uint8_t lens[288 + 32]; 616 | 617 | for (uint32_t n = 0; n < nlit + ndst;) 618 | { 619 | int sym = s_decode(s, s->len, (int)s->nlen); 620 | switch (sym) 621 | { 622 | case 16: for (uint32_t i = 3 + s_read_bits(s, 2); i; --i, ++n) lens[n] = lens[n - 1]; break; 623 | case 17: for (uint32_t i = 3 + s_read_bits(s, 3); i; --i, ++n) lens[n] = 0; break; 624 | case 18: for (uint32_t i = 11 + s_read_bits(s, 7); i; --i, ++n) lens[n] = 0; break; 625 | default: lens[n++] = (uint8_t)sym; break; 626 | } 627 | } 628 | 629 | s->nlit = s_build(s, s->lit, lens, (int)nlit); 630 | s->ndst = s_build(0, s->dst, lens + nlit, (int)ndst); 631 | return 1; 632 | } 633 | 634 | // 3.2.3 635 | static int s_block(deflate_t* s) 636 | { 637 | while (1) 638 | { 639 | int symbol = s_decode(s, s->lit, (int)s->nlit); 640 | 641 | if (symbol < 256) 642 | { 643 | CUTE_ASEPRITE_CHECK(s->out + 1 <= s->out_end, "Attempted to overwrite out buffer while outputting a symbol."); 644 | *s->out = (char)symbol; 645 | s->out += 1; 646 | } 647 | 648 | else if (symbol > 256) 649 | { 650 | symbol -= 257; 651 | uint32_t length = s_read_bits(s, (int)(s_len_extra_bits[symbol])) + s_len_base[symbol]; 652 | int distance_symbol = s_decode(s, s->dst, (int)s->ndst); 653 | uint32_t backwards_distance = s_read_bits(s, s_dist_extra_bits[distance_symbol]) + s_dist_base[distance_symbol]; 654 | CUTE_ASEPRITE_CHECK(s->out - backwards_distance >= s->begin, "Attempted to write before out buffer (invalid backwards distance)."); 655 | CUTE_ASEPRITE_CHECK(s->out + length <= s->out_end, "Attempted to overwrite out buffer while outputting a string."); 656 | char* src = s->out - backwards_distance; 657 | char* dst = s->out; 658 | s->out += length; 659 | 660 | switch (backwards_distance) 661 | { 662 | case 1: // very common in images 663 | CUTE_ASEPRITE_MEMSET(dst, *src, (size_t)length); 664 | break; 665 | default: while (length--) *dst++ = *src++; 666 | } 667 | } 668 | 669 | else break; 670 | } 671 | 672 | return 1; 673 | 674 | ase_err: 675 | return 0; 676 | } 677 | 678 | // 3.2.3 679 | static int s_inflate(const void* in, int in_bytes, void* out, int out_bytes, void* mem_ctx) 680 | { 681 | CUTE_ASEPRITE_UNUSED(mem_ctx); 682 | deflate_t* s = (deflate_t*)CUTE_ASEPRITE_ALLOC(sizeof(deflate_t), mem_ctx); 683 | s->bits = 0; 684 | s->count = 0; 685 | s->word_index = 0; 686 | s->bits_left = in_bytes * 8; 687 | 688 | // s->words is the in-pointer rounded up to a multiple of 4 689 | int first_bytes = (int)((((size_t)in + 3) & (size_t)(~3)) - (size_t)in); 690 | s->words = (uint32_t*)((char*)in + first_bytes); 691 | s->word_count = (in_bytes - first_bytes) / 4; 692 | int last_bytes = ((in_bytes - first_bytes) & 3); 693 | 694 | for (int i = 0; i < first_bytes; ++i) 695 | s->bits |= (uint64_t)(((uint8_t*)in)[i]) << (i * 8); 696 | 697 | s->final_word_available = last_bytes ? 1 : 0; 698 | s->final_word = 0; 699 | for(int i = 0; i < last_bytes; i++) 700 | s->final_word |= ((uint8_t*)in)[in_bytes - last_bytes + i] << (i * 8); 701 | 702 | s->count = first_bytes * 8; 703 | 704 | s->out = (char*)out; 705 | s->out_end = s->out + out_bytes; 706 | s->begin = (char*)out; 707 | 708 | int count = 0; 709 | uint32_t bfinal; 710 | do 711 | { 712 | bfinal = s_read_bits(s, 1); 713 | uint32_t btype = s_read_bits(s, 2); 714 | 715 | switch (btype) 716 | { 717 | case 0: CUTE_ASEPRITE_CALL(s_stored(s)); break; 718 | case 1: s_fixed(s); CUTE_ASEPRITE_CALL(s_block(s)); break; 719 | case 2: s_dynamic(s); CUTE_ASEPRITE_CALL(s_block(s)); break; 720 | case 3: CUTE_ASEPRITE_CHECK(0, "Detected unknown block type within input stream."); 721 | } 722 | 723 | ++count; 724 | } 725 | while (!bfinal); 726 | 727 | CUTE_ASEPRITE_FREE(s, mem_ctx); 728 | return 1; 729 | 730 | ase_err: 731 | CUTE_ASEPRITE_FREE(s, mem_ctx); 732 | return 0; 733 | } 734 | 735 | typedef struct ase_state_t 736 | { 737 | uint8_t* in; 738 | uint8_t* end; 739 | void* mem_ctx; 740 | } ase_state_t; 741 | 742 | static uint8_t s_read_uint8(ase_state_t* s) 743 | { 744 | CUTE_ASEPRITE_ASSERT(s->in <= s->end + sizeof(uint8_t)); 745 | uint8_t** p = &s->in; 746 | uint8_t value = **p; 747 | ++(*p); 748 | return value; 749 | } 750 | 751 | static uint16_t s_read_uint16(ase_state_t* s) 752 | { 753 | CUTE_ASEPRITE_ASSERT(s->in <= s->end + sizeof(uint16_t)); 754 | uint8_t** p = &s->in; 755 | uint16_t value; 756 | value = (*p)[0]; 757 | value |= (((uint16_t)((*p)[1])) << 8); 758 | *p += 2; 759 | return value; 760 | } 761 | 762 | static ase_fixed_t s_read_fixed(ase_state_t* s) 763 | { 764 | ase_fixed_t value; 765 | value.a = s_read_uint16(s); 766 | value.b = s_read_uint16(s); 767 | return value; 768 | } 769 | 770 | static uint32_t s_read_uint32(ase_state_t* s) 771 | { 772 | CUTE_ASEPRITE_ASSERT(s->in <= s->end + sizeof(uint32_t)); 773 | uint8_t** p = &s->in; 774 | uint32_t value; 775 | value = (*p)[0]; 776 | value |= (((uint32_t)((*p)[1])) << 8); 777 | value |= (((uint32_t)((*p)[2])) << 16); 778 | value |= (((uint32_t)((*p)[3])) << 24); 779 | *p += 4; 780 | return value; 781 | } 782 | 783 | #ifdef CUTE_ASPRITE_S_READ_UINT64 784 | // s_read_uint64() is not currently used. 785 | static uint64_t s_read_uint64(ase_state_t* s) 786 | { 787 | CUTE_ASEPRITE_ASSERT(s->in <= s->end + sizeof(uint64_t)); 788 | uint8_t** p = &s->in; 789 | uint64_t value; 790 | value = (*p)[0]; 791 | value |= (((uint64_t)((*p)[1])) << 8 ); 792 | value |= (((uint64_t)((*p)[2])) << 16); 793 | value |= (((uint64_t)((*p)[3])) << 24); 794 | value |= (((uint64_t)((*p)[4])) << 32); 795 | value |= (((uint64_t)((*p)[5])) << 40); 796 | value |= (((uint64_t)((*p)[6])) << 48); 797 | value |= (((uint64_t)((*p)[7])) << 56); 798 | *p += 8; 799 | return value; 800 | } 801 | #endif 802 | 803 | #define s_read_int16(s) (int16_t)s_read_uint16(s) 804 | #define s_read_int32(s) (int32_t)s_read_uint32(s) 805 | 806 | #ifdef CUTE_ASPRITE_S_READ_BYTES 807 | // s_read_bytes() is not currently used. 808 | static void s_read_bytes(ase_state_t* s, uint8_t* bytes, int num_bytes) 809 | { 810 | for (int i = 0; i < num_bytes; ++i) { 811 | bytes[i] = s_read_uint8(s); 812 | } 813 | } 814 | #endif 815 | 816 | static const char* s_read_string(ase_state_t* s) 817 | { 818 | int len = (int)s_read_uint16(s); 819 | char* bytes = (char*)CUTE_ASEPRITE_ALLOC(len + 1, s->mem_ctx); 820 | for (int i = 0; i < len; ++i) { 821 | bytes[i] = (char)s_read_uint8(s); 822 | } 823 | bytes[len] = 0; 824 | return bytes; 825 | } 826 | 827 | static void s_skip(ase_state_t* ase, int num_bytes) 828 | { 829 | CUTE_ASEPRITE_ASSERT(ase->in <= ase->end + num_bytes); 830 | ase->in += num_bytes; 831 | } 832 | 833 | static char* s_fopen(const char* path, int* size, void* mem_ctx) 834 | { 835 | CUTE_ASEPRITE_UNUSED(mem_ctx); 836 | char* data = 0; 837 | CUTE_ASEPRITE_FILE* fp = CUTE_ASEPRITE_FOPEN(path, "rb"); 838 | int sz = 0; 839 | 840 | if (fp) { 841 | CUTE_ASEPRITE_FSEEK(fp, 0, CUTE_ASEPRITE_SEEK_END); 842 | sz = (int)CUTE_ASEPRITE_FTELL(fp); 843 | CUTE_ASEPRITE_FSEEK(fp, 0, CUTE_ASEPRITE_SEEK_SET); 844 | data = (char*)CUTE_ASEPRITE_ALLOC(sz + 1, mem_ctx); 845 | CUTE_ASEPRITE_FREAD(data, sz, 1, fp); 846 | data[sz] = 0; 847 | CUTE_ASEPRITE_FCLOSE(fp); 848 | } 849 | 850 | if (size) *size = sz; 851 | return data; 852 | } 853 | 854 | ase_t* cute_aseprite_load_from_file(const char* path, void* mem_ctx) 855 | { 856 | s_error_file = path; 857 | int sz; 858 | void* file = s_fopen(path, &sz, mem_ctx); 859 | if (!file) { 860 | CUTE_ASEPRITE_WARNING("Unable to find map file."); 861 | return NULL; 862 | } 863 | ase_t* aseprite = cute_aseprite_load_from_memory(file, sz, mem_ctx); 864 | CUTE_ASEPRITE_FREE(file, mem_ctx); 865 | s_error_file = NULL; 866 | return aseprite; 867 | } 868 | 869 | static int s_mul_un8(int a, int b) 870 | { 871 | int t = (a * b) + 0x80; 872 | return (((t >> 8) + t) >> 8); 873 | } 874 | 875 | static ase_color_t s_blend(ase_color_t src, ase_color_t dst, uint8_t opacity) 876 | { 877 | src.a = (uint8_t)s_mul_un8(src.a, opacity); 878 | int a = src.a + dst.a - s_mul_un8(src.a, dst.a); 879 | int r, g, b; 880 | if (a == 0) { 881 | r = g = b = 0; 882 | } else { 883 | r = dst.r + (src.r - dst.r) * src.a / a; 884 | g = dst.g + (src.g - dst.g) * src.a / a; 885 | b = dst.b + (src.b - dst.b) * src.a / a; 886 | } 887 | ase_color_t ret = { (uint8_t)r, (uint8_t)g, (uint8_t)b, (uint8_t)a }; 888 | return ret; 889 | } 890 | 891 | static int s_min(int a, int b) 892 | { 893 | return a < b ? a : b; 894 | } 895 | 896 | static int s_max(int a, int b) 897 | { 898 | return a < b ? b : a; 899 | } 900 | 901 | static ase_color_t s_color(ase_t* ase, void* src, int index) 902 | { 903 | ase_color_t result; 904 | if (ase->mode == ASE_MODE_RGBA) { 905 | result = ((ase_color_t*)src)[index]; 906 | } else if (ase->mode == ASE_MODE_GRAYSCALE) { 907 | uint8_t saturation = ((uint8_t*)src)[index * 2]; 908 | uint8_t a = ((uint8_t*)src)[index * 2 + 1]; 909 | result.r = result.g = result.b = saturation; 910 | result.a = a; 911 | } else { 912 | CUTE_ASEPRITE_ASSERT(ase->mode == ASE_MODE_INDEXED); 913 | uint8_t palette_index = ((uint8_t*)src)[index]; 914 | if (palette_index == ase->transparent_palette_entry_index) { 915 | result.r = 0; 916 | result.g = 0; 917 | result.b = 0; 918 | result.a = 0; 919 | } else { 920 | result = ase->palette.entries[palette_index].color; 921 | } 922 | } 923 | return result; 924 | } 925 | 926 | ase_t* cute_aseprite_load_from_memory(const void* memory, int size, void* mem_ctx) 927 | { 928 | ase_t* ase = (ase_t*)CUTE_ASEPRITE_ALLOC(sizeof(ase_t), mem_ctx); 929 | CUTE_ASEPRITE_MEMSET(ase, 0, sizeof(*ase)); 930 | 931 | ase_state_t state = { 0, 0, 0 }; 932 | ase_state_t* s = &state; 933 | s->in = (uint8_t*)memory; 934 | s->end = s->in + size; 935 | s->mem_ctx = mem_ctx; 936 | 937 | s_skip(s, sizeof(uint32_t)); // File size. 938 | int magic = (int)s_read_uint16(s); 939 | CUTE_ASEPRITE_ASSERT(magic == 0xA5E0); 940 | 941 | ase->frame_count = (int)s_read_uint16(s); 942 | ase->w = s_read_uint16(s); 943 | ase->h = s_read_uint16(s); 944 | uint16_t bpp = s_read_uint16(s) / 8; 945 | if (bpp == 4) ase->mode = ASE_MODE_RGBA; 946 | else if (bpp == 2) ase->mode = ASE_MODE_GRAYSCALE; 947 | else { 948 | CUTE_ASEPRITE_ASSERT(bpp == 1); 949 | ase->mode = ASE_MODE_INDEXED; 950 | } 951 | uint32_t valid_layer_opacity = s_read_uint32(s) & 1; 952 | int speed = s_read_uint16(s); 953 | s_skip(s, sizeof(uint32_t) * 2); // Spec says skip these bytes, as they're zero'd. 954 | ase->transparent_palette_entry_index = s_read_uint8(s); 955 | s_skip(s, 3); // Spec says skip these bytes. 956 | ase->number_of_colors = (int)s_read_uint16(s); 957 | ase->pixel_w = (int)s_read_uint8(s); 958 | ase->pixel_h = (int)s_read_uint8(s); 959 | ase->grid_x = (int)s_read_int16(s); 960 | ase->grid_y = (int)s_read_int16(s); 961 | ase->grid_w = (int)s_read_uint16(s); 962 | ase->grid_h = (int)s_read_uint16(s); 963 | s_skip(s, 84); // For future use (set to zero). 964 | 965 | ase->frames = (ase_frame_t*)CUTE_ASEPRITE_ALLOC((int)(sizeof(ase_frame_t)) * ase->frame_count, mem_ctx); 966 | CUTE_ASEPRITE_MEMSET(ase->frames, 0, sizeof(ase_frame_t) * (size_t)ase->frame_count); 967 | 968 | ase_udata_t* last_udata = NULL; 969 | int was_on_tags = 0; 970 | int tag_index = 0; 971 | 972 | ase_layer_t* layer_stack[CUTE_ASEPRITE_MAX_LAYERS]; 973 | 974 | // Parse all chunks in the .aseprite file. 975 | for (int i = 0; i < ase->frame_count; ++i) { 976 | ase_frame_t* frame = ase->frames + i; 977 | frame->ase = ase; 978 | s_skip(s, sizeof(uint32_t)); // Frame size. 979 | magic = (int)s_read_uint16(s); 980 | CUTE_ASEPRITE_ASSERT(magic == 0xF1FA); 981 | int chunk_count = (int)s_read_uint16(s); 982 | frame->duration_milliseconds = s_read_uint16(s); 983 | if (frame->duration_milliseconds == 0) frame->duration_milliseconds = speed; 984 | s_skip(s, 2); // For future use (set to zero). 985 | uint32_t new_chunk_count = s_read_uint32(s); 986 | if (new_chunk_count) chunk_count = (int)new_chunk_count; 987 | 988 | for (int j = 0; j < chunk_count; ++j) { 989 | uint32_t chunk_size = s_read_uint32(s); 990 | uint16_t chunk_type = s_read_uint16(s); 991 | chunk_size -= (uint32_t)(sizeof(uint32_t) + sizeof(uint16_t)); 992 | uint8_t* chunk_start = s->in; 993 | 994 | switch (chunk_type) { 995 | case 0x0004: // Old Palette chunk (used when there are no colors with alpha in the palette) 996 | { 997 | uint16_t nbPackets = s_read_uint16(s); 998 | for (uint16_t k = 0; k < nbPackets; k++) { 999 | uint16_t maxColor = 0; 1000 | uint16_t skip = (uint16_t)s_read_uint8(s); 1001 | uint16_t nbColors = (uint16_t)s_read_uint8(s); 1002 | if (nbColors == 0) nbColors = 256; 1003 | 1004 | for (uint16_t l = 0; l < nbColors; l++) { 1005 | ase_palette_entry_t entry; 1006 | entry.color.r = s_read_uint8(s); 1007 | entry.color.g = s_read_uint8(s); 1008 | entry.color.b = s_read_uint8(s); 1009 | entry.color.a = 255; 1010 | entry.color_name = NULL; 1011 | ase->palette.entries[skip + l] = entry; 1012 | if (skip + l > maxColor) maxColor = skip + l; 1013 | } 1014 | 1015 | ase->palette.entry_count = maxColor+1; 1016 | } 1017 | 1018 | } break; 1019 | case 0x2004: // Layer chunk. 1020 | { 1021 | CUTE_ASEPRITE_ASSERT(ase->layer_count < CUTE_ASEPRITE_MAX_LAYERS); 1022 | ase_layer_t* layer = ase->layers + ase->layer_count++; 1023 | layer->flags = (ase_layer_flags_t)s_read_uint16(s); 1024 | layer->type = (ase_layer_type_t)s_read_uint16(s); 1025 | layer->parent = NULL; 1026 | int child_level = (int)s_read_uint16(s); 1027 | layer_stack[child_level] = layer; 1028 | if (child_level) { 1029 | layer->parent = layer_stack[child_level - 1]; 1030 | } 1031 | s_skip(s, sizeof(uint16_t)); // Default layer width in pixels (ignored). 1032 | s_skip(s, sizeof(uint16_t)); // Default layer height in pixels (ignored). 1033 | int blend_mode = (int)s_read_uint16(s); 1034 | if (blend_mode) CUTE_ASEPRITE_WARNING("Unknown blend mode encountered."); 1035 | layer->opacity = s_read_uint8(s) / 255.0f; 1036 | if (!valid_layer_opacity) layer->opacity = 1.0f; 1037 | s_skip(s, 3); // For future use (set to zero). 1038 | layer->name = s_read_string(s); 1039 | last_udata = &layer->udata; 1040 | } break; 1041 | 1042 | case 0x2005: // Cel chunk. 1043 | { 1044 | CUTE_ASEPRITE_ASSERT(frame->cel_count < CUTE_ASEPRITE_MAX_LAYERS); 1045 | ase_cel_t* cel = frame->cels + frame->cel_count++; 1046 | int layer_index = (int)s_read_uint16(s); 1047 | cel->layer = ase->layers + layer_index; 1048 | cel->x = s_read_int16(s); 1049 | cel->y = s_read_int16(s); 1050 | cel->opacity = s_read_uint8(s) / 255.0f; 1051 | int cel_type = (int)s_read_uint16(s); 1052 | s_skip(s, 7); // For future (set to zero). 1053 | switch (cel_type) { 1054 | case 0: // Raw cel. 1055 | cel->w = s_read_uint16(s); 1056 | cel->h = s_read_uint16(s); 1057 | cel->pixels = CUTE_ASEPRITE_ALLOC(cel->w * cel->h * bpp, mem_ctx); 1058 | CUTE_ASEPRITE_MEMCPY(cel->pixels, s->in, (size_t)(cel->w * cel->h * bpp)); 1059 | s_skip(s, cel->w * cel->h * bpp); 1060 | break; 1061 | 1062 | case 1: // Linked cel. 1063 | cel->is_linked = 1; 1064 | cel->linked_frame_index = s_read_uint16(s); 1065 | break; 1066 | 1067 | case 2: // Compressed image cel. 1068 | { 1069 | cel->w = s_read_uint16(s); 1070 | cel->h = s_read_uint16(s); 1071 | int zlib_byte0 = s_read_uint8(s); 1072 | int zlib_byte1 = s_read_uint8(s); 1073 | int deflate_bytes = (int)chunk_size - (int)(s->in - chunk_start); 1074 | void* pixels = s->in; 1075 | CUTE_ASEPRITE_ASSERT((zlib_byte0 & 0x0F) == 0x08); // Only zlib compression method (RFC 1950) is supported. 1076 | CUTE_ASEPRITE_ASSERT((zlib_byte0 & 0xF0) <= 0x70); // Innapropriate window size detected. 1077 | CUTE_ASEPRITE_ASSERT(!(zlib_byte1 & 0x20)); // Preset dictionary is present and not supported. 1078 | int pixels_sz = cel->w * cel->h * bpp; 1079 | void* pixels_decompressed = CUTE_ASEPRITE_ALLOC(pixels_sz, mem_ctx); 1080 | int ret = s_inflate(pixels, deflate_bytes, pixels_decompressed, pixels_sz, mem_ctx); 1081 | if (!ret) CUTE_ASEPRITE_WARNING(s_error_reason); 1082 | cel->pixels = pixels_decompressed; 1083 | s_skip(s, deflate_bytes); 1084 | } break; 1085 | } 1086 | last_udata = &cel->udata; 1087 | } break; 1088 | 1089 | case 0x2006: // Cel extra chunk. 1090 | { 1091 | ase_cel_t* cel = frame->cels + frame->cel_count; 1092 | cel->has_extra = 1; 1093 | cel->extra.precise_bounds_are_set = (int)s_read_uint32(s); 1094 | cel->extra.precise_x = s_read_fixed(s); 1095 | cel->extra.precise_y = s_read_fixed(s); 1096 | cel->extra.w = s_read_fixed(s); 1097 | cel->extra.h = s_read_fixed(s); 1098 | s_skip(s, 16); // For future use (set to zero). 1099 | } break; 1100 | 1101 | case 0x2007: // Color profile chunk. 1102 | { 1103 | ase->has_color_profile = 1; 1104 | ase->color_profile.type = (ase_color_profile_type_t)s_read_uint16(s); 1105 | ase->color_profile.use_fixed_gamma = (int)s_read_uint16(s) & 1; 1106 | ase->color_profile.gamma = s_read_fixed(s); 1107 | s_skip(s, 8); // For future use (set to zero). 1108 | if (ase->color_profile.type == ASE_COLOR_PROFILE_TYPE_EMBEDDED_ICC) { 1109 | // Use the embedded ICC profile. 1110 | ase->color_profile.icc_profile_data_length = s_read_uint32(s); 1111 | ase->color_profile.icc_profile_data = CUTE_ASEPRITE_ALLOC(ase->color_profile.icc_profile_data_length, mem_ctx); 1112 | CUTE_ASEPRITE_MEMCPY(ase->color_profile.icc_profile_data, s->in, ase->color_profile.icc_profile_data_length); 1113 | s->in += ase->color_profile.icc_profile_data_length; 1114 | } 1115 | } break; 1116 | 1117 | case 0x2018: // Tags chunk. 1118 | { 1119 | ase->tag_count = (int)s_read_uint16(s); 1120 | s_skip(s, 8); // For future (set to zero). 1121 | CUTE_ASEPRITE_ASSERT(ase->tag_count < CUTE_ASEPRITE_MAX_TAGS); 1122 | for (int k = 0; k < ase->tag_count; ++k) { 1123 | ase->tags[k].from_frame = (int)s_read_uint16(s); 1124 | ase->tags[k].to_frame = (int)s_read_uint16(s); 1125 | ase->tags[k].loop_animation_direction = (ase_animation_direction_t)s_read_uint8(s); 1126 | ase->tags[k].repeat = s_read_uint16(s); 1127 | s_skip(s, 6); // For future (set to zero). 1128 | ase->tags[k].r = s_read_uint8(s); 1129 | ase->tags[k].g = s_read_uint8(s); 1130 | ase->tags[k].b = s_read_uint8(s); 1131 | s_skip(s, 1); // Extra byte (zero). 1132 | ase->tags[k].name = s_read_string(s); 1133 | } 1134 | was_on_tags = 1; 1135 | } break; 1136 | 1137 | case 0x2019: // Palette chunk. 1138 | { 1139 | ase->palette.entry_count = (int)s_read_uint32(s); 1140 | CUTE_ASEPRITE_ASSERT(ase->palette.entry_count <= CUTE_ASEPRITE_MAX_PALETTE_ENTRIES); 1141 | int first_index = (int)s_read_uint32(s); 1142 | int last_index = (int)s_read_uint32(s); 1143 | s_skip(s, 8); // For future (set to zero). 1144 | for (int k = first_index; k <= last_index; ++k) { 1145 | int has_name = s_read_uint16(s); 1146 | ase_palette_entry_t entry; 1147 | entry.color.r = s_read_uint8(s); 1148 | entry.color.g = s_read_uint8(s); 1149 | entry.color.b = s_read_uint8(s); 1150 | entry.color.a = s_read_uint8(s); 1151 | if (has_name) { 1152 | entry.color_name = s_read_string(s); 1153 | } else { 1154 | entry.color_name = NULL; 1155 | } 1156 | CUTE_ASEPRITE_ASSERT(k < CUTE_ASEPRITE_MAX_PALETTE_ENTRIES); 1157 | ase->palette.entries[k] = entry; 1158 | } 1159 | } break; 1160 | 1161 | case 0x2020: // Udata chunk. 1162 | { 1163 | CUTE_ASEPRITE_ASSERT(last_udata || was_on_tags); 1164 | if (was_on_tags && !last_udata) { 1165 | CUTE_ASEPRITE_ASSERT(tag_index < ase->tag_count); 1166 | last_udata = &ase->tags[tag_index++].udata; 1167 | } 1168 | int flags = (int)s_read_uint32(s); 1169 | if (flags & 1) { 1170 | last_udata->has_text = 1; 1171 | last_udata->text = s_read_string(s); 1172 | } 1173 | if (flags & 2) { 1174 | last_udata->color.r = s_read_uint8(s); 1175 | last_udata->color.g = s_read_uint8(s); 1176 | last_udata->color.b = s_read_uint8(s); 1177 | last_udata->color.a = s_read_uint8(s); 1178 | } 1179 | last_udata = NULL; 1180 | } break; 1181 | 1182 | case 0x2022: // Slice chunk. 1183 | { 1184 | int slice_count = (int)s_read_uint32(s); 1185 | int flags = (int)s_read_uint32(s); 1186 | s_skip(s, sizeof(uint32_t)); // Reserved. 1187 | const char* name = s_read_string(s); 1188 | for (int k = 0; k < (int)slice_count; ++k) { 1189 | ase_slice_t slice; 1190 | CUTE_ASEPRITE_MEMSET(&slice, 0, sizeof(slice)); 1191 | slice.name = name; 1192 | slice.frame_number = (int)s_read_uint32(s); 1193 | slice.origin_x = (int)s_read_int32(s); 1194 | slice.origin_y = (int)s_read_int32(s); 1195 | slice.w = (int)s_read_uint32(s); 1196 | slice.h = (int)s_read_uint32(s); 1197 | if (flags & 1) { 1198 | // It's a 9-patches slice. 1199 | slice.has_center_as_9_slice = 1; 1200 | slice.center_x = (int)s_read_int32(s); 1201 | slice.center_y = (int)s_read_int32(s); 1202 | slice.center_w = (int)s_read_uint32(s); 1203 | slice.center_h = (int)s_read_uint32(s); 1204 | } 1205 | if (flags & 2) { 1206 | // Has pivot information. 1207 | slice.has_pivot = 1; 1208 | slice.pivot_x = (int)s_read_int32(s); 1209 | slice.pivot_y = (int)s_read_int32(s); 1210 | } 1211 | CUTE_ASEPRITE_ASSERT(ase->slice_count < CUTE_ASEPRITE_MAX_SLICES); 1212 | ase->slices[ase->slice_count++] = slice; 1213 | last_udata = &ase->slices[ase->slice_count - 1].udata; 1214 | } 1215 | } break; 1216 | 1217 | default: 1218 | s_skip(s, (int)chunk_size); 1219 | break; 1220 | } 1221 | 1222 | uint32_t size_read = (uint32_t)(s->in - chunk_start); 1223 | CUTE_ASEPRITE_ASSERT(size_read == chunk_size); 1224 | } 1225 | } 1226 | 1227 | // Blend all cel pixels into each of their respective frames, for convenience. 1228 | for (int i = 0; i < ase->frame_count; ++i) { 1229 | ase_frame_t* frame = ase->frames + i; 1230 | frame->pixels = (ase_color_t*)CUTE_ASEPRITE_ALLOC((int)(sizeof(ase_color_t)) * ase->w * ase->h, mem_ctx); 1231 | CUTE_ASEPRITE_MEMSET(frame->pixels, 0, sizeof(ase_color_t) * (size_t)ase->w * (size_t)ase->h); 1232 | ase_color_t* dst = frame->pixels; 1233 | for (int j = 0; j < frame->cel_count; ++j) { 1234 | ase_cel_t* cel = frame->cels + j; 1235 | if (!(cel->layer->flags & ASE_LAYER_FLAGS_VISIBLE)) { 1236 | continue; 1237 | } 1238 | if (cel->layer->parent && !(cel->layer->parent->flags & ASE_LAYER_FLAGS_VISIBLE)) { 1239 | continue; 1240 | } 1241 | while (cel->is_linked) { 1242 | ase_frame_t* frame = ase->frames + cel->linked_frame_index; 1243 | int found = 0; 1244 | for (int k = 0; k < frame->cel_count; ++k) { 1245 | if (frame->cels[k].layer == cel->layer) { 1246 | cel = frame->cels + k; 1247 | found = 1; 1248 | break; 1249 | } 1250 | } 1251 | CUTE_ASEPRITE_ASSERT(found); 1252 | } 1253 | void* src = cel->pixels; 1254 | uint8_t opacity = (uint8_t)(cel->opacity * cel->layer->opacity * 255.0f); 1255 | int cx = cel->x; 1256 | int cy = cel->y; 1257 | int cw = cel->w; 1258 | int ch = cel->h; 1259 | int cl = -s_min(cx, 0); 1260 | int ct = -s_min(cy, 0); 1261 | int dl = s_max(cx, 0); 1262 | int dt = s_max(cy, 0); 1263 | int dr = s_min(ase->w, cw + cx); 1264 | int db = s_min(ase->h, ch + cy); 1265 | int aw = ase->w; 1266 | for (int dx = dl, sx = cl; dx < dr; dx++, sx++) { 1267 | for (int dy = dt, sy = ct; dy < db; dy++, sy++) { 1268 | int dst_index = aw * dy + dx; 1269 | ase_color_t src_color = s_color(ase, src, cw * sy + sx); 1270 | ase_color_t dst_color = dst[dst_index]; 1271 | ase_color_t result = s_blend(src_color, dst_color, opacity); 1272 | dst[dst_index] = result; 1273 | } 1274 | } 1275 | } 1276 | } 1277 | 1278 | ase->mem_ctx = mem_ctx; 1279 | return ase; 1280 | } 1281 | 1282 | void cute_aseprite_free(ase_t* ase) 1283 | { 1284 | for (int i = 0; i < ase->frame_count; ++i) { 1285 | ase_frame_t* frame = ase->frames + i; 1286 | CUTE_ASEPRITE_FREE(frame->pixels, ase->mem_ctx); 1287 | for (int j = 0; j < frame->cel_count; ++j) { 1288 | ase_cel_t* cel = frame->cels + j; 1289 | CUTE_ASEPRITE_FREE(cel->pixels, ase->mem_ctx); 1290 | CUTE_ASEPRITE_FREE((void*)cel->udata.text, ase->mem_ctx); 1291 | } 1292 | } 1293 | for (int i = 0; i < ase->layer_count; ++i) { 1294 | ase_layer_t* layer = ase->layers + i; 1295 | CUTE_ASEPRITE_FREE((void*)layer->name, ase->mem_ctx); 1296 | CUTE_ASEPRITE_FREE((void*)layer->udata.text, ase->mem_ctx); 1297 | } 1298 | for (int i = 0; i < ase->tag_count; ++i) { 1299 | ase_tag_t* tag = ase->tags + i; 1300 | CUTE_ASEPRITE_FREE((void*)tag->name, ase->mem_ctx); 1301 | } 1302 | for (int i = 0; i < ase->slice_count; ++i) { 1303 | ase_slice_t* slice = ase->slices + i; 1304 | CUTE_ASEPRITE_FREE((void*)slice->udata.text, ase->mem_ctx); 1305 | } 1306 | if (ase->slice_count) { 1307 | CUTE_ASEPRITE_FREE((void*)ase->slices[0].name, ase->mem_ctx); 1308 | } 1309 | for (int i = 0; i < ase->palette.entry_count; ++i) { 1310 | CUTE_ASEPRITE_FREE((void*)ase->palette.entries[i].color_name, ase->mem_ctx); 1311 | } 1312 | CUTE_ASEPRITE_FREE(ase->color_profile.icc_profile_data, ase->mem_ctx); 1313 | CUTE_ASEPRITE_FREE(ase->frames, ase->mem_ctx); 1314 | CUTE_ASEPRITE_FREE(ase, ase->mem_ctx); 1315 | } 1316 | 1317 | #endif // CUTE_ASEPRITE_IMPLEMENTATION_ONCE 1318 | #endif // CUTE_ASEPRITE_IMPLEMENTATION 1319 | 1320 | /* 1321 | ------------------------------------------------------------------------------ 1322 | This software is available under 2 licenses - you may choose the one you like. 1323 | ------------------------------------------------------------------------------ 1324 | ALTERNATIVE A - zlib license 1325 | Copyright (c) 2017 Randy Gaul http://www.randygaul.net 1326 | This software is provided 'as-is', without any express or implied warranty. 1327 | In no event will the authors be held liable for any damages arising from 1328 | the use of this software. 1329 | Permission is granted to anyone to use this software for any purpose, 1330 | including commercial applications, and to alter it and redistribute it 1331 | freely, subject to the following restrictions: 1332 | 1. The origin of this software must not be misrepresented; you must not 1333 | claim that you wrote the original software. If you use this software 1334 | in a product, an acknowledgment in the product documentation would be 1335 | appreciated but is not required. 1336 | 2. Altered source versions must be plainly marked as such, and must not 1337 | be misrepresented as being the original software. 1338 | 3. This notice may not be removed or altered from any source distribution. 1339 | ------------------------------------------------------------------------------ 1340 | ALTERNATIVE B - Public Domain (www.unlicense.org) 1341 | This is free and unencumbered software released into the public domain. 1342 | Anyone is free to copy, modify, publish, use, compile, sell, or distribute this 1343 | software, either in source code form or as a compiled binary, for any purpose, 1344 | commercial or non-commercial, and by any means. 1345 | In jurisdictions that recognize copyright laws, the author or authors of this 1346 | software dedicate any and all copyright interest in the software to the public 1347 | domain. We make this dedication for the benefit of the public at large and to 1348 | the detriment of our heirs and successors. We intend this dedication to be an 1349 | overt act of relinquishment in perpetuity of all present and future rights to 1350 | this software under copyright law. 1351 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 1352 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 1353 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 1354 | AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 1355 | ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 1356 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 1357 | ------------------------------------------------------------------------------ 1358 | */ 1359 | -------------------------------------------------------------------------------- /include/raylib-aseprite.h: -------------------------------------------------------------------------------- 1 | /********************************************************************************************** 2 | * 3 | * raylib-aseprite - Aseprite sprite loader for raylib. 4 | * 5 | * Copyright 2021 Rob Loach (@RobLoach) 6 | * 7 | * DEPENDENCIES: 8 | * raylib 5.0+ https://www.raylib.com/ 9 | * 10 | * LICENSE: zlib/libpng 11 | * 12 | * raylib-aseprite is licensed under an unmodified zlib/libpng license, which is an OSI-certified, 13 | * BSD-like license that allows static linking with closed source software: 14 | * 15 | * This software is provided "as-is", without any express or implied warranty. In no event 16 | * will the authors be held liable for any damages arising from the use of this software. 17 | * 18 | * Permission is granted to anyone to use this software for any purpose, including commercial 19 | * applications, and to alter it and redistribute it freely, subject to the following restrictions: 20 | * 21 | * 1. The origin of this software must not be misrepresented; you must not claim that you 22 | * wrote the original software. If you use this software in a product, an acknowledgment 23 | * in the product documentation would be appreciated but is not required. 24 | * 25 | * 2. Altered source versions must be plainly marked as such, and must not be misrepresented 26 | * as being the original software. 27 | * 28 | * 3. This notice may not be removed or altered from any source distribution. 29 | * 30 | **********************************************************************************************/ 31 | 32 | #ifndef INCLUDE_RAYLIB_ASEPRITE_H_ 33 | #define INCLUDE_RAYLIB_ASEPRITE_H_ 34 | 35 | #include "raylib.h" // NOLINT 36 | #include "cute_aseprite.h" // NOLINT 37 | 38 | #ifdef __cplusplus 39 | extern "C" { 40 | #endif 41 | 42 | /** 43 | * Aseprite object containing a pointer to the ase_t* from cute_aseprite.h. 44 | * 45 | * @see LoadAseprite() 46 | * @see UnloadAseprite() 47 | */ 48 | typedef struct Aseprite { 49 | ase_t* ase; // Pointer to the cute_aseprite data. 50 | } Aseprite; 51 | 52 | /** 53 | * Tag information from an Aseprite object. 54 | * 55 | * @see LoadAsepriteTag() 56 | * @see LoadAsepriteTagFromIndex() 57 | */ 58 | typedef struct AsepriteTag { 59 | char* name; // The name of the tag. 60 | int currentFrame; // The frame that the tag is currently on 61 | float timer; // The countdown timer in seconds 62 | int direction; // Whether we are moving forwards, or backwards through the frames 63 | float speed; // The animation speed factor (1 is normal speed, 2 is double speed) 64 | Color color; // The color provided for the tag 65 | bool loop; // Whether to continue to play the animation when the animation finishes 66 | bool paused; // Set to true to not progression of the animation 67 | Aseprite aseprite; // The loaded Aseprite file 68 | ase_tag_t* tag; // The active tag to act upon 69 | } AsepriteTag; 70 | 71 | /** 72 | * Slice data for the Aseprite. 73 | * 74 | * @see LoadAsepriteSlice() 75 | * @see https://www.aseprite.org/docs/slices/ 76 | */ 77 | typedef struct AsepriteSlice { 78 | char* name; // The name of the slice. 79 | Rectangle bounds; // The rectangle outer bounds for the slice. 80 | } AsepriteSlice; 81 | 82 | // Aseprite functions 83 | Aseprite LoadAseprite(const char* fileName); // Load an .aseprite file 84 | Aseprite LoadAsepriteFromMemory(unsigned char* fileData, int size); // Load an aseprite file from memory 85 | bool IsAsepriteValid(Aseprite aseprite); // Check if the given Aseprite was loaded successfully 86 | void UnloadAseprite(Aseprite aseprite); // Unloads the aseprite file 87 | void TraceAseprite(Aseprite aseprite); // Display all information associated with the aseprite 88 | Texture GetAsepriteTexture(Aseprite aseprite); // Retrieve the raylib texture associated with the aseprite 89 | int GetAsepriteWidth(Aseprite aseprite); // Get the width of the sprite 90 | int GetAsepriteHeight(Aseprite aseprite); // Get the height of the sprite 91 | void DrawAseprite(Aseprite aseprite, int frame, int posX, int posY, Color tint); 92 | void DrawAsepriteFlipped(Aseprite aseprite, int frame, int posX, int posY, bool horizontalFlip, bool verticalFlip, Color tint); 93 | void DrawAsepriteV(Aseprite aseprite, int frame, Vector2 position, Color tint); 94 | void DrawAsepriteVFlipped(Aseprite aseprite, int frame, Vector2 position, bool horizontalFlip, bool verticalFlip, Color tint); 95 | void DrawAsepriteExFlipped(Aseprite aseprite, int frame, Vector2 position, float rotation, float scale, bool horizontalFlip, bool verticalFlip, Color tint); 96 | void DrawAsepritePro(Aseprite aseprite, int frame, Rectangle dest, Vector2 origin, float rotation, Color tint); 97 | void DrawAsepriteProFlipped(Aseprite aseprite, int frame, Rectangle dest, Vector2 origin, float rotation, bool horizontalFlip, bool verticalFlip, Color tint); 98 | 99 | // Aseprite Tag functions 100 | AsepriteTag LoadAsepriteTag(Aseprite aseprite, const char* name); // Load an Aseprite tag animation sequence 101 | AsepriteTag LoadAsepriteTagFromIndex(Aseprite aseprite, int index); // Load an Aseprite tag animation sequence from its index 102 | int GetAsepriteTagCount(Aseprite aseprite); // Get the total amount of available tags 103 | bool IsAsepriteTagValid(AsepriteTag tag); // Check if the given Aseprite tag was loaded successfully 104 | void UpdateAsepriteTag(AsepriteTag* tag); // Update the tag animation frame 105 | AsepriteTag GenAsepriteTagDefault(); // Generate an empty Tag with sane defaults 106 | void DrawAsepriteTag(AsepriteTag tag, int posX, int posY, Color tint); 107 | void DrawAsepriteTagFlipped(AsepriteTag tag, int posX, int posY, bool horizontalFlip, bool verticalFlip, Color tint); 108 | void DrawAsepriteTagV(AsepriteTag tag, Vector2 position, Color tint); 109 | void DrawAsepriteTagVFlipped(AsepriteTag tag, Vector2 position, bool horizontalFlip, bool verticalFlip, Color tint); 110 | void DrawAsepriteTagEx(AsepriteTag tag, Vector2 position, float rotation, float scale, Color tint); 111 | void DrawAsepriteTagExFlipped(AsepriteTag tag, Vector2 position, float rotation, float scale, bool horizontalFlip, bool verticalFlip, Color tint); 112 | void DrawAsepriteTagPro(AsepriteTag tag, Rectangle dest, Vector2 origin, float rotation, Color tint); 113 | void DrawAsepriteTagProFlipped(AsepriteTag tag, Rectangle dest, Vector2 origin, float rotation, bool horizontalFlip, bool verticalFlip, Color tint); 114 | void SetAsepriteTagFrame(AsepriteTag* tag, int frameNumber); // Sets which frame the tag is currently displaying. 115 | int GetAsepriteTagFrame(AsepriteTag tag); 116 | 117 | // Aseprite Slice functions 118 | AsepriteSlice LoadAsepriteSlice(Aseprite aseprite, const char* name); // Load a slice from an Aseprite based on its name. 119 | AsepriteSlice LoadAsperiteSliceFromIndex(Aseprite aseprite, int index); // Load a slice from an Aseprite based on its index. 120 | int GetAsepriteSliceCount(Aseprite aseprite); // Get the amount of slices that are defined in the Aseprite. 121 | bool IsAsepriteSliceValid(AsepriteSlice slice); // Return whether or not the given slice was found. 122 | AsepriteSlice GenAsepriteSliceDefault(); // Generate empty Aseprite slice data. 123 | 124 | #ifdef __cplusplus 125 | } 126 | #endif 127 | 128 | #endif // INCLUDE_RAYLIB_ASEPRITE_H_ 129 | 130 | #ifdef RAYLIB_ASEPRITE_IMPLEMENTATION 131 | #ifndef RAYLIB_ASEPRITE_IMPLEMENTATION_ONCE 132 | #define RAYLIB_ASEPRITE_IMPLEMENTATION_ONCE 133 | 134 | #ifndef CUTE_ASEPRITE_IMPLEMENTATION 135 | #define CUTE_ASEPRITE_IMPLEMENTATION 136 | #endif 137 | 138 | #ifdef __cplusplus 139 | extern "C" { 140 | #endif 141 | 142 | // Have cute_aseprite report warnings through raylib. 143 | #define CUTE_ASEPRITE_WARNING(msg) TraceLog(LOG_WARNING, "ASEPRITE: %s (cute_headers.h:%i)", msg, __LINE__) 144 | 145 | #define CUTE_ASEPRITE_ASSERT(condition) do { if (!(condition)) { TraceLog(LOG_WARNING, "ASEPRITE: Failed assert \"%s\" in %s:%i", #condition, __FILE__, __LINE__); } } while(0) 146 | 147 | #define CUTE_ASEPRITE_ALLOC(size, ctx) MemAlloc((unsigned int)(size)) 148 | #define CUTE_ASEPRITE_FREE(mem, ctx) MemFree((void*)(mem)) 149 | 150 | #define CUTE_ASEPRITE_SEEK_SET 0 151 | #define CUTE_ASEPRITE_SEEK_END 0 152 | #define CUTE_ASEPRITE_FILE void 153 | #define CUTE_ASEPRITE_FOPEN(file, property) (CUTE_ASEPRITE_FILE*)file 154 | #define CUTE_ASEPRITE_FSEEK(fp, sz, pos) TraceLog(LOG_ERROR, "ASEPRITE: fseek() was removed") 155 | #define CUTE_ASEPRITE_FREAD(data, sz, num, fp) TraceLog(LOG_ERROR, "ASEPRITE: fread() was removed") 156 | #define CUTE_ASEPRITE_FTELL(fp) (0) 157 | #define CUTE_ASEPRITE_FCLOSE(fp) TraceLog(LOG_ERROR, "ASEPRITE: fclose() was removed") 158 | 159 | #include "cute_aseprite.h" // NOLINT 160 | 161 | /** 162 | * Load an .aseprite file through its memory data. 163 | * 164 | * @param fileData The loaded file data for the .aseprite file. 165 | * @param size The size of file in bytes. 166 | * 167 | * @see UnloadAseprite() 168 | * @see LoadAseprite() 169 | */ 170 | Aseprite LoadAsepriteFromMemory(unsigned char* fileData, int size) { 171 | struct Aseprite aseprite; 172 | aseprite.ase = 0; 173 | 174 | if (!IsWindowReady()) { 175 | TraceLog(LOG_ERROR, "ASEPRITE: Loading an Aseprite requires the Window to be running"); 176 | return aseprite; 177 | } 178 | 179 | ase_t* ase = cute_aseprite_load_from_memory(fileData, (int)size, 0); 180 | if (ase == 0 || ase->frame_count == 0 || ase->w == 0 || ase->h == 0) { 181 | TraceLog(LOG_ERROR, "ASEPRITE: Failed to load Aseprite"); 182 | return aseprite; 183 | } 184 | 185 | // Build the sprite's final image. 186 | Image image = GenImageColor(ase->w * ase->frame_count, ase->h, BLANK); 187 | 188 | // Load all frames. 189 | for (int i = 0; i < ase->frame_count; i++) { 190 | ase_frame_t* frame = ase->frames + i; 191 | Rectangle dest = {(float)(i * ase->w), 0, (float)ase->w, (float)ase->h}; 192 | Rectangle src = {0, 0, (float)ase->w, (float)ase->h}; 193 | Image frameImage = { 194 | .data = frame->pixels, 195 | .width = ase->w, 196 | .height = ase->h, 197 | .mipmaps = 1, 198 | .format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8 199 | }; 200 | ImageDraw(&image, frameImage, src, dest, WHITE); 201 | } 202 | 203 | // Apply the transparent pixel. 204 | int transparency = ase->transparent_palette_entry_index; 205 | if (transparency >= 0 && transparency < ase->palette.entry_count) { 206 | ase_color_t transparentColor = ase->palette.entries[transparency].color; 207 | Color source = { 208 | .r = transparentColor.r, 209 | .g = transparentColor.g, 210 | .b = transparentColor.b, 211 | .a = transparentColor.a 212 | }; 213 | ImageColorReplace(&image, source, BLANK); 214 | } 215 | 216 | // Create the Texture 217 | Texture2D texture = LoadTextureFromImage(image); 218 | 219 | // Now that the texture is loaded into the GPU, we can clear the image. 220 | UnloadImage(image); 221 | 222 | // Save the texture as the Aseprite context. 223 | ase->mem_ctx = MemAlloc(sizeof(Texture2D)); 224 | Texture2D* texturePointer = (Texture2D*)ase->mem_ctx; 225 | texturePointer->format = texture.format; 226 | texturePointer->id = texture.id; 227 | texturePointer->mipmaps = texture.mipmaps; 228 | texturePointer->width = texture.width; 229 | texturePointer->height = texture.height; 230 | aseprite.ase = ase; 231 | TraceLog(LOG_INFO, "ASEPRITE: Loaded successfully (%ix%i - %i frames)", ase->w, ase->h, ase->frame_count); 232 | 233 | return aseprite; 234 | } 235 | 236 | /** 237 | * Load an .aseprite file. 238 | * 239 | * @param fileName The path to the file to load. 240 | * 241 | * @return The loaded Aseprite object, or an empty one on failure. 242 | * 243 | * @see UnloadAseprite() 244 | * @see IsAsepriteValid() 245 | */ 246 | Aseprite LoadAseprite(const char* fileName) { 247 | int bytesRead; 248 | unsigned char* fileData = LoadFileData(fileName, &bytesRead); 249 | if (bytesRead == 0 || fileData == 0) { 250 | TraceLog(LOG_ERROR, "ASEPRITE: Failed to load aseprite file \"%s\"", fileName); 251 | struct Aseprite aseprite; 252 | aseprite.ase = 0; 253 | return aseprite; 254 | } 255 | 256 | Aseprite ase = LoadAsepriteFromMemory(fileData, bytesRead); 257 | UnloadFileData(fileData); 258 | return ase; 259 | } 260 | 261 | /** 262 | * Determine whether or not the given Aseprite object was loaded successfully. 263 | * 264 | * @param aseprite The loaded Aseprite object. 265 | * 266 | * @return True if the Aseprite was loaded successfully, false otherwise. 267 | */ 268 | bool IsAsepriteValid(Aseprite aseprite) { 269 | return aseprite.ase != 0; 270 | } 271 | 272 | /** 273 | * Get the loaded raylib texture for the Aseprite. 274 | * 275 | * @param aseprite The loaded Aseprite object to retrieve the texture for. 276 | * 277 | * @return The internal texture associated with the Aseprite, or an empty Texture on failure. 278 | */ 279 | inline Texture GetAsepriteTexture(Aseprite aseprite) { 280 | if (aseprite.ase == 0) { 281 | struct Texture texture; 282 | TraceLog(LOG_WARNING, "ASEPRITE: Cannot get Texture from non-existant aseprite"); 283 | texture.id = 0; 284 | texture.width = 0; 285 | texture.height = 0; 286 | texture.mipmaps = 0; 287 | texture.format = 0; 288 | return texture; 289 | } 290 | 291 | Texture2D* texturePointer = (Texture2D*)aseprite.ase->mem_ctx; 292 | return *texturePointer; 293 | } 294 | 295 | /** 296 | * Get the width of the aseprite sprite. 297 | * 298 | * @param aseprite The loaded Aseprite object to retrieve the width for. 299 | * 300 | * @return The width of the sprite, or 0 on failure. 301 | */ 302 | int GetAsepriteWidth(Aseprite aseprite) { 303 | if (aseprite.ase == 0) { 304 | TraceLog(LOG_WARNING, "ASEPRITE: Cannot get width from non-existant aseprite"); 305 | return 0; 306 | } 307 | 308 | return aseprite.ase->w; 309 | } 310 | 311 | /** 312 | * Get the height of the aseprite sprite. 313 | * 314 | * @param aseprite The loaded Aseprite object to retrieve the height for. 315 | * 316 | * @return The height of the sprite, or 0 on failure. 317 | */ 318 | int GetAsepriteHeight(Aseprite aseprite) { 319 | if (aseprite.ase == 0) { 320 | TraceLog(LOG_WARNING, "ASEPRITE: Cannot get width from non-existant aseprite"); 321 | return 0; 322 | } 323 | 324 | return aseprite.ase->h; 325 | } 326 | 327 | /** 328 | * Get the amount of tags defined in the aseprite sprite. 329 | * 330 | * @param aseprite The loaded Aseprite object to retrieve the amount of tags for. 331 | * 332 | * @return The number of tags that are defined within the aseprite, or 0 on failure. 333 | */ 334 | int GetAsepriteTagCount(Aseprite aseprite) { 335 | if (aseprite.ase == 0) { 336 | TraceLog(LOG_WARNING, "ASEPRITE: Cannot get tag count non-existant aseprite"); 337 | return 0; 338 | } 339 | 340 | return aseprite.ase->tag_count; 341 | } 342 | 343 | /** 344 | * Unloads the given Aseprite. This will also unload the internal Texture. 345 | * 346 | * @param aseprite The loaded Aseprite object of which to unload. 347 | * 348 | * @see LoadAseprite() 349 | */ 350 | void UnloadAseprite(Aseprite aseprite) { 351 | ase_t* ase = aseprite.ase; 352 | if (ase == 0) { 353 | return; 354 | } 355 | 356 | // Destroy the texture. 357 | if (ase->mem_ctx != 0) { 358 | // Unload the texture from the GPU. 359 | UnloadTexture(GetAsepriteTexture(aseprite)); 360 | 361 | // Unload the texture object. 362 | MemFree(ase->mem_ctx); 363 | } 364 | 365 | // Destory the aseprite data. 366 | cute_aseprite_free(ase); 367 | 368 | TraceLog(LOG_INFO, "ASEPRITE: Unloaded Aseprite data successfully"); 369 | } 370 | 371 | void DrawAseprite(Aseprite aseprite, int frame, int posX, int posY, Color tint) { 372 | DrawAsepriteFlipped(aseprite, frame, posX, posY, false, false, tint); 373 | } 374 | 375 | void DrawAsepriteFlipped(Aseprite aseprite, int frame, int posX, int posY, bool horizontalFlip, bool verticalFlip, Color tint) { 376 | Vector2 position = {(float)posX, (float)posY}; 377 | DrawAsepriteVFlipped(aseprite, frame, position, horizontalFlip, verticalFlip, tint); 378 | } 379 | 380 | void DrawAsepriteV(Aseprite aseprite, int frame, Vector2 position, Color tint) { 381 | DrawAsepriteVFlipped(aseprite, frame, position, false, false, tint); 382 | } 383 | 384 | void DrawAsepriteVFlipped(Aseprite aseprite, int frame, Vector2 position, bool horizontalFlip, bool verticalFlip, Color tint) { 385 | ase_t* ase = aseprite.ase; 386 | if (ase == 0 || frame < 0 || frame >= ase->frame_count) { 387 | return; 388 | } 389 | 390 | Rectangle source = {(float)(frame * ase->w), 0, 391 | horizontalFlip ? (float)-ase->w : (float)ase->w, 392 | verticalFlip ? (float)-ase->h : (float)ase->h 393 | }; 394 | Texture2D texture = GetAsepriteTexture(aseprite); 395 | DrawTextureRec(texture, source, position, tint); 396 | } 397 | 398 | void DrawAsepriteEx(Aseprite aseprite, int frame, Vector2 position, float rotation, float scale, Color tint) { 399 | DrawAsepriteExFlipped(aseprite, frame, position, rotation, scale, false, false, tint); 400 | } 401 | 402 | void DrawAsepriteExFlipped(Aseprite aseprite, int frame, Vector2 position, float rotation, float scale, bool horizontalFlip, bool verticalFlip, Color tint) { 403 | ase_t* ase = aseprite.ase; 404 | if (ase == 0 || frame < 0 || frame >= ase->frame_count) { 405 | return; 406 | } 407 | 408 | Rectangle source = {(float)(frame * ase->w), 0, 409 | horizontalFlip ? (float)-ase->w : (float)ase->w, 410 | verticalFlip ? (float)-ase->h : (float)ase->h 411 | }; 412 | Texture2D texture = GetAsepriteTexture(aseprite); 413 | Rectangle dest = {(float)position.x, (float)position.y, (float)ase->w * scale, (float)ase->h * scale}; 414 | Vector2 origin = {0, 0}; 415 | DrawTexturePro(texture, source, dest, origin, rotation, tint); // Draw a part of a texture defined by a rectangle with 'pro' parameters 416 | } 417 | 418 | void DrawAsepritePro(Aseprite aseprite, int frame, Rectangle dest, Vector2 origin, float rotation, Color tint) { 419 | DrawAsepriteProFlipped(aseprite, frame, dest, origin, rotation, false, false, tint); 420 | } 421 | 422 | void DrawAsepriteProFlipped(Aseprite aseprite, int frame, Rectangle dest, Vector2 origin, float rotation, bool horizontalFlip, bool verticalFlip, Color tint) { 423 | ase_t* ase = aseprite.ase; 424 | if (ase == 0 || frame < 0 || frame >= ase->frame_count) { 425 | return; 426 | } 427 | 428 | Rectangle source = { 429 | (float)(frame * ase->w), 0, 430 | horizontalFlip ? (float)-ase->w : (float)ase->w, 431 | verticalFlip ? (float)-ase->h : (float)ase->h 432 | }; 433 | Texture2D texture = GetAsepriteTexture(aseprite); 434 | DrawTexturePro(texture, source, dest, origin, rotation, tint); 435 | } 436 | 437 | /** 438 | * Display information about the loaded asprite. 439 | */ 440 | void TraceAseprite(Aseprite aseprite) { 441 | ase_t* ase = aseprite.ase; 442 | if (ase == 0) { 443 | TraceLog(LOG_INFO, "ASEPRITE: Empty Aseprite information"); 444 | return; 445 | } 446 | 447 | TraceLog(LOG_INFO, "ASEPRITE: Aseprite information: (%ix%i - %i frames)", ase->w, ase->h, ase->frame_count); 448 | TraceLog(LOG_INFO, " > Colors: %i", ase->number_of_colors); 449 | TraceLog(LOG_INFO, " > Mode: %i", ase->mode); 450 | TraceLog(LOG_INFO, " > Layers: %i", ase->layer_count); 451 | for (int i = 0; i < ase->layer_count; i++) { 452 | ase_layer_t* layer = ase->layers + i; 453 | TraceLog(LOG_INFO, " - %s", layer->name); 454 | } 455 | 456 | TraceLog(LOG_INFO, " > Tags: %i", ase->tag_count); 457 | for (int i = 0; i < ase->tag_count; i++) { 458 | ase_tag_t* tag = ase->tags + i; 459 | TraceLog(LOG_INFO, " - %s", tag->name); 460 | } 461 | 462 | TraceLog(LOG_INFO, " > Slices: %i", ase->slice_count); 463 | for (int i = 0; i < ase->slice_count; i++) { 464 | ase_slice_t* slice = ase->slices + i; 465 | TraceLog(LOG_INFO, " - %s", slice->name); 466 | } 467 | } 468 | 469 | /** 470 | * Updates a tag to progress through its animation frame. 471 | * 472 | * @param tag The AsepriteTag passed in by reference (&tag). 473 | */ 474 | void UpdateAsepriteTag(AsepriteTag* tag) { 475 | if (tag == 0 || tag->tag == 0 || tag->aseprite.ase == 0) { 476 | TraceLog(LOG_WARNING, "ASEPRITE: Cannot update empty tag"); 477 | return; 478 | } 479 | 480 | // Don't progress if the tag is paused. 481 | if (tag->paused) { 482 | return; 483 | } 484 | 485 | ase_t* ase = tag->aseprite.ase; 486 | ase_tag_t* aseTag = tag->tag; 487 | 488 | // Count down the timer and see if it's time to update the frame. 489 | tag->timer -= GetFrameTime() * tag->speed; 490 | if (tag->timer > 0) { 491 | return; 492 | } 493 | 494 | // Advance the frame and see if it's time to reset the position. 495 | tag->currentFrame += tag->direction; 496 | switch (aseTag->loop_animation_direction) { 497 | case ASE_ANIMATION_DIRECTION_FORWARDS: 498 | if (tag->currentFrame > aseTag->to_frame) { 499 | if (tag->loop) { 500 | tag->currentFrame = aseTag->from_frame; 501 | } else { 502 | tag->currentFrame = aseTag->to_frame; 503 | tag->paused = true; 504 | } 505 | } 506 | break; 507 | case ASE_ANIMATION_DIRECTION_BACKWORDS: 508 | if (tag->currentFrame < aseTag->from_frame) { 509 | if (tag->loop) { 510 | tag->currentFrame = aseTag->to_frame; 511 | } else { 512 | tag->currentFrame = aseTag->from_frame; 513 | tag->paused = true; 514 | } 515 | } 516 | break; 517 | case ASE_ANIMATION_DIRECTION_PINGPONG: 518 | if (tag->direction > 0) { 519 | if (tag->currentFrame > aseTag->to_frame) { 520 | tag->direction = -1; 521 | if (tag->loop) { 522 | tag->currentFrame = aseTag->to_frame - 1; 523 | } else { 524 | tag->currentFrame = aseTag->to_frame; 525 | tag->paused = true; 526 | } 527 | } 528 | } else { 529 | if (tag->currentFrame < aseTag->from_frame) { 530 | tag->direction = 1; 531 | if (tag->loop) { 532 | tag->currentFrame = aseTag->from_frame + 1; 533 | } else { 534 | tag->currentFrame = aseTag->from_frame; 535 | tag->paused = true; 536 | } 537 | } 538 | } 539 | break; 540 | } 541 | 542 | // Reset the timer. 543 | // TODO(RobLoach): Add the original tag->timer to make up the different in frame time? 544 | tag->timer = (float)(ase->frames[tag->currentFrame].duration_milliseconds) / 1000.0f /* + tag->timer; */; 545 | } 546 | 547 | /** 548 | * Set which frame the Aseprite tag is on. 549 | * 550 | * @param tag The Aseprite tag to modify. 551 | * @param frameNumber Which frame to set the active tag to. If negative, will start from the end. 552 | */ 553 | void SetAsepriteTagFrame(AsepriteTag* tag, int frameNumber) { 554 | // TODO: Need to attribute frame number for ASE_ANIMATION_DIRECTION_BACKWORDS? 555 | if (frameNumber >= 0) { 556 | tag->currentFrame = tag->tag->from_frame + frameNumber; 557 | } 558 | else { 559 | tag->currentFrame = tag->tag->to_frame + frameNumber; 560 | } 561 | 562 | // Bounds 563 | if (tag->currentFrame < tag->tag->from_frame) { 564 | tag->currentFrame = tag->tag->from_frame; 565 | } 566 | else if (tag->currentFrame > tag->tag->to_frame) { 567 | tag->currentFrame = tag->tag->to_frame; 568 | } 569 | } 570 | 571 | int GetAsepriteTagFrame(AsepriteTag tag) { 572 | // TODO: Need to attribute frame number for ASE_ANIMATION_DIRECTION_BACKWORDS? 573 | return tag.currentFrame - tag.tag->from_frame; 574 | } 575 | 576 | void DrawAsepriteTag(AsepriteTag tag, int posX, int posY, Color tint) { 577 | DrawAseprite(tag.aseprite, tag.currentFrame, posX, posY, tint); 578 | } 579 | 580 | void DrawAsepriteTagFlipped(AsepriteTag tag, int posX, int posY, bool horizontalFlip, bool verticalFlip, Color tint) { 581 | DrawAsepriteFlipped(tag.aseprite, tag.currentFrame, posX, posY, horizontalFlip, verticalFlip, tint); 582 | } 583 | 584 | void DrawAsepriteTagV(AsepriteTag tag, Vector2 position, Color tint) { 585 | DrawAsepriteV(tag.aseprite, tag.currentFrame, position, tint); 586 | } 587 | 588 | void DrawAsepriteTagVFlipped(AsepriteTag tag, Vector2 position, bool horizontalFlip, bool verticalFlip, Color tint) { 589 | DrawAsepriteVFlipped(tag.aseprite, tag.currentFrame, position, horizontalFlip, verticalFlip, tint); 590 | } 591 | 592 | void DrawAsepriteTagEx(AsepriteTag tag, Vector2 position, float rotation, float scale, Color tint) { 593 | DrawAsepriteEx(tag.aseprite, tag.currentFrame, position, rotation, scale, tint); 594 | } 595 | 596 | void DrawAsepriteTagExFlipped(AsepriteTag tag, Vector2 position, float rotation, float scale, bool horizontalFlip, bool verticalFlip, Color tint) { 597 | DrawAsepriteExFlipped(tag.aseprite, tag.currentFrame, position, rotation, scale, horizontalFlip, verticalFlip, tint); 598 | } 599 | 600 | void DrawAsepriteTagPro(AsepriteTag tag, Rectangle dest, Vector2 origin, float rotation, Color tint) { 601 | DrawAsepritePro(tag.aseprite, tag.currentFrame, dest, origin, rotation, tint); 602 | } 603 | 604 | void DrawAsepriteTagProFlipped(AsepriteTag tag, Rectangle dest, Vector2 origin, float rotation, bool horizontalFlip, bool verticalFlip, Color tint) { 605 | DrawAsepriteProFlipped(tag.aseprite, tag.currentFrame, dest, origin, rotation, horizontalFlip, verticalFlip, tint); 606 | } 607 | 608 | /** 609 | * Generate an aseprite tag with sane defaults. 610 | * 611 | * @return An AsepriteTag with sane defaults. 612 | */ 613 | AsepriteTag GenAsepriteTagDefault() { 614 | struct AsepriteTag tag; 615 | tag.aseprite.ase = 0; 616 | tag.currentFrame = 0; 617 | tag.tag = 0; 618 | tag.timer = 0; 619 | tag.direction = 0; 620 | tag.speed = 1.0f; 621 | tag.color = BLACK; 622 | tag.loop = true; 623 | tag.paused = false; 624 | tag.name = 0; 625 | return tag; 626 | } 627 | 628 | /** 629 | * Load an Aseprite tag from the given index. 630 | * 631 | * @param aseprite The loaded Aseprite object from which to load the tag. 632 | * @param index The tag index within the Aseprite object. 633 | * 634 | * @return The Aseprite tag information, or an empty tag on failure. 635 | * 636 | * @see LoadAsepriteTag() 637 | * @see IsAsepriteTagValid() 638 | */ 639 | AsepriteTag LoadAsepriteTagFromIndex(Aseprite aseprite, int index) { 640 | AsepriteTag tag = GenAsepriteTagDefault(); 641 | 642 | // Ensure the Aseprite exists. 643 | ase_t* ase = aseprite.ase; 644 | if (ase == 0) { 645 | TraceLog(LOG_ERROR, "ASEPRITE: Asprite not loaded when attempting to load tag #%i", index); 646 | return tag; 647 | } 648 | 649 | // Ensure the tag exists 650 | if (index < 0 || index >= ase->tag_count) { 651 | TraceLog(LOG_ERROR, "ASEPRITE: Tag index %i out of range for %i tags", index, ase->tag_count); 652 | return tag; 653 | } 654 | 655 | // Base tag information 656 | tag.aseprite.ase = aseprite.ase; 657 | tag.tag = &ase->tags[index]; 658 | 659 | // Set up the frame range 660 | tag.direction = 1; 661 | tag.currentFrame = tag.tag->from_frame; 662 | if (tag.tag->loop_animation_direction == ASE_ANIMATION_DIRECTION_BACKWORDS) { 663 | tag.currentFrame = tag.tag->to_frame; 664 | tag.direction = -1; 665 | } 666 | 667 | // Pause the animation if it's a one-frame tag 668 | if (tag.tag->from_frame == tag.tag->to_frame) { 669 | tag.paused = true; 670 | } 671 | 672 | // Timer in seconds 673 | tag.timer = (float)(ase->frames[tag.currentFrame].duration_milliseconds) / 1000.0f; 674 | 675 | // Color 676 | tag.color.r = (unsigned char)tag.tag->r; 677 | tag.color.g = (unsigned char)tag.tag->g; 678 | tag.color.b = (unsigned char)tag.tag->b; 679 | 680 | // Name 681 | tag.name = (char*)tag.tag->name; 682 | 683 | // Display a trace log about the aseprite tag 684 | TraceLog(LOG_TRACE, "ASEPRITE: [ID %i] Asprite tag loaded successfully (%s)", index, tag.name); 685 | 686 | return tag; 687 | } 688 | 689 | /** 690 | * Load an Aseprite tag from the given name. 691 | * 692 | * @param aseprite The Aseprite object to load the tag from. 693 | * @param name The name of the tag to be loaded. 694 | * 695 | * @return The loaded Aseprite tag, or an empty tag on failure. 696 | * 697 | * @see IsAsepriteTagValid() 698 | * @see UpdateAsepriteTag() 699 | */ 700 | AsepriteTag LoadAsepriteTag(Aseprite aseprite, const char* name) { 701 | AsepriteTag tag = GenAsepriteTagDefault(); 702 | ase_t* ase = aseprite.ase; 703 | if (ase == 0) { 704 | TraceLog(LOG_ERROR, "ASEPRITE: Cannot load tag \"%s\" from an Aseprite that's not loaded", name); 705 | return tag; 706 | } 707 | 708 | // Loop through all tags to find the correct name. 709 | for (int i = 0; i < ase->tag_count; i++) { 710 | if (TextIsEqual(name, ase->tags[i].name)) { 711 | return LoadAsepriteTagFromIndex(aseprite, i); 712 | } 713 | } 714 | 715 | // Display a warning about the missing aseprite 716 | TraceLog(LOG_WARNING, "ASEPRITE: Could not find tag \"%s\"", name); 717 | 718 | return tag; 719 | } 720 | 721 | /** 722 | * Determine whether or not the Aseprite tag was loaded successfully. 723 | * 724 | * @param tag The tag of which to check if it was loaded. 725 | * 726 | * @return True if the tag was loaded, false otherwise. 727 | */ 728 | bool IsAsepriteTagValid(AsepriteTag tag) { 729 | return tag.tag != 0; 730 | } 731 | 732 | /** 733 | * Load a slice from an Aseprite based on its name. 734 | * 735 | * @param name The name of the slice to find. 736 | * 737 | * @return The loaded slice, or an empty one if not found. 738 | */ 739 | AsepriteSlice LoadAsepriteSlice(Aseprite aseprite, const char* name) { 740 | if (aseprite.ase == NULL) { 741 | TraceLog(LOG_WARNING, "ASEPRITE: Cannot load slice on empty aseprite"); 742 | return GenAsepriteSliceDefault(); 743 | } 744 | for (int i = 0; i < aseprite.ase->slice_count; i++) { 745 | ase_slice_t* slice = &aseprite.ase->slices[i]; 746 | if (TextIsEqual(name, slice->name)) { 747 | return LoadAsperiteSliceFromIndex(aseprite, i); 748 | } 749 | } 750 | 751 | return GenAsepriteSliceDefault(); 752 | } 753 | 754 | /** 755 | * Load a slice from an Aseprite based on its index. 756 | * 757 | * @param index The index of the slice to load. 758 | * 759 | * @return The loaded slice, or an empty one if not found. 760 | */ 761 | AsepriteSlice LoadAsperiteSliceFromIndex(Aseprite aseprite, int index) { 762 | if (aseprite.ase == NULL) { 763 | TraceLog(LOG_WARNING, "ASEPRITE: Cannot load slice index from empty aseprite"); 764 | return GenAsepriteSliceDefault(); 765 | } 766 | if (index < aseprite.ase->slice_count) { 767 | AsepriteSlice output; 768 | ase_slice_t* slice = &aseprite.ase->slices[index]; 769 | output.bounds.x = (float)slice->origin_x; 770 | output.bounds.y = (float)slice->origin_y; 771 | output.bounds.width = (float)slice->w; 772 | output.bounds.height = (float)slice->h; 773 | output.name = (char*)slice->name; 774 | return output; 775 | } 776 | 777 | return GenAsepriteSliceDefault(); 778 | } 779 | 780 | /** 781 | * Generate empty Aseprite slice data. 782 | */ 783 | AsepriteSlice GenAsepriteSliceDefault() { 784 | AsepriteSlice slice; 785 | slice.name = ""; 786 | slice.bounds = (Rectangle){0, 0, 0, 0}; 787 | return slice; 788 | } 789 | 790 | /** 791 | * Get the amount of slices that are defined in the Aseprite. 792 | * 793 | * @return The amount of slices. 794 | */ 795 | int GetAsepriteSliceCount(Aseprite aseprite) { 796 | return aseprite.ase->slice_count; 797 | } 798 | 799 | /** 800 | * Return whether or not the given slice was found. 801 | */ 802 | bool IsAsepriteSliceValid(AsepriteSlice slice) { 803 | return TextLength(slice.name) != 0; 804 | } 805 | 806 | #ifdef __cplusplus 807 | } 808 | #endif 809 | 810 | #endif // RAYLIB_ASEPRITE_IMPLEMENTATION_ONCE 811 | #endif // RAYLIB_ASEPRITE_IMPLEMENTATION 812 | -------------------------------------------------------------------------------- /test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # raylib-aseprite-test 2 | add_executable(raylib-aseprite-test raylib-aseprite-test.c) 3 | target_compile_options(raylib-aseprite-test PRIVATE -Wall -Wextra -Wconversion -Wsign-conversion) 4 | target_link_libraries(raylib-aseprite-test PUBLIC 5 | raylib 6 | raylib_aseprite 7 | ) 8 | set_property(TARGET raylib-aseprite-test PROPERTY C_STANDARD 99) 9 | 10 | # Copy the resources 11 | file(GLOB resources resources/*) 12 | set(test_resources) 13 | list(APPEND test_resources ${resources}) 14 | file(COPY ${test_resources} DESTINATION "resources/") 15 | 16 | # Set up the test 17 | list(APPEND CMAKE_CTEST_ARGUMENTS "--output-on-failure") 18 | add_test(NAME raylib-aseprite-test COMMAND raylib-aseprite-test) 19 | -------------------------------------------------------------------------------- /test/raylib-aseprite-test.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "raylib.h" 4 | 5 | #define RAYLIB_ASEPRITE_IMPLEMENTATION 6 | #include "raylib-aseprite.h" 7 | 8 | int main(int argc, char *argv[]) { 9 | // Initialization 10 | SetTraceLogLevel(LOG_ALL); 11 | TraceLog(LOG_INFO, "================================"); 12 | TraceLog(LOG_INFO, "raylib-aseprite-test"); 13 | TraceLog(LOG_INFO, "================================"); 14 | 15 | InitWindow(640, 480, "raylib-aseprite-tests"); 16 | assert(IsWindowReady()); 17 | 18 | // Make sure we're running in the correct directory. 19 | assert(argc > 0); 20 | const char* dir = GetDirectoryPath(argv[0]); 21 | assert(ChangeDirectory(dir)); 22 | 23 | // LoadAseprite() 24 | Aseprite aseprite = LoadAseprite("resources/numbers.aseprite"); 25 | assert(aseprite.ase != NULL); 26 | assert(aseprite.ase->frame_count > 20); 27 | assert(aseprite.ase->frame_count < 40); 28 | 29 | // IsAsepriteValid() 30 | assert(IsAsepriteValid(aseprite)); 31 | 32 | // GetAsepriteWidth() 33 | assert(GetAsepriteWidth(aseprite) == 64); 34 | 35 | // GetAsepriteHeight() 36 | assert(GetAsepriteHeight(aseprite) == 64); 37 | 38 | // GetAsepriteTagCount() 39 | assert(GetAsepriteTagCount(aseprite) == 3); 40 | 41 | // TraceAseprite() 42 | TraceAseprite(aseprite); 43 | 44 | // GetAsepriteTagCount() 45 | assert(GetAsepriteTagCount(aseprite) == 3); 46 | 47 | // LoadAsepriteTag() 48 | AsepriteTag tag = LoadAsepriteTag(aseprite, "Backwards"); 49 | assert(tag.timer > 0); 50 | Color expected = (Color){0, 135, 81, 255}; 51 | assert(tag.color.r == expected.r); 52 | assert(tag.color.g == expected.g); 53 | assert(tag.color.b == expected.b); 54 | assert(tag.color.a == expected.a); 55 | assert(TextIsEqual(tag.name, "Backwards")); 56 | 57 | // IsAsepriteTagValid() 58 | assert(IsAsepriteTagValid(tag)); 59 | 60 | // LoadAsepriteTagFromIndex() 61 | { 62 | AsepriteTag tag2 = LoadAsepriteTagFromIndex(aseprite, 2); 63 | assert(tag2.speed == 1.0f); 64 | assert(tag2.currentFrame == 20); 65 | assert(TextIsEqual(tag2.name, "Ping-Pong")); 66 | } 67 | 68 | // SetAsepriteTagFrame() 69 | { 70 | AsepriteTag tag = LoadAsepriteTagFromIndex(aseprite, 2); 71 | SetAsepriteTagFrame(&tag, 4); 72 | 73 | int frame = GetAsepriteTagFrame(tag); 74 | assert(frame == 4); 75 | 76 | SetAsepriteTagFrame(&tag, -3); 77 | frame = GetAsepriteTagFrame(tag); 78 | assert(frame == 9 - 3); 79 | } 80 | 81 | // GetAsepriteTexture() 82 | Texture texture = GetAsepriteTexture(aseprite); 83 | assert(texture.width > 50); 84 | assert(texture.height > 50); 85 | 86 | BeginDrawing(); 87 | { 88 | // DrawAseprite() 89 | DrawAseprite(aseprite, 3, 10, 10, WHITE); 90 | // DrawAsepriteV() 91 | DrawAsepriteV(aseprite, 5, (Vector2){10, 20}, WHITE); 92 | // DrawAsepriteEx() 93 | DrawAsepriteEx(aseprite, 6, (Vector2){10, 30}, 20, 3, WHITE); 94 | // DrawAsepritePro() 95 | DrawAsepritePro(aseprite, 7, (Rectangle){30, 30, 20, 20}, (Vector2){0, 0}, 0.5f, WHITE); 96 | 97 | // DrawAsepriteTag() 98 | DrawAsepriteTag(tag, 10, 10, WHITE); 99 | // DrawAsepriteTagV() 100 | DrawAsepriteTagV(tag, (Vector2){10, 20}, WHITE); 101 | // DrawAsepriteTagEx() 102 | DrawAsepriteTagEx(tag, (Vector2){10, 30}, 20, 3, WHITE); 103 | // DrawAsepriteTagPro() 104 | DrawAsepriteTagPro(tag, (Rectangle){30, 30, 20, 20}, (Vector2){0, 0}, 0.5f, WHITE); 105 | 106 | // UpdateAsepriteTag() 107 | UpdateAsepriteTag(&tag); 108 | UpdateAsepriteTag(NULL); // Expect warning 109 | } 110 | EndDrawing(); 111 | 112 | // GetAsepriteSliceCount() 113 | assert(GetAsepriteSliceCount(aseprite) == 2); 114 | 115 | // LoadAsepriteSlice() 116 | { 117 | AsepriteSlice slice = LoadAsepriteSlice(aseprite, "Label"); 118 | assert(slice.bounds.x > 20); 119 | assert(TextIsEqual(slice.name, "Label")); 120 | assert(slice.bounds.y > 20); 121 | assert(slice.bounds.width > 2); 122 | assert(slice.bounds.height > 2); 123 | } 124 | 125 | // LoadAsperiteSliceFromIndex() 126 | { 127 | AsepriteSlice slice = LoadAsperiteSliceFromIndex(aseprite, 1); 128 | assert(TextIsEqual(slice.name, "Number")); 129 | } 130 | 131 | // IsAsepriteSliceValid() 132 | { 133 | AsepriteSlice slice = LoadAsperiteSliceFromIndex(aseprite, 0); 134 | assert(IsAsepriteSliceValid(slice)); 135 | 136 | AsepriteSlice noSlice; 137 | noSlice.name = ""; 138 | assert(!IsAsepriteSliceValid(noSlice)); 139 | 140 | noSlice = LoadAsperiteSliceFromIndex(aseprite, 100); 141 | assert(!IsAsepriteSliceValid(noSlice)); 142 | } 143 | 144 | // GenAsepriteSliceDefault() 145 | AsepriteSlice defaultSlice = GenAsepriteSliceDefault(); 146 | assert(TextLength(defaultSlice.name) == 0); 147 | 148 | // UnloadAseprite() 149 | UnloadAseprite(aseprite); 150 | 151 | CloseWindow(); 152 | TraceLog(LOG_INFO, "================================"); 153 | TraceLog(LOG_INFO, "raylib-aseprite tests succesful"); 154 | TraceLog(LOG_INFO, "================================"); 155 | 156 | return 0; 157 | } 158 | -------------------------------------------------------------------------------- /test/resources/numbers.aseprite: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RobLoach/raylib-aseprite/e268a17c6e320a7443d476e28ecfc8023fc64be7/test/resources/numbers.aseprite --------------------------------------------------------------------------------