Как спроектировать загрузчик для разных типов изображений? - PullRequest
2 голосов
/ 11 мая 2019

Я работаю над побочным проектом, который выполняет матричные вычисления для большого количества изображений.Чтобы достичь этого, я использую несколько различных библиотек (включая libpng, libjpg, libtiff и easybmp) для загрузки, возможно, предварительной обработки (то есть в оттенках серого или изменения размера) и сохранения изображений.Каждая из этих библиотек хранит изображения по-разному, и именно здесь возникает проблема.Я хочу использовать стратегию, чтобы иметь базовый класс с именем Image и несколько производных классов (например, ImagePNG, ImageBMP и т. Д.) И фабрику для создания экземпляра объекта в зависимости от типа изображения, которое я хочу загрузить.

Один из способов решить эту проблему - использовать void * или std :: any внутри базового класса и сохранить там объект.Тем не менее, я бы предпочел, чтобы в базовом классе не было никаких объектов внутри, только чисто виртуальные функции, и я не фанат приведения в C ++.

Другая проблема заключается в том, что я хочу, чтобы кодБыть быстрее, а использование стратегии и фабрики, похоже, резко замедлит их, поэтому я подумал об их отбрасывании и использовании только шаблонов.Тем не менее, это может привести к другим проблемам, так как шаблоны являются средой выполнения, и я не уверен в дизайне, поскольку это потребует большой специализации шаблонов.

Хорошо, что мне нужно возвращать все изображения какуказатель на массив uint8_t для возможности их обработки, что означает, что только загружаемая часть может быть разной для каждого типа изображения.

Плохо то, что мне все еще нужно использовать некоторую предварительную обработку, которая ужереализовано в библиотеках, которые я использую.Я мог бы написать это сам, но библиотеки уже давно существуют, и я сомневаюсь, что смогу добиться лучшей производительности, написав это сам.Кроме того, предварительная обработка не является моей конечной целью, поэтому я бы предпочел не реализовывать ее самостоятельно, если не буду этого делать.

У кого-нибудь есть какие-либо советы о том, как это сконструировать?Или какой-нибудь отзыв о моих идеях?

Любая помощь очень ценится!

Ответы [ 2 ]

1 голос
/ 12 мая 2019

Простое решение, которое я часто использую, заключается в поддержке только одного очень простого формата файла (PPM - это мой выбор для цветных изображений и PGM для изображений в оттенках серого), а изображения в памяти представлены как:

template<typename Pixel>
struct Image {
    int w, h;
    std::vector<Pixel> data;
};

Сохранение и загрузка для других форматов выполняется с помощью утилиты внешнего преобразования, такой как convert ImageMagick, с выводом на стандартный вывод и выполнением из моей программы popen.

См. https://github.com/6502/images

1 голос
/ 12 мая 2019

Для аналогичных целей я храню данные в виде несжатого растрового изображения с массивом указателей, который отображает их в виде двумерного пиксельного массива для прямого доступа к пикселям. Поэтому я думаю, что вы должны сделать что-то подобное. В некоторых случаях мне нужны также разные типы. Тогда класс, содержащий данные изображения, имеет дескриптор типа обычно float* и DWORD* и двумерный массив, сопоставленный с одномерным массивом, который представляет изображение. Это отделит кодировку файла изображения от его представления. Из этого вам просто нужен загрузчик / заставка, которая кодирует / декодирует между этим представлением и файлом.

Вот пример того, что я использую (на 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 / code для кодирования / декодирования ...

Я также был вынужден использовать другой стиль декодирования (более сложный) в некоторых приложениях для двумерных векторных данных, который определяет формат файла по его заголовкам, потому что многие пользователи обычно используют Проводник 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;
    }

Порядок форматов файлов должен быть тщательно продуман, так как самые медленные декодеры должны следовать за более быстрыми. Кроме того, некоторые файловые форматы не имеют подписи, и их обнаружение выполняется путем поиска ошибки формата. Они должны идти последними.

Осторожно, оба загрузчика ожидают общего представления данных для каждого поддерживаемого файлового формата.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...