chromium/ui/file_manager/image_loader/piex/piex.cpp

// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "node_modules/piex/src/piex.h"

#include <assert.h>
#include <emscripten.h>
#include <emscripten/bind.h>
#include <emscripten/val.h>
#include <stdint.h>
#include <string.h>

class PiexStreamReader : public piex::StreamInterface {
 public:
  PiexStreamReader(const char* data, size_t size) : data_(data), size_(size) {}

  piex::Error GetData(const size_t offset, const size_t length, uint8_t* out) {
    if (!EnsureData(offset, length))
      return piex::kFail;
    memcpy(out, data_ + offset, length);
    return piex::kOk;
  }

 private:
  bool EnsureData(const size_t offset, const size_t length) const {
    if (offset > size_ || (size_ - offset) < length)
      return false;
    return true;
  }

 private:
  const char* data_;
  const size_t size_;
};

class PiexReader {
 public:
  static emscripten::val ReadImage(const char* data, size_t size) {
    assert(data);

    auto result = emscripten::val::object();
    PiexStreamReader reader(data, size);
    piex::PreviewImageData image;

    switch (piex::GetPreviewImageData(&reader, &image)) {
      case piex::kFail:
        result.set("error", emscripten::val("failed to extract preview"));
        break;
      case piex::kUnsupported:
        result.set("error", emscripten::val("unsupported preview type"));
        break;
      case piex::kOk:
        result = GetProperties(image);
        break;
      default:
        result.set("error", emscripten::val("unknown error"));
        break;
    }

    return result;
  }

 private:
  static emscripten::val GetProperties(const piex::PreviewImageData& image) {
    auto result = emscripten::val::object();

    result.set("details", GetDetails(image));
    result.set("preview", GetPreview(image));
    result.set("thumbnail", GetThumbnail(image));

    return result;
  }

  static emscripten::val GetDetails(const piex::PreviewImageData& image) {
    auto object = emscripten::val::object();

    object.set("cameraMaker", emscripten::val(image.maker));
    object.set("cameraModel", emscripten::val(image.model));

    object.set("aperture", GetRational(image.fnumber));
    object.set("focalLength", GetRational(image.focal_length));
    object.set("exposureTime", GetRational(image.exposure_time));
    object.set("isoSpeed", GetNonZeroValue(image.iso));

    object.set("width", emscripten::val(image.full_width));
    object.set("height", emscripten::val(image.full_height));
    object.set("orientation", emscripten::val(image.exif_orientation));

    object.set("colorSpace", emscripten::val("sRGB"));
    const auto space = static_cast<uint32_t>(image.color_space);
    if (space == piex::PreviewImageData::kAdobeRgb)
      object.set("colorSpace", emscripten::val("AdobeRGB1998"));

    object.set("date", emscripten::val(image.date_time));
    return object;
  }

  static emscripten::val GetPreview(const piex::PreviewImageData& image) {
    const auto undefined = emscripten::val::undefined();

    const auto format = static_cast<uint32_t>(image.preview.format);
    if (format != piex::Image::Format::kJpegCompressed)
      return undefined;
    if (!image.preview.offset || !image.preview.length)
      return undefined;

    auto object = emscripten::val::object();
    object.set("colorSpace", GetColorSpace(image));
    object.set("orientation", emscripten::val(image.exif_orientation));
    object.set("format", emscripten::val(format));
    object.set("offset", emscripten::val(image.preview.offset));
    object.set("length", emscripten::val(image.preview.length));
    object.set("width", emscripten::val(image.preview.width));
    object.set("height", emscripten::val(image.preview.height));

    return object;
  }

  static emscripten::val GetThumbnail(const piex::PreviewImageData& image) {
    const auto undefined = emscripten::val::undefined();

    const auto format = static_cast<uint32_t>(image.thumbnail.format);
    if (format > piex::Image::Format::kUncompressedRgb)
      return undefined;
    if (!image.thumbnail.offset || !image.thumbnail.length)
      return undefined;

    auto object = emscripten::val::object();
    object.set("colorSpace", GetColorSpace(image));
    object.set("orientation", emscripten::val(image.exif_orientation));
    object.set("format", emscripten::val(format));
    object.set("offset", emscripten::val(image.thumbnail.offset));
    object.set("length", emscripten::val(image.thumbnail.length));
    object.set("width", emscripten::val(image.thumbnail.width));
    object.set("height", emscripten::val(image.thumbnail.height));

    return object;
  }

  static emscripten::val GetColorSpace(const piex::PreviewImageData& image) {
    const auto space = static_cast<uint32_t>(image.color_space);
    if (space == piex::PreviewImageData::kAdobeRgb)
      return emscripten::val("adobeRgb");
    return emscripten::val("sRgb");
  }

  static emscripten::val GetNonZeroValue(const uint32_t value) {
    return value > 0 ? emscripten::val(value) : emscripten::val::undefined();
  }

  static float GetRational(const piex::PreviewImageData::Rational& number) {
    if (number.denominator != 0)
      return float(number.numerator) / number.denominator;
    return 0.0f;
  }
};

emscripten::val image(int data, size_t size) {
  return PiexReader::ReadImage(reinterpret_cast<char*>(data), size);
}

EMSCRIPTEN_BINDINGS(PiexWasmModule) {
  emscripten::function("image", &image);
}