From 6232d13f5321b87ddf12a1aa36b4545da45f173d Mon Sep 17 00:00:00 2001 From: Joel Klinghed Date: Wed, 17 Nov 2021 22:34:57 +0100 Subject: Travel3: Simple image and video display site Reads the images and videos from filesystem and builds a site in memroy. --- src/image.cc | 344 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 344 insertions(+) create mode 100644 src/image.cc (limited to 'src/image.cc') diff --git a/src/image.cc b/src/image.cc new file mode 100644 index 0000000..111568c --- /dev/null +++ b/src/image.cc @@ -0,0 +1,344 @@ +#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_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 +} -- cgit v1.2.3-70-g09d2