bmp-resize-python/main.py

458 lines
16 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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)
sys.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 [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 最邻近插值
mode = 0
flur = 0
angle = 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)
################## 数据输入 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 ##################
# unpack
def byteSize(begin, length):
size = 0
for i in range(length):
size += imgBytes[i + begin] * 16**(2 * i)
return size
# pack
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] # 调色板
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)):
index = dataStart + currentRow * rowLength + currentCol
if (bpp == 32):
pixels[currentRow].append({
'r': imgBytes[index],
'g': imgBytes[index + 1],
'b': imgBytes[index + 2],
'a': imgBytes[index + 3]
})
currentCol += bpp // 8
else:
pixels[currentRow].append({
'r': imgBytes[index],
'g': imgBytes[index + 1],
'b': imgBytes[index + 2],
'a': 0
})
currentCol += bpp // 8
# 读入图片为像素数组完成
################## 像素点读入 End ##################
################## 缩放图片 Start ##################
# 变量预处理
if (angle != 0):
new_width = width
new_height = height
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]
if (angle in [0, 180]):
rowLength = floor(new_width * bpp / 8)
while (rowLength % 4 != 0 or rowLength == 0):
rowLength += 1
new_fileSize = 54 + rowLength * new_height # 文件头中的文件大小
new_imageSize = rowLength * new_height
else:
rowLength = floor(new_height * bpp / 8)
while (rowLength % 4 != 0 or rowLength == 0):
rowLength += 1
new_fileSize = 54 + rowLength * new_width # 文件头中的文件大小
new_imageSize = rowLength * new_width
# 新文件头
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)
new_height_b = sizeByte(new_height, 4)
if (angle in [0, 180]):
newimgArray.extend(new_width_b)
newimgArray.extend(new_height_b)
else:
newimgArray.extend(new_height_b)
newimgArray.extend(new_width_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))
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
newimgArray.extend(new_colorsBoard)
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]
if (angle in [0, 180]):
for i in tqdm(range(len(newpixels)), "将像素点格式化中"):
row = newpixels[i]
for col in range(len(row)):
if (1 == flur):
newimgArray.extend(flur(i, col))
else:
if (angle == 0):
pixel = [
newpixels[i][col]['r'], newpixels[i][col]['g'],
newpixels[i][col]['b']
]
if (new_bpp == 32):
pixel.append(newpixels[i][col]['a'])
elif (angle == 180):
pixel = [
newpixels[len(newpixels) - i - 1][len(row) - col -
1]['r'],
newpixels[len(newpixels) - i - 1][len(row) - col -
1]['g'],
newpixels[len(newpixels) - i - 1][len(row) - col -
1]['b']
]
if (new_bpp == 32):
pixel.append(newpixels[len(newpixels) - i -
1][len(row) - col - 1]['a'])
newimgArray.extend(pixel)
newimgArray.extend(sizeByte(0, rowLength - len(row) * (new_bpp // 8)))
else:
for i in tqdm(range(len(newpixels[0])), "将像素点格式化中"):
for j in range(len(newpixels) - 1, -1, -1):
if (angle == 270):
pixel = [
newpixels[j][i]['r'], newpixels[j][i]['g'],
newpixels[j][i]['b']
]
if (new_bpp == 32):
pixel.append(newpixels[j][i]['a'])
elif (angle == 90):
pixel = [
newpixels[len(newpixels) - j - 1][len(newpixels[0]) - i -
1]['r'],
newpixels[len(newpixels) - j - 1][len(newpixels[0]) - i -
1]['g'],
newpixels[len(newpixels) - j - 1][len(newpixels[0]) - i -
1]['b']
]
if (new_bpp == 32):
pixel.append(newpixels[len(newpixels) - j -
1][len(newpixels[0]) - i - 1]['a'])
newimgArray.extend(pixel)
newimgArray.extend(
sizeByte(0, rowLength - len(newpixels) * (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
sys.exit(0)
################## 写出文件 End ##################