|
1
|
|
|
2
|
"""
|
|
3
|
Generate 100 small BMP images: img001.bmp .. img100.bmp
|
|
4
|
- 16x16 pixels
|
|
5
|
- 8-bit indexed (palette) BMP (BI_RGB, uncompressed)
|
|
6
|
- Each image has a distinct pattern + embedded index color cue.
|
|
7
|
|
|
8
|
No external dependencies.
|
|
9
|
"""
|
|
10
|
|
|
11
|
import os
|
|
12
|
import struct
|
|
13
|
from pathlib import Path
|
|
14
|
|
|
15
|
OUT_DIR = Path(r"images/")
|
|
16
|
WIDTH, HEIGHT = 16, 16
|
|
17
|
BPP = 8
|
|
18
|
NUM_IMAGES = 100
|
|
19
|
|
|
20
|
def bmp_bytes_indexed_8bit(width: int, height: int, pixels_topdown: bytes, palette_bgr0: bytes) -> bytes:
|
|
21
|
"""
|
|
22
|
Build a minimal BMP (BITMAPFILEHEADER + BITMAPINFOHEADER + palette + pixel array).
|
|
23
|
pixels_topdown: width*height bytes, row-major top-to-bottom.
|
|
24
|
palette_bgr0: 256*4 bytes, each entry B,G,R,0.
|
|
25
|
"""
|
|
26
|
assert len(pixels_topdown) == width * height
|
|
27
|
assert len(palette_bgr0) == 256 * 4
|
|
28
|
|
|
29
|
|
|
30
|
row_bytes = width
|
|
31
|
row_padded = (row_bytes + 3) & ~3
|
|
32
|
padding = b"\x00" * (row_padded - row_bytes)
|
|
33
|
|
|
34
|
|
|
35
|
pixel_rows = []
|
|
36
|
for y in range(height - 1, -1, -1):
|
|
37
|
start = y * width
|
|
38
|
pixel_rows.append(pixels_topdown[start:start + width] + padding)
|
|
39
|
pixel_array = b"".join(pixel_rows)
|
|
40
|
|
|
41
|
|
|
42
|
bfType = b"BM"
|
|
43
|
bfOffBits = 14 + 40 + len(palette_bgr0)
|
|
44
|
bfSize = bfOffBits + len(pixel_array)
|
|
45
|
|
|
46
|
file_header = struct.pack("<2sIHHI", bfType, bfSize, 0, 0, bfOffBits)
|
|
47
|
|
|
48
|
biSize = 40
|
|
49
|
biWidth = width
|
|
50
|
biHeight = height
|
|
51
|
biPlanes = 1
|
|
52
|
biBitCount = BPP
|
|
53
|
biCompression = 0
|
|
54
|
biSizeImage = len(pixel_array)
|
|
55
|
biXPelsPerMeter = 2835
|
|
56
|
biYPelsPerMeter = 2835
|
|
57
|
biClrUsed = 256
|
|
58
|
biClrImportant = 0
|
|
59
|
|
|
60
|
info_header = struct.pack(
|
|
61
|
"<IIIHHIIIIII",
|
|
62
|
biSize, biWidth, biHeight, biPlanes, biBitCount,
|
|
63
|
biCompression, biSizeImage,
|
|
64
|
biXPelsPerMeter, biYPelsPerMeter,
|
|
65
|
biClrUsed, biClrImportant
|
|
66
|
)
|
|
67
|
|
|
68
|
return file_header + info_header + palette_bgr0 + pixel_array
|
|
69
|
|
|
70
|
def make_palette() -> bytes:
|
|
71
|
"""
|
|
72
|
Create a simple 256-color palette (B,G,R,0).
|
|
73
|
We'll do a grayscale ramp, but reserve a few indices for accent colors.
|
|
74
|
"""
|
|
75
|
pal = bytearray()
|
|
76
|
for i in range(256):
|
|
77
|
|
|
78
|
r = g = b = i
|
|
79
|
pal += bytes([b, g, r, 0])
|
|
80
|
|
|
81
|
|
|
82
|
accents = {
|
|
83
|
250: (255, 0, 0),
|
|
84
|
251: (0, 255, 0),
|
|
85
|
252: (0, 0, 255),
|
|
86
|
253: (255, 255, 0),
|
|
87
|
254: (255, 0, 255),
|
|
88
|
255: (0, 255, 255),
|
|
89
|
}
|
|
90
|
for idx, (r, g, b) in accents.items():
|
|
91
|
off = idx * 4
|
|
92
|
pal[off:off+4] = bytes([b, g, r, 0])
|
|
93
|
|
|
94
|
return bytes(pal)
|
|
95
|
|
|
96
|
def generate_pixels(i: int, width: int, height: int) -> bytes:
|
|
97
|
"""
|
|
98
|
Create a distinct 16x16 pattern per index using simple math:
|
|
99
|
- background is grayscale gradient
|
|
100
|
- a diagonal + border use accent colors depending on i
|
|
101
|
"""
|
|
102
|
accent_idx = 250 + (i % 6)
|
|
103
|
|
|
104
|
buf = bytearray(width * height)
|
|
105
|
for y in range(height):
|
|
106
|
for x in range(width):
|
|
107
|
|
|
108
|
base = (x * 16 + y * 8 + i * 3) & 0xFF
|
|
109
|
|
|
110
|
|
|
111
|
if x == 0 or y == 0 or x == width - 1 or y == height - 1:
|
|
112
|
val = accent_idx
|
|
113
|
|
|
114
|
elif x == y or x == (width - 1 - y):
|
|
115
|
val = accent_idx
|
|
116
|
|
|
117
|
elif 2 <= x <= 5 and 2 <= y <= 5:
|
|
118
|
val = (i * 7 + x * 13 + y * 17) & 0xFF
|
|
119
|
else:
|
|
120
|
val = base
|
|
121
|
|
|
122
|
buf[y * width + x] = val
|
|
123
|
|
|
124
|
return bytes(buf)
|
|
125
|
|
|
126
|
def main():
|
|
127
|
OUT_DIR.mkdir(parents=True, exist_ok=True)
|
|
128
|
palette = make_palette()
|
|
129
|
|
|
130
|
sizes = []
|
|
131
|
for i in range(1, NUM_IMAGES + 1):
|
|
132
|
pixels = generate_pixels(i, WIDTH, HEIGHT)
|
|
133
|
bmp = bmp_bytes_indexed_8bit(WIDTH, HEIGHT, pixels, palette)
|
|
134
|
fname = OUT_DIR / f"img{i:03d}.bmp"
|
|
135
|
fname.write_bytes(bmp)
|
|
136
|
sizes.append((fname.name, len(bmp)))
|
|
137
|
|
|
138
|
|
|
139
|
min_s = min(s for _, s in sizes)
|
|
140
|
max_s = max(s for _, s in sizes)
|
|
141
|
print(f"Generated {NUM_IMAGES} BMPs in: {OUT_DIR}")
|
|
142
|
print(f"Size range: {min_s} .. {max_s} bytes")
|
|
143
|
print("First 5 sizes:", sizes[:5])
|
|
144
|
|
|
145
|
if __name__ == "__main__":
|
|
146
|
main()
|