How to read images? (height maps)

hello, so i’ve been wondering is there a way to read images and return the color value of a pixel?

1 Like

What kinds of images? And how do you want to get them into the game?

upload them to roblox, and im talking about height maps.

1 Like

There might be some way of getting them with a HTTP request using the asset API https://api.roblox.com/docs#Assets

Haven’t used it so can’t tell you how.

Is this for letting players upload height maps for a game?

i want to create the earth (this sounds weird lmfao)

Would it be fine if you can only “get images into the game” through Studio? I.e. get the heightmap into a script running in Studio and generating the mesh from that?

https://developer.roblox.com/en-us/api-reference/function/StudioService/PromptImportFile

how do i read the height map though

1 Like

Step 1 is to convert it to a simpler image format so you don’t have to decode PNG compression and other complicated stuff. I recommend a simple bitmap (.bmp), or even better raw rgb data (.data). Just open your image in GIMP or whatever and export as raw (.data). You should get an option between standard and planar, definitely go for planar (RRRGGGBBB instead of (RGBRGBRGB) if your heightmap is greyscale because that way you can just take the first 1/3rd of the data instead of having to get every 3rd byte of the data.

With this test image

image

I get this binary content:

image

Which corresponds to first a black (0% white) pixel, then a 50% white pixel, then a bunch of 100% white pixels (the image keeps going with some random drawing I did, that’s why it’s not ff’s all the way but also e.g. fc, f0, etc.). This is just to show that this way of exporting works as expected.

When you call File:GetBinaryContents you get a string, which you need to decode into numbers. Do yourself a favor and use 24 bit color depth = 1 byte per color channel, that way each character corresponds to the color value of exactly one pixel. You can turn a 1-character string into the corresponding number value by calling string.byte on it.

Hold on I gotta get to my windows computer to type the rest of this post xD

Here’s a sequence of commands you can type in the Command Bar in Studio to get hold of the image data:

imageFile = game:GetService("StudioService"):PromptImportFile({"data"})
imageString = imageFile:GetBinaryContents()
imageData = {} for i = 1, #imageString do imageData[i] = imageString:sub(i, i):byte() end

Since raw image files don’t have any metadata, there’s no way to see from the file what the dimensions (width and height) of the image are, but if you know ahead of time then that’s not an issue.

Aaaand here’s how you could use the imageData to generate part terrain from the height map:

image

image

for y = 1, 16 do 
    for x = 1, 16 do 
        local i = (y - 1) * 16 + x
        local p = game.Workspace.Part:Clone()
        p.CFrame = CFrame.new(x, imageData[i]/256, y)
        p.Color = Color3.fromHSV(.33, .75, imageData[i]/255)
        if imageData[i] == 0 then 
            p.Color = Color3.fromRGB(0, 0, 255) 
        end 
        p.Parent = game.Workspace.DaParts  
    end 
end

PromptImportFile has a size limit of 100 MB, so you can only do about 5773x5773 images this way. If you want bigger images you’ll have to split them up and import the pieces separately.

5 Likes

im starting to understand, first i need to upload a .data or .bmp file by doing game:GetService('StudioService'):PromptImportFile() in the command bar

1 Like

It doesn’t actually upload the file anywhere, it just lets you manipulate the file in Studio. I updated my previous comment, it goes over the whole process

Actually, if you’re using GIMP you can set your image to only be greyscale so you don’t waste bytes by having all three channels and instead just have a single greyscale channel:

image

The Export dialog still asks you if you want normal or planar mode which doesn’t make sense when you have only 1 channel. It still worked though, with a 2x2 test image the exported file is only 4 bytes, exactly as expected. That means you can do 10000x10000 images!

EDIT: Oh, and the images of course don’t need to be square, you could do a 10 million times 1 pixel image if you wanted or anything in between, just multiply the dimensions to make sure you get a number less than or equal to 10 million.

EDIT 2: My Studio crashes at around 500x500 :c

Found a map of Europe (9000x6705)

A tiny region of the alps (200x200):

image

Generated using


imageFile = game:GetService("StudioService"):PromptImportFile({"data"})
imageString = imageFile:GetBinaryContents()
game.Workspace.DaParts:ClearAllChildren()
stopIt = false --Type stopIt = true in the Command Bar to stop generating
local w, h = 9000, 6705 --Heightmap dimensions
local x0, y0 = 4489, 3299 --Starting coordinates
local sx, sy = 200, 200 --Generated size, Studio crashes for me around 500, 500
for y = y0, y0 + sx do -- 
    if stopIt then break end
    wait()
    for x = x0, x0 + sy do 
        local i = (y - 1) * w + x --(x, y) coordinate to offset into file
        local height = imageString:sub(i, i):byte() / 255 --Convert to the range [0, 1]
        
        local p = game.Workspace.Part:Clone()
        p.CFrame = CFrame.new(x - x0, height * 50, y - y0)
        p.Color = Color3.fromHSV(.33, 1 - height, 0.9 - height)
        if height == 0 then 
            p.Color = Color3.fromRGB(0, 0, 255) 
        end 
        p.Parent = game.Workspace.DaParts  
    end 
end
6 Likes