bmp纵横比问题

bmp aspect ratio issue

我一直在尝试了解 bmp 文件的工作原理,以便我可以渲染一些 Mandelbrot 集图片并将它们输出为 bmp 文件,因为这似乎是最简单的方法之一,但由于某些原因,当我使用纵横比时那不是 1:1,即使它是 4 的幂(所以不需要填充)我得到像这些奇怪的人工制品 200:100 48:100 what I'm trying to do is turning an array of pixels that has white for even numbers and black for odd numbers into a bmp, this (100:100) 是 1:1 宽高比的样子。 我已经尝试阅读维基百科文章,看看我是否能弄清楚我做错了什么,但我仍然不明白我错过了什么。

这是我目前在 Lua 中编写的脚本:

ResolutionX = 100
ResolutionY = 100

local cos, atan, sin, atan2, sqrt, floor = math.cos, math.atan, math.sin, math.atan2, math.sqrt, math.floor
local insert, concat = table.insert, table.concat
local sub, char, rep = string.sub, string.char, string.rep

io.output("Test.bmp")

function Basen(n,b)
    n = floor(n)
    if not b or b == 10 then return tostring(n) end
    local digits = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
    local t = {}
    repeat
        local d = (n % b) + 1
        n = floor(n / b)
        insert(t, 1, digits:sub(d,d))
    until n == 0
    return rep("0",32-#t)..concat(t,"")
end

FileSize = Basen(ResolutionY*ResolutionX*3 + 54,2)
FileSize4 = tonumber(sub(FileSize,1,8),2) or 0
FileSize3 = tonumber(sub(FileSize,9,16),2) or 0
FileSize2 = tonumber(sub(FileSize,17,24),2) or 0
FileSize1 = tonumber(sub(FileSize,25,32),2) or 0

Width = Basen(ResolutionX,2)
print("Width:   ",Width)
Width4 = tonumber(sub(Width,1,8),2) or 0
Width3 = tonumber(sub(Width,9,16),2) or 0
Width2 = tonumber(sub(Width,17,24),2) or 0
Width1 = tonumber(sub(Width,25,32),2) or 0

Height = Basen(ResolutionY,2)
print("Height: ",Height)
Height4 = tonumber(sub(Height,1,8),2) or 0
Height3 = tonumber(sub(Height,9,16),2) or 0
Height2 = tonumber(sub(Height,17,24),2) or 0
Height1 = tonumber(sub(Height,25,32),2) or 0

BMPSize = Basen(ResolutionY*ResolutionX*3,2)
BMPSize4 = tonumber(sub(BMPSize,1,8),2) or 0
BMPSize3 = tonumber(sub(BMPSize,9,16),2) or 0
BMPSize2 = tonumber(sub(BMPSize,17,24),2) or 0
BMPSize1 = tonumber(sub(BMPSize,25,32),2) or 0

print("TotalSize: ",FileSize1,FileSize2,FileSize3,FileSize4,"\nWidth:   ",Width1,Width2,Width3,Width4,"\nHeight: ",Height1,Height2,Height3,Height4,"\nImage data: ",BMPSize1,BMPSize2,BMPSize3,BMPSize4)

Result = {"BM"..char(                       --File type
    FileSize1,FileSize2,FileSize3,FileSize4,--File size
    0,0,0,0,                                --Reserved
    54,0,0,0,                               --Where the pixel data starts
    40,0,0,0,                               --DIB header
    Width1,Width2,Width3,Width4,            --Width
    Height1,Height2,Height3,Height4,        --Height
    1,0,                                    --Color planes
    24,00,                                  --Bit depth
    0,0,0,0,                                --Compression
    BMPSize1,BMPSize2,BMPSize3,BMPSize4,    --The amount of bytes pixel data will consume
    Width1,Width2,Width3,Width4,
    Height1,Height2,Height3,Height4,
    0,0,0,0,                                --Number of colors in palatte
    0,0,0,0
)}

for X = 0, ResolutionX - 1 do
    for Y = 0, ResolutionY - 1 do
        insert(Result,rep(char(255 * ((X + 1) % 2) * ((Y + 1) % 2)),3))
    end
end

io.write(table.concat(Result))

欢迎使用 Stack Exchange :)

我建议看一下 PPM 文件,它们很简单。可以使用其他工具将它们转换为 png 或 bmp。

Wikipedia PPM Specification

这是一个 PPM 解决方案:

ResolutionX = 48
ResolutionY = 100

local cos, atan, sin, atan2, sqrt, floor = math.cos, math.atan, math.sin, math.atan2, math.sqrt, math.floor
local insert, concat = table.insert, table.concat
local sub, char, rep = string.sub, string.char, string.rep

local file = io.open("Test.ppm","w")

-- PPM File headers
local Output = { }
Output[1] = "P3"
Output[2] = tostring(ResolutionX) .. " " .. tostring(ResolutionY)
Output[3] = "255"

