Clicky

Fortran Wiki
writegif

This module can write a GIF file in GIF89 format. Originally written by Jos Bergervoet, this version by Clive Page makes use of Fortran stream I/O. Licence: public domain.

module gif_util
!  Conversion of raster data to GIF format.
!
!  Version 1.01, August 1999
!  Written by Jos Bergervoet
! 2008 Jan 28: Modified by Clive Page to use stream I/O, array as colourmap.
!
implicit none         !  Check all declarations
private               !  bin_io is used private, no transfer to main program
public  :: writegif   !  Writes GIF89 image, given pixel array and color map
private :: giflzw, slicewrite, InitTable, flushbuffer
integer, parameter, private     :: Bufend=260
character(len=Bufend), private  :: buf
integer, private  ::  ibuf                            ! output buffer vars
integer, parameter, private  ::    maxcode = 4095 
integer, parameter, private  ::    nocode = maxcode+1 ! definitions for LZW

! Define LZW code tables for hashing:
character(len=1), private, dimension(0:maxcode+1)  :: endbyte
integer, private, dimension(0:maxcode)             :: follow, next
  !
  ! For any code P, which codes for a sequence af pixel-values, endbyte(P)
  ! is the last pixel-value, follow(P) points to another code (if it exists)
  ! which codes for this same sequence, but with one more pixel-value
  ! appended.
  !   For each code P, next(P) points to another code which codes for a
  ! similar sequence with only the endbyte different. This is a hashing
  ! pointer, for fast look-up.
  !   All pointers are 'nocode' if they point to nothing
  !

integer, private :: ncod, curmaxcode, EOI, CC, P, K, child, &
                    maxbase, skip, slen, blen, accum, nout   ! local vars

contains
!-----------------------------------------------------------------------------

! CHAR2 Converts the two least sig bytes of an integer to a 2-character string
character(len=2) function char2(ival)
integer, intent(in) :: ival
char2 = achar(mod(ival,256)) // achar(mod(ival/256,256))
end function char2
!-----------------------------------------------------------------------------

subroutine flushbuffer(F_unit)
! Flushes up to 255 bytes to output file if buffer contains data, keeping
! rest of data in buffer. If skip>0 there is a partially filled last byte
! in buf[ibuf]. This byte will be written only if ibuf<256. That should be
! the last call to flushbuffer.
integer, intent(in) :: F_unit   ! I/O unit to use
  integer  :: bl    !   number of bytes to write (to be determined)

  if (ibuf > 255) then
    bl = 255        !   we will write buf[1..255]
  else if (skip /= 0) then
    bl = ibuf       !   buf[ibuf] is partially used, write buf[1..ibuf]
  else if (ibuf > 1) then
    bl = ibuf-1     !   write buf[1..ibuf-1], there is no partial byte
  else
    return          !   nothing to write
  end if

  write(F_unit) CHAR(bl)
  write(F_unit) buf(1:bl)
  buf(1:ibuf-bl) = buf(bl+1:ibuf) ! shift down remaining data
  ibuf = ibuf - bl
  return
end subroutine flushbuffer
!-----------------------------------------------------------------------------
subroutine giflzw(F_unit, Pixel)          ! routine for LZW coding
  integer, intent(in)                 :: F_unit
  integer, intent(in), dimension(:,:) :: Pixel
  integer                             :: i, j

  nout=0                          ! for counting the codes going out
  if (blen<2) then
    blen=2                        ! pixel code-length, 2 is minimum for GIF
  end if
  write(F_unit) CHAR(blen)
  maxbase = 2**blen - 1
  call InitTable()
  call slicewrite(F_unit, CC)

  do j=1, ubound(Pixel,2)
   do i=1, ubound(Pixel,1)
    K = modulo(Pixel(i,j), maxbase+1)    ! take next byte, prevent overflow
    if (i==1 .and. j==1) then
      P = K                       ! first raster byte has one-byte code P
      cycle                       ! for the first byte no further action
    end if
                                  ! Now see if code exists for sequence [.P.]K
    child = follow(P)             ! [.P.]K is "string coded by P" followed by K
    childloop: do
      if ((child == nocode) .or. (ichar(endbyte(child)) == K)) then
        exit childloop
      end if
      child = next(child)
    end do childloop

    if (child /= nocode) then     ! If code for [.P.]K was found, store it in P
      P = child
    else                          ! If not: output P and create code for [.P.]K
      call slicewrite(F_unit, P)
      if (ncod > maxcode) then    ! check if a new code can be added
        call slicewrite(F_unit, CC)       ! If not: tell listener to clear table
        call InitTable()          ! and clear our own table
      else
        if (ncod > curmaxcode) then
          slen = slen+1                     ! New codes will be one bit longer
          curmaxcode = curmaxcode * 2 + 1   ! and more codes are possible
        end if
        endbyte(ncod) = char(K)   ! ncod is the new code for [.P.]K
        follow(ncod) = nocode
        next(ncod) = follow(P)    ! include ncod in the hashing list
        follow(P) = ncod          !     of codes with same start-sequence
        ncod = ncod+1
      end if
      P = K
    end if
   end do
  end do
  call slicewrite(F_unit, P)              ! send the last code to buffer
  call slicewrite(F_unit, EOI)            ! send 'end of image' to buffer
  call flushbuffer(F_unit)        ! extra flush, including partial last byte
  return
