summaryrefslogtreecommitdiff
path: root/src/image.cc
diff options
context:
space:
mode:
Diffstat (limited to 'src/image.cc')
-rw-r--r--src/image.cc344
1 files changed, 344 insertions, 0 deletions
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 <algorithm>
+#include <limits>
+
+#if HAVE_JPEG
+#include <jpeglib.h>
+#include <setjmp.h>
+#endif
+
+#if HAVE_EXIF
+#include <libexif/exif-data.h>
+#include <libexif/exif-entry.h>
+#include <libexif/exif-format.h>
+#include <libexif/exif-ifd.h>
+#include <libexif/exif-loader.h>
+#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<ThumbnailImpl>(std::move(mime_type), size);
+ }
+
+private:
+ uint64_t width_;
+ uint64_t height_;
+ Location location_;
+ Rotation rotation_{Rotation::UNKNOWN};
+ Date date_;
+ std::unique_ptr<ThumbnailImpl> thumbnail_;
+};
+
+#if HAVE_EXIF
+double make_double(ExifRational rat) {
+ return static_cast<double>(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<char const*>(entry->data));
+ if (!date.empty())
+ img->set_date(date);
+ }
+ auto* lat = exif_content_get_entry(
+ data->ifd[EXIF_IFD_GPS],
+ static_cast<ExifTag>(EXIF_TAG_GPS_LATITUDE));
+ auto* lat_ref = exif_content_get_entry(
+ data->ifd[EXIF_IFD_GPS],
+ static_cast<ExifTag>(EXIF_TAG_GPS_LATITUDE_REF));
+ auto* lng = exif_content_get_entry(
+ data->ifd[EXIF_IFD_GPS],
+ static_cast<ExifTag>(EXIF_TAG_GPS_LONGITUDE));
+ auto* lng_ref = exif_content_get_entry(
+ data->ifd[EXIF_IFD_GPS],
+ static_cast<ExifTag>(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<size_t>(std::numeric_limits<unsigned int>::max()));
+ if (exif_loader_write(loader_, reinterpret_cast<unsigned char*>(
+ const_cast<char*>(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<char const*>(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<my_jpeg_error*>(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<Image> 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<ImageImpl>(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> 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> ThumbnailReader::create() {
+#if HAVE_EXIF
+ return std::make_unique<ExifThumbnailReader>();
+#else
+ return std::make_unique<FailingThumbnailReader>();
+#endif
+}