Home My Page Projects Code Snippets Project Openings 3D graphics for Standard ML
Summary Activity SCM

SCM Repository

[sml3d] View of /trunk/sml3d/src/image-io/tga-file-io.sml
ViewVC logotype

View of /trunk/sml3d/src/image-io/tga-file-io.sml

Parent Directory Parent Directory | Revision Log Revision Log


Revision 314 - (download) (annotate)
Thu Oct 2 08:43:19 2008 UTC (11 years, 1 month ago) by jhr
Original Path: trunk/sml3d/src/image/tga-file-io.sml
File size: 7967 byte(s)
  Working on image IO
(* tga-file-io.sml
 *
 * COPYRIGHT (c) 2008 John Reppy (http://cs.uchicago.edu/~jhr)
 * All rights reserved.
 *)

structure TGAFileIO : sig

  (* the native formats that a TGA file can hold (at least those that
   * we handle!)
   *)
    datatype tga_image
      = Gray of Word8.word ImageTypes.image2
      | GrayAlpha of (Word8.word * Word8.word) ImageTypes.image2
      | RGB of (Word8.word * Word8.word * Word8.word) ImageTypes.image2
      | RGBA of (Word8.word * Word8.word * Word8.word * Word8.word) ImageTypes.image2

    val loadFile : {file : string, flip : bool} -> tga_image

  end = struct

    structure W8V = Word8Vector

  (* need to do something sensible about error reporting *)
    fun error msg = raise Fail msg

  (* the native formats that a TGA file can hold (at least those that
   * we handle!)
   *)
    datatype tga_image
      = Gray of Word8.word ImageTypes.image2
      | GrayAlpha of (Word8.word * Word8.word) ImageTypes.image2
      | RGB of (Word8.word * Word8.word * Word8.word) ImageTypes.image2
      | RGBA of (Word8.word * Word8.word * Word8.word * Word8.word) ImageTypes.image2

  (* TGA file-header layout *)
    val hdrSz = 18
    val offIDLength = 0
    val offCMapType = 1
    val offImageType = 2
    val offCMapSpec = 3
    val offWid = 12
    val offHt = 14
    val offBPP = 16
    val offImgDesc = 17

    fun getByte (vec, i) = Word.fromLargeWord(Word8.toLargeWordX(W8V.sub(vec, i)))
    fun getShort (vec, i) = Word.toIntX(Word.orb(
	  Word.<< (getByte(vec, i+1), 0w8),
	  getByte(vec, i)))

  (* read the file header *)
    fun readHdr inS = let
	  val vec = BinIO.inputN (inS, hdrSz)
	  in
	    if (W8V.length vec <> hdrSz)
	      then error "bogus TGA file"
	      else let
		val idLen = getByte(vec, offIDLength)
		val cmapTy = getByte(vec, offCMapType)
		val (color, rle) = (case getByte(vec, offImageType)
		       of 0w2 => (true, false)
			| 0w3 => (false, false)
			| 0w10 => (true, true)
			| 0w11 => (false, true)
			| n => error "unsupported image type"
		      (* end case *))
		val wid = getShort(vec, offWid)
		val ht = getShort(vec, offHt)
		val bytesPP = Word.toIntX(getByte(vec, offBPP) div 0w8)
		val imageDesc = getByte(vec, offImgDesc)
		val alpha = (Word.andb(imageDesc, 0wxF) <> 0w0)
		in
		(* do some sanity checking *)
		  if (cmapTy <> 0w0) then error "ill-formed TGA header" else ();
		  case (color, alpha, bytesPP)
		   of (true, true, 4) => ()	(* RGBA data *)
		    | (true, false, 3) => ()	(* RGB data *)
		    | (false, false, 1) => ()	(* 8-bit gray-scale *)
		    | _ => error "unsupported format"
		  (* end case *);
		(* skip the ID field, if present *)
		  if (idLen <> 0w0)
		    then ignore(BinIO.inputN(inS, Word.toIntX idLen))
		    else ();
		(* return the header information *)
		  { wid = wid,
		    ht = ht,
		    bytesPerPixel = bytesPP,
		    size = wid * ht * bytesPP,
		    compressed = rle
		  }
		end
	  end

  (* parse a packet header in a RLE image *)
    fun parsePacketHdr ub = let
	  val np = Word8.andb(ub, 0wx7f)
	  in
	    {raw = (ub = np), nPixels = Word8.toInt np + 1}
	  end

    local
      fun getGrayPixel (v, i) = W8V.sub(v, i)
      fun getGrayAlphaPixel (v, i) = (W8V.sub(v, i), W8V.sub(v, i+1))
      fun getRGBPixel (v, i) = (W8V.sub(v, i), W8V.sub(v, i+1), W8V.sub(v, i+2))
      fun getRGBAPixel (v, i) = (W8V.sub(v, i), W8V.sub(v, i+1), W8V.sub(v, i+2), W8V.sub(v, i+3))
      fun readRow (bpp, get, put) (inS, buf, i, wid) = let
	    val n = bpp*wid
	    val v = BinIO.inputN (inS, n)
	    fun lp j = if (j < wid)
		  then (put(buf, i+j, get(v, j)); lp j)
		  else i+wid
	    in
	      if (W8V.length v <> n)
		then error "error reading file"
		else lp 0
	    end
    in
    val readGrayRow = readRow (1, getGrayPixel, DataBuffer.setub)
    val readGrayAlphaRow = readRow (2, getGrayAlphaPixel, DataBuffer.set2ub)
    val readRGBRow = readRow (3, getRGBPixel, DataBuffer.set3ub)
    val readRGBARow = readRow (3, getRGBAPixel, DataBuffer.set4ub)
    end

    local
      fun getGrayPixel inS = valOf(BinIO.input1 inS)
      fun getGrayAlphaPixel inS = let
	    val v = BinIO.inputN(inS, 2)
	    in
	      (W8V.sub(v, 0), W8V.sub(v, 1))
	    end
      fun getRGBPixel inS = let
	    val v = BinIO.inputN(inS, 3)
	    in
	      (W8V.sub(v, 0), W8V.sub(v, 1), W8V.sub(v, 2))
	    end
      fun getRGBAPixel inS = let
	    val v = BinIO.inputN(inS, 4)
	    in
	      (W8V.sub(v, 0), W8V.sub(v, 1), W8V.sub(v, 2), W8V.sub(v, 3))
	    end
  
    (* read and uncompress a row of an REL encoded image *)
      fun readRLERow (get, put) (inS, buf, i, wid) = let
	    val stop = i + wid
	    fun lp i = if (i < stop)
		  then let
		    val {raw, nPixels} = parsePacketHdr(valOf(BinIO.input1 inS))
		    in
		      if raw
			then let
			  fun read (i, 0) = lp i
			    | read (i, n) = (put(buf, i, get inS); read(i+1, n-1))
			  in
			    read (i, nPixels)
			  end
			else let
			  val pix = get inS
			  fun repeat (i, 0) = lp i
			    | repeat (i, n) = (put(buf, i, pix); repeat(i+1, n-1))
			  in
			    repeat (i, nPixels)
			  end
		    end
		  else i
	    in
	      lp i
	    end
    in
    val readGrayRLERow = readRLERow (getGrayPixel, DataBuffer.setub)
    val readGrayAlphaRLERow = readRLERow (getGrayAlphaPixel, DataBuffer.set2ub)
    val readRGBRLERow = readRLERow (getRGBPixel, DataBuffer.set3ub)
    val readRGBARLERow = readRLERow (getRGBAPixel, DataBuffer.set4ub)
    end

  (* read an image while row by row *)
    fun readImage readRow (inS, wid, ht, buf) = let
	  val stop = wid * ht
	  fun lp i = if (i < stop)
		then lp (readRow (inS, buf, i, wid))
		else ()
	  in
	    lp 0
	  end

  (* read an image while flipping the Y axis *)
    fun flipImage readRow (inS, wid, ht, buf) = let
	  val start = (ht - 1) * wid
	  fun lp i = if (i < 0)
		then ()
		else (ignore (readRow (inS, buf, i, wid)); lp(i-wid))
	  in
	    lp ((ht - 1) * wid)
	  end

  fun loadFile {file, flip} = let
	val inS = BinIO.openIn file
	fun load () = let
	      val {wid, ht, size, bytesPerPixel, compressed} = readHdr inS
	      in
		case bytesPerPixel
		 of 1 => let
		      val buf = DataBuffer.new (DataBuffer.sizeub, size)
		      in
			case (flip, compressed)
			 of (false, false) => readImage readGrayRow (inS, wid, ht, buf)
			  | (false, true) => readImage readGrayRLERow (inS, wid, ht, buf)
			  | (true, false) => flipImage readGrayRow (inS, wid, ht, buf)
			  | (true, true) => flipImage readGrayRLERow (inS, wid, ht, buf)
			(* end case *);
			Gray{wid=wid, ht=ht, data=buf}
		      end
		  | 2 => let
		      val buf = DataBuffer.new (DataBuffer.size2ub, size)
		      in
			case (flip, compressed)
			 of (false, false) => readImage readGrayAlphaRow (inS, wid, ht, buf)
			  | (false, true) => readImage readGrayAlphaRLERow (inS, wid, ht, buf)
			  | (true, false) => flipImage readGrayAlphaRow (inS, wid, ht, buf)
			  | (true, true) => flipImage readGrayAlphaRLERow (inS, wid, ht, buf)
			(* end case *);
			GrayAlpha{wid=wid, ht=ht, data=buf}
		      end
		  | 3 => let
		      val buf = DataBuffer.new (DataBuffer.size3ub, size)
		      in
			case (flip, compressed)
			 of (false, false) => readImage readRGBRow (inS, wid, ht, buf)
			  | (false, true) => readImage readRGBRLERow (inS, wid, ht, buf)
			  | (true, false) => flipImage readRGBRow (inS, wid, ht, buf)
			  | (true, true) => flipImage readRGBRLERow (inS, wid, ht, buf)
			(* end case *);
			RGB{wid=wid, ht=ht, data=buf}
		      end
		  | 4 => let
		      val buf = DataBuffer.new (DataBuffer.size4ub, size)
		      in
			case (flip, compressed)
			 of (false, false) => readImage readRGBARow (inS, wid, ht, buf)
			  | (false, true) => readImage readRGBARLERow (inS, wid, ht, buf)
			  | (true, false) => flipImage readRGBARow (inS, wid, ht, buf)
			  | (true, true) => flipImage readRGBARLERow (inS, wid, ht, buf)
			(* end case *);
			RGBA{wid=wid, ht=ht, data=buf}
		      end
		(* end case *)
	      end
	in
	  load () handle ex => (BinIO.closeIn inS; raise ex)
	end

  end


root@smlnj-gforge.cs.uchicago.edu
ViewVC Help
Powered by ViewVC 1.0.0