如何用Python标准库实现PNG/GIF与二进制文件转换及图像操作?
解决思路与代码实现
你的核心错误是直接修改了PNG文件的整体二进制比特,这会破坏PNG的格式结构(文件头、压缩块、校验信息等),导致无法还原为正常图像。正确的逻辑是解析出图像的原始像素数据,操作像素后重新编码为PNG,以下是基于Python标准库的完整实现:
关键步骤说明
- 解析PNG文件:提取图像的宽、高、颜色格式等元信息,解压出原始像素数据
- 操作像素:针对像素数据执行黑白翻转等图像操作
- 保存/读取二进制文件:将原始/修改后的像素数据存入.bin/.dat文件
- 重新生成PNG:将修改后的像素数据按照PNG格式规范重新编码
完整代码实现
import struct import zlib def parse_png(file_path): with open(file_path, 'rb') as f: # 验证PNG文件头 header = f.read(8) assert header == b'\x89PNG\r\n\x1a\n', "输入文件不是PNG格式" ihdr_info = None idat_raw = b'' while True: # 读取块长度(大端4字节) len_bytes = f.read(4) if not len_bytes: break chunk_len = struct.unpack('!I', len_bytes)[0] # 读取块类型 chunk_type = f.read(4) # 读取块数据 chunk_data = f.read(chunk_len) # 跳过CRC校验(练习阶段简化处理) f.read(4) if chunk_type == b'IHDR': # 解析图像元信息:宽、高、位深度、颜色类型等 width, height, bit_depth, color_type, _, _, _ = struct.unpack('!IIBBBBB', chunk_data) ihdr_info = (width, height, bit_depth, color_type) elif chunk_type == b'IDAT': # 合并所有IDAT块的压缩数据 idat_raw += chunk_data elif chunk_type == b'IEND': break assert ihdr_info is not None, "未找到PNG图像头块" width, height, bit_depth, color_type = ihdr_info # 解压像素数据(包含每行的过滤字节) decompressed_data = zlib.decompress(idat_raw) # 去除过滤字节,提取纯像素数据 pixel_data = b'' bytes_per_pixel = (bit_depth // 8) * (3 if color_type == 2 else 1) # 支持灰度/简单RGB line_stride = bytes_per_pixel * width for i in range(height): # 跳过每行开头的过滤类型字节 pixel_data += decompressed_data[i*(line_stride+1)+1 : (i+1)*(line_stride+1)] return width, height, bit_depth, color_type, pixel_data def flip_gray_pixels(pixel_data): # 8位灰度图黑白翻转:255 - 每个像素的灰度值 return bytearray([255 - b for b in pixel_data]) def flip_rgb_pixels(pixel_data): # RGB图黑白翻转:每个颜色通道都取反 flipped = bytearray() for i in range(0, len(pixel_data), 3): flipped.append(255 - pixel_data[i]) flipped.append(255 - pixel_data[i+1]) flipped.append(255 - pixel_data[i+2]) return flipped def build_png(width, height, bit_depth, color_type, pixel_data): # 重新添加过滤字节(使用0型无过滤) bytes_per_pixel = (bit_depth // 8) * (3 if color_type == 2 else 1) line_stride = bytes_per_pixel * width filtered_data = b'' for i in range(height): filtered_data += b'\x00' # 过滤类型0 filtered_data += pixel_data[i*line_stride : (i+1)*line_stride] # 压缩处理后的像素数据 compressed_data = zlib.compress(filtered_data, level=6) # 组装PNG文件 png_content = b'\x89PNG\r\n\x1a\n' # 添加IHDR块 ihdr_struct = struct.pack('!IIBBBBB', width, height, bit_depth, color_type, 0, 0, 0) ihdr_len = struct.pack('!I', len(ihdr_struct)) ihdr_crc = struct.pack('!I', zlib.crc32(b'IHDR' + ihdr_struct)) png_content += ihdr_len + b'IHDR' + ihdr_struct + ihdr_crc # 添加IDAT块(简化为单个块) idat_len = struct.pack('!I', len(compressed_data)) idat_crc = struct.pack('!I', zlib.crc32(b'IDAT' + compressed_data)) png_content += idat_len + b'IDAT' + compressed_data + idat_crc # 添加IEND块 iend_len = struct.pack('!I', 0) iend_crc = struct.pack('!I', zlib.crc32(b'IEND')) png_content += iend_len + b'IEND' + b'' + iend_crc return png_content # 主执行流程 if __name__ == '__main__': # 1. 解析原始PNG width, height, bit_depth, color_type, raw_pixels = parse_png('984269812_1.png') # 2. 保存原始像素到.bin文件 with open('original_pixels.bin', 'wb') as f: f.write(raw_pixels) # 3. 执行黑白翻转(根据图像类型选择对应函数) if color_type == 0 and bit_depth == 8: flipped_pixels = flip_gray_pixels(raw_pixels) elif color_type == 2 and bit_depth == 24: flipped_pixels = flip_rgb_pixels(raw_pixels) else: raise ValueError("当前仅支持8位灰度图或24位RGB图") # 4. 保存修改后的像素到.dat文件 with open('flipped_pixels.dat', 'wb') as f: f.write(flipped_pixels) # 5. 从.dat读取像素并生成翻转后的PNG with open('flipped_pixels.dat', 'rb') as f: flipped_pixels = f.read() new_png = build_png(width, height, bit_depth, color_type, flipped_pixels) with open('flipped_image.png', 'wb') as f: f.write(new_png)
代码说明
- 仅依赖Python标准库:
struct用于解析二进制结构,zlib处理PNG的压缩/解压 - 支持8位灰度图和24位RGB图的黑白翻转,如需处理二值图(1位),可修改翻转逻辑为
bytearray([0xff ^ b for b in pixel_data]) - .bin/.dat文件存储的是原始像素数据(非PNG压缩数据),符合你理解图像与二进制关系的练习目的
内容的提问来源于stack exchange,提问作者The Pointer
相关产品推荐
相关产品推荐

