from math import floor, ceil from tqdm import tqdm import sys import os def error(code): print("[error:" + str(code) + "]", end="", file=sys.stderr) if (code == 1): print('参数数量错误', file=sys.stderr) elif (code == 2): print('文件无法读入,请检查你的图片路径', file=sys.stderr) elif (code == 3): print('输出路径无法写出,请检查你的路径', file=sys.stderr) elif (code == 4): print('参数有误,缩放请使用 __*__,旋转请使用 90/180/270', file=sys.stderr) elif (code == 5): print('缩放参数错误,新高/宽必须大于 0', file=sys.stderr) elif (code == 6): print('指定文件不是BMP格式,无法读入', file=sys.stderr) elif (code == 7): print('暂不支持此图片,目前仅支持 bpp=24/32 的文件', file=sys.stderr) exit(code) def warn(code): print("[warn:" + str(code) + "]", end="") if (code == 1): print('缩放模式异常,已重置为双线性插值模式') elif (code == 2): print('附加功能异常,已恢复默认值') else: print('未知错误') ################## 数据输入 Start ################## if (len(sys.argv) not in [3, 4, 5, 6]): error(1) # 输入数据 # img = input("请输入图片路径:") # new_width = int(input("请输入新宽度(px):")) # new_height = int(input("请输入新高度(px):")) # print("模式 0 双线性插值") # print("模式 1 最邻近插值") # print("模式 2 高斯模糊缩放") # mode = int(input("请输入模式:")) # img = 'imgs/text.bmp' # new_width = 2560 * 3 # new_height = 2560 # mode = 0 input_file = sys.argv[1] output_file = sys.argv[2] new = sys.argv[3] # 模式 0 双线性插值 # 模式 1 最邻近插值 # 模式 2 旋转图片 mode = 0 flur = 0 if ('*' in new): new_width, new_height = map(int, sys.argv[3].split('*')) if (new_width <= 0 or new_height <= 0): error(5) if (len(sys.argv) >= 5): mode = int(sys.argv[4]) if (mode not in [0, 1]): warn(1) mode = 0 if (len(sys.argv) >= 6): flur = int(sys.argv[5]) if (flur not in [0, 1]): warn(2) flur = 0 else: mode = 0 else: angle = int(new) if (angle not in [90, 180, 270]): error(4) mode = 2 ################## 数据输入 End ################## ################## 文件读入 Start ################## if (not os.path.exists(input_file) or not os.access(input_file, os.R_OK)): error(2) if (not os.access(output_file, os.W_OK) and os.access(output_file, os.F_OK)): error(3) # 读入图片 with open(input_file, 'rb') as f: try: imgBytes = f.read() except: error(2) for _ in tqdm(range(len(imgBytes)), "读入文件中"): pass ################## 文件读入 End ################## # 十六进制转十进制 def byteSize(begin, length): size = 0 for i in range(length): size += imgBytes[i + begin] * 16**(2 * i) return size # 十进制转十六进制 def sizeByte(size, length): b = [] while (size): b.append(size // 16**((length - len(b) - 1) * 2)) size %= 16**((length - len(b)) * 2) b.extend([0] * (length - len(b))) return b[::-1] ################## 文件头处理 Start ################## # 位图文件头 # 检验文件头是否为 BM if (imgBytes[0] != 66 or imgBytes[1] != 77): error(6) fileSize = byteSize(2, 4) #文件头中的文件大小 dataStart = byteSize(10, 4) #文件头中的数据开始字节 # bmp 文件头 headerSize = byteSize(14, 4) #该头结构的大小(40字节) width = byteSize(18, 4) #位图宽度,单位为像素(有符号整数) height = byteSize(22, 4) #位图高度,单位为像素(有符号整数) nbplan = byteSize(26, 2) #色彩平面数;只有1为有效值 bpp = byteSize(28, 2) #每个像素所占位数,即图像的色深。典型值为1、4、8、16、24和32 compression = byteSize(30, 4) #所使用的压缩方法,可取值见下表。 imageSize = byteSize(34, 4) #图像大小。指原始位图数据的大小(详见后文),与文件大小不是同一个概念。 wppm = byteSize(38, 4) #图像的横向分辨率,单位为像素每米(有符号整数) hppm = byteSize(42, 4) #图像的纵向分辨率,单位为像素每米(有符号整数) colorsNum = byteSize(46, 4) #调色板的颜色数,为0时表示颜色数为默认的2^色深个 icolorsNum = byteSize(50, 4) #重要颜色数,为0时表示所有颜色都是重要的;通常不使用本项 colorsBoard = imgBytes[54:dataStart] #调色板 colors = [] i = 0 while (i < len(colorsBoard)): colors.append({ 'r': colorsBoard[i], 'g': colorsBoard[i + 1], 'b': colorsBoard[i + 2], 'a': colorsBoard[i + 3], }) i += 4 if (not (bpp in [24, 32])): error(7) ################## 文件头处理 End ################## ################## 像素点读入 Start ################## # 变量预处理 pixels = [] for i in range(height): pixels.append([]) rowLength = floor(width * bpp / 8) while (rowLength % 4 != 0 or rowLength == 0): rowLength += 1 # colorsIndex = [] # if (bpp == 1): # initPixel = imgBytes[dataStart] // 128 # colorsIndex.append() # 计算像素点 i = dataStart for currentRow in tqdm(range(height), "读入像素点中"): currentCol = 0 while (currentCol < width * (bpp // 8)): if (bpp == 32): pixels[currentRow].append({ 'r': imgBytes[dataStart + currentRow * rowLength + currentCol], 'g': imgBytes[dataStart + currentRow * rowLength + currentCol + 1], 'b': imgBytes[dataStart + currentRow * rowLength + currentCol + 2], 'a': imgBytes[dataStart + currentRow * rowLength + currentCol + 3] }) currentCol += bpp // 8 else: pixels[currentRow].append({ 'r': imgBytes[dataStart + currentRow * rowLength + currentCol], 'g': imgBytes[dataStart + currentRow * rowLength + currentCol + 1], 'b': imgBytes[dataStart + currentRow * rowLength + currentCol + 2], 'a': 0 }) currentCol += bpp // 8 # 读入图片为像素数组完成 ################## 像素点读入 End ################## ################## 缩放图片 Start ################## # 变量预处理 scale_w = new_width / width scale_h = new_height / height newpixels = [] for i in range(new_height): newpixels.append([]) # 辅助计算函数 def linear_single(x, x1, x2, f1, f2): return floor(f1 * (x2 - x) / (x2 - x1) + f2 * (x - x1) / (x2 - x1)) # 双线性插值函数 # 参考 https://zh.wikipedia.org/wiki/%E5%8F%8C%E7%BA%BF%E6%80%A7%E6%8F%92%E5%80%BC def linear_insert(row, col): x = row / scale_h y = col / scale_w # 如果整数格点就直接返回 x1 = floor(x) x2 = ceil(x) y1 = floor(y) y2 = ceil(y) if (x2 >= height): x2 = x1 if (y2 >= width): y2 = y1 if (x1 == x2 and y1 == y2): return pixels[x1][y1] if (x1 == x2): x = x1 r = linear_single(y, y1, y2, pixels[x][y1]['r'], pixels[x][y2]['r']) g = linear_single(y, y1, y2, pixels[x][y1]['g'], pixels[x][y2]['g']) b = linear_single(y, y1, y2, pixels[x][y1]['b'], pixels[x][y2]['b']) a = linear_single(y, y1, y2, pixels[x][y1]['a'], pixels[x][y2]['a']) return {'r': r, 'g': g, 'b': b, 'a': a} if (y1 == y2): y = y1 r = linear_single(x, x1, x2, pixels[x1][y]['r'], pixels[x2][y]['r']) g = linear_single(x, x1, x2, pixels[x1][y]['g'], pixels[x2][y]['g']) b = linear_single(x, x1, x2, pixels[x1][y]['b'], pixels[x2][y]['b']) a = linear_single(x, x1, x2, pixels[x1][y]['a'], pixels[x2][y]['a']) return {'r': r, 'g': g, 'b': b, 'a': a} fy1_r = linear_single(x, x1, x2, pixels[x1][y1]['r'], pixels[x2][y1]['r']) fy1_g = linear_single(x, x1, x2, pixels[x1][y1]['g'], pixels[x2][y1]['g']) fy1_b = linear_single(x, x1, x2, pixels[x1][y1]['b'], pixels[x2][y1]['b']) fy1_a = linear_single(x, x1, x2, pixels[x1][y1]['a'], pixels[x2][y1]['a']) fy2_r = linear_single(x, x1, x2, pixels[x1][y2]['r'], pixels[x2][y2]['r']) fy2_g = linear_single(x, x1, x2, pixels[x1][y2]['g'], pixels[x2][y2]['g']) fy2_b = linear_single(x, x1, x2, pixels[x1][y2]['b'], pixels[x2][y2]['b']) fy2_a = linear_single(x, x1, x2, pixels[x1][y2]['a'], pixels[x2][y2]['a']) r = linear_single(y, y1, y2, fy1_r, fy2_r) g = linear_single(y, y1, y2, fy1_g, fy2_g) b = linear_single(y, y1, y2, fy1_b, fy2_b) a = linear_single(y, y1, y2, fy1_a, fy2_a) return {'r': r, 'g': g, 'b': b, 'a': a} # 计算新像素 for currentRow in tqdm(range(new_height), "计算新像素点中"): for currentCol in range(new_width): if (mode == 0): newpixels[currentRow].append(linear_insert(currentRow, currentCol)) elif (mode == 1): ori_row = floor(currentRow / scale_h) ori_col = floor(currentCol / scale_w) newpixels[currentRow].append(pixels[ori_row][ori_col]) ################## 缩放图片 End ################## ################## 字节计算 Start ################## # 处理新文件 newimgArray = [66, 77] rowLength = new_width rowLength *= bpp // 8 while (rowLength % 4 != 0): rowLength += 1 # 新文件头 new_fileSize = 54 + rowLength * new_height #文件头中的文件大小 newimgArray.extend(sizeByte(new_fileSize, 4)) newimgArray.extend(sizeByte(0, 4)) new_dataStart = dataStart newimgArray.extend(sizeByte(new_dataStart, 4)) # 新 bmp 文件头 new_headerSize = headerSize newimgArray.extend(sizeByte(headerSize, 4)) new_width_b = sizeByte(new_width, 4) newimgArray.extend(new_width_b) new_height_b = sizeByte(new_height, 4) newimgArray.extend(new_height_b) new_nbplan = nbplan newimgArray.extend(sizeByte(new_nbplan, 2)) new_bpp = bpp newimgArray.extend(sizeByte(bpp, 2)) new_compression = compression newimgArray.extend(sizeByte(new_compression, 4)) new_imageSize = rowLength * new_height newimgArray.extend(sizeByte(new_imageSize, 4)) new_wppm = wppm newimgArray.extend(sizeByte(new_wppm, 4)) new_hppm = hppm newimgArray.extend(sizeByte(new_hppm, 4)) new_colorsNum = colorsNum newimgArray.extend(sizeByte(new_colorsNum, 4)) new_icolorsNum = icolorsNum newimgArray.extend(sizeByte(new_icolorsNum, 4)) new_colorsBoard = colorsBoard for color in new_colorsBoard: newimgArray.append(color) def flur(row, col): if (row - 1 < 0): row = new_height + 1 if (row + 1 >= new_height): row = 0 if (col - 1 < 0): col = new_width + 1 if (col + 1 >= new_width): col = 0 p = [0.0947416, 0.118318, 0.147761] r = floor(newpixels[row - 1][col - 1]['r'] * p[0] + newpixels[row - 1][col + 1]['r'] * p[0] + newpixels[row + 1][col - 1]['r'] * p[0] + newpixels[row + 1][col + 1]['r'] * p[0] + newpixels[row][col + 1]['r'] * p[1] + newpixels[row][col - 1]['r'] * p[1] + newpixels[row - 1][col]['r'] * p[1] + newpixels[row + 1][col]['r'] * p[1] + newpixels[row][col]['r'] * p[2]) g = floor(newpixels[row - 1][col - 1]['g'] * p[0] + newpixels[row - 1][col + 1]['g'] * p[0] + newpixels[row + 1][col - 1]['g'] * p[0] + newpixels[row + 1][col + 1]['g'] * p[0] + newpixels[row][col + 1]['g'] * p[1] + newpixels[row][col - 1]['g'] * p[1] + newpixels[row - 1][col]['g'] * p[1] + newpixels[row + 1][col]['g'] * p[1] + newpixels[row][col]['g'] * p[2]) b = floor(newpixels[row - 1][col - 1]['b'] * p[0] + newpixels[row - 1][col + 1]['b'] * p[0] + newpixels[row + 1][col - 1]['b'] * p[0] + newpixels[row + 1][col + 1]['b'] * p[0] + newpixels[row][col + 1]['b'] * p[1] + newpixels[row][col - 1]['b'] * p[1] + newpixels[row - 1][col]['b'] * p[1] + newpixels[row + 1][col]['b'] * p[1] + newpixels[row][col]['b'] * p[2]) if (new_bpp // 8 == 3): return [r, g, b] else: a = floor(newpixels[row - 1][col - 1]['a'] * p[0] + newpixels[row - 1][col + 1]['a'] * p[0] + newpixels[row + 1][col - 1]['a'] * p[0] + newpixels[row + 1][col + 1]['a'] * p[0] + newpixels[row][col + 1]['a'] * p[1] + newpixels[row][col - 1]['a'] * p[1] + newpixels[row - 1][col]['a'] * p[1] + newpixels[row + 1][col]['a'] * p[1] + newpixels[row][col]['a'] * p[2]) return [r, g, b, a] for i in tqdm(range(len(newpixels)), "将像素点格式化中"): row = newpixels[i] for col in range(len(row)): if (1 == flur): print("?") newimgArray.extend(flur(i, col)) else: if (new_bpp // 8 == 3): pixel = [ newpixels[i][col]['r'], newpixels[i][col]['g'], newpixels[i][col]['b'] ] else: pixel = [ newpixels[i][col]['r'], newpixels[i][col]['g'], newpixels[i][col]['b'], newpixels[i][col]['a'] ] newimgArray.extend(pixel) newimgArray.extend(sizeByte(0, rowLength - len(row) * (new_bpp // 8))) ################## 字节计算 END ################## ################## 写出文件 Start ################## # 写入新的文件 newimgBytes = bytes(newimgArray) with open(output_file, 'wb') as f: f.write(newimgBytes) for _ in tqdm(range(len(newimgBytes)), "写出图片中"): pass ################## 写出文件 End ##################