我正在开展一个业余项目,该项目对大量图像进行矩阵计算。为了实现这一目标,我使用了几个不同的库(包括 libpng、libjpg、libtiff 和 easybmp)来加载、可能预处理(即灰度或调整大小)和存储图像。每个库以不同的方式存储图像,这就是问题发生的地方。我想使用策略来拥有一个名为 Image 的基类和多个派生类(即 ImagePNG、ImageBMP 等),并使用工厂来根据我要加载的图像的类型来实例化对象。
我想到解决这个问题的一种方法是在基类中使用 void* 或 std::any 并将对象存储在那里。但是,我希望基类内部不包含任何对象,而只有纯虚函数,而且我不喜欢在 C++ 中进行强制转换。
另一个问题是我希望代码更快,而使用策略和工厂似乎会大大减慢速度,这就是为什么我考虑放弃它们并仅使用模板。然而,这会带来其他问题,因为模板是运行时的,而且我不确定设计,因为它会要求大量的模板特化。
好处是,我需要将所有图像作为指向 uint8_t 数组的指针返回,以便能够处理它们,这意味着每种类型的图像只有加载部分可能不同。
坏事是我仍然需要使用一些已经在我使用的库中实现的预处理。我可以自己写,但是库已经存在很长时间了,我怀疑自己写可以取得更好的性能。另外,预处理不是我的最终目标,这就是为什么如果不需要的话我宁愿不自己实现。
有人对如何设计这个有什么建议吗?或者对我提出的想法有一些反馈?
非常感谢任何帮助!
最佳答案
出于类似的目的,我将数据存储为未压缩的位图,并带有指针数组,将其映射为 2D 像素数组以进行直接像素访问。所以我认为你应该做类似的事情。在某些情况下,我还需要不同的类型。然后,保存图像数据的类具有通常为 float*
和 DWORD*
类型的描述符,以及映射到表示图像的 1D 数组的 2D 数组。这会将图像文件编码与其表示形式分开。由此,您只需要在该表示形式和文件之间进行编码/解码的加载程序/保存程序。
这是我使用的示例(基于C++/VCL):
//------------------------------------------------------------------------------
int picture_load(Graphics::TBitmap *bmp,AnsiString name,int *_alpha)
{
if (bmp==NULL) { _errorlog+="picture_load bmp is NULL\n"; return 0; }
if (!FileExists(name)){ _errorlog+="picture_load file \""+name+"\" dont exist\n"; return 0; }
bmp->HandleType=bmDIB;
bmp->PixelFormat=pf32bit;
AnsiString ext=ExtractFileExt(name).LowerCase();
for(;;)
{
if (ext==".bmp")
{
bmp->LoadFromFile(name);
break;
}
if (ext==".jpg")
{
TJPEGImage *jpg=new TJPEGImage;
#ifdef _mmap_h
if (jpg) mmap_new('GL',jpg,sizeof(TJPEGImage));
#endif
if (jpg==NULL) { _errorlog+="picture_load not enough memory\n"; return 0; }
jpg->LoadFromFile(name);
bmp->Assign(jpg);
#ifdef _mmap_h
mmap_del('GL',jpg);
#endif
delete jpg;
break;
}
if (ext==".png")
{
TPNGObject *png=new TPNGObject;
#ifdef _mmap_h
if (png) mmap_new('GL',png,sizeof(TJPEGImage));
#endif
if (png==NULL) { _errorlog+="picture_load not enough memory\n"; return 0; }
png->LoadFromFile(name);
bmp->Assign(png);
#ifdef _mmap_h
mmap_del('GL',png);
#endif
delete png;
break;
}
if ((ext==".sgi")||(ext==".rgb"))
{
sgi sss;
sss.load(name);
bmp->Width=sss.rgba->Width;
bmp->Height=sss.rgba->Height;
bmp->Canvas->Draw(0,0,sss.rgba);
break;
}
if (ext==".pcx")
{
unsigned int *p,c;
int x,y,adr;
int hnd,siz,l,xs,ys;
unsigned int pal[256],r,g,b;
Byte *dat;
for(;;)
{
hnd=FileOpen(name,fmOpenRead);
if (hnd<0) { _errorlog+="picture_load file \""+name+"\" dont exist\n"; return 0; }
siz=FileSeek(hnd,0,2);
FileSeek(hnd,0,0);
dat=new Byte[siz];
#ifdef _mmap_h
if (dat) mmap_new('GL',dat,siz*sizeof(BYTE));
#endif
if (dat==NULL) { FileClose(hnd); _errorlog+="picture_load not enough memory\n"; return 0; }
FileRead(hnd,dat,siz);
FileClose(hnd);
adr=siz-3*256;
for (l=0;l<256;l++)
{
r=dat[adr]; adr++; r&=255;
g=dat[adr]; adr++; g&=255;
b=dat[adr]; adr++; b&=255;
c=(r<<16)|(g<<8)|(b);
c&=0x00FFFFFF;
pal[l]=c;
}
xs=int(dat[ 8])-int(dat[4])+((int(dat[ 9])-int(dat[5]))<<8)+1;
ys=int(dat[10])-int(dat[6])+((int(dat[11])-int(dat[7]))<<8)+1;
bmp->HandleType=bmDIB;
bmp->PixelFormat=pf32bit;
bmp->Width=xs;
bmp->Height=ys;
xs=bmp->Width;
ys=bmp->Height;
adr=128;
for (y=0;y<ys;y++)
{
p=(unsigned int*)bmp->ScanLine[y];
for (x=0;x<xs;)
{
c=dat[adr];
if (c<192) l=1;
else{
l=c&63;
adr++;
c=dat[adr];
}
adr++;
for (;l>0;l--)
{
if (x>=xs) break;
p[x]=pal[c];
x++;
}
}
}
#ifdef _mmap_h
mmap_del('GL',dat);
#endif
delete[] dat;
break;
}
break;
}
if (ext==".dds")
{
DDS::load(bmp,name);
_errorlog+=DDS::_errorlog;
DDS::_errorlog="";
break;
}
_errorlog+="picture_load unsuported file extension \""+ext+"\"\n";
return 0;
}
bmp->HandleType=bmDIB;
if (_alpha) _alpha[0]=(bmp->PixelFormat==pf32bit);
bmp->PixelFormat=pf32bit;
return 1;
}
//------------------------------------------------------------------------------
int picture_save(Graphics::TBitmap *bmp,AnsiString name)
{
if (bmp==NULL) { _errorlog+="picture_load bmp is NULL\n"; return 0; }
bmp->HandleType=bmDIB;
bmp->PixelFormat=pf32bit;
AnsiString ext=ExtractFileExt(name).LowerCase();
for(;;)
{
if (ext==".bmp")
{
bmp->SaveToFile(name);
break;
}
if (ext==".jpg")
{
TJPEGImage *jpg=new TJPEGImage;
#ifdef _mmap_h
if (jpg) mmap_new('GL',jpg,sizeof(TJPEGImage));
#endif
if (jpg==NULL) { _errorlog+="picture_load not enough memory\n"; return 0; }
jpg->Assign(bmp);
jpg->SaveToFile(name);
#ifdef _mmap_h
mmap_del('GL',jpg);
#endif
delete jpg;
break;
}
if (ext==".png")
{
TPNGObject *png=new TPNGObject;
#ifdef _mmap_h
if (png) mmap_new('GL',png,sizeof(TJPEGImage));
#endif
if (png==NULL) { _errorlog+="picture_load not enough memory\n"; return 0; }
png->Assign(bmp);
png->SaveToFile(name);
#ifdef _mmap_h
mmap_del('GL',png);
#endif
delete png;
break;
}
_errorlog+="picture_load unsuported file extension \""+ext+"\"\n";
return 0;
}
return 1;
}
//------------------------------------------------------------------------------
所以我根据文件名扩展名决定格式,并使用适当的对象/lib/代码进行编码/解码...
我还被迫在某些用于 2D vector 数据的应用程序上使用不同的解码样式(更复杂的解码样式),这些数据从文件头检测文件格式,因为许多用户通常使用 Windows 文件资源管理器,并且经常错误地损坏扩展名,并且因为今天的用户不知道文件名和文件扩展名的含义,并且他们还倾向于重命名不应该重命名的内容。
bool decode_interface_class::load(AnsiString name)
{
int hnd=-1;
int siz=0,siz0=0;
BYTE *dat=NULL;
reset();
#ifdef decode_interface_log
decode_id.num=0;
decode_log="";
#endif
decode_cfg =true;
decode_col =true;
decode_tool=true;
decode_ext=ExtractFileExt(name).LowerCase();
decoded_ext=".";
decoded_info="";
decode_emf emf;
decode_wmf wmf;
decode_dkr dkr;
decode_dk3 dk3;
decode_box box;
decode_bxl bxl;
decode_dxf dxf;
decode_svg svg;
decode_v2x v2x;
decode_v2d v2d;
const int _size=4096;
BYTE head[_size];
#ifdef decode_interface_log
siz=0; // find minimal size
if (siz<_decode_emf_hdr) siz=_decode_emf_hdr;
if (siz<_decode_wmf_hdr) siz=_decode_wmf_hdr;
if (siz<_decode_dkr_hdr) siz=_decode_dkr_hdr;
if (siz<_decode_dk3_hdr) siz=_decode_dk3_hdr;
if (siz<_decode_box_hdr) siz=_decode_box_hdr;
if (siz<_decode_bxl_hdr) siz=_decode_bxl_hdr;
if (siz<_decode_dxf_hdr) siz=_decode_dxf_hdr;
if (siz<_decode_svg_hdr) siz=_decode_svg_hdr;
if (siz<_decode_v2x_hdr) siz=_decode_v2x_hdr;
if (siz<_decode_v2d_hdr) siz=_decode_v2d_hdr;
if (siz>_size)
{
decode_log+="Decoding header size too small needed to be "+AnsiString(siz)+" Bytes.\r\n";
}
#endif
hnd=FileOpen(name,fmOpenRead);
if (hnd<0)
{
#ifdef decode_interface_log
decode_log+="File "+name+" not found.\r\n";
#endif
return false;
}
siz=FileSeek(hnd,0,2);
FileSeek(hnd,0,0);
dat=new BYTE[siz];
if (dat==NULL)
{
#ifdef decode_interface_log
decode_log+="Not enough memory need: "+AnsiString(siz)+" Bytes.\r\n";
#endif
FileClose(hnd);
return false;
}
siz0=siz;
siz=FileRead(hnd,dat,siz);
FileClose(hnd);
if (siz!=siz0)
{
#ifdef decode_interface_log
decode_log+="Disc drive or file system error.\r\n";
#endif
}
// file signature detection
for (int i=0;i<_size;i++) if (i<siz) head[i]=dat[i]; else head[i]=0;
if (emf.is_header(head,_size,siz)) { decoded_ext=_decode_emf_ext; emf.load(this[0],dat,siz); }
else if (wmf.is_header(head,_size,siz)) { decoded_ext=_decode_wmf_ext; wmf.load(this[0],dat,siz); }
else if (dkr.is_header(head,_size,siz)) { decoded_ext=_decode_dkr_ext; dkr.load(this[0],dat,siz); }
else if (dk3.is_header(head,_size,siz)) { decoded_ext=_decode_dk3_ext; dk3.load(this[0],dat,siz); }
else if (box.is_header(head,_size,siz)) { decoded_ext=_decode_box_ext; box.load(this[0],dat,siz); }
else if (bxl.is_header(head,_size,siz)) { decoded_ext=_decode_bxl_ext; bxl.load(this[0],dat,siz); }
else if (dxf.is_header(head,_size,siz)) { decoded_ext=_decode_dxf_ext; dxf.load(this[0],dat,siz); } // toto koli rychlost ku koncu (hlada string)
else if (svg.is_header(head,_size,siz)) { decoded_ext=_decode_svg_ext; svg.load(this[0],dat,siz); } // toto koli rychlost ku koncu (hlada string)
else if (v2x.is_header(head,_size,siz)) { decoded_ext=_decode_v2x_ext; v2x.load(this[0],dat,siz); } // toto az na konci pre bezpecnost (nema signaturu)
else if (v2d.is_header(head,_size,siz)) { decoded_ext=_decode_v2d_ext; v2d.load(this[0],dat,siz); } // toto az na konci pre bezpecnost (nema signaturu)
// if fail use file extension
else if (decode_ext==_decode_emf_ext) { decoded_ext=_decode_emf_ext; emf.load(this[0],dat,siz); decoded_info="*"+decoded_info; }
else if (decode_ext==_decode_wmf_ext) { decoded_ext=_decode_wmf_ext; wmf.load(this[0],dat,siz); decoded_info="*"+decoded_info; }
else if (decode_ext==_decode_dkr_ext) { decoded_ext=_decode_dkr_ext; dkr.load(this[0],dat,siz); decoded_info="*"+decoded_info; }
else if (decode_ext==_decode_dk3_ext) { decoded_ext=_decode_dk3_ext; dk3.load(this[0],dat,siz); decoded_info="*"+decoded_info; }
else if (decode_ext==_decode_box_ext) { decoded_ext=_decode_box_ext; box.load(this[0],dat,siz); decoded_info="*"+decoded_info; }
else if (decode_ext==_decode_bxl_ext) { decoded_ext=_decode_bxl_ext; bxl.load(this[0],dat,siz); decoded_info="*"+decoded_info; }
else if (decode_ext==_decode_dxf_ext) { decoded_ext=_decode_dxf_ext; dxf.load(this[0],dat,siz); decoded_info="*"+decoded_info; }
else if (decode_ext==_decode_svg_ext) { decoded_ext=_decode_svg_ext; svg.load(this[0],dat,siz); decoded_info="*"+decoded_info; }
else if (decode_ext==_decode_v2x_ext) { decoded_ext=_decode_v2x_ext; v2x.load(this[0],dat,siz); decoded_info="*"+decoded_info; }
else if (decode_ext==_decode_v2d_ext) { decoded_ext=_decode_v2d_ext; v2d.load(this[0],dat,siz); decoded_info="*"+decoded_info; }
// if fail then error
else{
#ifdef decode_interface_log
decode_log+="File "+name+" not recognized.\r\n";
#endif
}
if (decode_cfg)
{
if (!decode_col )
{
if (decode_tool) set_cfgs (dk3_charaktool ,33);
set_colors(dk3_charakcolor,33);
}
if (!decode_tool) set_tools (dk3_charaktool ,33);
}
#ifdef decode_interface_log
if (decode_ext!=decoded_ext)
decode_log+="Wrong file extension in "+name+" should be \""+decoded_ext+"\"\r\n";
hnd=FileCreate(ExtractFilePath(Application->ExeName)+"svg_decode.log");
FileWrite(hnd,decode_log.c_str(),decode_log.Length());
FileClose(hnd);
#endif
compute();
compute_objsize();
if (dat) delete[] dat;
return true;
}
因此,我首先将最多 4096 字节加载到内存中(大小取决于支持的文件格式),并测试每个支持的文件格式的文件格式签名。第一次成功时,使用该文件格式来解码文件...
每种支持的文件类型都需要具有检测其签名的函数WMF示例:
bool decode_wmf::is_header(BYTE *head,DWORD size,DWORD filesize)
{
dst=NULL;
if (size<_decode_wmf_hdr) return 0;
if (((DWORD*)(head+0))[0]==0x9AC6CDD7) return 1; // placeable wmf
WORD *dw=(WORD*)head,a;
a=dw[0]; // type mem/file
if ((a!=0)&&(a!=1)) return 0;
a=dw[1]; // header size
if (a!=9) return 0;
// a=dw[2]; // version
// if (a!=) return 0;
return 1;
}
应仔细设计文件格式的顺序,因为最慢的解码器或最不常见的解码器应位于较快的解码器之后。此外,某些文件格式没有签名,它们的检测是通过查找格式错误来完成的。这些应该放在最后。
请注意,这两个加载程序都希望每个受支持的文件格式具有通用的数据表示形式。
关于c++ - 如何针对不同类型的图片设计加载器?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56090191/