How load an image in OCaml?

Hello,

I’m currently learning OCaml and I’m trying to edit PNG images. My idea is to introduce the following type :

type image = (int * int * int) array array;;

(a matrix whose coefficients are pixels represented by three integers between 0 and 255)

However, when it comes to load a PNG image, I can’t figure out how. The only packages I found for that are camlimagesand imagelib. The documentation I found for these packages is really unhelpful : all I was able to find on the Internet is a list of types and functions, with no indications regarding what those actually do.

When trying to load a PNG with imagelibwith the following code

#use “topfind”;;
#require “imagelib”;;
open Image;;
open ImagePNG;;
open ImageUtil;;
let png = parsefile (chunk_reader_of_string “image.png”);;

I get the error :
Exception: Image.Corrupted_image "Invalid PNG header...".

But the image isn’t corrupted (I get this same error with any PNG file). Actually I don’t even know how functions parsefile and chunk_reader_of_string work, as I couldn’t find any clear documentation on them. I just looked as the list of functions of modules Image, ImagePNG, and ImageUtil, and tried it. I can’t find any more information about how to use these modules.
I searched everywhere (I even asked AI, which wasn’t better) but was not able to find any solution to this problem. All I would like to do is to create functions load_png (file : string) : imageand write_png (img : image) (destination : string) : unit where image is the type described above, and where fileand destinationare string indicating a file location on my computer, from which to load / where to write a PNG image.
I also wasn’t able to install package camlimages:
[ERROR] The compilation of camlimages.5.0.5 failed at "dune build -p camlimages -j 7 @install".

but I think this has more to do with opam.

I would really appreciate if somebody could help me understand all this.
Thank you very much.

You can also use tsdl-image:

#require "tsdl-image";;
open Tsdl;;
open Tsdl_image;;
Sdl.(init Init.video);;
let surf = match Image.load "image.png" with Ok s -> s | _ -> failwith "error loading file";;
Sdl.lock_surface surf;;
let data = Sdl.get_surface_pixels surf Bigarray.int8_unsigned;;

Then, data is a bigarray containing your image.
You can inspect the image format (eg: RGBA) with Sdl.get_surface_format_enum surf

1 Like

A script that creates a grayscale version of an image:

#! /usr/bin/env -S thin-ocamlscript -package bimage,bimage-unix -linkpkg --

let () =
  let src =
    Bimage_unix.Magick.read Bimage.f32 Bimage.rgb Sys.argv.(1) |> Result.get_ok
  in
  let dst = Bimage.Image.like_with_color Bimage.Color.gray src in
  Bimage.Image.convert_to ~dest:dst src;
  Bimage_unix.Magick.write ~quality:80 Sys.argv.(2) dst

bimage-unix provides the two functions that you want, although not specific to PNG. bimage provides image manipulation.

You can compile this by removing the #! line and using ocamlfind -package bimage,bimage-unix -linkpkg script.ml

For imagelib:

#! /usr/bin/env -S thin-ocamlscript -package imagelib -linkpkg --

let load_png path =
  In_channel.with_open_bin path (fun file ->
      ImagePNG.parsefile
        (ImageUtil.chunk_reader_of_string (In_channel.input_all file)))

let write_png img path =
  Out_channel.with_open_bin path (fun file ->
      let buf = Buffer.create 0 in
      let writer = ImageUtil.chunk_writer_of_buffer buf in
      ImagePNG.write_png writer img;
      Out_channel.output_string file (Buffer.contents buf))

let grayscale = function
  | Image.RGB (Pix8 ra, Pix8 ga, Pix8 ba) ->
    let module A = Bigarray.Array2 in
    let rows = A.dim1 ra in
    let cols = A.dim2 ra in
    let dst = A.create Bigarray.int8_unsigned Bigarray.c_layout rows cols in
    for y = 0 to rows - 1 do
      for x = 0 to cols - 1 do
        let r, g, b = A.get ra y x, A.get ga y x, A.get ba y x in
        let r, g, b = Float.of_int r, Float.of_int g, Float.of_int b in
        A.set dst y x
          (truncate ((0.2126 *. r) +. (0.7152 *. g) +. (0.0722 *. b)))
      done
    done;
    Image.Grey (Pix8 dst)
  | _ -> failwith "unsupported png"

let () =
  let img = load_png Sys.argv.(1) in
  let img = {img with pixels = grayscale img.pixels} in
  write_png img Sys.argv.(2)

When there are no docs, there is still :MerlinLocateImpl , which reveals that you were getting a PNG format error because the string “image.png” is not a PNG.

1 Like