2

I just started using LibTIFF.NET in my c# application to read Tiff images as heightmaps obtained from ArcGIS servers. All I need is to populate an array with image's pixel values for terrain generation based on smooth gradients. The image is a LZW compressed 32-bit Grayscale Tiff with floating point pixel values representing elevaion in meters.

It's been some days now that I struggle to return right values but all I get is just "0" values assuming it's a total black or white image!

Here's the code so far: (Updated - Read Update 1)

using (Tiff inputImage = Tiff.Open(fileName, "r"))
        {
            int width = inputImage.GetField(TiffTag.IMAGEWIDTH)[0].ToInt();
            int height = inputImage.GetField(TiffTag.IMAGELENGTH)[0].ToInt();
            int bytesPerPixel = 4;
            int count = (int)inputImage.RawTileSize(0); //Has to be: "width * height * bytesPerPixel" ?
            int resolution = (int)Math.Sqrt(count);
            byte[] inputImageData = new byte[count]; //Has to be: byte[] inputImageData = new byte[width * height * bytesPerPixel];
            int offset = 0;

            for (int i = 0; i < inputImage.NumberOfTiles(); i++)
            {
                offset += inputImage.ReadEncodedTile(i, inputImageData, offset, (int)inputImage.RawTileSize(i));
            }

            float[,] outputImageData = new float[resolution, resolution]; //Has to be: float[,] outputImageData = new float[width * height];
            int length = inputImageData.Length;
            Buffer.BlockCopy(inputImageData, 0, outputImageData, 0, length);

            using (StreamWriter sr = new StreamWriter(fileName.Replace(".tif", ".txt"))) {
                string row = "";

                for(int i = 0; i < resolution; i++) { //Change "resolution" to "width" in order to have correct array size
                    for(int j = 0; j < resolution; j++) { //Change "resolution" to "height" in order to have correct array size
                        row += outputImageData[i, j] + " ";
                    }
                    sr.Write(row.Remove(row.Length - 1) + Environment.NewLine);
                    row = "";
                }
            }
        }

Sample Files & Results: http://terraunity.com/SampleElevationTiff_Results.zip

Already searched everywhere on internet and couldn't find the solution for this specific issue. So I really appreciate the help which makes it useful for others too.

Update 1:

Changed the code based on Antti Leppänen's answer but got weird results which seems to be a bug or am I missing something? Please see uploaded zip file to see the results with new 32x32 tiff images here:

http://terraunity.com/SampleElevationTiff_Results.zip

Results:

  • LZW Compressed: RawStripSize = ArraySize = 3081 = 55x55 grid
  • Unompressed: RawStripSize = ArraySize = 65536 = 256x256 grid

Has to be: RawStripSize = ArraySize = 4096 = 32x32 grid

As you see the results, LibTIFF skips some rows and gives irrelevant orderings and it even gets worse if the image size is not power of 2!

TerraUnity
  • 21
  • 1
  • 5

3 Answers3

0

Your example file seems to be tiled tiff and not stripped. Console says:

ElevationMap.tif: Can not read scanlines from a tiled image

I changed your code to read tiles. This way it seems to be reading data.

for (int i = 0; i < inputImage.NumberOfTiles(); i++)
{
    offset += inputImage.ReadEncodedTile(i, inputImageData, offset, (int)inputImage.RawTileSize(i));
}
Antti Leppänen
  • 363
  • 2
  • 7
  • Thanks for the answer Antti, your code led me to the right direction but please see the update in the first post to see latest issues. – TerraUnity Jun 08 '15 at 11:48
  • Hi I am also trying to read pixel value from grayscale tiff using LibTiff.Net library but somehow couldn't figure out - Did anyone has working solution? In Python rasterio library, there is a function called sample where you can pass raster data, X and Y and it returns the value at that pixel. Any help would be greatly appreciated. Thanks – Tejas Patel Nov 16 '17 at 23:17
0

I know it could be late, but I had the same mistake recently and I found the solution, so it could be helpful. The mistake is in the parameter count of the function Tiff.ReadEncodedTile(tile, buffer, offset, count). It must be the decompressed bytes size, not the compressed bytes size. That's the reason why you have not all the information, because you are not saving the whole data in your buffer. See how-to-translate-tiff-readencodedtile-to-elevation-terrain-matrix-from-height.

Community
  • 1
  • 1
0

A fast method to read a floating point tiff.

public static unsafe float[,] ReadTiff(Tiff image)
{
    const int pixelStride = 4; // bytes per pixel

    int imageWidth = image.GetField(TiffTag.IMAGEWIDTH)[0].ToInt();
    int imageHeight = image.GetField(TiffTag.IMAGELENGTH)[0].ToInt();
    float[,] result = new float[imageWidth, imageHeight];

    int tileCount = image.NumberOfTiles();
    int tileWidth = image.GetField(TiffTag.TILEWIDTH)[0].ToInt();
    int tileHeight = image.GetField(TiffTag.TILELENGTH)[0].ToInt();
    int tileStride = (imageWidth + tileWidth - 1) / tileWidth;

    int bufferSize = tileWidth * tileHeight * pixelStride;
    byte[] buffer = new byte[bufferSize];
    fixed (byte* bufferPtr = buffer)
    {
        float* array = (float*)bufferPtr;

        for (int t = 0; t < tileCount; t++)
        {
            image.ReadEncodedTile(t, buffer, 0, buffer.Length);

            int x = tileWidth * (t % tileStride);
            int y = tileHeight * (t / tileStride);
            var copyWidth = Math.Min(tileWidth, imageWidth - x);
            var copyHeight = Math.Min(tileHeight, imageHeight - y);

            for (int j = 0; j < copyHeight; j++)
            {
                for (int i = 0; i < copyWidth; i++)
                {
                    result[x + i, y + j] = array[j * tileWidth + i];
                }
            }
        }
    }

    return result;
}
Axiverse
  • 1,589
  • 3
  • 14
  • 30