458 lines
16 KiB
Python
458 lines
16 KiB
Python
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 ##################
|