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.
@@ -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
+ }
@@ -0,0 +1,3 @@
1
+ from ._core import ColorType, PNGData, load_image, save_image
2
+
3
+ __all__ = ["ColorType", "PNGData", "load_image", "save_image"]