Array Returned from Erlang Module. How to use it?

Hi Everyone, your help is appreciated. I am trying to do image manipulation on a test.jpg file by calling the erlang module wxImage.getData()

defmodule Imedit2 do
  def readimg(image) do
    {:ok, _file} = File.open("happy737.txt", [:write])
    IO.puts("hi there")
    output = File.read(image) |> :wxImage.getData()
    #    is_list(output)
    IO.puts(is_tuple(output))
    #    IO.binwrite(file, output)
    #   File.close(file)
  end
end

The erlang documentation tells me that the return from that function is an array of RGB values. I have tried to check if the returned output is automatically converted to tuple or list in my code but it’s not working, since I am actually getting this error before I have the change to get is_tuple or is_list

** (FunctionClauseError) no function clause matching in :wxImage.getData/1

    The following arguments were given to :wxImage.getData/1:

        # 1
        {:ok,
         <<255, 216, 255, 226, 2, 28, 73, 67, 67, 95, 80, 82, 79, 70, 73, 76, 69, 0, 1,
           1, 0, 0, 2, 12, 108, 99, 109, 115, 2, 16, 0, 0, 109, 110, 116, 114, 82, 71,
           66, 32, 88, 89, 90, 32, 7, 220, 0, 1, ...>>}

    gen/wxImage.erl:405: :wxImage.getData/1
    lib/imedit2.ex:5: Imedit2.readimg/1

I have DuckDuckGo-ed this error and found many replies that it’s syntax related. However, VS Code is telling me the syntax is correct.

Probably replace with

File.read!(image)

File.read returns a tuple
File.read! returns a binary

3 Likes

Thanks for the tip. It did return a binary (i.e. the :ok atom no longer shows) but the function clause error remains :S

Often You need to convert binary to charlist when using Erlang from Elixir.

File.read!(image) |> to_charlist

You will also need to convert back when receiving data from Erlang, with to_string()

2 Likes

Well, I am testing all combinations by trying your inputs. I am sure we are close, but I think the reason it’s not working is because I am not able to understand the wxImage.getData() module/function input values.

Based on this screenshot, what does it take as an argument? Unsigned characters? Or binary image?

I think if we are able to understand how this function works, its input and output we will succeed. I especially do not understand what erl doc means by * near char

Well, I have never used :wxImage. Maybe somebody can help with data to provide?

2 Likes

To me the Erlang documentation suggest that the xwImage data has to be created via one of the

functions (effectively acting as the wxImage constructor).

It is that data (This) that is then used with the remaining wxImage functions like getData/1.

  • It seems that he wxImage data has to be released with destroy/1
  • loadFile/2, loadFile/3, loadFile/4 seem to be able to load an image into an existing wxImage (presumably created with new/0 and mutating it).
  • All (default) image handlers are initialized. i.e. for an image to load the matching handler has to be initialized.
4 Likes

Thank you @peerreynders for your response. You are correct, and I have received a similar response on StackOverflow here. After that, I modified the output as much as I can to a string so I can write it to a file “happy.txt” so I can see the list of characters that rerpresent the RGB values.

But no matter what I do, the bitstring/binary is not translated to readable characters in the text file. I wonder what is the proper way? Should I use File.write or IO.binwrite? Why the text file is not displayed in readable characters when it is encoded UTF8 by default?

defmodule Imedit2 do
  def readimg(image) do
    :wx.new()

    data =
      image
      |> String.to_charlist()
      |> :wxImage.new()
      |> :wxImage.getData()
      |> :binary.bin_to_list()
      |> List.to_charlist()
      |> to_string()

    :wx.destroy()
    IO.puts("Is it a map? #{is_map(data)}")
    IO.puts("Is it a bitstring? #{is_bitstring(data)}")
    IO.puts("Is it a binary? #{is_binary(data)}")
    IO.puts("Is it a list? #{is_list(data)}")

    {:ok, file} = File.open("happy.txt", [:write, :binary])
    File.write("happy.txt", data, [:utf8])
#    IO.binwrite(file, data)
    File.close(file)
  end
end

image

For the record. You may be asking to yourself what my end goal is. I am learning image manipulation because it will be of use for the elixir/phoenix project in mind. Meanwhile, I’m taking advantage to learn the data types and conversions between them. The conversion of list of RGB values into a text file will not be of use for my final program, but it will teach me about the underlying data types and how I can transform them when I need. Thanks for all of the community’s help.

I especially do not understand what erl doc means by * near char

The wxImage docs are for the native C++ bindings, so it’s going to be less-than-ideal reading them.

In this case, unsigned char * at the start of a declaration indicates that the function wxImage::GetData returns a pointer to an array (*) of bytes (unsigned char). The Erlang type for “an array of bytes” is binary()

The () after GetData states that this function requires no arguments, but note that C++ is a language with implicit this so an Erlang binding to this function would take a single argument of type wxImage().

BTW, if you’re interested in human-readable formatting for images take a look at wxImage’s support for writing the PNM format which was specifically designed for that use case.

2 Likes

Thanks for your feedback @al2o3cr I will check the PNIM format. Meanwhile, my understanding was if IEX displays the following for data variable:

image

Then I should be able to write this same output in a text file. Isn’t it?

Yes, you should be able to write it. However there is no guarantee that it will be “readable characters”. In fact, it is highly unlikely that it would be readable characters, since it’s binary data representing an image.

1 Like

wxImage.getData() returns the list of RGB values of each pixel in a list. This is how I understand it,

This means that If I have 3 pixels in this image, I should get something like this:
[[R,G,B],R,G,B],R,G,B]] where each value is an integer from 0 to 255.

So just to simplify, lets shorten the binary to <<24, 24, 24>>. This is a nice gray color. You write that to a file and then open it with your editor, which sees the bytes 24, 24, 24. So it helpfully goes to an ASCII table and determines that the correct way to render 24 is as the cancel character, which has no visual display. So you get gibberish (actually it’s probably more complex and the editor is trying to figure out how to render it, since it’s doesn’t recognize it as proper text.

What you seem to actually want is for it to write <<24, 24, 24>> as a string to the file. inspect will let you do that.

iex(1)> a = <<24, 24, 24>>   
<<24, 24, 24>>
iex(2)> a
<<24, 24, 24>>
iex(3)> inspect(a)
"<<24, 24, 24>>"
3 Likes

Thank you @jola for being helpful the inspect did the job! (y)