The module called gif contains a subroutine read_gif which can read GIF files of types Gif87a and Gif89 (and maybe others). The code comes from various authors, see comments below. This version was put together by Clive Page who has put it into the public domain.
! read_gif2.f90 cgp 2010 Aug 28
! Authors: Jos Bergervoet, Van Snyder, Maurizio Cremonesi, Clive Page, and others
! Original code from: http://it.geocities.com/butonoj/doc/gif-io/gifio.htm (now a dead link)
module gif
implicit none
public :: read_gif ! procedure, see below
private
! ***** private stuff ******************************************
integer,private, parameter :: interlace = 6 ! index of bit indicating interlaced
integer,private, parameter :: max_lzw_bits = 12
integer,private, parameter :: use_local_colormap = 7 ! bit indicating to use local color map
integer, public, parameter :: max_colormap_size = 256
integer,private, save :: lun ! logical unit number
logical,private :: zero_data_block
type, public :: gif_screen_type
integer :: aspect_ratio, background
integer :: bit_pixel ! size of colormap
integer :: color_resolution
integer :: height, width ! shape(image) = (/width,height/)
integer :: color_map_size ! size of local_colormap
logical :: use_local_colormap ! .true. if local color map, else global
end type gif_screen_type
type(gif_screen_type), public :: gif_screen
type, public :: gif89_type
integer :: transparent
integer :: delaytime
integer :: inputflag
integer :: disposal
end type gif89_type
type(gif89_type), public :: gif89
CONTAINS
! =========================================================================================
subroutine read_gif(filename, num_image, image, iostat, color_map, verbose)
! read the num_image'th gif image from filename into arrays image and color_map
character(len=*), intent(in) :: filename ! input file
integer, intent(in) :: num_image ! number of image required
integer, intent(out), allocatable :: image(:,:) ! Image data returned
integer, intent(out) :: iostat ! I/O error number, =0 if ok
real , allocatable, intent(out) :: color_map(:,:) ! RGB for each level, range 0.0 to 1.0
logical, intent(in), optional :: verbose ! .true.for verbose output
! ----- local variables ------------------------------------
character(len=16) :: buf ! input buffer
character (len=1):: c ! shorter input buffer
integer :: image_count ! number of images processed so far
logical :: my_verbose
! ----- executable statements ------------------------------
zero_data_block = .false.
gif89= gif89_type( -1, -1, -1, 0 )
my_verbose = .false.
if ( present(verbose) ) my_verbose = verbose
call open_gif (filename, iostat, my_verbose, color_map )
if (iostat /= 0) RETURN
image_count = 0
do ! forever
call read_buf(c, iostat )
if (iostat /= 0) then
call io_error ( "reading file", iostat, filename )
RETURN
end if
if ( c == ";" ) then ! gif image terminator
if ( image_count < num_image ) then
write (*,*) "only", image_count, "image(s) found in file"
iostat = -1
end if
close ( unit=lun )
RETURN
end if
if ( c == "!" ) then
! gif extension
call do_extension (filename, iostat, my_verbose )
if (iostat /= 0) RETURN
CYCLE
end if
if ( c /= "," ) then
! not a valid start character
write (*,*) "ignoring bogus character ", ichar(c)
CYCLE
end if
image_count = image_count + 1
if (image_count>num_image) RETURN
call read_buf(buf(1:9), iostat )
if (iostat /= 0) then
call io_error("cannot read width/height", iostat, filename)
RETURN
end if
!
! If local colour map exists: read it
!
gif_screen%use_local_colormap = btest(ichar(buf(9:9)),use_local_colormap)
if ( gif_screen%use_local_colormap ) then
gif_screen%color_map_size = 2**(modulo(ichar(buf(9:9)),8)+1)
if(my_verbose) write(*,*)'read_gif error in local colour map, size=', &
gif_screen%color_map_size
allocate(color_map(3,gif_screen%color_map_size))
call read_colormap(color_map, iostat )
if (iostat /= 0) then
call io_error ( " error reading local color map", iostat, filename )
RETURN
end if
call read_image(bcint2b(buf(5:6)), bcint2b(buf(7:8)), &
btest(ichar(buf(9:9)),interlace), image_count /= num_image, &
my_verbose, filename, iostat, image)
if (iostat /= 0) RETURN
else
call read_image(bcint2b(buf(5:6)), bcint2b(buf(7:8)), &
btest(ichar(buf(9:9)),interlace), image_count /= num_image, &
my_verbose, filename, iostat, image )
if (iostat /= 0) RETURN
end if
end do
close(unit=lun)
print *,'closed unit', lun, ' in read_gif'
end subroutine read_gif
! ***** private module procedures *********************************
! ================================================ bcint2b =====
function bcint2b ( buf ) result (iresult)
! convert two bytes to an integer. the bytes are in little-endian
! order -- the first one is the low-order byte, and the second is
! the high-order byte
character(len=*), intent(in) :: buf
integer :: iresult
iresult = 256*ichar(buf(2:2)) + ichar(buf(1:1))
end function bcint2b
! =========================================== do_extension =====
subroutine do_extension (filename, iostat, verbose )
character(len=*),intent(in) :: filename ! in case it's needed in a message
integer, intent(out) :: iostat
logical, intent(in) :: verbose
!
character(len=256), save :: buf ! long input buffer
character (len=1) :: c ! short input buffer
integer :: countx ! length of a data block
character(len=256) :: str ! part of a message, if verbose
!
call read_buf(c, iostat )
if (iostat /= 0) then
call io_error ( " error reading extension", iostat, filename )
RETURN
end if
select case ( ichar(c) )
case ( 1 ) ! 0x01 -- plain text extension
str = "plain text extension"
case ( 249 ) ! 0xf9 -- graphic control extension
str = "graphic control extension"
call get_data_block(buf, filename, countx, iostat )
if (iostat /= 0) then
RETURN
end if
! the gif89 structure isn't used. why do we do this?
gif89%disposal = modulo(ichar(buf(1:1))/4, 7)
gif89%inputflag = modulo(ichar(buf(1:1))/2, 1)
gif89%delaytime = bcint2b(buf(2:3))
if ( modulo(ichar(buf(1:1)),2) /= 0 ) then
gif89%transparent = ichar(buf(4:4))
end if
do
call get_data_block(buf, filename, countx, iostat )
if (iostat /= 0) then
RETURN
end if
if ( countx == 0 ) then
EXIT
end if
end do
case ( 254 ) ! 0xfe -- comment extension
str = "comment extension"
do
call get_data_block(buf, filename, countx, iostat )
if (iostat /= 0) then
RETURN
end if
if ( countx == 0 ) then
EXIT
end if
if (verbose) write (*,*) " gif comment: ", buf(:countx)
end do
case ( 255 ) ! 0xff -- application extension
str = "application extension"
case default ! oops
write (*,*) " unknown extension ", ichar(c), " label"
str = buf
end select
if ( verbose ) write(*,*) 'read_gif: extension ', trim(str)
end subroutine do_extension
! =============================================== get_code =====
subroutine get_code ( result,code_size, flag, filename, iostat )
integer, intent(out) :: result
integer, intent(in) :: code_size
logical, intent(in) :: flag ! first-time flag
character (len=*), intent(in) :: filename ! in case it's needed in a message
integer, intent(out) :: iostat
!
integer :: bint ! bit an integer (not logical)
character(len=280), save :: buf ! input buffer
integer :: countx
integer, save :: curbit
logical, save :: done
integer :: i, j, k
integer, save :: lastbit
integer, save :: last_byte
!
if ( flag ) then
curbit = 0
lastbit = 0
last_byte = 2
buf(1:2) = " " ! so it has a value the first time around
done = .false.
result = 0
RETURN
end if
if ( curbit + code_size >= lastbit ) then
if ( done ) then
if ( curbit >= lastbit ) write (*,*) " ran off the end of my bits"
result = -1
RETURN
end if
buf(1:2) = buf(last_byte-1:last_byte)
call get_data_block(buf(3:), filename, countx, iostat )
if (iostat /= 0) then
result = -1
RETURN
end if
if ( countx == 0 ) done = .true.
curbit = (curbit - lastbit) + 16
last_byte = 2 + countx
lastbit = last_byte * 8
end if
result = 0
i = curbit / 8 + 1
k = modulo(curbit, 8)
do j = 0, code_size-1
bint = 0
if (btest(ichar(buf(i:i)), k)) bint = 1
result = ior( result, ishft(bint,j) )
curbit = curbit + 1
k = k + 1
if ( k == 8 ) then
k = 0
i = i + 1
end if
end do
end subroutine get_code
! ========================================= get_data_block =====
subroutine get_data_block (buf, filename, countx, iostat )
character(len=*), intent(out) :: buf
character(len=*), intent(in):: filename ! in case it's needed in a message
integer, intent(out) :: countx ! size of data block
integer, intent(out) :: iostat
!
character (len=1) :: c
call read_buf(c, iostat )
if (iostat /= 0) then
call io_error ( " error in count for data block", iostat, filename )
RETURN
end if
countx = ichar(c)
zero_data_block = countx == 0
if ( countx /= 0 ) then
call read_buf(buf(1:countx), iostat )
if (iostat /= 0) &
call io_error (" error reading data block", iostat, filename )
end if
end subroutine get_data_block
! ======================================================================
subroutine get_lun ( lun )
integer, intent(out) :: lun ! a free logical unit number
!
logical :: inuse, exists
!
do lun = 100, 6, -1
inquire (unit=lun, opened=inuse, exist=exists )
if (exists .and. .not. inuse) RETURN
end do
write(*,*) "get_lun: no free logical unit numbers"
lun = -1
end subroutine get_lun
! ======================================================================
subroutine io_error ( message, iostat, filename )
character(len=*), intent(in) :: message
integer, intent(in) :: iostat
character(len=*), intent(in) :: filename
!
write (*,*) "read_gif error ", trim(message), ' in ', trim(filename), &
" code =", iostat
end subroutine io_error
! ======================================================================
subroutine lzw_read_byte ( result, input_code_size, flag, filename, iostat )
integer, intent (out) :: result
integer, intent(in) :: input_code_size
logical, intent(in) :: flag ! first-time flag
character(len=*), intent(in) :: filename ! in case it's needed in a message
integer, intent(out) :: iostat
!
character(len=260) :: buf
integer, save :: clear_code
integer :: code
integer, save :: code_size
integer :: countx
integer, save :: end_code
integer, save :: firstcode
logical, save :: fresh = .false.
integer :: i
integer :: incode
integer, save :: max_code, max_code_size
integer, parameter :: max_max_code = 2**max_lzw_bits
integer, parameter :: max_stack = 2*max_max_code
integer, save :: oldcode
integer, save :: set_code_size
integer, dimension(0:1,0:max_max_code-1), save :: table
integer,dimension(max_stack), save :: stack
integer, save :: stack_ptr
result = 0
if ( flag ) then ! setup
set_code_size = input_code_size
clear_code = 2 ** set_code_size
if ( set_code_size > max_lzw_bits ) then
result= -1
RETURN
end if
end_code = clear_code + 1
code_size = set_code_size + 1
max_code = clear_code + 2
max_code_size = 2 * clear_code
stack_ptr = 1
table(0,:) = 0
do i = 0, clear_code-1
table(1,i) = i
end do
table(1,clear_code:) = 0
call get_code(i, code_size, .true., filename, iostat ) ! initialize
fresh = .true.
RETURN
end if
if ( fresh ) then
fresh = .false.
do
call get_code(oldcode, code_size, .false., filename, iostat )
firstcode = oldcode
if ( firstcode /= clear_code ) EXIT
end do
result = firstcode
RETURN
end if
if ( stack_ptr > 1 ) then
stack_ptr = stack_ptr - 1
result = stack(stack_ptr)
RETURN
end if
do
call get_code(code, code_size, .false., filename, iostat )
if ( code < 0 ) EXIT
if ( code == clear_code ) then
code_size = set_code_size + 1
max_code = clear_code + 2
max_code_size = 2 * clear_code
stack_ptr = 1
table(0,:) = 0
do i = 0, clear_code-1
table(1,i) = i
end do
table(1,clear_code:) = 0
call get_code( oldcode, code_size, .false., filename, iostat )
firstcode = oldcode
result = firstcode
RETURN
end if
if ( code == end_code ) then
result = -2
if ( zero_data_block ) RETURN
do
call get_data_block(buf, filename, countx, iostat )
if (iostat /= 0) RETURN
if ( countx <= 0 ) EXIT
end do
if ( countx /= 0 ) then
write (unit=*, fmt="(a)") "missing eod in data stream in file"
write (unit=*, fmt="(a)") trim(filename)
write (unit=*, fmt="(a)") "(this is not unusual)"
end if
RETURN
end if
incode = code
if ( code >= max_code ) then
stack(stack_ptr) = firstcode
stack_ptr = stack_ptr + 1
code = oldcode
end if
do
if ( code >= clear_code ) then
stack(stack_ptr) = table(1,code)
stack_ptr = stack_ptr + 1
if ( code == table(0,code) ) then
write (unit=*, fmt="(a,i6,a,i6)") "code =", code,", table(0,code) =", table(0,code)
write (unit=*, fmt="(a)") "circular table entry in file"
write (unit=*, fmt="(a)") trim(filename)
write (unit=*, fmt="(a)") "this is a serious error."
result = -2
RETURN
end if
code = table(0,code)
else
EXIT
end if
end do
firstcode = table(1,code)
stack(stack_ptr) = firstcode
stack_ptr = stack_ptr + 1
code = max_code
if ( code < max_max_code ) then
table(0,code) = oldcode
table(1,code) = firstcode
max_code = max_code + 1
if ( max_code >= max_code_size .and. max_code_size < max_max_code ) then
max_code_size = max_code_size * 2
code_size = code_size + 1
end if
end if
oldcode = incode
if ( stack_ptr > 1 ) then
stack_ptr = stack_ptr - 1
result = stack(stack_ptr)
RETURN
end if
end do
result = code
end subroutine lzw_read_byte
! =============================================== open_gif =====
subroutine open_gif (filename, iostat, verbose, color_map)
! open a gif file, verify it's either gif87a or gif89a
! fill the gif_screen structure, including reading the color map if
! necessary.
! iostat > 0 is status from open or read, iostat = -1 means wrong format.
! a message will be printed if iostat /= 0.
character(len=*), intent(in) :: filename
integer, intent(out) :: iostat
logical, intent(in) :: verbose
real, allocatable, intent(out) :: color_map(:,:)
!
character(len=7) :: buf ! input buffer
!
call get_lun ( lun )
if (lun<1) then
iostat = -1
RETURN
end if
if ( verbose ) write (unit=*, fmt="(a,a)") "opening ", trim(filename)
open(unit=lun, file=filename, access="stream", status="old", iostat=iostat)
if (iostat /= 0) then
call io_error ( " failed to open", iostat, filename )
RETURN
else
end if
call read_buf (buf(1:6), iostat )
if (iostat /= 0) then
call io_error ( " error reading 'magic number'", iostat, filename )
RETURN
end if
if ( buf(1:6) /= "GIF87a" .and. buf(1:6) /= "GIF89a" ) then
write (*,*) " invalid GIF format", buf(1:6), " in ", trim(filename),&
" expected GIF87a or GIF89a"
iostat = -1
close ( unit=lun )
RETURN
end if
call read_buf (buf(1:7), iostat )
if (iostat /= 0) then
RETURN
end if
gif_screen%width = bcint2b(buf(1:2))
gif_screen%height = bcint2b(buf(3:4))
gif_screen%bit_pixel = ishft(2,iand(ichar(buf(5:5)),7))
gif_screen%color_resolution = iand(ishft(ichar(buf(5:5)),-3),14)+1
gif_screen%background = ichar(buf(6:6))
gif_screen%aspect_ratio = ichar(buf(7:7))
if ( btest(ichar(buf(5:5)), use_local_colormap) ) then
if(verbose) write(*,*) &
'read_gif error in global colour map size', gif_screen%bit_pixel
allocate(color_map(3,gif_screen%bit_pixel))
call read_colormap(color_map, iostat )
if (iostat /= 0) then
call io_error ( "error reading global colormap", iostat, filename )
RETURN
end if
end if
if ( gif_screen%aspect_ratio /= 0 .and. gif_screen%aspect_ratio /= 49 ) then
write (*,*) "read_gif warning: non-square pixels, ratio=", &
( gif_screen%aspect_ratio + 15.0 ) / 64.0
end if
end subroutine open_gif
! =============================================== read_buf =====
subroutine read_buf (buf, iostat )
! read the next len(buf) characters from the gif file opened by open_gif.
character(len=*), intent(out) :: buf
integer, intent(out) :: iostat ! I/O status, =0 if no error
read(unit=lun, iostat=iostat) buf
end subroutine read_buf
! ========================================== read_colormap =====
subroutine read_colormap (colormap, iostat )
! read the colormap.
real, dimension(:), intent(out) :: colormap(:,:) ! 1st dimension: red/green/blue
integer, intent(out) :: iostat
!
integer :: j
character :: triplet*3
!
do j = 1, size(colormap,2)
call read_buf(triplet, iostat )
if (iostat /= 0) then
write(*,*) 'read_gif: error reading colormap at j=', j, iostat
RETURN
end if
colormap(1,j) = ichar(triplet(1:1)) / 255.0
colormap(2,j) = ichar(triplet(2:2)) / 255.0
colormap(3,j) = ichar(triplet(3:3)) / 255.0
end do
end subroutine read_colormap
! ============================================= read_image =====
subroutine read_image (length, height, interlace, ignore, &
verbose, filename, iostat, image)
integer, intent(in) :: length
integer, intent(in) :: height
logical, intent(in) :: interlace
logical, intent(in) :: ignore
character(len=*),intent(in) :: filename ! so it can appear in messages
logical, intent(in) :: verbose
! character (len=*),dimension(:,:), pointer :: image
integer, intent(out) :: iostat
integer, intent(out), allocatable :: image(:,:)
!
character (len=1) :: c ! input buffer
integer :: pass ! pass number, for interlaced images
integer :: vbyte ! output from lzw_read_byte
integer :: xpos, ypos ! coordinates of pixel
integer :: rslt
!
call read_buf(c, iostat )
if (iostat /= 0) then
call io_error ( "error reading code size byte", iostat, filename )
RETURN
end if
call lzw_read_byte( rslt, ichar(c), .true., filename, iostat )
if ( rslt < 0 ) then
call io_error ( "reading image prefix", iostat, filename )
RETURN
end if
if ( ignore ) then ! skip an uninteresting image
if ( verbose ) then
write (unit=*, fmt="(a)") "skipping an image"
end if
call lzw_read_byte (rslt, ichar(c), .false., filename, iostat )
do
if ( rslt >= 0 ) then
call lzw_read_byte (rslt, ichar(c), .false., filename, iostat )
else
EXIT
end if
end do
RETURN
end if
!
if(verbose) write(unit=*,fmt=*) "Image size:",length,height
allocate ( image(length,height), stat=iostat )
!
if (iostat /= 0) then
call io_error ( "while allocating image", iostat, filename )
RETURN
end if
if ( verbose ) write (*,*) " reading", length," by", height, " GIF image from ", &
trim(filename), " ", merge("interlaced", " ", interlace)
pass = 0
xpos = 1
ypos = 1
do
call lzw_read_byte (vbyte, ichar(c), .false., filename, iostat )
if (vbyte < 0) EXIT
image(xpos,ypos) = vbyte
xpos = xpos + 1
if ( xpos > length ) then
xpos = 1
if ( interlace ) then
select case ( pass )
case ( 0, 1 )
ypos = ypos + 8
case ( 2 )
ypos = ypos + 4
case ( 3 )
ypos = ypos + 2
end select
if ( ypos > height ) then
pass = pass + 1
select case ( pass )
case ( 1 )
ypos = 5
case ( 2 )
ypos = 3
case ( 3 )
ypos = 2
case default
EXIT
end select
end if
else
ypos = ypos + 1
end if
end if
if ( ypos > height ) EXIT
end do
call lzw_read_byte ( rslt,ichar(c), .false., filename, iostat )
if ( rslt >= 0 ) then
write (*,*) "read_gif: too much input data, ignoring extra..."
do
if ( rslt >= 0 ) then
call lzw_read_byte ( rslt, ichar(c), .false., filename, iostat )
else
EXIT
end if
end do
end if
end subroutine read_image
end module gif