【问题标题】:bmp aspect ratio issuebmp长宽比问题
【发布时间】:2021-07-01 15:53:28
【问题描述】:

我一直在尝试了解 bmp 文件的工作原理,以便我可以渲染一些 Mandelbrot 集图片并将它们输出为 bmp 文件,因为这似乎是最简单的方法之一,但由于某种原因,当我使用't 1:1 即使它是 4 的幂(所以不需要填充) 我得到像这些 200:100 48:100 这样奇怪的伪影我想要做的是把一个像素数组变成白色偶数和奇数黑色成 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))

【问题讨论】:

  • for X = 0, ResolutionX - 1 do 必须是内循环。
  • 注意BMP中Y轴从底部开始(可以倒数),不是英文阅读顺序
  • 我想我应该更加关注它现在工作的循环感谢@EgorSkriptunoff
  • @HAX 你也缺少字节对齐,每行的字节数应该是4的倍数。在这种情况下,如果宽度不是4的倍数,可能会出现问题.稍后我会发布更完整的答案。
  • @Real 我已经解决了,在我让循环正常工作后,我只是在循环之前添加了Padding = 0 if (ResolutionX*3)%4 > 0 then Padding = 4 - (ResolutionX*3)%4 end ,并在 X 循环之后添加了Output[#Output+1] = rep(char(0),Padding)

标签: lua bmp


【解决方案1】:

好的,这是 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 提供代码。

【讨论】:

    【解决方案2】:

    欢迎来到 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(行)。

    【讨论】:

    • 浏览器似乎不支持 ppm。
    • 虽然这有很大帮助 bmp 的传播范围更广,渲染图像并不是我想学习如何处理 bmp 文件的唯一原因,但我可以使用其他程序将 ppm 转换为bmp /png 就像你说的那样,但是基于 bmp 的解决方案会有所帮助,因为我主要是为了了解 bmp 文件。
    猜你喜欢
    • 2011-04-28
    • 1970-01-01
    • 1970-01-01
    • 2019-12-24
    • 1970-01-01
    • 2013-04-06
    • 2015-12-21
    • 2016-03-03
    • 1970-01-01
    相关资源
    最近更新 更多