1 // Copyright (c) 2018, ETH Zurich and UNC Chapel Hill.
2 // All rights reserved.
3 //
4 // Redistribution and use in source and binary forms, with or without
5 // modification, are permitted provided that the following conditions are met:
6 //
7 //     * Redistributions of source code must retain the above copyright
8 //       notice, this list of conditions and the following disclaimer.
9 //
10 //     * Redistributions in binary form must reproduce the above copyright
11 //       notice, this list of conditions and the following disclaimer in the
12 //       documentation and/or other materials provided with the distribution.
13 //
14 //     * Neither the name of ETH Zurich and UNC Chapel Hill nor the names of
15 //       its contributors may be used to endorse or promote products derived
16 //       from this software without specific prior written permission.
17 //
18 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19 // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20 // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21 // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
22 // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
23 // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
24 // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
25 // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
26 // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
27 // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28 // POSSIBILITY OF SUCH DAMAGE.
29 //
30 // Author: Johannes L. Schoenberger (jsch-at-demuc-dot-de)
31 
32 #include "util/bitmap.h"
33 
34 #include <unordered_map>
35 
36 #include <boost/filesystem/operations.hpp>
37 #include <boost/regex.hpp>
38 
39 #include "base/camera_database.h"
40 #include "VLFeat/imopv.h"
41 #include "util/logging.h"
42 #include "util/math.h"
43 #include "util/misc.h"
44 
45 namespace colmap {
46 
Bitmap()47 Bitmap::Bitmap()
48     : data_(nullptr, &FreeImage_Unload), width_(0), height_(0), channels_(0) {}
49 
Bitmap(const Bitmap & other)50 Bitmap::Bitmap(const Bitmap& other) : Bitmap() {
51   if (other.data_) {
52     SetPtr(FreeImage_Clone(other.data_.get()));
53   }
54 }
55 
Bitmap(Bitmap && other)56 Bitmap::Bitmap(Bitmap&& other) : Bitmap() {
57   data_ = std::move(other.data_);
58   width_ = other.width_;
59   height_ = other.height_;
60   channels_ = other.channels_;
61 }
62 
Bitmap(FIBITMAP * data)63 Bitmap::Bitmap(FIBITMAP* data) : Bitmap() { SetPtr(data); }
64 
operator =(const Bitmap & other)65 Bitmap& Bitmap::operator=(const Bitmap& other) {
66   if (other.data_) {
67     SetPtr(FreeImage_Clone(other.data_.get()));
68   }
69   return *this;
70 }
71 
operator =(Bitmap && other)72 Bitmap& Bitmap::operator=(Bitmap&& other) {
73   if (this != &other) {
74     data_ = std::move(other.data_);
75     width_ = other.width_;
76     height_ = other.height_;
77     channels_ = other.channels_;
78   }
79   return *this;
80 }
81 
Allocate(const int width,const int height,const bool as_rgb)82 bool Bitmap::Allocate(const int width, const int height, const bool as_rgb) {
83   FIBITMAP* data = nullptr;
84   width_ = width;
85   height_ = height;
86   if (as_rgb) {
87     const int kNumBitsPerPixel = 24;
88     data = FreeImage_Allocate(width, height, kNumBitsPerPixel);
89     channels_ = 3;
90   } else {
91     const int kNumBitsPerPixel = 8;
92     data = FreeImage_Allocate(width, height, kNumBitsPerPixel);
93     channels_ = 1;
94   }
95   data_ = FIBitmapPtr(data, &FreeImage_Unload);
96   return data != nullptr;
97 }
98 
Deallocate()99 void Bitmap::Deallocate() {
100   data_.reset();
101   width_ = 0;
102   height_ = 0;
103   channels_ = 0;
104 }
105 
NumBytes() const106 size_t Bitmap::NumBytes() const {
107   if (data_) {
108     return ScanWidth() * height_;
109   } else {
110     return 0;
111   }
112 }
113 
ConvertToRawBits() const114 std::vector<uint8_t> Bitmap::ConvertToRawBits() const {
115   const unsigned int scan_width = ScanWidth();
116   const unsigned int bpp = BitsPerPixel();
117   const bool kTopDown = true;
118   std::vector<uint8_t> raw_bits(scan_width * height_, 0);
119   FreeImage_ConvertToRawBits(raw_bits.data(), data_.get(), scan_width, bpp,
120                              FI_RGBA_RED_MASK, FI_RGBA_GREEN_MASK,
121                              FI_RGBA_BLUE_MASK, kTopDown);
122   return raw_bits;
123 }
124 
ConvertToRowMajorArray() const125 std::vector<uint8_t> Bitmap::ConvertToRowMajorArray() const {
126   std::vector<uint8_t> array(width_ * height_ * channels_);
127   size_t i = 0;
128   for (int y = 0; y < height_; ++y) {
129     const uint8_t* line = FreeImage_GetScanLine(data_.get(), height_ - 1 - y);
130     for (int x = 0; x < width_; ++x) {
131       for (int d = 0; d < channels_; ++d) {
132         array[i] = line[x * channels_ + d];
133         i += 1;
134       }
135     }
136   }
137   return array;
138 }
139 
ConvertToColMajorArray() const140 std::vector<uint8_t> Bitmap::ConvertToColMajorArray() const {
141   std::vector<uint8_t> array(width_ * height_ * channels_);
142   size_t i = 0;
143   for (int d = 0; d < channels_; ++d) {
144     for (int x = 0; x < width_; ++x) {
145       for (int y = 0; y < height_; ++y) {
146         const uint8_t* line =
147             FreeImage_GetScanLine(data_.get(), height_ - 1 - y);
148         array[i] = line[x * channels_ + d];
149         i += 1;
150       }
151     }
152   }
153   return array;
154 }
155 
GetPixel(const int x,const int y,BitmapColor<uint8_t> * color) const156 bool Bitmap::GetPixel(const int x, const int y,
157                       BitmapColor<uint8_t>* color) const {
158   if (x < 0 || x >= width_ || y < 0 || y >= height_) {
159     return false;
160   }
161 
162   const uint8_t* line = FreeImage_GetScanLine(data_.get(), height_ - 1 - y);
163 
164   if (IsGrey()) {
165     color->r = line[x];
166     return true;
167   } else if (IsRGB()) {
168     color->r = line[3 * x + FI_RGBA_RED];
169     color->g = line[3 * x + FI_RGBA_GREEN];
170     color->b = line[3 * x + FI_RGBA_BLUE];
171     return true;
172   }
173 
174   return false;
175 }
176 
SetPixel(const int x,const int y,const BitmapColor<uint8_t> & color)177 bool Bitmap::SetPixel(const int x, const int y,
178                       const BitmapColor<uint8_t>& color) {
179   if (x < 0 || x >= width_ || y < 0 || y >= height_) {
180     return false;
181   }
182 
183   uint8_t* line = FreeImage_GetScanLine(data_.get(), height_ - 1 - y);
184 
185   if (IsGrey()) {
186     line[x] = color.r;
187     return true;
188   } else if (IsRGB()) {
189     line[3 * x + FI_RGBA_RED] = color.r;
190     line[3 * x + FI_RGBA_GREEN] = color.g;
191     line[3 * x + FI_RGBA_BLUE] = color.b;
192     return true;
193   }
194 
195   return false;
196 }
197 
GetScanline(const int y) const198 const uint8_t* Bitmap::GetScanline(const int y) const {
199   CHECK_GE(y, 0);
200   CHECK_LT(y, height_);
201   return FreeImage_GetScanLine(data_.get(), height_ - 1 - y);
202 }
203 
Fill(const BitmapColor<uint8_t> & color)204 void Bitmap::Fill(const BitmapColor<uint8_t>& color) {
205   for (int y = 0; y < height_; ++y) {
206     uint8_t* line = FreeImage_GetScanLine(data_.get(), height_ - 1 - y);
207     for (int x = 0; x < width_; ++x) {
208       if (IsGrey()) {
209         line[x] = color.r;
210       } else if (IsRGB()) {
211         line[3 * x + FI_RGBA_RED] = color.r;
212         line[3 * x + FI_RGBA_GREEN] = color.g;
213         line[3 * x + FI_RGBA_BLUE] = color.b;
214       }
215     }
216   }
217 }
218 
InterpolateNearestNeighbor(const double x,const double y,BitmapColor<uint8_t> * color) const219 bool Bitmap::InterpolateNearestNeighbor(const double x, const double y,
220                                         BitmapColor<uint8_t>* color) const {
221   const int xx = static_cast<int>(std::round(x));
222   const int yy = static_cast<int>(std::round(y));
223   return GetPixel(xx, yy, color);
224 }
225 
InterpolateBilinear(const double x,const double y,BitmapColor<float> * color) const226 bool Bitmap::InterpolateBilinear(const double x, const double y,
227                                  BitmapColor<float>* color) const {
228   // FreeImage's coordinate system origin is in the lower left of the image.
229   const double inv_y = height_ - 1 - y;
230 
231   const int x0 = static_cast<int>(std::floor(x));
232   const int x1 = x0 + 1;
233   const int y0 = static_cast<int>(std::floor(inv_y));
234   const int y1 = y0 + 1;
235 
236   if (x0 < 0 || x1 >= width_ || y0 < 0 || y1 >= height_) {
237     return false;
238   }
239 
240   const double dx = x - x0;
241   const double dy = inv_y - y0;
242   const double dx_1 = 1 - dx;
243   const double dy_1 = 1 - dy;
244 
245   const uint8_t* line0 = FreeImage_GetScanLine(data_.get(), y0);
246   const uint8_t* line1 = FreeImage_GetScanLine(data_.get(), y1);
247 
248   if (IsGrey()) {
249     // Top row, column-wise linear interpolation.
250     const double v0 = dx_1 * line0[x0] + dx * line0[x1];
251 
252     // Bottom row, column-wise linear interpolation.
253     const double v1 = dx_1 * line1[x0] + dx * line1[x1];
254 
255     // Row-wise linear interpolation.
256     color->r = dy_1 * v0 + dy * v1;
257     return true;
258   } else if (IsRGB()) {
259     const uint8_t* p00 = &line0[3 * x0];
260     const uint8_t* p01 = &line0[3 * x1];
261     const uint8_t* p10 = &line1[3 * x0];
262     const uint8_t* p11 = &line1[3 * x1];
263 
264     // Top row, column-wise linear interpolation.
265     const double v0_r = dx_1 * p00[FI_RGBA_RED] + dx * p01[FI_RGBA_RED];
266     const double v0_g = dx_1 * p00[FI_RGBA_GREEN] + dx * p01[FI_RGBA_GREEN];
267     const double v0_b = dx_1 * p00[FI_RGBA_BLUE] + dx * p01[FI_RGBA_BLUE];
268 
269     // Bottom row, column-wise linear interpolation.
270     const double v1_r = dx_1 * p10[FI_RGBA_RED] + dx * p11[FI_RGBA_RED];
271     const double v1_g = dx_1 * p10[FI_RGBA_GREEN] + dx * p11[FI_RGBA_GREEN];
272     const double v1_b = dx_1 * p10[FI_RGBA_BLUE] + dx * p11[FI_RGBA_BLUE];
273 
274     // Row-wise linear interpolation.
275     color->r = dy_1 * v0_r + dy * v1_r;
276     color->g = dy_1 * v0_g + dy * v1_g;
277     color->b = dy_1 * v0_b + dy * v1_b;
278     return true;
279   }
280 
281   return false;
282 }
283 
ExifFocalLength(double * focal_length) const284 bool Bitmap::ExifFocalLength(double* focal_length) const {
285   const double max_size = std::max(width_, height_);
286 
287   //////////////////////////////////////////////////////////////////////////////
288   // Focal length in 35mm equivalent
289   //////////////////////////////////////////////////////////////////////////////
290 
291   std::string focal_length_35mm_str;
292   if (ReadExifTag(FIMD_EXIF_EXIF, "FocalLengthIn35mmFilm",
293                   &focal_length_35mm_str)) {
294     const boost::regex regex(".*?([0-9.]+).*?mm.*?");
295     boost::cmatch result;
296     if (boost::regex_search(focal_length_35mm_str.c_str(), result, regex)) {
297       const double focal_length_35 = std::stold(result[1]);
298       if (focal_length_35 > 0) {
299         *focal_length = focal_length_35 / 35.0 * max_size;
300         return true;
301       }
302     }
303   }
304 
305   //////////////////////////////////////////////////////////////////////////////
306   // Focal length in mm
307   //////////////////////////////////////////////////////////////////////////////
308 
309   std::string focal_length_str;
310   if (ReadExifTag(FIMD_EXIF_EXIF, "FocalLength", &focal_length_str)) {
311     boost::regex regex(".*?([0-9.]+).*?mm");
312     boost::cmatch result;
313     if (boost::regex_search(focal_length_str.c_str(), result, regex)) {
314       const double focal_length_mm = std::stold(result[1]);
315 
316       // Lookup sensor width in database.
317       std::string make_str;
318       std::string model_str;
319       if (ReadExifTag(FIMD_EXIF_MAIN, "Make", &make_str) &&
320           ReadExifTag(FIMD_EXIF_MAIN, "Model", &model_str)) {
321         CameraDatabase database;
322         double sensor_width;
323         if (database.QuerySensorWidth(make_str, model_str, &sensor_width)) {
324           *focal_length = focal_length_mm / sensor_width * max_size;
325           return true;
326         }
327       }
328 
329       // Extract sensor width from EXIF.
330       std::string pixel_x_dim_str;
331       std::string x_res_str;
332       std::string res_unit_str;
333       if (ReadExifTag(FIMD_EXIF_EXIF, "PixelXDimension", &pixel_x_dim_str) &&
334           ReadExifTag(FIMD_EXIF_EXIF, "FocalPlaneXResolution", &x_res_str) &&
335           ReadExifTag(FIMD_EXIF_EXIF, "FocalPlaneResolutionUnit",
336                       &res_unit_str)) {
337         regex = boost::regex(".*?([0-9.]+).*?");
338         if (boost::regex_search(pixel_x_dim_str.c_str(), result, regex)) {
339           const double pixel_x_dim = std::stold(result[1]);
340           regex = boost::regex(".*?([0-9.]+).*?/.*?([0-9.]+).*?");
341           if (boost::regex_search(x_res_str.c_str(), result, regex)) {
342             const double x_res = std::stold(result[2]) / std::stold(result[1]);
343             // Use PixelXDimension instead of actual width of image, since
344             // the image might have been resized, but the EXIF data preserved.
345             const double ccd_width = x_res * pixel_x_dim;
346             if (ccd_width > 0 && focal_length_mm > 0) {
347               if (res_unit_str == "cm") {
348                 *focal_length = focal_length_mm / (ccd_width * 10.0) * max_size;
349                 return true;
350               } else if (res_unit_str == "inches") {
351                 *focal_length = focal_length_mm / (ccd_width * 25.4) * max_size;
352                 return true;
353               }
354             }
355           }
356         }
357       }
358     }
359   }
360 
361   return false;
362 }
363 
ExifLatitude(double * latitude) const364 bool Bitmap::ExifLatitude(double* latitude) const {
365   std::string str;
366   if (ReadExifTag(FIMD_EXIF_GPS, "GPSLatitude", &str)) {
367     const boost::regex regex(".*?([0-9.]+):([0-9.]+):([0-9.]+).*?");
368     boost::cmatch result;
369     if (boost::regex_search(str.c_str(), result, regex)) {
370       const double hours = std::stold(result[1]);
371       const double minutes = std::stold(result[2]);
372       const double seconds = std::stold(result[3]);
373       *latitude = hours + minutes / 60.0 + seconds / 3600.0;
374       return true;
375     }
376   }
377   return false;
378 }
379 
ExifLongitude(double * longitude) const380 bool Bitmap::ExifLongitude(double* longitude) const {
381   std::string str;
382   if (ReadExifTag(FIMD_EXIF_GPS, "GPSLongitude", &str)) {
383     const boost::regex regex(".*?([0-9.]+):([0-9.]+):([0-9.]+).*?");
384     boost::cmatch result;
385     if (boost::regex_search(str.c_str(), result, regex)) {
386       const double hours = std::stold(result[1]);
387       const double minutes = std::stold(result[2]);
388       const double seconds = std::stold(result[3]);
389       *longitude = hours + minutes / 60.0 + seconds / 3600.0;
390       return true;
391     }
392   }
393   return false;
394 }
395 
ExifAltitude(double * altitude) const396 bool Bitmap::ExifAltitude(double* altitude) const {
397   std::string str;
398   if (ReadExifTag(FIMD_EXIF_GPS, "GPSAltitude", &str)) {
399     const boost::regex regex(".*?([0-9.]+).*?/.*?([0-9.]+).*?");
400     boost::cmatch result;
401     if (boost::regex_search(str.c_str(), result, regex)) {
402       *altitude = std::stold(result[1]) / std::stold(result[2]);
403       return true;
404     }
405   }
406   return false;
407 }
408 
Read(const std::string & path,const bool as_rgb)409 bool Bitmap::Read(const std::string& path, const bool as_rgb) {
410   if (!ExistsFile(path)) {
411     return false;
412   }
413 
414   const FREE_IMAGE_FORMAT format = FreeImage_GetFileType(path.c_str(), 0);
415 
416   if (format == FIF_UNKNOWN) {
417     return false;
418   }
419 
420   FIBITMAP* fi_bitmap = FreeImage_Load(format, path.c_str());
421   if (fi_bitmap == nullptr) {
422     return false;
423   }
424 
425   data_ = FIBitmapPtr(fi_bitmap, &FreeImage_Unload);
426 
427   if (!IsPtrRGB(data_.get()) && as_rgb) {
428     FIBITMAP* converted_bitmap = FreeImage_ConvertTo24Bits(fi_bitmap);
429     data_ = FIBitmapPtr(converted_bitmap, &FreeImage_Unload);
430   } else if (!IsPtrGrey(data_.get()) && !as_rgb) {
431     FIBITMAP* converted_bitmap = FreeImage_ConvertToGreyscale(fi_bitmap);
432     data_ = FIBitmapPtr(converted_bitmap, &FreeImage_Unload);
433   }
434 
435   if (!IsPtrSupported(data_.get())) {
436     data_.reset();
437     return false;
438   }
439 
440   width_ = FreeImage_GetWidth(data_.get());
441   height_ = FreeImage_GetHeight(data_.get());
442   channels_ = as_rgb ? 3 : 1;
443 
444   return true;
445 }
446 
Write(const std::string & path,const FREE_IMAGE_FORMAT format,const int flags) const447 bool Bitmap::Write(const std::string& path, const FREE_IMAGE_FORMAT format,
448                    const int flags) const {
449   FREE_IMAGE_FORMAT save_format;
450   if (format == FIF_UNKNOWN) {
451     save_format = FreeImage_GetFIFFromFilename(path.c_str());
452     if (save_format == FIF_UNKNOWN) {
453       // If format could not be deduced, save as PNG by default.
454       save_format = FIF_PNG;
455     }
456   } else {
457     save_format = format;
458   }
459 
460   int save_flags = flags;
461   if (save_format == FIF_JPEG && flags == 0) {
462     // Use superb JPEG quality by default to avoid artifacts.
463     save_flags = JPEG_QUALITYSUPERB;
464   }
465 
466   bool success = false;
467   if (save_flags == 0) {
468     success = FreeImage_Save(save_format, data_.get(), path.c_str());
469   } else {
470     success =
471         FreeImage_Save(save_format, data_.get(), path.c_str(), save_flags);
472   }
473 
474   return success;
475 }
476 
Smooth(const float sigma_x,const float sigma_y)477 void Bitmap::Smooth(const float sigma_x, const float sigma_y) {
478   std::vector<float> array(width_ * height_);
479   std::vector<float> array_smoothed(width_ * height_);
480   for (int d = 0; d < channels_; ++d) {
481     size_t i = 0;
482     for (int y = 0; y < height_; ++y) {
483       const uint8_t* line = FreeImage_GetScanLine(data_.get(), height_ - 1 - y);
484       for (int x = 0; x < width_; ++x) {
485         array[i] = line[x * channels_ + d];
486         i += 1;
487       }
488     }
489 
490     vl_imsmooth_f(array_smoothed.data(), width_, array.data(), width_, height_,
491                   width_, sigma_x, sigma_y);
492 
493     i = 0;
494     for (int y = 0; y < height_; ++y) {
495       uint8_t* line = FreeImage_GetScanLine(data_.get(), height_ - 1 - y);
496       for (int x = 0; x < width_; ++x) {
497         line[x * channels_ + d] =
498             TruncateCast<float, uint8_t>(array_smoothed[i]);
499         i += 1;
500       }
501     }
502   }
503 }
504 
Rescale(const int new_width,const int new_height,const FREE_IMAGE_FILTER filter)505 void Bitmap::Rescale(const int new_width, const int new_height,
506                      const FREE_IMAGE_FILTER filter) {
507   SetPtr(FreeImage_Rescale(data_.get(), new_width, new_height, filter));
508 }
509 
Clone() const510 Bitmap Bitmap::Clone() const { return Bitmap(FreeImage_Clone(data_.get())); }
511 
CloneAsGrey() const512 Bitmap Bitmap::CloneAsGrey() const {
513   if (IsGrey()) {
514     return Clone();
515   } else {
516     return Bitmap(FreeImage_ConvertToGreyscale(data_.get()));
517   }
518 }
519 
CloneAsRGB() const520 Bitmap Bitmap::CloneAsRGB() const {
521   if (IsRGB()) {
522     return Clone();
523   } else {
524     return Bitmap(FreeImage_ConvertTo24Bits(data_.get()));
525   }
526 }
527 
CloneMetadata(Bitmap * target) const528 void Bitmap::CloneMetadata(Bitmap* target) const {
529   CHECK_NOTNULL(target);
530   CHECK_NOTNULL(target->Data());
531   FreeImage_CloneMetadata(data_.get(), target->Data());
532 }
533 
ReadExifTag(const FREE_IMAGE_MDMODEL model,const std::string & tag_name,std::string * result) const534 bool Bitmap::ReadExifTag(const FREE_IMAGE_MDMODEL model,
535                          const std::string& tag_name,
536                          std::string* result) const {
537   FITAG* tag = nullptr;
538   FreeImage_GetMetadata(model, data_.get(), tag_name.c_str(), &tag);
539   if (tag == nullptr) {
540     *result = "";
541     return false;
542   } else {
543     if (tag_name == "FocalPlaneXResolution") {
544       // This tag seems to be in the wrong category.
545       *result = std::string(FreeImage_TagToString(FIMD_EXIF_INTEROP, tag));
546     } else {
547       *result = FreeImage_TagToString(model, tag);
548     }
549     return true;
550   }
551 }
552 
SetPtr(FIBITMAP * data)553 void Bitmap::SetPtr(FIBITMAP* data) {
554   if (!IsPtrSupported(data)) {
555     FreeImage_Unload(data);
556     data = FreeImage_ConvertTo24Bits(data);
557   }
558 
559   data_ = FIBitmapPtr(data, &FreeImage_Unload);
560   width_ = FreeImage_GetWidth(data);
561   height_ = FreeImage_GetHeight(data);
562   channels_ = IsPtrRGB(data) ? 3 : 1;
563 }
564 
IsPtrGrey(FIBITMAP * data)565 bool Bitmap::IsPtrGrey(FIBITMAP* data) {
566   return FreeImage_GetColorType(data) == FIC_MINISBLACK &&
567          FreeImage_GetBPP(data) == 8;
568 }
569 
IsPtrRGB(FIBITMAP * data)570 bool Bitmap::IsPtrRGB(FIBITMAP* data) {
571   return FreeImage_GetColorType(data) == FIC_RGB &&
572          FreeImage_GetBPP(data) == 24;
573 }
574 
IsPtrSupported(FIBITMAP * data)575 bool Bitmap::IsPtrSupported(FIBITMAP* data) {
576   return IsPtrGrey(data) || IsPtrRGB(data);
577 }
578 
Red(const float gray)579 float JetColormap::Red(const float gray) { return Base(gray - 0.25f); }
580 
Green(const float gray)581 float JetColormap::Green(const float gray) { return Base(gray); }
582 
Blue(const float gray)583 float JetColormap::Blue(const float gray) { return Base(gray + 0.25f); }
584 
Base(const float val)585 float JetColormap::Base(const float val) {
586   if (val <= 0.125f) {
587     return 0.0f;
588   } else if (val <= 0.375f) {
589     return Interpolate(2.0f * val - 1.0f, 0.0f, -0.75f, 1.0f, -0.25f);
590   } else if (val <= 0.625f) {
591     return 1.0f;
592   } else if (val <= 0.87f) {
593     return Interpolate(2.0f * val - 1.0f, 1.0f, 0.25f, 0.0f, 0.75f);
594   } else {
595     return 0.0f;
596   }
597 }
598 
Interpolate(const float val,const float y0,const float x0,const float y1,const float x1)599 float JetColormap::Interpolate(const float val, const float y0, const float x0,
600                                const float y1, const float x1) {
601   return (val - x0) * (y1 - y0) / (x1 - x0) + y0;
602 }
603 
604 }  // namespace colmap
605