diff options
author | 2022-11-13 23:46:45 +0530 | |
---|---|---|
committer | 2022-11-13 23:46:45 +0530 | |
commit | 9468226a9e2e2ab8cdd599f1d8538e860ca86120 (patch) | |
tree | 0a77ada226d6db80639f96b438bf83e4e756edb5 /env/lib/python3.10/site-packages/pikepdf/models/_transcoding.py | |
download | idcard-9468226a9e2e2ab8cdd599f1d8538e860ca86120.tar.gz idcard-9468226a9e2e2ab8cdd599f1d8538e860ca86120.tar.bz2 idcard-9468226a9e2e2ab8cdd599f1d8538e860ca86120.zip |
id card generator
Diffstat (limited to 'env/lib/python3.10/site-packages/pikepdf/models/_transcoding.py')
-rw-r--r-- | env/lib/python3.10/site-packages/pikepdf/models/_transcoding.py | 243 |
1 files changed, 243 insertions, 0 deletions
diff --git a/env/lib/python3.10/site-packages/pikepdf/models/_transcoding.py b/env/lib/python3.10/site-packages/pikepdf/models/_transcoding.py new file mode 100644 index 0000000..e54facf --- /dev/null +++ b/env/lib/python3.10/site-packages/pikepdf/models/_transcoding.py @@ -0,0 +1,243 @@ +# SPDX-FileCopyrightText: 2022 James R. Barlow +# SPDX-License-Identifier: MPL-2.0 + +from __future__ import annotations + +import struct +from typing import Any, Callable, NamedTuple, Union + +from PIL import Image +from PIL.TiffTags import TAGS_V2 as TIFF_TAGS + +BytesLike = Union[bytes, memoryview] +MutableBytesLike = Union[bytearray, memoryview] + + +def _next_multiple(n: int, k: int) -> int: + """Return the multiple of k that is greater than or equal n. + + >>> _next_multiple(101, 4) + 104 + >>> _next_multiple(100, 4) + 100 + """ + div, mod = divmod(n, k) + if mod > 0: + div += 1 + return div * k + + +def unpack_subbyte_pixels( + packed: BytesLike, size: tuple[int, int], bits: int, scale: int = 0 +) -> tuple[BytesLike, int]: + """Unpack subbyte *bits* pixels into full bytes and rescale. + + When scale is 0, the appropriate scale is calculated. + e.g. for 2-bit, the scale is adjusted so that + 0b00 = 0.00 = 0x00 + 0b01 = 0.33 = 0x55 + 0b10 = 0.66 = 0xaa + 0b11 = 1.00 = 0xff + When scale is 1, no scaling is applied, appropriate when + the bytes are palette indexes. + """ + width, height = size + bits_per_byte = 8 // bits + stride = _next_multiple(width, bits_per_byte) + buffer = bytearray(bits_per_byte * stride * height) + max_read = len(buffer) // bits_per_byte + if scale == 0: + scale = 255 / ((2**bits) - 1) + if bits == 4: + _4bit_inner_loop(packed[:max_read], buffer, scale) + elif bits == 2: + _2bit_inner_loop(packed[:max_read], buffer, scale) + # elif bits == 1: + # _1bit_inner_loop(packed[:max_read], buffer, scale) + else: + raise NotImplementedError(bits) + return memoryview(buffer), stride + + +# def _1bit_inner_loop(in_: BytesLike, out: MutableBytesLike, scale: int) -> None: +# """Unpack 1-bit values to their 8-bit equivalents. + +# Thus *out* must be 8x at long as *in*. +# """ +# for n, val in enumerate(in_): +# out[8 * n + 0] = int((val >> 7) & 0b1) * scale +# out[8 * n + 1] = int((val >> 6) & 0b1) * scale +# out[8 * n + 2] = int((val >> 5) & 0b1) * scale +# out[8 * n + 3] = int((val >> 4) & 0b1) * scale +# out[8 * n + 4] = int((val >> 3) & 0b1) * scale +# out[8 * n + 5] = int((val >> 2) & 0b1) * scale +# out[8 * n + 6] = int((val >> 1) & 0b1) * scale +# out[8 * n + 7] = int((val >> 0) & 0b1) * scale + + +def _2bit_inner_loop(in_: BytesLike, out: MutableBytesLike, scale: int) -> None: + """Unpack 2-bit values to their 8-bit equivalents. + + Thus *out* must be 4x at long as *in*. + """ + for n, val in enumerate(in_): + out[4 * n] = int((val >> 6) * scale) + out[4 * n + 1] = int(((val >> 4) & 0b11) * scale) + out[4 * n + 2] = int(((val >> 2) & 0b11) * scale) + out[4 * n + 3] = int((val & 0b11) * scale) + + +def _4bit_inner_loop(in_: BytesLike, out: MutableBytesLike, scale: int) -> None: + """Unpack 4-bit values to their 8-bit equivalents. + + Thus *out* must be 2x at long as *in*. + """ + for n, val in enumerate(in_): + out[2 * n] = int((val >> 4) * scale) + out[2 * n + 1] = int((val & 0b1111) * scale) + + +def image_from_byte_buffer(buffer: BytesLike, size: tuple[int, int], stride: int): + """Use Pillow to create one-component image from a byte buffer. + + *stride* is the number of bytes per row, and is essential for packed bits + with odd image widths. + """ + ystep = 1 # image is top to bottom in memory + return Image.frombuffer('L', size, buffer, "raw", 'L', stride, ystep) + + +def _make_rgb_palette(gray_palette: bytes) -> bytes: + palette = b'' + for entry in gray_palette: + palette += bytes([entry]) * 3 + return palette + + +def _depalettize_cmyk(buffer: BytesLike, palette: BytesLike): + with memoryview(buffer) as mv: + output = bytearray(4 * len(mv)) + for n, pal_idx in enumerate(mv): + output[4 * n : 4 * (n + 1)] = palette[4 * pal_idx : 4 * (pal_idx + 1)] + return output + + +def image_from_buffer_and_palette( + buffer: BytesLike, + size: tuple[int, int], + stride: int, + base_mode: str, + palette: BytesLike, +) -> Image.Image: + """Construct an image from a byte buffer and apply the palette. + + 1/2/4-bit images must be unpacked (no scaling!) to byte buffers first, such + that every 8-bit integer is an index into the palette. + """ + # Reminder Pillow palette byte order unintentionally changed in 8.3.0 + # https://github.com/python-pillow/Pillow/issues/5595 + # 8.2.0: all aligned by channel (very nonstandard) + # 8.3.0: all channels for one color followed by the next color (e.g. RGBRGBRGB) + + if base_mode == 'RGB': + im = image_from_byte_buffer(buffer, size, stride) + im.putpalette(palette, rawmode=base_mode) + elif base_mode == 'L': + # Pillow does not fully support palettes with rawmode='L'. + # Convert to RGB palette. + gray_palette = _make_rgb_palette(palette) + im = image_from_byte_buffer(buffer, size, stride) + im.putpalette(gray_palette, rawmode='RGB') + elif base_mode == 'CMYK': + # Pillow does not support CMYK with palettes; convert manually + output = _depalettize_cmyk(buffer, palette) + im = Image.frombuffer('CMYK', size, data=output, decoder_name='raw') + else: + raise NotImplementedError(f'palette with {base_mode}') + return im + + +def fix_1bit_palette_image( + im: Image.Image, base_mode: str, palette: BytesLike +) -> Image.Image: + """Apply palettes to 1-bit images.""" + im = im.convert('P') + if base_mode == 'RGB' and len(palette) == 6: + # rgbrgb -> rgb000000...rgb + palette = palette[0:3] + (b'\x00\x00\x00' * (256 - 2)) + palette[3:6] + im.putpalette(palette, rawmode='RGB') + elif base_mode == 'L': + try: + im.putpalette(palette, rawmode='L') + except ValueError as e: + if 'unrecognized raw mode' in str(e): + rgb_palette = _make_rgb_palette(palette) + im.putpalette(rgb_palette, rawmode='RGB') + return im + + +def generate_ccitt_header( + size: tuple[int, int], + data_length: int, + ccitt_group: int, + photometry: int, + icc: bytes, +) -> bytes: + """Generate binary CCITT header for image with given parameters.""" + tiff_header_struct = '<' + '2s' + 'H' + 'L' + 'H' + + tag_keys = {tag.name: key for key, tag in TIFF_TAGS.items()} # type: ignore + ifd_struct = '<HHLL' + + class IFD(NamedTuple): + key: int + typecode: Any + count_: int + data: int | Callable[[], int | None] + + ifds: list[IFD] = [] + + def header_length(ifd_count) -> int: + return ( + struct.calcsize(tiff_header_struct) + + struct.calcsize(ifd_struct) * ifd_count + + 4 + ) + + def add_ifd(tag_name: str, data: int | Callable[[], int | None], count: int = 1): + key = tag_keys[tag_name] + typecode = TIFF_TAGS[key].type # type: ignore + ifds.append(IFD(key, typecode, count, data)) + + image_offset = None + width, height = size + add_ifd('ImageWidth', width) + add_ifd('ImageLength', height) + add_ifd('BitsPerSample', 1) + add_ifd('Compression', ccitt_group) + add_ifd('PhotometricInterpretation', int(photometry)) + add_ifd('StripOffsets', lambda: image_offset) + add_ifd('RowsPerStrip', height) + add_ifd('StripByteCounts', data_length) + + icc_offset = 0 + if icc: + add_ifd('ICCProfile', lambda: icc_offset, count=len(icc)) + + icc_offset = header_length(len(ifds)) + image_offset = icc_offset + len(icc) + + ifd_args = [(arg() if callable(arg) else arg) for ifd in ifds for arg in ifd] + tiff_header = struct.pack( + (tiff_header_struct + ifd_struct[1:] * len(ifds) + 'L'), + b'II', # Byte order indication: Little endian + 42, # Version number (always 42) + 8, # Offset to first IFD + len(ifds), # Number of tags in IFD + *ifd_args, + 0, # Last IFD + ) + + if icc: + tiff_header += icc + return tiff_header |