1 /*  $Id: image_io.cpp 618367 2020-10-19 14:54:50Z grichenk $
2  * ===========================================================================
3  *
4  *                            PUBLIC DOMAIN NOTICE
5  *               National Center for Biotechnology Information
6  *
7  *  This software/database is a "United States Government Work" under the
8  *  terms of the United States Copyright Act.  It was written as part of
9  *  the author's official duties as a United States Government employee and
10  *  thus cannot be copyrighted.  This software/database is freely available
11  *  to the public for use. The National Library of Medicine and the U.S.
12  *  Government have not placed any restriction on its use or reproduction.
13  *
14  *  Although all reasonable efforts have been taken to ensure the accuracy
15  *  and reliability of the software and data, the NLM and the U.S.
16  *  Government do not and cannot warrant the performance or results that
17  *  may be obtained by using this software or data. The NLM and the U.S.
18  *  Government disclaim all warranties, express or implied, including
19  *  warranties of performance, merchantability or fitness for any particular
20  *  purpose.
21  *
22  *  Please cite the author in any work or product based on this material.
23  *
24  * ===========================================================================
25  *
26  * Authors:  Mike DiCuccio
27  *
28  * File Description:
29  *    CImageIO -- framework for reading/writing images
30  */
31 
32 #include <ncbi_pch.hpp>
33 #include <util/image/image_io.hpp>
34 #include <util/image/image_exception.hpp>
35 #include <corelib/stream_utils.hpp>
36 #include <util/error_codes.hpp>
37 
38 #include "image_io_raw.hpp"
39 #include "image_io_bmp.hpp"
40 #include "image_io_gif.hpp"
41 #include "image_io_jpeg.hpp"
42 #include "image_io_png.hpp"
43 #include "image_io_sgi.hpp"
44 #include "image_io_tiff.hpp"
45 
46 
47 #define NCBI_USE_ERRCODE_X   Util_Image
48 
49 
50 BEGIN_NCBI_SCOPE
51 
52 //
53 // file magic signatures for various formats
54 //
55 
56 // we read at most 16 bytes
57 static const int kMaxMagic = 16;
58 
59 struct SMagicInfo
60 {
61     CImageIO::EType m_Type;
62     unsigned int    m_Length;
63     unsigned char   m_Signature[kMaxMagic];
64 };
65 static const struct SMagicInfo kMagicTable[] = {
66     { CImageIO::eBmp,     2, "BM" },
67     { CImageIO::eGif,     4, "GIF8" },
68     { CImageIO::eJpeg,    2, "\xff\xd8" },
69     { CImageIO::ePng,     4, "\x89PNG" },
70     { CImageIO::eSgi,     2, "\x1\xda" },
71     { CImageIO::eTiff,    4, "II*\0" },
72     { CImageIO::eTiff,    4, "MM\0*" }, // this one might need to be { 'M', 'M', 0, '*' }
73     { CImageIO::eXpm,     7, "/* XPM */" },
74     { CImageIO::eRaw,     4, "RAW\0" },
75 
76     // must be last
77     { CImageIO::eUnknown, 0, "" }
78 };
79 
80 // extract an image's type from its magic number
GetTypeFromMagic(const string & file)81 CImageIO::EType CImageIO::GetTypeFromMagic(const string& file)
82 {
83     CNcbiIfstream i_file (file.c_str());
84     if (!i_file) {
85         return eUnknown;
86     }
87 
88     return GetTypeFromMagic(i_file);
89 }
90 
GetTypeFromMagic(CNcbiIstream & istr)91 CImageIO::EType CImageIO::GetTypeFromMagic(CNcbiIstream& istr)
92 {
93     // magic numbers live in the first few bytes
94     unsigned char magic[kMaxMagic];
95     memset(magic, 0x00, kMaxMagic);
96 
97     istr.read((char *)magic, kMaxMagic);
98     istr.seekg(-istr.gcount(), ios::cur);
99     //CStreamUtils::Pushback(istr, (CT_CHAR_TYPE*)magic, istr.gcount());
100 
101     EType type = eUnknown;
102 
103     // we just compare against our (small) table of known image magic values
104     for (const SMagicInfo* i = kMagicTable;  i->m_Length;  ++i) {
105         if (memcmp(magic, i->m_Signature, i->m_Length) == 0) {
106             type = i->m_Type;
107             break;
108         }
109     }
110 
111     return type;
112 }
113 
114 
115 //
116 // magic from extension info
117 //
118 struct SExtMagicInfo
119 {
120     CImageIO::EType m_Type;
121     const char* m_Ext;
122 };
123 
124 static const SExtMagicInfo kExtensionMagicTable[] = {
125     { CImageIO::eBmp,  "bmp" },
126     { CImageIO::eGif,  "gif" },
127     { CImageIO::eJpeg, "jpeg" },
128     { CImageIO::eJpeg, "jpg" },
129     { CImageIO::ePng,  "png" },
130     { CImageIO::eSgi,  "sgi" },
131     { CImageIO::eTiff, "tif" },
132     { CImageIO::eTiff, "tiff" },
133     { CImageIO::eXpm,  "xpm" },
134     { CImageIO::eRaw,  "raw" },
135 
136     // must be last
137     { CImageIO::eUnknown, NULL }
138 };
139 
140 //
141 // GetTypeFromFileName()
142 // This function will determine a file's expected type from a given file name
143 //
GetTypeFromFileName(const string & fname)144 CImageIO::EType CImageIO::GetTypeFromFileName(const string& fname)
145 {
146     string::size_type pos = fname.find_last_of(".");
147     if (pos == string::npos) {
148         return eUnknown;
149     }
150 
151     string ext(fname.substr(pos + 1, fname.length() - pos - 1));
152     ext = NStr::ToLower(ext);
153 
154     // compare the extension against our table
155     for (const SExtMagicInfo* i = kExtensionMagicTable;  i->m_Ext;  ++i) {
156         if (ext == i->m_Ext) {
157             return i->m_Type;
158         }
159     }
160     return eUnknown;
161 }
162 
163 /// read just the image information
ReadImageInfo(const string & file,size_t * width,size_t * height,size_t * depth,EType * type)164 bool CImageIO::ReadImageInfo(const string& file,
165                              size_t* width, size_t* height, size_t* depth,
166                              EType* type)
167 {
168     CNcbiIfstream istr(file.c_str(), ios::in|ios::binary);
169     return ReadImageInfo(istr, width, height, depth, type);
170 }
171 
172 
ReadImageInfo(CNcbiIstream & istr,size_t * width,size_t * height,size_t * depth,EType * type)173 bool CImageIO::ReadImageInfo(CNcbiIstream& istr,
174                              size_t* width, size_t* height, size_t* depth,
175                              EType* type)
176 {
177     try {
178         /// first, set up a simple buffer - we hold the first 16k
179         /// or so of the image.  this will allow us to manage things like TIFF
180         /// image directories
181 
182         EType image_type = GetTypeFromMagic(istr);
183         if (type) {
184             *type = image_type;
185         }
186         CRef<CImageIOHandler> handler(x_GetHandler(image_type));
187 
188         size_t pos = istr.tellg();
189         bool res = handler->ReadImageInfo(istr, width, height, depth);
190         istr.seekg(pos, ios::beg);
191         return res;
192     }
193     catch (CImageException& e) {
194         ERR_POST_X(3, Error << "Error reading image: " << e.what());
195     }
196     return false;
197 }
198 
199 
200 
201 //
202 // ReadImage()
203 // read an image from disk, returning its contents
204 //
ReadImage(const string & file,EType type)205 CImage* CImageIO::ReadImage(const string& file, EType type)
206 {
207     CNcbiIfstream istr(file.c_str(), ios::in|ios::binary);
208     return ReadImage(istr, type);
209 }
210 
211 
ReadImage(CNcbiIstream & istr,EType type)212 CImage* CImageIO::ReadImage(CNcbiIstream& istr, EType type)
213 {
214     try {
215         if (type == eUnknown) {
216             type = GetTypeFromMagic(istr);
217         }
218         CRef<CImageIOHandler> handler(x_GetHandler(type));
219         return handler->ReadImage(istr);
220     }
221     catch (CImageException& e) {
222         ERR_POST_X(4, Error << "Error reading image: " << e.what());
223         return NULL;
224     }
225     // all other exceptions are explicitly not caught - handler in user code
226 }
227 
228 
229 //
230 // ReadSubImage()
231 // read part of an image from disk, returning its contents
232 //
ReadSubImage(const string & file,size_t x,size_t y,size_t w,size_t h,EType type)233 CImage* CImageIO::ReadSubImage(const string& file,
234                                size_t x, size_t y, size_t w, size_t h,
235                                EType type)
236 {
237     CNcbiIfstream istr(file.c_str(), ios::in|ios::binary);
238     return ReadSubImage(istr, x, y, w, h, type);
239 }
240 
241 
ReadSubImage(CNcbiIstream & istr,size_t x,size_t y,size_t w,size_t h,EType type)242 CImage* CImageIO::ReadSubImage(CNcbiIstream& istr,
243                                size_t x, size_t y, size_t w, size_t h,
244                                EType type)
245 {
246     try {
247         if (type == eUnknown) {
248             type = GetTypeFromMagic(istr);
249         }
250         CRef<CImageIOHandler> handler(x_GetHandler(type));
251         return handler->ReadImage(istr, x, y, w, h);
252     }
253     catch (CImageException& e) {
254         ERR_POST_X(5, Error << "Error reading subimage: " << e.what());
255         return NULL;
256     }
257     // all other exceptions are explicitly not caught - handler in user code
258 }
259 
260 
261 //
262 // WriteImage()
263 // write an image to disk
264 //
WriteImage(const CImage & image,const string & file,EType type,ECompress compress)265 bool CImageIO::WriteImage(const CImage& image, const string& file,
266                           EType type, ECompress compress)
267 {
268     try {
269         if (type == eUnknown) {
270             type = GetTypeFromFileName(file);
271         }
272     }
273     catch (CImageException& e) {
274         ERR_POST_X(6, Error << "Error writing image: " << e.what());
275         return false;
276     }
277 
278     CNcbiOfstream ostr(file.c_str(), ios::out|ios::binary);
279     if (ostr.good()) {
280         return WriteImage(image, ostr, type, compress);
281     }
282 
283     return false;
284 }
285 
286 
WriteImage(const CImage & image,CNcbiOstream & ostr,EType type,ECompress compress)287 bool CImageIO::WriteImage(const CImage& image, CNcbiOstream& ostr,
288                           EType type, ECompress compress)
289 {
290     try {
291         CRef<CImageIOHandler> handler(x_GetHandler(type));
292         handler->WriteImage(image, ostr, compress);
293         return true;
294     }
295     catch (CImageException& e) {
296         ERR_POST_X(7, Error << "Error writing image: " << e.what());
297         return false;
298     }
299     // all other exceptions are explicitly not caught - handler in user code
300 }
301 
302 
303 //
304 // WriteSubImage()
305 // write part of an image to disk
306 //
WriteSubImage(const CImage & image,const string & file,size_t x,size_t y,size_t w,size_t h,EType type,ECompress compress)307 bool CImageIO::WriteSubImage(const CImage& image,
308                              const string& file,
309                              size_t x, size_t y, size_t w, size_t h,
310                              EType type, ECompress compress)
311 {
312     try {
313         if (type == eUnknown) {
314             type = GetTypeFromFileName(file);
315         }
316     }
317     catch (CImageException& e) {
318         ERR_POST_X(8, Error << "Error writing image: " << e.what());
319         return false;
320     }
321 
322     CNcbiOfstream ostr(file.c_str(), ios::out|ios::binary);
323     if (ostr.good()) {
324         return WriteSubImage(image, ostr, x, y, w, h, type, compress);
325     }
326 
327     return false;
328 }
329 
330 
WriteSubImage(const CImage & image,CNcbiOstream & ostr,size_t x,size_t y,size_t w,size_t h,EType type,ECompress compress)331 bool CImageIO::WriteSubImage(const CImage& image,
332                              CNcbiOstream& ostr,
333                              size_t x, size_t y, size_t w, size_t h,
334                              EType type, ECompress compress)
335 {
336     try {
337         CRef<CImageIOHandler> handler(x_GetHandler(type));
338         handler->WriteImage(image, ostr, x, y, w, h, compress);
339         return true;
340     }
341     catch (CImageException& e) {
342         ERR_POST_X(9, Error << "Error writing image: " << e.what());
343         return false;
344     }
345     // all other exceptions are explicitly not caught - handler in user code
346 }
347 
348 
349 //
350 // x_GetImageHandler()
351 // Retrieve a CImageIOHandler-derived class to handle reading and writing for a
352 // given image formag
353 //
x_GetHandler(EType type)354 CImageIOHandler* CImageIO::x_GetHandler(EType type)
355 {
356     switch (type) {
357     default:
358     case eUnknown:
359         NCBI_THROW(CImageException, eInvalidImage,
360                    "Image format not supported");
361 
362     case eJpeg:
363         return new CImageIOJpeg();
364 
365     case eBmp:
366         return new CImageIOBmp();
367 
368     case ePng:
369         return new CImageIOPng();
370 
371     case eSgi:
372         return new CImageIOSgi();
373 
374     case eGif:
375         return new CImageIOGif();
376 
377     case eRaw:
378         return new CImageIORaw();
379 
380     case eTiff:
381         return new CImageIOTiff();
382     }
383 }
384 
385 
386 END_NCBI_SCOPE
387