When a person right-clicks a .tif file, then clicks Properties, then clicks the Details tab, they can see Horizontal Resolution. My question is: How can I read this number in from a .tif file using C++?
deAngel, I tried your code on a specific .tif image I have and ithe code froze. Could you send me an e-mail to steve61706#yahoo.com and then I can send you the .tif file that caused the code to freeze? I really need this to work. Thank you so much.
Use libTIFF
Read the fields (sample code here)
Profit!
If you just want to get one or two fields from the header then adding libtiff may be overkill. In this case you can parse the file yourself and locate the tags that you want, the format is quite simple to parse. See the file format details and tag reference.
Below is an example which reads the horizontal and vertical resolution from a TIFF file.
As reference I used the TIFF Revision 6.0 sections 2 and 8 (TIFF structure, IFD structure and data types).
Please note: the example only treats the LSB byte order case and only the first image in the TIFF file. A good implementation must also take into account the MSB order and multiple images per file (which could have different resolutions). I appologize for bugs that may have slipped in (unintentionally). Source was compiled with Borland C++ 3.1. I tried to keep it as simple as I could...
#include <iostream.h>
#include <fstream.h>
//converts a 4-byte LSB order array to ULong
unsigned long lsbToUL(unsigned char *buff)
{
unsigned long val=0;
int i=0;
for(i=3;i>0;i--)
val=(val+buff[i])<<8;
val=val+buff[0];
return val;
}
//converts a 2-byte LSB order array to UInt
unsigned int lsbToUI(unsigned char *buff)
{
unsigned int val=0;
val=val+buff[1];
val=val<<8;
val=val+buff[0];
return val;
}
//reads the resolution values of the first image of a TIFF file
void tiffRes(char *path, double *xRes, double *yRes, int *unit)
{
unsigned int XRES=0x011A; //TIFF XResolution tag
unsigned int YRES=0x011B; //TIFF YResolution tag
unsigned int UNIT=0x0128; //TIFF Resolution Unit tag
unsigned char buff[5];
unsigned long seekAddr=0;
unsigned int tag=0;
unsigned long xAddr=0,yAddr=0;
unsigned int resUnit=0;
ifstream f;
f.open(path,ios::binary);
if(f.fail())
{
cout<<"File error"<<endl;
return;
}
f.get(buff,3); //reads the byte order
if(buff[0]==0x49 && buff[1]==0x49)
{
//this example treats the LSB case
f.seekg(4); //seeks the offset of first IFD from header
f.get(buff,5); //reads the address of the first IFD
seekAddr=lsbToUL(buff);
seekAddr=seekAddr+2;
f.seekg(seekAddr); //seeks the addr of the first TIFF tag
//skipping the no. of directory entries as it is not used
f.get(buff,3); //reads the first TIFF tag
tag=lsbToUI(buff);
//reading the file for XRES,YRES and UNIT TIFF tags
//should be a better way of doing this
while(xAddr==0 || yAddr==0 || resUnit==0)
{
if(tag==UNIT || tag==XRES || tag==YRES)
{
if(tag==UNIT)
{
f.seekg(seekAddr+8);
f.get(buff,3); //read the Resolution Unit
resUnit=lsbToUI(buff);
*unit=resUnit;
}
else
{
f.seekg(seekAddr+8);
f.get(buff,5); //reads the address of the XRes or YRes
if(tag==XRES)
xAddr=lsbToUL(buff); //XRes address
else
yAddr=lsbToUL(buff); //YRes address
}
}
seekAddr=seekAddr+12; //computes the address of next IFD
f.seekg(seekAddr); //seeks the next IFD
f.get(buff,3); //reads the next tag
tag=lsbToUI(buff);
}
//actual reading of the resolution values
//X resolution
f.seekg(xAddr);
f.get(buff,5);
*xRes=lsbToUL(buff);
f.seekg(xAddr+4);
f.get(buff,5);
*xRes=*xRes/(double)lsbToUL(buff);
//Y resolution
f.seekg(yAddr);
f.get(buff,5);
*yRes=lsbToUL(buff);
f.seekg(yAddr+4);
f.get(buff,5);
*yRes=*yRes/(double)lsbToUL(buff);
f.close();
}
else
{
//Not covered by the example
f.close();
if(buff[0]==0x4D && buff[1]==0x4D)
cout<<"MSB mode used. Not covered by the example..."<<endl;
else
cout<<"Unkown byte order..."<<endl;
}
}
void main()
{
double *xRes=new double;
double *yRes=new double;
int *unit=new int;
typedef char string[8];
string resUnit[4]={"","NO_UNIT","INCH","CM"};
char *path="test.tif";
*xRes=0;
*yRes=0;
*unit=0;
tiffRes(path,xRes,yRes,unit);
cout<<"UNIT: "<<resUnit[(*unit)]<<endl;
cout<<"X res: "<<(*xRes)<<endl;
cout<<"Y res: "<<(*yRes)<<endl;
}
Related
I have a small bmp file and I want to get the RGB values of each pixel and output those values into a txt file if R, G, and B aren't all zero. I wrote the following program; it reads the header data correctly, but the RGB values aren't coming up. I assume I did something wrong in the for loop.
#include <iostream>
#include <fstream>
#include <cstdlib>
using namespace std;
int main()
{
ifstream ifs;
ofstream ofs;
char input[80];
char output[80];
cout<<"Input file name"<<endl;
cin>>input;
ifs.open(input, ios::binary);
if(!ifs)
{
cout<<"Error in opening file"<<endl;
system("pause");
return 0;
}
cout<<"Output file name"<<endl;
cin>>output;
ofs.open(output, ios::binary);
ifs.seekg(2);
int file_size;
ifs.read((char*)&file_size, sizeof(int));
ofs<<"Bitmap size: "<<file_size<<"\r\n";
ifs.seekg(10);
int beg;
ifs.read((char*)&beg, sizeof(int));
ofs<<"Beggining of image: "<<beg<<"\r\n";
ifs.seekg(18);
int columns;
ifs.read((char*)&columns, sizeof(int));
ofs<<"Column number: "<<columns<<"\r\n";
ifs.seekg(22);
int rows;
ifs.read((char*)&rows, sizeof(int));
ofs<<"Row number: "<<rows<<"\r\n";
int image_size=0;
columns+=(3*columns)%4;
image_size=3*columns*rows;
ofs<<"Size of image"<<image_size<<"\r\n";
ifs.seekg(beg);
unsigned char R,G,B;
for(int i=0; i<image_size; i+=3)
{
ifs.read((char*)&B, sizeof(unsigned char));
ifs.read((char*)&G, sizeof(unsigned char));
ifs.read((char*)&R, sizeof(unsigned char));
if(R!=0 || G!=0 || B!=0)
ofs<<"R: "<<R<<" G: "<<G<<" B: "<<B<<" position in file: "<<ifs.tellg()<<"\r\n";
}
system("pause");
return 0;
}
I ran the code and it works fine, I presume you mean by 'RGB values aren't coming up' you are not seeing the integer values, in which case this will fix it:
ofs<<"R: "<<int(R)<<" G: "<<int(G)<<" B: "<<int(B)<<" position in file: "<<ifs.tellg()<<"\r\n";
Update: I posted earlier that you could replace ifs.read() with ifs >> R >> G >> B; As #Benjamin Lindley points out, this is incorrect as the >> operator is for formatted text, not binary. This means if the file contains eg a space/newline/etc character, the operator will skip it and take the next char. Better to use ifs.get(char) in this simple case.
You make several assumptions on the encoding of the image that you need to check.
If you look at the BMP header, you'll see:
at offset 28 that the file doesn't necessarily have 3*8 bits per pixel as you assume. It can have 1, 4, 8, or 24 bits per pixel;
at offset 30, the compression type is specified. It can be 0 for none
(your assumption) but also be Running Length Encoding: 1=RLE-8 or 2=RLE-4.
at offset 34 you can directly read the size of image data in bytes so that you don't need to calculate it on your own.
Attention also that sizeof(int) could be in theory different from 4. It's not the problem here, but this explains the practice of using microsoft's DWORD (for int) and WORD (for short) as documented here.
I suspect that RLE is used in you file: In this case, due to the compression, you can no longer look at the pixel bytes at a fixed position: you'd need to uncompress the data first.
I'm doing an external merge sort for an assignment and I'm given two structs:
// This is the definition of a record of the input file. Contains three fields, recid, num and str
typedef struct {
unsigned int recid;
unsigned int num;
char str[STR_LENGTH];
bool valid; // if set, then this record is valid
int blockID; //The block the record belongs too -> Used only for minheap
} record_t;
// This is the definition of a block, which contains a number of fixed-sized records
typedef struct {
unsigned int blockid;
unsigned int nreserved; // how many reserved entries
record_t entries[MAX_RECORDS_PER_BLOCK]; // array of records
bool valid; // if set, then this block is valid
unsigned char misc;
unsigned int next_blockid;
unsigned int dummy;
} block_t;
Also I'm given this:
void MergeSort (char *infile, unsigned char field, block_t *buffer,
unsigned int nmem_blocks, char *outfile,
unsigned int *nsorted_segs, unsigned int *npasses,
unsigned int *nios)
Now, at phase 0 I'm allocating memory like this:
buffer = (block_t *) malloc (sizeof(block_t)*nmem_blocks);
//Allocate disc space for records in buffer
record_t *records = (record_t*)malloc(nmem_blocks*MAX_RECORDS_PER_BLOCK*sizeof(record_t));
And then after I read the records from a binary file (runs smoothly), I write them to multiple files (after sorting of course and some other steps) with this command:
outputfile = fopen(name.c_str(), "wb");
fwrite(records, recordsIndex, sizeof(record_t), outputfile);
and read like this:
fread(&buffer[b].entries[rec],sizeof(record_t),1,currentFiles[b])
And it works! Then I want to combine some of these files to produce a larger sorted file using a priority_queue turned to minheap (it's tested, it works), but when I try to write to files using this command:
outputfile = fopen(outputName.c_str(), "ab"); //Opens file for appending
fwrite(&buffer[nmem_blocks-1].entries, buffer[nmem_blocks-1].
nreserved, sizeof(record_t), outputfile);
It writes nonsense in the file, as if it reads random parts of memory.
I know that the code is probably not nearly enough, but all of it is quite large.
I'm making sure I'm closing the output file before I open it again using a new name. Also I use memset() (and not free()) to clear the buffer before I fill it again.
Finally the main problem was the way I was trying to open the file:
outputfile = fopen(outputName.c_str(), "ab"); //Opens file for appending
Instead I should have used again:
outputfile = fopen(outputName.c_str(), "wb"); //Opens file for writing to end of file
Because the file in the meantime never closed, so it was trying to open an already open file for appending and it didn't work quite well. But you couldn't have known since you didn't have the full code. Thank you for your help though! :)
This is my first post,so I am sorry if I write something wrong.I cannot understand how height and width are being extracted from the header.Here's the code till the part I am interested.
GLuint load_bmp(const char* imagepath)
{
unsigned char header[54];
unsigned int imageSize;
unsigned int dataPos;
unsigned int width;
unsigned int height;
unsigned char *data;
FILE *file=fopen(imagepath,"rb");
if(!file)
{
return false;
}
else
{
if(fread(header,1,54,file)!=54)
{
return false;
}
if((header[0]!='B')||(header[1]!='M'))
{
return false;
}
dataPos=*(int*)&header[0x0A];//This line
imageSize=*(int*)&header[0x22];//This line
height=*(int*)&header[0x12];//This line
width=*(int*)&header[0x16];//This line
}
}
How do you get the right values using these 4 lines of code?
The header is read into a buffer. The lines in question then cast addresses into that buffer as if they were pointing to binary integers, and read them.
So for example the height is a four-byte integer that is represented by the bytes from header[0x12] to header[0x15]. The code casts the address of the first byte as if it's pointing to an integer, then reads the contents of that integer pointer. I don't know if C++ has more guarantees about this sort of thing than C, but if not, then the code is making some assumptions about the size and byte representation of an int that won't work in some environments.
I am experimenting with reading the width and height of a PNG file.
This is my code:
struct TImageSize {
int width;
int height;
};
bool getPngSize(const char *fileName, TImageSize &is) {
std::ifstream file(fileName, std::ios_base::binary | std::ios_base::in);
if (!file.is_open() || !file) {
file.close();
return false;
}
// Skip PNG file signature
file.seekg(9, std::ios_base::cur);
// First chunk: IHDR image header
// Skip Chunk Length
file.seekg(4, std::ios_base::cur);
// Skip Chunk Type
file.seekg(4, std::ios_base::cur);
__int32 width, height;
file.read((char*)&width, 4);
file.read((char*)&height, 4);
std::cout << file.tellg();
is.width = width;
is.height = height;
file.close();
return true;
}
If I try to read for example from this image from Wikipedia, I'm getting these wrong values:
252097920 (should be 800)
139985408 (should be 600)
Note that the function is not returning false so the contents of the width and height variables must come from the file.
It looks like you're off by a byte:
// Skip PNG file signature
file.seekg(9, std::ios_base::cur);
The PNG Specification says the header is 8 bytes long, so you want that "9" to be an "8" instead. Positions start at 0.
Also note that the spec says that integers are in network (big-endian) order, so you may want or need to use ntohl() or otherwise convert byte order if you're on a little-endian system.
It's probably worth using libpng or stb_image or something similar rather than attempting to parse the png yourself, though -- unless you're doing this to learn.
When you look at Portable Network Graphics Technical details, it says the signature is 8 bytes not 9.
Plus, are you sure your system has the same byte order as the PNG standard? ntohl(3) will ensure the correct byte order. It's available for windows also.
I was making a function to read a file containing some dumped data (sequence of 1 byte values). As the dumped values were 1 byte each, I read them as chars. I opened the file in binary mode, read the data as chars and did a casting into int (so I get the ascii codes). But the data read isn't correct (compared in a hex-editor). Here's my code:
int** read_data(char* filename, int** data, int& height, int& width)
{
data=new int*[height];
int row,col;
ifstream infile;
infile.open(filename,ios::binary|ios::in);
if(!infile.good())
{
return 0;
}
char* ch= new char[width];
for(row=0; row<height; row++)
{
data[row]=new int[width];
infile.read(ch,width);
for(col=0; col<width; col++)
{
data[row][col]=int(ch[col]);
cout<<data[row][col]<<" ";
}
cout<<endl;
}
infile.close();
return data;
}
Any ideas what might be wrong with this code?
My machine is windows, I'm using Visual Studio 2005 and the (exact) filename that i passed is:
"D:\\files\\output.dat"
EDIT: If I don't use unsigned char, the first 8 values, which are all 245, are read as -11.
I think, you might have to use unsigned char and unsigned int to get correct results. In your code, the bytes you read are interpreted as signed values. I assume you did not intend that.
Your error seems to cover in using of char* for ch. When you try to output it, all chars are printed until the first zero value.
A plain char can be either signed or unsigned, depending on the compiler. To get a consistent (and correct) result, you can cast the value to unsigned char before assigning to the int.
data[row][col]=static_cast<unsigned char>(ch[col]);