C++读取并保存Tiff文件(纯C++,不需要配置opencv、boost.GIL等环境)

C++读取并保存Tiff文件(纯C++,不需要配置opencv、boost.GIL等环境),第1张

背景说明

最近需要写一个能在LINUX上读取tif图像、修改像素并保存的C文件,之前都是matlab直接调函数,现在突然使用C读取着实有点懵。

找了很多网上的程序却都是需要调特定函数库,openclass="superseo">cv库的安装与配置环境在windows下跟着教程还能勉强实现,但到linux配环境属实有点难(为此还租了一个云服务器,哭~)。而且因为最后整个项目要打包到其他电脑运行,这个时候又得考虑去其他电脑是否安装opencv,或者得考虑生成静态库、动态库, ~,那对于像我这样的菜菜估计又是一大波得不到结果的 *** 作(整了一天愣是没整成功)。

boost.GIL库安装配置倒还好,但是读取图像的时候发现缺少“jerror.h”头文件,得,发现还得下载安装libjpeg、libtiff等库,就算在windows下安装好还得考虑linux下的。得,又是一大波菜菜如我得不到结果的 *** 作。(也配置了一天,最后各种报错)。

最后,下定决心自己写一个了,索性网上有一位大佬本身写过了一个纯C读取Tiff的C文件,我的代码是基于这位前辈的,这里附上前辈的github代码项目:https://github.com/mm1994uestc/tiff_lib。但我下载安装之后会报一些错误,索性就在前辈的基础上自己写一个更新版吧,我使用的是C++写的,基于C++11标准,因为tiff本身是可以存储各种数据形式的图像文件,可扩展性很强,前辈的代码只返回char,并不能根据需要得到不同的数据,据此,我利用C++模板编程实现了根据需要返回对应类型的功能,而且重载了运算符,尽最大可能让大家能用matlab的形式调用。
话不多说,下面直接附上对应代码,并且也将该项目上传到我的github仓库。

实现代码
//tiffReadSave.h
#pragma once
#include 
#include 
#include 
#include 
#include 
using namespace std;
#define MC_GET_16(__data__) ((__data__ & 0xFF) << 8) | (__data__ >> 8)
#define MC_GET_64(__data__) ((__data__ & 0xFF) << 24) | ((__data__ & 0xFF00) << 8) | ((__data__ & 0xFF0000) >> 8) | ((__data__ & 0xFF000000) >> 24)

template<typename T>
struct complex {
	T re;
	T im;
	complex() : re(T{}), im(T{}) {};
	complex(T re_) :re(re_), im(T{}) {};
	complex(T re_, T im_) :re(re_), im(im_) {};
};
typedef complex<float> cor;
typedef struct
{
	uint16_t endian; // 字节顺序标志位,值为II或者MM:II表示小字节在前,又称为little-endian,MM表示大字节在前,又称为big-endian
	uint16_t magic;  // TIFF的标志位,一般都是42
	uint32_t ifdOffset; // 第一个IFD的偏移量,可以在任意位置,但必须是在一个字的边界,也就是说必须是2的整数倍
}IFH;
typedef struct
{
	uint16_t tag;  // 此TAG的唯一标识
	uint16_t type; // 数据类型
	uint32_t size; // 数量,通过类型和数量可以确定存储此TAG的数据需要占据的字节数
	uint32_t valOffset;
}DE;

