hufi 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.
- hufi-0.1.0/PKG-INFO +95 -0
- hufi-0.1.0/README.md +86 -0
- hufi-0.1.0/hufi/__init__.py +3 -0
- hufi-0.1.0/hufi/core.py +119 -0
- hufi-0.1.0/hufi.egg-info/PKG-INFO +95 -0
- hufi-0.1.0/hufi.egg-info/SOURCES.txt +9 -0
- hufi-0.1.0/hufi.egg-info/dependency_links.txt +1 -0
- hufi-0.1.0/hufi.egg-info/requires.txt +1 -0
- hufi-0.1.0/hufi.egg-info/top_level.txt +1 -0
- hufi-0.1.0/pyproject.toml +16 -0
- hufi-0.1.0/setup.cfg +4 -0
hufi-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: hufi
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A Python library for encoding and decoding .hufi image files.
|
|
5
|
+
Project-URL: Homepage, https://github.com/yourusername/hufipy
|
|
6
|
+
Requires-Python: >=3.8
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
Requires-Dist: Pillow>=9.0.0
|
|
9
|
+
|
|
10
|
+
```markdown
|
|
11
|
+
# HUFI (`hufipy`)
|
|
12
|
+
|
|
13
|
+
A lightweight Python library for encoding, decoding, and inspecting **`.hufi`** (Hagan's Ultimate Format for Images) files.
|
|
14
|
+
|
|
15
|
+
`.hufi` is a custom binary image format that pairs horizontal predictive sub-filtering with zlib Deflate compression to achieve fast, efficient, lossless image storage.
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Installation
|
|
20
|
+
|
|
21
|
+
Install via `pip`:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
pip install hufi
|
|
25
|
+
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## Quickstart
|
|
31
|
+
|
|
32
|
+
### 1. Save and Open `.hufi` Files
|
|
33
|
+
|
|
34
|
+
```python
|
|
35
|
+
import hufi
|
|
36
|
+
|
|
37
|
+
# Convert standard image formats (PNG, JPG, WEBP, etc.) to .hufi
|
|
38
|
+
hufi.save("input.png", "output.hufi")
|
|
39
|
+
|
|
40
|
+
# Load a .hufi file directly as a Pillow (PIL) Image
|
|
41
|
+
img = hufi.open_hufi("output.hufi")
|
|
42
|
+
img.show()
|
|
43
|
+
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### 2. Extract Metadata Fast
|
|
47
|
+
|
|
48
|
+
Inspect header details without decompressing the entire image payload:
|
|
49
|
+
|
|
50
|
+
```python
|
|
51
|
+
import hufi
|
|
52
|
+
|
|
53
|
+
metadata = hufi.get_metadata("output.hufi")
|
|
54
|
+
print(f"Dimensions: {metadata['width']}x{metadata['height']} px")
|
|
55
|
+
print(f"Channels: {metadata['channels']}")
|
|
56
|
+
print(f"Magic: {metadata['magic']}")
|
|
57
|
+
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### 3. In-Memory Encoding & Decoding
|
|
61
|
+
|
|
62
|
+
Work with bytes and Pillow image objects in memory without writing to disk:
|
|
63
|
+
|
|
64
|
+
```python
|
|
65
|
+
import hufi
|
|
66
|
+
from PIL import Image
|
|
67
|
+
|
|
68
|
+
# Encode a PIL Image to raw .hufi bytes
|
|
69
|
+
img = Image.open("photo.jpg")
|
|
70
|
+
hufi_bytes = hufi.encode(img, level=6)
|
|
71
|
+
|
|
72
|
+
# Decode raw .hufi bytes back to a PIL Image
|
|
73
|
+
restored_img = hufi.decode(hufi_bytes)
|
|
74
|
+
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
## Technical Specification
|
|
80
|
+
|
|
81
|
+
The `.hufi` format consists of a 13-byte binary header followed by a compressed payload:
|
|
82
|
+
|
|
83
|
+
| Field | Type | Size | Description |
|
|
84
|
+
| --- | --- | --- | --- |
|
|
85
|
+
| **Magic Signature** | ASCII String | 4 Bytes | Magic bytes `b"HUFI"` |
|
|
86
|
+
| **Width** | Uint32 (Little-Endian) | 4 Bytes | Image width in pixels |
|
|
87
|
+
| **Height** | Uint32 (Little-Endian) | 4 Bytes | Image height in pixels |
|
|
88
|
+
| **Channels** | Uint8 | 1 Byte | Color channels (default `3` for RGB) |
|
|
89
|
+
| **Payload** | Deflate Bytes | Variable | Horizontal sub-filtered byte deltas compressed via Zlib |
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
## License
|
|
94
|
+
|
|
95
|
+
Licensed under the Apache License, Version 2.0. See `LICENSE` for details.
|
hufi-0.1.0/README.md
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
```markdown
|
|
2
|
+
# HUFI (`hufipy`)
|
|
3
|
+
|
|
4
|
+
A lightweight Python library for encoding, decoding, and inspecting **`.hufi`** (Hagan's Ultimate Format for Images) files.
|
|
5
|
+
|
|
6
|
+
`.hufi` is a custom binary image format that pairs horizontal predictive sub-filtering with zlib Deflate compression to achieve fast, efficient, lossless image storage.
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Installation
|
|
11
|
+
|
|
12
|
+
Install via `pip`:
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
pip install hufi
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Quickstart
|
|
22
|
+
|
|
23
|
+
### 1. Save and Open `.hufi` Files
|
|
24
|
+
|
|
25
|
+
```python
|
|
26
|
+
import hufi
|
|
27
|
+
|
|
28
|
+
# Convert standard image formats (PNG, JPG, WEBP, etc.) to .hufi
|
|
29
|
+
hufi.save("input.png", "output.hufi")
|
|
30
|
+
|
|
31
|
+
# Load a .hufi file directly as a Pillow (PIL) Image
|
|
32
|
+
img = hufi.open_hufi("output.hufi")
|
|
33
|
+
img.show()
|
|
34
|
+
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### 2. Extract Metadata Fast
|
|
38
|
+
|
|
39
|
+
Inspect header details without decompressing the entire image payload:
|
|
40
|
+
|
|
41
|
+
```python
|
|
42
|
+
import hufi
|
|
43
|
+
|
|
44
|
+
metadata = hufi.get_metadata("output.hufi")
|
|
45
|
+
print(f"Dimensions: {metadata['width']}x{metadata['height']} px")
|
|
46
|
+
print(f"Channels: {metadata['channels']}")
|
|
47
|
+
print(f"Magic: {metadata['magic']}")
|
|
48
|
+
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### 3. In-Memory Encoding & Decoding
|
|
52
|
+
|
|
53
|
+
Work with bytes and Pillow image objects in memory without writing to disk:
|
|
54
|
+
|
|
55
|
+
```python
|
|
56
|
+
import hufi
|
|
57
|
+
from PIL import Image
|
|
58
|
+
|
|
59
|
+
# Encode a PIL Image to raw .hufi bytes
|
|
60
|
+
img = Image.open("photo.jpg")
|
|
61
|
+
hufi_bytes = hufi.encode(img, level=6)
|
|
62
|
+
|
|
63
|
+
# Decode raw .hufi bytes back to a PIL Image
|
|
64
|
+
restored_img = hufi.decode(hufi_bytes)
|
|
65
|
+
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
## Technical Specification
|
|
71
|
+
|
|
72
|
+
The `.hufi` format consists of a 13-byte binary header followed by a compressed payload:
|
|
73
|
+
|
|
74
|
+
| Field | Type | Size | Description |
|
|
75
|
+
| --- | --- | --- | --- |
|
|
76
|
+
| **Magic Signature** | ASCII String | 4 Bytes | Magic bytes `b"HUFI"` |
|
|
77
|
+
| **Width** | Uint32 (Little-Endian) | 4 Bytes | Image width in pixels |
|
|
78
|
+
| **Height** | Uint32 (Little-Endian) | 4 Bytes | Image height in pixels |
|
|
79
|
+
| **Channels** | Uint8 | 1 Byte | Color channels (default `3` for RGB) |
|
|
80
|
+
| **Payload** | Deflate Bytes | Variable | Horizontal sub-filtered byte deltas compressed via Zlib |
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
## License
|
|
85
|
+
|
|
86
|
+
Licensed under the Apache License, Version 2.0. See `LICENSE` for details.
|
hufi-0.1.0/hufi/core.py
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import struct
|
|
2
|
+
import zlib
|
|
3
|
+
from typing import Union, Dict, Any
|
|
4
|
+
from PIL import Image
|
|
5
|
+
|
|
6
|
+
# Format Specifications
|
|
7
|
+
MAGIC_HEADER = b"HUFI"
|
|
8
|
+
HEADER_FORMAT = "<4sIIB" # Magic (4s), Width (uint32), Height (uint32), Channels (uint8)
|
|
9
|
+
HEADER_SIZE = struct.calcsize(HEADER_FORMAT) # 13 bytes
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def apply_sub_filter(raw_bytes: bytes, width: int, height: int, channels: int = 3) -> bytes:
|
|
13
|
+
"""Applies horizontal predictive sub-filter: delta[x] = (pixel[x] - pixel[x-1]) & 0xFF."""
|
|
14
|
+
filtered = bytearray(len(raw_bytes))
|
|
15
|
+
stride = width * channels
|
|
16
|
+
|
|
17
|
+
for y in range(height):
|
|
18
|
+
row_start = y * stride
|
|
19
|
+
# First pixel in each row remains unchanged
|
|
20
|
+
filtered[row_start : row_start + channels] = raw_bytes[row_start : row_start + channels]
|
|
21
|
+
|
|
22
|
+
# Calculate byte deltas for remaining pixels in row
|
|
23
|
+
for x in range(1, width):
|
|
24
|
+
curr_i = row_start + x * channels
|
|
25
|
+
prev_i = curr_i - channels
|
|
26
|
+
for c in range(channels):
|
|
27
|
+
filtered[curr_i + c] = (raw_bytes[curr_i + c] - raw_bytes[prev_i + c]) & 0xFF
|
|
28
|
+
|
|
29
|
+
return bytes(filtered)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def undo_sub_filter(filtered_bytes: bytes, width: int, height: int, channels: int = 3) -> bytes:
|
|
33
|
+
"""Reconstructs raw pixel data from horizontal byte deltas."""
|
|
34
|
+
raw = bytearray(len(filtered_bytes))
|
|
35
|
+
stride = width * channels
|
|
36
|
+
|
|
37
|
+
for y in range(height):
|
|
38
|
+
row_start = y * stride
|
|
39
|
+
# First pixel in each row
|
|
40
|
+
raw[row_start : row_start + channels] = filtered_bytes[row_start : row_start + channels]
|
|
41
|
+
|
|
42
|
+
# Reconstruct pixel values from left neighbors
|
|
43
|
+
for x in range(1, width):
|
|
44
|
+
curr_i = row_start + x * channels
|
|
45
|
+
prev_i = curr_i - channels
|
|
46
|
+
for c in range(channels):
|
|
47
|
+
raw[curr_i + c] = (filtered_bytes[curr_i + c] + raw[prev_i + c]) & 0xFF
|
|
48
|
+
|
|
49
|
+
return bytes(raw)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def encode(image_input: Union[str, Image.Image], level: int = 6) -> bytes:
|
|
53
|
+
"""Encodes a file path or PIL Image into .hufi binary bytes."""
|
|
54
|
+
if isinstance(image_input, str):
|
|
55
|
+
img = Image.open(image_input).convert("RGB")
|
|
56
|
+
else:
|
|
57
|
+
img = image_input.convert("RGB")
|
|
58
|
+
|
|
59
|
+
width, height = img.size
|
|
60
|
+
channels = 3
|
|
61
|
+
raw_rgb = img.tobytes()
|
|
62
|
+
|
|
63
|
+
filtered_data = apply_sub_filter(raw_rgb, width, height, channels)
|
|
64
|
+
compressed_payload = zlib.compress(filtered_data, level=level)
|
|
65
|
+
header = struct.pack(HEADER_FORMAT, MAGIC_HEADER, width, height, channels)
|
|
66
|
+
|
|
67
|
+
return header + compressed_payload
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def decode(hufi_bytes: bytes) -> Image.Image:
|
|
71
|
+
"""Decodes .hufi binary bytes into a PIL Image object."""
|
|
72
|
+
if len(hufi_bytes) < HEADER_SIZE:
|
|
73
|
+
raise ValueError("Data is too small to contain a valid .hufi header.")
|
|
74
|
+
|
|
75
|
+
magic, width, height, channels = struct.unpack(HEADER_FORMAT, hufi_bytes[:HEADER_SIZE])
|
|
76
|
+
if magic != MAGIC_HEADER:
|
|
77
|
+
raise ValueError(f"Invalid format signature: expected {MAGIC_HEADER}, got {magic}")
|
|
78
|
+
|
|
79
|
+
compressed_payload = hufi_bytes[HEADER_SIZE:]
|
|
80
|
+
filtered_data = zlib.decompress(compressed_payload)
|
|
81
|
+
raw_rgb = undo_sub_filter(filtered_data, width, height, channels)
|
|
82
|
+
|
|
83
|
+
return Image.frombytes("RGB", (width, height), raw_rgb)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def save(image_input: Union[str, Image.Image], output_path: str, level: int = 6) -> None:
|
|
87
|
+
"""Encodes an image and writes it directly to disk as a .hufi file."""
|
|
88
|
+
binary_data = encode(image_input, level=level)
|
|
89
|
+
with open(output_path, "wb") as f:
|
|
90
|
+
f.write(binary_data)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def open_hufi(filepath: str) -> Image.Image:
|
|
94
|
+
"""Reads a .hufi file from disk and returns a PIL Image."""
|
|
95
|
+
with open(filepath, "rb") as f:
|
|
96
|
+
return decode(f.read())
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def get_metadata(filepath_or_bytes: Union[str, bytes]) -> Dict[str, Any]:
|
|
100
|
+
"""Reads header information without decompressing the payload."""
|
|
101
|
+
if isinstance(filepath_or_bytes, str):
|
|
102
|
+
with open(filepath_or_bytes, "rb") as f:
|
|
103
|
+
header_bytes = f.read(HEADER_SIZE)
|
|
104
|
+
else:
|
|
105
|
+
header_bytes = filepath_or_bytes[:HEADER_SIZE]
|
|
106
|
+
|
|
107
|
+
if len(header_bytes) < HEADER_SIZE:
|
|
108
|
+
raise ValueError("Header is corrupted or too short.")
|
|
109
|
+
|
|
110
|
+
magic, width, height, channels = struct.unpack(HEADER_FORMAT, header_bytes)
|
|
111
|
+
if magic != MAGIC_HEADER:
|
|
112
|
+
raise ValueError(f"Invalid HUFI signature: {magic}")
|
|
113
|
+
|
|
114
|
+
return {
|
|
115
|
+
"magic": magic.decode("ascii"),
|
|
116
|
+
"width": width,
|
|
117
|
+
"height": height,
|
|
118
|
+
"channels": channels,
|
|
119
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: hufi
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A Python library for encoding and decoding .hufi image files.
|
|
5
|
+
Project-URL: Homepage, https://github.com/yourusername/hufipy
|
|
6
|
+
Requires-Python: >=3.8
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
Requires-Dist: Pillow>=9.0.0
|
|
9
|
+
|
|
10
|
+
```markdown
|
|
11
|
+
# HUFI (`hufipy`)
|
|
12
|
+
|
|
13
|
+
A lightweight Python library for encoding, decoding, and inspecting **`.hufi`** (Hagan's Ultimate Format for Images) files.
|
|
14
|
+
|
|
15
|
+
`.hufi` is a custom binary image format that pairs horizontal predictive sub-filtering with zlib Deflate compression to achieve fast, efficient, lossless image storage.
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Installation
|
|
20
|
+
|
|
21
|
+
Install via `pip`:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
pip install hufi
|
|
25
|
+
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## Quickstart
|
|
31
|
+
|
|
32
|
+
### 1. Save and Open `.hufi` Files
|
|
33
|
+
|
|
34
|
+
```python
|
|
35
|
+
import hufi
|
|
36
|
+
|
|
37
|
+
# Convert standard image formats (PNG, JPG, WEBP, etc.) to .hufi
|
|
38
|
+
hufi.save("input.png", "output.hufi")
|
|
39
|
+
|
|
40
|
+
# Load a .hufi file directly as a Pillow (PIL) Image
|
|
41
|
+
img = hufi.open_hufi("output.hufi")
|
|
42
|
+
img.show()
|
|
43
|
+
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### 2. Extract Metadata Fast
|
|
47
|
+
|
|
48
|
+
Inspect header details without decompressing the entire image payload:
|
|
49
|
+
|
|
50
|
+
```python
|
|
51
|
+
import hufi
|
|
52
|
+
|
|
53
|
+
metadata = hufi.get_metadata("output.hufi")
|
|
54
|
+
print(f"Dimensions: {metadata['width']}x{metadata['height']} px")
|
|
55
|
+
print(f"Channels: {metadata['channels']}")
|
|
56
|
+
print(f"Magic: {metadata['magic']}")
|
|
57
|
+
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### 3. In-Memory Encoding & Decoding
|
|
61
|
+
|
|
62
|
+
Work with bytes and Pillow image objects in memory without writing to disk:
|
|
63
|
+
|
|
64
|
+
```python
|
|
65
|
+
import hufi
|
|
66
|
+
from PIL import Image
|
|
67
|
+
|
|
68
|
+
# Encode a PIL Image to raw .hufi bytes
|
|
69
|
+
img = Image.open("photo.jpg")
|
|
70
|
+
hufi_bytes = hufi.encode(img, level=6)
|
|
71
|
+
|
|
72
|
+
# Decode raw .hufi bytes back to a PIL Image
|
|
73
|
+
restored_img = hufi.decode(hufi_bytes)
|
|
74
|
+
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
## Technical Specification
|
|
80
|
+
|
|
81
|
+
The `.hufi` format consists of a 13-byte binary header followed by a compressed payload:
|
|
82
|
+
|
|
83
|
+
| Field | Type | Size | Description |
|
|
84
|
+
| --- | --- | --- | --- |
|
|
85
|
+
| **Magic Signature** | ASCII String | 4 Bytes | Magic bytes `b"HUFI"` |
|
|
86
|
+
| **Width** | Uint32 (Little-Endian) | 4 Bytes | Image width in pixels |
|
|
87
|
+
| **Height** | Uint32 (Little-Endian) | 4 Bytes | Image height in pixels |
|
|
88
|
+
| **Channels** | Uint8 | 1 Byte | Color channels (default `3` for RGB) |
|
|
89
|
+
| **Payload** | Deflate Bytes | Variable | Horizontal sub-filtered byte deltas compressed via Zlib |
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
## License
|
|
94
|
+
|
|
95
|
+
Licensed under the Apache License, Version 2.0. See `LICENSE` for details.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Pillow>=9.0.0
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
hufi
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "hufi"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "A Python library for encoding and decoding .hufi image files."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.8"
|
|
11
|
+
dependencies = [
|
|
12
|
+
"Pillow>=9.0.0"
|
|
13
|
+
]
|
|
14
|
+
|
|
15
|
+
[project.urls]
|
|
16
|
+
"Homepage" = "https://github.com/yourusername/hufipy"
|
hufi-0.1.0/setup.cfg
ADDED