lib-tiny-png 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- lib_tiny_png-0.1.0/CMakeLists.txt +135 -0
- lib_tiny_png-0.1.0/PKG-INFO +77 -0
- lib_tiny_png-0.1.0/include/tiny_png/bit_stream.h +44 -0
- lib_tiny_png-0.1.0/include/tiny_png/constants.h +29 -0
- lib_tiny_png-0.1.0/include/tiny_png/deflate.h +14 -0
- lib_tiny_png-0.1.0/include/tiny_png/huffman.h +14 -0
- lib_tiny_png-0.1.0/include/tiny_png/lz77.h +27 -0
- lib_tiny_png-0.1.0/include/tiny_png/png_chunk.h +29 -0
- lib_tiny_png-0.1.0/include/tiny_png/tiny_png.h +25 -0
- lib_tiny_png-0.1.0/pyproject.toml +60 -0
- lib_tiny_png-0.1.0/python/README.md +58 -0
- lib_tiny_png-0.1.0/python/binding.cpp +102 -0
- lib_tiny_png-0.1.0/python/wrapper/__init__.py +3 -0
- lib_tiny_png-0.1.0/src/bit_stream.cpp +77 -0
- lib_tiny_png-0.1.0/src/deflate.cpp +48 -0
- lib_tiny_png-0.1.0/src/huffman.cpp +370 -0
- lib_tiny_png-0.1.0/src/lz77.cpp +82 -0
- lib_tiny_png-0.1.0/src/png_chunk.cpp +119 -0
- lib_tiny_png-0.1.0/src/tiny_png.cpp +185 -0
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
cmake_minimum_required(VERSION 4.0)
|
|
2
|
+
project(TinyPNG)
|
|
3
|
+
|
|
4
|
+
set(CMAKE_CXX_STANDARD 20)
|
|
5
|
+
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
|
6
|
+
set(CMAKE_CXX_EXTENSIONS OFF)
|
|
7
|
+
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
|
|
8
|
+
|
|
9
|
+
option(TINY_PNG_ENABLE_TESTS "Build tests" OFF)
|
|
10
|
+
option(TINY_PNG_ENABLE_STRICT "Use strict compile options" OFF)
|
|
11
|
+
option(TINY_PNG_ENABLE_COVERAGE "Enable coverage reporting" OFF)
|
|
12
|
+
option(TINY_PNG_BIND_PYTHON "Enable Python binding" OFF)
|
|
13
|
+
option(TINY_PNG_BIND_ES "Enable ECMAScript binding" OFF)
|
|
14
|
+
|
|
15
|
+
if(APPLE)
|
|
16
|
+
set(ENV{PKG_CONFIG_PATH} "/opt/homebrew/lib/pkgconfig:$ENV{PKG_CONFIG_PATH}")
|
|
17
|
+
endif()
|
|
18
|
+
|
|
19
|
+
if(TINY_PNG_ENABLE_STRICT)
|
|
20
|
+
if (CMAKE_CXX_COMPILER_ID MATCHES "Clang|GNU")
|
|
21
|
+
add_compile_options(
|
|
22
|
+
-Wall
|
|
23
|
+
-Wextra
|
|
24
|
+
-Werror
|
|
25
|
+
-Wpedantic
|
|
26
|
+
-Wmissing-include-dirs
|
|
27
|
+
-Wundef
|
|
28
|
+
-Wredundant-decls
|
|
29
|
+
)
|
|
30
|
+
endif()
|
|
31
|
+
endif()
|
|
32
|
+
|
|
33
|
+
if(TINY_PNG_ENABLE_COVERAGE)
|
|
34
|
+
if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang")
|
|
35
|
+
add_compile_options(--coverage -O0 -g -fno-elide-constructors)
|
|
36
|
+
add_link_options(--coverage)
|
|
37
|
+
endif()
|
|
38
|
+
endif()
|
|
39
|
+
|
|
40
|
+
add_library(TinyPNG STATIC
|
|
41
|
+
include/tiny_png/constants.h
|
|
42
|
+
include/tiny_png/lz77.h
|
|
43
|
+
src/lz77.cpp
|
|
44
|
+
include/tiny_png/bit_stream.h
|
|
45
|
+
src/bit_stream.cpp
|
|
46
|
+
include/tiny_png/huffman.h
|
|
47
|
+
src/huffman.cpp
|
|
48
|
+
include/tiny_png/deflate.h
|
|
49
|
+
src/deflate.cpp
|
|
50
|
+
include/tiny_png/png_chunk.h
|
|
51
|
+
src/png_chunk.cpp
|
|
52
|
+
include/tiny_png/tiny_png.h
|
|
53
|
+
src/tiny_png.cpp
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
target_include_directories(TinyPNG
|
|
57
|
+
PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
if(TINY_PNG_ENABLE_TESTS)
|
|
61
|
+
enable_testing()
|
|
62
|
+
|
|
63
|
+
include(FetchContent)
|
|
64
|
+
|
|
65
|
+
FetchContent_Declare(
|
|
66
|
+
googletest
|
|
67
|
+
GIT_REPOSITORY https://github.com/google/googletest.git
|
|
68
|
+
GIT_TAG v1.17.0
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
FetchContent_MakeAvailable(googletest)
|
|
72
|
+
|
|
73
|
+
add_executable(runTests
|
|
74
|
+
tests/test_lz77.cpp
|
|
75
|
+
tests/test_bit_stream.cpp
|
|
76
|
+
tests/test_huffman.cpp
|
|
77
|
+
tests/test_deflate.cpp
|
|
78
|
+
tests/test_png_chunk.cpp
|
|
79
|
+
tests/test_tiny_png.cpp
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
target_link_libraries(runTests
|
|
83
|
+
PRIVATE TinyPNG
|
|
84
|
+
PRIVATE gtest gtest_main
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
target_compile_definitions(runTests
|
|
88
|
+
PRIVATE TINY_PNG_TEST_DATA_DIR="${CMAKE_CURRENT_SOURCE_DIR}/tests/data"
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
add_test(NAME TinyPNGTests COMMAND runTests)
|
|
92
|
+
endif()
|
|
93
|
+
|
|
94
|
+
if(TINY_PNG_BIND_PYTHON)
|
|
95
|
+
include(FetchContent)
|
|
96
|
+
FetchContent_Declare(
|
|
97
|
+
pybind11
|
|
98
|
+
GIT_REPOSITORY https://github.com/pybind/pybind11.git
|
|
99
|
+
GIT_TAG v3.0
|
|
100
|
+
)
|
|
101
|
+
FetchContent_MakeAvailable(pybind11)
|
|
102
|
+
|
|
103
|
+
pybind11_add_module(_core
|
|
104
|
+
python/binding.cpp
|
|
105
|
+
)
|
|
106
|
+
target_link_libraries(_core
|
|
107
|
+
PRIVATE TinyPNG
|
|
108
|
+
)
|
|
109
|
+
install(TARGETS _core
|
|
110
|
+
LIBRARY DESTINATION tiny_png)
|
|
111
|
+
install(FILES python/wrapper/__init__.py DESTINATION tiny_png)
|
|
112
|
+
endif()
|
|
113
|
+
|
|
114
|
+
if(TINY_PNG_BIND_ES)
|
|
115
|
+
add_executable(TinyPNGWASM
|
|
116
|
+
wasm/binding.cpp
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
target_link_libraries(TinyPNGWASM
|
|
120
|
+
PRIVATE TinyPNG
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
target_compile_options(TinyPNGWASM PRIVATE "-O3" "-sNO_DISABLE_EXCEPTION_CATCHING")
|
|
124
|
+
|
|
125
|
+
set_target_properties(TinyPNGWASM PROPERTIES SUFFIX ".js")
|
|
126
|
+
|
|
127
|
+
target_link_options(TinyPNGWASM PRIVATE
|
|
128
|
+
"--bind"
|
|
129
|
+
"-sMODULARIZE=1"
|
|
130
|
+
"-sEXPORT_ES6=1"
|
|
131
|
+
"-sEXPORT_NAME=TinyPNGWASMModule"
|
|
132
|
+
"-sALLOW_MEMORY_GROWTH=1"
|
|
133
|
+
"-sNO_DISABLE_EXCEPTION_CATCHING"
|
|
134
|
+
)
|
|
135
|
+
endif()
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
Metadata-Version: 2.2
|
|
2
|
+
Name: lib-tiny-png
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A lightweight, minimal PNG library.
|
|
5
|
+
License: MIT
|
|
6
|
+
Classifier: Programming Language :: Python :: 3
|
|
7
|
+
Classifier: Programming Language :: C++
|
|
8
|
+
Classifier: Operating System :: OS Independent
|
|
9
|
+
Project-URL: Homepage, https://github.com/CyberZHG/TinyPNG
|
|
10
|
+
Project-URL: Repository, https://github.com/CyberZHG/TinyPNG
|
|
11
|
+
Project-URL: Issues, https://github.com/CyberZHG/TinyPNG/issues
|
|
12
|
+
Requires-Python: >=3.8
|
|
13
|
+
Provides-Extra: test
|
|
14
|
+
Requires-Dist: pytest; extra == "test"
|
|
15
|
+
Requires-Dist: black; extra == "test"
|
|
16
|
+
Requires-Dist: isort; extra == "test"
|
|
17
|
+
Requires-Dist: flake8; extra == "test"
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
|
|
20
|
+
# TinyPNG
|
|
21
|
+
|
|
22
|
+
A lightweight, minimal PNG library.
|
|
23
|
+
|
|
24
|
+
## Installation
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
pip install lib-tiny-png
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Usage
|
|
31
|
+
|
|
32
|
+
```python
|
|
33
|
+
from tiny_png import ColorType, load_image, save_image
|
|
34
|
+
|
|
35
|
+
width, height = 4, 2
|
|
36
|
+
pixels = bytes([255, 0, 0, 255] * (width * height)) # Red RGBA pixels
|
|
37
|
+
save_image("red.png", width, height, pixels)
|
|
38
|
+
|
|
39
|
+
image = load_image("red.png")
|
|
40
|
+
if image.error:
|
|
41
|
+
print(image.error)
|
|
42
|
+
else:
|
|
43
|
+
print(image.width, image.height, image.color_type) # 4 2 ColorType.RGBA
|
|
44
|
+
print(image.buffer == pixels) # True
|
|
45
|
+
|
|
46
|
+
gray = bytes(range(16))
|
|
47
|
+
save_image("gray.png", 4, 4, gray, ColorType.GRAYSCALE)
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## API Reference
|
|
51
|
+
|
|
52
|
+
### `ColorType`
|
|
53
|
+
|
|
54
|
+
`GRAYSCALE` (1 byte per pixel), `GRAYSCALE_ALPHA` (2), `RGB` (3), `RGBA` (4).
|
|
55
|
+
|
|
56
|
+
### `save_image(file_path, width, height, data, color_type=ColorType.RGBA)`
|
|
57
|
+
|
|
58
|
+
Saves row-major 8-bit pixels as a PNG file.
|
|
59
|
+
|
|
60
|
+
**Parameters:**
|
|
61
|
+
- `file_path` (`str`) - The output file path.
|
|
62
|
+
- `width`, `height` (`int`) - The image size in pixels.
|
|
63
|
+
- `data` (`bytes`, `bytearray` or any uint8 buffer) - Pixels of size `width * height * channels`.
|
|
64
|
+
- `color_type` (`ColorType`, optional) - The color type of the pixels.
|
|
65
|
+
|
|
66
|
+
**Returns:**
|
|
67
|
+
- `bool` - `True` if the image is saved.
|
|
68
|
+
|
|
69
|
+
**Raises:**
|
|
70
|
+
- `ValueError` - If the size of `data` does not match the image size.
|
|
71
|
+
|
|
72
|
+
### `load_image(file_path)`
|
|
73
|
+
|
|
74
|
+
Loads a PNG file.
|
|
75
|
+
|
|
76
|
+
**Returns:**
|
|
77
|
+
- `PNGData` - With fields `width`, `height`, `color_type`, `buffer` (`bytes`) and `error` (`str`, empty on success).
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
#ifndef TINYPNG_BIT_STREAM_H
|
|
2
|
+
#define TINYPNG_BIT_STREAM_H
|
|
3
|
+
|
|
4
|
+
#include <cstdint>
|
|
5
|
+
#include <vector>
|
|
6
|
+
#include <string>
|
|
7
|
+
|
|
8
|
+
namespace tiny_png {
|
|
9
|
+
|
|
10
|
+
class BitStream {
|
|
11
|
+
public:
|
|
12
|
+
BitStream();
|
|
13
|
+
|
|
14
|
+
[[nodiscard]] const std::vector<uint8_t>& getBytes() const {
|
|
15
|
+
return _buffer;
|
|
16
|
+
}
|
|
17
|
+
template<typename InputIterator>
|
|
18
|
+
void setBytes(InputIterator first, InputIterator last) {
|
|
19
|
+
_buffer.assign(first, last);
|
|
20
|
+
_index = _buffer.size();
|
|
21
|
+
_pos = 0;
|
|
22
|
+
_readIndex = 0;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
void writeInt(std::int32_t value, std::int32_t numBits, bool msb);
|
|
26
|
+
|
|
27
|
+
void rewind();
|
|
28
|
+
bool nextBit();
|
|
29
|
+
void alignToByte();
|
|
30
|
+
std::int32_t readInt(std::int32_t numBits, bool msb);
|
|
31
|
+
|
|
32
|
+
[[nodiscard]] std::string toString() const;
|
|
33
|
+
|
|
34
|
+
private:
|
|
35
|
+
std::size_t _index;
|
|
36
|
+
std::uint8_t _pos;
|
|
37
|
+
std::vector<std::uint8_t> _buffer;
|
|
38
|
+
|
|
39
|
+
std::size_t _readIndex;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
#endif //TINYPNG_BIT_STREAM_H
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
#ifndef TINYPNG_CONSTANTS_H
|
|
2
|
+
#define TINYPNG_CONSTANTS_H
|
|
3
|
+
|
|
4
|
+
#include <cstdint>
|
|
5
|
+
|
|
6
|
+
namespace tiny_png {
|
|
7
|
+
|
|
8
|
+
enum class ColorType {
|
|
9
|
+
Grayscale = 0,
|
|
10
|
+
RGB = 2,
|
|
11
|
+
GrayscaleAlpha = 4,
|
|
12
|
+
RGBA = 6
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
enum class FilterType {
|
|
16
|
+
None = 0,
|
|
17
|
+
Sub = 1,
|
|
18
|
+
Up = 2,
|
|
19
|
+
Average = 3,
|
|
20
|
+
Paeth = 4,
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
constexpr std::uint16_t LZ77_MIN_MATCH_LEN = 3;
|
|
24
|
+
constexpr std::uint16_t LZ77_MAX_MATCH_LEN = 258;
|
|
25
|
+
constexpr std::uint16_t LZ77_MAX_MATCH_OFFSET = 32768;
|
|
26
|
+
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
#endif //TINYPNG_CONSTANTS_H
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#ifndef TINYPNG_DEFLATE_H
|
|
2
|
+
#define TINYPNG_DEFLATE_H
|
|
3
|
+
|
|
4
|
+
#include <cstdint>
|
|
5
|
+
#include <vector>
|
|
6
|
+
|
|
7
|
+
namespace tiny_png {
|
|
8
|
+
|
|
9
|
+
std::vector<std::uint8_t> deflate_encode(const std::uint8_t* data, std::size_t len);
|
|
10
|
+
std::vector<std::uint8_t> deflate_decode(const std::vector<std::uint8_t>& data);
|
|
11
|
+
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
#endif //TINYPNG_DEFLATE_H
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#ifndef TINYPNG_HUFFMAN_H
|
|
2
|
+
#define TINYPNG_HUFFMAN_H
|
|
3
|
+
|
|
4
|
+
#include "tiny_png/bit_stream.h"
|
|
5
|
+
#include "tiny_png/lz77.h"
|
|
6
|
+
|
|
7
|
+
namespace tiny_png {
|
|
8
|
+
|
|
9
|
+
BitStream huffman_encode(const std::vector<LZ77Seq>& seqs);
|
|
10
|
+
std::vector<LZ77Seq> huffman_decode(BitStream& bits);
|
|
11
|
+
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
#endif //TINYPNG_HUFFMAN_H
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
#ifndef TINYPNG_LZ77_H
|
|
2
|
+
#define TINYPNG_LZ77_H
|
|
3
|
+
|
|
4
|
+
#include <cstdint>
|
|
5
|
+
#include <vector>
|
|
6
|
+
|
|
7
|
+
namespace tiny_png {
|
|
8
|
+
|
|
9
|
+
struct LZ77Seq {
|
|
10
|
+
std::uint8_t byte;
|
|
11
|
+
std::uint16_t len;
|
|
12
|
+
std::uint16_t dist;
|
|
13
|
+
|
|
14
|
+
LZ77Seq(const std::uint8_t byte, const std::uint16_t len, const std::uint16_t dist) : byte(byte), len(len), dist(dist) {}
|
|
15
|
+
|
|
16
|
+
bool operator==(const LZ77Seq& other) const {
|
|
17
|
+
return byte == other.byte && len == other.len && dist == other.dist;
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
std::vector<LZ77Seq> lz77_encode(const std::uint8_t* data, std::size_t len);
|
|
22
|
+
|
|
23
|
+
std::vector<std::uint8_t> lz77_decode(const std::vector<LZ77Seq>& seqs);
|
|
24
|
+
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
#endif //TINYPNG_LZ77_H
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
#ifndef TINYPNG_PNG_CHUNK_H
|
|
2
|
+
#define TINYPNG_PNG_CHUNK_H
|
|
3
|
+
|
|
4
|
+
#include <cstdint>
|
|
5
|
+
#include <vector>
|
|
6
|
+
#include <utility>
|
|
7
|
+
#include "tiny_png/constants.h"
|
|
8
|
+
|
|
9
|
+
namespace tiny_png {
|
|
10
|
+
|
|
11
|
+
enum class ChunkType {
|
|
12
|
+
I_HEADER, I_DATA, I_END, OTHER
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
struct Header {
|
|
16
|
+
std::uint32_t width, height;
|
|
17
|
+
std::uint8_t bitDepth, colorType, compressionMethod, filterMethod, interlaceMethod;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
void addHeaderChunk(std::vector<std::uint8_t>& buffer, std::uint32_t width, std::uint32_t height, ColorType colorType);
|
|
21
|
+
void addDataChunk(std::vector<std::uint8_t>& buffer, const std::vector<std::uint8_t>& data);
|
|
22
|
+
void addEndChunk(std::vector<std::uint8_t>& buffer);
|
|
23
|
+
|
|
24
|
+
std::pair<std::uint32_t, ChunkType> readChunkInfo(const std::vector<std::uint8_t>& buffer, std::size_t& pos);
|
|
25
|
+
Header readHeaderChunk(const std::vector<std::uint8_t>& buffer, std::size_t& pos);
|
|
26
|
+
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
#endif //TINYPNG_PNG_CHUNK_H
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
#ifndef TINYPNG_TINY_PNG_H
|
|
2
|
+
#define TINYPNG_TINY_PNG_H
|
|
3
|
+
|
|
4
|
+
#include <string>
|
|
5
|
+
#include <vector>
|
|
6
|
+
#include "tiny_png/constants.h"
|
|
7
|
+
|
|
8
|
+
namespace tiny_png {
|
|
9
|
+
|
|
10
|
+
struct PNGData {
|
|
11
|
+
std::size_t width;
|
|
12
|
+
std::size_t height;
|
|
13
|
+
ColorType colorType;
|
|
14
|
+
std::vector<std::uint8_t> buffer;
|
|
15
|
+
std::string error;
|
|
16
|
+
|
|
17
|
+
PNGData();
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
bool save_image(const std::string& filePath, std::uint32_t width, std::uint32_t height, const std::uint8_t* data, ColorType colorType = ColorType::RGBA);
|
|
21
|
+
PNGData load_image(const std::string& filePath);
|
|
22
|
+
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
#endif //TINYPNG_TINY_PNG_H
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["scikit-build-core", "pybind11"]
|
|
3
|
+
build-backend = "scikit_build_core.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "lib-tiny-png"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "A lightweight, minimal PNG library."
|
|
9
|
+
requires-python = ">=3.8"
|
|
10
|
+
readme = "python/README.md"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
|
|
13
|
+
classifiers = [
|
|
14
|
+
"Programming Language :: Python :: 3",
|
|
15
|
+
"Programming Language :: C++",
|
|
16
|
+
"Operating System :: OS Independent",
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
[project.urls]
|
|
20
|
+
Homepage = "https://github.com/CyberZHG/TinyPNG"
|
|
21
|
+
Repository = "https://github.com/CyberZHG/TinyPNG"
|
|
22
|
+
Issues = "https://github.com/CyberZHG/TinyPNG/issues"
|
|
23
|
+
|
|
24
|
+
[tool.scikit-build]
|
|
25
|
+
build-dir = "../cmake-build-python/{wheel_tag}"
|
|
26
|
+
build.verbose = true
|
|
27
|
+
|
|
28
|
+
[tool.scikit-build.cmake.define]
|
|
29
|
+
TINY_PNG_BIND_PYTHON = "ON"
|
|
30
|
+
|
|
31
|
+
[tool.scikit-build.sdist]
|
|
32
|
+
exclude = [
|
|
33
|
+
".*",
|
|
34
|
+
"build*",
|
|
35
|
+
"cmake-build*",
|
|
36
|
+
"dist",
|
|
37
|
+
"demo",
|
|
38
|
+
"tests",
|
|
39
|
+
"wasm",
|
|
40
|
+
"*.md",
|
|
41
|
+
"python/tests",
|
|
42
|
+
]
|
|
43
|
+
include = [
|
|
44
|
+
"python/README.md",
|
|
45
|
+
]
|
|
46
|
+
|
|
47
|
+
[tool.black]
|
|
48
|
+
line-length = 120
|
|
49
|
+
|
|
50
|
+
[tool.isort]
|
|
51
|
+
profile = "black"
|
|
52
|
+
line_length = 120
|
|
53
|
+
|
|
54
|
+
[project.optional-dependencies]
|
|
55
|
+
test = [
|
|
56
|
+
"pytest",
|
|
57
|
+
"black",
|
|
58
|
+
"isort",
|
|
59
|
+
"flake8",
|
|
60
|
+
]
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# TinyPNG
|
|
2
|
+
|
|
3
|
+
A lightweight, minimal PNG library.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install lib-tiny-png
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
```python
|
|
14
|
+
from tiny_png import ColorType, load_image, save_image
|
|
15
|
+
|
|
16
|
+
width, height = 4, 2
|
|
17
|
+
pixels = bytes([255, 0, 0, 255] * (width * height)) # Red RGBA pixels
|
|
18
|
+
save_image("red.png", width, height, pixels)
|
|
19
|
+
|
|
20
|
+
image = load_image("red.png")
|
|
21
|
+
if image.error:
|
|
22
|
+
print(image.error)
|
|
23
|
+
else:
|
|
24
|
+
print(image.width, image.height, image.color_type) # 4 2 ColorType.RGBA
|
|
25
|
+
print(image.buffer == pixels) # True
|
|
26
|
+
|
|
27
|
+
gray = bytes(range(16))
|
|
28
|
+
save_image("gray.png", 4, 4, gray, ColorType.GRAYSCALE)
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## API Reference
|
|
32
|
+
|
|
33
|
+
### `ColorType`
|
|
34
|
+
|
|
35
|
+
`GRAYSCALE` (1 byte per pixel), `GRAYSCALE_ALPHA` (2), `RGB` (3), `RGBA` (4).
|
|
36
|
+
|
|
37
|
+
### `save_image(file_path, width, height, data, color_type=ColorType.RGBA)`
|
|
38
|
+
|
|
39
|
+
Saves row-major 8-bit pixels as a PNG file.
|
|
40
|
+
|
|
41
|
+
**Parameters:**
|
|
42
|
+
- `file_path` (`str`) - The output file path.
|
|
43
|
+
- `width`, `height` (`int`) - The image size in pixels.
|
|
44
|
+
- `data` (`bytes`, `bytearray` or any uint8 buffer) - Pixels of size `width * height * channels`.
|
|
45
|
+
- `color_type` (`ColorType`, optional) - The color type of the pixels.
|
|
46
|
+
|
|
47
|
+
**Returns:**
|
|
48
|
+
- `bool` - `True` if the image is saved.
|
|
49
|
+
|
|
50
|
+
**Raises:**
|
|
51
|
+
- `ValueError` - If the size of `data` does not match the image size.
|
|
52
|
+
|
|
53
|
+
### `load_image(file_path)`
|
|
54
|
+
|
|
55
|
+
Loads a PNG file.
|
|
56
|
+
|
|
57
|
+
**Returns:**
|
|
58
|
+
- `PNGData` - With fields `width`, `height`, `color_type`, `buffer` (`bytes`) and `error` (`str`, empty on success).
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
#include <stdexcept>
|
|
2
|
+
#include <pybind11/pybind11.h>
|
|
3
|
+
#include <pybind11/stl.h>
|
|
4
|
+
#include "tiny_png/tiny_png.h"
|
|
5
|
+
using namespace tiny_png;
|
|
6
|
+
|
|
7
|
+
namespace py = pybind11;
|
|
8
|
+
|
|
9
|
+
static std::size_t getBytesPerPixel(const ColorType colorType) {
|
|
10
|
+
switch (colorType) {
|
|
11
|
+
case ColorType::Grayscale: return 1;
|
|
12
|
+
case ColorType::RGB: return 3;
|
|
13
|
+
case ColorType::GrayscaleAlpha: return 2;
|
|
14
|
+
case ColorType::RGBA: return 4;
|
|
15
|
+
}
|
|
16
|
+
return 4;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
static bool saveImage(const std::string& filePath,
|
|
20
|
+
const std::uint32_t width,
|
|
21
|
+
const std::uint32_t height,
|
|
22
|
+
const py::buffer& data,
|
|
23
|
+
const ColorType colorType) {
|
|
24
|
+
const py::buffer_info info = data.request();
|
|
25
|
+
if (info.itemsize != 1) {
|
|
26
|
+
throw py::value_error("data must be a buffer of uint8 values");
|
|
27
|
+
}
|
|
28
|
+
const auto expected = static_cast<std::size_t>(width) * height * getBytesPerPixel(colorType);
|
|
29
|
+
if (static_cast<std::size_t>(info.size) != expected) {
|
|
30
|
+
throw py::value_error("data size mismatch: expected " + std::to_string(expected) +
|
|
31
|
+
" bytes, got " + std::to_string(info.size));
|
|
32
|
+
}
|
|
33
|
+
return save_image(filePath, width, height, static_cast<const std::uint8_t*>(info.ptr), colorType);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
static PNGData loadImage(const std::string& filePath) {
|
|
37
|
+
try {
|
|
38
|
+
return load_image(filePath);
|
|
39
|
+
} catch (const std::exception& e) {
|
|
40
|
+
PNGData imageData;
|
|
41
|
+
imageData.error = std::string("Failed to load image: ") + e.what();
|
|
42
|
+
return imageData;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
PYBIND11_MODULE(_core, m, py::mod_gil_not_used()) {
|
|
47
|
+
m.doc() = "A lightweight, minimal PNG library.";
|
|
48
|
+
|
|
49
|
+
py::enum_<ColorType>(m, "ColorType")
|
|
50
|
+
.value("GRAYSCALE", ColorType::Grayscale)
|
|
51
|
+
.value("RGB", ColorType::RGB)
|
|
52
|
+
.value("GRAYSCALE_ALPHA", ColorType::GrayscaleAlpha)
|
|
53
|
+
.value("RGBA", ColorType::RGBA);
|
|
54
|
+
|
|
55
|
+
py::class_<PNGData>(m, "PNGData")
|
|
56
|
+
.def_readonly("width", &PNGData::width)
|
|
57
|
+
.def_readonly("height", &PNGData::height)
|
|
58
|
+
.def_readonly("color_type", &PNGData::colorType)
|
|
59
|
+
.def_property_readonly("buffer", [](const PNGData& self) {
|
|
60
|
+
return py::bytes(reinterpret_cast<const char*>(self.buffer.data()), self.buffer.size());
|
|
61
|
+
})
|
|
62
|
+
.def_readonly("error", &PNGData::error)
|
|
63
|
+
.def("__repr__", [](const PNGData& self) {
|
|
64
|
+
return "<PNGData width=" + std::to_string(self.width) +
|
|
65
|
+
" height=" + std::to_string(self.height) +
|
|
66
|
+
" color_type=" + std::to_string(static_cast<int>(self.colorType)) +
|
|
67
|
+
" error='" + self.error + "'>";
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
m.def("save_image", &saveImage,
|
|
71
|
+
py::arg("file_path"), py::arg("width"), py::arg("height"), py::arg("data"),
|
|
72
|
+
py::arg("color_type") = ColorType::RGBA,
|
|
73
|
+
R"pbdoc(
|
|
74
|
+
Save raw pixels as a PNG file.
|
|
75
|
+
|
|
76
|
+
Args:
|
|
77
|
+
file_path: The output file path.
|
|
78
|
+
width: Image width in pixels.
|
|
79
|
+
height: Image height in pixels.
|
|
80
|
+
data: Row-major pixel bytes (bytes, bytearray or any uint8 buffer) of size
|
|
81
|
+
width * height * channels.
|
|
82
|
+
color_type: The color type of the pixels (default: ColorType.RGBA).
|
|
83
|
+
|
|
84
|
+
Returns:
|
|
85
|
+
True if the image is saved.
|
|
86
|
+
|
|
87
|
+
Raises:
|
|
88
|
+
ValueError: If the size of data does not match the image size.
|
|
89
|
+
)pbdoc");
|
|
90
|
+
|
|
91
|
+
m.def("load_image", &loadImage,
|
|
92
|
+
py::arg("file_path"),
|
|
93
|
+
R"pbdoc(
|
|
94
|
+
Load a PNG file.
|
|
95
|
+
|
|
96
|
+
Args:
|
|
97
|
+
file_path: The input file path.
|
|
98
|
+
|
|
99
|
+
Returns:
|
|
100
|
+
A PNGData object. If loading fails, its error field is non-empty.
|
|
101
|
+
)pbdoc");
|
|
102
|
+
}
|