typedef struct
{
	uint16_t n; // 表示此IFD包含了多少个DE,假设数目为n
	DE* p; // n个DE
	uint32_t nextIfdOffset; // 下一个IFD的偏移量,如果没有则置为0
}IFD;
typedef struct imgInfo
{
	uint32_t width; // 表示图像宽度
	uint32_t height;  // 表示图像高度
	uint8_t channel;  // 表示图像通道数
	uint64_t ImageStart;// 图像数据起始地址存储位置相对于文件开始的位置val的保存位置
	uint8_t ImageStartTypeSize;//
	uint16_t byteNum;
	uint16_t typeLabel;
}imInfo;
/*
* 测试机器是大端(big endian:返回false)还是小端(little endian:返回true)
*/
inline bool mashineEndian(void)
{
	uint16_t a = 1;
	uint16_t* p = &a;
	return *((uint8_t*)p) == a;
}
typedef union {
	uint8_t piexl_u8;
	uint16_t piexl_u16;
	uint32_t piexl_u32;
	uint64_t piexl_u64;
	int8_t piexl_i8;
	int16_t piexl_i16;
	int32_t piexl_i32;
	int64_t piexl_i64;
	float piexl_f;
	double piexl_d;
}ReturnType;
class TiffImg {
public:
	imInfo _imInfo;
	uint8_t* data;
	IFH _IFH;
	IFD _IFD;
	TiffImg(string path);
	void save(string savePath = "defaultSave.tif");
	~TiffImg() {
		save();
		free(data);
		data = nullptr;
		free(_IFD.p);
		_IFD.p = nullptr;
	}
	template<typename T>
	T& operator()(T, uint32_t row, uint32_t col, uint8_t depth = 1) {
		uint64_t sum = ((row - 1) * _imInfo.width * _imInfo.channel + (col - 1) * _imInfo.channel + (depth - 1)) * _imInfo.ImageStartTypeSize;
		return *((T*)(data + sum));
	}
private:
	string Path;
	bool EndianTrans;
	bool read();

