/*========================================================================= Program: Visualization Toolkit Module: vtkTIFFReader.cxx,v Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen All rights reserved. See Copyright.txt or http://www.kitware.com/Copyright.htm for details. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the above copyright notice for more information. =========================================================================*/ #include "vtkTIFFReader.h" #include "vtkTIFFReaderInternal.h" #include "vtkDataArray.h" #include "vtkErrorCode.h" #include "vtkImageData.h" #include "vtkObjectFactory.h" #include "vtkPointData.h" #include "vtksys/SystemTools.hxx" #include #include #include namespace { struct FlipTrue { }; struct FlipFalse { }; int GetFileRow(int row, int height, FlipTrue) { return height - row - 1; } int GetFileRow(int row, int, FlipFalse) { return row; } // This is inverse of GetFileRow(), which is same as calling GetFileRow() // again. template int GetImageRow(int file_row, int height, Flip flip) { return GetFileRow(file_row, height, flip); } bool SupportsRandomAccess(TIFF* image) { unsigned int rowsPerStrip; unsigned short compression; TIFFGetFieldDefaulted(image, TIFFTAG_COMPRESSION, &compression); TIFFGetFieldDefaulted(image, TIFFTAG_ROWSPERSTRIP, &rowsPerStrip); return (compression == COMPRESSION_NONE || rowsPerStrip == 1); } bool PurgeInitialScanLinesIfNeeded(int fileStartRow, TIFF* image) { if (fileStartRow == 0 || SupportsRandomAccess(image)) { return true; } // File doesn't support random access and we want to start at a non-0 row. We // read (and discard) initial scanlines. unsigned int isize = TIFFScanlineSize(image); tdata_t buf = _TIFFmalloc(isize); for (int i = 0; i < fileStartRow; ++i) { if (TIFFReadScanline(image, buf, i, 0) <= 0) { _TIFFfree(buf); return false; } } _TIFFfree(buf); return true; } // Simple scan line copy of a slice in a volume with tightly packed memory. template bool ReadTemplatedImage(T* out, Flip flip, int startCol, int endCol, int startRow, int endRow, int yIncrements, unsigned int height, TIFF* image) { int fileStartRow = GetFileRow(startRow, height, flip); int fileEndRow = GetFileRow(endRow, height, flip); int minFileRow = std::min(fileStartRow, fileEndRow); int maxFileRow = std::max(fileStartRow, fileEndRow); if (!PurgeInitialScanLinesIfNeeded(minFileRow, image)) { return false; } unsigned int isize = TIFFScanlineSize(image); size_t scanLineSize = endCol - startCol + 1; if (scanLineSize * sizeof(T) == isize) { // We can copy straight into the image data output. for (int fi = minFileRow; fi <= maxFileRow; ++fi) { int i = GetImageRow(fi, height, flip); T* tmp = out + (i - startRow) * yIncrements; if (TIFFReadScanline(image, tmp, fi, 0) <= 0) { return false; } } } else { // Copy into a buffer of the appropriate size, then subset into the output. tdata_t buf = _TIFFmalloc(isize); for (int fi = minFileRow; fi <= maxFileRow; ++fi) { int i = GetImageRow(fi, height, flip); T* tmp = out + (i - startRow) * yIncrements; if (TIFFReadScanline(image, buf, fi, 0) <= 0) { _TIFFfree(buf); return false; } memcpy(tmp, static_cast(buf) + startCol, sizeof(T) * scanLineSize); } _TIFFfree(buf); } return true; } } //------------------------------------------------------------------------------ vtkStandardNewMacro(vtkTIFFReader); extern "C" { static void vtkTIFFReaderInternalErrorHandler( const char* vtkNotUsed(module), const char* vtkNotUsed(fmt), va_list vtkNotUsed(ap)) { // Do nothing // Ignore errors } } //------------------------------------------------------------------------------ bool vtkTIFFReader::vtkTIFFReaderInternal::Open(const char* filename) { this->Clean(); vtksys::SystemTools::Stat_t fs; if (vtksys::SystemTools::Stat(filename, &fs)) { return false; } this->Image = TIFFOpen(filename, "r"); if (!this->Image) { this->Clean(); return false; } if (!this->Initialize()) { this->Clean(); return false; } this->IsOpen = true; return true; } //------------------------------------------------------------------------------ void vtkTIFFReader::vtkTIFFReaderInternal::Clean() { if (this->Image) { TIFFClose(this->Image); this->Image = nullptr; } this->Width = 0; this->Height = 0; this->SamplesPerPixel = 0; this->Compression = 0; this->BitsPerSample = 0; this->Photometrics = 0; this->HasValidPhotometricInterpretation = false; this->PlanarConfig = 0; this->TileDepth = 0; this->CurrentPage = 0; this->NumberOfPages = 0; this->NumberOfTiles = 0; this->TileRows = 0; this->TileColumns = 0; this->TileWidth = 0; this->TileHeight = 0; this->XResolution = 1; this->YResolution = 1; this->SubFiles = 0; this->SampleFormat = 1; this->ResolutionUnit = 1; // none this->IsOpen = false; } //------------------------------------------------------------------------------ vtkTIFFReader::vtkTIFFReaderInternal::vtkTIFFReaderInternal() { this->Image = nullptr; // Note that this suppresses all error/warning output from libtiff! TIFFSetErrorHandler(&vtkTIFFReaderInternalErrorHandler); TIFFSetWarningHandler(&vtkTIFFReaderInternalErrorHandler); this->Clean(); } //------------------------------------------------------------------------------ bool vtkTIFFReader::vtkTIFFReaderInternal::Initialize() { if (this->Image) { if (!TIFFGetField(this->Image, TIFFTAG_IMAGEWIDTH, &this->Width) || !TIFFGetField(this->Image, TIFFTAG_IMAGELENGTH, &this->Height)) { return false; } // Get the resolution in each direction TIFFGetField(this->Image, TIFFTAG_XRESOLUTION, &this->XResolution); TIFFGetField(this->Image, TIFFTAG_YRESOLUTION, &this->YResolution); TIFFGetField(this->Image, TIFFTAG_RESOLUTIONUNIT, &this->ResolutionUnit); // Check the number of pages. First by looking at the number of directories. this->NumberOfPages = TIFFNumberOfDirectories(this->Image); if (this->NumberOfPages == 0) { if (!TIFFGetField(this->Image, TIFFTAG_PAGENUMBER, &this->CurrentPage, &this->NumberOfPages)) { // Check the Image Description tag to know the number of images // This is used by ImageJ char** description = new char*[255]; if (TIFFGetField(this->Image, TIFFTAG_IMAGEDESCRIPTION, description)) { // look for the number of images std::string desc = description[0]; std::string::size_type pos = desc.find("images="); std::string::size_type pos2 = desc.find('\n'); if ((pos != std::string::npos) && (pos2 != std::string::npos)) { this->NumberOfPages = atoi(desc.substr(pos + 7, pos2 - pos - 7).c_str()); } } } } // If the number of pages is still zero we look if the image is tiled. if (this->NumberOfPages <= 1 && TIFFIsTiled(this->Image)) { this->NumberOfTiles = TIFFNumberOfTiles(this->Image); if (!TIFFGetField(this->Image, TIFFTAG_TILEWIDTH, &this->TileWidth) || !TIFFGetField(this->Image, TIFFTAG_TILELENGTH, &this->TileHeight)) { cerr << "Cannot read tile width and height from file" << endl; } else { TileRows = this->Height / this->TileHeight; TileColumns = this->Width / this->TileWidth; } } // Checking if the TIFF contains subfiles if (this->NumberOfPages > 1) { this->SubFiles = 0; for (unsigned int page = 0; page < this->NumberOfPages; ++page) { long subfiletype = 6; if (TIFFGetField(this->Image, TIFFTAG_SUBFILETYPE, &subfiletype)) { if (subfiletype == 0) { this->SubFiles += 1; } } TIFFReadDirectory(this->Image); } // Set the directory to the first image TIFFSetDirectory(this->Image, 0); } // TIFFTAG_ORIENTATION tag from the image data and use it if available. // If the tag is not found in the image data, use the ORIENTATION_TOPLEFT by default. int status = TIFFGetField(this->Image, TIFFTAG_ORIENTATION, &this->Orientation); if (!status) { this->Orientation = ORIENTATION_TOPLEFT; } TIFFGetFieldDefaulted(this->Image, TIFFTAG_SAMPLESPERPIXEL, &this->SamplesPerPixel); TIFFGetFieldDefaulted(this->Image, TIFFTAG_COMPRESSION, &this->Compression); TIFFGetFieldDefaulted(this->Image, TIFFTAG_BITSPERSAMPLE, &this->BitsPerSample); TIFFGetFieldDefaulted(this->Image, TIFFTAG_PLANARCONFIG, &this->PlanarConfig); TIFFGetFieldDefaulted(this->Image, TIFFTAG_SAMPLEFORMAT, &this->SampleFormat); // If SamplesPerPixel is one, then PlanarConfig has no meaning and some // files have it set arbitrarily. Therefore, set it to CONTIG so that // the reader will not refuse to read the file on a technicality. if (this->SamplesPerPixel == 1) { this->PlanarConfig = PLANARCONFIG_CONTIG; } // If TIFFGetField returns false, there's no Photometric Interpretation // set for this image, but that's a required field so we set a warning flag. // (Because the "Photometrics" field is an enum, we can't rely on setting // this->Photometrics to some signal value.) this->HasValidPhotometricInterpretation = TIFFGetField(this->Image, TIFFTAG_PHOTOMETRIC, &this->Photometrics) != 0; if (!TIFFGetField(this->Image, TIFFTAG_TILEDEPTH, &this->TileDepth)) { this->TileDepth = 0; } } return true; } //------------------------------------------------------------------------------ bool vtkTIFFReader::vtkTIFFReaderInternal::CanRead() { return (this->Image && (this->Width > 0) && (this->Height > 0) && (this->SamplesPerPixel > 0) && (this->Compression == COMPRESSION_NONE || this->Compression == COMPRESSION_PACKBITS || this->Compression == COMPRESSION_LZW || this->Compression == COMPRESSION_ADOBE_DEFLATE) && (this->HasValidPhotometricInterpretation) && (this->Photometrics == PHOTOMETRIC_RGB || this->Photometrics == PHOTOMETRIC_MINISWHITE || this->Photometrics == PHOTOMETRIC_MINISBLACK || this->Photometrics == PHOTOMETRIC_PALETTE) && (this->PlanarConfig == PLANARCONFIG_CONTIG) && (!this->TileDepth) && (this->BitsPerSample == 8 || this->BitsPerSample == 16 || this->BitsPerSample == 32)); } //------------------------------------------------------------------------------ vtkTIFFReader::vtkTIFFReader() { this->Initialize(); this->InternalImage = new vtkTIFFReader::vtkTIFFReaderInternal; this->OutputExtent[0] = 0; this->OutputExtent[1] = 0; this->OutputExtent[2] = 0; this->OutputExtent[3] = 0; this->OutputExtent[4] = 0; this->OutputExtent[5] = 0; this->OutputIncrements[0] = 0; this->OutputIncrements[1] = 0; this->OutputIncrements[2] = 0; this->OrientationTypeSpecifiedFlag = false; this->OriginSpecifiedFlag = false; this->SpacingSpecifiedFlag = false; // Make the default orientation type to be ORIENTATION_TOPLEFT this->OrientationType = ORIENTATION_TOPLEFT; this->IgnoreColorMap = false; } //------------------------------------------------------------------------------ vtkTIFFReader::~vtkTIFFReader() { delete this->InternalImage; } //------------------------------------------------------------------------------ void vtkTIFFReader::ExecuteInformation() { this->Initialize(); this->ComputeInternalFileName(this->DataExtent[4]); if (this->InternalFileName == nullptr) { vtkErrorMacro("Need to specify a filename"); this->SetErrorCode(vtkErrorCode::NoFileNameError); return; } if (!this->InternalImage->Open(this->InternalFileName)) { vtkErrorMacro("Unable to open file " << this->InternalFileName << " Reason: " << vtksys::SystemTools::GetLastSystemError()); this->SetErrorCode(vtkErrorCode::CannotOpenFileError); this->DataExtent[0] = 0; this->DataExtent[1] = 0; this->DataExtent[2] = 0; this->DataExtent[3] = 0; this->DataExtent[4] = 0; this->DataExtent[5] = 0; this->SetNumberOfScalarComponents(1); this->vtkImageReader2::ExecuteInformation(); return; } // If orientation information is provided, overwrite the value // read from the tiff image if (this->OrientationTypeSpecifiedFlag) { this->InternalImage->Orientation = OrientationType; } if (!SpacingSpecifiedFlag) { this->DataSpacing[0] = 1.0; this->DataSpacing[1] = 1.0; // If we have some spacing information we use it if (this->InternalImage->ResolutionUnit > 0 && this->InternalImage->XResolution > 0 && this->InternalImage->YResolution > 0) { if (this->InternalImage->ResolutionUnit == 2) // inches { this->DataSpacing[0] = 25.4 / this->InternalImage->XResolution; this->DataSpacing[1] = 25.4 / this->InternalImage->YResolution; } else if (this->InternalImage->ResolutionUnit == 3) // cm { this->DataSpacing[0] = 10.0 / this->InternalImage->XResolution; this->DataSpacing[1] = 10.0 / this->InternalImage->YResolution; } // Somewhat arbitrary choice of using the X spacing as also the // Z spacing. Used only with image stacks. this->DataSpacing[2] = this->DataSpacing[0]; } } if (!OriginSpecifiedFlag) { this->DataOrigin[0] = 0.0; this->DataOrigin[1] = 0.0; this->DataOrigin[2] = 0.0; } // Pull out the width/height, etc. this->DataExtent[0] = 0; this->DataExtent[1] = this->InternalImage->Width - 1; this->DataExtent[2] = 0; this->DataExtent[3] = this->InternalImage->Height - 1; switch (this->GetFormat()) { case vtkTIFFReader::GRAYSCALE: case vtkTIFFReader::PALETTE_GRAYSCALE: this->SetNumberOfScalarComponents(1); break; case vtkTIFFReader::RGB: this->SetNumberOfScalarComponents(this->InternalImage->SamplesPerPixel); break; case vtkTIFFReader::PALETTE_RGB: this->SetNumberOfScalarComponents(3); break; default: this->SetNumberOfScalarComponents(4); } if (!this->InternalImage->CanRead()) { this->SetNumberOfScalarComponents(4); } // Figure out the appropriate scalar type for the data. int scalarType = VTK_SIGNED_CHAR; short sampleFormat = this->InternalImage->SampleFormat; if (this->InternalImage->BitsPerSample <= 8) { scalarType = sampleFormat == 2 ? VTK_SIGNED_CHAR : VTK_UNSIGNED_CHAR; } else if (this->InternalImage->BitsPerSample <= 16) { scalarType = sampleFormat == 2 ? VTK_SHORT : VTK_UNSIGNED_SHORT; } else if (this->InternalImage->BitsPerSample <= 32 && sampleFormat <= 2) { scalarType = sampleFormat == 2 ? VTK_INT : VTK_UNSIGNED_INT; } else if (this->InternalImage->BitsPerSample <= 32 && sampleFormat == 3) { scalarType = VTK_FLOAT; } else { vtkErrorMacro("Unhandled Bit Per Sample: " << this->InternalImage->BitsPerSample); return; } this->SetDataScalarType(scalarType); // We check if we have a Zeiss image. // Meaning that the SamplesPerPixel is 2 but the image should be treated as // an RGB image. if (this->InternalImage->SamplesPerPixel == 2) { this->SetNumberOfScalarComponents(3); } // If the tiff file is multi-pages series of tiff images (3D volume) if (this->InternalImage->NumberOfPages > 1) { if (this->InternalImage->SubFiles > 0) { this->DataExtent[4] = 0; this->DataExtent[5] = this->InternalImage->SubFiles - 1; } else { this->DataExtent[4] = 0; this->DataExtent[5] = this->InternalImage->NumberOfPages - 1; } } // If the tiff is tiled if (this->InternalImage->NumberOfTiles > 1) { this->DataExtent[0] = 0; this->DataExtent[1] = this->InternalImage->Width - 1; this->DataExtent[2] = 0; this->DataExtent[3] = this->InternalImage->Height - 1; this->DataExtent[4] = 0; this->DataExtent[5] = 0; if (!SpacingSpecifiedFlag) { this->DataSpacing[2] = 1.0; } if (!OriginSpecifiedFlag) { this->DataOrigin[2] = 0.0; } } this->vtkImageReader2::ExecuteInformation(); // Don't close the file yet, since we need the image internal // parameters such as NumberOfPages, NumberOfTiles to decide // how to read in the image. } // Set orientation type // // ORIENTATION_TOPLEFT 1 (row 0 top, col 0 lhs) // ORIENTATION_TOPRIGHT 2 (row 0 top, col 0 rhs) // ORIENTATION_BOTRIGHT 3 (row 0 bottom, col 0 rhs) // ORIENTATION_BOTLEFT 4 (row 0 bottom, col 0 lhs) // ORIENTATION_LEFTTOP 5 (row 0 lhs, col 0 top) // ORIENTATION_RIGHTTOP 6 (row 0 rhs, col 0 top) // ORIENTATION_RIGHTBOT 7 (row 0 rhs, col 0 bottom) // ORIENTATION_LEFTBOT 8 (row 0 lhs, col 0 bottom) */ void vtkTIFFReader::SetOrientationType(unsigned int orientationType) { if (orientationType < 1 || orientationType > 8) { vtkErrorMacro("Invalid Orientation type specified"); return; } if (this->OrientationType != orientationType) { this->OrientationType = orientationType; this->Modified(); } if (!this->OrientationTypeSpecifiedFlag) { this->Modified(); } // To preserve backward compatibility OrientationTypeSpecifiedFlag would // always be set to true whatever user input... this->OrientationTypeSpecifiedFlag = true; } //------------------------------------------------------------------------------ template void vtkTIFFReader::Process2(OT* outPtr, int*) { if (!this->InternalImage->Open(this->GetInternalFileName())) { return; } // if orientation information is provided, overwrite the value // read from the tiff image if (this->GetOrientationTypeSpecifiedFlag()) { this->InternalImage->Orientation = this->GetOrientationType(); } this->Initialize(); this->ReadImageInternal(outPtr); } //------------------------------------------------------------------------------ // This function reads in one data of data. // templated to handle different data types. template void vtkTIFFReader::Process(OT* outPtr, int outExtent[6], vtkIdType outIncr[3]) { // multiple number of pages if (this->InternalImage->NumberOfPages > 1) { this->ReadVolume(outPtr); // close the TIFF file this->InternalImage->Clean(); return; } // tiled image if (this->InternalImage->NumberOfTiles > 0) { this->ReadTiles(outPtr); // close the TIFF file this->InternalImage->Clean(); return; } // The input tiff dataset is neither multiple pages and nor // tiled. Hence close the image and start reading each TIFF // file this->InternalImage->Clean(); OT* outPtr2 = outPtr; for (int idx2 = outExtent[4]; idx2 <= outExtent[5]; ++idx2) { this->ComputeInternalFileName(idx2); // read in a TIFF file this->Process2(outPtr2, outExtent); // close the TIFF file this->InternalImage->Clean(); this->UpdateProgress((idx2 - outExtent[4]) / (outExtent[5] - outExtent[4] + 1.0)); outPtr2 += outIncr[2]; } } //------------------------------------------------------------------------------ // This function reads a data from a file. The datas extent/axes // are assumed to be the same as the file extent/order. void vtkTIFFReader::ExecuteDataWithInformation(vtkDataObject* output, vtkInformation* outInfo) { if (this->InternalFileName == nullptr) { vtkErrorMacro("Either a FileName or FilePrefix must be specified."); return; } this->ComputeDataIncrements(); // Get the data vtkImageData* data = this->AllocateOutputData(output, outInfo); data->GetExtent(this->OutputExtent); data->GetIncrements(this->OutputIncrements); // Call the correct templated function for the input void* outPtr = data->GetScalarPointer(); switch (data->GetScalarType()) { vtkTemplateMacro(this->Process((VTK_TT*)(outPtr), this->OutputExtent, this->OutputIncrements)); default: vtkErrorMacro("UpdateFromFile: Unknown data type"); } data->GetPointData()->GetScalars()->SetName("Tiff Scalars"); } //------------------------------------------------------------------------------ unsigned int vtkTIFFReader::GetFormat() { if (this->ImageFormat != vtkTIFFReader::NOFORMAT) { return this->ImageFormat; } switch (this->InternalImage->Photometrics) { case PHOTOMETRIC_RGB: case PHOTOMETRIC_YCBCR: this->ImageFormat = vtkTIFFReader::RGB; return this->ImageFormat; case PHOTOMETRIC_MINISWHITE: case PHOTOMETRIC_MINISBLACK: this->ImageFormat = vtkTIFFReader::GRAYSCALE; return this->ImageFormat; case PHOTOMETRIC_PALETTE: if (this->IgnoreColorMap) { // ignore color map; setting vtkTIFFReader::PALETTE_GRAYSCALE ensures // we'll pick correct number of components. this->ImageFormat = vtkTIFFReader::PALETTE_GRAYSCALE; return this->ImageFormat; } for (unsigned int cc = 0; cc < 256; ++cc) { unsigned short red, green, blue; this->GetColor(cc, &red, &green, &blue); if (red != green || red != blue) { this->ImageFormat = vtkTIFFReader::PALETTE_RGB; return this->ImageFormat; } } this->ImageFormat = vtkTIFFReader::PALETTE_GRAYSCALE; return this->ImageFormat; } this->ImageFormat = vtkTIFFReader::OTHER; return this->ImageFormat; } //------------------------------------------------------------------------------ void vtkTIFFReader::GetColor( int index, unsigned short* red, unsigned short* green, unsigned short* blue) { *red = 0; *green = 0; *blue = 0; if (index < 0) { vtkErrorMacro("Color index has to be greater than 0"); return; } if (this->TotalColors > 0 && this->ColorRed && this->ColorGreen && this->ColorBlue) { if (index >= this->TotalColors) { vtkErrorMacro( "Color index has to be less than number of colors (" << this->TotalColors << ")"); return; } *red = *(this->ColorRed + index); *green = *(this->ColorGreen + index); *blue = *(this->ColorBlue + index); return; } unsigned short photometric; if (!TIFFGetField(this->InternalImage->Image, TIFFTAG_PHOTOMETRIC, &photometric)) { if (this->InternalImage->Photometrics != PHOTOMETRIC_PALETTE) { vtkErrorMacro("You can only access colors for palette images"); return; } } unsigned short *red_orig, *green_orig, *blue_orig; switch (this->InternalImage->BitsPerSample) { case 1: case 2: case 4: case 8: case 16: break; default: vtkErrorMacro( "Sorry, can not image with " << this->InternalImage->BitsPerSample << "-bit samples"); return; } if (!TIFFGetField( this->InternalImage->Image, TIFFTAG_COLORMAP, &red_orig, &green_orig, &blue_orig)) { vtkErrorMacro("Missing required \"Colormap\" tag"); return; } this->TotalColors = (1L << this->InternalImage->BitsPerSample); if (index >= this->TotalColors) { vtkErrorMacro("Color index has to be less than number of colors (" << this->TotalColors << ")"); return; } this->ColorRed = red_orig; this->ColorGreen = green_orig; this->ColorBlue = blue_orig; *red = *(red_orig + index); *green = *(green_orig + index); *blue = *(blue_orig + index); } //------------------------------------------------------------------------------ void vtkTIFFReader::Initialize() { this->ColorRed = nullptr; this->ColorGreen = nullptr; this->ColorBlue = nullptr; this->TotalColors = -1; this->ImageFormat = vtkTIFFReader::NOFORMAT; } //------------------------------------------------------------------------------ template void vtkTIFFReader::ReadVolume(T* buffer) { int width = this->InternalImage->Width; int height = this->InternalImage->Height; int samplesPerPixel = this->InternalImage->SamplesPerPixel; unsigned int npages = this->InternalImage->NumberOfPages; int outDims[3]; vtkStructuredData::GetDimensionsFromExtent(this->OutputExtent, outDims); // counter for slices (not every page is a slice) int slice = 0; for (unsigned int page = 0; page < npages; ++page) { this->UpdateProgress(static_cast(page + 1) / npages); if (this->InternalImage->SubFiles > 0) { long subfiletype = 6; if (TIFFGetField(this->InternalImage->Image, TIFFTAG_SUBFILETYPE, &subfiletype)) { if (subfiletype != 0) { TIFFReadDirectory(this->InternalImage->Image); continue; } } } if (slice >= this->OutputExtent[4] && slice <= this->OutputExtent[5]) { // if we have a Zeiss image meaning that the SamplesPerPixel is 2 if (samplesPerPixel == 2) { if (outDims[0] != width || outDims[1] != height) { vtkErrorMacro("Case not supported currently! Please report back!"); return; } T* volume = buffer; volume += width * height * samplesPerPixel * (slice - this->OutputExtent[4]); this->ReadTwoSamplesPerPixelImage(volume, width, height); break; } else { this->ReadImageInternal(buffer + static_cast(slice - this->OutputExtent[4]) * this->OutputIncrements[2]); } } // advance to next slice slice++; TIFFReadDirectory(this->InternalImage->Image); } } /** Read a tiled tiff */ void vtkTIFFReader::ReadTiles(void* buffer) { unsigned char* volume = reinterpret_cast(buffer); unsigned char* tile = new unsigned char[TIFFTileSize(this->InternalImage->Image)]; const unsigned int width = this->InternalImage->Width; const unsigned int height = this->InternalImage->Height; const unsigned int tileWidth = this->InternalImage->TileWidth; const unsigned int tileHeight = this->InternalImage->TileHeight; const unsigned int pixelSize = this->InternalImage->SamplesPerPixel; const bool rowMultiple = height % tileHeight == 0; const bool colMultiple = width % tileWidth == 0; const bool flip = this->InternalImage->Orientation != ORIENTATION_TOPLEFT; for (unsigned int slice = 0; slice < this->InternalImage->NumberOfPages; ++slice) { for (unsigned int row = 0; row < (rowMultiple ? height : height - tileHeight); row += tileHeight) { const unsigned int r = flip ? height - row - tileHeight : row; for (unsigned int col = 0; col < (colMultiple ? width : width - tileWidth); col += tileWidth) { if (TIFFReadTile(this->InternalImage->Image, tile, col, r, slice, 0) < 0) { vtkErrorMacro(<< "Cannot read tile : " << r << "," << col << " from file"); delete[] tile; return; } // Currently not using tile depth unsigned int zz = 0; for (unsigned int yy = 0; yy < tileHeight; ++yy) { const unsigned int y = flip ? tileHeight + height % tileHeight - yy - 1 : yy; memcpy(volume + (((slice + zz) * height + row + y) * width + col) * pixelSize, tile + (zz * tileHeight + yy) * tileWidth * pixelSize, tileWidth * pixelSize); } } } } // Fill the boundaries if (!colMultiple) { const unsigned int lenx = width % tileWidth; const unsigned int col = width - lenx; const unsigned int tilexWidth = lenx; for (unsigned int row = 0; row < (rowMultiple ? height : height - tileHeight); row += tileHeight) { const unsigned int r = flip ? height - row - tileHeight - 1 : row; if (TIFFReadTile(this->InternalImage->Image, tile, col, r, 0, 0) < 0) { vtkErrorMacro(<< "Cannot read tile : " << r << "," << col << " from file"); delete[] tile; return; } const unsigned int zz = 0; for (unsigned int yy = 0; yy < tileHeight; ++yy) { const unsigned int y = flip ? tileHeight + height % tileHeight - yy - 1 : yy; memcpy(volume + (((0 + zz) * height + row + y) * width + col) * pixelSize, tile + (zz * tileHeight + yy) * tileWidth * pixelSize, tilexWidth * pixelSize); } } } if (!rowMultiple) { const unsigned int leny = height % tileHeight; const unsigned int row = height - leny; const unsigned int r = flip ? 0 : row; for (unsigned int col = 0; col < (colMultiple ? width : width - tileWidth); col += tileWidth) { if (TIFFReadTile(this->InternalImage->Image, tile, col, row, 0, 0) < 0) { vtkErrorMacro(<< "Cannot read tile : " << r << "," << col << " from file"); delete[] tile; return; } const unsigned int zz = 0; for (unsigned int yy = 0; yy < leny; ++yy) { const unsigned int y = flip ? leny - yy - 1 : yy; memcpy(volume + (((0 + zz) * height + r + y) * width + col) * pixelSize, tile + (zz * tileHeight + yy) * tileWidth * pixelSize, tileWidth * pixelSize); } } } if (!colMultiple && !rowMultiple) { const unsigned int lenx = width % tileWidth; const unsigned int col = width - lenx; const unsigned int leny = height % tileHeight; const unsigned int row = height - leny; const unsigned int tilexWidth = lenx; const unsigned int r = flip ? 0 : row; if (TIFFReadTile(this->InternalImage->Image, tile, col, row, 0, 0) < 0) { vtkErrorMacro(<< "Cannot read tile : " << r << "," << col << " from file"); delete[] tile; return; } const unsigned int zz = 0; unsigned int y; for (unsigned int yy = 0; yy < leny; ++yy) { y = flip ? leny - yy - 1 : yy; memcpy(volume + (((0 + zz) * height + r + y) * width + col) * pixelSize, tile + (zz * tileHeight + yy) * tileWidth * pixelSize, tilexWidth * pixelSize); } } delete[] tile; } /** To Support Zeiss images that contains only 2 samples per pixel but are actually * RGB images */ void vtkTIFFReader::ReadTwoSamplesPerPixelImage(void* out, unsigned int width, unsigned int height) { unsigned int isize = TIFFScanlineSize(this->InternalImage->Image); unsigned int cc; int row; tdata_t buf = _TIFFmalloc(isize); int inc = 1; if (this->GetDataScalarType() == VTK_UNSIGNED_CHAR) { unsigned char* image; if (this->InternalImage->PlanarConfig == PLANARCONFIG_CONTIG) { for (row = 0; row < (int)height; row++) { if (TIFFReadScanline(this->InternalImage->Image, buf, row, 0) <= 0) { vtkErrorMacro(<< "Problem reading the row: " << row); break; } if (this->InternalImage->Orientation == ORIENTATION_TOPLEFT) { image = reinterpret_cast(out) + row * width * inc; } else { image = reinterpret_cast(out) + width * inc * (height - (row + 1)); } for (cc = 0; cc < isize; cc += this->InternalImage->SamplesPerPixel) { inc = this->EvaluateImageAt(image, static_cast(buf) + cc); image += inc; } } } else if (this->InternalImage->PlanarConfig == PLANARCONFIG_SEPARATE) { unsigned long s; unsigned long nsamples = 0; TIFFGetField(this->InternalImage->Image, TIFFTAG_SAMPLESPERPIXEL, &nsamples); for (s = 0; s < nsamples; s++) { for (row = 0; row < (int)height; row++) { if (TIFFReadScanline(this->InternalImage->Image, buf, row, s) <= 0) { vtkErrorMacro(<< "Problem reading the row: " << row); break; } inc = 3; if (this->InternalImage->Orientation == ORIENTATION_TOPLEFT) { image = reinterpret_cast(out) + row * width * inc; } else { image = reinterpret_cast(out) + width * inc * (height - (row + 1)); } // We translate the output pixel to be on the right RGB image += s; for (cc = 0; cc < isize; cc += 1) { (*image) = *(static_cast(buf) + cc); inc = 3; image += inc; } } } } } else if (this->GetDataScalarType() == VTK_UNSIGNED_SHORT) { isize /= 2; unsigned short* image; if (this->InternalImage->PlanarConfig == PLANARCONFIG_CONTIG) { for (row = 0; row < (int)height; row++) { if (TIFFReadScanline(this->InternalImage->Image, buf, row, 0) <= 0) { vtkErrorMacro(<< "Problem reading the row: " << row); break; } if (this->InternalImage->Orientation == ORIENTATION_TOPLEFT) { image = reinterpret_cast(out) + row * width * inc; } else { image = reinterpret_cast(out) + width * inc * (height - (row + 1)); } for (cc = 0; cc < isize; cc += this->InternalImage->SamplesPerPixel) { inc = this->EvaluateImageAt(image, static_cast(buf) + cc); image += inc; } } } else if (this->InternalImage->PlanarConfig == PLANARCONFIG_SEPARATE) { unsigned long s, nsamples; TIFFGetField(this->InternalImage->Image, TIFFTAG_SAMPLESPERPIXEL, &nsamples); for (s = 0; s < nsamples; s++) { for (row = 0; row < (int)height; row++) { if (TIFFReadScanline(this->InternalImage->Image, buf, row, s) <= 0) { vtkErrorMacro(<< "Problem reading the row: " << row); break; } if (this->InternalImage->Orientation == ORIENTATION_TOPLEFT) { image = reinterpret_cast(out) + row * width * inc; } else { image = reinterpret_cast(out) + width * inc * (height - (row + 1)); } // We translate the output pixel to be on the right RGB image += s; for (cc = 0; cc < isize; cc += 1) { (*image) = *(static_cast(buf) + cc); inc = 3; image += inc; } } } } } _TIFFfree(buf); } template void vtkTIFFReader::ReadGenericImage(T* out, unsigned int, unsigned int height) { // Fast path for simple images unsigned int format = this->GetFormat(); if (this->InternalImage->PlanarConfig == PLANARCONFIG_CONTIG && this->OutputIncrements[0] == 1 && (format == vtkTIFFReader::GRAYSCALE && this->InternalImage->Photometrics == PHOTOMETRIC_MINISBLACK && this->InternalImage->SamplesPerPixel == 1)) { if (this->InternalImage->Orientation == ORIENTATION_TOPLEFT) { FlipFalse flip; if (!ReadTemplatedImage(out, flip, this->OutputExtent[0], this->OutputExtent[1], this->OutputExtent[2], this->OutputExtent[3], this->OutputIncrements[1], height, this->InternalImage->Image)) { vtkErrorMacro(<< "Problem reading slice of volume in TIFF file."); } } else { FlipTrue flip; if (!ReadTemplatedImage(out, flip, this->OutputExtent[0], this->OutputExtent[1], this->OutputExtent[2], this->OutputExtent[3], this->OutputIncrements[1], height, this->InternalImage->Image)) { vtkErrorMacro(<< "Problem reading slice of volume in TIFF file."); } } return; } unsigned int isize = TIFFScanlineSize(this->InternalImage->Image); if (this->InternalImage->PlanarConfig != PLANARCONFIG_CONTIG) { vtkErrorMacro(<< "This reader can only do PLANARCONFIG_CONTIG"); return; } tdata_t buf = _TIFFmalloc(isize); T* image; if (this->InternalImage->PlanarConfig == PLANARCONFIG_CONTIG) { int fileRow = 0; for (int row = this->OutputExtent[2]; row <= this->OutputExtent[3]; ++row) { // Flip from lower left origin to upper left if necessary. if (this->InternalImage->Orientation == ORIENTATION_TOPLEFT) { fileRow = row; } else { fileRow = height - row - 1; } if (TIFFReadScanline(this->InternalImage->Image, buf, fileRow, 0) <= 0) { vtkErrorMacro(<< "Problem reading the row: " << fileRow); break; } image = out + (row - this->OutputExtent[2]) * this->OutputIncrements[1]; // Copy the pixels into the output buffer unsigned int cc = this->OutputExtent[0] * this->InternalImage->SamplesPerPixel; for (int ix = this->OutputExtent[0]; ix <= this->OutputExtent[1]; ++ix) { this->EvaluateImageAt(image, static_cast(buf) + cc); image += this->OutputIncrements[0]; cc += this->InternalImage->SamplesPerPixel; } } } else if (this->InternalImage->PlanarConfig == PLANARCONFIG_SEPARATE) { int fileRow = 0; unsigned long nsamples; TIFFGetField(this->InternalImage->Image, TIFFTAG_SAMPLESPERPIXEL, &nsamples); for (unsigned long s = 0; s < nsamples; ++s) { for (int row = this->OutputExtent[2]; row <= this->OutputExtent[3]; ++row) { if (this->InternalImage->Orientation == ORIENTATION_TOPLEFT) { fileRow = row; } else { fileRow = height - row - 1; } if (TIFFReadScanline(this->InternalImage->Image, buf, fileRow, s) <= 0) { vtkErrorMacro(<< "Problem reading the row: " << fileRow); break; } image = out + (row - this->OutputExtent[2]) * this->OutputIncrements[1]; unsigned int cc = this->OutputExtent[0] * this->InternalImage->SamplesPerPixel; for (int ix = this->OutputExtent[0]; ix <= this->OutputExtent[1]; ++ix) { this->EvaluateImageAt(image, static_cast(buf) + cc); image += this->OutputIncrements[0]; cc += this->InternalImage->SamplesPerPixel; } } } } _TIFFfree(buf); // release color map ptrs, if any. since color map changes with each IFD, // to avoid reading obsolete ptrs in `ReadVolume`, we just clear these // references. The overhead to doing that is minimal. The next invocation to // `ReadGenericImage` may have to fetch these points again. Note, these are // just pointers to memory internally allocated by libtiff and hence we are // not actually deep-copying the color map. this->ColorRed = this->ColorBlue = this->ColorGreen = nullptr; this->TotalColors = -1; } //------------------------------------------------------------------------------ template void vtkTIFFReader::ReadImageInternal(T* outPtr) { int width = this->InternalImage->Width; int height = this->InternalImage->Height; if (!this->InternalImage->CanRead()) { // Why do we read the image for the ! CanRead case? uint32_t* tempImage = reinterpret_cast(outPtr); if (this->OutputExtent[0] != 0 || this->OutputExtent[1] != width - 1 || this->OutputExtent[2] != 0 || this->OutputExtent[3] != height - 1) { tempImage = new uint32_t[width * height]; } // This should really be fixed to read only the rows necessary. if (!TIFFReadRGBAImage(this->InternalImage->Image, width, height, tempImage, 0)) { vtkErrorMacro("Problem reading RGB image"); if (tempImage != reinterpret_cast(outPtr)) { delete[] tempImage; } return; } const bool flip = this->InternalImage->Orientation != ORIENTATION_TOPLEFT; T* fimage = outPtr; for (int yy = 0; yy < height; ++yy) { uint32_t* ssimage = flip ? (tempImage + yy * width) : (tempImage + (height - yy - 1) * width); for (int xx = 0; xx < width; ++xx) { if (xx >= this->OutputExtent[0] && xx <= this->OutputExtent[1] && yy >= this->OutputExtent[2] && yy <= this->OutputExtent[3]) { *(fimage) = static_cast(TIFFGetR(*ssimage)); // Red *(fimage + 1) = static_cast(TIFFGetG(*ssimage)); // Green *(fimage + 2) = static_cast(TIFFGetB(*ssimage)); // Blue *(fimage + 3) = static_cast(TIFFGetA(*ssimage)); // Alpha fimage += 4; } ++ssimage; } } if (tempImage != reinterpret_cast(outPtr)) { delete[] tempImage; } return; } switch (this->GetFormat()) { case vtkTIFFReader::GRAYSCALE: case vtkTIFFReader::RGB: case vtkTIFFReader::PALETTE_RGB: case vtkTIFFReader::PALETTE_GRAYSCALE: this->ReadGenericImage(outPtr, width, height); break; default: return; } } //------------------------------------------------------------------------------ template int vtkTIFFReader::EvaluateImageAt(T* out, T* in) { unsigned char* image = reinterpret_cast(out); unsigned char* source = reinterpret_cast(in); unsigned short red, green, blue; switch (this->GetFormat()) { case vtkTIFFReader::GRAYSCALE: if (this->InternalImage->Photometrics == PHOTOMETRIC_MINISBLACK) { *out = *in; } else { *image = ~(*source); } return 1; case vtkTIFFReader::PALETTE_GRAYSCALE: if (this->IgnoreColorMap) { *out = *in; } else { this->GetColor(static_cast(*in), &red, &green, &blue); *out = static_cast(red); // red } return 1; case vtkTIFFReader::RGB: *(image) = *(source); // red *(image + 1) = *(source + 1); // green *(image + 2) = *(source + 2); // blue if (this->InternalImage->SamplesPerPixel == 4) { *(image + 3) = 255 - *(source + 3); // alpha } return this->InternalImage->SamplesPerPixel; case vtkTIFFReader::PALETTE_RGB: assert(!this->IgnoreColorMap); // if IgnoreColorMap, the format is set to PALETTE_GRAYSCALE. this->GetColor(static_cast(*in), &red, &green, &blue); *(out) = red << 8; *(out + 1) = green << 8; *(out + 2) = blue << 8; if (this->GetDataScalarType() == VTK_SHORT || this->GetDataScalarType() == VTK_UNSIGNED_SHORT) { this->GetColor(static_cast(*in), &red, &green, &blue); *(out) = red << 8; *(out + 1) = green << 8; *(out + 2) = blue << 8; } else { this->GetColor(static_cast(*in), &red, &green, &blue); *(out) = red >> 8; *(out + 1) = green >> 8; *(out + 2) = blue >> 8; } return 3; default: return 0; } } //------------------------------------------------------------------------------ int vtkTIFFReader::CanReadFile(const char* fname) { vtkTIFFReaderInternal tf; int res = tf.Open(fname); tf.Clean(); if (res) { return 3; } return 0; } //------------------------------------------------------------------------------ void vtkTIFFReader::PrintSelf(ostream& os, vtkIndent indent) { this->Superclass::PrintSelf(os, indent); os << indent << "OrientationType: " << this->OrientationType << endl; os << indent << "OrientationTypeSpecifiedFlag: " << this->OrientationTypeSpecifiedFlag << endl; os << indent << "OriginSpecifiedFlag: " << this->OriginSpecifiedFlag << endl; os << indent << "SpacingSpecifiedFlag: " << this->SpacingSpecifiedFlag << endl; os << indent << "IgnoreColorMap: " << this->IgnoreColorMap << endl; }