| """PBC3 bitstream framing, separate from the image algorithm.""" |
|
|
| import numpy as np |
|
|
| import pbc3_ops as ops |
| from pbc3_types import BitReader, BitWriter |
|
|
|
|
| def write_grid(bw: BitWriter, values, bitcount: int) -> None: |
| """Writes a flat grid of palette indices.""" |
| acc = bw.acc |
| nbits = bw.nbits |
| data = bw.data |
| bitmask = (1 << bitcount) - 1 |
| for value in values: |
| acc = (acc << bitcount) | (int(value) & bitmask) |
| nbits += bitcount |
| while nbits >= 8: |
| shift = nbits - 8 |
| data.append((acc >> shift) & 255) |
| acc &= (1 << shift) - 1 |
| nbits = shift |
| bw.acc = acc |
| bw.nbits = nbits |
|
|
|
|
| def read_grid(br: BitReader, count: int, bitcount: int) -> np.ndarray: |
| """Reads a flat grid of palette indices.""" |
| values = np.empty(count, dtype=np.uint16) |
| data = br.data |
| data_len = len(data) |
| data_index = br.i |
| acc = br.acc |
| nbits = br.nbits |
| bitmask = (1 << bitcount) - 1 |
| for i in range(count): |
| while nbits < bitcount: |
| if data_index >= data_len: |
| raise EOFError("bitstream ended early") |
| acc = (acc << 8) | data[data_index] |
| data_index += 1 |
| nbits += 8 |
| shift = nbits - bitcount |
| values[i] = (acc >> shift) & bitmask |
| acc &= (1 << shift) - 1 |
| nbits = shift |
| br.i = data_index |
| br.acc = acc |
| br.nbits = nbits |
| return values |
|
|
|
|
| def write_header(bw, w, h, original_w, original_h, downsampled, color_id, channels, |
| channel_bits, positive_bias, has_alpha, patch_count, base_values, warmup=None): |
| """Writes the image-level stream header.""" |
| bw.write(int(downsampled), 1) |
| if downsampled: |
| bw.write(original_w, 16) |
| bw.write(original_h, 16) |
| bw.write(w, 16) |
| bw.write(h, 16) |
| bw.write(color_id, 2) |
| bw.write(channels, 8) |
| bw.write(channel_bits, 4) |
| bw.write(int(positive_bias), 1) |
| bw.write(int(has_alpha), 1) |
| bw.write(patch_count, 32) |
| for base in base_values: |
| bw.write(base, 8) |
| bw.write(int(warmup is not None), 1) |
| if warmup is not None: |
| warm_w, warm_h, warm_split = warmup |
| bw.write(warm_w, 16) |
| bw.write(warm_h, 16) |
| bw.write(warm_split, 32) |
|
|
|
|
| def read_header(br, color_space_names): |
| """Reads the image-level stream header.""" |
| downsampled = bool(br.read(1)) |
| original_w = br.read(16) if downsampled else None |
| original_h = br.read(16) if downsampled else None |
| w = br.read(16) |
| h = br.read(16) |
| color_id = br.read(2) |
| channels = br.read(8) |
| channel_bits = br.read(4) |
| positive_bias = bool(br.read(1)) |
| has_alpha = bool(br.read(1)) |
| patch_count = br.read(32) |
| base_values = [br.read(8) for _ in range(channels)] |
| warmup_on = bool(br.read(1)) |
| warm_w = warm_h = warmup_split = None |
| if warmup_on: |
| warm_w = br.read(16) |
| warm_h = br.read(16) |
| warmup_split = br.read(32) |
| return ( |
| downsampled, original_w, original_h, w, h, color_space_names[color_id], channels, |
| channel_bits, positive_bias, has_alpha, patch_count, base_values, warmup_on, warm_w, |
| warm_h, warmup_split, |
| ) |
|
|
|
|
| def write_patch(bw, patch, channel_bits: int) -> None: |
| """Writes one generated-palette patch.""" |
| bw.write(patch["channel"], channel_bits) |
| bw.write(patch["x"], 16) |
| bw.write(patch["y"], 16) |
| bw.write(patch["w"], 16) |
| bw.write(patch["h"], 16) |
| bw.write(0, 1) |
| mask = patch["mask"] |
| bw.write(len(mask), 10) |
| for bit in mask: |
| bw.write(bit, 1) |
| bw.write(patch["neg"], 8) |
| bw.write(patch["pos"], 8) |
| bw.write(patch["max_bitcount"], 4) |
| bw.write(patch["cell_size"], 16) |
| write_grid(bw, patch["indices"].ravel().astype(np.int64), patch["bitcount"]) |
|
|
|
|
| def read_patch(br, channel_bits: int, positive_bias: bool = True): |
| """Reads one generated-palette patch and returns its decoded values.""" |
| channel = br.read(channel_bits) |
| x, y = br.read(16), br.read(16) |
| w, h = br.read(16), br.read(16) |
| if br.read(1) != 0: |
| raise ValueError("explicit palette patches were removed in PBC3 3.0 release cleanup") |
| mask = [br.read(1) for _ in range(br.read(10))] |
| negative_max, positive_max = br.read(8), br.read(8) |
| max_bitcount = br.read(4) |
| bitcount = ops.resolve_palette_bitcount(mask, max_bitcount, negative_max, positive_max, positive_bias) |
| palette = ops.palette_generator(mask, max_bitcount, negative_max, positive_max, positive_bias) |
| cell_size = br.read(16) |
| gw, gh = ops.ceil_div(w, cell_size), ops.ceil_div(h, cell_size) |
| indices = read_grid(br, gh * gw, bitcount).reshape(gh, gw) |
| return channel, x, y, w, h, cell_size, palette[indices], bitcount |
|
|