I did a little more digging and discovered why some of the image dimensions were not working correctly. It turns out that we need to specify the alignment requirements for the start of each pixel row in memory.
I’ve quoted a relevant article below:
You create storage for a Texture and upload pixels to it with glTexImage2D (or similar functions, as appropriate to the type of texture). If your program crashes during the upload, or diagonal lines appear in the resulting image, this is because the alignment of each horizontal line of your pixel array is not multiple of 4. This typically happens to users loading an image that is of the RGB or BGR format (for example, 24 BPP images), depending on the source of your image data.
Example, your image width = 401 and height = 500. The height is irrelevant; what matters is the width. If we do the math, 401 pixels x 3 bytes = 1203, which is not divisible by 4. Some image file formats may inherently align each row to 4 bytes, but some do not. For those that don’t, each row will start exactly 1203 bytes from the start of the last. OpenGL’s row alignment can be changed to fit the row alignment for your image data. This is done by calling glPixelStorei(GL_UNPACK_ALIGNMENT, #), where # is the alignment you want. The default alignment is 4.
And if you are interested, most GPUs like chunks of 4 bytes. In other words, GL_RGBA or GL_BGRA is preferred when each component is a byte. GL_RGB and GL_BGR is considered bizarre since most GPUs, most CPUs and any other kind of chip don’t handle 24 bits. This means, the driver converts your GL_RGB or GL_BGR to what the GPU prefers, which typically is RGBA/BGRA.
Similarly, if you read a buffer with glReadPixels, you might get similar problems. There is a GL_PACK_ALIGNMENT just like the GL_UNPACK_ALIGNMENT. The default alignment is again 4 which means each horizontal line must be a multiple of 4 in size. If you read the buffer with a format such as GL_BGRA or GL_RGBA you won’t have any problems since the line will always be a multiple of 4. If you read it in a format such as GL_BGR or GL_RGB then you risk running into this problem.
The GL_PACK/UNPACK_ALIGNMENTs can only be 1, 2, 4, or 8. So an alignment of 3 is not allowed. If your intention really is to work with packed RGB/BGR data, you should set the alignment to 1 (or preferably, consider switching to RGBA/BGRA.)
It turns out that when you create the texture you can specify a GL_RGBA
format even if you intend to upload only three components.
Image formats do not have to store each component. When the shader samples such a texture, it will still resolve to a 4-value RGBA vector. The components not stored by the image format are filled in automatically. Zeros are used if R, G, or B is missing, while a missing Alpha always resolves to 1.
In order to fix the problem all that we need to do is add glPixelStorei(GL_UNPACK_ALIGNMENT, 1)
before the create the image texture, i.e.
# create texture for image drawing
img₀ = B
img₁= transpose(img₀)
img_width, img_height = size(img₁)
glPixelStorei(GL_UNPACK_ALIGNMENT, 1)
image_id = ImGui_ImplOpenGL3_CreateImageTexture(img_width, img_height, format = GL_RGBA)
img′ = unsafe_wrap(Array{UInt8,3}, convert(Ptr{UInt8}, pointer(img₁)), (Cint(3), Cint(img_width), Cint(img_height)))
ImGui_ImplOpenGL3_UpdateImageTexture(image_id, img′, img_width, img_height; format = GL_RGB)
You can execute a self-contained example by running the following script:
using CImGui
using CImGui.CSyntax
using CImGui.GLFWBackend
using CImGui.OpenGLBackend
using CImGui.GLFWBackend.GLFW
using CImGui.OpenGLBackend.ModernGL
using Printf
using ImageCore
function construct_B()
B = repeat(1:500, 1, 401)
low = colorant"navyblue";
medium = colorant"limegreen";
high = colorant"yellow";
Bcolormap = colorsigned(low,medium,high) ∘ scalesigned(minimum(B),(minimum(B)+maximum(B)) / 2, maximum(B))
Bcolormap.(B)
end
function example(B::AbstractArray)
@static if Sys.isapple()
# OpenGL 3.2 + GLSL 150
glsl_version = 150
GLFW.WindowHint(GLFW.CONTEXT_VERSION_MAJOR, 3)
GLFW.WindowHint(GLFW.CONTEXT_VERSION_MINOR, 2)
GLFW.WindowHint(GLFW.OPENGL_PROFILE, GLFW.OPENGL_CORE_PROFILE) # 3.2+ only
GLFW.WindowHint(GLFW.OPENGL_FORWARD_COMPAT, GL_TRUE) # required on Mac
else
# OpenGL 3.0 + GLSL 130
glsl_version = 130
GLFW.WindowHint(GLFW.CONTEXT_VERSION_MAJOR, 3)
GLFW.WindowHint(GLFW.CONTEXT_VERSION_MINOR, 0)
# GLFW.WindowHint(GLFW.OPENGL_PROFILE, GLFW.OPENGL_CORE_PROFILE) # 3.2+ only
# GLFW.WindowHint(GLFW.OPENGL_FORWARD_COMPAT, GL_TRUE) # 3.0+ only
end
# setup GLFW error callback
error_callback(err::GLFW.GLFWError) = @error "GLFW ERROR: code $(err.code) msg: $(err.description)"
GLFW.SetErrorCallback(error_callback)
# create window
window = GLFW.CreateWindow(1280, 720, "Demo")
@assert window != C_NULL
GLFW.MakeContextCurrent(window)
GLFW.SwapInterval(1) # enable vsync
# setup Dear ImGui context
ctx = CImGui.CreateContext()
# setup Dear ImGui style
CImGui.StyleColorsDark()
# create texture for image drawing
img₀ = B
img₁= transpose(img₀)
img_width, img_height = size(img₁)
glPixelStorei(GL_UNPACK_ALIGNMENT, 1)
image_id = ImGui_ImplOpenGL3_CreateImageTexture(img_width, img_height, format = GL_RGBA)
img′ = unsafe_wrap(Array{UInt8,3}, convert(Ptr{UInt8}, pointer(img₁)), (Cint(3), Cint(img_width), Cint(img_height)))
ImGui_ImplOpenGL3_UpdateImageTexture(image_id, img′, img_width, img_height; format = GL_RGB)
# setup Platform/Renderer bindings
ImGui_ImplGlfw_InitForOpenGL(window, true)
ImGui_ImplOpenGL3_Init(glsl_version)
demo_open = true
clear_color = Cfloat[0.45, 0.55, 0.60, 1.00]
while !GLFW.WindowShouldClose(window)
demo_open # oh my global scope
image_id, img_width, img_height
GLFW.PollEvents()
# start the Dear ImGui frame
ImGui_ImplOpenGL3_NewFrame()
ImGui_ImplGlfw_NewFrame()
CImGui.NewFrame()
CImGui.Begin("Image Demo")
CImGui.Image(Ptr{Cvoid}(image_id), (img_width, img_height))
CImGui.End()
# rendering
CImGui.Render()
GLFW.MakeContextCurrent(window)
display_w, display_h = GLFW.GetFramebufferSize(window)
glViewport(0, 0, display_w, display_h)
glClearColor(clear_color...)
glClear(GL_COLOR_BUFFER_BIT)
ImGui_ImplOpenGL3_RenderDrawData(CImGui.GetDrawData())
GLFW.MakeContextCurrent(window)
GLFW.SwapBuffers(window)
end
# cleanup
ImGui_ImplOpenGL3_Shutdown()
ImGui_ImplGlfw_Shutdown()
CImGui.DestroyContext(ctx)
GLFW.DestroyWindow(window)
end
# Run the demo
example(construct_B())