I'm trying binding libgd library with Nim, but I'm facing a problem with pointers.
My interface:
# https://libgd.github.io/manuals/2.2.5/files/gd-h.html#gdPoint
type
gdImagePtr* = ref object
type
gdPoint* = object
x*: int
y*: int
proc gdImageCreate*(width: int, height: int): gdImagePtr {.importc, dynlib: "/usr/lib/libgd.so".}
proc gdImageDestroy*(im: gdImagePtr): void {.importc, dynlib: "/usr/lib/libgd.so".}
#
# These functions mimic the original functions of the library and it is in these functions that I have problems.
#
proc gdImagePolygon*(im: gdImagePtr, p: gdPointPtr, n: int, c: int): void {.importc, dynlib: "/usr/lib/libgd.so".}
proc gdImageOpenPolygon*(im: gdImagePtr, p: gdPointPtr, n: int, c: int): void {.importc, dynlib: "/usr/lib/libgd.so".}
proc gdImageFilledPolygon*(im: gdImagePtr, p: gdPointPtr, n: int, c: int): void {.importc, dynlib: "/usr/lib/libgd.so".}
# I skipped many functions
template withGd*(img: untyped, width: int, height: int, body: untyped): typed =
block gd:
let img = gdImageCreate(width, height)
body
gdImageDestroy(img)
# Polygon
withGd im, 500, 500:
var points: array[4, gdPoint]
points[0].x = 50
points[0].y = 50
points[1].x = 250
points[1].y = 50
points[2].x = 250
points[2].y = 200
points[3].x = 50
points[3].y = 200
let white = im.color(255, 255, 255)
let red = im.color(255, 0, 0)
im.gdImagePolygon(addr points, points.len, red)
let png_out = open("outputs/test_polygon.png", fmWrite)
im.save(png_out, extension="png")
png_out.close()
I need to pass a pointer to the array of points. So I replace gdImagePtr with pointer type.
# Original
proc gdImagePolygon*(im: gdImagePtr, p: gdPointPtr, n: int, c: int): void {.importc, dynlib: "/usr/lib/libgd.so".}
# I replace the gdPointPtr with pointer type
proc gdImagePolygon*(im: gdImagePtr, p: pointer, n: int, c: int): void {.importc, dynlib: "/usr/lib/libgd.so".}
The code compiles, but does not draw the lines properly, only one line appears at the top. What is wrong?
Bingo Work!!! Tanks @yglukhov the solution is replace int with cint ;-)
type
gdPoint* = object
x*: cint
y*: cint
$ c2nim --dynlib:libgd --cdecl ./gd.h --out:gdtest.nim /home/hdias/development/tmp/gd.h(14, 62) Warning: comment 'version605b5d1778' ignored [CommentXIgnored] /home/hdias/development/tmp/gd.h(15, 62) Warning: comment 'version605b5d1778' ignored [CommentXIgnored] /home/hdias/development/tmp/gd.h(16, 62) Warning: comment 'version605b5d1778' ignored [CommentXIgnored] /home/hdias/development/tmp/gd.h(17, 62) Warning: comment 'version605b5d1778' ignored [CommentXIgnored] /home/hdias/development/tmp/gd.h(22, 58) Error: expected ';'
Ok, you're right. But conversion is not trivial!
There's a lot of preprocessor stuff going on. Use nimterop to process that out and make your life easier.
nimble install nimterop
cd path/to/libgd/src
toast --preprocess --recurse gd.h > gdfull.h
c2nim gdfull.h
You could also use toast with --pnim and it will generate nim output but it doesn't do as well as c2nim. Although, both still miss some symbols.
Here's the errors from nim check for toast's nim output:
gdfull.nim(122, 21) Error: undeclared identifier: 'gdImage'
gdfull.nim(142, 37) Error: undeclared identifier: 'gdIOCtxPtr'
gdfull.nim(148, 44) Error: undeclared identifier: 'gdIOCtx'
gdfull.nim(169, 40) Error: undeclared identifier: 'gdSourcePtr'
gdfull.nim(207, 154) Error: undeclared identifier: 'gdFTStringExtraPtr'
gdfull.nim(208, 40) Error: undeclared identifier: 'gdPointPtr'
gdfull.nim(268, 46) Error: undeclared identifier: 'gdSinkPtr'
gdfull.nim(301, 42) Error: undeclared identifier: 'gdScatterPtr'
gdfull.nim(323, 45) Error: undeclared identifier: 'gdRect'
Here's c2nim's nim check output:
gdfull.nim(72, 17) Error: undeclared identifier: 'gdImageStruct'
gdfull.nim(116, 53) Error: undeclared identifier: 'va_list'
gdfull.nim(136, 37) Error: undeclared identifier: 'gdIOCtxPtr'
gdfull.nim(142, 44) Error: undeclared identifier: 'gdIOCtx'
At least most of the hard work is done by the tools. You can mix and match and get it working.
Update: The results I talked about yesterday were obtained with Nim simply -d:release compiling but with quite some optimization for the C reference code.
Today I cleaned up some minor lose ends and did some polishing (for both, C and Nim) and set Nim to compile with --opt:speed plus some checks disabled (which is a) unnecessary in this case, and b) fair because C has none of those at all).
And - I hope you are seated properly - Bang, the algorithm implemented in Nim is on average 2% to 3% faster than the C version!
And no that's not due to an error. I cross checked over 100K test vectors. The Nim implementation is correct.
Kudos to @Araq and the Nim team!