	const uint16_t littleEndian = 0x4949;
	const uint16_t bigEndian = 0x4d4d;
};
TiffImg::TiffImg(string path) {
	Path = path;
	if (read()) {
		ifstream frd(Path, ios::in | ios::binary);
		if (frd) {//文件打开成功
			frd.seekg(static_cast<long>(_imInfo.ImageStart), ios::beg);
			for (uint32_t row = 0; row < _imInfo.height; ++row) {
				for (uint32_t col = 0; col < _imInfo.width; ++col) {
					for (uint8_t dep = 0; dep < _imInfo.channel; ++dep) {
						uint8_t* temp = data + (row * _imInfo.width * _imInfo.channel + col * _imInfo.channel + dep) * _imInfo.ImageStartTypeSize;
						frd.read(reinterpret_cast<char*>(temp), _imInfo.ImageStartTypeSize);
						if (EndianTrans) {//大小端转换,暂未完成
							;
						}
					}
				}
			}
			frd.close();
		}
	}
};
bool TiffImg::read()
{
	ifstream frd(Path, ios::in | ios::binary);
	if (frd) {//文件打开成功
		frd.read(reinterpret_cast<char*>(&(_IFH.endian)), sizeof(_IFH.endian));
		frd.read(reinterpret_cast<char*>(&(_IFH.magic)), sizeof(_IFH.magic));
		frd.read(reinterpret_cast<char*>(&(_IFH.ifdOffset)), sizeof(_IFH.ifdOffset));
		bool imEndian{ _IFH.endian == littleEndian }; //图像存储方式是大端(false)还是小端(true)
		EndianTrans = (imEndian != mashineEndian());
		if (EndianTrans) {
			_IFH.endian = MC_GET_16(_IFH.endian);
			_IFH.magic = MC_GET_16(_IFH.magic);
			_IFH.ifdOffset = MC_GET_64(_IFH.ifdOffset);
		}
		auto IFD_Start = _IFH.ifdOffset;
		frd.seekg(static_cast<long>(IFD_Start), ios::beg);
		frd.read(reinterpret_cast<char*>(&(_IFD.n)), sizeof(_IFD.n));
		if (EndianTrans) {
			_IFD.n = MC_GET_16(_IFD.n);
		}
		auto DE_n = _IFD.n;
		_IFD.p = (DE*)malloc(sizeof(DE) * DE_n);
		uint32_t DE_val_size{};
		for (uint32_t i = 0; i < DE_n; ++i) {
			frd.read(reinterpret_cast<char*>(&(_IFD.p[i].tag)), sizeof(_IFD.p[i].tag));
			frd.read(reinterpret_cast<char*>(&(_IFD.p[i].type)), sizeof(_IFD.p[i].type));
			frd.read(reinterpret_cast<char*>(&(_IFD.p[i].size)), sizeof(_IFD.p[i].size));
			frd.read(reinterpret_cast<char*>(&(_IFD.p[i].valOffset)), sizeof(_IFD.p[i].valOffset));
			if ((_IFH.endian == littleEndian) != mashineEndian()) {
				_IFD.p[i].tag = MC_GET_16(_IFD.p[i].tag);
				_IFD.p[i].type = MC_GET_16(_IFD.p[i].type);
				_IFD.p[i].size = MC_GET_64(_IFD.p[i].size);
			}

			if (EndianTrans) {
				if (DE_val_size == 1) {
					_IFD.p[i].valOffset = _IFD.p[i].valOffset;
				}
				else if (DE_val_size == 2) {
					_IFD.p[i].valOffset = MC_GET_16(_IFD.p[i].valOffset);
				}
				else if (DE_val_size >= 4) {
					_IFD.p[i].valOffset = MC_GET_64(_IFD.p[i].valOffset);
				}
			}
			switch (_IFD.p[i].tag) {
			case 0x0100: _imInfo.width = _IFD.p[i].valOffset; break;// 图像宽度存储位置
			case 0x0101: _imInfo.height = _IFD.p[i].valOffset; break;// 图像高度存储位置
			case 0x0102: {
				_imInfo.typeLabel = _IFD.p[i].type;
				switch (_IFD.p[i].type) {
				case 1:_imInfo.ImageStartTypeSize = 1;//Byte
				case 2: DE_val_size = (_IFD.p)[i].size; break;//文本类型,7位Ascii码加1位二进制0
				case 3:   _imInfo.ImageStartTypeSize = 2; DE_val_size = (_IFD.p)[i].size * sizeof(uint16_t); break;//2字节
				case 4: _imInfo.ImageStartTypeSize = 4; DE_val_size = (_IFD.p)[i].size * sizeof(uint32_t); break;//4字节
				case 5:   _imInfo.ImageStartTypeSize = 8; DE_val_size = (_IFD.p)[i].size * sizeof(uint64_t); break;//暂不准确:有理数,第一无符号Long为小数,第二个为整数部分
				case 6:  _imInfo.ImageStartTypeSize = 1; DE_val_size = (_IFD.p)[i].size * sizeof(int8_t); break;
				case 8:  _imInfo.ImageStartTypeSize = 2; DE_val_size = (_IFD.p)[i].size * sizeof(int16_t); break;
				case 9:   _imInfo.ImageStartTypeSize = 4; DE_val_size = (_IFD.p)[i].size * sizeof(int32_t); break;
				case 10:  _imInfo.ImageStartTypeSize = 8; DE_val_size = (_IFD.p)[i].size * sizeof(int64_t); break;///暂不准确:有理数,第一Long为小数,第二个为整数部分
				case 11: _imInfo.ImageStartTypeSize = 4; DE_val_size = (_IFD.p)[i].size * sizeof(float); break;
				case 12:  _imInfo.ImageStartTypeSize = 8; DE_val_size = (_IFD.p)[i].size * sizeof(double); break;
				default: _imInfo.ImageStartTypeSize = 1;  DE_val_size = (_IFD.p)[i].size; break;
				}
				break;// 颜色深度  值=1为单色,=4为16色,=8为256色。如果该类型数据个数>2个,说明是真彩图像
			}
			case 0x0103: break;// 图像是否压缩 05表示压缩
			case 262: {// 表示图像的种类: 0-WhiteisZero 1-BlackisZero 2-RGBImg 3-PaletteColor ...
				_imInfo.channel = _IFD.p[i].valOffset == 2 ? 3 : 1;
				break;
			}
			case 0x0111: {// 图像数据起始地址存储位置相对于文件开始的位置val的保存位置
				_imInfo.ImageStart = _IFD.p[i].valOffset;
				break;
			}
			case 274: break;// 图像坐标系方式: 1[left top] 2[right top] 3[bottom right] ...
			case 277: break;
			case 278: break;// 表示每一个Strip内包含的图像的行数 eg:Img[w][h][w] --> RowsPerStrip=1
			case 0x0117: {
				_imInfo.byteNum = _IFD.p[i].valOffset;
				break; // 图像字节总数,非偶数后面补零
			}
			case 284: break;// 全彩图像每一个像素点排列方式: 1[RGB...RGB]-交叉 2[[R...R],[G...G],[B...B]]-平铺
			default:  break;
			}
		}
		frd.close();
		data = (uint8_t*)malloc(_imInfo.height * _imInfo.width * _imInfo.channel * _imInfo.ImageStartTypeSize);
		return true;
	}
	return false;
}
void TiffImg::save(string savePath) {
	ofstream fd{ savePath, ios::out | ios::binary };
	if (fd.is_open()) {
		fd.seekp(0L, ios::beg);
		if (EndianTrans) {
			uint16_t Endian = (_IFH.endian == littleEndian ? bigEndian : littleEndian);
			fd.write((char*)(&(Endian)), sizeof(_IFH.endian));
		}
		else {
			fd.write((char*)&(_IFH.endian), sizeof(_IFH.endian));
		}
		fd.write((char*)&(_IFH.magic), sizeof(_IFH.magic));
		fd.write((char*)&(_IFH.ifdOffset), sizeof(_IFH.ifdOffset));
		fd.seekp(_IFH.ifdOffset, ios::beg);
		fd.write((char*)&(_IFD.n), sizeof(_IFD.n));
		for (uint16_t i = 0; i < _IFD.n; ++i) {//仅仅适用于有一个IFH的文件
			fd.write((char*)(&(_IFD.p[i].tag)), sizeof(_IFD.p->tag));
			fd.write((char*)(&(_IFD.p[i].type)), sizeof(_IFD.p->type));
			fd.write((char*)(&(_IFD.p[i].size)), sizeof(_IFD.p->size));
			fd.write((char*)(&(_IFD.p[i].valOffset)), sizeof(_IFD.p->valOffset));
		}
		fd.write((char*)(&(_IFD.nextIfdOffset)), sizeof(_IFD.nextIfdOffset)); // 下一个图像文件的入口位置,一般对于单一文件,这里为0
		fd.seekp(_imInfo.ImageStart, ios::beg);
		uint64_t byteNum = _imInfo.height * _imInfo.width * _imInfo.channel * _imInfo.ImageStartTypeSize;
		for (uint64_t i = 0; i < byteNum; ++i) {//写图像数据
			fd.write((char*)(&(data[i])), sizeof(data[i]));
		}
		fd.close();
	}

}

下面给出对应的调用方法:

#include"tiffReadSave.h"
#include 

int main() {
	TiffImg img("test.tif");//img为自定义的变量名
	//img._imInfo结构体保存了图像的高度,宽度,通道数,位深等信息
	img(uint16_t(0),149, 11,1);//这句话表示获取图像的第149行11列的第1个通道的数据(第四个参数缺省为1)
	cout << img(uint16_t(0), 149, 11);//可以看到该表达式可以放在等式右边赋值给其他变量(右值)
	img(uint16_t(0), 149, 11)=3;//可以看到该表达式可以放在等式左边被其他变量所赋值(左值)
	img.save("defaultSave.tif");//调用保存函数保存图像文件,不过忘记保存也没关系,在跑出作用域/程序结束后会自动保存的,默认保存文件名:defaultSave.tif
	//已在matlab比照数据验证过
	return 0;
	img._imInfo.typeLabel
}

菜菜属实不知道该怎么重载运算符使得可以用这种方式调用:img[149][11][1],退而求其次,简单的重载了()运算符,第一个参数是我们希望得到的数据类型,其中图像像素类型可用如下方式查看:

img._imInfo.typeLabel里面存的数值决定了图像所存的数据类型。对应关系如下:

写在最后

当前这个代码是有缺陷的,考虑到的情况有限。
比如说:
1、只考虑了有一个IFO的情况。
2、有些DE数据标签并没有读取。
3、欢迎大家评论区指出
也欢迎大佬们到我的github仓库上面完善该程序,谢谢uu们。
附:GitHub链接:https://github.com/TerrencePai/TifReadSave

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/langs/758646.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-01
下一篇 2022-05-01

发表评论

登录后才能评论

评论列表(0条)

保存