All Downloads are FREE. Search and download functionalities are using the official Maven repository.

geotrellis.raster.io.geotiff.compression.Decompressor.scala Maven / Gradle / Ivy

package geotrellis.raster.io.geotiff.compression

import geotrellis.raster.io.geotiff.tags._
import geotrellis.raster.io.geotiff.reader.{GeoTiffReaderLimitationException, MalformedGeoTiffException}
import java.nio.ByteOrder
import spire.syntax.cfor._

trait Decompressor extends Serializable {
  def code: Int

  def byteOrder: ByteOrder = ByteOrder.BIG_ENDIAN

  def decompress(bytes: Array[Byte], segmentIndex: Int): Array[Byte]

  /** Internally, we use the ByteBuffer's default byte ordering (BigEndian).
    * If the decompressed bytes are LittleEndian, flip 'em.
    */
  def flipEndian(bytesPerFlip: Int): Decompressor = 
    new Decompressor {
      def code = Decompressor.this.code

      override
      def byteOrder = ByteOrder.LITTLE_ENDIAN // Since we have to flip, image data is in Little Endian

      def decompress(bytes: Array[Byte], segmentIndex: Int): Array[Byte] =
        flip(Decompressor.this.decompress(bytes, segmentIndex))

      def flip(bytes: Array[Byte]): Array[Byte] = {
        val arr = bytes.clone
        val size = arr.size

        var i = 0
        while (i < size) {
          var j = 0
          while (j < bytesPerFlip) {
            arr(i + j) = bytes(i + bytesPerFlip - 1 - j)
            j += 1
          }

          i += bytesPerFlip
        }

        arr
      }
    }

  def withPredictor(predictor: Predictor): Decompressor =
    new Decompressor {
      def code = Decompressor.this.code

      def decompress(bytes: Array[Byte], segmentIndex: Int): Array[Byte] =
        predictor(Decompressor.this.decompress(bytes, segmentIndex), segmentIndex)
    }
}

object Decompressor {
  def apply(tiffTags: TiffTags, byteOrder: ByteOrder): Decompressor = {
    import geotrellis.raster.io.geotiff.tags.codes.CompressionType._

    def checkEndian(d: Decompressor): Decompressor = {
      if(byteOrder != ByteOrder.BIG_ENDIAN && tiffTags.bitsPerPixel > 8) {
        d.flipEndian(tiffTags.bytesPerPixel / tiffTags.bandCount)
      } else {
        d
      }
    }

    def checkPredictor(d: Decompressor): Decompressor = {
      val predictor = Predictor(tiffTags)
      if(predictor.checkEndian)
        checkEndian(d).withPredictor(predictor)
      else
        d.withPredictor(predictor)
    }

    val segmentCount = tiffTags.segmentCount
    val segmentSizes = Array.ofDim[Int](segmentCount)
    val bandCount = tiffTags.bandCount
    if(!tiffTags.hasPixelInterleave || bandCount == 1) {
      cfor(0)(_ < segmentCount, _ + 1) { i =>
        segmentSizes(i) = tiffTags.imageSegmentByteSize(i).toInt
      }
    } else {
      cfor(0)(_ < segmentCount, _ + 1) { i =>
        segmentSizes(i) = tiffTags.imageSegmentByteSize(i).toInt * tiffTags.bandCount
      }
    }

    tiffTags.compression match {
      case Uncompressed => 
        checkEndian(NoCompression)
      case LZWCoded => 
        checkPredictor(LZWDecompressor(segmentSizes))
      case ZLibCoded | PkZipCoded => 
        checkPredictor(DeflateCompression.createDecompressor(segmentSizes))
      case PackBitsCoded => 
        checkEndian(PackBitsDecompressor(segmentSizes))

      // Unsupported compression types
      case JpegCoded =>
        val msg = "compression type JPEG is not supported by this reader."
        throw new GeoTiffReaderLimitationException(msg)
      case HuffmanCoded =>
        val msg = "compression type CCITTRLE is not supported by this reader."
        throw new GeoTiffReaderLimitationException(msg)
      case GroupThreeCoded =>
        val msg = s"compression type CCITTFAX3 is not supported by this reader."
        throw new GeoTiffReaderLimitationException(msg)
      case GroupFourCoded =>
        val msg = s"compression type CCITTFAX4 is not supported by this reader."
        throw new GeoTiffReaderLimitationException(msg)
      case JpegOldCoded =>
        val msg = "old jpeg (compression = 6) is deprecated."
        throw new MalformedGeoTiffException(msg)
      case compression =>
        val msg = s"compression type $compression is not supported by this reader."
        throw new GeoTiffReaderLimitationException(msg)
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy