#include "common.hh" #include "buffer.hh" #include "image.hh" #include "mime_types.hh" #include #include #if HAVE_JPEG #include #include #endif #if HAVE_EXIF #include #include #include #include #include #endif namespace { bool is_jpeg(const unsigned char* data, size_t size); class ThumbnailImpl : public Image::Thumbnail { public: ThumbnailImpl(std::string mime_type, uint64_t size) : mime_type_(std::move(mime_type)), size_(size) {} std::string_view mime_type() const override { return mime_type_; } uint64_t size() const override { return size_; } private: std::string mime_type_; uint64_t size_; }; class ImageImpl : public Image { public: ImageImpl(uint64_t width, uint64_t height) : width_(width), height_(height) {} uint64_t width() const override { return width_; } uint64_t height() const override { return height_; } Location location() const override { return location_; } Rotation rotation() const override { return rotation_; } Date date() const override { return date_; } Thumbnail* thumbnail() const override { return thumbnail_.get(); } void set_location(Location location) { location_ = location; } void set_rotation(Rotation rotation) { rotation_ = rotation; } void set_date(Date date) { date_ = date; } void set_thumbnail(std::string mime_type, uint64_t size) { thumbnail_ = std::make_unique(std::move(mime_type), size); } private: uint64_t width_; uint64_t height_; Location location_; Rotation rotation_{Rotation::UNKNOWN}; Date date_; std::unique_ptr thumbnail_; }; #if HAVE_EXIF double make_double(ExifRational rat) { return static_cast(rat.numerator) / rat.denominator; } void load_exif(std::filesystem::path const& path, ImageImpl* img) { ExifData *data = exif_data_new_from_file(path.c_str()); if (!data) return; auto byte_order = exif_data_get_byte_order(data); auto* entry = exif_content_get_entry(data->ifd[EXIF_IFD_0], EXIF_TAG_ORIENTATION); if (entry && entry->format == EXIF_FORMAT_SHORT && entry->components == 1) { auto orientation = exif_get_short(entry->data, byte_order); switch (orientation) { case 1: img->set_rotation(Rotation::NONE); break; case 2: img->set_rotation(Rotation::MIRRORED); break; case 3: img->set_rotation(Rotation::ROTATED_180); break; case 4: img->set_rotation(Rotation::ROTATED_180_MIRRORED); break; case 5: img->set_rotation(Rotation::ROTATED_90); break; case 6: img->set_rotation(Rotation::ROTATED_90_MIRRORED); break; case 7: img->set_rotation(Rotation::ROTATED_270); break; case 8: img->set_rotation(Rotation::ROTATED_270_MIRRORED); break; default: break; } } entry = exif_content_get_entry(data->ifd[EXIF_IFD_EXIF], EXIF_TAG_DATE_TIME_ORIGINAL); if (!entry) { entry = exif_content_get_entry(data->ifd[EXIF_IFD_EXIF], EXIF_TAG_DATE_TIME_DIGITIZED); if (!entry) { entry = exif_content_get_entry(data->ifd[EXIF_IFD_0], EXIF_TAG_DATE_TIME); } } if (entry && entry->format == EXIF_FORMAT_ASCII) { auto date = Date::from_format("%Y:%m:%d %H:%M:%S", reinterpret_cast(entry->data)); if (!date.empty()) img->set_date(date); } auto* lat = exif_content_get_entry( data->ifd[EXIF_IFD_GPS], static_cast(EXIF_TAG_GPS_LATITUDE)); auto* lat_ref = exif_content_get_entry( data->ifd[EXIF_IFD_GPS], static_cast(EXIF_TAG_GPS_LATITUDE_REF)); auto* lng = exif_content_get_entry( data->ifd[EXIF_IFD_GPS], static_cast(EXIF_TAG_GPS_LONGITUDE)); auto* lng_ref = exif_content_get_entry( data->ifd[EXIF_IFD_GPS], static_cast(EXIF_TAG_GPS_LONGITUDE_REF)); if (lat && lat->format == EXIF_FORMAT_RATIONAL && lat->components == 3 && lat_ref && lat_ref->format == EXIF_FORMAT_ASCII && lng && lng->format == EXIF_FORMAT_RATIONAL && lng->components == 3 && lng_ref && lng_ref->format == EXIF_FORMAT_ASCII) { auto step = exif_format_get_size(EXIF_FORMAT_RATIONAL); auto lat_double = make_double(exif_get_rational(lat->data + step * 0, byte_order)) + make_double(exif_get_rational(lat->data + step * 1, byte_order)) / 60.0 + make_double(exif_get_rational(lat->data + step * 2, byte_order)) / 3600.0; if (lat_ref->data[0] != 'N') lat_double = -lat_double; auto lng_double = make_double(exif_get_rational(lng->data + step * 0, byte_order)) + make_double(exif_get_rational(lng->data + step * 1, byte_order)) / 60.0 + make_double(exif_get_rational(lng->data + step * 2, byte_order)) / 3600.0; if (lng_ref->data[0] != 'E') lng_double = -lng_double; img->set_location(Location(lat_double, lng_double)); } if (data->data) { if (is_jpeg(data->data, data->size)) { img->set_thumbnail("image/jpeg", data->size); } } exif_data_free(data); } class ExifThumbnailReader : public ThumbnailReader { public: ExifThumbnailReader() : loader_(exif_loader_new()), data_(nullptr) {} ~ExifThumbnailReader() { if (loader_) exif_loader_unref(loader_); if (data_) exif_data_free(data_); } Return drain(RoBuffer* buf, size_t* bytes) override { if (bytes) *bytes = 0; if (loader_) { while (true) { size_t avail; auto* ptr = buf->rbuf(1, avail); if (avail == 0) return Return::NEED_MORE; avail = std::min( avail, static_cast(std::numeric_limits::max())); if (exif_loader_write(loader_, reinterpret_cast( const_cast(ptr)), avail)) { buf->rcommit(avail); if (bytes) *bytes += avail; // Loop to see if there is any more data avail. } else { // No data read or no hope there will be any. break; } } data_ = exif_loader_get_data(loader_); exif_loader_unref(loader_); loader_ = nullptr; } if (data_ && data_->data) return Return::DONE; return Return::ERR; } std::string_view data() const override { return data_ && data_->data ? std::string_view(reinterpret_cast(data_->data), data_->size) : std::string_view(); } private: ExifLoader* loader_; ExifData* data_; }; #else // HAVE_EXIF class FailingThumbnailReader : public ThumbnailReader { public: FailingThumbnailReader() = default; Return drain(RoBuffer*, size_t* bytes) override { if (bytes) *bytes = 0; return Return::ERR; } std::string_view data() const override { return std::string_view(); } }; #endif // HAVE_EXIT #if HAVE_JPEG struct my_jpeg_error { struct jpeg_error_mgr base; jmp_buf jmp_buffer; }; void jpeg_error_exit(j_common_ptr cinfo) { auto myerr = reinterpret_cast(cinfo->err); longjmp(myerr->jmp_buffer, 1); } bool is_jpeg(const unsigned char* data, size_t size) { struct jpeg_decompress_struct info; struct my_jpeg_error err; info.err = jpeg_std_error(&err.base); err.base.error_exit = jpeg_error_exit; if (setjmp(err.jmp_buffer)) { jpeg_destroy_decompress(&info); return false; } jpeg_create_decompress(&info); jpeg_mem_src(&info, data, size); jpeg_read_header(&info, TRUE); jpeg_destroy_decompress(&info); return true; } std::unique_ptr load_jpeg(std::filesystem::path const& path) { struct jpeg_decompress_struct info; struct my_jpeg_error err; FILE* fh = fopen(path.c_str(), "rb"); if (!fh) return nullptr; info.err = jpeg_std_error(&err.base); err.base.error_exit = jpeg_error_exit; if (setjmp(err.jmp_buffer)) { jpeg_destroy_decompress(&info); fclose(fh); return nullptr; } jpeg_create_decompress(&info); jpeg_stdio_src(&info, fh); jpeg_read_header(&info, TRUE); auto img = std::make_unique(info.image_width, info.image_height); jpeg_destroy_decompress(&info); fclose(fh); #if HAVE_EXIF load_exif(path, img.get()); #endif return img; } #endif // HAVE_JPEG } // namespace std::unique_ptr Image::load(std::filesystem::path const& path) { if (!path.has_extension()) return nullptr; auto mime_type = mime_types::from_extension( std::string(path.extension()).substr(1)); #if HAVE_JPEG if (mime_type == "image/jpeg") { return load_jpeg(path); } #endif // HAVE_JPEG return nullptr; } std::unique_ptr ThumbnailReader::create() { #if HAVE_EXIF return std::make_unique(); #else return std::make_unique(); #endif }