问题
I'm working on a side project that does matrix calculations over a large number of images. To achieve this, I'm using several different libraries (including libpng, libjpg, libtiff and easybmp) to load, maybe preprocess (i.e. grayscale or resize), and store the images. Each of these libraries stores the images differently, and this is where the issue occurs. I want to use strategy to have a base class named Image and multiple derived classes (i.e. ImagePNG, ImageBMP, etc.), and factory to instantiate an object depending of the type of the image I want to load.
One way I thought of solving this was by using void* or std::any inside the base class and store the object there. However, I'd prefer if the base class wouldn't have any objects inside it, only pure virtual functions and I'm not a fan of casting in C++.
Another issue is that I want the code to be faster, and using strategy and factory seems like it's going to slow it down drastically, which is why I thought of dropping them and use templates only. However, that would provide other issues, since templates are runtime and I'm not sure about the design since it would ask for a lot of template specialization.
The good thing is that I need to return all images as a pointer to uint8_t array to be able to process them, which means that only the loading part could be different for each type of the image.
The bad thing is that I still need to use some preprocessing that is already implemented in the libraries that I use. I could write it myself, but the libraries are out there for a long time, and I doubt that I could achieve a better performance by writing it myself. Also, the preprocessing isn't my final goal, which is why I'd rather not implement it myself if I don't have to.
Does anyone have any advice on how to design this? Or some feedback on the ideas that I presented?
Any help is very much appreciated!
回答1:
For similar purposes I am storing the data as uncompressed bitmap with array of pointers that maps it as 2D pixel array for direct pixel access. So I think you should do something similar. In some cases I need also different types. Then the class holding image data has a descriptor of the type usually float*
and DWORD*
and 2D array mapped into 1D array that represents the image. This will separate image file encoding from its representation. From this you just need loader/saver that encodes/decodes between this representation and file.
Here is example what I use (C++/VCL based):
//------------------------------------------------------------------------------
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;
}
//------------------------------------------------------------------------------
So I decide the format based on the filename extention and use appropriate object/lib/code for encoding/decoding ...
I was also forced to use different decoding style (more complicated one) on some applications for 2D vector data that detects the file format from its headers because many users are using usually Windows File Explorer and very often corrupt the extensions by mistake and as todays users do not know what filename and file extentions means and they also tend to rename what they should not by design.
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;
}
So I load first up to 4096 Bytes into memory (the size depends on supported fileformats), and test for fileformat signature of each supported fileformat. On first success use that fileformat to decode the file...
Each supported filetype need to have function that detects its signature here WMF example:
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;
}
The order of the fileformats should be carefully designed as the slowest decoders or the least common ones should go after the faster ones. Also some fileformats does not have signature and their detection is done by looking for format bug. Those should go last.
Beware both of the loaders expects common data representation for every supported fileformat.
回答2:
A simple solution I am using often is to support only one very simple file format (PPM
is my choice for color images and PGM
for grayscale images) and having images in memory represented by somewhat like:
template<typename Pixel>
struct Image {
int w, h;
std::vector<Pixel> data;
};
Saving and loading for other formats is done calling an external conversion utility like convert
of ImageMagick with output to stdout and executed with popen
from my program.
See https://github.com/6502/images
来源:https://stackoverflow.com/questions/56090191/how-to-design-a-loader-for-different-types-of-images