-- Image body
for Y = 0, ResolutionY - 1 do
    for X = 0, ResolutionX - 1 do
        local value = 255 * ((X + 1) % 2) * ((Y + 1) % 2)
        Output[#Output+1] = rep(tostring(floor(value)),3," ")
    end
end

-- Join lines together
local Result = table.concat(Output,"\n")

file:write(Result)

注意: 我无法让您的代码写入文件(请参阅我对 file 的用法)。如果写入顺序为英文阅读顺序(左-右下-上),内循环必须是X(列),外循环必须是Y(行)。

好的,这是 BMP 版本。我已经把东西放在一个模块中,所以它可能更容易使用。

local writeBMP = {}

local floor = math.floor
local insert, concat = table.insert, table.concat
local sub, char, rep = string.sub, string.char, string.rep

local function Basen(n,b)
    n = floor(n)
    if not b or b == 10 then return tostring(n) end
    local digits = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
    local t = {}
    repeat
        local d = (n % b) + 1
        n = floor(n / b)
        insert(t, 1, digits:sub(d,d))
    until n == 0
    return rep("0",32-#t)..concat(t,"")
end

local function nextMul4(x)
    if ( x % 4 == 0 ) then
        return x
    else
        return x+4-(x%4)
    end
end

local function clamp(x)
    local y = x
    if ( x > 255 ) then
        y = 255
    elseif ( x < 0 ) then
        y = 0
    end
    return floor(y)
end

-- Accepts array of type pixelsXYC[X][Y][C] of numbers 0-255
-- C=1,2,3 are the red, green and blue channels respectively
-- X increases left to right, and Y increases top to bottom
function writeBMP.data(pixelsXYC, resolutionX, resolutionY)
    local Pixels = pixelsXYC
    local ResolutionX = resolutionX
    local ResolutionY = resolutionY
    assert(#Pixels == ResolutionX, "Table size and X resolution mismatch")
    assert(#Pixels[1] == ResolutionY, "Table size and Y resolution mismatch")

    local FileSize = Basen(ResolutionY*nextMul4(3*ResolutionX) + 54,2)
    local FileSize4 = tonumber(sub(FileSize,1,8),2) or 0
    local FileSize3 = tonumber(sub(FileSize,9,16),2) or 0
    local FileSize2 = tonumber(sub(FileSize,17,24),2) or 0
    local FileSize1 = tonumber(sub(FileSize,25,32),2) or 0

    local Width = Basen(ResolutionX,2)
    local Width4 = tonumber(sub(Width,1,8),2) or 0
    local Width3 = tonumber(sub(Width,9,16),2) or 0
    local Width2 = tonumber(sub(Width,17,24),2) or 0
    local Width1 = tonumber(sub(Width,25,32),2) or 0

    local Height = Basen(ResolutionY,2)
    local Height4 = tonumber(sub(Height,1,8),2) or 0
    local Height3 = tonumber(sub(Height,9,16),2) or 0
    local Height2 = tonumber(sub(Height,17,24),2) or 0
    local Height1 = tonumber(sub(Height,25,32),2) or 0

    local BMPSize = Basen(ResolutionY*nextMul4(3*ResolutionX),2)
    local BMPSize4 = tonumber(sub(BMPSize,1,8),2) or 0
    local BMPSize3 = tonumber(sub(BMPSize,9,16),2) or 0
    local BMPSize2 = tonumber(sub(BMPSize,17,24),2) or 0
    local BMPSize1 = tonumber(sub(BMPSize,25,32),2) or 0

    local Result = {}
    Result[1] = "BM" .. char(                   --File type 
        FileSize1,FileSize2,FileSize3,FileSize4,--File size
        0,0,                                    --Reserved
        0,0,                                    --Reserved
        54,0,0,0,                               --Where the pixel data starts
        40,0,0,0,                               --DIB header
        Width1,Width2,Width3,Width4,            --Width
        Height1,Height2,Height3,Height4,        --Height
        1,0,                                    --Color planes
        24,0,                                   --Bit depth
        0,0,0,0,                                --Compression
        BMPSize1,BMPSize2,BMPSize3,BMPSize4,    --The amount of bytes pixel data will consume
        37,22,0,0,                              --Pixels per meter horizontal
        37,22,0,0,                              --Pixels per meter vertical
        0,0,0,0,                                --Number of colors in palatte
        0,0,0,0
    )

    local Y = ResolutionY
    while( Y >= 1 ) do
        for X = 1, ResolutionX do
            local r = clamp( Pixels[X][Y][1] )
            local g = clamp( Pixels[X][Y][2] )
            local b = clamp( Pixels[X][Y][3] )
            Result[#Result+1] = char(b)
            Result[#Result+1] = char(g)
            Result[#Result+1] = char(r)
        end
        -- byte alignment
        if ( ( (3*ResolutionX) % 4 ) ~= 0 ) then
            local Padding = 4 - ((3*ResolutionX) % 4)
            Result[#Result+1] = rep(char(0),Padding)
        end
        Y = Y - 1
    end

    return table.concat(Result)
end

function writeBMP.write(pixelsXYC, resolutionX, resolutionY, filename)
    local file = io.open(filename,"wb")
    local data = writeBMP.data(pixelsXYC, resolutionX, resolutionY)
    file:write(data)
end

return writeBMP

一个简单的测试:

-- writeBMP example
local writeBMP = require "writeBMP"

local resolutionX = 100
local resolutionY = 100
-- Pixel data
local pixels = {}
for x=1,resolutionX do
    pixels[x] = {}
    for y=1, resolutionY do
        pixels[x][y] = {}
        local red = 255*(resolutionX-x+resolutionY-y)/(resolutionX+resolutionY)
        local green = 255*y/resolutionY
        local blue = 255*x/resolutionX
        pixels[x][y][1] = red
        pixels[x][y][2] = green
        pixels[x][y][3] = blue
    end
end

writeBMP.write(pixels,resolutionX,resolutionY,"testwritebmp.bmp")
return

注意:在BMP中,Y轴从底部开始。我比较习惯计算机图形中的Y轴从上到下(所以我这样写)。

感谢 HAX 提供代码。