Dump to bitmap? - or - How to include a C-library?

Hi!

At several points in my FORTRAN90 programs, I have 2D arrays. I’d like to have a diagnostic routine that dumps such arrays to simple bitmap files, e.g.

CALL Grid2BMP(OutputFileName,NX,NY,RedArray,GreenArray,BlueArray)

where I have already constructed a set of INTEGER arrays containing the RGB-components for the pixels, associated with the values in the original 2D array.

→ Question: Is there a simple routine that already does the job in PGF90?

→ If not: at
http://www.inf.u-szeged.hu/~ssip/2001/projects/files/project20/segmentation/
I found a set of C-routines, collected in files bmp_io.c and bmp_io.h
This library has a subroutine with header as follows:


/****/
int bmp_write ( char fileout_name, int xsize, int ysize, int rarray,
int garray, int barray ) {
/
/
Parameters:
Input, char *FILEOUT_NAME, the name of the output file.
Input, int XSIZE, YSIZE, the X and Y dimensions of the image.
Input, int *RARRAY, *GARRAY, *BARRAY, pointers to the red, green
and blue color arrays.
*/
FILE *fileout;
int result;



The compile-command “pgf90 -c bmp_io.c” generates an object-file “bmp_io.o”.

How can I make a call to the above C-routine and how can I link it with my program?

At present, my RGB-arrays in the F90 program are “regular INTEGER arrays”. How should I prepare my data in the calling F90 program, such that the C-subroutine is correcty called?

I am not familiar with programming in C. And in particular, I don’t look forward to coding the necessary F90 interface module blocks for all subroutines in the C-file. My experience with using pointers is very limited.
For the rest I have coded lots of PASCAL and F77.

Thanks,


Arjan
E-mail: [email protected]

Hi Arjan,

While I don’t know of any off-hand, my guess is that there is a Fortran library which can create a bitmap file. Googling might help. However, I went ahead an created an example Fortran program which creates a simple bitmap file using the C bmp_write function found in bmp_io.c. I though it was a good example on how to call C from Fortran and illustrates several pitfalls. First, please read Chapter 10 of the PGI User’s Guide for an introduction on Inter-language calling.

First thing you need to do is to add the following to “bmp_io.c” just below bmp_write’s definition:

int bmp_write_ ( char *fileout_name, int xsize, int ysize, int *rarray,
  int *garray, int *barray ) {
    return bmp_write(fileout_name, xsize, ysize, rarray, garray, barray);
}

Fortran appends a single underscore to symbol names in order to avoid namespace collisions with C symbols. As such, you need append an underscore to your C function names or create a wrapper function as I have done. Note that the PGI 7.0 release will support F2003 ISO_C_BINDING to make this process easier, but I will save that discussion for another day.

Next I created a sample program which creates a simple bmp file:

program test_bmp
   implicit none
   integer rv, nx, ny, x, y
   integer, allocatable, dimension(:,:) :: rarray, garray, barray

   interface
       integer function bmp_write(file,NX,NY,rarr,garr,barr)
           character(*), intent(in) :: file
           integer, intent(in) :: NX, NY
           integer, dimension(:,:), intent(in) :: rarr, garr, barr
       end function bmp_write
   end interface

   nx = 100
   ny = 100

   allocate(rarray(ny,nx))
   allocate(garray(ny,nx))
   allocate(barray(ny,nx))

   do x = 1, nx/2
     do y = 1, ny
        rarray(y,x) = 0
        garray(y,x) = 0
        barray(y,x) = 255
     end do
   end do

   do x = nx/2, nx
     do y = 1, ny
        rarray(y,x) = 255
        garray(y,x) = 0
        barray(y,x) = 0
     end do
   end do

   rv = bmp_write ( 'test.bmp'//char(0), %VAL(nx), %VAL(ny), rarray, garray, barray)
   print *, rv
   deallocate(rarray)
   deallocate(garray)
   deallocate(barray)

end program test_bmp

Somethings to note are:
The color arrays are indexed (y,x) not (x,y). This is because Fortran arrays are “column major” meaning the data is stored as (1,1), (2,1), (3,1)…(1,2),(2,2),(3,2)… etc. while C stores the array data as (1,1),(1,2),(1,3)…(2,1),(2,2),(2,3)…etc.

C character arrays expect a NULL terminating charater. Because Fortran character arrays do not have a NULL, you need to concatenate one to your string before passing it to the C functions.

By default, all variables are passed by reference in Fortran. Hence when calling a C function which passes a variable by value, you need to use ‘%VAL(x)’ to have Fortran pass the value and not the reference.

I hope this helps!
Mat

Mat,

It sounds as if my problem has been solved. At the moment I’m home (no access to the compiler…). Monday I’ll check the solution.

Suggestion: with Borland Pascal, and also with Free Pascal (sorry for the blasphemy…), I always appreciated the option to see examples, like the one you just wrote, from within the IDE. Could you please add the combination Help/Example to your IDE? The way it works with Freepascal (www.freepascal.org) is that you can press the “Help-key” to get help on the subject specified by the word under the cursor. Then you get to see both the explanation PLUS an example code, plus hyperlinks to help on other or related words. Works great and creates a steep learning curve!

Thanks and I’ll come back in 2 days with a report on the implementation of your suggestion!

Arjan

Mat, your sample program works! (but of course you already knew this…)
Now I’ll see if my own data will be sufficiently obedient to be processed by the same call.

Thanks!

Arjan



ps.Adding (short!) samples like this to the PGF distribution and intertwining them with the HELP-function in the IDE would be truely an asset!

Mat,

In your sample program, you allocate the colour arrays in order
(nx,ny), but access the elements in order (y,x). This goes wrong when nx and ny differ. Therefore I made all array references in the order (x,y)

After this subtle modification, I managed to plot a height-map of the earth in nice colours.

Subsequent deferring the whole bitmap-file stuff to a dedicated subroutine in a MODULE failed. I’m still narrowing down the problem to a compact example.

Regards,

Arjan

Hi Arjan,

In your sample program, you allocate the colour arrays in order
(nx,ny), but access the elements in order (y,x).

Sorry about that. I’ve edited the above code so it’s correct.

Suggestion: with Borland Pascal, and also with Free Pascal (sorry for the blasphemy…), I always appreciated the option to see examples, like the one you just wrote, from within the IDE. Could you please add the combination Help/Example to your IDE? The way it works with Freepascal (> www.freepascal.org> ) is that you can press the “Help-key” to get help on the subject specified by the word under the cursor. Then you get to see both the explanation PLUS an example code, plus hyperlinks to help on other or related words. Works great and creates a steep learning curve!

I’ve passed this on to our Tools team and Docs team. Although our docs do include samples, I think suggestions such as these are good ideas.

Thanks,
Mat

Hi!

This should probably go under “Debugging and Profiling”, but it is closely related to my earlier question, so I put it here.

Until yesterday, the C-library for making .bmp-files was doing its job. Then I saw that the same dataset generates different pictures. The rgb-values in a dump are the same. Between the different calls to the C-library, I allocate space for complex data types. (A routine reads a collection of fields and the C-library shows the content). Maybe the C-library does not like this?

Can someone tell me what to do?

Regards,

Arjan

Hi Arjan,

What is different about the two calls to the bitmap function? Are you using the exact same varaibles and values in each call? Are you passing the complex arrays to the bitmap function? Are you writing to the complex arrays inbetween the two calls?

A sample of the code would also be helpful.

  • Mat

Hi!

I placed the routine to fill RGB-arrays and to call the C-function (the latter construction was taken from your previous messages) in a module:

   TYPE BMPSpecsType
     INTEGER :: Palette
     REAL(Float) :: ZMin,ZMax
   END TYPE BMPSpecsType

   SUBROUTINE Grid2BMP(Grid,BMPSpecs,OutName)

This subroutine can output the RGB-values for all pixels to file for diagnostic ends. No matter how often I call this subroutine in a standalone program that reads a test dataset (heights at every 1*1 degree cell on earth), I always get the same picture. Then I placed this very test-program in the middle of my real, but malfunctioning program. Of course, the test-data have nothing to do with my real program, but I wanted to isolate the bmp-problem from my real program and use an independent dataset. Now, a subroutine in my program has a segment of code where the following happens out of the blue:

   TYPE GridSpecsType
     INTEGER :: NGridX, NGridY  
     REAL(Float) :: XMin,XMax,YMin,YMax
   END TYPE GridSpecsType

   TYPE GridType
     TYPE(GridSpecsType) :: GridSpecs
     REAL(Float), ALLOCATABLE, DIMENSION(:,:) :: Values
   END TYPE GridType

(the above two came from another module)
.....

   Grid%GridSpecs = GridSpecsType(359,179,-179.5,179.5,-89.5,89.5)
      ALLOCATE(Grid%Values(0:Grid%GridSpecs%NGridX,0:Grid%GridSpecs%NGridY))

   OPEN(13,FILE='height.dat',FORM='FORMATTED')
   DO j=0,Grid%GridSpecs%NGridY
     DO i=0,Grid%GridSpecs%NGridX
       READ(13,*) Dum,Dum,Grid%Values(i,j)
     ENDDO
   ENDDO
   CLOSE(13)

   CALL Grid2File(Grid,'testhoog.dat',15,5) ! To check the values
   OutName = 'hoognu.bmp'
   BMPSpecs = BMPSpecsType(4,0.,4000.)
   CALL Grid2BMP(Grid,BMPSpecs,OutName)

   DEALLOCATE(Grid%Values)

And dependent on when this segment is executed, I get bugus bmp-files, sometimes even not readable, or meaningful pictures…

Any suggestions? Can it be a problem that I use index 0 to start with, instead of 1?

Regards,

Arjan

ps. Just for completion I send the routine Grid2BMP:


   SUBROUTINE Grid2BMP(Grid,BMPSpecs,OutName)
!
! Write a grid to .bmp-file
!
   TYPE(GridType), INTENT(IN) :: Grid
   TYPE(BMPSpecsType), INTENT(IN) :: BMPSpecs
   CHARACTER(*), INTENT(IN) :: OutName

   INTEGER :: i,j,ColourNr,Rv,NX,NY
   INTEGER, ALLOCATABLE, DIMENSION(:,:) :: RedC,GreenC,BlueC
   TYPE(MonotoneType) :: Monotone
!
! Generate the colour arrays
!
   CALL MakeMonoPalette(BMPSpecs%Palette,Monotone)
!
! Make RGB arrays
!
   NX = Grid%GridSpecs%NGridX+1
   NY = Grid%GridSpecs%NGridY+1

   ALLOCATE(RedC  (NX,NY))
   ALLOCATE(GreenC(NX,NY))
   ALLOCATE(BlueC (NX,NY))
   
   DO j = 0, Grid%GridSpecs%NGridY
      DO i = 0, Grid%GridSpecs%NGridX
	ColourNr = Value2Pixel(Grid%Values(i,j),BMPSpecs)
	RedC  (i+1,j+1) = Monotone%RedArray  (ColourNr)
	GreenC(i+1,j+1) = Monotone%GreenArray(ColourNr)
	BlueC (i+1,j+1) = Monotone%BlueArray (ColourNr)
      ENDDO
   ENDDO
!
! Make a call to C-routine bmp_write from file bmp_io.c
!
   Rv = BMP_Write(OutName//CHAR(0),%VAL(NX),%VAL(NY),RedC,GreenC,BlueC)
!
! Tidy up leftovers
!
   DEALLOCATE(RedC)
   DEALLOCATE(GreenC)
   DEALLOCATE(BlueC)
!
   END SUBROUTINE Grid2BMP

psps. The main program tries to fill a variable of MeteoFieldArrayType. This, as you can see from the previously geven definition of GridType, requires a lot of ALLOCATE statements. These allocations will only be performed if a certain meteo-file exists. A Vector2GridType is like a GridType, but can contain Vector2 type of elements (an array with 2 elements):

TYPE MeteoSpecsType
  INTEGER :: Origin, CoordinateSystem, NWindLevels 
  REAL(Float) :: LevelHeight(MaxNWindLevels)
  TYPE(GridSpecsType) :: GridSpecs
END TYPE MeteoSpecsType
!
! A meteo-field consists of its fixed characteristics, some variable
! characteristics and a set of (1D or 2D) grids
!
TYPE MeteoFieldType
  TYPE(MeteoSpecsType) :: MeteoSpecs
  LOGICAL :: IsActual ! Is it not just a prognosis?
  TYPE(DateType) :: Date
  TYPE(Vector2GridType) :: Wind(MaxNWindLevels)
  TYPE(GridType) :: Rain, Temperature, Stability, MixingHeight
END TYPE MeteoType
!
! All meteo and derived fields can be stored in the following type:
!
TYPE MeteoFieldArrayType
  LOGICAL :: Exists(MaxNMeteoFields)
  TYPE(MeteoFieldType) :: Meteo(0:MaxNMeteoFields)
END TYPE MeteoFieldArrayType

Hi!

Maybe the allocation of the Vector2GridType is not correctly implemented.

   INTEGER,PARAMETER :: Float = 4

   TYPE Vector2
     REAL(Float) :: x(2)
   END TYPE Vector2

   TYPE Vector2GridType
     TYPE(GridSpecsType) :: GridSpecs
     TYPE(Vector2), ALLOCATABLE, DIMENSION(:,:) :: Values
   END TYPE GridType

   TYPE(Vector2GridType) :: x

...
   ALLOCATE(x%Values(0:x%GridSpecs%NGridX,0:x%GridSpecs%NGridY))

I assumed that PGF90 knows that it has to allocate space for the types stored in the elements of x%Values. i.e. “Vector2”. Was this assumption correct? If not: how else should I allocate space for x%Values?

Regards,

Arjan

Hi Arjan,

I don’t see anything obvious, although your indexing is a bit confusing. Any reason why not use 1-N instead of 0-N? It seems correct, but all your adjusting for the extra array element might cause an off-by-one error elsewhere. Also, I did not see where you allocate the complex array which you thought was the root problem.

Try compiling with “-Mbounds” to see if your writing beyond the bounds of your array. If this doesn’t show anything, you’ll need to compile with “-g” and step through your program using pgdbg (the debugger).

  • Mat

Sorry, my use of “complex” refers to “complicated” and not to complex-valued.

The indexing with 0-N finds its root in the interpolation that will later take place: the zeroth element is the starting point for the array. Its location is subtracted from the other elements. By starting with index 0, all elements automatically have an index proportional to their relative location: the edge is at zero.

I’ll try the -Mbounds flag.

Can you confirm that

ALLOCATE(Grid%Values(0:Grid%GridSpecs%NGridX,0:Grid%GridSpecs%NGridY))

is the correct allocation call for a (sub) variable of type Vector2GridType%Values?

Thanks,

Arjan