end subroutine giflzw
!-----------------------------------------------------------------------------
subroutine InitTable()
  integer :: i

  do i=0,maxbase                  ! Start with defining the codes 0..maxbase
    endbyte(i) = char(i)          ! for one-pixel sequences (code=pixelvalue)
  end do                          ! Initially no multi-pixel codes exist
  follow(0:maxbase) = nocode
  next(0:maxbase) = nocode
  CC = maxbase+1                  ! `clear code-tabel', a control code
  EOI = maxbase+2                 ! `end of image', another control code
  ncod = CC + 2                   ! ncod = number of currently defined codes
  slen = blen + 1                 ! current number of bits to write one code
  curmaxcode = 2**slen - 1        ! currently the highest, until slen increases
  return
end subroutine InitTable
!-----------------------------------------------------------------------------
subroutine open_for_write(Fname, Funit)
! Creates a new Stream I/O file returning I/O unit used   
! CGP 2009 Jan 28
character(len=*), intent(in)  :: Fname
integer, intent(out)          :: Funit
!
logical :: exists, open
! Get free I/O unit number
do Funit = 90, 7, -1
    inquire(unit=Funit, exist=exists, opened=open)
    if(exists .and. .not. open) EXIT
end do
if(Funit < 7) STOP 'open_for_write failed - no free I/O units'
open (unit=Funit, file=Fname, access="STREAM", status="REPLACE")
end subroutine open_for_write
!-----------------------------------------------------------------------------
subroutine slicewrite(F_unit, code)       ! add some bits (a 'slice') to output buffer
  integer, intent(in)  :: F_unit
  integer, intent(in)  :: code

  if (nout == 0) then             ! initiate output buffer
    ibuf = 1
    skip = 0
    accum = 0
  end if
  nout = nout+1

  accum = accum + code * 2**skip  ! add bits at correct position in accum
  skip = skip + slen              ! slen is current slice length, in bits

  shiftout: do
    buf(ibuf:ibuf) = char(modulo(accum, 256))
    if (skip<8) then
      exit shiftout
    end if
    ibuf = ibuf+1                 ! last written buffer-byte is now permanent
    accum = accum / 256           ! remove that byte from accum
    skip = skip-8                 ! skip points to next bit to write in accum
  end do shiftout

  if (ibuf>255) then
    call flushbuffer(F_unit)            ! won't write unfinished byte in buf[ibuf]
  end if
  return                          ! at most 255 bytes will be left in buffer
end subroutine slicewrite
!-----------------------------------------------------------------------------
subroutine writegif (FileName, Pixel, ColorMap, Transparent)
!
! Codes pixel-map with palette into GIF format. Optional transparent color
!
character(len=*), intent(in)            :: FileName ! file to create or replace
integer, intent(in), dimension(:,:)     :: Pixel    ! Pixel values 0 to ncol
integer, intent(in), dimension(:,0:)    :: ColorMap ! RGB 0:255 for colours 0:ncol
integer, intent(in), optional           :: Transparent ! Optional

  character(len=256) :: s
  integer            :: InfoByte, nx, ny, Cblen, HasMap, maxincol,  &
                        maxgifcol, Background, i, F_unit

  call open_for_write (FileName, F_unit)
  nx = ubound(Pixel, 1)
  ny = ubound(Pixel, 2)
  maxincol = ubound(ColorMap,2) 
!!  print *,'image size', nx, ny, ' colours', maxincol
  do i=1,8                           ! find the bitsize, blen, for pixels
    blen = i
    maxgifcol = 2**blen - 1          ! Number of colors has to be power of 2 
    if (maxgifcol >= maxincol) then
      exit                           ! now blen and maxgifcol are correct
    end if                           ! only op to 256 colors can be 
  end do
   write(F_unit) "GIF89a" 
!  Create information for screen descriptor
  Background = 0
  if (present(Transparent)) then
    Background = Transparent
  end if
  HasMap = 1
  Cblen = blen
  InfoByte = HasMap * 128 + (Cblen-1) * 16 + blen-1
!  Write the screen descriptor
  write(F_unit) char2(nx), char2(ny), CHAR(InfoByte), CHAR(Background), CHAR(0)
  do i=0,maxgifcol                                 ! write global colormap
    write(F_unit) CHAR(colormap(1,min(i,maxincol))), &
                  CHAR(colormap(2,min(i,maxincol))), &
                  CHAR(colormap(3,min(i,maxincol)))
  end do
  if (present(Transparent)) then
    write(unit=*,fmt=*) "Transparent color: ", Transparent
    s = "!" // char(249) // char(4) // char(1) // char(0) // char(0)  &
            // char(Transparent) // char(0)
    write(F_unit) s(1:8)                            ! GIF transparent extension
  end if
   write(F_unit) ","                                ! Announce image
!    Now create and write image descriptor
  HasMap = 0
  InfoByte = HasMap * 128 + blen-1                 ! add 64, if interlaced
!    x_margin, y_margin (not used), image dimensions
  write(F_unit) char2(0), char2(0), char2(nx), char2(ny), CHAR(InfoByte) 
  call giflzw (F_unit, Pixel)                       ! now the raster data
  write(F_unit) CHAR(0), ';'                    ! Terminating 0-block ; for GIF
  close(unit=F_unit)
  return
end subroutine writegif
!-----------------------------------------------------------------------------
end module gif_util