/*========================================================================= Program: Visualization Toolkit Module: vtkBMPReader.cxx 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 "vtkBMPReader.h" #include "vtkByteSwap.h" #include "vtkImageData.h" #include "vtkLookupTable.h" #include "vtkObjectFactory.h" #include "vtkPointData.h" #include vtkStandardNewMacro(vtkBMPReader); vtkBMPReader::vtkBMPReader() { this->Colors = nullptr; this->SetDataByteOrderToLittleEndian(); this->Depth = 0; // we need to create it now in case its asked for later (pointer must be valid) this->LookupTable = vtkLookupTable::New(); this->Allow8BitBMP = 0; } //------------------------------------------------------------------------------ vtkBMPReader::~vtkBMPReader() { // free any old memory delete[] this->Colors; this->Colors = nullptr; if (this->LookupTable) { this->LookupTable->Delete(); this->LookupTable = nullptr; } } //------------------------------------------------------------------------------ void vtkBMPReader::ExecuteInformation() { int xsize, ysize; FILE* fp; vtkTypeInt32 tmp; vtkTypeInt32 offset; vtkTypeInt32 infoSize; vtkTypeInt16 stmp1, stmp2; // free any old memory delete[] this->Colors; this->Colors = nullptr; // if the user has not set the extent, but has set the VOI // set the zaxis extent to the VOI z axis if (this->DataExtent[4] == 0 && this->DataExtent[5] == 0 && (this->DataVOI[4] || this->DataVOI[5])) { this->DataExtent[4] = this->DataVOI[4]; this->DataExtent[5] = this->DataVOI[5]; } this->ComputeInternalFileName(this->DataExtent[4]); if (this->InternalFileName == nullptr || this->InternalFileName[0] == '\0') { return; } // get the magic number by reading in a file fp = vtksys::SystemTools::Fopen(this->InternalFileName, "rb"); if (!fp) { vtkErrorMacro("Unable to open file " << this->InternalFileName); return; } // compare magic number to determine file type if ((fgetc(fp) != 'B') || (fgetc(fp) != 'M')) { vtkErrorMacro(<< "Unknown file type! " << this->InternalFileName << " is not a Windows BMP file!"); fclose(fp); return; } // error indicator bool errorOccurred = false; // get the size of the file // skip 4 bytes if (fread(&tmp, 4, 1, fp) != 1) { errorOccurred = true; } // skip 4 more bytes else if (fread(&tmp, 4, 1, fp) != 1) { errorOccurred = true; } // read the offset else if (fread(&offset, 4, 1, fp) != 1) { errorOccurred = true; } // get size of header else if (fread(&infoSize, 4, 1, fp) != 1) { errorOccurred = true; } if (errorOccurred) { vtkErrorMacro("Error reading file: " << this->InternalFileName << " Premature end of file."); fclose(fp); return; } vtkByteSwap::Swap4LE(&infoSize); // error checking if ((infoSize != 40) && (infoSize != 12)) { vtkErrorMacro("Unknown file type! " << this->InternalFileName << " is not a Windows BMP file!"); fclose(fp); return; } // there are two different types of BMP files if (infoSize == 40) { // now get the dimensions if (fread(&xsize, 4, 1, fp) != 1) { errorOccurred = true; } else if (fread(&ysize, 4, 1, fp) != 1) { errorOccurred = true; } vtkByteSwap::Swap4LE(&xsize); vtkByteSwap::Swap4LE(&ysize); } else { if (fread(&stmp1, 2, 1, fp) != 1) { errorOccurred = true; } else if (fread(&stmp2, 2, 1, fp) != 1) { errorOccurred = true; } vtkByteSwap::Swap2LE(&stmp1); vtkByteSwap::Swap2LE(&stmp2); xsize = stmp1; ysize = stmp2; } if (errorOccurred) { vtkErrorMacro("BMPReader error reading file: " << this->InternalFileName << " Premature EOF while reading size."); fclose(fp); return; } // is corner in upper left or lower left if (ysize < 0) { ysize = ysize * -1; this->FileLowerLeft = 0; } else { this->FileLowerLeft = 1; } // ignore planes if (fread(&stmp1, 2, 1, fp) != 1) { errorOccurred = true; } // read depth else if (fread(&stmp2, 2, 1, fp) != 1) { errorOccurred = true; } if (errorOccurred) { vtkErrorMacro("BMPReader error reading file: " << this->InternalFileName << " Premature EOF while reading depth."); fclose(fp); return; } vtkByteSwap::Swap2LE(&stmp2); this->Depth = stmp2; if ((this->Depth != 8) && (this->Depth != 24)) { vtkErrorMacro("Only BMP depths of (8,24) are supported. Not " << this->Depth); fclose(fp); return; } // skip over rest of info for long format if (infoSize == 40) { int skip[6]; if (fread(&skip, 4, 6, fp) != 6) { vtkErrorMacro("BMPReader error reading file: " << this->InternalFileName << " Premature EOF skipping rest of info for long format."); fclose(fp); return; } } // read in color table if required if (this->Depth < 24) { int numColors = 256; this->Colors = new unsigned char[numColors * 3]; for (tmp = 0; tmp < numColors; tmp++) { this->Colors[tmp * 3 + 2] = fgetc(fp); this->Colors[tmp * 3 + 1] = fgetc(fp); this->Colors[tmp * 3] = fgetc(fp); if (infoSize == 40) { fgetc(fp); } } if (this->Allow8BitBMP) { if (!this->LookupTable) { this->LookupTable = vtkLookupTable::New(); } this->LookupTable->SetNumberOfTableValues(numColors); for (tmp = 0; tmp < numColors; tmp++) { this->LookupTable->SetTableValue(tmp, this->Colors[tmp * 3 + 0] / 255.0, this->Colors[tmp * 3 + 1] / 255.0, this->Colors[tmp * 3 + 2] / 255.0, 1); } this->LookupTable->SetRange(0, 255); } } if (fclose(fp)) { vtkWarningMacro("File close failed on " << this->InternalFileName); } // Offset is the true header size. See bug 14397 vtkByteSwap::Swap4LE(&offset); this->ManualHeaderSize = 1; this->HeaderSize = offset; // if the user has set the VOI, just make sure its valid if (this->DataVOI[0] || this->DataVOI[1] || this->DataVOI[2] || this->DataVOI[3] || this->DataVOI[4] || this->DataVOI[5]) { if ((this->DataVOI[0] < 0) || (this->DataVOI[1] >= xsize) || (this->DataVOI[2] < 0) || (this->DataVOI[3] >= ysize)) { vtkWarningMacro( "The requested VOI is larger than the file's (" << this->InternalFileName << ") extent "); this->DataVOI[0] = 0; this->DataVOI[1] = xsize - 1; this->DataVOI[2] = 0; this->DataVOI[3] = ysize - 1; } } this->DataExtent[0] = 0; this->DataExtent[1] = xsize - 1; this->DataExtent[2] = 0; this->DataExtent[3] = ysize - 1; this->SetDataScalarTypeToUnsignedChar(); if ((this->Depth == 8) && this->Allow8BitBMP) { this->SetNumberOfScalarComponents(1); } else { this->SetNumberOfScalarComponents(3); } this->vtkImageReader::ExecuteInformation(); } //------------------------------------------------------------------------------ // This function opens a file to determine the file size, and to // automatically determine the header size. void vtkBMPReader::ComputeDataIncrements() { int idx; vtkIdType fileDataLength; // Determine the expected length of the data ... switch (this->DataScalarType) { case VTK_FLOAT: fileDataLength = sizeof(float); break; case VTK_INT: fileDataLength = sizeof(int); break; case VTK_SHORT: fileDataLength = sizeof(short); break; case VTK_UNSIGNED_SHORT: fileDataLength = sizeof(unsigned short); break; case VTK_UNSIGNED_CHAR: fileDataLength = sizeof(unsigned char); break; default: vtkErrorMacro(<< "Unknown DataScalarType"); return; } fileDataLength *= (this->Depth / 8); // a row must end on a 4 byte boundary // so update the Increments[1] this->DataIncrements[0] = fileDataLength; fileDataLength = fileDataLength * (this->DataExtent[1] - this->DataExtent[0] + 1); // move to 4 byte boundary fileDataLength = fileDataLength + (4 - fileDataLength % 4) % 4; // compute the fileDataLength (in units of bytes) for (idx = 1; idx < 3; ++idx) { this->DataIncrements[idx] = fileDataLength; fileDataLength = fileDataLength * (this->DataExtent[idx * 2 + 1] - this->DataExtent[idx * 2] + 1); } } //------------------------------------------------------------------------------ // This function reads in one data of data. // templated to handle different data types. template void vtkBMPReaderUpdate2(vtkBMPReader* self, vtkImageData* data, OT* outPtr) { vtkIdType inIncr[3], outIncr[3]; OT *outPtr0, *outPtr1, *outPtr2; vtkIdType streamSkip0, streamSkip1; vtkIdType streamRead; int idx0, idx1, idx2, pixelRead; int inExtent[6]; int dataExtent[6]; int pixelSkip; unsigned char* Colors; unsigned long count = 0; unsigned long target; int Keep8bit = 0; // Get the requested extents. data->GetExtent(inExtent); // Convert them into to the extent needed from the file. self->ComputeInverseTransformedExtent(inExtent, dataExtent); // get and transform the increments data->GetIncrements(inIncr); self->ComputeInverseTransformedIncrements(inIncr, outIncr); // get the color lut Colors = self->GetColors(); // are we converting to RGB or staying as 8bit if ((self->GetDepth() == 8) && self->GetAllow8BitBMP()) { Keep8bit = 1; } // compute outPtr2 outPtr2 = outPtr; if (outIncr[0] < 0) { outPtr2 = outPtr2 - outIncr[0] * (dataExtent[1] - dataExtent[0]); } if (outIncr[1] < 0) { outPtr2 = outPtr2 - outIncr[1] * (dataExtent[3] - dataExtent[2]); } if (outIncr[2] < 0) { outPtr2 = outPtr2 - outIncr[2] * (dataExtent[5] - dataExtent[4]); } // length of a row, num pixels read at a time pixelRead = dataExtent[1] - dataExtent[0] + 1; streamRead = (vtkIdType)(pixelRead * self->GetDataIncrements()[0]); streamSkip0 = (vtkIdType)(self->GetDataIncrements()[1] - streamRead); streamSkip1 = (vtkIdType)(self->GetDataIncrements()[2] - (dataExtent[3] - dataExtent[2] + 1) * self->GetDataIncrements()[1]); pixelSkip = self->GetDepth() / 8; // read from the bottom up if (!self->GetFileLowerLeft()) { streamSkip0 = (vtkIdType)(-streamRead - self->GetDataIncrements()[1]); } target = (unsigned long)((dataExtent[5] - dataExtent[4] + 1) * (dataExtent[3] - dataExtent[2] + 1) / 50.0); target++; if (self->GetFileDimensionality() == 3) { if (!self->OpenAndSeekFile(dataExtent, 0)) { return; } } // create a buffer to hold a row of the data std::vector buf(streamRead); // read the data row by row for (idx2 = dataExtent[4]; idx2 <= dataExtent[5]; ++idx2) { if (self->GetFileDimensionality() == 2) { if (!self->OpenAndSeekFile(dataExtent, idx2)) { return; } } outPtr1 = outPtr2; for (idx1 = dataExtent[2]; !self->AbortExecute && idx1 <= dataExtent[3]; ++idx1) { if (!(count % target)) { self->UpdateProgress(count / (50.0 * target)); } count++; outPtr0 = outPtr1; // read the row. if (!self->GetFile()->read(reinterpret_cast(buf.data()), streamRead)) { vtkErrorWithObjectMacro(self, "File operation failed. row = " << idx1 << ", Read = " << streamRead << ", Skip0 = " << streamSkip0 << ", Skip1 = " << streamSkip1 << ", FilePos = " << static_cast(self->GetFile()->tellg()) << ", FileName = " << self->GetInternalFileName()); self->CloseFile(); return; } // copy the bytes into the typed data auto inPtr = buf.cbegin(); for (idx0 = dataExtent[0]; idx0 <= dataExtent[1]; ++idx0) { // Copy pixel into the output. if (self->GetDepth() == 8 && !Keep8bit) { outPtr0[0] = (OT)(Colors[inPtr[0] * 3]); outPtr0[1] = (OT)(Colors[inPtr[0] * 3 + 1]); outPtr0[2] = (OT)(Colors[inPtr[0] * 3 + 2]); } else if (self->GetDepth() == 8 && Keep8bit) { outPtr0[0] = (OT)(inPtr[0]); } else { outPtr0[0] = (OT)(inPtr[2]); outPtr0[1] = (OT)(inPtr[1]); outPtr0[2] = (OT)(inPtr[0]); } // move to next pixel inPtr += pixelSkip; outPtr0 += outIncr[0]; } // move to the next row in the file and data self->GetFile()->seekg(static_cast(self->GetFile()->tellg()) + streamSkip0, ios::beg); outPtr1 += outIncr[1]; } // move to the next image in the file and data self->GetFile()->seekg(static_cast(self->GetFile()->tellg()) + streamSkip1, ios::beg); outPtr2 += outIncr[2]; } self->CloseFile(); // delete the temporary buffer } //------------------------------------------------------------------------------ // This function reads a data from a file. The datas extent/axes // are assumed to be the same as the file extent/order. void vtkBMPReader::ExecuteDataWithInformation(vtkDataObject* output, vtkInformation* outInfo) { vtkImageData* data = this->AllocateOutputData(output, outInfo); if (this->UpdateExtentIsEmpty(outInfo, output)) { return; } if (this->InternalFileName == nullptr) { vtkErrorMacro(<< "Either a FileName or FilePrefix must be specified."); return; } data->GetPointData()->GetScalars()->SetName("BMPImage"); this->ComputeDataIncrements(); // Call the correct templated function for the output void* outPtr; // Call the correct templated function for the input outPtr = data->GetScalarPointer(); switch (data->GetScalarType()) { vtkTemplateMacro(vtkBMPReaderUpdate2(this, data, static_cast(outPtr))); default: vtkErrorMacro(<< "Execute: Unknown data type"); } } //------------------------------------------------------------------------------ void vtkBMPReader::PrintSelf(ostream& os, vtkIndent indent) { this->Superclass::PrintSelf(os, indent); // this->Colors is not printed os << indent << "Depth: " << this->Depth << "\n"; os << indent << "Allow8BitBMP: " << this->Allow8BitBMP << "\n"; if (this->LookupTable) { os << indent << "LookupTable: " << this->LookupTable << "\n"; } else { os << indent << "LookupTable: nullptr\n"; } } //------------------------------------------------------------------------------ int vtkBMPReader::CanReadFile(const char* fname) { // get the magic number by reading in a file FILE* fp = vtksys::SystemTools::Fopen(fname, "rb"); if (!fp) { return 0; } // compare magic number to determine file type if ((fgetc(fp) != 'B') || (fgetc(fp) != 'M')) { fclose(fp); return 0; } vtkTypeInt32 tmp; vtkTypeInt32 infoSize = 0; // error indicator bool errorOccurred = false; // skip 4 bytes if (fread(&tmp, 4, 1, fp) != 1) { errorOccurred = true; } // skip 4 more bytes else if (fread(&tmp, 4, 1, fp) != 1) { errorOccurred = true; } // read the offset else if (fread(&tmp, 4, 1, fp) != 1) { errorOccurred = true; } // get size of header else if (fread(&infoSize, 4, 1, fp) != 1) { infoSize = 0; errorOccurred = true; } vtkByteSwap::Swap4LE(&infoSize); // error checking if ((infoSize != 40) && (infoSize != 12)) { errorOccurred = true; } fclose(fp); return !errorOccurred; }