diff --git a/app/src/main/cpp/CMakeLists.txt b/app/src/main/cpp/CMakeLists.txt index 8a7547be..f897506f 100644 --- a/app/src/main/cpp/CMakeLists.txt +++ b/app/src/main/cpp/CMakeLists.txt @@ -72,7 +72,33 @@ endif(OpenCV_FOUND) set(ncnn_DIR ${NCNN_ROOT}/${ANDROID_ABI}/lib/cmake/ncnn) find_package(ncnn REQUIRED) -# include(mars/src/CMakeUtils.txt) + +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/libcutils/include) +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/libutils/include) +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/img_utils/include) + +SET( IMG_UTILS_SRCS + "img_utils/src/EndianUtils.cpp" + #"img_utils/src/FileInput.cpp" + #"img_utils/src/FileOutput.cpp" + #"img_utils/src/SortedEntryVector.cpp" + "img_utils/src/Input.cpp" + "img_utils/src/Output.cpp" + "img_utils/src/Orderable.cpp" + "img_utils/src/TiffIfd.cpp" + "img_utils/src/TiffWritable.cpp" + "img_utils/src/TiffWriter.cpp" + "img_utils/src/TiffEntry.cpp" + "img_utils/src/TiffEntryImpl.cpp" + "img_utils/src/ByteArrayOutput.cpp" + "img_utils/src/DngUtils.cpp" + "img_utils/src/StripSource.cpp" + + libutils/SharedBuffer.cpp + libutils/StrongPointer.cpp + + DngCreator.cpp + ) message(WARNING "include_directories ${HDRPLUS_ROOT}/${ANDROID_ABI}/include") @@ -327,6 +353,8 @@ add_library( # Sets the name of the library. ${HDRPLUS_SOURCES} ${CAMERA2_SOURCES} + ${IMG_UTILS_SRCS} + ${TERM_CORE_ROOT}/Factory.cpp ${TERM_CORE_ROOT}/FilePoster.cpp ${TERM_CORE_ROOT}/LogThread.cpp diff --git a/app/src/main/cpp/DngCreator.cpp b/app/src/main/cpp/DngCreator.cpp new file mode 100644 index 00000000..cdb90d66 --- /dev/null +++ b/app/src/main/cpp/DngCreator.cpp @@ -0,0 +1,2637 @@ +//#define LOG_NDEBUG 0 +#define LOG_TAG "DngCreator_JNI" +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "DngCreator.h" + +// #include "core_jni_helpers.h" + +// #include "android_runtime/AndroidRuntime.h" +// #include "android_runtime/android_hardware_camera2_CameraMetadata.h" + +#include +// #include + +using namespace android; +using namespace img_utils; +// using android::base::GetProperty; + + +ByteVectorOutput::ByteVectorOutput(std::vector& buf) : m_buf(buf) +{ +} +ByteVectorOutput::~ByteVectorOutput() +{ +} +status_t ByteVectorOutput::open() +{ + return OK; +} +status_t ByteVectorOutput::close() +{ + return OK; +} + +status_t ByteVectorOutput::write(const uint8_t* buf, size_t offset, size_t count) +{ + m_buf.insert(m_buf.end(), buf + offset, buf + offset + count); + return OK; +} + +ByteVectorInput::ByteVectorInput(const std::vector& buf) : m_buf(buf), m_offset(0) +{ +} + +ByteVectorInput::~ByteVectorInput() +{ +} + +status_t ByteVectorInput::open() +{ + return OK; +} +ssize_t ByteVectorInput::read(uint8_t* buf, size_t offset, size_t count) +{ + if (m_buf.empty() || m_offset >= m_buf.size()) + { + return NOT_ENOUGH_DATA; + } + + size_t left = m_buf.size() - m_offset; + if (left >= count) + { + memcpy(buf + offset, &m_buf[m_offset], count); + m_offset += count; + return count; + } + else + { + memcpy(buf + offset, &m_buf[m_offset], left); + m_offset += left; + return left; + } +} +/** + * Skips bytes in the input. + * + * Returns the number of bytes skipped, or NOT_ENOUGH_DATA if at the end of the file. If an + * error has occurred, this will return a negative error code other than NOT_ENOUGH_DATA. + */ +ssize_t ByteVectorInput::skip(size_t count) +{ + size_t left = m_buf.size() - m_offset; + if (left >= count) + { + m_offset += count; + return count; + } + else + { + m_offset += left; + return left; + } +} + +/** + * Close the Input. It is not valid to call open on a previously closed Input. + * + * Returns OK on success, or a negative error code. + */ +status_t ByteVectorInput::close() +{ +} + + +ByteBufferInput::ByteBufferInput(const uint8_t* buf, size_t len) : m_buf(buf), m_len(len), m_offset(0) +{ +} + +ByteBufferInput::~ByteBufferInput() +{ +} + +status_t ByteBufferInput::open() +{ + return OK; +} +ssize_t ByteBufferInput::read(uint8_t* buf, size_t offset, size_t count) +{ + if (m_buf == NULL || m_offset >= m_len) + { + return NOT_ENOUGH_DATA; + } + + size_t left = m_len - m_offset; + if (left >= count) + { + memcpy(buf + offset, m_buf + m_offset, count); + m_offset += count; + return count; + } + else + { + memcpy(buf + offset, m_buf + m_offset, left); + m_offset += left; + return left; + } +} +/** + * Skips bytes in the input. + * + * Returns the number of bytes skipped, or NOT_ENOUGH_DATA if at the end of the file. If an + * error has occurred, this will return a negative error code other than NOT_ENOUGH_DATA. + */ +ssize_t ByteBufferInput::skip(size_t count) +{ + size_t left = m_len - m_offset; + if (left >= count) + { + m_offset += count; + return count; + } + else + { + m_offset += left; + return left; + } +} + +status_t ByteBufferInput::close() +{ +} + + +/** + * Convert a single YUV pixel to RGB. + */ +static void yuvToRgb(const uint8_t yuvData[3], int outOffset, /*out*/uint8_t rgbOut[3]) { + const int COLOR_MAX = 255; + + float y = yuvData[0] & 0xFF; // Y channel + float cb = yuvData[1] & 0xFF; // U channel + float cr = yuvData[2] & 0xFF; // V channel + + // convert YUV -> RGB (from JFIF's "Conversion to and from RGB" section) + float r = y + 1.402f * (cr - 128); + float g = y - 0.34414f * (cb - 128) - 0.71414f * (cr - 128); + float b = y + 1.772f * (cb - 128); + + // clamp to [0,255] + rgbOut[outOffset] = (uint8_t) std::max(0, std::min(COLOR_MAX, (int)r)); + rgbOut[outOffset + 1] = (uint8_t) std::max(0, std::min(COLOR_MAX, (int)g)); + rgbOut[outOffset + 2] = (uint8_t) std::max(0, std::min(COLOR_MAX, (int)b)); +} + +/** + * Convert a single {@link Color} pixel to RGB. + */ +static void colorToRgb(int color, int outOffset, /*out*/uint8_t rgbOut[3]) { + rgbOut[outOffset] = (uint8_t)(color >> 16) & 0xFF; + rgbOut[outOffset + 1] = (uint8_t)(color >> 8) & 0xFF; // color >> 8)&0xFF + rgbOut[outOffset + 2] = (uint8_t) color & 0xFF; + // Discards Alpha +} + +/** + * Generate a direct RGB {@link ByteBuffer} from a YUV420_888 {@link Image}. + */ +#if 0 +static ByteBuffer convertToRGB(Image yuvImage) { + // TODO: Optimize this with renderscript intrinsic. + int width = yuvImage.getWidth(); + int height = yuvImage.getHeight(); + ByteBuffer buf = ByteBuffer.allocateDirect(BYTES_PER_RGB_PIX * width * height); + + Image.Plane yPlane = yuvImage.getPlanes()[0]; + Image.Plane uPlane = yuvImage.getPlanes()[1]; + Image.Plane vPlane = yuvImage.getPlanes()[2]; + + ByteBuffer yBuf = yPlane.getBuffer(); + ByteBuffer uBuf = uPlane.getBuffer(); + ByteBuffer vBuf = vPlane.getBuffer(); + + yBuf.rewind(); + uBuf.rewind(); + vBuf.rewind(); + + int yRowStride = yPlane.getRowStride(); + int vRowStride = vPlane.getRowStride(); + int uRowStride = uPlane.getRowStride(); + + int yPixStride = yPlane.getPixelStride(); + int vPixStride = vPlane.getPixelStride(); + int uPixStride = uPlane.getPixelStride(); + + byte[] yuvPixel = { 0, 0, 0 }; + byte[] yFullRow = new byte[yPixStride * (width - 1) + 1]; + byte[] uFullRow = new byte[uPixStride * (width / 2 - 1) + 1]; + byte[] vFullRow = new byte[vPixStride * (width / 2 - 1) + 1]; + byte[] finalRow = new byte[BYTES_PER_RGB_PIX * width]; + for (int i = 0; i < height; i++) { + int halfH = i / 2; + yBuf.position(yRowStride * i); + yBuf.get(yFullRow); + uBuf.position(uRowStride * halfH); + uBuf.get(uFullRow); + vBuf.position(vRowStride * halfH); + vBuf.get(vFullRow); + for (int j = 0; j < width; j++) { + int halfW = j / 2; + yuvPixel[0] = yFullRow[yPixStride * j]; + yuvPixel[1] = uFullRow[uPixStride * halfW]; + yuvPixel[2] = vFullRow[vPixStride * halfW]; + yuvToRgb(yuvPixel, j * BYTES_PER_RGB_PIX, /*out*/finalRow); + } + buf.put(finalRow); + } + + yBuf.rewind(); + uBuf.rewind(); + vBuf.rewind(); + buf.rewind(); + return buf; + } +#endif + + + + DngCreator::DngCreator(ACameraMetadata* characteristics, ACameraMetadata* result) : NativeContext(characteristics, result) + { + // Find current time + time_t ts = time(NULL); + + // Find boot time + // long bootTimeMillis = currentTime - SystemClock.elapsedRealtime(); + + // Find capture time (nanos since boot) +#if 0 + Long timestamp = metadata.get(CaptureResult.SENSOR_TIMESTAMP); + long captureTime = currentTime; + if (timestamp != null) { + captureTime = timestamp / 1000000 + bootTimeMillis; + } + + // Format for metadata + String formattedCaptureTime = sDateTimeStampFormat.format(captureTime); +#endif + + std::string formattedCaptureTime; + init(characteristics, result, formattedCaptureTime); + } + + +#if 0 + void DngCreator::setLocation(Location location) + { + double latitude = location.getLatitude(); + double longitude = location.getLongitude(); + long time = location.getTime(); + + int[] latTag = toExifLatLong(latitude); + int[] longTag = toExifLatLong(longitude); + String latRef = latitude >= 0 ? GPS_LAT_REF_NORTH : GPS_LAT_REF_SOUTH; + String longRef = longitude >= 0 ? GPS_LONG_REF_EAST : GPS_LONG_REF_WEST; + + String dateTag = sExifGPSDateStamp.format(time); + mGPSTimeStampCalendar.setTimeInMillis(time); + int[] timeTag = new int[] { mGPSTimeStampCalendar.get(Calendar.HOUR_OF_DAY), 1, + mGPSTimeStampCalendar.get(Calendar.MINUTE), 1, + mGPSTimeStampCalendar.get(Calendar.SECOND), 1 }; + nativeSetGpsTags(latTag, latRef, longTag, longRef, dateTag, timeTag); + } +#endif + + void DngCreator::writeInputStream(std::vector& dngOutput, SIZE size, const std::vector& pixels, long offset) + { + int width = size.width; + int height = size.height; + if (width <= 0 || height <= 0) { +#if 0 + throw new IllegalArgumentException("Size with invalid width, height: (" + width + "," + + height + ") passed to writeInputStream"); +#endif + } + writeInputStream(dngOutput, pixels, width, height, offset); + } + + void DngCreator::writeByteBuffer(std::vector& dngOutput, SIZE size, const std::vector& pixels, long offset) + { + int width = size.width; + int height = size.height; + + writeByteBuffer(width, height, pixels, dngOutput, DEFAULT_PIXEL_STRIDE, + width * DEFAULT_PIXEL_STRIDE, offset); + } + +#if 0 + void DngCreator::writeImage(OutputStream& dngOutput, AImage& pixels) + { + int format = pixels.getFormat(); + if (format != ImageFormat.RAW_SENSOR) { + + } + + Image.Plane[] planes = pixels.getPlanes(); + if (planes == null || planes.length <= 0) { + + } + + ByteBuffer buf = planes[0].getBuffer(); + writeByteBuffer(pixels.getWidth(), pixels.getHeight(), buf, dngOutput, + planes[0].getPixelStride(), planes[0].getRowStride(), 0); + } +#endif + + void DngCreator::close() { + + } + + // private static final DateFormat sExifGPSDateStamp = new SimpleDateFormat(GPS_DATE_FORMAT_STR); + // private static final DateFormat sDateTimeStampFormat = new SimpleDateFormat(TIFF_DATETIME_FORMAT); +#if 0 + static { + sDateTimeStampFormat.setTimeZone(TimeZone.getDefault()); + sExifGPSDateStamp.setTimeZone(TimeZone.getTimeZone("UTC")); + } +#endif + + /** + * Offset, rowStride, and pixelStride are given in bytes. Height and width are given in pixels. + */ + void DngCreator::writeByteBuffer(int width, int height, const std::vector& pixels, std::vector& dngOutput, int pixelStride, int rowStride, long offset) + { + if (width <= 0 || height <= 0) { + } + long capacity = pixels.capacity(); + long totalSize = ((long) rowStride) * height + offset; + if (capacity < totalSize) { +#if 0 + throw new IllegalArgumentException("Image size " + capacity + + " is too small (must be larger than " + totalSize + ")"); +#endif + } + int minRowStride = pixelStride * width; + if (minRowStride > rowStride) { +#if 0 + throw new IllegalArgumentException("Invalid image pixel stride, row byte width " + + minRowStride + " is too large, expecting " + rowStride); +#endif + } + // pixels.clear(); // Reset mark and limit + writeImage(dngOutput, width, height, pixels, rowStride, pixelStride, offset, true); + // pixels.clear(); + } + + + /** + * Generate a direct RGB {@link ByteBuffer} from a {@link Bitmap}. + */ +#if 0 + static ByteBuffer DngCreator::convertToRGB(Bitmap argbBitmap) { + // TODO: Optimize this. + int width = argbBitmap.getWidth(); + int height = argbBitmap.getHeight(); + ByteBuffer buf = ByteBuffer.allocateDirect(BYTES_PER_RGB_PIX * width * height); + + int[] pixelRow = new int[width]; + byte[] finalRow = new byte[BYTES_PER_RGB_PIX * width]; + for (int i = 0; i < height; i++) { + argbBitmap.getPixels(pixelRow, /*offset*/0, /*stride*/width, /*x*/0, /*y*/i, + /*width*/width, /*height*/1); + for (int j = 0; j < width; j++) { + colorToRgb(pixelRow[j], j * BYTES_PER_RGB_PIX, /*out*/finalRow); + } + buf.put(finalRow); + } + + buf.rewind(); + return buf; + } +#endif + + /** + * Convert coordinate to EXIF GPS tag format. + */ + void DngCreator::toExifLatLong(double value, int data[6]) + { + // convert to the format dd/1 mm/1 ssss/100 + value = std::abs(value); + data[0] = (int) value; + data[1] = 1; + value = (value - data[0]) * 60; + data[2] = (int) value; + data[3] = 1; + value = (value - data[2]) * 6000; + data[4] = (int) value; + data[5] = 100; + } + + + +NativeContext::NativeContext(ACameraMetadata* characteristics, ACameraMetadata* result) : + mCharacteristics(characteristics), mResult(result), mThumbnailWidth(0), + mThumbnailHeight(0), mOrientation(TAG_ORIENTATION_UNKNOWN), mThumbnailSet(false), + mGpsSet(false), mDescriptionSet(false), mCaptureTimeSet(false) {} + +NativeContext::~NativeContext() {} + +TiffWriter* NativeContext::getWriter() { + return &mWriter; +} + +ACameraMetadata* NativeContext::getCharacteristics() const { + return mCharacteristics; +} + +ACameraMetadata* NativeContext::getResult() const { + return mResult; +} + +uint32_t NativeContext::getThumbnailWidth() const { + return mThumbnailWidth; +} + +uint32_t NativeContext::getThumbnailHeight() const { + return mThumbnailHeight; +} + +const uint8_t* NativeContext::getThumbnail() const { + return &mCurrentThumbnail[0]; +} + +bool NativeContext::hasThumbnail() const { + return mThumbnailSet; +} + +bool NativeContext::setThumbnail(const std::vector& buffer, uint32_t width, uint32_t height) { + mThumbnailWidth = width; + mThumbnailHeight = height; + + size_t size = BYTES_PER_RGB_PIXEL * width * height; + mCurrentThumbnail.resize(size); + //if (mCurrentThumbnail.resize(size) < 0) { + // ALOGE("%s: Could not resize thumbnail buffer.", __FUNCTION__); + // return false; + //} + + // uint8_t* thumb = mCurrentThumbnail.editArray(); + memcpy(&mCurrentThumbnail[0], &buffer[0], size); + mThumbnailSet = true; + return true; +} + +void NativeContext::setOrientation(uint16_t orientation) { + mOrientation = orientation; +} + +uint16_t NativeContext::getOrientation() const { + return mOrientation; +} + +void NativeContext::setDescription(const std::string& desc) { + mDescription = desc; + mDescriptionSet = true; +} + +std::string NativeContext::getDescription() const { + return mDescription; +} + +bool NativeContext::hasDescription() const { + return mDescriptionSet; +} + +void NativeContext::setGpsData(const GpsData& data) { + mGpsData = data; + mGpsSet = true; +} + +GpsData NativeContext::getGpsData() const { + return mGpsData; +} + +bool NativeContext::hasGpsData() const { + return mGpsSet; +} + +void NativeContext::setCaptureTime(const std::string& formattedCaptureTime) { + mFormattedCaptureTime = formattedCaptureTime; + mCaptureTimeSet = true; +} + +std::string NativeContext::getCaptureTime() const { + return mFormattedCaptureTime; +} + +bool NativeContext::hasCaptureTime() const { + return mCaptureTimeSet; +} + +// End of NativeContext +// ---------------------------------------------------------------------------- + + +/** + * StripSource subclass for Input types. + * + * This class is not intended to be used across JNI calls. + */ + +class InputStripSource : public StripSource, public LightRefBase { +public: + InputStripSource(Input& input, uint32_t ifd, uint32_t width, uint32_t height, + uint32_t pixStride, uint32_t rowStride, uint64_t offset, uint32_t bytesPerSample, + uint32_t samplesPerPixel); + + virtual ~InputStripSource(); + + virtual status_t writeToStream(Output& stream, uint32_t count); + + virtual uint32_t getIfd() const; +protected: + uint32_t mIfd; + Input* mInput; + uint32_t mWidth; + uint32_t mHeight; + uint32_t mPixStride; + uint32_t mRowStride; + uint64_t mOffset; + uint32_t mBytesPerSample; + uint32_t mSamplesPerPixel; +}; + +InputStripSource::InputStripSource(Input& input, uint32_t ifd, uint32_t width, + uint32_t height, uint32_t pixStride, uint32_t rowStride, uint64_t offset, + uint32_t bytesPerSample, uint32_t samplesPerPixel) : mIfd(ifd), mInput(&input), + mWidth(width), mHeight(height), mPixStride(pixStride), mRowStride(rowStride), + mOffset(offset), mBytesPerSample(bytesPerSample), + mSamplesPerPixel(samplesPerPixel) {} + +InputStripSource::~InputStripSource() {} + +status_t InputStripSource::writeToStream(Output& stream, uint32_t count) { + uint32_t fullSize = mWidth * mHeight * mBytesPerSample * mSamplesPerPixel; + jlong offset = mOffset; + + if (fullSize != count) { + ALOGE("%s: Amount to write %u doesn't match image size %u", __FUNCTION__, count, + fullSize); + // jniThrowException(mEnv, "java/lang/IllegalStateException", "Not enough data to write"); + return BAD_VALUE; + } + + // Skip offset + while (offset > 0) { + ssize_t skipped = mInput->skip(offset); + if (skipped <= 0) { + if (skipped == NOT_ENOUGH_DATA || skipped == 0) { +#if 0 + jniThrowExceptionFmt(mEnv, "java/io/IOException", + "Early EOF encountered in skip, not enough pixel data for image of size %u", + fullSize); +#endif + skipped = NOT_ENOUGH_DATA; + } else { +#if 0 + if (!mEnv->ExceptionCheck()) { + + jniThrowException(mEnv, "java/io/IOException", + "Error encountered while skip bytes in input stream."); + } +#endif + } + + return skipped; + } + offset -= skipped; + } + + std::vector row; + row.resize(mRowStride); +#if 0 + if (row.resize(mRowStride) < 0) { + jniThrowException(mEnv, "java/lang/OutOfMemoryError", "Could not allocate row vector."); + return BAD_VALUE; + } +#endif + + uint8_t* rowBytes = &row[0]; + + for (uint32_t i = 0; i < mHeight; ++i) { + size_t rowFillAmt = 0; + size_t rowSize = mRowStride; + + while (rowFillAmt < mRowStride) { + ssize_t bytesRead = mInput->read(rowBytes, rowFillAmt, rowSize); + if (bytesRead <= 0) { + if (bytesRead == NOT_ENOUGH_DATA || bytesRead == 0) { + ALOGE("%s: Early EOF on row %" PRIu32 ", received bytesRead %zd", + __FUNCTION__, i, bytesRead); +#if 0 + jniThrowExceptionFmt(mEnv, "java/io/IOException", + "Early EOF encountered, not enough pixel data for image of size %" + PRIu32, fullSize); +#endif + bytesRead = NOT_ENOUGH_DATA; + } else { +#if 0 + if (!mEnv->ExceptionCheck()) { + jniThrowException(mEnv, "java/io/IOException", + "Error encountered while reading"); + } +#endif + } + return bytesRead; + } + rowFillAmt += bytesRead; + rowSize -= bytesRead; + } + + if (mPixStride == mBytesPerSample * mSamplesPerPixel) { + ALOGV("%s: Using stream per-row write for strip.", __FUNCTION__); + + if (stream.write(rowBytes, 0, mBytesPerSample * mSamplesPerPixel * mWidth) != OK) { +#if 0 + if (!mEnv->ExceptionCheck()) { + jniThrowException(mEnv, "java/io/IOException", "Failed to write pixel data"); + } +#endif + return BAD_VALUE; + } + } else { + ALOGV("%s: Using stream per-pixel write for strip.", __FUNCTION__); +#if 0 + jniThrowException(mEnv, "java/lang/IllegalStateException", + "Per-pixel strides are not supported for RAW16 -- pixels must be contiguous"); +#endif + return BAD_VALUE; + + // TODO: Add support for non-contiguous pixels if needed. + } + } + return OK; +} + +uint32_t InputStripSource::getIfd() const { + return mIfd; +} + +// End of InputStripSource +// ---------------------------------------------------------------------------- + +/** + * StripSource subclass for direct buffer types. + * + * This class is not intended to be used across JNI calls. + */ + +class DirectStripSource : public StripSource, public LightRefBase { +public: + DirectStripSource(const uint8_t* pixelBytes, uint32_t ifd, uint32_t width, + uint32_t height, uint32_t pixStride, uint32_t rowStride, uint64_t offset, + uint32_t bytesPerSample, uint32_t samplesPerPixel); + + virtual ~DirectStripSource(); + + virtual status_t writeToStream(Output& stream, uint32_t count); + + virtual uint32_t getIfd() const; +protected: + uint32_t mIfd; + const uint8_t* mPixelBytes; + uint32_t mWidth; + uint32_t mHeight; + uint32_t mPixStride; + uint32_t mRowStride; + uint16_t mOffset; + uint32_t mBytesPerSample; + uint32_t mSamplesPerPixel; +}; + +DirectStripSource::DirectStripSource(const uint8_t* pixelBytes, uint32_t ifd, + uint32_t width, uint32_t height, uint32_t pixStride, uint32_t rowStride, + uint64_t offset, uint32_t bytesPerSample, uint32_t samplesPerPixel) : mIfd(ifd), + mPixelBytes(pixelBytes), mWidth(width), mHeight(height), mPixStride(pixStride), + mRowStride(rowStride), mOffset(offset), mBytesPerSample(bytesPerSample), + mSamplesPerPixel(samplesPerPixel) {} + +DirectStripSource::~DirectStripSource() {} + +status_t DirectStripSource::writeToStream(Output& stream, uint32_t count) { + uint32_t fullSize = mWidth * mHeight * mBytesPerSample * mSamplesPerPixel; + + if (fullSize != count) { + ALOGE("%s: Amount to write %u doesn't match image size %u", __FUNCTION__, count, + fullSize); +#if 0 + jniThrowException(mEnv, "java/lang/IllegalStateException", "Not enough data to write"); +#endif + return BAD_VALUE; + } + + + if (mPixStride == mBytesPerSample * mSamplesPerPixel + && mRowStride == mWidth * mBytesPerSample * mSamplesPerPixel) { + ALOGV("%s: Using direct single-pass write for strip.", __FUNCTION__); + + if (stream.write(mPixelBytes, mOffset, fullSize) != OK) { +#if 0 + if (!mEnv->ExceptionCheck()) { + jniThrowException(mEnv, "java/io/IOException", "Failed to write pixel data"); + } +#endif + return BAD_VALUE; + } + } else if (mPixStride == mBytesPerSample * mSamplesPerPixel) { + ALOGV("%s: Using direct per-row write for strip.", __FUNCTION__); + + for (size_t i = 0; i < mHeight; ++i) { + if (stream.write(mPixelBytes, mOffset + i * mRowStride, mPixStride * mWidth) != OK/* || + mEnv->ExceptionCheck()*/) { +#if 0 + if (!mEnv->ExceptionCheck()) { + jniThrowException(mEnv, "java/io/IOException", "Failed to write pixel data"); + } +#endif + return BAD_VALUE; + } + } + } else { + ALOGV("%s: Using direct per-pixel write for strip.", __FUNCTION__); +#if 0 + jniThrowException(mEnv, "java/lang/IllegalStateException", + "Per-pixel strides are not supported for RAW16 -- pixels must be contiguous"); +#endif + return BAD_VALUE; + + // TODO: Add support for non-contiguous pixels if needed. + } + return OK; + +} + +uint32_t DirectStripSource::getIfd() const { + return mIfd; +} + +// End of DirectStripSource +// ---------------------------------------------------------------------------- + +/** + * Calculate the default crop relative to the "active area" of the image sensor (this active area + * will always be the pre-correction active area rectangle), and set this. + */ +static status_t calculateAndSetCrop(ACameraMetadata* characteristics, + sp writer) { + + ACameraMetadata_const_entry entry = { 0 }; + // ACAMERA_SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE + // ANDROID_SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE + camera_status_t status = ACameraMetadata_getConstEntry(characteristics, + ACAMERA_SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE, &entry); + uint32_t width = static_cast(entry.data.i32[2]); + uint32_t height = static_cast(entry.data.i32[3]); + + const uint32_t margin = 8; // Default margin recommended by Adobe for interpolation. + + if (width < margin * 2 || height < margin * 2) { + ALOGE("%s: Cannot calculate default crop for image, pre-correction active area is too" + "small: h=%" PRIu32 ", w=%" PRIu32, __FUNCTION__, height, width); +#if 0 + jniThrowException(env, "java/lang/IllegalStateException", + "Pre-correction active area is too small."); +#endif + return BAD_VALUE; + } + + uint32_t defaultCropOrigin[] = {margin, margin}; + uint32_t defaultCropSize[] = {width - defaultCropOrigin[0] - margin, + height - defaultCropOrigin[1] - margin}; + + BAIL_IF_INVALID_R(writer->addEntry(TAG_DEFAULTCROPORIGIN, 2, defaultCropOrigin, + TIFF_IFD_0), env, TAG_DEFAULTCROPORIGIN, writer); + BAIL_IF_INVALID_R(writer->addEntry(TAG_DEFAULTCROPSIZE, 2, defaultCropSize, + TIFF_IFD_0), env, TAG_DEFAULTCROPSIZE, writer); + + return OK; +} + +static bool validateDngHeader(sp writer, ACameraMetadata* characteristics, uint32_t width, uint32_t height) +{ + if (width <= 0 || height <= 0) { +#if 0 + jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException", \ + "Image width %d is invalid", width); +#endif + return false; + } + + ACameraMetadata_const_entry preCorrectionEntry = { 0 }; + camera_status_t status = ACameraMetadata_getConstEntry(characteristics, ACAMERA_SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE, &preCorrectionEntry); + ACameraMetadata_const_entry pixelArrayEntry = { 0 }; + status = ACameraMetadata_getConstEntry(characteristics, ACAMERA_SENSOR_INFO_PIXEL_ARRAY_SIZE, &pixelArrayEntry); + + int pWidth = static_cast(pixelArrayEntry.data.i32[0]); + int pHeight = static_cast(pixelArrayEntry.data.i32[1]); + int cWidth = static_cast(preCorrectionEntry.data.i32[2]); + int cHeight = static_cast(preCorrectionEntry.data.i32[3]); + + bool matchesPixelArray = (pWidth == width && pHeight == height); + bool matchesPreCorrectionArray = (cWidth == width && cHeight == height); + + if (!(matchesPixelArray || matchesPreCorrectionArray)) { +#if 0 + jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException", \ + "Image dimensions (w=%d,h=%d) are invalid, must match either the pixel " + "array size (w=%d, h=%d) or the pre-correction array size (w=%d, h=%d)", + width, height, pWidth, pHeight, cWidth, cHeight); +#endif + return false; + } + + return true; +} + +static status_t moveEntries(sp writer, uint32_t ifdFrom, uint32_t ifdTo, + const std::vector& entries) { + for (size_t i = 0; i < entries.size(); ++i) { + uint16_t tagId = entries[i]; + sp entry = writer->getEntry(tagId, ifdFrom); + if (entry.get() == nullptr) { + ALOGE("%s: moveEntries failed, entry %u not found in IFD %u", __FUNCTION__, tagId, + ifdFrom); + return BAD_VALUE; + } + if (writer->addEntry(entry, ifdTo) != OK) { + ALOGE("%s: moveEntries failed, could not add entry %u to IFD %u", __FUNCTION__, tagId, + ifdFrom); + return BAD_VALUE; + } + writer->removeEntry(tagId, ifdFrom); + } + return OK; +} + +/** + * Write CFA pattern for given CFA enum into cfaOut. cfaOut must have length >= 4. + * Returns OK on success, or a negative error code if the CFA enum was invalid. + */ +static status_t convertCFA(uint8_t cfaEnum, /*out*/uint8_t* cfaOut) { + acamera_metadata_enum_android_sensor_info_color_filter_arrangement_t cfa = + static_cast( + cfaEnum); + switch(cfa) { + case ACAMERA_SENSOR_INFO_COLOR_FILTER_ARRANGEMENT_RGGB: { + cfaOut[0] = 0; + cfaOut[1] = 1; + cfaOut[2] = 1; + cfaOut[3] = 2; + break; + } + case ACAMERA_SENSOR_INFO_COLOR_FILTER_ARRANGEMENT_GRBG: { + cfaOut[0] = 1; + cfaOut[1] = 0; + cfaOut[2] = 2; + cfaOut[3] = 1; + break; + } + case ACAMERA_SENSOR_INFO_COLOR_FILTER_ARRANGEMENT_GBRG: { + cfaOut[0] = 1; + cfaOut[1] = 2; + cfaOut[2] = 0; + cfaOut[3] = 1; + break; + } + case ACAMERA_SENSOR_INFO_COLOR_FILTER_ARRANGEMENT_BGGR: { + cfaOut[0] = 2; + cfaOut[1] = 1; + cfaOut[2] = 1; + cfaOut[3] = 0; + break; + } + // MONO and NIR are degenerate case of RGGB pattern: only Red channel + // will be used. + case ACAMERA_SENSOR_INFO_COLOR_FILTER_ARRANGEMENT_MONO: + case ACAMERA_SENSOR_INFO_COLOR_FILTER_ARRANGEMENT_NIR: { + cfaOut[0] = 0; + break; + } + default: { + return BAD_VALUE; + } + } + return OK; +} + +/** + * Convert the CFA layout enum to an OpcodeListBuilder::CfaLayout enum, defaults to + * RGGB for an unknown enum. + */ +static OpcodeListBuilder::CfaLayout convertCFAEnumToOpcodeLayout(uint8_t cfaEnum) { + acamera_metadata_enum_android_sensor_info_color_filter_arrangement_t cfa = + static_cast( + cfaEnum); + switch(cfa) { + case ACAMERA_SENSOR_INFO_COLOR_FILTER_ARRANGEMENT_RGGB: { + return OpcodeListBuilder::CFA_RGGB; + } + case ACAMERA_SENSOR_INFO_COLOR_FILTER_ARRANGEMENT_GRBG: { + return OpcodeListBuilder::CFA_GRBG; + } + case ACAMERA_SENSOR_INFO_COLOR_FILTER_ARRANGEMENT_GBRG: { + return OpcodeListBuilder::CFA_GBRG; + } + case ACAMERA_SENSOR_INFO_COLOR_FILTER_ARRANGEMENT_BGGR: { + return OpcodeListBuilder::CFA_BGGR; + } + default: { + return OpcodeListBuilder::CFA_RGGB; + } + } +} + +/** + * For each color plane, find the corresponding noise profile coefficients given in the + * per-channel noise profile. If multiple channels in the CFA correspond to a color in the color + * plane, this method takes the pair of noise profile coefficients with the higher S coefficient. + * + * perChannelNoiseProfile - numChannels * 2 noise profile coefficients. + * cfa - numChannels color channels corresponding to each of the per-channel noise profile + * coefficients. + * numChannels - the number of noise profile coefficient pairs and color channels given in + * the perChannelNoiseProfile and cfa arguments, respectively. + * planeColors - the color planes in the noise profile output. + * numPlanes - the number of planes in planeColors and pairs of coefficients in noiseProfile. + * noiseProfile - 2 * numPlanes doubles containing numPlanes pairs of noise profile coefficients. + * + * returns OK, or a negative error code on failure. + */ +static status_t generateNoiseProfile(const double* perChannelNoiseProfile, uint8_t* cfa, + size_t numChannels, const uint8_t* planeColors, size_t numPlanes, + /*out*/double* noiseProfile) { + + for (size_t p = 0; p < numPlanes; ++p) { + size_t S = p * 2; + size_t O = p * 2 + 1; + + noiseProfile[S] = 0; + noiseProfile[O] = 0; + bool uninitialized = true; + for (size_t c = 0; c < numChannels; ++c) { + if (cfa[c] == planeColors[p] && perChannelNoiseProfile[c * 2] > noiseProfile[S]) { + noiseProfile[S] = perChannelNoiseProfile[c * 2]; + noiseProfile[O] = perChannelNoiseProfile[c * 2 + 1]; + uninitialized = false; + } + } + if (uninitialized) { + ALOGE("%s: No valid NoiseProfile coefficients for color plane %zu", + __FUNCTION__, p); + return BAD_VALUE; + } + } + return OK; +} + +static void undistort(/*inout*/double& x, /*inout*/double& y, + const std::array& distortion, + const float cx, const float cy, const float f) { + double xp = (x - cx) / f; + double yp = (y - cy) / f; + + double x2 = xp * xp; + double y2 = yp * yp; + double r2 = x2 + y2; + double xy2 = 2.0 * xp * yp; + + const float k0 = distortion[0]; + const float k1 = distortion[1]; + const float k2 = distortion[2]; + const float k3 = distortion[3]; + const float p1 = distortion[4]; + const float p2 = distortion[5]; + + double kr = k0 + ((k3 * r2 + k2) * r2 + k1) * r2; + double xpp = xp * kr + p1 * xy2 + p2 * (r2 + 2.0 * x2); + double ypp = yp * kr + p1 * (r2 + 2.0 * y2) + p2 * xy2; + + x = xpp * f + cx; + y = ypp * f + cy; + return; +} + +static inline bool unDistortWithinPreCorrArray( + double x, double y, + const std::array& distortion, + const float cx, const float cy, const float f, + const int preCorrW, const int preCorrH, const int xMin, const int yMin) { + undistort(x, y, distortion, cx, cy, f); + // xMin and yMin are inclusive, and xMax and yMax are exclusive. + int xMax = xMin + preCorrW; + int yMax = yMin + preCorrH; + if (x < xMin || y < yMin || x >= xMax || y >= yMax) { + return false; + } + return true; +} + +static inline bool boxWithinPrecorrectionArray( + int left, int top, int right, int bottom, + const std::array& distortion, + const float cx, const float cy, const float f, + const int preCorrW, const int preCorrH, const int xMin, const int yMin){ + // Top row + if (!unDistortWithinPreCorrArray(left, top, + distortion, cx, cy, f, preCorrW, preCorrH, xMin, yMin)) { + return false; + } + + if (!unDistortWithinPreCorrArray(cx, top, + distortion, cx, cy, f, preCorrW, preCorrH, xMin, yMin)) { + return false; + } + + if (!unDistortWithinPreCorrArray(right, top, + distortion, cx, cy, f, preCorrW, preCorrH, xMin, yMin)) { + return false; + } + + // Middle row + if (!unDistortWithinPreCorrArray(left, cy, + distortion, cx, cy, f, preCorrW, preCorrH, xMin, yMin)) { + return false; + } + + if (!unDistortWithinPreCorrArray(right, cy, + distortion, cx, cy, f, preCorrW, preCorrH, xMin, yMin)) { + return false; + } + + // Bottom row + if (!unDistortWithinPreCorrArray(left, bottom, + distortion, cx, cy, f, preCorrW, preCorrH, xMin, yMin)) { + return false; + } + + if (!unDistortWithinPreCorrArray(cx, bottom, + distortion, cx, cy, f, preCorrW, preCorrH, xMin, yMin)) { + return false; + } + + if (!unDistortWithinPreCorrArray(right, bottom, + distortion, cx, cy, f, preCorrW, preCorrH, xMin, yMin)) { + return false; + } + return true; +} + +static inline bool scaledBoxWithinPrecorrectionArray( + double scale/*must be <= 1.0*/, + const std::array& distortion, + const float cx, const float cy, const float f, + const int preCorrW, const int preCorrH, + const int xMin, const int yMin){ + + double left = cx * (1.0 - scale); + double right = (preCorrW - 1) * scale + cx * (1.0 - scale); + double top = cy * (1.0 - scale); + double bottom = (preCorrH - 1) * scale + cy * (1.0 - scale); + + return boxWithinPrecorrectionArray(left, top, right, bottom, + distortion, cx, cy, f, preCorrW, preCorrH, xMin, yMin); +} + +static status_t findPostCorrectionScale( + double stepSize, double minScale, + const std::array& distortion, + const float cx, const float cy, const float f, + const int preCorrW, const int preCorrH, const int xMin, const int yMin, + /*out*/ double* outScale) { + if (outScale == nullptr) { + ALOGE("%s: outScale must not be null", __FUNCTION__); + return BAD_VALUE; + } + + for (double scale = 1.0; scale > minScale; scale -= stepSize) { + if (scaledBoxWithinPrecorrectionArray( + scale, distortion, cx, cy, f, preCorrW, preCorrH, xMin, yMin)) { + *outScale = scale; + return OK; + } + } + ALOGE("%s: cannot find cropping scale for lens distortion: stepSize %f, minScale %f", + __FUNCTION__, stepSize, minScale); + return BAD_VALUE; +} + +// Apply a scale factor to distortion coefficients so that the image is zoomed out and all pixels +// are sampled within the precorrection array +static void normalizeLensDistortion( + /*inout*/std::array& distortion, + float cx, float cy, float f, int preCorrW, int preCorrH, int xMin = 0, int yMin = 0) { + ALOGV("%s: distortion [%f, %f, %f, %f, %f, %f], (cx,cy) (%f, %f), f %f, (W,H) (%d, %d)" + ", (xmin, ymin, xmax, ymax) (%d, %d, %d, %d)", + __FUNCTION__, distortion[0], distortion[1], distortion[2], + distortion[3], distortion[4], distortion[5], + cx, cy, f, preCorrW, preCorrH, + xMin, yMin, xMin + preCorrW - 1, yMin + preCorrH - 1); + + // Only update distortion coeffients if we can find a good bounding box + double scale = 1.0; + if (OK == findPostCorrectionScale(0.002, 0.5, + distortion, cx, cy, f, preCorrW, preCorrH, xMin, yMin, + /*out*/&scale)) { + ALOGV("%s: scaling distortion coefficients by %f", __FUNCTION__, scale); + // The formula: + // xc = xi * (k0 + k1*r^2 + k2*r^4 + k3*r^6) + k4 * (2*xi*yi) + k5 * (r^2 + 2*xi^2) + // To create effective zoom we want to replace xi by xi *m, yi by yi*m and r^2 by r^2*m^2 + // Factor the extra m power terms into k0~k6 + std::array scalePowers = {1, 3, 5, 7, 2, 2}; + for (size_t i = 0; i < 6; i++) { + distortion[i] *= pow(scale, scalePowers[i]); + } + } + return; +} + +// ---------------------------------------------------------------------------- +#if 0 +static NativeContext* DngCreator_getNativeContext(JNIEnv* env, jobject thiz) { + ALOGV("%s:", __FUNCTION__); + return reinterpret_cast(env->GetLongField(thiz, + gDngCreatorClassInfo.mNativeContext)); +} + +static void DngCreator_setNativeContext(JNIEnv* env, jobject thiz, sp context) { + ALOGV("%s:", __FUNCTION__); + NativeContext* current = DngCreator_getNativeContext(env, thiz); + + if (context != nullptr) { + context->incStrong((void*) DngCreator_setNativeContext); + } + + if (current) { + current->decStrong((void*) DngCreator_setNativeContext); + } + + env->SetLongField(thiz, gDngCreatorClassInfo.mNativeContext, + reinterpret_cast(context.get())); +} + +static void DngCreator_nativeClassInit(JNIEnv* env, jclass clazz) { + ALOGV("%s:", __FUNCTION__); + + gDngCreatorClassInfo.mNativeContext = GetFieldIDOrDie(env, + clazz, ANDROID_DNGCREATOR_CTX_JNI_ID, "J"); + + jclass outputStreamClazz = FindClassOrDie(env, "java/io/OutputStream"); + gOutputStreamClassInfo.mWriteMethod = GetMethodIDOrDie(env, + outputStreamClazz, "write", "([BII)V"); + + jclass inputStreamClazz = FindClassOrDie(env, "java/io/InputStream"); + gInputStreamClassInfo.mReadMethod = GetMethodIDOrDie(env, inputStreamClazz, "read", "([BII)I"); + gInputStreamClassInfo.mSkipMethod = GetMethodIDOrDie(env, inputStreamClazz, "skip", "(J)J"); + + jclass inputBufferClazz = FindClassOrDie(env, "java/nio/ByteBuffer"); + gInputByteBufferClassInfo.mGetMethod = GetMethodIDOrDie(env, + inputBufferClazz, "get", "([BII)Ljava/nio/ByteBuffer;"); +} +#endif + +void DngCreator::init(ACameraMetadata* characteristics, + ACameraMetadata* results, const std::string& captureTime) { + ALOGV("%s:", __FUNCTION__); + + sp nativeContext = new NativeContext(characteristics, results); + + size_t len = captureTime.size() + 1; + if (len != NativeContext::DATETIME_COUNT) { +#if 0 + jniThrowException(env, "java/lang/IllegalArgumentException", + "Formatted capture time string length is not required 20 characters"); +#endif + return; + } + + nativeContext->setCaptureTime(captureTime); + + // DngCreator_setNativeContext(env, thiz, nativeContext); +} + +sp DngCreator::setup(uint32_t imageWidth, uint32_t imageHeight) +{ + ACameraMetadata* characteristics = getCharacteristics(); + ACameraMetadata* results = getResult(); + + sp writer = new TiffWriter(); + + uint32_t preXMin = 0; + uint32_t preYMin = 0; + uint32_t preWidth = 0; + uint32_t preHeight = 0; + uint8_t colorFilter = 0; + camera_status_t status; + bool isBayer = true; + { + // Check dimensions + ACameraMetadata_const_entry entry = { 0 }; + status = ACameraMetadata_getConstEntry(characteristics, ACAMERA_SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE, &entry); + BAIL_IF_EMPTY_RET_NULL_SP(entry, env, TAG_IMAGEWIDTH, writer); + preXMin = static_cast(entry.data.i32[0]); + preYMin = static_cast(entry.data.i32[1]); + preWidth = static_cast(entry.data.i32[2]); + preHeight = static_cast(entry.data.i32[3]); + + ACameraMetadata_const_entry pixelArrayEntry = { 0 }; + status = ACameraMetadata_getConstEntry(characteristics, ACAMERA_SENSOR_INFO_PIXEL_ARRAY_SIZE, &pixelArrayEntry); + uint32_t pixWidth = static_cast(pixelArrayEntry.data.i32[0]); + uint32_t pixHeight = static_cast(pixelArrayEntry.data.i32[1]); + + if (!((imageWidth == preWidth && imageHeight == preHeight) || + (imageWidth == pixWidth && imageHeight == pixHeight))) { +#if 0 + jniThrowException(env, "java/lang/AssertionError", + "Height and width of image buffer did not match height and width of" + "either the preCorrectionActiveArraySize or the pixelArraySize."); +#endif + return nullptr; + } + + ACameraMetadata_const_entry colorFilterEntry = { 0 }; + status = ACameraMetadata_getConstEntry(characteristics, ACAMERA_SENSOR_INFO_COLOR_FILTER_ARRANGEMENT, &colorFilterEntry); + colorFilter = colorFilterEntry.data.u8[0]; + ACameraMetadata_const_entry capabilitiesEntry = { 0 }; + status = ACameraMetadata_getConstEntry(characteristics, ACAMERA_REQUEST_AVAILABLE_CAPABILITIES, & capabilitiesEntry); + size_t capsCount = capabilitiesEntry.count; + const uint8_t* caps = capabilitiesEntry.data.u8; + + if (std::find(caps, caps+capsCount, ACAMERA_REQUEST_AVAILABLE_CAPABILITIES_MONOCHROME) + != caps+capsCount) { + isBayer = false; + } else if (colorFilter == ACAMERA_SENSOR_INFO_COLOR_FILTER_ARRANGEMENT_MONO || + colorFilter == ACAMERA_SENSOR_INFO_COLOR_FILTER_ARRANGEMENT_NIR) { +#if 0 + jniThrowException(env, "java/lang/AssertionError", + "A camera device with MONO/NIR color filter must have MONOCHROME capability."); +#endif + return nullptr; + } + } + + writer->addIfd(TIFF_IFD_0); + + status_t err = OK; + + const uint32_t samplesPerPixel = 1; + const uint32_t bitsPerSample = BITS_PER_SAMPLE; + + OpcodeListBuilder::CfaLayout opcodeCfaLayout = OpcodeListBuilder::CFA_NONE; + uint8_t cfaPlaneColor[3] = {0, 1, 2}; + ACameraMetadata_const_entry cfaEntry = { 0 }; + status = ACameraMetadata_getConstEntry(characteristics, ACAMERA_SENSOR_INFO_COLOR_FILTER_ARRANGEMENT, &cfaEntry); + BAIL_IF_EMPTY_RET_NULL_SP(cfaEntry, env, TAG_CFAPATTERN, writer); + uint8_t cfaEnum = cfaEntry.data.u8[0]; + + // TODO: Greensplit. + // TODO: Add remaining non-essential tags + + // Setup main image tags + + { + // Set orientation + uint16_t orientation = TAG_ORIENTATION_NORMAL; + BAIL_IF_INVALID_RET_NULL_SP(writer->addEntry(TAG_ORIENTATION, 1, &orientation, TIFF_IFD_0), + env, TAG_ORIENTATION, writer); + } + + { + // Set subfiletype + uint32_t subfileType = 0; // Main image + BAIL_IF_INVALID_RET_NULL_SP(writer->addEntry(TAG_NEWSUBFILETYPE, 1, &subfileType, + TIFF_IFD_0), env, TAG_NEWSUBFILETYPE, writer); + } + + { + // Set bits per sample + uint16_t bits = static_cast(bitsPerSample); + BAIL_IF_INVALID_RET_NULL_SP(writer->addEntry(TAG_BITSPERSAMPLE, 1, &bits, TIFF_IFD_0), env, + TAG_BITSPERSAMPLE, writer); + } + + { + // Set compression + uint16_t compression = 1; // None + BAIL_IF_INVALID_RET_NULL_SP(writer->addEntry(TAG_COMPRESSION, 1, &compression, + TIFF_IFD_0), env, TAG_COMPRESSION, writer); + } + + { + // Set dimensions + BAIL_IF_INVALID_RET_NULL_SP(writer->addEntry(TAG_IMAGEWIDTH, 1, &imageWidth, TIFF_IFD_0), + env, TAG_IMAGEWIDTH, writer); + BAIL_IF_INVALID_RET_NULL_SP(writer->addEntry(TAG_IMAGELENGTH, 1, &imageHeight, TIFF_IFD_0), + env, TAG_IMAGELENGTH, writer); + } + + { + // Set photometric interpretation + uint16_t interpretation = isBayer ? 32803 /* CFA */ : + 34892; /* Linear Raw */; + BAIL_IF_INVALID_RET_NULL_SP(writer->addEntry(TAG_PHOTOMETRICINTERPRETATION, 1, + &interpretation, TIFF_IFD_0), env, TAG_PHOTOMETRICINTERPRETATION, writer); + } + + { + uint16_t repeatDim[2] = {2, 2}; + if (!isBayer) { + repeatDim[0] = repeatDim[1] = 1; + } + BAIL_IF_INVALID_RET_NULL_SP(writer->addEntry(TAG_BLACKLEVELREPEATDIM, 2, repeatDim, + TIFF_IFD_0), env, TAG_BLACKLEVELREPEATDIM, writer); + + // Set blacklevel tags, using dynamic black level if available + ACameraMetadata_const_entry entry = { 0 }; + camera_status_t status = ACameraMetadata_getConstEntry(results, ACAMERA_SENSOR_DYNAMIC_BLACK_LEVEL, &entry); + uint32_t blackLevelRational[8] = {0}; + if (entry.count != 0) { + BAIL_IF_EXPR_RET_NULL_SP(entry.count != 4, env, TAG_BLACKLEVEL, writer); + for (size_t i = 0; i < entry.count; i++) { + blackLevelRational[i * 2] = static_cast(entry.data.f[i] * 100); + blackLevelRational[i * 2 + 1] = 100; + } + } else { + // Fall back to static black level which is guaranteed + status = ACameraMetadata_getConstEntry(characteristics, ACAMERA_SENSOR_BLACK_LEVEL_PATTERN, &entry); + BAIL_IF_EXPR_RET_NULL_SP(entry.count != 4, env, TAG_BLACKLEVEL, writer); + for (size_t i = 0; i < entry.count; i++) { + blackLevelRational[i * 2] = static_cast(entry.data.i32[i]); + blackLevelRational[i * 2 + 1] = 1; + } + } + BAIL_IF_INVALID_RET_NULL_SP(writer->addEntry(TAG_BLACKLEVEL, repeatDim[0]*repeatDim[1], + blackLevelRational, TIFF_IFD_0), env, TAG_BLACKLEVEL, writer); + } + + { + // Set samples per pixel + uint16_t samples = static_cast(samplesPerPixel); + BAIL_IF_INVALID_RET_NULL_SP(writer->addEntry(TAG_SAMPLESPERPIXEL, 1, &samples, TIFF_IFD_0), + env, TAG_SAMPLESPERPIXEL, writer); + } + + { + // Set planar configuration + uint16_t config = 1; // Chunky + BAIL_IF_INVALID_RET_NULL_SP(writer->addEntry(TAG_PLANARCONFIGURATION, 1, &config, + TIFF_IFD_0), env, TAG_PLANARCONFIGURATION, writer); + } + + // All CFA pattern tags are not necessary for monochrome cameras. + if (isBayer) { + // Set CFA pattern dimensions + uint16_t repeatDim[2] = {2, 2}; + BAIL_IF_INVALID_RET_NULL_SP(writer->addEntry(TAG_CFAREPEATPATTERNDIM, 2, repeatDim, + TIFF_IFD_0), env, TAG_CFAREPEATPATTERNDIM, writer); + + // Set CFA pattern + const int cfaLength = 4; + uint8_t cfa[cfaLength]; + if ((err = convertCFA(cfaEnum, /*out*/cfa)) != OK) { +#if 0 + jniThrowExceptionFmt(env, "java/lang/IllegalStateException", + "Invalid metadata for tag %d", TAG_CFAPATTERN); +#endif + } + + BAIL_IF_INVALID_RET_NULL_SP(writer->addEntry(TAG_CFAPATTERN, cfaLength, cfa, TIFF_IFD_0), + env, TAG_CFAPATTERN, writer); + + opcodeCfaLayout = convertCFAEnumToOpcodeLayout(cfaEnum); + + // Set CFA plane color + BAIL_IF_INVALID_RET_NULL_SP(writer->addEntry(TAG_CFAPLANECOLOR, 3, cfaPlaneColor, + TIFF_IFD_0), env, TAG_CFAPLANECOLOR, writer); + + // Set CFA layout + uint16_t cfaLayout = 1; + BAIL_IF_INVALID_RET_NULL_SP(writer->addEntry(TAG_CFALAYOUT, 1, &cfaLayout, TIFF_IFD_0), + env, TAG_CFALAYOUT, writer); + } + + { + // image description + uint8_t imageDescription = '\0'; // empty + BAIL_IF_INVALID_RET_NULL_SP(writer->addEntry(TAG_IMAGEDESCRIPTION, 1, &imageDescription, + TIFF_IFD_0), env, TAG_IMAGEDESCRIPTION, writer); + } + + { + // make + // Use "" to represent unknown make as suggested in TIFF/EP spec. + char manufacturer[PROP_VALUE_MAX] = { 0 }; + __system_property_get("ro.product.manufacturer", manufacturer); + uint32_t count = static_cast(strlen(manufacturer)) + 1; + + BAIL_IF_INVALID_RET_NULL_SP(writer->addEntry(TAG_MAKE, count, + reinterpret_cast(manufacturer), TIFF_IFD_0), env, TAG_MAKE, + writer); + } + + { + // model + // Use "" to represent unknown model as suggested in TIFF/EP spec. + char model[PROP_VALUE_MAX] = { 0 }; + __system_property_get("ro.product.model", model); + uint32_t count = static_cast(strlen(model)) + 1; + BAIL_IF_INVALID_RET_NULL_SP(writer->addEntry(TAG_MODEL, count, + reinterpret_cast(model), TIFF_IFD_0), env, TAG_MODEL, + writer); + } + + { + // x resolution + uint32_t xres[] = { 72, 1 }; // default 72 ppi + BAIL_IF_INVALID_RET_NULL_SP(writer->addEntry(TAG_XRESOLUTION, 1, xres, TIFF_IFD_0), + env, TAG_XRESOLUTION, writer); + + // y resolution + uint32_t yres[] = { 72, 1 }; // default 72 ppi + BAIL_IF_INVALID_RET_NULL_SP(writer->addEntry(TAG_YRESOLUTION, 1, yres, TIFF_IFD_0), + env, TAG_YRESOLUTION, writer); + + uint16_t unit = 2; // inches + BAIL_IF_INVALID_RET_NULL_SP(writer->addEntry(TAG_RESOLUTIONUNIT, 1, &unit, TIFF_IFD_0), + env, TAG_RESOLUTIONUNIT, writer); + } + + { + // software + char software[PROP_VALUE_MAX] = { 0 }; + __system_property_get("ro.build.fingerprint", software); + uint32_t count = static_cast(strlen(software)) + 1; + BAIL_IF_INVALID_RET_NULL_SP(writer->addEntry(TAG_SOFTWARE, count, + reinterpret_cast(software), TIFF_IFD_0), env, TAG_SOFTWARE, + writer); + } + + if (hasCaptureTime()) { + // datetime + std::string captureTime = getCaptureTime(); + + if (writer->addEntry(TAG_DATETIME, NativeContext::DATETIME_COUNT, + reinterpret_cast(captureTime.c_str()), TIFF_IFD_0) != OK) { +#if 0 + jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException", + "Invalid metadata for tag %x", TAG_DATETIME); +#endif + return nullptr; + } + + // datetime original + if (writer->addEntry(TAG_DATETIMEORIGINAL, NativeContext::DATETIME_COUNT, + reinterpret_cast(captureTime.c_str()), TIFF_IFD_0) != OK) { +#if 0 + jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException", + "Invalid metadata for tag %x", TAG_DATETIMEORIGINAL); +#endif + return nullptr; + } + } + + { + // TIFF/EP standard id + uint8_t standardId[] = { 1, 0, 0, 0 }; + BAIL_IF_INVALID_RET_NULL_SP(writer->addEntry(TAG_TIFFEPSTANDARDID, 4, standardId, + TIFF_IFD_0), env, TAG_TIFFEPSTANDARDID, writer); + } + + { + // copyright + uint8_t copyright = '\0'; // empty + BAIL_IF_INVALID_RET_NULL_SP(writer->addEntry(TAG_COPYRIGHT, 1, ©right, + TIFF_IFD_0), env, TAG_COPYRIGHT, writer); + } + + { + // exposure time + ACameraMetadata_const_entry entry = { 0 }; + status = ACameraMetadata_getConstEntry(results, ACAMERA_SENSOR_EXPOSURE_TIME, &entry); + BAIL_IF_EMPTY_RET_NULL_SP(entry, env, TAG_EXPOSURETIME, writer); + + int64_t exposureTime = *(entry.data.i64); + + if (exposureTime < 0) { + // Should be unreachable +#if 0 + jniThrowException(env, "java/lang/IllegalArgumentException", + "Negative exposure time in metadata"); +#endif + return nullptr; + } + + // Ensure exposure time doesn't overflow (for exposures > 4s) + uint32_t denominator = 1000000000; + while (exposureTime > UINT32_MAX) { + exposureTime >>= 1; + denominator >>= 1; + if (denominator == 0) { + // Should be unreachable +#if 0 + jniThrowException(env, "java/lang/IllegalArgumentException", + "Exposure time too long"); +#endif + return nullptr; + } + } + + uint32_t exposure[] = { static_cast(exposureTime), denominator }; + BAIL_IF_INVALID_RET_NULL_SP(writer->addEntry(TAG_EXPOSURETIME, 1, exposure, + TIFF_IFD_0), env, TAG_EXPOSURETIME, writer); + + } + + { + // ISO speed ratings + ACameraMetadata_const_entry entry = { 0 }; + status = ACameraMetadata_getConstEntry(results, ACAMERA_SENSOR_SENSITIVITY, &entry); + BAIL_IF_EMPTY_RET_NULL_SP(entry, env, TAG_ISOSPEEDRATINGS, writer); + + int32_t tempIso = *(entry.data.i32); + if (tempIso < 0) { +#if 0 + jniThrowException(env, "java/lang/IllegalArgumentException", + "Negative ISO value"); +#endif + return nullptr; + } + + if (tempIso > UINT16_MAX) { + ALOGW("%s: ISO value overflows UINT16_MAX, clamping to max", __FUNCTION__); + tempIso = UINT16_MAX; + } + + uint16_t iso = static_cast(tempIso); + BAIL_IF_INVALID_RET_NULL_SP(writer->addEntry(TAG_ISOSPEEDRATINGS, 1, &iso, + TIFF_IFD_0), env, TAG_ISOSPEEDRATINGS, writer); + } + + { + // Baseline exposure + ACameraMetadata_const_entry entry = { 0 }; + status = ACameraMetadata_getConstEntry(results, ACAMERA_CONTROL_POST_RAW_SENSITIVITY_BOOST, &entry); + BAIL_IF_EMPTY_RET_NULL_SP(entry, env, TAG_BASELINEEXPOSURE, writer); + + // post RAW gain should be boostValue / 100 + double postRAWGain = static_cast (entry.data.i32[0]) / 100.f; + // Baseline exposure should be in EV units so log2(gain) = + // log10(gain)/log10(2) + double baselineExposure = std::log(postRAWGain) / std::log(2.0f); + int32_t baseExposureSRat[] = { static_cast (baselineExposure * 100), + 100 }; + BAIL_IF_INVALID_RET_NULL_SP(writer->addEntry(TAG_BASELINEEXPOSURE, 1, + baseExposureSRat, TIFF_IFD_0), env, TAG_BASELINEEXPOSURE, writer); + } + + { + // focal length + ACameraMetadata_const_entry entry = { 0 }; + status = ACameraMetadata_getConstEntry(results, ACAMERA_LENS_FOCAL_LENGTH, &entry); + BAIL_IF_EMPTY_RET_NULL_SP(entry, env, TAG_FOCALLENGTH, writer); + + uint32_t focalLength[] = { static_cast(*(entry.data.f) * 100), 100 }; + BAIL_IF_INVALID_RET_NULL_SP(writer->addEntry(TAG_FOCALLENGTH, 1, focalLength, + TIFF_IFD_0), env, TAG_FOCALLENGTH, writer); + } + + { + // f number + ACameraMetadata_const_entry entry = { 0 }; + status = ACameraMetadata_getConstEntry(results, ACAMERA_LENS_APERTURE, &entry); + BAIL_IF_EMPTY_RET_NULL_SP(entry, env, TAG_FNUMBER, writer); + + uint32_t fnum[] = { static_cast(*(entry.data.f) * 100), 100 }; + BAIL_IF_INVALID_RET_NULL_SP(writer->addEntry(TAG_FNUMBER, 1, fnum, + TIFF_IFD_0), env, TAG_FNUMBER, writer); + } + + { + // Set DNG version information + uint8_t version[4] = {1, 4, 0, 0}; + BAIL_IF_INVALID_RET_NULL_SP(writer->addEntry(TAG_DNGVERSION, 4, version, TIFF_IFD_0), + env, TAG_DNGVERSION, writer); + + uint8_t backwardVersion[4] = {1, 1, 0, 0}; + BAIL_IF_INVALID_RET_NULL_SP(writer->addEntry(TAG_DNGBACKWARDVERSION, 4, backwardVersion, + TIFF_IFD_0), env, TAG_DNGBACKWARDVERSION, writer); + } + + { + // Set whitelevel + ACameraMetadata_const_entry entry = { 0 }; + status = ACameraMetadata_getConstEntry(characteristics, ACAMERA_SENSOR_INFO_WHITE_LEVEL, &entry); + BAIL_IF_EMPTY_RET_NULL_SP(entry, env, TAG_WHITELEVEL, writer); + uint32_t whiteLevel = static_cast(entry.data.i32[0]); + BAIL_IF_INVALID_RET_NULL_SP(writer->addEntry(TAG_WHITELEVEL, 1, &whiteLevel, TIFF_IFD_0), + env, TAG_WHITELEVEL, writer); + } + + { + // Set default scale + uint32_t defaultScale[4] = {1, 1, 1, 1}; + BAIL_IF_INVALID_RET_NULL_SP(writer->addEntry(TAG_DEFAULTSCALE, 2, defaultScale, + TIFF_IFD_0), env, TAG_DEFAULTSCALE, writer); + } + + bool singleIlluminant = false; + if (isBayer) { + // Set calibration illuminants + ACameraMetadata_const_entry entry1 = { 0 }; + status = ACameraMetadata_getConstEntry(characteristics, ACAMERA_SENSOR_REFERENCE_ILLUMINANT1, &entry1); + BAIL_IF_EMPTY_RET_NULL_SP(entry1, env, TAG_CALIBRATIONILLUMINANT1, writer); + ACameraMetadata_const_entry entry2 = { 0 }; + status = ACameraMetadata_getConstEntry(characteristics, ACAMERA_SENSOR_REFERENCE_ILLUMINANT2, &entry2); + if (entry2.count == 0) { + singleIlluminant = true; + } + uint16_t ref1 = entry1.data.u8[0]; + + BAIL_IF_INVALID_RET_NULL_SP(writer->addEntry(TAG_CALIBRATIONILLUMINANT1, 1, &ref1, + TIFF_IFD_0), env, TAG_CALIBRATIONILLUMINANT1, writer); + + if (!singleIlluminant) { + uint16_t ref2 = entry2.data.u8[0]; + BAIL_IF_INVALID_RET_NULL_SP(writer->addEntry(TAG_CALIBRATIONILLUMINANT2, 1, &ref2, + TIFF_IFD_0), env, TAG_CALIBRATIONILLUMINANT2, writer); + } + } + + if (isBayer) { + // Set color transforms + ACameraMetadata_const_entry entry1 = { 0 }; + status = ACameraMetadata_getConstEntry(characteristics, ACAMERA_SENSOR_COLOR_TRANSFORM1, &entry1); + BAIL_IF_EMPTY_RET_NULL_SP(entry1, env, TAG_COLORMATRIX1, writer); + + int32_t colorTransform1[entry1.count * 2]; + + size_t ctr = 0; + for(size_t i = 0; i < entry1.count; ++i) { + colorTransform1[ctr++] = entry1.data.r[i].numerator; + colorTransform1[ctr++] = entry1.data.r[i].denominator; + } + + BAIL_IF_INVALID_RET_NULL_SP(writer->addEntry(TAG_COLORMATRIX1, entry1.count, + colorTransform1, TIFF_IFD_0), env, TAG_COLORMATRIX1, writer); + + if (!singleIlluminant) { + ACameraMetadata_const_entry entry2 = { 0 }; + status = ACameraMetadata_getConstEntry(characteristics, ACAMERA_SENSOR_COLOR_TRANSFORM2, &entry2); + BAIL_IF_EMPTY_RET_NULL_SP(entry2, env, TAG_COLORMATRIX2, writer); + int32_t colorTransform2[entry2.count * 2]; + + ctr = 0; + for(size_t i = 0; i < entry2.count; ++i) { + colorTransform2[ctr++] = entry2.data.r[i].numerator; + colorTransform2[ctr++] = entry2.data.r[i].denominator; + } + + BAIL_IF_INVALID_RET_NULL_SP(writer->addEntry(TAG_COLORMATRIX2, entry2.count, + colorTransform2, TIFF_IFD_0), env, TAG_COLORMATRIX2, writer); + } + } + + if (isBayer) { + // Set calibration transforms + ACameraMetadata_const_entry entry1 = { 0 }; + status = ACameraMetadata_getConstEntry(characteristics, ACAMERA_SENSOR_CALIBRATION_TRANSFORM1, &entry1); + BAIL_IF_EMPTY_RET_NULL_SP(entry1, env, TAG_CAMERACALIBRATION1, writer); + + int32_t calibrationTransform1[entry1.count * 2]; + + size_t ctr = 0; + for(size_t i = 0; i < entry1.count; ++i) { + calibrationTransform1[ctr++] = entry1.data.r[i].numerator; + calibrationTransform1[ctr++] = entry1.data.r[i].denominator; + } + + BAIL_IF_INVALID_RET_NULL_SP(writer->addEntry(TAG_CAMERACALIBRATION1, entry1.count, + calibrationTransform1, TIFF_IFD_0), env, TAG_CAMERACALIBRATION1, writer); + + if (!singleIlluminant) { + ACameraMetadata_const_entry entry2 = { 0 }; + status = ACameraMetadata_getConstEntry(characteristics, ACAMERA_SENSOR_CALIBRATION_TRANSFORM2, &entry2); + BAIL_IF_EMPTY_RET_NULL_SP(entry2, env, TAG_CAMERACALIBRATION2, writer); + int32_t calibrationTransform2[entry2.count * 2]; + + ctr = 0; + for(size_t i = 0; i < entry2.count; ++i) { + calibrationTransform2[ctr++] = entry2.data.r[i].numerator; + calibrationTransform2[ctr++] = entry2.data.r[i].denominator; + } + + BAIL_IF_INVALID_RET_NULL_SP(writer->addEntry(TAG_CAMERACALIBRATION2, entry2.count, + calibrationTransform2, TIFF_IFD_0), env, TAG_CAMERACALIBRATION2, writer); + } + } + + if (isBayer) { + // Set forward transforms + ACameraMetadata_const_entry entry1 = { 0 }; + status = ACameraMetadata_getConstEntry(characteristics, ACAMERA_SENSOR_FORWARD_MATRIX1, &entry1); + BAIL_IF_EMPTY_RET_NULL_SP(entry1, env, TAG_FORWARDMATRIX1, writer); + + int32_t forwardTransform1[entry1.count * 2]; + + size_t ctr = 0; + for(size_t i = 0; i < entry1.count; ++i) { + forwardTransform1[ctr++] = entry1.data.r[i].numerator; + forwardTransform1[ctr++] = entry1.data.r[i].denominator; + } + + BAIL_IF_INVALID_RET_NULL_SP(writer->addEntry(TAG_FORWARDMATRIX1, entry1.count, + forwardTransform1, TIFF_IFD_0), env, TAG_FORWARDMATRIX1, writer); + + if (!singleIlluminant) { + ACameraMetadata_const_entry entry2 = { 0 }; + status = ACameraMetadata_getConstEntry(characteristics, ACAMERA_SENSOR_FORWARD_MATRIX2, &entry2); + BAIL_IF_EMPTY_RET_NULL_SP(entry2, env, TAG_FORWARDMATRIX2, writer); + int32_t forwardTransform2[entry2.count * 2]; + + ctr = 0; + for(size_t i = 0; i < entry2.count; ++i) { + forwardTransform2[ctr++] = entry2.data.r[i].numerator; + forwardTransform2[ctr++] = entry2.data.r[i].denominator; + } + + BAIL_IF_INVALID_RET_NULL_SP(writer->addEntry(TAG_FORWARDMATRIX2, entry2.count, + forwardTransform2, TIFF_IFD_0), env, TAG_FORWARDMATRIX2, writer); + } + } + + if (isBayer) { + // Set camera neutral + ACameraMetadata_const_entry entry = { 0 }; + status = ACameraMetadata_getConstEntry(results, ACAMERA_SENSOR_NEUTRAL_COLOR_POINT, &entry); + BAIL_IF_EMPTY_RET_NULL_SP(entry, env, TAG_ASSHOTNEUTRAL, writer); + uint32_t cameraNeutral[entry.count * 2]; + + size_t ctr = 0; + for(size_t i = 0; i < entry.count; ++i) { + cameraNeutral[ctr++] = + static_cast(entry.data.r[i].numerator); + cameraNeutral[ctr++] = + static_cast(entry.data.r[i].denominator); + } + + BAIL_IF_INVALID_RET_NULL_SP(writer->addEntry(TAG_ASSHOTNEUTRAL, entry.count, cameraNeutral, + TIFF_IFD_0), env, TAG_ASSHOTNEUTRAL, writer); + } + + { + // Set dimensions + if (calculateAndSetCrop(characteristics, writer) != OK) { + return nullptr; + } + ACameraMetadata_const_entry entry = { 0 }; + status = ACameraMetadata_getConstEntry(characteristics, ACAMERA_SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE, &entry); + BAIL_IF_EMPTY_RET_NULL_SP(entry, env, TAG_ACTIVEAREA, writer); + uint32_t xmin = static_cast(entry.data.i32[0]); + uint32_t ymin = static_cast(entry.data.i32[1]); + uint32_t width = static_cast(entry.data.i32[2]); + uint32_t height = static_cast(entry.data.i32[3]); + + // If we only have a buffer containing the pre-correction rectangle, ignore the offset + // relative to the pixel array. + if (imageWidth == width && imageHeight == height) { + xmin = 0; + ymin = 0; + } + + uint32_t activeArea[] = {ymin, xmin, ymin + height, xmin + width}; + BAIL_IF_INVALID_RET_NULL_SP(writer->addEntry(TAG_ACTIVEAREA, 4, activeArea, TIFF_IFD_0), + env, TAG_ACTIVEAREA, writer); + } + + { + // Setup unique camera model tag + char model[PROP_VALUE_MAX] = { 0 }; + __system_property_get("ro.product.model", model); + char manufacturer[PROP_VALUE_MAX] = { 0 }; + __system_property_get("ro.product.manufacturer", manufacturer); + char brand[PROP_VALUE_MAX] = { 0 }; + __system_property_get("ro.product.brand", brand); + + std::string cameraModel = model; + cameraModel += "-"; + cameraModel += manufacturer; + cameraModel += "-"; + cameraModel += brand; + + BAIL_IF_INVALID_RET_NULL_SP(writer->addEntry(TAG_UNIQUECAMERAMODEL, cameraModel.size() + 1, + reinterpret_cast(cameraModel.c_str()), TIFF_IFD_0), env, + TAG_UNIQUECAMERAMODEL, writer); + } + + { + // Setup sensor noise model + ACameraMetadata_const_entry entry = { 0 }; + status = ACameraMetadata_getConstEntry(results, ACAMERA_SENSOR_NOISE_PROFILE, &entry); + + const status_t numPlaneColors = isBayer ? 3 : 1; + const status_t numCfaChannels = isBayer ? 4 : 1; + + uint8_t cfaOut[numCfaChannels]; + if ((err = convertCFA(cfaEnum, /*out*/cfaOut)) != OK) { +#if 0 + jniThrowException(env, "java/lang/IllegalArgumentException", + "Invalid CFA from camera characteristics"); +#endif + return nullptr; + } + + double noiseProfile[numPlaneColors * 2]; + + if (entry.count > 0) { + if (entry.count != numCfaChannels * 2) { + ALOGW("%s: Invalid entry count %zu for noise profile returned " + "in characteristics, no noise profile tag written...", + __FUNCTION__, entry.count); + } else { + if ((err = generateNoiseProfile(entry.data.d, cfaOut, numCfaChannels, + cfaPlaneColor, numPlaneColors, /*out*/ noiseProfile)) == OK) { + + BAIL_IF_INVALID_RET_NULL_SP(writer->addEntry(TAG_NOISEPROFILE, + numPlaneColors * 2, noiseProfile, TIFF_IFD_0), env, TAG_NOISEPROFILE, + writer); + } else { + ALOGW("%s: Error converting coefficients for noise profile, no noise profile" + " tag written...", __FUNCTION__); + } + } + } else { + ALOGW("%s: No noise profile found in result metadata. Image quality may be reduced.", + __FUNCTION__); + } + } + + { + // Set up opcode List 2 + OpcodeListBuilder builder; + status_t err = OK; + + // Set up lens shading map + ACameraMetadata_const_entry entry1 = { 0 }; + status = ACameraMetadata_getConstEntry(characteristics, ACAMERA_LENS_INFO_SHADING_MAP_SIZE, &entry1); + + uint32_t lsmWidth = 0; + uint32_t lsmHeight = 0; + + if (entry1.count != 0) { + lsmWidth = static_cast(entry1.data.i32[0]); + lsmHeight = static_cast(entry1.data.i32[1]); + } + + ACameraMetadata_const_entry entry2 = { 0 }; + status = ACameraMetadata_getConstEntry(results, ACAMERA_STATISTICS_LENS_SHADING_MAP, &entry2); + + ACameraMetadata_const_entry entry = { 0 }; + status = ACameraMetadata_getConstEntry(characteristics, ACAMERA_SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE, &entry); + BAIL_IF_EMPTY_RET_NULL_SP(entry, env, TAG_IMAGEWIDTH, writer); + uint32_t xmin = static_cast(entry.data.i32[0]); + uint32_t ymin = static_cast(entry.data.i32[1]); + uint32_t width = static_cast(entry.data.i32[2]); + uint32_t height = static_cast(entry.data.i32[3]); + if (entry2.count > 0 && entry2.count == lsmWidth * lsmHeight * 4) { + // GainMap rectangle is relative to the active area origin. + err = builder.addGainMapsForMetadata(lsmWidth, + lsmHeight, + 0, + 0, + height, + width, + opcodeCfaLayout, + entry2.data.f); + if (err != OK) { + ALOGE("%s: Could not add Lens shading map.", __FUNCTION__); +#if 0 + jniThrowRuntimeException(env, "failed to add lens shading map."); +#endif + return nullptr; + } + } + + // Hot pixel map is specific to bayer camera per DNG spec. + if (isBayer) { + // Set up bad pixel correction list + ACameraMetadata_const_entry entry3 = { 0 }; + status = ACameraMetadata_getConstEntry(characteristics, ACAMERA_STATISTICS_HOT_PIXEL_MAP, &entry3); + + if ((entry3.count % 2) != 0) { + ALOGE("%s: Hot pixel map contains odd number of values, cannot map to pairs!", + __FUNCTION__); +#if 0 + jniThrowRuntimeException(env, "failed to add hotpixel map."); +#endif + return nullptr; + } + + // Adjust the bad pixel coordinates to be relative to the origin of the active area DNG tag + std::vector v; + for (size_t i = 0; i < entry3.count; i += 2) { + int32_t x = entry3.data.i32[i]; + int32_t y = entry3.data.i32[i + 1]; + x -= static_cast(xmin); + y -= static_cast(ymin); + if (x < 0 || y < 0 || static_cast(x) >= width || + static_cast(y) >= height) { + continue; + } + v.push_back(x); + v.push_back(y); + } + const uint32_t* badPixels = &v[0]; + uint32_t badPixelCount = v.size(); + + if (badPixelCount > 0) { + err = builder.addBadPixelListForMetadata(badPixels, badPixelCount, opcodeCfaLayout); + + if (err != OK) { + ALOGE("%s: Could not add hotpixel map.", __FUNCTION__); +#if 0 + jniThrowRuntimeException(env, "failed to add hotpixel map."); +#endif + return nullptr; + } + } + } + + if (builder.getCount() > 0) { + size_t listSize = builder.getSize(); + uint8_t opcodeListBuf[listSize]; + err = builder.buildOpList(opcodeListBuf); + if (err == OK) { + BAIL_IF_INVALID_RET_NULL_SP(writer->addEntry(TAG_OPCODELIST2, listSize, + opcodeListBuf, TIFF_IFD_0), env, TAG_OPCODELIST2, writer); + } else { + ALOGE("%s: Could not build list of opcodes for lens shading map and bad pixel " + "correction.", __FUNCTION__); +#if 0 + jniThrowRuntimeException(env, "failed to construct opcode list for lens shading " + "map and bad pixel correction"); +#endif + return nullptr; + } + } + } + + { + // Set up opcode List 3 + OpcodeListBuilder builder; + status_t err = OK; + + // Set up rectilinear distortion correction + std::array distortion = {1.f, 0.f, 0.f, 0.f, 0.f, 0.f}; + bool gotDistortion = false; + + ACameraMetadata_const_entry entry4 = { 0 }; + status = ACameraMetadata_getConstEntry(results, ACAMERA_LENS_INTRINSIC_CALIBRATION, &entry4); + + if (entry4.count == 5) { + float cx = entry4.data.f[/*c_x*/2]; + float cy = entry4.data.f[/*c_y*/3]; + // Assuming f_x = f_y, or at least close enough. + // Also assuming s = 0, or at least close enough. + float f = entry4.data.f[/*f_x*/0]; + + ACameraMetadata_const_entry entry3 = { 0 }; + status = ACameraMetadata_getConstEntry(results, ACAMERA_LENS_DISTORTION, &entry3); + if (entry3.count == 5) { + gotDistortion = true; + + // Scale the distortion coefficients to create a zoom in warpped image so that all + // pixels are drawn within input image. + for (size_t i = 0; i < entry3.count; i++) { + distortion[i+1] = entry3.data.f[i]; + } + + if (preWidth == imageWidth && preHeight == imageHeight) { + normalizeLensDistortion(distortion, cx, cy, f, preWidth, preHeight); + } else { + // image size == pixel array size (contains optical black pixels) + // cx/cy is defined in preCorrArray so adding the offset + // Also changes default xmin/ymin so that pixels are only + // sampled within preCorrection array + normalizeLensDistortion( + distortion, cx + preXMin, cy + preYMin, f, preWidth, preHeight, + preXMin, preYMin); + } + + float m_x = std::fmaxf(preWidth - cx, cx); + float m_y = std::fmaxf(preHeight - cy, cy); + float m_sq = m_x*m_x + m_y*m_y; + float m = sqrtf(m_sq); // distance to farthest corner from optical center + float f_sq = f * f; + // Conversion factors from Camera2 K factors for new LENS_DISTORTION field + // to DNG spec. + // + // Camera2 / OpenCV assume distortion is applied in a space where focal length + // is factored out, while DNG assumes a normalized space where the distance + // from optical center to the farthest corner is 1. + // Scale from camera2 to DNG spec accordingly. + // distortion[0] is always 1 with the new LENS_DISTORTION field. + const double convCoeff[5] = { + m_sq / f_sq, + pow(m_sq, 2) / pow(f_sq, 2), + pow(m_sq, 3) / pow(f_sq, 3), + m / f, + m / f + }; + for (size_t i = 0; i < entry3.count; i++) { + distortion[i+1] *= convCoeff[i]; + } + } else { + status = ACameraMetadata_getConstEntry(results, ACAMERA_LENS_RADIAL_DISTORTION, &entry3); + if (entry3.count == 6) { + gotDistortion = true; + // Conversion factors from Camera2 K factors to DNG spec. K factors: + // + // Note: these are necessary because our unit system assumes a + // normalized max radius of sqrt(2), whereas the DNG spec's + // WarpRectilinear opcode assumes a normalized max radius of 1. + // Thus, each K coefficient must include the domain scaling + // factor (the DNG domain is scaled by sqrt(2) to emulate the + // domain used by the Camera2 specification). + const double convCoeff[6] = { + sqrt(2), + 2 * sqrt(2), + 4 * sqrt(2), + 8 * sqrt(2), + 2, + 2 + }; + for (size_t i = 0; i < entry3.count; i++) { + distortion[i] = entry3.data.f[i] * convCoeff[i]; + } + } + } + if (gotDistortion) { + err = builder.addWarpRectilinearForMetadata( + distortion.data(), preWidth, preHeight, cx, cy); + if (err != OK) { + ALOGE("%s: Could not add distortion correction.", __FUNCTION__); +#if 0 + jniThrowRuntimeException(env, "failed to add distortion correction."); +#endif + return nullptr; + } + } + } + + if (builder.getCount() > 0) { + size_t listSize = builder.getSize(); + uint8_t opcodeListBuf[listSize]; + err = builder.buildOpList(opcodeListBuf); + if (err == OK) { + BAIL_IF_INVALID_RET_NULL_SP(writer->addEntry(TAG_OPCODELIST3, listSize, + opcodeListBuf, TIFF_IFD_0), env, TAG_OPCODELIST3, writer); + } else { + ALOGE("%s: Could not build list of opcodes for distortion correction.", + __FUNCTION__); +#if 0 + jniThrowRuntimeException(env, "failed to construct opcode list for distortion" + " correction"); +#endif + return nullptr; + } + } + } + + { + // Set up orientation tags. + // Note: There's only one orientation field for the whole file, in IFD0 + // The main image and any thumbnails therefore have the same orientation. + uint16_t orientation = getOrientation(); + BAIL_IF_INVALID_RET_NULL_SP(writer->addEntry(TAG_ORIENTATION, 1, &orientation, TIFF_IFD_0), + env, TAG_ORIENTATION, writer); + + } + + if (hasDescription()){ + // Set Description + std::string description = getDescription(); + size_t len = description.size() + 1; + if (writer->addEntry(TAG_IMAGEDESCRIPTION, len, + reinterpret_cast(description.c_str()), TIFF_IFD_0) != OK) { +#if 0 + jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException", + "Invalid metadata for tag %x", TAG_IMAGEDESCRIPTION); +#endif + } + } + + if (hasGpsData()) { + // Set GPS tags + GpsData gpsData = getGpsData(); + if (!writer->hasIfd(TIFF_IFD_GPSINFO)) { + if (writer->addSubIfd(TIFF_IFD_0, TIFF_IFD_GPSINFO, TiffWriter::GPSINFO) != OK) { + ALOGE("%s: Failed to add GpsInfo IFD %u to IFD %u", __FUNCTION__, TIFF_IFD_GPSINFO, + TIFF_IFD_0); +#if 0 + jniThrowException(env, "java/lang/IllegalStateException", "Failed to add GPSINFO"); +#endif + return nullptr; + } + } + + { + uint8_t version[] = {2, 3, 0, 0}; + BAIL_IF_INVALID_RET_NULL_SP(writer->addEntry(TAG_GPSVERSIONID, 4, version, + TIFF_IFD_GPSINFO), env, TAG_GPSVERSIONID, writer); + } + + { + BAIL_IF_INVALID_RET_NULL_SP(writer->addEntry(TAG_GPSLATITUDEREF, + GpsData::GPS_REF_LENGTH, gpsData.mLatitudeRef, TIFF_IFD_GPSINFO), env, + TAG_GPSLATITUDEREF, writer); + } + + { + BAIL_IF_INVALID_RET_NULL_SP(writer->addEntry(TAG_GPSLONGITUDEREF, + GpsData::GPS_REF_LENGTH, gpsData.mLongitudeRef, TIFF_IFD_GPSINFO), env, + TAG_GPSLONGITUDEREF, writer); + } + + { + BAIL_IF_INVALID_RET_NULL_SP(writer->addEntry(TAG_GPSLATITUDE, 3, gpsData.mLatitude, + TIFF_IFD_GPSINFO), env, TAG_GPSLATITUDE, writer); + } + + { + BAIL_IF_INVALID_RET_NULL_SP(writer->addEntry(TAG_GPSLONGITUDE, 3, gpsData.mLongitude, + TIFF_IFD_GPSINFO), env, TAG_GPSLONGITUDE, writer); + } + + { + BAIL_IF_INVALID_RET_NULL_SP(writer->addEntry(TAG_GPSTIMESTAMP, 3, gpsData.mTimestamp, + TIFF_IFD_GPSINFO), env, TAG_GPSTIMESTAMP, writer); + } + + { + BAIL_IF_INVALID_RET_NULL_SP(writer->addEntry(TAG_GPSDATESTAMP, + GpsData::GPS_DATE_LENGTH, gpsData.mDate, TIFF_IFD_GPSINFO), env, + TAG_GPSDATESTAMP, writer); + } + } + + + if (hasThumbnail()) { + if (!writer->hasIfd(TIFF_IFD_SUB1)) { + if (writer->addSubIfd(TIFF_IFD_0, TIFF_IFD_SUB1) != OK) { + ALOGE("%s: Failed to add SubIFD %u to IFD %u", __FUNCTION__, TIFF_IFD_SUB1, + TIFF_IFD_0); +#if 0 + jniThrowException(env, "java/lang/IllegalStateException", "Failed to add SubIFD"); +#endif + return nullptr; + } + } + + std::vector tagsToMove; + tagsToMove.push_back(TAG_NEWSUBFILETYPE); + tagsToMove.push_back(TAG_ACTIVEAREA); + tagsToMove.push_back(TAG_BITSPERSAMPLE); + tagsToMove.push_back(TAG_COMPRESSION); + tagsToMove.push_back(TAG_IMAGEWIDTH); + tagsToMove.push_back(TAG_IMAGELENGTH); + tagsToMove.push_back(TAG_PHOTOMETRICINTERPRETATION); + tagsToMove.push_back(TAG_BLACKLEVEL); + tagsToMove.push_back(TAG_BLACKLEVELREPEATDIM); + tagsToMove.push_back(TAG_SAMPLESPERPIXEL); + tagsToMove.push_back(TAG_PLANARCONFIGURATION); + if (isBayer) { + tagsToMove.push_back(TAG_CFAREPEATPATTERNDIM); + tagsToMove.push_back(TAG_CFAPATTERN); + tagsToMove.push_back(TAG_CFAPLANECOLOR); + tagsToMove.push_back(TAG_CFALAYOUT); + } + tagsToMove.push_back(TAG_XRESOLUTION); + tagsToMove.push_back(TAG_YRESOLUTION); + tagsToMove.push_back(TAG_RESOLUTIONUNIT); + tagsToMove.push_back(TAG_WHITELEVEL); + tagsToMove.push_back(TAG_DEFAULTSCALE); + tagsToMove.push_back(TAG_DEFAULTCROPORIGIN); + tagsToMove.push_back(TAG_DEFAULTCROPSIZE); + + if (nullptr != writer->getEntry(TAG_OPCODELIST2, TIFF_IFD_0).get()) { + tagsToMove.push_back(TAG_OPCODELIST2); + } + + if (nullptr != writer->getEntry(TAG_OPCODELIST3, TIFF_IFD_0).get()) { + tagsToMove.push_back(TAG_OPCODELIST3); + } + + if (moveEntries(writer, TIFF_IFD_0, TIFF_IFD_SUB1, tagsToMove) != OK) { +#if 0 + jniThrowException(env, "java/lang/IllegalStateException", "Failed to move entries"); +#endif + return nullptr; + } + + // Setup thumbnail tags + + { + // Set photometric interpretation + uint16_t interpretation = 2; // RGB + BAIL_IF_INVALID_RET_NULL_SP(writer->addEntry(TAG_PHOTOMETRICINTERPRETATION, 1, + &interpretation, TIFF_IFD_0), env, TAG_PHOTOMETRICINTERPRETATION, writer); + } + + { + // Set planar configuration + uint16_t config = 1; // Chunky + BAIL_IF_INVALID_RET_NULL_SP(writer->addEntry(TAG_PLANARCONFIGURATION, 1, &config, + TIFF_IFD_0), env, TAG_PLANARCONFIGURATION, writer); + } + + { + // Set samples per pixel + uint16_t samples = SAMPLES_PER_RGB_PIXEL; + BAIL_IF_INVALID_RET_NULL_SP(writer->addEntry(TAG_SAMPLESPERPIXEL, 1, &samples, + TIFF_IFD_0), env, TAG_SAMPLESPERPIXEL, writer); + } + + { + // Set bits per sample + uint16_t bits[SAMPLES_PER_RGB_PIXEL]; + for (int i = 0; i < SAMPLES_PER_RGB_PIXEL; i++) bits[i] = BITS_PER_RGB_SAMPLE; + BAIL_IF_INVALID_RET_NULL_SP( + writer->addEntry(TAG_BITSPERSAMPLE, SAMPLES_PER_RGB_PIXEL, bits, TIFF_IFD_0), + env, TAG_BITSPERSAMPLE, writer); + } + + { + // Set subfiletype + uint32_t subfileType = 1; // Thumbnail image + BAIL_IF_INVALID_RET_NULL_SP(writer->addEntry(TAG_NEWSUBFILETYPE, 1, &subfileType, + TIFF_IFD_0), env, TAG_NEWSUBFILETYPE, writer); + } + + { + // Set compression + uint16_t compression = 1; // None + BAIL_IF_INVALID_RET_NULL_SP(writer->addEntry(TAG_COMPRESSION, 1, &compression, + TIFF_IFD_0), env, TAG_COMPRESSION, writer); + } + + { + // Set dimensions + uint32_t uWidth = getThumbnailWidth(); + uint32_t uHeight = getThumbnailHeight(); + BAIL_IF_INVALID_RET_NULL_SP(writer->addEntry(TAG_IMAGEWIDTH, 1, &uWidth, TIFF_IFD_0), + env, TAG_IMAGEWIDTH, writer); + BAIL_IF_INVALID_RET_NULL_SP(writer->addEntry(TAG_IMAGELENGTH, 1, &uHeight, TIFF_IFD_0), + env, TAG_IMAGELENGTH, writer); + } + + { + // x resolution + uint32_t xres[] = { 72, 1 }; // default 72 ppi + BAIL_IF_INVALID_RET_NULL_SP(writer->addEntry(TAG_XRESOLUTION, 1, xres, TIFF_IFD_0), + env, TAG_XRESOLUTION, writer); + + // y resolution + uint32_t yres[] = { 72, 1 }; // default 72 ppi + BAIL_IF_INVALID_RET_NULL_SP(writer->addEntry(TAG_YRESOLUTION, 1, yres, TIFF_IFD_0), + env, TAG_YRESOLUTION, writer); + + uint16_t unit = 2; // inches + BAIL_IF_INVALID_RET_NULL_SP(writer->addEntry(TAG_RESOLUTIONUNIT, 1, &unit, TIFF_IFD_0), + env, TAG_RESOLUTIONUNIT, writer); + } + } + + if (writer->addStrip(TIFF_IFD_0) != OK) { + ALOGE("%s: Could not setup thumbnail strip tags.", __FUNCTION__); +#if 0 + jniThrowException(env, "java/lang/IllegalStateException", + "Failed to setup thumbnail strip tags."); +#endif + return nullptr; + } + + if (writer->hasIfd(TIFF_IFD_SUB1)) { + if (writer->addStrip(TIFF_IFD_SUB1) != OK) { + ALOGE("%s: Could not main image strip tags.", __FUNCTION__); +#if 0 + jniThrowException(env, "java/lang/IllegalStateException", + "Failed to setup main image strip tags."); +#endif + return nullptr; + } + } + return writer; +} + +void DngCreator::setGpsTags(const std::vector& latTag, + const std::string& latRef, const std::vector& longTag, const std::string& longRef, const std::string& dateTag, const std::vector& timeTag) { + ALOGV("%s:", __FUNCTION__); + + GpsData data; + + size_t latLen = latTag.size(); + size_t longLen = longTag.size(); + size_t timeLen = timeTag.size(); + if (latLen != GpsData::GPS_VALUE_LENGTH) { +#if 0 + jniThrowException(env, "java/lang/IllegalArgumentException", + "invalid latitude tag length"); +#endif + return; + } else if (longLen != GpsData::GPS_VALUE_LENGTH) { +#if 0 + jniThrowException(env, "java/lang/IllegalArgumentException", + "invalid longitude tag length"); +#endif + return; + } else if (timeLen != GpsData::GPS_VALUE_LENGTH) { +#if 0 + jniThrowException(env, "java/lang/IllegalArgumentException", + "invalid time tag length"); +#endif + return; + } + + memcpy(&data.mLatitude, &latTag[0], sizeof(int) * GpsData::GPS_VALUE_LENGTH); + memcpy(&data.mLongitude, &longTag[0], sizeof(int) * GpsData::GPS_VALUE_LENGTH); + memcpy(&data.mTimestamp, &timeTag[0], sizeof(int) * GpsData::GPS_VALUE_LENGTH); + + memcpy(&data.mLatitudeRef, latRef.c_str(), 1); + data.mLatitudeRef[GpsData::GPS_REF_LENGTH - 1] = '\0'; + memcpy(&data.mLongitudeRef, longRef.c_str(), 1); + data.mLongitudeRef[GpsData::GPS_REF_LENGTH - 1] = '\0'; + memcpy(&data.mDate, dateTag.c_str(), GpsData::GPS_DATE_LENGTH - 1); + data.mDate[GpsData::GPS_DATE_LENGTH - 1] = '\0'; + + setGpsData(data); +} + +// TODO: Refactor out common preamble for the two nativeWrite methods. +void DngCreator::writeImage(std::vector& outStream, uint32_t uWidth, + uint32_t uHeight, const std::vector& inBuffer, int rowStride, int pixStride, uint64_t uOffset, bool isDirect) { + ALOGV("%s:", __FUNCTION__); + ALOGV("%s: nativeWriteImage called with: width=%d, height=%d, " + "rowStride=%d, pixStride=%d, offset=%" PRId64, __FUNCTION__, uWidth, + uHeight, rowStride, pixStride, uOffset); + uint32_t rStride = static_cast(rowStride); + uint32_t pStride = static_cast(pixStride); + + std::vector& out = outStream; + + // sp out = new JniOutputStream(env, outStream); + // if(env->ExceptionCheck()) { + // ALOGE("%s: Could not allocate buffers for output stream", __FUNCTION__); + // return; + // } + + sp writer = setup(uWidth, uHeight); + + if (writer.get() == nullptr) { + return; + } + + // Validate DNG size + if (!validateDngHeader(writer, getCharacteristics(), uWidth, uHeight)) { + return; + } + + // sp inBuf; + std::vector sources; + sp thumbnailSource; + uint32_t targetIfd = TIFF_IFD_0; + + bool hasThumbnail = writer->hasIfd(TIFF_IFD_SUB1); + + if (hasThumbnail) { +#if 0 + ALOGV("%s: Adding thumbnail strip sources.", __FUNCTION__); + uint32_t bytesPerPixel = SAMPLES_PER_RGB_PIXEL * BYTES_PER_RGB_SAMPLE; + uint32_t thumbWidth = getThumbnailWidth(); + thumbnailSource = new DirectStripSource(env, getThumbnail(), TIFF_IFD_0, + thumbWidth, context->getThumbnailHeight(), bytesPerPixel, + bytesPerPixel * thumbWidth, /*offset*/0, BYTES_PER_RGB_SAMPLE, + SAMPLES_PER_RGB_PIXEL); + sources.push_back(thumbnailSource.get()); + targetIfd = TIFF_IFD_SUB1; +#endif + } + + if (isDirect) { + size_t fullSize = rStride * uHeight; + jlong capacity = inBuffer.size(); + if (capacity < 0 || fullSize + uOffset > static_cast(capacity)) { +#if 0 + jniThrowExceptionFmt(env, "java/lang/IllegalStateException", + "Invalid size %d for Image, size given in metadata is %d at current stride", + capacity, fullSize); +#endif + return; + } + + uint8_t* pixelBytes = (uint8_t*)&inBuffer[0]; + + ALOGV("%s: Using direct-type strip source.", __FUNCTION__); + DirectStripSource stripSource(pixelBytes, targetIfd, uWidth, uHeight, pStride, + rStride, uOffset, BYTES_PER_SAMPLE, SAMPLES_PER_RAW_PIXEL); + sources.push_back(&stripSource); + + status_t ret = OK; + ByteVectorOutput byteVectorOutput(outStream); + + if ((ret = writer->write(&byteVectorOutput, &sources[0], sources.size())) != OK) { + ALOGE("%s: write failed with error %d.", __FUNCTION__, ret); +#if 0 + if (!env->ExceptionCheck()) { + jniThrowExceptionFmt(env, "java/io/IOException", + "Encountered error %d while writing file.", ret); + } +#endif + return; + } + } else { + int aa = 0; + // inBuf = new JniInputByteBuffer(env, inBuffer); +#if 0 + ALOGV("%s: Using input-type strip source.", __FUNCTION__); + InputStripSource stripSource(*inBuf, targetIfd, uWidth, uHeight, pStride, + rStride, uOffset, BYTES_PER_SAMPLE, SAMPLES_PER_RAW_PIXEL); + sources.push_back(&stripSource); + + status_t ret = OK; + if ((ret = writer->write(out.get(), &sources[0], sources.size())) != OK) { + ALOGE("%s: write failed with error %d.", __FUNCTION__, ret); +#if 0 + if (!env->ExceptionCheck()) { + jniThrowExceptionFmt(env, "java/io/IOException", + "Encountered error %d while writing file.", ret); + } +#endif + return; + } +#endif + } +} + +void DngCreator::writeInputStream(std::vector& outStream, + const std::vector& inStream, uint32_t uWidth, uint32_t uHeight, long offset) { + ALOGV("%s:", __FUNCTION__); + + uint32_t rowStride = uWidth * BYTES_PER_SAMPLE; + uint32_t pixStride = BYTES_PER_SAMPLE; + + uint64_t uOffset = static_cast(offset); + + ALOGV("%s: nativeWriteInputStream called with: width=%u, height=%u, " + "rowStride=%d, pixStride=%d, offset=%" PRId64, __FUNCTION__, uWidth, + uHeight, rowStride, pixStride, offset); + + ByteVectorOutput out(outStream); + // std::vector& out = outStream; + + sp writer = setup(uWidth, uHeight); + + if (writer.get() == nullptr) { + return; + } + + // Validate DNG size + if (!validateDngHeader(writer, getCharacteristics(), uWidth, uHeight)) { + return; + } + + sp thumbnailSource; + uint32_t targetIfd = TIFF_IFD_0; + bool hasThumbnail = writer->hasIfd(TIFF_IFD_SUB1); + std::vector sources; + + if (hasThumbnail) + { +#if 0 + ALOGV("%s: Adding thumbnail strip sources.", __FUNCTION__); + uint32_t bytesPerPixel = SAMPLES_PER_RGB_PIXEL * BYTES_PER_RGB_SAMPLE; + uint32_t width = getThumbnailWidth(); + thumbnailSource = new DirectStripSource(getThumbnail(), TIFF_IFD_0, + width, getThumbnailHeight(), bytesPerPixel, + bytesPerPixel * width, /*offset*/0, BYTES_PER_RGB_SAMPLE, + SAMPLES_PER_RGB_PIXEL); + sources.pus_back(thumbnailSource.get()); + targetIfd = TIFF_IFD_SUB1; +#endif + } + + // sp in = new JniInputStream(env, inStream); + + ByteVectorInput in(inStream); + + ALOGV("%s: Using input-type strip source.", __FUNCTION__); + InputStripSource stripSource(in, targetIfd, uWidth, uHeight, pixStride, + rowStride, uOffset, BYTES_PER_SAMPLE, SAMPLES_PER_RAW_PIXEL); + sources.push_back(&stripSource); + + status_t ret = OK; + if ((ret = writer->write(&out, &sources[0], sources.size())) != OK) { + ALOGE("%s: write failed with error %d.", __FUNCTION__, ret); +#if 0 + if (!env->ExceptionCheck()) { + jniThrowExceptionFmt(env, "java/io/IOException", + "Encountered error %d while writing file.", ret); + } +#endif + return; + } +} + +void DngCreator::writeInputBuffer(std::vector& outStream, + const uint8_t* inBuffer, size_t bufferLength, uint32_t uWidth, uint32_t uHeight, long offset) { + ALOGV("%s:", __FUNCTION__); + + uint32_t rowStride = uWidth * BYTES_PER_SAMPLE; + uint32_t pixStride = BYTES_PER_SAMPLE; + + uint64_t uOffset = static_cast(offset); + + ALOGV("%s: nativeWriteInputStream called with: width=%u, height=%u, " + "rowStride=%d, pixStride=%d, offset=%" PRId64, __FUNCTION__, uWidth, + uHeight, rowStride, pixStride, offset); + + ByteVectorOutput out(outStream); + // std::vector& out = outStream; + + sp writer = setup(uWidth, uHeight); + + if (writer.get() == nullptr) { + return; + } + + + // Validate DNG size + if (!validateDngHeader(writer, getCharacteristics(), uWidth, uHeight)) { + return; + } + + sp thumbnailSource; + uint32_t targetIfd = TIFF_IFD_0; + bool hasThumbnail = writer->hasIfd(TIFF_IFD_SUB1); + std::vector sources; + + if (hasThumbnail) + { +#if 0 + ALOGV("%s: Adding thumbnail strip sources.", __FUNCTION__); + uint32_t bytesPerPixel = SAMPLES_PER_RGB_PIXEL * BYTES_PER_RGB_SAMPLE; + uint32_t width = getThumbnailWidth(); + thumbnailSource = new DirectStripSource(getThumbnail(), TIFF_IFD_0, + width, getThumbnailHeight(), bytesPerPixel, + bytesPerPixel * width, /*offset*/0, BYTES_PER_RGB_SAMPLE, + SAMPLES_PER_RGB_PIXEL); + sources.push_back(thumbnailSource.get()); + targetIfd = TIFF_IFD_SUB1; +#endif + } + + // sp in = new JniInputStream(env, inStream); + + ByteBufferInput in(inBuffer, bufferLength); + + ALOGV("%s: Using input-type strip source.", __FUNCTION__); + InputStripSource stripSource(in, targetIfd, uWidth, uHeight, pixStride, + rowStride, uOffset, BYTES_PER_SAMPLE, SAMPLES_PER_RAW_PIXEL); + sources.push_back(&stripSource); + + status_t ret = OK; + if ((ret = writer->write(&out, &sources[0], sources.size())) != OK) { + ALOGE("%s: write failed with error %d.", __FUNCTION__, ret); +#if 0 + if (!env->ExceptionCheck()) { + jniThrowExceptionFmt(env, "java/io/IOException", + "Encountered error %d while writing file.", ret); + } +#endif + return; + } +} diff --git a/app/src/main/cpp/DngCreator.h b/app/src/main/cpp/DngCreator.h new file mode 100644 index 00000000..a19c6374 --- /dev/null +++ b/app/src/main/cpp/DngCreator.h @@ -0,0 +1,332 @@ +//#define LOG_NDEBUG 0 +#define LOG_TAG "DngCreator_JNI" +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// #include "core_jni_helpers.h" + +// #include "android_runtime/AndroidRuntime.h" +// #include "android_runtime/android_hardware_camera2_CameraMetadata.h" + +#include +// #include + +using namespace android; +using namespace img_utils; +// using android::base::GetProperty; + + +/** + * Max width or height dimension for thumbnails. + */ +// max pixel dimension for TIFF/EP +#define MAX_THUMBNAIL_DIMENSION 256 + + +// bytes per sample +#define DEFAULT_PIXEL_STRIDE 2 +// byts per pixel +#define BYTES_PER_RGB_PIX 3 + + +#define GPS_LAT_REF_NORTH "N" +#define GPS_LAT_REF_SOUTH "S" +#define GPS_LONG_REF_EAST "E" +#define GPS_LONG_REF_WEST "W" + +#define GPS_DATE_FORMAT_STR "yyyy:MM:dd" +#define TIFF_DATETIME_FORMAT "yyyy:MM:dd kk:mm:ss" + +class ByteVectorOutput : public Output { +public: + ByteVectorOutput(std::vector& buf); + virtual ~ByteVectorOutput(); + + virtual status_t open(); + + virtual status_t write(const uint8_t* buf, size_t offset, size_t count); + + virtual status_t close(); + +protected: + std::vector& m_buf; +}; + +class ByteVectorInput : public Input { +public: + ByteVectorInput(const std::vector& buf); + virtual ~ByteVectorInput(); + + /** + * Open this Input. + * + * Returns OK on success, or a negative error code. + */ + status_t open(); + + /** + * Read bytes into the given buffer. At most, the number of bytes given in the + * count argument will be read. Bytes will be written into the given buffer starting + * at the index given in the offset argument. + * + * Returns the number of bytes read, or NOT_ENOUGH_DATA if at the end of the file. If an + * error has occurred, this will return a negative error code other than NOT_ENOUGH_DATA. + */ + ssize_t read(uint8_t* buf, size_t offset, size_t count); + + /** + * Skips bytes in the input. + * + * Returns the number of bytes skipped, or NOT_ENOUGH_DATA if at the end of the file. If an + * error has occurred, this will return a negative error code other than NOT_ENOUGH_DATA. + */ + ssize_t skip(size_t count); + + /** + * Close the Input. It is not valid to call open on a previously closed Input. + * + * Returns OK on success, or a negative error code. + */ + status_t close(); + +protected: + const std::vector& m_buf; + size_t m_offset; +}; + +class ByteBufferInput : public Input { +public: + ByteBufferInput(const uint8_t* buf, size_t len); + virtual ~ByteBufferInput(); + + /** + * Open this Input. + * + * Returns OK on success, or a negative error code. + */ + status_t open(); + + /** + * Read bytes into the given buffer. At most, the number of bytes given in the + * count argument will be read. Bytes will be written into the given buffer starting + * at the index given in the offset argument. + * + * Returns the number of bytes read, or NOT_ENOUGH_DATA if at the end of the file. If an + * error has occurred, this will return a negative error code other than NOT_ENOUGH_DATA. + */ + ssize_t read(uint8_t* buf, size_t offset, size_t count); + + /** + * Skips bytes in the input. + * + * Returns the number of bytes skipped, or NOT_ENOUGH_DATA if at the end of the file. If an + * error has occurred, this will return a negative error code other than NOT_ENOUGH_DATA. + */ + ssize_t skip(size_t count); + + /** + * Close the Input. It is not valid to call open on a previously closed Input. + * + * Returns OK on success, or a negative error code. + */ + status_t close(); + +protected: + const uint8_t* m_buf; + size_t m_len; + size_t m_offset; +}; + +struct SIZE +{ + int width; + int height; +}; + +#define BAIL_IF_INVALID_RET_BOOL(expr, jnienv, tagId, writer) \ + if ((expr) != OK) { \ + return false; \ + } + +#define BAIL_IF_INVALID_RET_NULL_SP(expr, jnienv, tagId, writer) \ + if ((expr) != OK) { \ + return nullptr; \ + } + +#define BAIL_IF_INVALID_R(expr, jnienv, tagId, writer) \ + if ((expr) != OK) { \ + return -1; \ + } + +#define BAIL_IF_EMPTY_RET_NULL_SP(entry, jnienv, tagId, writer) \ + if ((entry).count == 0) { \ + return nullptr; \ + } + +#define BAIL_IF_EXPR_RET_NULL_SP(expr, jnienv, tagId, writer) \ + if (expr) { \ + return nullptr; \ + } + +#define ANDROID_DNGCREATOR_CTX_JNI_ID "mNativeContext" + +enum { + BITS_PER_SAMPLE = 16, + BYTES_PER_SAMPLE = 2, + BYTES_PER_RGB_PIXEL = 3, + BITS_PER_RGB_SAMPLE = 8, + BYTES_PER_RGB_SAMPLE = 1, + SAMPLES_PER_RGB_PIXEL = 3, + SAMPLES_PER_RAW_PIXEL = 1, + TIFF_IFD_0 = 0, + TIFF_IFD_SUB1 = 1, + TIFF_IFD_GPSINFO = 2, +}; + +/** + * POD container class for GPS tag data. + */ +class GpsData { +public: + enum { + GPS_VALUE_LENGTH = 6, + GPS_REF_LENGTH = 2, + GPS_DATE_LENGTH = 11, + }; + + uint32_t mLatitude[GPS_VALUE_LENGTH]; + uint32_t mLongitude[GPS_VALUE_LENGTH]; + uint32_t mTimestamp[GPS_VALUE_LENGTH]; + uint8_t mLatitudeRef[GPS_REF_LENGTH]; + uint8_t mLongitudeRef[GPS_REF_LENGTH]; + uint8_t mDate[GPS_DATE_LENGTH]; +}; + +// ---------------------------------------------------------------------------- + +/** + * Container class for the persistent native context. + */ + +class NativeContext : public LightRefBase { +public: + enum { + DATETIME_COUNT = 20, + }; + + NativeContext(ACameraMetadata* characteristics, ACameraMetadata* result); + virtual ~NativeContext(); + + TiffWriter* getWriter(); + + ACameraMetadata* getCharacteristics() const; + ACameraMetadata* getResult() const; + + uint32_t getThumbnailWidth() const; + uint32_t getThumbnailHeight() const; + const uint8_t* getThumbnail() const; + bool hasThumbnail() const; + + bool setThumbnail(const std::vector& buffer, uint32_t width, uint32_t height); + + void setOrientation(uint16_t orientation); + uint16_t getOrientation() const; + + void setDescription(const std::string& desc); + std::string getDescription() const; + bool hasDescription() const; + + void setGpsData(const GpsData& data); + GpsData getGpsData() const; + bool hasGpsData() const; + + void setCaptureTime(const std::string& formattedCaptureTime); + std::string getCaptureTime() const; + bool hasCaptureTime() const; + +protected: + std::vector mCurrentThumbnail; + TiffWriter mWriter; + ACameraMetadata* mCharacteristics; + ACameraMetadata* mResult; + uint32_t mThumbnailWidth; + uint32_t mThumbnailHeight; + uint16_t mOrientation; + bool mThumbnailSet; + bool mGpsSet; + bool mDescriptionSet; + bool mCaptureTimeSet; + std::string mDescription; + GpsData mGpsData; + std::string mFormattedCaptureTime; +}; + +class DngCreator : public NativeContext +{ + +public: + DngCreator(ACameraMetadata* characteristics, ACameraMetadata* result); + +#if 0 + void setLocation(Location location); +#endif + + void writeInputStream(std::vector& dngOutput, SIZE size, const std::vector& pixels, long offset); + void writeByteBuffer(std::vector& dngOutput, SIZE size, const std::vector& pixels, long offset); + +#if 0 + void writeImage(OutputStream& dngOutput, AImage& pixels); +#endif + + void close(); + + // private static final DateFormat sExifGPSDateStamp = new SimpleDateFormat(GPS_DATE_FORMAT_STR); + // private static final DateFormat sDateTimeStampFormat = new SimpleDateFormat(TIFF_DATETIME_FORMAT); +#if 0 + static { + sDateTimeStampFormat.setTimeZone(TimeZone.getDefault()); + sExifGPSDateStamp.setTimeZone(TimeZone.getTimeZone("UTC")); + } +#endif + + /** + * Offset, rowStride, and pixelStride are given in bytes. Height and width are given in pixels. + */ + void writeByteBuffer(int width, int height, const std::vector& pixels, std::vector& dngOutput, int pixelStride, int rowStride, long offset); + + /** + * Generate a direct RGB {@link ByteBuffer} from a {@link Bitmap}. + */ + + /** + * Convert coordinate to EXIF GPS tag format. + */ + void toExifLatLong(double value, int data[6]); + + void init(ACameraMetadata* characteristics, ACameraMetadata* result, const std::string& captureTime); + sp setup(uint32_t imageWidth, uint32_t imageHeight); + void destroy(); + void setGpsTags(const std::vector& latTag, const std::string& latRef, const std::vector& longTag, const std::string& longRef, const std::string& dateTag, const std::vector& timeTag); + void writeImage(std::vector& out, uint32_t width, uint32_t height, const std::vector& rawBuffer, int rowStride, int pixStride, uint64_t offset, bool isDirect); + + void writeInputStream(std::vector& out, const std::vector& rawStream, uint32_t width, uint32_t height, long offset); + + void writeInputBuffer(std::vector& out, const uint8_t* rawBuffer, size_t bufferLen, uint32_t width, uint32_t height, long offset); + +}; diff --git a/app/src/main/cpp/PhoneDevice.cpp b/app/src/main/cpp/PhoneDevice.cpp index 1055c96c..a7ffa30b 100644 --- a/app/src/main/cpp/PhoneDevice.cpp +++ b/app/src/main/cpp/PhoneDevice.cpp @@ -178,6 +178,11 @@ bool CPhoneDevice::CPhoneCamera::on_image(cv::Mat& rgb) return false; } +bool CPhoneDevice::CPhoneCamera::onBurstCapture(std::shared_ptr characteristics, const std::vector >& results, const std::vector >& frames) +{ + return true; +} + void CPhoneDevice::CPhoneCamera::on_error(const std::string& msg) { if (m_dev != NULL) @@ -199,6 +204,11 @@ CPhoneDevice::CJpegCamera::CJpegCamera(CPhoneDevice* dev, int32_t width, int32_t { } +bool CPhoneDevice::CJpegCamera::onBurstCapture(std::shared_ptr characteristics, const std::vector >& results, const std::vector >& frames) +{ + return true; +} + void CPhoneDevice::CJpegCamera::onImageAvailable(AImageReader* reader) { ALOGD("onImageAvailable %p", reader); diff --git a/app/src/main/cpp/PhoneDevice.h b/app/src/main/cpp/PhoneDevice.h index 7b993a83..609d2810 100644 --- a/app/src/main/cpp/PhoneDevice.h +++ b/app/src/main/cpp/PhoneDevice.h @@ -161,6 +161,7 @@ public: virtual bool on_image(cv::Mat& rgb); virtual void on_error(const std::string& msg); virtual void onDisconnected(ACameraDevice* device); + virtual bool onBurstCapture(std::shared_ptr characteristics, const std::vector >& results, const std::vector >& frames); protected: CPhoneDevice* m_dev; @@ -173,6 +174,7 @@ public: virtual void onImageAvailable(AImageReader* reader); virtual int32_t getOutputFormat() const; + virtual bool onBurstCapture(std::shared_ptr characteristics, const std::vector >& results, const std::vector >& frames); protected: std::string m_path; diff --git a/app/src/main/cpp/camera2/ndkcamera.cpp b/app/src/main/cpp/camera2/ndkcamera.cpp index 0e8797db..2eaec467 100644 --- a/app/src/main/cpp/camera2/ndkcamera.cpp +++ b/app/src/main/cpp/camera2/ndkcamera.cpp @@ -26,6 +26,7 @@ #include "Camera2Helper.h" #include #include +#include "DngCreator.h" static void onAvailabilityCallback(void* context, const char* cameraId) { @@ -279,6 +280,7 @@ int NdkCamera::open(const std::string& cameraId) { status = ACameraManager_getCameraCharacteristics(camera_manager, cameraId.c_str(), &camera_metadata); AASSERT(status == ACAMERA_OK, "ACameraManager_getCameraCharacteristics return error, %d", status); + mCharacteristics = std::shared_ptr(camera_metadata, ACameraMetadata_free); { ACameraMetadata_const_entry e = { 0 }; status = ACameraMetadata_getConstEntry(camera_metadata, ACAMERA_SCALER_AVAILABLE_STREAM_CONFIGURATIONS, &e); @@ -327,7 +329,7 @@ int NdkCamera::open(const std::string& cameraId) { if (!foundIt || foundRes.width() == 0 || foundRes.height() == 0) { - ACameraMetadata_free(camera_metadata); + // ACameraMetadata_free(camera_metadata); XYLOG(XYLOG_SEVERITY_ERROR, "Camera RES(%d, %d) Not Found on ID: %s", mWidth, mHeight, cameraId.c_str()); return 1; } @@ -543,7 +545,7 @@ int NdkCamera::open(const std::string& cameraId) { } } - ACameraMetadata_free(camera_metadata); + // ACameraMetadata_free(camera_metadata); } // open camera @@ -999,99 +1001,8 @@ void NdkCamera::onImageAvailable(AImageReader* reader) mFinalResult.duration = GetMicroTimeStamp() - m_startTime; - int32_t format; - AImage_getFormat(image, &format); + mCaptureFrames.push_back(std::shared_ptr(image, AImage_delete)); - if (format == AIMAGE_FORMAT_YUV_420_888) - { - int32_t width = 0; - int32_t height = 0; - AImage_getWidth(image, &width); - AImage_getHeight(image, &height); - - int32_t y_pixelStride = 0; - int32_t u_pixelStride = 0; - int32_t v_pixelStride = 0; - AImage_getPlanePixelStride(image, 0, &y_pixelStride); - AImage_getPlanePixelStride(image, 1, &u_pixelStride); - AImage_getPlanePixelStride(image, 2, &v_pixelStride); - - int32_t y_rowStride = 0; - int32_t u_rowStride = 0; - int32_t v_rowStride = 0; - AImage_getPlaneRowStride(image, 0, &y_rowStride); - AImage_getPlaneRowStride(image, 1, &u_rowStride); - AImage_getPlaneRowStride(image, 2, &v_rowStride); - - uint8_t* y_data = 0; - uint8_t* u_data = 0; - uint8_t* v_data = 0; - int y_len = 0; - int u_len = 0; - int v_len = 0; - AImage_getPlaneData(image, 0, &y_data, &y_len); - AImage_getPlaneData(image, 1, &u_data, &u_len); - AImage_getPlaneData(image, 2, &v_data, &v_len); - - if (u_data == v_data + 1 && v_data == y_data + width * height && y_pixelStride == 1 && u_pixelStride == 2 && v_pixelStride == 2 && y_rowStride == width && u_rowStride == width && v_rowStride == width) - { - // already nv21 :) - on_image((unsigned char*)y_data, (int)width, (int)height); - } - else - { - // construct nv21 - unsigned char* nv21 = new unsigned char[width * height + width * height / 2]; - { - // Y - unsigned char* yptr = nv21; - for (int y = 0; y < height; y++) - { - const unsigned char* y_data_ptr = y_data + y_rowStride * y; - for (int x = 0; x < width; x++) - { - yptr[0] = y_data_ptr[0]; - yptr++; - y_data_ptr += y_pixelStride; - } - } - - // UV - unsigned char* uvptr = nv21 + width * height; - for (int y = 0; y < height / 2; y++) - { - const unsigned char* v_data_ptr = v_data + v_rowStride * y; - const unsigned char* u_data_ptr = u_data + u_rowStride * y; - for (int x = 0; x < width / 2; x++) - { - uvptr[0] = v_data_ptr[0]; - uvptr[1] = u_data_ptr[0]; - uvptr += 2; - v_data_ptr += v_pixelStride; - u_data_ptr += u_pixelStride; - } - } - } - - on_image((unsigned char*)nv21, (int)width, (int)height); - - delete[] nv21; - } - } - else if (format == AIMAGE_FORMAT_JPEG) - { - uint32_t frameNumber = mFrameNumber.fetch_add(1); - std::string path = "/sdcard/com.xypower.mpapp/tmp/" + std::to_string(frameNumber) + ".jpg"; - writeJpegFile(image, path.c_str()); - } - else if (format == AIMAGE_FORMAT_RAW16) - { - uint32_t frameNumber = mFrameNumber.fetch_add(1); - std::string path = "/sdcard/com.xypower.mpapp/tmp/" + std::to_string(frameNumber) + ".dng"; - writeRawFile(image, path.c_str()); - } - - AImage_delete(image); } void NdkCamera::on_error(const std::string& msg) @@ -1108,6 +1019,11 @@ bool NdkCamera::on_image(cv::Mat& rgb) return false; } +bool NdkCamera::onBurstCapture(std::shared_ptr characteristics, const std::vector >& results, const std::vector >& frames) +{ + return false; +} + void NdkCamera::on_image(const unsigned char* nv21, int nv21_width, int nv21_height) { // ALOGW("nv21 size: %d x %d", nv21_width, nv21_height); @@ -1228,6 +1144,43 @@ void NdkCamera::on_image(const unsigned char* nv21, int nv21_width, int nv21_hei void NdkCamera::onSessionReady(ACameraCaptureSession *session) { + if (m_photoTaken) + { + AASSERT(mCaptureFrames.size() == mCaptureResults.size(), "Frame size %u doesn't equal to result size %u", + (uint32_t)mCaptureFrames.size(), (uint32_t)mCaptureResults.size()); +#ifndef NDEBUG + for (int idx = 0; idx < mCaptureFrames.size(); idx++) + { + std::shared_ptr spImage = mCaptureFrames[idx]; + + + int32_t format; + AImage_getFormat(spImage.get(), &format); + if (format == AIMAGE_FORMAT_YUV_420_888) + { + } + else + { + ALOGW("Capture Available TID=%lld", (long long)getThreadIdOfULL()); + uint32_t frameNumber = mFrameNumber.fetch_add(1); + std::string path = "/sdcard/com.xypower.mpapp/tmp/" + std::to_string(frameNumber); + if (format == AIMAGE_FORMAT_JPEG) + { + path += ".jpg"; + writeJpegFile(spImage.get(), path.c_str()); + } + else + { + path += ".dng"; + writeRawFile(spImage.get(), mCharacteristics.get(), mCaptureResults[idx].get(), path.c_str()); + + } + } + + } +#endif // NDEBUG + + } } void NdkCamera::onCaptureProgressed(ACameraCaptureSession* session, ACaptureRequest* request, const ACameraMetadata* result) @@ -1433,16 +1386,11 @@ void NdkCamera::onCaptureCompleted(ACameraCaptureSession* session, ACaptureReque } else { - for (int idx = 1; idx < mCaptureRequests.size(); idx++) - { - if (mCaptureRequests[idx]->request == request) - { - ACameraMetadata* pCopy = ACameraMetadata_copy(result); - CaptureResult captureResult = { pCopy, NULL, mCaptureRequests[idx]->sessionSequenceId }; - mCaptureResults.push_back(captureResult); - break; - } - } + uint64_t tid = getThreadIdOfULL(); + ALOGW("Capture Result sequenceId=%d TID=%lld", pCaptureRequest->sessionSequenceId, (long long)tid); + + ACameraMetadata* pCopy = ACameraMetadata_copy(result); + mCaptureResults.push_back(std::shared_ptr(pCopy, ACameraMetadata_free)); } } @@ -1632,8 +1580,16 @@ void NdkCamera::writeJpegFile(AImage *image, const char* path) } } -void NdkCamera::writeRawFile(AImage *image, const char* path) +void NdkCamera::writeRawFile(AImage *image, ACameraMetadata* characteristics, ACameraMetadata* result, const char* path) { + + + // dngCreator. + int32_t width; + int32_t height; + AImage_getWidth(image, &width); + AImage_getHeight(image, &height); + int planeCount; media_status_t status = AImage_getNumberOfPlanes(image, &planeCount); // ASSERT(status == AMEDIA_OK && planeCount == 1, @@ -1642,12 +1598,99 @@ void NdkCamera::writeRawFile(AImage *image, const char* path) int len = 0; AImage_getPlaneData(image, 0, &data, &len); + DngCreator dngCreator(characteristics, result); + + std::vector dngFile; + + // std::vector& out, const uint8_t* rawBuffer, size_t bufferLen, uint32_t width, uint32_t height, long offset); + dngCreator.writeInputBuffer(dngFile, data, len, width, height, 0); + + if (dngFile.empty()) + { + return; + } FILE *file = fopen(path, "wb"); if (file) { if (data && len) { - fwrite(data, 1, len, file); + fwrite(&dngFile[0], 1, dngFile.size(), file); } fclose(file); } +} + +bool NdkCamera::convertAImageToNv21(AImage* image, uint8_t** nv21, int32_t& width, int32_t& height) +{ + media_status_t status; + status = AImage_getWidth(image, &width); + status = AImage_getHeight(image, &height); + + int32_t y_pixelStride = 0; + int32_t u_pixelStride = 0; + int32_t v_pixelStride = 0; + AImage_getPlanePixelStride(image, 0, &y_pixelStride); + AImage_getPlanePixelStride(image, 1, &u_pixelStride); + AImage_getPlanePixelStride(image, 2, &v_pixelStride); + + int32_t y_rowStride = 0; + int32_t u_rowStride = 0; + int32_t v_rowStride = 0; + AImage_getPlaneRowStride(image, 0, &y_rowStride); + AImage_getPlaneRowStride(image, 1, &u_rowStride); + AImage_getPlaneRowStride(image, 2, &v_rowStride); + + uint8_t* y_data = 0; + uint8_t* u_data = 0; + uint8_t* v_data = 0; + int y_len = 0; + int u_len = 0; + int v_len = 0; + AImage_getPlaneData(image, 0, &y_data, &y_len); + AImage_getPlaneData(image, 1, &u_data, &u_len); + AImage_getPlaneData(image, 2, &v_data, &v_len); + + if (u_data == v_data + 1 && v_data == y_data + width * height && y_pixelStride == 1 && u_pixelStride == 2 && v_pixelStride == 2 && y_rowStride == width && u_rowStride == width && v_rowStride == width) + { + // already nv21 :) + // on_image((unsigned char*)y_data, (int)width, (int)height); + } + else + { + // construct nv21 + unsigned char* nv21 = new unsigned char[width * height + width * height / 2]; + { + // Y + unsigned char* yptr = nv21; + for (int y = 0; y < height; y++) + { + const unsigned char* y_data_ptr = y_data + y_rowStride * y; + for (int x = 0; x < width; x++) + { + yptr[0] = y_data_ptr[0]; + yptr++; + y_data_ptr += y_pixelStride; + } + } + + // UV + unsigned char* uvptr = nv21 + width * height; + for (int y = 0; y < height / 2; y++) + { + const unsigned char* v_data_ptr = v_data + v_rowStride * y; + const unsigned char* u_data_ptr = u_data + u_rowStride * y; + for (int x = 0; x < width / 2; x++) + { + uvptr[0] = v_data_ptr[0]; + uvptr[1] = u_data_ptr[0]; + uvptr += 2; + v_data_ptr += v_pixelStride; + u_data_ptr += u_pixelStride; + } + } + } + + // on_image((unsigned char*)nv21, (int)width, (int)height); + + delete[] nv21; + } } \ No newline at end of file diff --git a/app/src/main/cpp/camera2/ndkcamera.h b/app/src/main/cpp/camera2/ndkcamera.h index 30f29e7a..6b3b5560 100644 --- a/app/src/main/cpp/camera2/ndkcamera.h +++ b/app/src/main/cpp/camera2/ndkcamera.h @@ -144,7 +144,7 @@ public: int selfTest(const std::string& cameraId, int32_t& maxResolutionX, int32_t& maxResolutionY); void writeJpegFile(AImage *image, const char* path); - void writeRawFile(AImage *image, const char* path); + void writeRawFile(AImage *image, ACameraMetadata* characteristics, ACameraMetadata* result, const char* path); void onAvailabilityCallback(const char* cameraId); void onUnavailabilityCallback(const char* cameraId); @@ -160,6 +160,7 @@ public: virtual void on_error(const std::string& msg); virtual void on_image(const unsigned char* nv21, int nv21_width, int nv21_height); virtual void onDisconnected(ACameraDevice* device); + virtual bool onBurstCapture(std::shared_ptr characteristics, const std::vector >& results, const std::vector >& frames); void onCaptureProgressed(ACameraCaptureSession* session, ACaptureRequest* request, const ACameraMetadata* result); void onCaptureCompleted(ACameraCaptureSession* session, ACaptureRequest* request, const ACameraMetadata* result); @@ -174,6 +175,8 @@ public: bool IsCameraAvailable(const std::string& cameraId); + static bool convertAImageToNv21(AImage* image, uint8_t** nv21, int32_t& width, int32_t& height); + protected: std::mutex m_locker; std::set m_availableCameras; @@ -235,9 +238,11 @@ protected: ANativeWindow* mImageWindow; ACameraOutputTarget* mOutputTarget; + std::shared_ptr mCharacteristics; std::vector mCaptureRequests; - std::vector mCaptureResults; + std::vector > mCaptureResults; + std::vector > mCaptureFrames; ACameraCaptureSession* capture_session; diff --git a/app/src/main/cpp/img_utils/Android.bp b/app/src/main/cpp/img_utils/Android.bp new file mode 100644 index 00000000..64530e19 --- /dev/null +++ b/app/src/main/cpp/img_utils/Android.bp @@ -0,0 +1,60 @@ +// Copyright 2014 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +cc_library_shared { + name: "libimg_utils", + + srcs: [ + "src/EndianUtils.cpp", + "src/FileInput.cpp", + "src/FileOutput.cpp", + "src/SortedEntryVector.cpp", + "src/Input.cpp", + "src/Output.cpp", + "src/Orderable.cpp", + "src/TiffIfd.cpp", + "src/TiffWritable.cpp", + "src/TiffWriter.cpp", + "src/TiffEntry.cpp", + "src/TiffEntryImpl.cpp", + "src/ByteArrayOutput.cpp", + "src/DngUtils.cpp", + "src/StripSource.cpp", + ], + + shared_libs: [ + "liblog", + "libutils", + "libcutils", + ], + + cflags: [ + "-Wall", + "-Wextra", + "-Werror", + "-fvisibility=hidden", + ], + + product_variables: { + debuggable: { + // Enable assert() in eng builds + cflags: [ + "-UNDEBUG", + "-DLOG_NDEBUG=1", + ], + }, + }, + + export_include_dirs: ["include"], +} diff --git a/app/src/main/cpp/img_utils/include/img_utils/ByteArrayOutput.h b/app/src/main/cpp/img_utils/include/img_utils/ByteArrayOutput.h new file mode 100644 index 00000000..31ec24d5 --- /dev/null +++ b/app/src/main/cpp/img_utils/include/img_utils/ByteArrayOutput.h @@ -0,0 +1,83 @@ +/* + * Copyright 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef IMG_UTILS_BYTE_ARRAY_OUTPUT_H +#define IMG_UTILS_BYTE_ARRAY_OUTPUT_H + +#include + +#include +// #include + +#include +#include +#include + +namespace android { +namespace img_utils { + +/** + * Utility class that accumulates written bytes into a buffer. + */ +class ANDROID_API ByteArrayOutput : public Output { + public: + + ByteArrayOutput(); + + virtual ~ByteArrayOutput(); + + /** + * Open this ByteArrayOutput. + * + * Returns OK on success, or a negative error code. + */ + virtual status_t open(); + + /** + * Write bytes from the given buffer. The number of bytes given in the count + * argument will be written. Bytes will be written from the given buffer starting + * at the index given in the offset argument. + * + * Returns OK on success, or a negative error code. + */ + virtual status_t write(const uint8_t* buf, size_t offset, size_t count); + + /** + * Close this ByteArrayOutput. + * + * Returns OK on success, or a negative error code. + */ + virtual status_t close(); + + /** + * Get current size of the array of bytes written. + */ + virtual size_t getSize() const; + + /** + * Get pointer to array of bytes written. It is not valid to use this pointer if + * open, write, or close is called after this method. + */ + virtual const uint8_t* getArray() const; + + protected: + std::vector mByteArray; +}; + +} /*namespace img_utils*/ +} /*namespace android*/ + +#endif /*IMG_UTILS_BYTE_ARRAY_OUTPUT_H*/ diff --git a/app/src/main/cpp/img_utils/include/img_utils/DngUtils.h b/app/src/main/cpp/img_utils/include/img_utils/DngUtils.h new file mode 100644 index 00000000..8819f87b --- /dev/null +++ b/app/src/main/cpp/img_utils/include/img_utils/DngUtils.h @@ -0,0 +1,232 @@ +/* + * Copyright 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef IMG_UTILS_DNG_UTILS_H +#define IMG_UTILS_DNG_UTILS_H + +#include +#include + +#include +#include +#include + +#include +#include + +namespace android { +namespace img_utils { + +#define NELEMS(x) ((int) (sizeof(x) / sizeof((x)[0]))) +#define CLAMP(x, low, high) (((x) > (high)) ? (high) : (((x) < (low)) ? (low) : (x))) + +/** + * Utility class for building values for the OpcodeList tags specified + * in the Adobe DNG 1.4 spec. + */ +class ANDROID_API OpcodeListBuilder : public LightRefBase { + public: + // Note that the Adobe DNG 1.4 spec for Bayer phase (defined for the + // FixBadPixelsConstant and FixBadPixelsList opcodes) is incorrect. It's + // inconsistent with the DNG SDK (cf. dng_negative::SetBayerMosaic and + // dng_opcode_FixBadPixelsList::IsGreen), and Adobe confirms that the + // spec should be updated to match the SDK. + enum CfaLayout { + CFA_GRBG = 0, + CFA_RGGB, + CFA_BGGR, + CFA_GBRG, + CFA_NONE, + }; + + OpcodeListBuilder(); + virtual ~OpcodeListBuilder(); + + /** + * Get the total size of this opcode list in bytes. + */ + virtual size_t getSize() const; + + /** + * Get the number of opcodes defined in this list. + */ + virtual uint32_t getCount() const; + + /** + * Write the opcode list into the given buffer. This buffer + * must be able to hold at least as many elements as returned + * by calling the getSize() method. + * + * Returns OK on success, or a negative error code. + */ + virtual status_t buildOpList(/*out*/ uint8_t* buf) const; + + /** + * Add GainMap opcode(s) for the given metadata parameters. The given + * CFA layout must match the layout of the shading map passed into the + * lensShadingMap parameter. + * + * Returns OK on success, or a negative error code. + */ + virtual status_t addGainMapsForMetadata(uint32_t lsmWidth, + uint32_t lsmHeight, + uint32_t activeAreaTop, + uint32_t activeAreaLeft, + uint32_t activeAreaBottom, + uint32_t activeAreaRight, + CfaLayout cfa, + const float* lensShadingMap); + + /** + * Add a GainMap opcode with the given fields. The mapGains array + * must have mapPointsV * mapPointsH * mapPlanes elements. + * + * Returns OK on success, or a negative error code. + */ + virtual status_t addGainMap(uint32_t top, + uint32_t left, + uint32_t bottom, + uint32_t right, + uint32_t plane, + uint32_t planes, + uint32_t rowPitch, + uint32_t colPitch, + uint32_t mapPointsV, + uint32_t mapPointsH, + double mapSpacingV, + double mapSpacingH, + double mapOriginV, + double mapOriginH, + uint32_t mapPlanes, + const float* mapGains); + + /** + * Add WarpRectilinear opcode for the given metadata parameters. + * + * Returns OK on success, or a negative error code. + */ + virtual status_t addWarpRectilinearForMetadata(const float* kCoeffs, + uint32_t activeArrayWidth, + uint32_t activeArrayHeight, + float opticalCenterX, + float opticalCenterY); + + /** + * Add a WarpRectilinear opcode. + * + * numPlanes - Number of planes included in this opcode. + * opticalCenterX, opticalCenterY - Normalized x,y coordinates of the sensor optical + * center relative to the top,left pixel of the produced images (e.g. [0.5, 0.5] + * gives a sensor optical center in the image center. + * kCoeffs - A list of coefficients for the polynomial equation representing the distortion + * correction. For each plane, 6 coefficients must be included: + * {k_r0, k_r1, k_r2, k_r3, k_t0, k_t1}. See the DNG 1.4 specification for an + * outline of the polynomial used here. + * + * Returns OK on success, or a negative error code. + */ + virtual status_t addWarpRectilinear(uint32_t numPlanes, + double opticalCenterX, + double opticalCenterY, + const double* kCoeffs); + + + /** + * Add FixBadPixelsList opcode for the given metadata parameters. + * + * Returns OK on success, or a negative error code. + */ + virtual status_t addBadPixelListForMetadata(const uint32_t* hotPixels, + uint32_t xyPairCount, + uint32_t colorFilterArrangement); + + /** + * Add FixBadPixelsList opcode. + * + * bayerPhase - 0=top-left of image is red, 1=top-left of image is green pixel in red row, + * 2=top-left of image is green pixel in blue row, 3=top-left of image is + * blue. + * badPointCount - number of (x,y) pairs of bad pixels are given in badPointRowColPairs. + * badRectCount - number of (top, left, bottom, right) tuples are given in + * badRectTopLeftBottomRightTuples + * + * Returns OK on success, or a negative error code. + */ + virtual status_t addBadPixelList(uint32_t bayerPhase, + uint32_t badPointCount, + uint32_t badRectCount, + const uint32_t* badPointRowColPairs, + const uint32_t* badRectTopLeftBottomRightTuples); + + // TODO: Add other Opcode methods + protected: + static const uint32_t FLAG_OPTIONAL = 0x1u; + static const uint32_t FLAG_OPTIONAL_FOR_PREVIEW = 0x2u; + + // Opcode IDs + enum { + WARP_RECTILINEAR_ID = 1, + FIX_BAD_PIXELS_LIST = 5, + GAIN_MAP_ID = 9, + }; + + // LSM mosaic indices + enum { + LSM_R_IND = 0, + LSM_GE_IND = 1, + LSM_GO_IND = 2, + LSM_B_IND = 3, + }; + + uint32_t mCount; + ByteArrayOutput mOpList; + EndianOutput mEndianOut; + + status_t addOpcodePreamble(uint32_t opcodeId); + + private: + /** + * Add Bayer GainMap opcode(s) for the given metadata parameters. + * CFA layout must match the layout of the shading map passed into the + * lensShadingMap parameter. + * + * Returns OK on success, or a negative error code. + */ + status_t addBayerGainMapsForMetadata(uint32_t lsmWidth, + uint32_t lsmHeight, + uint32_t activeAreaWidth, + uint32_t activeAreaHeight, + CfaLayout cfa, + const float* lensShadingMap); + + /** + * Add Bayer GainMap opcode(s) for the given metadata parameters. + * CFA layout must match the layout of the shading map passed into the + * lensShadingMap parameter. + * + * Returns OK on success, or a negative error code. + */ + status_t addMonochromeGainMapsForMetadata(uint32_t lsmWidth, + uint32_t lsmHeight, + uint32_t activeAreaWidth, + uint32_t activeAreaHeight, + const float* lensShadingMap); +}; + +} /*namespace img_utils*/ +} /*namespace android*/ + +#endif /*IMG_UTILS_DNG_UTILS_H*/ diff --git a/app/src/main/cpp/img_utils/include/img_utils/EndianUtils.h b/app/src/main/cpp/img_utils/include/img_utils/EndianUtils.h new file mode 100644 index 00000000..bfa42e97 --- /dev/null +++ b/app/src/main/cpp/img_utils/include/img_utils/EndianUtils.h @@ -0,0 +1,250 @@ +/* + * Copyright 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef IMG_UTILS_ENDIAN_UTILS +#define IMG_UTILS_ENDIAN_UTILS + +#include + +#include +#include +#include +#include +#include + +namespace android { +namespace img_utils { + +/** + * Endianness types supported. + */ +enum ANDROID_API Endianness { + UNDEFINED_ENDIAN, // Default endianness will be used. + BIG, + LITTLE +}; + +/** + * Convert from the native device endianness to big endian. + */ +template +T convertToBigEndian(T in); + +/** + * Convert from the native device endianness to little endian. + */ +template +T convertToLittleEndian(T in); + +/** + * A utility class for writing to an Output with the given endianness. + */ +class ANDROID_API EndianOutput : public Output { + public: + /** + * Wrap the given Output. Calling write methods will result in + * writes to this output. + */ + explicit EndianOutput(Output* out, Endianness end=LITTLE); + + virtual ~EndianOutput(); + + /** + * Call open on the wrapped output. + */ + virtual status_t open(); + + /** + * Call close on the wrapped output. + */ + virtual status_t close(); + + /** + * Set the endianness to use when writing. + */ + virtual void setEndianness(Endianness end); + + /** + * Get the currently configured endianness. + */ + virtual Endianness getEndianness() const; + + /** + * Get the current number of bytes written by this EndianOutput. + */ + virtual uint32_t getCurrentOffset() const; + + + // TODO: switch write methods to uint32_t instead of size_t, + // the max size of a TIFF files is bounded + + /** + * The following methods will write elements from given input buffer to the output. + * Count elements in the buffer will be written with the endianness set for this + * EndianOutput. If the given offset is greater than zero, that many elements will + * be skipped in the buffer before writing. + * + * Returns OK on success, or a negative error code. + */ + virtual status_t write(const uint8_t* buf, size_t offset, size_t count); + + virtual status_t write(const int8_t* buf, size_t offset, size_t count); + + virtual status_t write(const uint16_t* buf, size_t offset, size_t count); + + virtual status_t write(const int16_t* buf, size_t offset, size_t count); + + virtual status_t write(const uint32_t* buf, size_t offset, size_t count); + + virtual status_t write(const int32_t* buf, size_t offset, size_t count); + + virtual status_t write(const uint64_t* buf, size_t offset, size_t count); + + virtual status_t write(const int64_t* buf, size_t offset, size_t count); + + virtual status_t write(const float* buf, size_t offset, size_t count); + + virtual status_t write(const double* buf, size_t offset, size_t count); + + protected: + template + inline status_t writeHelper(const T* buf, size_t offset, size_t count); + + uint32_t mOffset; + Output* mOutput; + Endianness mEndian; +}; + +template +inline status_t EndianOutput::writeHelper(const T* buf, size_t offset, size_t count) { + assert(offset <= count); + status_t res = OK; + size_t size = sizeof(T); + switch(mEndian) { + case BIG: { + for (size_t i = offset; i < count; ++i) { + T tmp = convertToBigEndian(buf[offset + i]); + if ((res = mOutput->write(reinterpret_cast(&tmp), 0, size)) + != OK) { + return res; + } + mOffset += size; + } + break; + } + case LITTLE: { + for (size_t i = offset; i < count; ++i) { + T tmp = convertToLittleEndian(buf[offset + i]); + if ((res = mOutput->write(reinterpret_cast(&tmp), 0, size)) + != OK) { + return res; + } + mOffset += size; + } + break; + } + default: { + return BAD_VALUE; + } + } + return res; +} + +template<> +inline uint8_t convertToBigEndian(uint8_t in) { + return in; +} + +template<> +inline int8_t convertToBigEndian(int8_t in) { + return in; +} + +template<> +inline uint16_t convertToBigEndian(uint16_t in) { + return htobe16(in); +} + +template<> +inline int16_t convertToBigEndian(int16_t in) { + return htobe16(in); +} + +template<> +inline uint32_t convertToBigEndian(uint32_t in) { + return htobe32(in); +} + +template<> +inline int32_t convertToBigEndian(int32_t in) { + return htobe32(in); +} + +template<> +inline uint64_t convertToBigEndian(uint64_t in) { + return htobe64(in); +} + +template<> +inline int64_t convertToBigEndian(int64_t in) { + return htobe64(in); +} + +template<> +inline uint8_t convertToLittleEndian(uint8_t in) { + return in; +} + +template<> +inline int8_t convertToLittleEndian(int8_t in) { + return in; +} + +template<> +inline uint16_t convertToLittleEndian(uint16_t in) { + return htole16(in); +} + +template<> +inline int16_t convertToLittleEndian(int16_t in) { + return htole16(in); +} + +template<> +inline uint32_t convertToLittleEndian(uint32_t in) { + return htole32(in); +} + +template<> +inline int32_t convertToLittleEndian(int32_t in) { + return htole32(in); +} + +template<> +inline uint64_t convertToLittleEndian(uint64_t in) { + return htole64(in); +} + +template<> +inline int64_t convertToLittleEndian(int64_t in) { + return htole64(in); +} + +} /*namespace img_utils*/ +} /*namespace android*/ + +#endif /*IMG_UTILS_ENDIAN_UTILS*/ + diff --git a/app/src/main/cpp/img_utils/include/img_utils/FileInput.h b/app/src/main/cpp/img_utils/include/img_utils/FileInput.h new file mode 100644 index 00000000..66afaff5 --- /dev/null +++ b/app/src/main/cpp/img_utils/include/img_utils/FileInput.h @@ -0,0 +1,76 @@ +/* + * Copyright 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef IMG_UTILS_FILE_INPUT_H +#define IMG_UTILS_FILE_INPUT_H + +#include + +#include +#include +#include +#include +#include + +namespace android { +namespace img_utils { + +/** + * Utility class for reading from a file. + */ +class ANDROID_API FileInput : public Input { + public: + /** + * Create a file input for the given path. + */ + explicit FileInput(String8 path); + + virtual ~FileInput(); + + /** + * Open a file descriptor to the path given in the constructor. + * + * Returns OK on success, or a negative error code. + */ + virtual status_t open(); + + /** + * Read bytes from the file into the given buffer. At most, the number + * of bytes given in the count argument will be read. Bytes will be written + * into the given buffer starting at the index given in the offset argument. + * + * Returns the number of bytes read, or NOT_ENOUGH_DATA if at the end of the file. If an + * error has occurred, this will return a negative error code other than NOT_ENOUGH_DATA. + */ + virtual ssize_t read(uint8_t* buf, size_t offset, size_t count); + + /** + * Close the file descriptor to the path given in the constructor. + * + * Returns OK on success, or a negative error code. + */ + virtual status_t close(); + private: + FILE *mFp; + String8 mPath; + bool mOpen; +}; + +} /*namespace img_utils*/ +} /*namespace android*/ + + +#endif /*IMG_UTILS_INPUT_H*/ diff --git a/app/src/main/cpp/img_utils/include/img_utils/FileOutput.h b/app/src/main/cpp/img_utils/include/img_utils/FileOutput.h new file mode 100644 index 00000000..3d4cf764 --- /dev/null +++ b/app/src/main/cpp/img_utils/include/img_utils/FileOutput.h @@ -0,0 +1,46 @@ +/* + * Copyright 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef IMG_UTILS_FILE_OUTPUT_H +#define IMG_UTILS_FILE_OUTPUT_H + +#include +#include +#include +#include +#include +#include + +namespace android { +namespace img_utils { + +class ANDROID_API FileOutput : public Output { + public: + explicit FileOutput(String8 path); + virtual ~FileOutput(); + virtual status_t open(); + virtual status_t write(const uint8_t* buf, size_t offset, size_t count); + virtual status_t close(); + private: + FILE *mFp; + String8 mPath; + bool mOpen; +}; + +} /*namespace img_utils*/ +} /*namespace android*/ + +#endif /*IMG_UTILS_FILE_OUTPUT_H*/ diff --git a/app/src/main/cpp/img_utils/include/img_utils/Input.h b/app/src/main/cpp/img_utils/include/img_utils/Input.h new file mode 100644 index 00000000..6a03647f --- /dev/null +++ b/app/src/main/cpp/img_utils/include/img_utils/Input.h @@ -0,0 +1,71 @@ +/* + * Copyright 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef IMG_UTILS_INPUT_H +#define IMG_UTILS_INPUT_H + +#include +#include +#include + +namespace android { +namespace img_utils { + +/** + * Utility class used as a source of bytes. + */ +class ANDROID_API Input { + public: + virtual ~Input(); + + /** + * Open this Input. + * + * Returns OK on success, or a negative error code. + */ + virtual status_t open(); + + /** + * Read bytes into the given buffer. At most, the number of bytes given in the + * count argument will be read. Bytes will be written into the given buffer starting + * at the index given in the offset argument. + * + * Returns the number of bytes read, or NOT_ENOUGH_DATA if at the end of the file. If an + * error has occurred, this will return a negative error code other than NOT_ENOUGH_DATA. + */ + virtual ssize_t read(uint8_t* buf, size_t offset, size_t count) = 0; + + /** + * Skips bytes in the input. + * + * Returns the number of bytes skipped, or NOT_ENOUGH_DATA if at the end of the file. If an + * error has occurred, this will return a negative error code other than NOT_ENOUGH_DATA. + */ + virtual ssize_t skip(size_t count); + + /** + * Close the Input. It is not valid to call open on a previously closed Input. + * + * Returns OK on success, or a negative error code. + */ + virtual status_t close(); +}; + +} /*namespace img_utils*/ +} /*namespace android*/ + + +#endif /*IMG_UTILS_INPUT_H*/ diff --git a/app/src/main/cpp/img_utils/include/img_utils/Orderable.h b/app/src/main/cpp/img_utils/include/img_utils/Orderable.h new file mode 100644 index 00000000..87253a4c --- /dev/null +++ b/app/src/main/cpp/img_utils/include/img_utils/Orderable.h @@ -0,0 +1,57 @@ +/* + * Copyright 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef IMG_UTILS_ORDERABLE +#define IMG_UTILS_ORDERABLE + +#include +#include + +namespace android { +namespace img_utils { + +#define COMPARE_DEF(op) \ +inline bool operator op (const Orderable& orderable) const; + +/** + * Subclasses of Orderable can be compared and sorted. This is + * intended to be used to create sorted arrays of TIFF entries + * and IFDs. + */ +class ANDROID_API Orderable { + public: + virtual ~Orderable(); + + /** + * Comparison operatotors are based on the value returned + * from this method. + */ + virtual uint32_t getComparableValue() const = 0; + + COMPARE_DEF(>) + COMPARE_DEF(<) + COMPARE_DEF(>=) + COMPARE_DEF(<=) + COMPARE_DEF(==) + COMPARE_DEF(!=) +}; + +#undef COMPARE_DEF + +} /*namespace img_utils*/ +} /*namespace android*/ + +#endif /*IMG_UTILS_ORDERABLE*/ diff --git a/app/src/main/cpp/img_utils/include/img_utils/Output.h b/app/src/main/cpp/img_utils/include/img_utils/Output.h new file mode 100644 index 00000000..35fae239 --- /dev/null +++ b/app/src/main/cpp/img_utils/include/img_utils/Output.h @@ -0,0 +1,61 @@ +/* + * Copyright 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef IMG_UTILS_OUTPUT_H +#define IMG_UTILS_OUTPUT_H + +#include +#include +#include + +namespace android { +namespace img_utils { + +/** + * Utility class used to output bytes. + */ +class ANDROID_API Output { + public: + virtual ~Output(); + + /** + * Open this Output. + * + * Returns OK on success, or a negative error code. + */ + virtual status_t open(); + + /** + * Write bytes from the given buffer. The number of bytes given in the count + * argument will be written. Bytes will be written from the given buffer starting + * at the index given in the offset argument. + * + * Returns OK on success, or a negative error code. + */ + virtual status_t write(const uint8_t* buf, size_t offset, size_t count) = 0; + + /** + * Close this Output. It is not valid to call open on a previously closed Output. + * + * Returns OK on success, or a negative error code. + */ + virtual status_t close(); +}; + +} /*namespace img_utils*/ +} /*namespace android*/ + +#endif /*IMG_UTILS_OUTPUT_H*/ diff --git a/app/src/main/cpp/img_utils/include/img_utils/Pair.h b/app/src/main/cpp/img_utils/include/img_utils/Pair.h new file mode 100644 index 00000000..d651cacc --- /dev/null +++ b/app/src/main/cpp/img_utils/include/img_utils/Pair.h @@ -0,0 +1,44 @@ +/* + * Copyright 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef IMG_UTILS_PAIR_H +#define IMG_UTILS_PAIR_H + +#include + +namespace android { +namespace img_utils { + +/** + * Generic pair utility class. Nothing special here. + */ +template +class ANDROID_API Pair { + public: + F first; + S second; + + Pair() {} + + Pair(const Pair& o) : first(o.first), second(o.second) {} + + Pair(const F& f, const S& s) : first(f), second(s) {} +}; + +} /*namespace img_utils*/ +} /*namespace android*/ + +#endif /*IMG_UTILS_PAIR_H*/ diff --git a/app/src/main/cpp/img_utils/include/img_utils/SortedEntryVector.h b/app/src/main/cpp/img_utils/include/img_utils/SortedEntryVector.h new file mode 100644 index 00000000..f059a82a --- /dev/null +++ b/app/src/main/cpp/img_utils/include/img_utils/SortedEntryVector.h @@ -0,0 +1,53 @@ +/* + * Copyright 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef IMG_UTILS_SORTED_ENTRY_VECTOR_H +#define IMG_UTILS_SORTED_ENTRY_VECTOR_H + +#include + +#include +#include + +namespace android { +namespace img_utils { + +/** + * Subclass of SortedVector that has been extended to + * do comparisons/lookups based on the tag ID of the entries. + */ +class SortedEntryVector : public SortedVector > { + public: + virtual ~SortedEntryVector(); + + /** + * Returns the index of the entry with the given tag ID, or + * -1 if none exists. + */ + ssize_t indexOfTag(uint16_t tag) const; + + protected: + /** + * Compare tag ID. + */ + virtual int do_compare(const void* lhs, const void* rhs) const; +}; + + +} /*namespace img_utils*/ +} /*namespace android*/ + +#endif /*IMG_UTILS_SORTED_ENTRY_VECTOR_H*/ diff --git a/app/src/main/cpp/img_utils/include/img_utils/StripSource.h b/app/src/main/cpp/img_utils/include/img_utils/StripSource.h new file mode 100644 index 00000000..b5c6b609 --- /dev/null +++ b/app/src/main/cpp/img_utils/include/img_utils/StripSource.h @@ -0,0 +1,53 @@ +/* + * Copyright 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef IMG_UTILS_STRIP_SOURCE_H +#define IMG_UTILS_STRIP_SOURCE_H + +#include + +#include +#include + +#include + +namespace android { +namespace img_utils { + +/** + * This class acts as a data source for strips set in a TiffIfd. + */ +class ANDROID_API StripSource { + public: + virtual ~StripSource(); + + /** + * Write count bytes to the stream. + * + * Returns OK on success, or a negative error code. + */ + virtual status_t writeToStream(Output& stream, uint32_t count) = 0; + + /** + * Return the source IFD. + */ + virtual uint32_t getIfd() const = 0; +}; + +} /*namespace img_utils*/ +} /*namespace android*/ + +#endif /*IMG_UTILS_STRIP_SOURCE_H*/ diff --git a/app/src/main/cpp/img_utils/include/img_utils/TagDefinitions.h b/app/src/main/cpp/img_utils/include/img_utils/TagDefinitions.h new file mode 100644 index 00000000..1cc98669 --- /dev/null +++ b/app/src/main/cpp/img_utils/include/img_utils/TagDefinitions.h @@ -0,0 +1,1404 @@ +/* + * Copyright 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef IMG_UTILS_TIFF_TAG_DEFINITION_H +#define IMG_UTILS_TIFF_TAG_DEFINITION_H + +#include +#include +#include +#include + +namespace android { +namespace img_utils { + +/** + * Tag definitions contain information about standard TIFF compatible tags. + */ +typedef struct TagDefinition { + // The tag name. + const char* tagName; + // The specified tag ID. + const uint16_t tagId; + // The default type for this tag. This must be a valid TIFF type. + const TagType defaultType; + // The default Image File Directory (IFD) for this tag. + const uint32_t defaultIfd; + // The valid count for this tag, or 0 if the count is not fixed. + const uint32_t fixedCount; + // The endianness of the tag value, or UNDEFINED_ENDIAN if there is no fixed endian + const Endianness fixedEndian; +} TagDefinition_t; + +/** + * Convenience defines for tag ids. + */ +enum { + TAG_RAWTOPREVIEWGAIN = 0xC7A8u, + TAG_NEWRAWIMAGEDIGEST = 0xC7A7u, + TAG_ORIGINALDEFAULTCROPSIZE = 0xC793u, + TAG_ORIGINALBESTQUALITYFINALSIZE = 0xC792u, + TAG_ORIGINALDEFAULTFINALSIZE = 0xC791u, + TAG_PROFILEHUESATMAPENCODING = 0xC7A3u, + TAG_PROFILELOOKTABLEENCODING = 0xC7A4u, + TAG_BASELINEEXPOSUREOFFSET = 0xC7A5u, + TAG_DEFAULTBLACKRENDER = 0xC7A6u, + TAG_DEFAULTUSERCROP = 0xC7B5u, + TAG_NOISEPROFILE = 0xC761u, + TAG_OPCODELIST3 = 0xC74Eu, + TAG_OPCODELIST2 = 0xC741u, + TAG_OPCODELIST1 = 0xC740u, + TAG_PROFILELOOKTABLEDATA = 0xC726u, + TAG_PROFILELOOKTABLEDIMS = 0xC725u, + TAG_ROWINTERLEAVEFACTOR = 0xC71Fu, + TAG_SUBTILEBLOCKSIZE = 0xC71Eu, + TAG_ORIGINALRAWFILEDIGEST = 0xC71Du, + TAG_RAWIMAGEDIGEST = 0xC71Cu, + TAG_PREVIEWDATETIME = 0xC71Bu, + TAG_PREVIEWCOLORSPACE = 0xC71Au, + TAG_PREVIEWSETTINGSDIGEST = 0xC719u, + TAG_PREVIEWSETTINGSNAME = 0xC718u, + TAG_PREVIEWAPPLICATIONVERSION = 0xC717u, + TAG_PREVIEWAPPLICATIONNAME = 0xC716u, + TAG_FORWARDMATRIX2 = 0xC715u, + TAG_FORWARDMATRIX1 = 0xC714u, + TAG_PROFILECOPYRIGHT = 0xC6FEu, + TAG_PROFILEEMBEDPOLICY = 0xC6FDu, + TAG_PROFILETONECURVE = 0xC6FCu, + TAG_PROFILEHUESATMAPDATA2 = 0xC6FBu, + TAG_PROFILEHUESATMAPDATA1 = 0xC6FAu, + TAG_PROFILEHUESATMAPDIMS = 0xC6F9u, + TAG_PROFILENAME = 0xC6F8u, + TAG_NOISEREDUCTIONAPPLIED = 0xC6F7u, + TAG_ASSHOTPROFILENAME = 0xC6F6u, + TAG_EXTRACAMERAPROFILES = 0xC6F5u, + TAG_PROFILECALIBRATIONSIGNATURE = 0xC6F4u, + TAG_CAMERACALIBRATIONSIGNATURE = 0xC6F3u, + TAG_COLORIMETRICREFERENCE = 0xC6BFu, + TAG_CURRENTPREPROFILEMATRIX = 0xC692u, + TAG_CURRENTICCPROFILE = 0xC691u, + TAG_ASSHOTPREPROFILEMATRIX = 0xC690u, + TAG_ASSHOTICCPROFILE = 0xC68Fu, + TAG_MASKEDAREAS = 0xC68Eu, + TAG_ACTIVEAREA = 0xC68Du, + TAG_ORIGINALRAWFILEDATA = 0xC68Cu, + TAG_ORIGINALRAWFILENAME = 0xC68Bu, + TAG_RAWDATAUNIQUEID = 0xC65Du, + TAG_MAKERNOTESAFETY = 0xC635u, + TAG_DNGPRIVATEDATA = 0xC634u, + TAG_SHADOWSCALE = 0xC633u, + TAG_ANTIALIASSTRENGTH = 0xC632u, + TAG_CHROMABLURRADIUS = 0xC631u, + TAG_LENSINFO = 0xC630u, + TAG_CAMERASERIALNUMBER = 0xC62Fu, + TAG_LINEARRESPONSELIMIT = 0xC62Eu, + TAG_BAYERGREENSPLIT = 0xC62Du, + TAG_BASELINESHARPNESS = 0xC62Cu, + TAG_BASELINENOISE = 0xC62Bu, + TAG_BASELINEEXPOSURE = 0xC62Au, + TAG_ASSHOTWHITEXY = 0xC629u, + TAG_ASSHOTNEUTRAL = 0xC628u, + TAG_ANALOGBALANCE = 0xC627u, + TAG_REDUCTIONMATRIX2 = 0xC626u, + TAG_REDUCTIONMATRIX1 = 0xC625u, + TAG_CAMERACALIBRATION2 = 0xC624u, + TAG_CAMERACALIBRATION1 = 0xC623u, + TAG_COLORMATRIX2 = 0xC622u, + TAG_COLORMATRIX1 = 0xC621u, + TAG_CALIBRATIONILLUMINANT2 = 0xC65Bu, + TAG_CALIBRATIONILLUMINANT1 = 0xC65Au, + TAG_DEFAULTCROPSIZE = 0xC620u, + TAG_DEFAULTCROPORIGIN = 0xC61Fu, + TAG_BESTQUALITYSCALE = 0xC65Cu, + TAG_DEFAULTSCALE = 0xC61Eu, + TAG_WHITELEVEL = 0xC61Du, + TAG_BLACKLEVELDELTAV = 0xC61Cu, + TAG_BLACKLEVELDELTAH = 0xC61Bu, + TAG_BLACKLEVEL = 0xC61Au, + TAG_BLACKLEVELREPEATDIM = 0xC619u, + TAG_LINEARIZATIONTABLE = 0xC618u, + TAG_CFALAYOUT = 0xC617u, + TAG_CFAPLANECOLOR = 0xC616u, + TAG_LOCALIZEDCAMERAMODEL = 0xC615u, + TAG_UNIQUECAMERAMODEL = 0xC614u, + TAG_DNGBACKWARDVERSION = 0xC613u, + TAG_DNGVERSION = 0xC612u, + TAG_SUBFILETYPE = 0x00FFu, + TAG_YRESOLUTION = 0x011Bu, + TAG_XRESOLUTION = 0x011Au, + TAG_THRESHHOLDING = 0x0107u, + TAG_STRIPOFFSETS = 0x0111u, + TAG_STRIPBYTECOUNTS = 0x0117u, + TAG_SOFTWARE = 0x0131u, + TAG_SAMPLESPERPIXEL = 0x0115u, + TAG_ROWSPERSTRIP = 0x0116u, + TAG_RESOLUTIONUNIT = 0x0128u, + TAG_PLANARCONFIGURATION = 0x011Cu, + TAG_PHOTOMETRICINTERPRETATION = 0x0106u, + TAG_ORIENTATION = 0x0112u, + TAG_NEWSUBFILETYPE = 0x00FEu, + TAG_MODEL = 0x0110u, + TAG_MINSAMPLEVALUE = 0x0118u, + TAG_MAXSAMPLEVALUE = 0x0119u, + TAG_MAKE = 0x010Fu, + TAG_IMAGEWIDTH = 0x0100u, + TAG_IMAGELENGTH = 0x0101u, + TAG_IMAGEDESCRIPTION = 0x010Eu, + TAG_HOSTCOMPUTER = 0x013Cu, + TAG_GRAYRESPONSEUNIT = 0x0122u, + TAG_GRAYRESPONSECURVE = 0x0123u, + TAG_FREEOFFSETS = 0x0120u, + TAG_FREEBYTECOUNTS = 0x0121u, + TAG_FILLORDER = 0x010Au, + TAG_EXTRASAMPLES = 0x0152u, + TAG_DATETIME = 0x0132u, + TAG_COPYRIGHT = 0x8298u, + TAG_COMPRESSION = 0x0103u, + TAG_COLORMAP = 0x0140u, + TAG_CELLWIDTH = 0x0108u, + TAG_CELLLENGTH = 0x0109u, + TAG_BITSPERSAMPLE = 0x0102u, + TAG_ARTIST = 0x013Bu, + TAG_EXIFVERSION = 0x9000u, + TAG_CFAREPEATPATTERNDIM = 0x828Du, + TAG_DATETIMEORIGINAL = 0x9003u, + TAG_CFAPATTERN = 0x828Eu, + TAG_SUBIFDS = 0x014Au, + TAG_TIFFEPSTANDARDID = 0x9216u, + TAG_EXPOSURETIME = 0x829Au, + TAG_ISOSPEEDRATINGS = 0x8827u, + TAG_FOCALLENGTH = 0x920Au, + TAG_FNUMBER = 0x829Du, + TAG_GPSINFO = 0x8825u, + TAG_GPSVERSIONID = 0x0u, + TAG_GPSLATITUDEREF = 0x1u, + TAG_GPSLATITUDE = 0x2u, + TAG_GPSLONGITUDEREF = 0x3u, + TAG_GPSLONGITUDE = 0x4u, + TAG_GPSTIMESTAMP = 0x7u, + TAG_GPSDATESTAMP = 0x001Du, +}; + +/** + * Convenience values for tags with enumerated values + */ + +enum { + TAG_ORIENTATION_NORMAL = 1, + TAG_ORIENTATION_ROTATE_180 = 3, + TAG_ORIENTATION_ROTATE_90 = 6, + TAG_ORIENTATION_ROTATE_270 = 8, + TAG_ORIENTATION_UNKNOWN = 9 +}; + +/** + * TIFF_EP_TAG_DEFINITIONS contains tags defined in the TIFF EP spec + */ +const TagDefinition_t TIFF_EP_TAG_DEFINITIONS[] = { + { // PhotometricInterpretation + "PhotometricInterpretation", + 0x0106u, + SHORT, + IFD_0, + 1, + UNDEFINED_ENDIAN + }, + { // SubIfds + "SubIfds", + 0x014Au, + LONG, + IFD_0, + 0, + UNDEFINED_ENDIAN + }, + { // CFAPattern + "CFAPattern", + 0x828Eu, + BYTE, + IFD_0, + 0, + UNDEFINED_ENDIAN + }, + { // CFARepeatPatternDim + "CFARepeatPatternDim", + 0x828Du, + SHORT, + IFD_0, + 2, + UNDEFINED_ENDIAN + }, + { // DateTimeOriginal + "DateTimeOriginal", + 0x9003u, + ASCII, + IFD_0, + 20, + UNDEFINED_ENDIAN + }, + { // Tiff/EPStandardID + "Tiff", + 0x9216u, + BYTE, + IFD_0, + 4, + UNDEFINED_ENDIAN + }, + { // ExposureTime + "ExposureTime", + 0x829Au, + RATIONAL, + IFD_0, + 0, + UNDEFINED_ENDIAN + }, + { // ISOSpeedRatings + "ISOSpeedRatings", + 0x8827u, + SHORT, + IFD_0, + 0, + UNDEFINED_ENDIAN + }, + { // FocalLength + "FocalLength", + 0x920Au, + RATIONAL, + IFD_0, + 0, + UNDEFINED_ENDIAN + }, + { // FNumber + "FNumber", + 0x829Du, + RATIONAL, + IFD_0, + 0, + UNDEFINED_ENDIAN + }, + { // GPSInfo + "GPSInfo", + 0x8825u, + LONG, + IFD_0, + 1, + UNDEFINED_ENDIAN + }, + { // GPSVersionID + "GPSVersionID", + 0x0u, + BYTE, + IFD_0, + 4, + UNDEFINED_ENDIAN + }, + { // GPSLatitudeRef + "GPSLatitudeRef", + 0x1u, + ASCII, + IFD_0, + 2, + UNDEFINED_ENDIAN + }, + { // GPSLatitude + "GPSLatitude", + 0x2u, + RATIONAL, + IFD_0, + 3, + UNDEFINED_ENDIAN + }, + { // GPSLongitudeRef + "GPSLongitudeRef", + 0x3u, + ASCII, + IFD_0, + 2, + UNDEFINED_ENDIAN + }, + { // GPSLongitude + "GPSLongitude", + 0x4u, + RATIONAL, + IFD_0, + 3, + UNDEFINED_ENDIAN + }, + { // GPSTimeStamp + "GPSTimeStamp", + 0x7u, + RATIONAL, + IFD_0, + 3, + UNDEFINED_ENDIAN + }, + /*TODO: Remaining TIFF EP tags*/ +}; + +/** + * EXIF_2_3_TAG_DEFINITIONS contains tags defined in the Jeita EXIF 2.3 spec + */ +const TagDefinition_t EXIF_2_3_TAG_DEFINITIONS[] = { + { // ExifVersion + "ExifVersion", + 0x9000u, + UNDEFINED, + IFD_0, + 4, + UNDEFINED_ENDIAN + }, + { // GPSDateStamp + "GPSDateStamp", + 0x001Du, + ASCII, + IFD_0, + 11, + UNDEFINED_ENDIAN + }, + /*TODO: Remaining EXIF 2.3 tags*/ +}; + +/** + * TIFF_6_TAG_DEFINITIONS contains tags defined in the TIFF 6.0 spec + */ +const TagDefinition_t TIFF_6_TAG_DEFINITIONS[] = { + { // SubFileType + "SubFileType", + 0x00FFu, + SHORT, + IFD_0, + 1, + UNDEFINED_ENDIAN + }, + { // Artist + "Artist", + 0x013Bu, + ASCII, + IFD_0, + 0, + UNDEFINED_ENDIAN + }, + { // BitsPerSample + "BitsPerSample", + 0x0102u, + SHORT, + IFD_0, + 0, + UNDEFINED_ENDIAN + }, + { // CellLength + "CellLength", + 0x0109u, + SHORT, + IFD_0, + 1, + UNDEFINED_ENDIAN + }, + { // CellWidth + "CellWidth", + 0x0108u, + SHORT, + IFD_0, + 1, + UNDEFINED_ENDIAN + }, + { // ColorMap + "ColorMap", + 0x0140u, + SHORT, + IFD_0, + 0, + UNDEFINED_ENDIAN + }, + { // Compression + "Compression", + 0x0103u, + SHORT, + IFD_0, + 1, + UNDEFINED_ENDIAN + }, + { // Copyright + "Copyright", + 0x8298u, + ASCII, + IFD_0, + 0, + UNDEFINED_ENDIAN + }, + { // DateTime + "DateTime", + 0x0132u, + ASCII, + IFD_0, + 20, + UNDEFINED_ENDIAN + }, + { // ExtraSamples + "ExtraSamples", + 0x0152u, + SHORT, + IFD_0, + 0, + UNDEFINED_ENDIAN + }, + { // FillOrder + "FillOrder", + 0x010Au, + SHORT, + IFD_0, + 1, + UNDEFINED_ENDIAN + }, + { // FreeByteCounts + "FreeByteCounts", + 0x0121u, + LONG, + IFD_0, + 1, + UNDEFINED_ENDIAN + }, + { // FreeOffsets + "FreeOffsets", + 0x0120u, + LONG, + IFD_0, + 1, + UNDEFINED_ENDIAN + }, + { // GrayResponseCurve + "GrayResponseCurve", + 0x0123u, + SHORT, + IFD_0, + 0, + UNDEFINED_ENDIAN + }, + { // GrayResponseUnit + "GrayResponseUnit", + 0x0122u, + SHORT, + IFD_0, + 1, + UNDEFINED_ENDIAN + }, + { // HostComputer + "HostComputer", + 0x013Cu, + ASCII, + IFD_0, + 0, + UNDEFINED_ENDIAN + }, + { // ImageDescription + "ImageDescription", + 0x010Eu, + ASCII, + IFD_0, + 0, + UNDEFINED_ENDIAN + }, + { // ImageLength + "ImageLength", + 0x0101u, + LONG, + IFD_0, + 1, + UNDEFINED_ENDIAN + }, + { // ImageWidth + "ImageWidth", + 0x0100u, + LONG, + IFD_0, + 1, + UNDEFINED_ENDIAN + }, + { // Make + "Make", + 0x010Fu, + ASCII, + IFD_0, + 0, + UNDEFINED_ENDIAN + }, + { // MaxSampleValue + "MaxSampleValue", + 0x0119u, + SHORT, + IFD_0, + 0, + UNDEFINED_ENDIAN + }, + { // MinSampleValue + "MinSampleValue", + 0x0118u, + SHORT, + IFD_0, + 0, + UNDEFINED_ENDIAN + }, + { // Model + "Model", + 0x0110u, + ASCII, + IFD_0, + 0, + UNDEFINED_ENDIAN + }, + { // NewSubfileType + "NewSubfileType", + 0x00FEu, + LONG, + IFD_0, + 1, + UNDEFINED_ENDIAN + }, + { // Orientation + "Orientation", + 0x0112u, + SHORT, + IFD_0, + 1, + UNDEFINED_ENDIAN + }, + { // PhotoMetricInterpretation + "PhotoMetricInterpretation", + 0x0106u, + SHORT, + IFD_0, + 1, + UNDEFINED_ENDIAN + }, + { // PlanarConfiguration + "PlanarConfiguration", + 0x011Cu, + SHORT, + IFD_0, + 1, + UNDEFINED_ENDIAN + }, + { // ResolutionUnit + "ResolutionUnit", + 0x0128u, + SHORT, + IFD_0, + 1, + UNDEFINED_ENDIAN + }, + { // RowsPerStrip + "RowsPerStrip", + 0x0116u, + LONG, + IFD_0, + 1, + UNDEFINED_ENDIAN + }, + { // SamplesPerPixel + "SamplesPerPixel", + 0x0115u, + SHORT, + IFD_0, + 1, + UNDEFINED_ENDIAN + }, + { // Software + "Software", + 0x0131u, + ASCII, + IFD_0, + 0, + UNDEFINED_ENDIAN + }, + { // StripByteCounts + "StripByteCounts", + 0x0117u, + LONG, + IFD_0, + 0, + UNDEFINED_ENDIAN + }, + { // StripOffsets + "StripOffsets", + 0x0111u, + LONG, + IFD_0, + 0, + UNDEFINED_ENDIAN + }, + { // SubfileType + "SubfileType", + 0x00FFu, + SHORT, + IFD_0, + 1, + UNDEFINED_ENDIAN + }, + { // Threshholding + "Threshholding", + 0x0107u, + SHORT, + IFD_0, + 1, + UNDEFINED_ENDIAN + }, + { // XResolution + "XResolution", + 0x011Au, + RATIONAL, + IFD_0, + 1, + UNDEFINED_ENDIAN + }, + { // YResolution + "YResolution", + 0x011Bu, + RATIONAL, + IFD_0, + 1, + UNDEFINED_ENDIAN + }, +}; + +/** + * DNG_TAG_DEFINITIONS contains tags defined in the DNG 1.4 spec + */ +const TagDefinition_t DNG_TAG_DEFINITIONS[] = { + { // DNGVersion + "DNGVersion", + 0xC612u, + BYTE, + IFD_0, + 4, + UNDEFINED_ENDIAN + }, + { // DNGBackwardVersion + "DNGBackwardVersion", + 0xC613u, + BYTE, + IFD_0, + 4, + UNDEFINED_ENDIAN + }, + { // UniqueCameraModel + "UniqueCameraModel", + 0xC614u, + ASCII, + IFD_0, + 0, + UNDEFINED_ENDIAN + }, + { // LocalizedCameraModel + "LocalizedCameraModel", + 0xC615u, + ASCII, + IFD_0, + 0, + UNDEFINED_ENDIAN + }, + { // CFAPlaneColor + "CFAPlaneColor", + 0xC616u, + BYTE, + RAW_IFD, + 0, + UNDEFINED_ENDIAN + }, + { // CFALayout + "CFALayout", + 0xC617u, + SHORT, + RAW_IFD, + 1, + UNDEFINED_ENDIAN + }, + { // LinearizationTable + "LinearizationTable", + 0xC618u, + SHORT, + RAW_IFD, + 0, + UNDEFINED_ENDIAN + }, + { // BlackLevelRepeatDim + "BlackLevelRepeatDim", + 0xC619u, + SHORT, + RAW_IFD, + 2, + UNDEFINED_ENDIAN + }, + { // BlackLevel + "BlackLevel", + 0xC61Au, + RATIONAL, + RAW_IFD, + 0, + UNDEFINED_ENDIAN + }, + { // BlackLevelDeltaH + "BlackLevelDeltaH", + 0xC61Bu, + SRATIONAL, + RAW_IFD, + 0, + UNDEFINED_ENDIAN + }, + { // BlackLevelDeltaV + "BlackLevelDeltaV", + 0xC61Cu, + SRATIONAL, + RAW_IFD, + 0, + UNDEFINED_ENDIAN + }, + { // WhiteLevel + "WhiteLevel", + 0xC61Du, + LONG, + RAW_IFD, + 0, + UNDEFINED_ENDIAN + }, + { // DefaultScale + "DefaultScale", + 0xC61Eu, + RATIONAL, + RAW_IFD, + 2, + UNDEFINED_ENDIAN + }, + { // BestQualityScale + "BestQualityScale", + 0xC65Cu, + RATIONAL, + RAW_IFD, + 1, + UNDEFINED_ENDIAN + }, + { // DefaultCropOrigin + "DefaultCropOrigin", + 0xC61Fu, + LONG, + RAW_IFD, + 2, + UNDEFINED_ENDIAN + }, + { // DefaultCropSize + "DefaultCropSize", + 0xC620u, + LONG, + RAW_IFD, + 2, + UNDEFINED_ENDIAN + }, + { // CalibrationIlluminant1 + "CalibrationIlluminant1", + 0xC65Au, + SHORT, + PROFILE_IFD, + 1, + UNDEFINED_ENDIAN + }, + { // CalibrationIlluminant2 + "CalibrationIlluminant2", + 0xC65Bu, + SHORT, + PROFILE_IFD, + 1, + UNDEFINED_ENDIAN + }, + { // ColorMatrix1 + "ColorMatrix1", + 0xC621u, + SRATIONAL, + PROFILE_IFD, + 0, + UNDEFINED_ENDIAN + }, + { // ColorMatrix2 + "ColorMatrix2", + 0xC622u, + SRATIONAL, + PROFILE_IFD, + 0, + UNDEFINED_ENDIAN + }, + { // CameraCalibration1 + "CameraCalibration1", + 0xC623u, + SRATIONAL, + IFD_0, + 0, + UNDEFINED_ENDIAN + }, + { // CameraCalibration2 + "CameraCalibration2", + 0xC624u, + SRATIONAL, + IFD_0, + 0, + UNDEFINED_ENDIAN + }, + { // ReductionMatrix1 + "ReductionMatrix1", + 0xC625u, + SRATIONAL, + PROFILE_IFD, + 0, + UNDEFINED_ENDIAN + }, + { // ReductionMatrix2 + "ReductionMatrix2", + 0xC626u, + SRATIONAL, + PROFILE_IFD, + 0, + UNDEFINED_ENDIAN + }, + { // AnalogBalance + "AnalogBalance", + 0xC627u, + RATIONAL, + IFD_0, + 0, + UNDEFINED_ENDIAN + }, + { // AsShotNeutral + "AsShotNeutral", + 0xC628u, + RATIONAL, + IFD_0, + 0, + UNDEFINED_ENDIAN + }, + { // AsShotWhiteXY + "AsShotWhiteXY", + 0xC629u, + RATIONAL, + IFD_0, + 2, + UNDEFINED_ENDIAN + }, + { // BaselineExposure + "BaselineExposure", + 0xC62Au, + SRATIONAL, + IFD_0, + 1, + UNDEFINED_ENDIAN + }, + { // BaselineNoise + "BaselineNoise", + 0xC62Bu, + RATIONAL, + IFD_0, + 1, + UNDEFINED_ENDIAN + }, + { // BaselineSharpness + "BaselineSharpness", + 0xC62Cu, + RATIONAL, + IFD_0, + 1, + UNDEFINED_ENDIAN + }, + { // BayerGreenSplit + "BayerGreenSplit", + 0xC62Du, + LONG, + RAW_IFD, + 1, + UNDEFINED_ENDIAN + }, + { // LinearResponseLimit + "LinearResponseLimit", + 0xC62Eu, + RATIONAL, + IFD_0, + 1, + UNDEFINED_ENDIAN + }, + { // CameraSerialNumber + "CameraSerialNumber", + 0xC62Fu, + ASCII, + IFD_0, + 0, + UNDEFINED_ENDIAN + }, + { // LensInfo + "LensInfo", + 0xC630u, + RATIONAL, + IFD_0, + 4, + UNDEFINED_ENDIAN + }, + { // ChromaBlurRadius + "ChromaBlurRadius", + 0xC631u, + RATIONAL, + RAW_IFD, + 1, + UNDEFINED_ENDIAN + }, + { // AntiAliasStrength + "AntiAliasStrength", + 0xC632u, + RATIONAL, + RAW_IFD, + 1, + UNDEFINED_ENDIAN + }, + { // ShadowScale + "ShadowScale", + 0xC633u, + RATIONAL, + IFD_0, + 1, + UNDEFINED_ENDIAN + }, + { // DNGPrivateData + "DNGPrivateData", + 0xC634u, + BYTE, + IFD_0, + 0, + UNDEFINED_ENDIAN + }, + { // MakerNoteSafety + "MakerNoteSafety", + 0xC635u, + SHORT, + IFD_0, + 1, + UNDEFINED_ENDIAN + }, + { // RawDataUniqueID + "RawDataUniqueID", + 0xC65Du, + BYTE, + IFD_0, + 16, + UNDEFINED_ENDIAN + }, + { // OriginalRawFileName + "OriginalRawFileName", + 0xC68Bu, + ASCII, + IFD_0, + 0, + UNDEFINED_ENDIAN + }, + { // OriginalRawFileData + "OriginalRawFileData", + 0xC68Cu, + UNDEFINED, + IFD_0, + 0, + BIG + }, + { // ActiveArea + "ActiveArea", + 0xC68Du, + LONG, + RAW_IFD, + 4, + UNDEFINED_ENDIAN + }, + { // MaskedAreas + "MaskedAreas", + 0xC68Eu, + LONG, + RAW_IFD, + 0, + UNDEFINED_ENDIAN + }, + { // AsShotICCProfile + "AsShotICCProfile", + 0xC68Fu, + UNDEFINED, + IFD_0, + 0, + UNDEFINED_ENDIAN + }, + { // AsShotPreProfileMatrix + "AsShotPreProfileMatrix", + 0xC690u, + SRATIONAL, + IFD_0, + 0, + UNDEFINED_ENDIAN + }, + { // CurrentICCProfile + "CurrentICCProfile", + 0xC691u, + UNDEFINED, + IFD_0, + 0, + UNDEFINED_ENDIAN + }, + { // CurrentICCProfile + "CurrentICCProfile", + 0xC691u, + UNDEFINED, + IFD_0, + 0, + UNDEFINED_ENDIAN + }, + { // CurrentPreProfileMatrix + "CurrentPreProfileMatrix", + 0xC692u, + SRATIONAL, + IFD_0, + 0, + UNDEFINED_ENDIAN + }, + { // ColorimetricReference + "ColorimetricReference", + 0xC6BFu, + SHORT, + IFD_0, + 1, + UNDEFINED_ENDIAN + }, + { // CameraCalibrationSignature + "CameraCalibrationSignature", + 0xC6F3u, + ASCII, + IFD_0, + 0, + UNDEFINED_ENDIAN + }, + { // ProfileCalibrationSignature + "ProfileCalibrationSignature", + 0xC6F4u, + ASCII, + PROFILE_IFD, + 0, + UNDEFINED_ENDIAN + }, + { // ExtraCameraProfiles + "ExtraCameraProfiles", + 0xC6F5u, + LONG, + IFD_0, + 0, + UNDEFINED_ENDIAN + }, + { // AsShotProfileName + "AsShotProfileName", + 0xC6F6u, + ASCII, + IFD_0, + 0, + UNDEFINED_ENDIAN + }, + { // NoiseReductionApplied + "NoiseReductionApplied", + 0xC6F7u, + RATIONAL, + RAW_IFD, + 1, + UNDEFINED_ENDIAN + }, + { // ProfileName + "ProfileName", + 0xC6F8u, + ASCII, + PROFILE_IFD, + 0, + UNDEFINED_ENDIAN + }, + { // ProfileHueSatMapDims + "ProfileHueSatMapDims", + 0xC6F9u, + LONG, + PROFILE_IFD, + 3, + UNDEFINED_ENDIAN + }, + { // ProfileHueSatMapData1 + "ProfileHueSatMapData1", + 0xC6FAu, + FLOAT, + PROFILE_IFD, + 0, + UNDEFINED_ENDIAN + }, + { // ProfileHueSatMapData2 + "ProfileHueSatMapData2", + 0xC6FBu, + FLOAT, + PROFILE_IFD, + 0, + UNDEFINED_ENDIAN + }, + { // ProfileToneCurve + "ProfileToneCurve", + 0xC6FCu, + FLOAT, + PROFILE_IFD, + 0, + UNDEFINED_ENDIAN + }, + { // ProfileEmbedPolicy + "ProfileEmbedPolicy", + 0xC6FDu, + LONG, + PROFILE_IFD, + 1, + UNDEFINED_ENDIAN + }, + { // ProfileCopyright + "ProfileCopyright", + 0xC6FEu, + ASCII, + PROFILE_IFD, + 0, + UNDEFINED_ENDIAN + }, + { // ForwardMatrix1 + "ForwardMatrix1", + 0xC714u, + SRATIONAL, + PROFILE_IFD, + 0, + UNDEFINED_ENDIAN + }, + { // ForwardMatrix2 + "ForwardMatrix2", + 0xC715u, + SRATIONAL, + PROFILE_IFD, + 0, + UNDEFINED_ENDIAN + }, + { // PreviewApplicationName + "PreviewApplicationName", + 0xC716u, + ASCII, + PREVIEW_IFD, + 0, + UNDEFINED_ENDIAN + }, + { // PreviewApplicationVersion + "PreviewApplicationVersion", + 0xC717u, + ASCII, + PREVIEW_IFD, + 0, + UNDEFINED_ENDIAN + }, + { // PreviewSettingsName + "PreviewSettingsName", + 0xC718u, + ASCII, + PREVIEW_IFD, + 0, + UNDEFINED_ENDIAN + }, + { // PreviewSettingsDigest + "PreviewSettingsDigest", + 0xC719u, + BYTE, + PREVIEW_IFD, + 16, + UNDEFINED_ENDIAN + }, + { // PreviewColorSpace + "PreviewColorSpace", + 0xC71Au, + LONG, + PREVIEW_IFD, + 1, + UNDEFINED_ENDIAN + }, + { // PreviewDateTime + "PreviewDateTime", + 0xC71Bu, + ASCII, + PREVIEW_IFD, + 0, + UNDEFINED_ENDIAN + }, + { // RawImageDigest + "RawImageDigest", + 0xC71Cu, + BYTE, + IFD_0, + 16, + UNDEFINED_ENDIAN + }, + { // OriginalRawFileDigest + "OriginalRawFileDigest", + 0xC71Du, + BYTE, + IFD_0, + 16, + UNDEFINED_ENDIAN + }, + { // SubTileBlockSize + "SubTileBlockSize", + 0xC71Eu, + LONG, + RAW_IFD, + 2, + UNDEFINED_ENDIAN + }, + { // RowInterleaveFactor + "RowInterleaveFactor", + 0xC71Fu, + LONG, + RAW_IFD, + 1, + UNDEFINED_ENDIAN + }, + { // ProfileLookTableDims + "ProfileLookTableDims", + 0xC725u, + LONG, + PROFILE_IFD, + 3, + UNDEFINED_ENDIAN + }, + { // ProfileLookTableData + "ProfileLookTableData", + 0xC726u, + FLOAT, + PROFILE_IFD, + 0, + UNDEFINED_ENDIAN + }, + { // OpcodeList1 + "OpcodeList1", + 0xC740u, + UNDEFINED, + RAW_IFD, + 0, + BIG + }, + { // OpcodeList2 + "OpcodeList2", + 0xC741u, + UNDEFINED, + RAW_IFD, + 0, + BIG + }, + { // OpcodeList3 + "OpcodeList3", + 0xC74Eu, + UNDEFINED, + RAW_IFD, + 0, + BIG + }, + { // NoiseProfile + "NoiseProfile", + 0xC761u, + DOUBLE, + RAW_IFD, + 0, + UNDEFINED_ENDIAN + }, + { // DefaultUserCrop + "DefaultUserCrop", + 0xC7B5u, + RATIONAL, + RAW_IFD, + 4, + UNDEFINED_ENDIAN + }, + { // DefaultBlackRender + "DefaultBlackRender", + 0xC7A6u, + LONG, + PROFILE_IFD, + 1, + UNDEFINED_ENDIAN + }, + { // BaselineExposureOffset + "BaselineExposureOffset", + 0xC7A5u, + RATIONAL, + PROFILE_IFD, + 1, + UNDEFINED_ENDIAN + }, + { // ProfileLookTableEncoding + "ProfileLookTableEncoding", + 0xC7A4u, + LONG, + PROFILE_IFD, + 1, + UNDEFINED_ENDIAN + }, + { // ProfileHueSatMapEncoding + "ProfileHueSatMapEncoding", + 0xC7A3u, + LONG, + PROFILE_IFD, + 1, + UNDEFINED_ENDIAN + }, + { // OriginalDefaultFinalSize + "OriginalDefaultFinalSize", + 0xC791u, + LONG, + IFD_0, + 2, + UNDEFINED_ENDIAN + }, + { // OriginalBestQualityFinalSize + "OriginalBestQualityFinalSize", + 0xC792u, + LONG, + IFD_0, + 2, + UNDEFINED_ENDIAN + }, + { // OriginalDefaultCropSize + "OriginalDefaultCropSize", + 0xC793u, + LONG, + IFD_0, + 2, + UNDEFINED_ENDIAN + }, + { // NewRawImageDigest + "NewRawImageDigest", + 0xC7A7u, + BYTE, + IFD_0, + 16, + UNDEFINED_ENDIAN + }, + { // RawToPreviewGain + "RawToPreviewGain", + 0xC7A8u, + DOUBLE, + PREVIEW_IFD, + 1, + UNDEFINED_ENDIAN + }, +}; + +} /*namespace img_utils*/ +} /*namespace android*/ + +#endif /*IMG_UTILS_TIFF_TAG_DEFINITION_H*/ diff --git a/app/src/main/cpp/img_utils/include/img_utils/TiffEntry.h b/app/src/main/cpp/img_utils/include/img_utils/TiffEntry.h new file mode 100644 index 00000000..09f86a55 --- /dev/null +++ b/app/src/main/cpp/img_utils/include/img_utils/TiffEntry.h @@ -0,0 +1,130 @@ +/* + * Copyright 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef IMG_UTILS_TIFF_ENTRY +#define IMG_UTILS_TIFF_ENTRY + +#include +#include +#include + +#include +// #include +#include +#include + +namespace android { +namespace img_utils { + +#define COMPARE_DEF(op) \ +inline bool operator op (const TiffEntry& entry) const; + +/** + * This class holds a single TIFF IFD entry. + * + * Subclasses are expected to support assignment and copying operations. + */ +class ANDROID_API TiffEntry : public TiffWritable { + public: + virtual ~TiffEntry(); + + /** + * Write the 12-byte IFD entry to the output. The given offset will be + * set as the tag value if the size of the tag value exceeds the max + * size for the TIFF Value field (4 bytes), and should be word aligned. + * + * Returns OK on success, or a negative error code on failure. + */ + virtual status_t writeTagInfo(uint32_t offset, /*out*/EndianOutput* out) const = 0; + + /** + * Get the count set for this entry. This corresponds to the TIFF Count + * field. + */ + virtual uint32_t getCount() const = 0; + + /** + * Get the tag id set for this entry. This corresponds to the TIFF Tag + * field. + */ + virtual uint16_t getTag() const = 0; + + /** + * Get the type set for this entry. This corresponds to the TIFF Type + * field. + */ + virtual TagType getType() const = 0; + + /** + * Get the defined endianness for this entry. If this is defined, + * the tag value will be written with the given byte order. + */ + virtual Endianness getEndianness() const = 0; + + /** + * Get the value for this entry. This corresponds to the TIFF Value + * field. + * + * Returns NULL if the value is NULL, or if the type used does not + * match the type of this tag. + */ + template + const T* getData() const; + + virtual std::string toString() const; + + /** + * Force the type used here to be a valid TIFF type. + * + * Returns NULL if the given value is NULL, or if the type given does + * not match the type of the value given. + */ + template + static const T* forceValidType(TagType type, const T* value); + + virtual const void* getDataHelper() const = 0; + + COMPARE_DEF(>) + COMPARE_DEF(<) + + protected: + enum { + MAX_PRINT_STRING_LENGTH = 256 + }; +}; + +#define COMPARE(op) \ +bool TiffEntry::operator op (const TiffEntry& entry) const { \ + return getComparableValue() op entry.getComparableValue(); \ +} + +COMPARE(>) +COMPARE(<) + + +template +const T* TiffEntry::getData() const { + const T* value = reinterpret_cast(getDataHelper()); + return forceValidType(getType(), value); +} + +#undef COMPARE +#undef COMPARE_DEF + +} /*namespace img_utils*/ +} /*namespace android*/ + +#endif /*IMG_UTILS_TIFF_ENTRY*/ diff --git a/app/src/main/cpp/img_utils/include/img_utils/TiffEntryImpl.h b/app/src/main/cpp/img_utils/include/img_utils/TiffEntryImpl.h new file mode 100644 index 00000000..ffdd3274 --- /dev/null +++ b/app/src/main/cpp/img_utils/include/img_utils/TiffEntryImpl.h @@ -0,0 +1,219 @@ +/* + * Copyright 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef IMG_UTILS_TIFF_ENTRY_IMPL +#define IMG_UTILS_TIFF_ENTRY_IMPL + +#include +#include +#include +#include +#include + +#include +#include +// #include +#include +#include +#include + +namespace android { +namespace img_utils { + +template +class TiffEntryImpl : public TiffEntry { + public: + TiffEntryImpl(uint16_t tag, TagType type, uint32_t count, Endianness end, const T* data); + virtual ~TiffEntryImpl(); + + status_t writeData(uint32_t offset, /*out*/EndianOutput* out) const; + status_t writeTagInfo(uint32_t offset, /*out*/EndianOutput* out) const; + + uint32_t getCount() const; + uint16_t getTag() const; + TagType getType() const; + Endianness getEndianness() const; + size_t getSize() const; + uint32_t getComparableValue() const; + + protected: + const void* getDataHelper() const; + uint32_t getActualSize() const; + + uint16_t mTag; + uint16_t mType; + uint32_t mCount; + Endianness mEnd; + std::vector mData; + +}; + +template +TiffEntryImpl::TiffEntryImpl(uint16_t tag, TagType type, uint32_t count, Endianness end, + const T* data) + : mTag(tag), mType(static_cast(type)), mCount(count), mEnd(end) { + count = (type == RATIONAL || type == SRATIONAL) ? count * 2 : count; + auto it = mData.insert(mData.end(), data, data + count); + // LOG_ALWAYS_FATAL_IF(index < 0, "%s: Could not allocate vector for data.", __FUNCTION__); +} + +template +TiffEntryImpl::~TiffEntryImpl() {} + +template +uint32_t TiffEntryImpl::getCount() const { + return mCount; +} + +template +uint16_t TiffEntryImpl::getTag() const { + return mTag; +} + +template +TagType TiffEntryImpl::getType() const { + return static_cast(mType); +} + +template +const void* TiffEntryImpl::getDataHelper() const { + return reinterpret_cast(&mData[0]); +} + +template +size_t TiffEntryImpl::getSize() const { + uint32_t total = getActualSize(); + WORD_ALIGN(total) + return (total <= OFFSET_SIZE) ? 0 : total; +} + +template +uint32_t TiffEntryImpl::getActualSize() const { + uint32_t total = sizeof(T) * mCount; + if (getType() == RATIONAL || getType() == SRATIONAL) { + // 2 ints stored for each rational, multiply by 2 + total <<= 1; + } + return total; +} + +template +Endianness TiffEntryImpl::getEndianness() const { + return mEnd; +} + +template +uint32_t TiffEntryImpl::getComparableValue() const { + return mTag; +} + +template +status_t TiffEntryImpl::writeTagInfo(uint32_t offset, /*out*/EndianOutput* out) const { + assert((offset % TIFF_WORD_SIZE) == 0); + status_t ret = OK; + BAIL_ON_FAIL(out->write(&mTag, 0, 1), ret); + BAIL_ON_FAIL(out->write(&mType, 0, 1), ret); + BAIL_ON_FAIL(out->write(&mCount, 0, 1), ret); + + uint32_t dataSize = getActualSize(); + if (dataSize > OFFSET_SIZE) { + BAIL_ON_FAIL(out->write(&offset, 0, 1), ret); + } else { + uint32_t count = mCount; + if (getType() == RATIONAL || getType() == SRATIONAL) { + /** + * Rationals are stored as an array of ints. Each + * rational is represented by 2 ints. To recover the + * size of the array here, multiply the count by 2. + */ + count <<= 1; + } + BAIL_ON_FAIL(out->write(&mData[0], 0, count), ret); + ZERO_TILL_WORD(out, dataSize, ret); + } + return ret; +} + +template +status_t TiffEntryImpl::writeData(uint32_t /*offset*/, EndianOutput* out) const { + status_t ret = OK; + + // Some tags have fixed-endian value output + Endianness tmp = UNDEFINED_ENDIAN; + if (mEnd != UNDEFINED_ENDIAN) { + tmp = out->getEndianness(); + out->setEndianness(mEnd); + } + + uint32_t count = mCount; + if (getType() == RATIONAL || getType() == SRATIONAL) { + /** + * Rationals are stored as an array of ints. Each + * rational is represented by 2 ints. To recover the + * size of the array here, multiply the count by 2. + */ + count <<= 1; + } + + BAIL_ON_FAIL(out->write(&mData[0], 0, count), ret); + + if (mEnd != UNDEFINED_ENDIAN) { + out->setEndianness(tmp); + } + + // Write to next word alignment + ZERO_TILL_WORD(out, sizeof(T) * count, ret); + return ret; +} + +template<> +inline status_t TiffEntryImpl >::writeTagInfo(uint32_t offset, + /*out*/EndianOutput* out) const { + assert((offset % TIFF_WORD_SIZE) == 0); + status_t ret = OK; + BAIL_ON_FAIL(out->write(&mTag, 0, 1), ret); + BAIL_ON_FAIL(out->write(&mType, 0, 1), ret); + BAIL_ON_FAIL(out->write(&mCount, 0, 1), ret); + + BAIL_ON_FAIL(out->write(&offset, 0, 1), ret); + return ret; +} + +template<> +inline uint32_t TiffEntryImpl >::getActualSize() const { + uint32_t total = 0; + for (size_t i = 0; i < mData.size(); ++i) { + total += mData[i]->getSize(); + } + return total; +} + +template<> +inline status_t TiffEntryImpl >::writeData(uint32_t offset, EndianOutput* out) const { + status_t ret = OK; + for (uint32_t i = 0; i < mCount; ++i) { + BAIL_ON_FAIL(mData[i]->writeData(offset, out), ret); + offset += mData[i]->getSize(); + } + return ret; +} + +} /*namespace img_utils*/ +} /*namespace android*/ + +#endif /*IMG_UTILS_TIFF_ENTRY_IMPL*/ + + diff --git a/app/src/main/cpp/img_utils/include/img_utils/TiffHelpers.h b/app/src/main/cpp/img_utils/include/img_utils/TiffHelpers.h new file mode 100644 index 00000000..3e5f8630 --- /dev/null +++ b/app/src/main/cpp/img_utils/include/img_utils/TiffHelpers.h @@ -0,0 +1,132 @@ +/* + * Copyright 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef IMG_UTILS_TIFF_HELPERS_H +#define IMG_UTILS_TIFF_HELPERS_H + +#include + +namespace android { +namespace img_utils { + +const uint8_t ZERO_WORD[] = {0, 0, 0, 0}; + +#define BAIL_ON_FAIL(x, flag) \ + if (((flag) = (x)) != OK) return flag; + +#define BYTES_TILL_WORD(index) \ + ((TIFF_WORD_SIZE - ((index) % TIFF_WORD_SIZE)) % TIFF_WORD_SIZE) + +#define WORD_ALIGN(count) \ + count += BYTES_TILL_WORD(count); + +#define ZERO_TILL_WORD(output, index, ret) \ + { \ + size_t remaining = BYTES_TILL_WORD(index); \ + if (remaining > 0) { \ + BAIL_ON_FAIL((output)->write(ZERO_WORD, 0, remaining), ret); \ + } \ + } + +/** + * Basic TIFF header constants. + */ +enum { + BAD_OFFSET = 0, + TIFF_WORD_SIZE = 4, // Size in bytes + IFD_HEADER_SIZE = 2, // Size in bytes + IFD_FOOTER_SIZE = 4, // Size in bytes + TIFF_ENTRY_SIZE = 12, // Size in bytes + MAX_IFD_ENTRIES = UINT16_MAX, + FILE_HEADER_SIZE = 8, // Size in bytes + ENDIAN_MARKER_SIZE = 2, // Size in bytes + TIFF_MARKER_SIZE = 2, // Size in bytes + OFFSET_MARKER_SIZE = 4, // Size in bytes + TIFF_FILE_MARKER = 42, + BIG_ENDIAN_MARKER = 0x4D4Du, + LITTLE_ENDIAN_MARKER = 0x4949u +}; + +/** + * Constants for the TIFF tag types. + */ +enum TagType { + UNKNOWN_TAGTYPE = 0, + BYTE=1, + ASCII, + SHORT, + LONG, + RATIONAL, + SBYTE, + UNDEFINED, + SSHORT, + SLONG, + SRATIONAL, + FLOAT, + DOUBLE +}; + +/** + * Sizes of the TIFF entry fields (in bytes). + */ +enum { + TAG_SIZE = 2, + TYPE_SIZE = 2, + COUNT_SIZE = 4, + OFFSET_SIZE = 4 +}; + +/** + * Convenience IFD id constants. + */ +enum { + IFD_0 = 0, + RAW_IFD, + PROFILE_IFD, + PREVIEW_IFD +}; + +inline size_t getTypeSize(TagType type) { + switch(type) { + case UNDEFINED: + case ASCII: + case BYTE: + case SBYTE: + return 1; + case SHORT: + case SSHORT: + return 2; + case LONG: + case SLONG: + case FLOAT: + return 4; + case RATIONAL: + case SRATIONAL: + case DOUBLE: + return 8; + default: + return 0; + } +} + +inline uint32_t calculateIfdSize(size_t numberOfEntries) { + return IFD_HEADER_SIZE + IFD_FOOTER_SIZE + TIFF_ENTRY_SIZE * numberOfEntries; +} + +} /*namespace img_utils*/ +} /*namespace android*/ + +#endif /*IMG_UTILS_TIFF_HELPERS_H*/ diff --git a/app/src/main/cpp/img_utils/include/img_utils/TiffIfd.h b/app/src/main/cpp/img_utils/include/img_utils/TiffIfd.h new file mode 100644 index 00000000..0021e8ce --- /dev/null +++ b/app/src/main/cpp/img_utils/include/img_utils/TiffIfd.h @@ -0,0 +1,164 @@ +/* + * Copyright 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef IMG_UTILS_TIFF_IFD_H +#define IMG_UTILS_TIFF_IFD_H + +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace android { +namespace img_utils { + +/** + * This class holds a single TIFF Image File Directory (IFD) structure. + * + * This maps to the TIFF IFD structure that is logically composed of: + * - A 2-byte field listing the number of entries. + * - A list of 12-byte TIFF entries. + * - A 4-byte offset to the next IFD. + */ +class ANDROID_API TiffIfd : public TiffWritable { + public: + explicit TiffIfd(uint32_t ifdId); + virtual ~TiffIfd(); + + /** + * Add a TiffEntry to this IFD or replace an existing entry with the + * same tag ID. No validation is done. + * + * Returns OK on success, or a negative error code on failure. + */ + virtual status_t addEntry(const sp& entry); + + /** + * Set the pointer to the next IFD. This is used to create a linked + * list of IFDs as defined by the TIFF 6.0 spec., and is not included + * when calculating the size of IFD and entries for the getSize() + * method (unlike SubIFDs). + */ + virtual void setNextIfd(const sp& ifd); + + /** + * Get the pointer to the next IFD, or NULL if none exists. + */ + virtual sp getNextIfd() const; + + /** + * Write the IFD data. This includes the IFD header, entries, footer, + * and the corresponding values for each entry (recursively including + * sub-IFDs). The written amount should end on a word boundary, and + * the given offset should be word aligned. + * + * Returns OK on success, or a negative error code on failure. + */ + virtual status_t writeData(uint32_t offset, /*out*/EndianOutput* out) const; + + /** + * Get the size of the IFD. This includes the IFD header, entries, footer, + * and the corresponding values for each entry (recursively including + * any sub-IFDs). + */ + virtual size_t getSize() const; + + /** + * Get the id of this IFD. + */ + virtual uint32_t getId() const; + + /** + * Get an entry with the given tag ID. + * + * Returns a strong pointer to the entry if it exists, or an empty strong + * pointer. + */ + virtual sp getEntry(uint16_t tag) const; + + /** + * Remove the entry with the given tag ID if it exists. + */ + virtual void removeEntry(uint16_t tag); + + /** + * Convenience method to validate and set strip-related image tags. + * + * This sets all strip related tags, but leaves offset values unitialized. + * setStripOffsets must be called with the desired offset before writing. + * The strip tag values are calculated from the existing tags for image + * dimensions and pixel type set in the IFD. + * + * Does not handle planar image configurations (PlanarConfiguration != 1). + * + * Returns OK on success, or a negative error code. + */ + virtual status_t validateAndSetStripTags(); + + /** + * Returns true if validateAndSetStripTags has been called, but not setStripOffsets. + */ + virtual bool uninitializedOffsets() const; + + /** + * Convenience method to set beginning offset for strips. + * + * Call this to update the strip offsets before calling writeData. + * + * Returns OK on success, or a negative error code. + */ + virtual status_t setStripOffset(uint32_t offset); + + /** + * Get the total size of the strips in bytes. + * + * This sums the byte count at each strip offset, and returns + * the total count of bytes stored in strips for this IFD. + */ + virtual uint32_t getStripSize() const; + + /** + * Get a formatted string representing this IFD. + */ + virtual std::string toString() const; + + /** + * Print a formatted string representing this IFD to logcat. + */ + void log() const; + + /** + * Get value used to determine sort order. + */ + virtual uint32_t getComparableValue() const; + + protected: + virtual uint32_t checkAndGetOffset(uint32_t offset) const; + std::map > mEntries; + sp mNextIfd; + uint32_t mIfdId; + bool mStripOffsetsInitialized; +}; + +} /*namespace img_utils*/ +} /*namespace android*/ + +#endif /*IMG_UTILS_TIFF_IFD_H*/ diff --git a/app/src/main/cpp/img_utils/include/img_utils/TiffWritable.h b/app/src/main/cpp/img_utils/include/img_utils/TiffWritable.h new file mode 100644 index 00000000..a72cecca --- /dev/null +++ b/app/src/main/cpp/img_utils/include/img_utils/TiffWritable.h @@ -0,0 +1,60 @@ +/* + * Copyright 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef IMG_UTILS_TIFF_WRITABLE +#define IMG_UTILS_TIFF_WRITABLE + +#include +#include +#include + +#include +#include +#include +#include + +namespace android { +namespace img_utils { + +/** + * TiffWritable subclasses represent TIFF metadata objects that can be written + * to an EndianOutput object. This is used for TIFF entries and IFDs. + */ +class ANDROID_API TiffWritable : public Orderable, public LightRefBase { + public: + TiffWritable(); + virtual ~TiffWritable(); + + /** + * Write the data to the output. The given offset is used to calculate + * the header offset for values written. The offset is defined + * relative to the beginning of the TIFF header, and is word aligned. + * + * Returns OK on success, or a negative error code on failure. + */ + virtual status_t writeData(uint32_t offset, /*out*/EndianOutput* out) const = 0; + + /** + * Get the size of the data to write. + */ + virtual size_t getSize() const = 0; + +}; + +} /*namespace img_utils*/ +} /*namespace android*/ + +#endif /*IMG_UTILS_TIFF_WRITABLE*/ diff --git a/app/src/main/cpp/img_utils/include/img_utils/TiffWriter.h b/app/src/main/cpp/img_utils/include/img_utils/TiffWriter.h new file mode 100644 index 00000000..3cb7bbff --- /dev/null +++ b/app/src/main/cpp/img_utils/include/img_utils/TiffWriter.h @@ -0,0 +1,328 @@ +/* + * Copyright 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef IMG_UTILS_TIFF_WRITER_H +#define IMG_UTILS_TIFF_WRITER_H + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +#include +#include + +namespace android { +namespace img_utils { + +class TiffEntry; +class TiffIfd; +class Output; + +/** + * This class holds a collection of TIFF IFDs that can be written as a + * complete DNG file header. + * + * This maps to the TIFF header structure that is logically composed of: + * - An 8-byte file header containing an endianness indicator, the TIFF + * file marker, and the offset to the first IFD. + * - A list of TIFF IFD structures. + */ +class ANDROID_API TiffWriter : public LightRefBase { + public: + enum SubIfdType { + SUBIFD = 0, + GPSINFO + }; + + /** + * Constructs a TiffWriter with the default tag mappings. This enables + * all of the tags defined in TagDefinitions.h, and uses the following + * mapping precedence to resolve collisions: + * (highest precedence) TIFF/EP > DNG > EXIF 2.3 > TIFF 6.0 + */ + TiffWriter(); + + /** + * Constructs a TiffWriter with the given tag mappings. The mapping + * precedence will be in the order that the definition maps are given, + * where the lower index map gets precedence. + * + * This can be used with user-defined definitions, or definitions form + * TagDefinitions.h + * + * The enabledDefinitions mapping object is owned by the caller, and must + * stay alive for the lifespan of the constructed TiffWriter object. + */ + TiffWriter(std::map* enabledDefinitions, + size_t length); + + virtual ~TiffWriter(); + + /** + * Write a TIFF header containing each IFD set. This will recursively + * write all SubIFDs and tags. + * + * Any StripSources passed in will be written to the output as image strips + * at the appropriate offests. The StripByteCounts, RowsPerStrip, and + * StripOffsets tags must be set to use this. To set these tags in a + * given IFD, use the addStrip method. + * + * Returns OK on success, or a negative error code on failure. + */ + virtual status_t write(Output* out, StripSource** sources, size_t sourcesCount, + Endianness end = LITTLE); + + /** + * Write a TIFF header containing each IFD set. This will recursively + * write all SubIFDs and tags. + * + * Image data for strips or tiles must be written separately at the + * appropriate offsets. These offsets must not fall within the file + * header written this way. The size of the header written is given + * by the getTotalSize() method. + * + * Returns OK on success, or a negative error code on failure. + */ + virtual status_t write(Output* out, Endianness end = LITTLE); + + /** + * Get the total size in bytes of the TIFF header. This includes all + * IFDs, tags, and values set for this TiffWriter. + */ + virtual uint32_t getTotalSize() const; + + /** + * Add an entry to the IFD with the given ID. + * + * Returns OK on success, or a negative error code on failure. Valid + * error codes for this method are: + * - BAD_INDEX - The given tag doesn't exist. + * - BAD_VALUE - The given count doesn't match the required count for + * this tag. + * - BAD_TYPE - The type of the given data isn't compatible with the + * type required for this tag. + * - NAME_NOT_FOUND - No ifd exists with the given ID. + */ + virtual status_t addEntry(const sp& entry, uint32_t ifd); + + /** + * Build an entry for a known tag and add it to the IFD with the given ID. + * This tag must be defined in one of the definition vectors this TIFF writer + * was constructed with. The count and type are validated. + * + * Returns OK on success, or a negative error code on failure. Valid + * error codes for this method are: + * - BAD_INDEX - The given tag doesn't exist. + * - BAD_VALUE - The given count doesn't match the required count for + * this tag. + * - BAD_TYPE - The type of the given data isn't compatible with the + * type required for this tag. + * - NAME_NOT_FOUND - No ifd exists with the given ID. + */ + template + status_t addEntry(uint16_t tag, uint32_t count, const T* data, uint32_t ifd); + + /** + * Build an entry for a known tag. This tag must be one of the tags + * defined in one of the definition vectors this TIFF writer was constructed + * with. The count and type are validated. If this succeeds, the resulting + * entry will be placed in the outEntry pointer. + * + * Returns OK on success, or a negative error code on failure. Valid + * error codes for this method are: + * - BAD_INDEX - The given tag doesn't exist. + * - BAD_VALUE - The given count doesn't match the required count for + * this tag. + * - BAD_TYPE - The type of the given data isn't compatible with the + * type required for this tag. + */ + template + status_t buildEntry(uint16_t tag, uint32_t count, const T* data, + /*out*/sp* outEntry) const; + + /** + * Convenience function to set the strip related tags for a given IFD. + * + * Call this before using a StripSource as an input to write. + * The following tags must be set before calling this method: + * - ImageWidth + * - ImageLength + * - SamplesPerPixel + * - BitsPerSample + * + * Returns OK on success, or a negative error code. + */ + virtual status_t addStrip(uint32_t ifd); + + /** + * Return the TIFF entry with the given tag ID in the IFD with the given ID, + * or an empty pointer if none exists. + */ + virtual sp getEntry(uint16_t tag, uint32_t ifd) const; + + /** + * Remove the TIFF entry with the given tag ID in the given IFD if it exists. + */ + virtual void removeEntry(uint16_t tag, uint32_t ifd); + + /** + * Create an empty IFD with the given ID and add it to the end of the + * list of IFDs. + */ + virtual status_t addIfd(uint32_t ifd); + + /** + * Create an empty IFD with the given ID and add it as a SubIfd of the + * parent IFD. + */ + virtual status_t addSubIfd(uint32_t parentIfd, uint32_t ifd, SubIfdType type = SUBIFD); + + /** + * Returns the default type for the given tag ID. + */ + virtual TagType getDefaultType(uint16_t tag) const; + + /** + * Returns the default count for a given tag ID, or 0 if this + * tag normally has a variable count. + */ + virtual uint32_t getDefaultCount(uint16_t tag) const; + + /** + * Returns true if an IFD with the given ID exists. + */ + virtual bool hasIfd(uint32_t ifd) const; + + /** + * Returns true if a definition exist for the given tag ID. + */ + virtual bool checkIfDefined(uint16_t tag) const; + + /** + * Returns the name of the tag if a definition exists for the given tag + * ID, or null if no definition exists. + */ + virtual const char* getTagName(uint16_t tag) const; + + /** + * Print the currently configured IFDs and entries to logcat. + */ + virtual void log() const; + + /** + * Build an entry. No validation is done. + * + * WARNING: Using this method can result in creating poorly formatted + * TIFF files. + * + * Returns a TiffEntry with the given tag, type, count, endianness, + * and data. + */ + template + static sp uncheckedBuildEntry(uint16_t tag, TagType type, + uint32_t count, Endianness end, const T* data); + + /** + * Utility function to build atag-to-definition mapping from a given + * array of tag definitions. + */ +#if 0 + static KeyedVector buildTagMap( + const TagDefinition_t* definitions, size_t length); +#endif + + protected: + enum { + DEFAULT_NUM_TAG_MAPS = 4, + }; + + sp findLastIfd(); + status_t writeFileHeader(EndianOutput& out); + const TagDefinition_t* lookupDefinition(uint16_t tag) const; + status_t calculateOffsets(); + + sp mIfd; + std::map > mNamedIfds; + std::vector > mTagMaps; + size_t mNumTagMaps; +#if 0 + static KeyedVector sTagMaps[]; +#endif +}; + +template +status_t TiffWriter::buildEntry(uint16_t tag, uint32_t count, const T* data, + /*out*/sp* outEntry) const { + const TagDefinition_t* definition = lookupDefinition(tag); + + if (definition == NULL) { + ALOGE("%s: No such tag exists for id %x.", __FUNCTION__, tag); + return BAD_INDEX; + } + + uint32_t fixedCount = definition->fixedCount; + if (fixedCount > 0 && fixedCount != count) { + ALOGE("%s: Invalid count %d for tag %x (expects %d).", __FUNCTION__, count, tag, + fixedCount); + return BAD_VALUE; + } + + TagType fixedType = definition->defaultType; + if (TiffEntry::forceValidType(fixedType, data) == NULL) { + ALOGE("%s: Invalid type used for tag value for tag %x.", __FUNCTION__, tag); + return BAD_TYPE; + } + + *outEntry = new TiffEntryImpl(tag, fixedType, count, + definition->fixedEndian, data); + + return OK; +} + +template +status_t TiffWriter::addEntry(uint16_t tag, uint32_t count, const T* data, uint32_t ifd) { + sp outEntry; + + status_t ret = buildEntry(tag, count, data, &outEntry); + if (ret != OK) { + ALOGE("%s: Could not build entry for tag %x.", __FUNCTION__, tag); + return ret; + } + + return addEntry(outEntry, ifd); +} + +template +sp TiffWriter::uncheckedBuildEntry(uint16_t tag, TagType type, uint32_t count, + Endianness end, const T* data) { + TiffEntryImpl* entry = new TiffEntryImpl(tag, type, count, end, data); + return sp(entry); +} + +} /*namespace img_utils*/ +} /*namespace android*/ + + +#endif /*IMG_UTILS_TIFF_WRITER_H*/ diff --git a/app/src/main/cpp/img_utils/src/ByteArrayOutput.cpp b/app/src/main/cpp/img_utils/src/ByteArrayOutput.cpp new file mode 100644 index 00000000..c4f0ea93 --- /dev/null +++ b/app/src/main/cpp/img_utils/src/ByteArrayOutput.cpp @@ -0,0 +1,54 @@ +/* + * Copyright 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include + +namespace android { +namespace img_utils { + +ByteArrayOutput::ByteArrayOutput() {} + +ByteArrayOutput::~ByteArrayOutput() {} + +status_t ByteArrayOutput::open() { + return OK; +} + +status_t ByteArrayOutput::write(const uint8_t* buf, size_t offset, size_t count) { + if (mByteArray.insert(mByteArray.end(), buf + offset, buf + offset + count) == mByteArray.end()) { + ALOGE("%s: Failed to write to ByteArrayOutput.", __FUNCTION__); + return BAD_VALUE; + } + return OK; +} + +status_t ByteArrayOutput::close() { + mByteArray.clear(); + return OK; +} + +size_t ByteArrayOutput::getSize() const { + return mByteArray.size(); +} + +const uint8_t* ByteArrayOutput::getArray() const { + return &mByteArray[0]; +} + +} /*namespace img_utils*/ +} /*namespace android*/ diff --git a/app/src/main/cpp/img_utils/src/DngUtils.cpp b/app/src/main/cpp/img_utils/src/DngUtils.cpp new file mode 100644 index 00000000..79140302 --- /dev/null +++ b/app/src/main/cpp/img_utils/src/DngUtils.cpp @@ -0,0 +1,496 @@ +/* + * Copyright 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include + +#include +#include +#include + +namespace android { +namespace img_utils { + +OpcodeListBuilder::OpcodeListBuilder() : mCount(0), mOpList(), mEndianOut(&mOpList, BIG) { + if(mEndianOut.open() != OK) { + ALOGE("%s: Open failed.", __FUNCTION__); + } +} + +OpcodeListBuilder::~OpcodeListBuilder() { + if(mEndianOut.close() != OK) { + ALOGE("%s: Close failed.", __FUNCTION__); + } +} + +size_t OpcodeListBuilder::getSize() const { + return mOpList.getSize() + sizeof(mCount); +} + +uint32_t OpcodeListBuilder::getCount() const { + return mCount; +} + +status_t OpcodeListBuilder::buildOpList(uint8_t* buf) const { + uint32_t count = convertToBigEndian(mCount); + memcpy(buf, &count, sizeof(count)); + memcpy(buf + sizeof(count), mOpList.getArray(), mOpList.getSize()); + return OK; +} + +status_t OpcodeListBuilder::addGainMapsForMetadata(uint32_t lsmWidth, + uint32_t lsmHeight, + uint32_t activeAreaTop, + uint32_t activeAreaLeft, + uint32_t activeAreaBottom, + uint32_t activeAreaRight, + CfaLayout cfa, + const float* lensShadingMap) { + status_t err = OK; + uint32_t activeAreaWidth = activeAreaRight - activeAreaLeft; + uint32_t activeAreaHeight = activeAreaBottom - activeAreaTop; + + switch (cfa) { + case CFA_RGGB: + case CFA_GRBG: + case CFA_GBRG: + case CFA_BGGR: + err = addBayerGainMapsForMetadata(lsmWidth, lsmHeight, activeAreaWidth, + activeAreaHeight, cfa, lensShadingMap); + break; + case CFA_NONE: + err = addMonochromeGainMapsForMetadata(lsmWidth, lsmHeight, activeAreaWidth, + activeAreaHeight, lensShadingMap); + break; + default: + ALOGE("%s: Unknown CFA layout %d", __FUNCTION__, cfa); + err = BAD_VALUE; + break; + } + return err; +} + +status_t OpcodeListBuilder::addBayerGainMapsForMetadata(uint32_t lsmWidth, + uint32_t lsmHeight, + uint32_t activeAreaWidth, + uint32_t activeAreaHeight, + CfaLayout cfa, + const float* lensShadingMap) { + uint32_t redTop = 0; + uint32_t redLeft = 0; + uint32_t greenEvenTop = 0; + uint32_t greenEvenLeft = 1; + uint32_t greenOddTop = 1; + uint32_t greenOddLeft = 0; + uint32_t blueTop = 1; + uint32_t blueLeft = 1; + + switch(cfa) { + case CFA_RGGB: + redTop = 0; + redLeft = 0; + greenEvenTop = 0; + greenEvenLeft = 1; + greenOddTop = 1; + greenOddLeft = 0; + blueTop = 1; + blueLeft = 1; + break; + case CFA_GRBG: + redTop = 0; + redLeft = 1; + greenEvenTop = 0; + greenEvenLeft = 0; + greenOddTop = 1; + greenOddLeft = 1; + blueTop = 1; + blueLeft = 0; + break; + case CFA_GBRG: + redTop = 1; + redLeft = 0; + greenEvenTop = 0; + greenEvenLeft = 0; + greenOddTop = 1; + greenOddLeft = 1; + blueTop = 0; + blueLeft = 1; + break; + case CFA_BGGR: + redTop = 1; + redLeft = 1; + greenEvenTop = 0; + greenEvenLeft = 1; + greenOddTop = 1; + greenOddLeft = 0; + blueTop = 0; + blueLeft = 0; + break; + default: + ALOGE("%s: Unknown CFA layout %d", __FUNCTION__, cfa); + return BAD_VALUE; + } + + std::vector redMapVector(lsmWidth * lsmHeight); + float *redMap = redMapVector.data(); + + std::vector greenEvenMapVector(lsmWidth * lsmHeight); + float *greenEvenMap = greenEvenMapVector.data(); + + std::vector greenOddMapVector(lsmWidth * lsmHeight); + float *greenOddMap = greenOddMapVector.data(); + + std::vector blueMapVector(lsmWidth * lsmHeight); + float *blueMap = blueMapVector.data(); + + double spacingV = 1.0 / std::max(1u, lsmHeight - 1); + double spacingH = 1.0 / std::max(1u, lsmWidth - 1); + + size_t lsmMapSize = lsmWidth * lsmHeight * 4; + + // Split lens shading map channels into separate arrays + size_t j = 0; + for (size_t i = 0; i < lsmMapSize; i += 4, ++j) { + redMap[j] = lensShadingMap[i + LSM_R_IND]; + greenEvenMap[j] = lensShadingMap[i + LSM_GE_IND]; + greenOddMap[j] = lensShadingMap[i + LSM_GO_IND]; + blueMap[j] = lensShadingMap[i + LSM_B_IND]; + } + + status_t err = addGainMap(/*top*/redTop, + /*left*/redLeft, + /*bottom*/activeAreaHeight, + /*right*/activeAreaWidth, + /*plane*/0, + /*planes*/1, + /*rowPitch*/2, + /*colPitch*/2, + /*mapPointsV*/lsmHeight, + /*mapPointsH*/lsmWidth, + /*mapSpacingV*/spacingV, + /*mapSpacingH*/spacingH, + /*mapOriginV*/0, + /*mapOriginH*/0, + /*mapPlanes*/1, + /*mapGains*/redMap); + if (err != OK) return err; + + err = addGainMap(/*top*/greenEvenTop, + /*left*/greenEvenLeft, + /*bottom*/activeAreaHeight, + /*right*/activeAreaWidth, + /*plane*/0, + /*planes*/1, + /*rowPitch*/2, + /*colPitch*/2, + /*mapPointsV*/lsmHeight, + /*mapPointsH*/lsmWidth, + /*mapSpacingV*/spacingV, + /*mapSpacingH*/spacingH, + /*mapOriginV*/0, + /*mapOriginH*/0, + /*mapPlanes*/1, + /*mapGains*/greenEvenMap); + if (err != OK) return err; + + err = addGainMap(/*top*/greenOddTop, + /*left*/greenOddLeft, + /*bottom*/activeAreaHeight, + /*right*/activeAreaWidth, + /*plane*/0, + /*planes*/1, + /*rowPitch*/2, + /*colPitch*/2, + /*mapPointsV*/lsmHeight, + /*mapPointsH*/lsmWidth, + /*mapSpacingV*/spacingV, + /*mapSpacingH*/spacingH, + /*mapOriginV*/0, + /*mapOriginH*/0, + /*mapPlanes*/1, + /*mapGains*/greenOddMap); + if (err != OK) return err; + + err = addGainMap(/*top*/blueTop, + /*left*/blueLeft, + /*bottom*/activeAreaHeight, + /*right*/activeAreaWidth, + /*plane*/0, + /*planes*/1, + /*rowPitch*/2, + /*colPitch*/2, + /*mapPointsV*/lsmHeight, + /*mapPointsH*/lsmWidth, + /*mapSpacingV*/spacingV, + /*mapSpacingH*/spacingH, + /*mapOriginV*/0, + /*mapOriginH*/0, + /*mapPlanes*/1, + /*mapGains*/blueMap); + return err; +} + +status_t OpcodeListBuilder::addMonochromeGainMapsForMetadata(uint32_t lsmWidth, + uint32_t lsmHeight, + uint32_t activeAreaWidth, + uint32_t activeAreaHeight, + const float* lensShadingMap) { + std::vector mapVector(lsmWidth * lsmHeight); + float *map = mapVector.data(); + + double spacingV = 1.0 / std::max(1u, lsmHeight - 1); + double spacingH = 1.0 / std::max(1u, lsmWidth - 1); + + size_t lsmMapSize = lsmWidth * lsmHeight * 4; + + // Split lens shading map channels into separate arrays + size_t j = 0; + for (size_t i = 0; i < lsmMapSize; i += 4, ++j) { + map[j] = lensShadingMap[i]; + } + + status_t err = addGainMap(/*top*/0, + /*left*/0, + /*bottom*/activeAreaHeight, + /*right*/activeAreaWidth, + /*plane*/0, + /*planes*/1, + /*rowPitch*/1, + /*colPitch*/1, + /*mapPointsV*/lsmHeight, + /*mapPointsH*/lsmWidth, + /*mapSpacingV*/spacingV, + /*mapSpacingH*/spacingH, + /*mapOriginV*/0, + /*mapOriginH*/0, + /*mapPlanes*/1, + /*mapGains*/map); + if (err != OK) return err; + + return err; +} + +status_t OpcodeListBuilder::addGainMap(uint32_t top, + uint32_t left, + uint32_t bottom, + uint32_t right, + uint32_t plane, + uint32_t planes, + uint32_t rowPitch, + uint32_t colPitch, + uint32_t mapPointsV, + uint32_t mapPointsH, + double mapSpacingV, + double mapSpacingH, + double mapOriginV, + double mapOriginH, + uint32_t mapPlanes, + const float* mapGains) { + + status_t err = addOpcodePreamble(GAIN_MAP_ID); + if (err != OK) return err; + + // Allow this opcode to be skipped if not supported + uint32_t flags = FLAG_OPTIONAL; + + err = mEndianOut.write(&flags, 0, 1); + if (err != OK) return err; + + const uint32_t NUMBER_INT_ARGS = 11; + const uint32_t NUMBER_DOUBLE_ARGS = 4; + + uint32_t totalSize = NUMBER_INT_ARGS * sizeof(uint32_t) + NUMBER_DOUBLE_ARGS * sizeof(double) + + mapPointsV * mapPointsH * mapPlanes * sizeof(float); + + err = mEndianOut.write(&totalSize, 0, 1); + if (err != OK) return err; + + // Batch writes as much as possible + uint32_t settings1[] = { top, + left, + bottom, + right, + plane, + planes, + rowPitch, + colPitch, + mapPointsV, + mapPointsH }; + + err = mEndianOut.write(settings1, 0, NELEMS(settings1)); + if (err != OK) return err; + + double settings2[] = { mapSpacingV, + mapSpacingH, + mapOriginV, + mapOriginH }; + + err = mEndianOut.write(settings2, 0, NELEMS(settings2)); + if (err != OK) return err; + + err = mEndianOut.write(&mapPlanes, 0, 1); + if (err != OK) return err; + + err = mEndianOut.write(mapGains, 0, mapPointsV * mapPointsH * mapPlanes); + if (err != OK) return err; + + mCount++; + + return OK; +} + +status_t OpcodeListBuilder::addWarpRectilinearForMetadata(const float* kCoeffs, + uint32_t activeArrayWidth, + uint32_t activeArrayHeight, + float opticalCenterX, + float opticalCenterY) { + if (activeArrayWidth <= 1 || activeArrayHeight <= 1) { + ALOGE("%s: Cannot add opcode for active array with dimensions w=%" PRIu32 ", h=%" PRIu32, + __FUNCTION__, activeArrayWidth, activeArrayHeight); + return BAD_VALUE; + } + + double normalizedOCX = opticalCenterX / static_cast(activeArrayWidth); + double normalizedOCY = opticalCenterY / static_cast(activeArrayHeight); + + normalizedOCX = CLAMP(normalizedOCX, 0, 1); + normalizedOCY = CLAMP(normalizedOCY, 0, 1); + + double coeffs[6] = { + kCoeffs[0], + kCoeffs[1], + kCoeffs[2], + kCoeffs[3], + kCoeffs[4], + kCoeffs[5] + }; + + return addWarpRectilinear(/*numPlanes*/1, + /*opticalCenterX*/normalizedOCX, + /*opticalCenterY*/normalizedOCY, + coeffs); +} + +status_t OpcodeListBuilder::addWarpRectilinear(uint32_t numPlanes, + double opticalCenterX, + double opticalCenterY, + const double* kCoeffs) { + + status_t err = addOpcodePreamble(WARP_RECTILINEAR_ID); + if (err != OK) return err; + + // Allow this opcode to be skipped if not supported + uint32_t flags = FLAG_OPTIONAL; + + err = mEndianOut.write(&flags, 0, 1); + if (err != OK) return err; + + const uint32_t NUMBER_CENTER_ARGS = 2; + const uint32_t NUMBER_COEFFS = numPlanes * 6; + uint32_t totalSize = (NUMBER_CENTER_ARGS + NUMBER_COEFFS) * sizeof(double) + sizeof(uint32_t); + + err = mEndianOut.write(&totalSize, 0, 1); + if (err != OK) return err; + + err = mEndianOut.write(&numPlanes, 0, 1); + if (err != OK) return err; + + err = mEndianOut.write(kCoeffs, 0, NUMBER_COEFFS); + if (err != OK) return err; + + err = mEndianOut.write(&opticalCenterX, 0, 1); + if (err != OK) return err; + + err = mEndianOut.write(&opticalCenterY, 0, 1); + if (err != OK) return err; + + mCount++; + + return OK; +} + +status_t OpcodeListBuilder::addBadPixelListForMetadata(const uint32_t* hotPixels, + uint32_t xyPairCount, + uint32_t colorFilterArrangement) { + if (colorFilterArrangement > 3) { + ALOGE("%s: Unknown color filter arrangement %" PRIu32, __FUNCTION__, + colorFilterArrangement); + return BAD_VALUE; + } + + return addBadPixelList(colorFilterArrangement, xyPairCount, 0, hotPixels, nullptr); +} + +status_t OpcodeListBuilder::addBadPixelList(uint32_t bayerPhase, + uint32_t badPointCount, + uint32_t badRectCount, + const uint32_t* badPointRowColPairs, + const uint32_t* badRectTopLeftBottomRightTuples) { + + status_t err = addOpcodePreamble(FIX_BAD_PIXELS_LIST); + if (err != OK) return err; + + // Allow this opcode to be skipped if not supported + uint32_t flags = FLAG_OPTIONAL; + + err = mEndianOut.write(&flags, 0, 1); + if (err != OK) return err; + + const uint32_t NUM_NON_VARLEN_FIELDS = 3; + const uint32_t SIZE_OF_POINT = 2; + const uint32_t SIZE_OF_RECT = 4; + + uint32_t totalSize = (NUM_NON_VARLEN_FIELDS + badPointCount * SIZE_OF_POINT + + badRectCount * SIZE_OF_RECT) * sizeof(uint32_t); + err = mEndianOut.write(&totalSize, 0, 1); + if (err != OK) return err; + + err = mEndianOut.write(&bayerPhase, 0, 1); + if (err != OK) return err; + + err = mEndianOut.write(&badPointCount, 0, 1); + if (err != OK) return err; + + err = mEndianOut.write(&badRectCount, 0, 1); + if (err != OK) return err; + + if (badPointCount > 0) { + err = mEndianOut.write(badPointRowColPairs, 0, SIZE_OF_POINT * badPointCount); + if (err != OK) return err; + } + + if (badRectCount > 0) { + err = mEndianOut.write(badRectTopLeftBottomRightTuples, 0, SIZE_OF_RECT * badRectCount); + if (err != OK) return err; + } + + mCount++; + return OK; +} + +status_t OpcodeListBuilder::addOpcodePreamble(uint32_t opcodeId) { + status_t err = mEndianOut.write(&opcodeId, 0, 1); + if (err != OK) return err; + + uint8_t version[] = {1, 3, 0, 0}; + err = mEndianOut.write(version, 0, NELEMS(version)); + if (err != OK) return err; + return OK; +} + +} /*namespace img_utils*/ +} /*namespace android*/ diff --git a/app/src/main/cpp/img_utils/src/EndianUtils.cpp b/app/src/main/cpp/img_utils/src/EndianUtils.cpp new file mode 100644 index 00000000..8681cbe2 --- /dev/null +++ b/app/src/main/cpp/img_utils/src/EndianUtils.cpp @@ -0,0 +1,83 @@ +/* + * Copyright 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +namespace android { +namespace img_utils { + +EndianOutput::EndianOutput(Output* out, Endianness end) + : mOffset(0), mOutput(out), mEndian(end) {} + +EndianOutput::~EndianOutput() {} + +status_t EndianOutput::open() { + mOffset = 0; + return mOutput->open(); +} + +status_t EndianOutput::close() { + return mOutput->close(); +} + +void EndianOutput::setEndianness(Endianness end) { + mEndian = end; +} + +uint32_t EndianOutput::getCurrentOffset() const { + return mOffset; +} + +Endianness EndianOutput::getEndianness() const { + return mEndian; +} + +status_t EndianOutput::write(const uint8_t* buf, size_t offset, size_t count) { + status_t res = OK; + if((res = mOutput->write(buf, offset, count)) == OK) { + mOffset += count; + } + return res; +} + +status_t EndianOutput::write(const int8_t* buf, size_t offset, size_t count) { + return write(reinterpret_cast(buf), offset, count); +} + +#define DEFINE_WRITE(_type_) \ +status_t EndianOutput::write(const _type_* buf, size_t offset, size_t count) { \ + return writeHelper<_type_>(buf, offset, count); \ +} + +DEFINE_WRITE(uint16_t) +DEFINE_WRITE(int16_t) +DEFINE_WRITE(uint32_t) +DEFINE_WRITE(int32_t) +DEFINE_WRITE(uint64_t) +DEFINE_WRITE(int64_t) + +status_t EndianOutput::write(const float* buf, size_t offset, size_t count) { + assert(sizeof(float) == sizeof(uint32_t)); + return writeHelper(reinterpret_cast(buf), offset, count); +} + +status_t EndianOutput::write(const double* buf, size_t offset, size_t count) { + assert(sizeof(double) == sizeof(uint64_t)); + return writeHelper(reinterpret_cast(buf), offset, count); +} + +} /*namespace img_utils*/ +} /*namespace android*/ diff --git a/app/src/main/cpp/img_utils/src/FileInput.cpp b/app/src/main/cpp/img_utils/src/FileInput.cpp new file mode 100644 index 00000000..4c85a518 --- /dev/null +++ b/app/src/main/cpp/img_utils/src/FileInput.cpp @@ -0,0 +1,85 @@ +/* + * Copyright 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include + +namespace android { +namespace img_utils { + +FileInput::FileInput(String8 path) : mFp(NULL), mPath(path), mOpen(false) {} + +FileInput::~FileInput() { + if (mOpen) { + ALOGE("%s: FileInput destroyed without calling close!", __FUNCTION__); + close(); + } + +} + +status_t FileInput::open() { + if (mOpen) { + ALOGW("%s: Open called when file %s already open.", __FUNCTION__, mPath.string()); + return OK; + } + mFp = ::fopen(mPath, "rb"); + if (!mFp) { + ALOGE("%s: Could not open file %s", __FUNCTION__, mPath.string()); + return BAD_VALUE; + } + mOpen = true; + return OK; +} + +ssize_t FileInput::read(uint8_t* buf, size_t offset, size_t count) { + if (!mOpen) { + ALOGE("%s: Could not read file %s, file not open.", __FUNCTION__, mPath.string()); + return BAD_VALUE; + } + + size_t bytesRead = ::fread(buf + offset, sizeof(uint8_t), count, mFp); + int error = ::ferror(mFp); + if (error != 0) { + ALOGE("%s: Error %d occurred while reading file %s.", __FUNCTION__, error, mPath.string()); + return BAD_VALUE; + } + + // End of file reached + if (::feof(mFp) != 0 && bytesRead == 0) { + return NOT_ENOUGH_DATA; + } + + return bytesRead; +} + +status_t FileInput::close() { + if(!mOpen) { + ALOGW("%s: Close called when file %s already close.", __FUNCTION__, mPath.string()); + return OK; + } + + status_t ret = OK; + if(::fclose(mFp) != 0) { + ALOGE("%s: Failed to close file %s.", __FUNCTION__, mPath.string()); + ret = BAD_VALUE; + } + mOpen = false; + return ret; +} + +} /*namespace img_utils*/ +} /*namespace android*/ diff --git a/app/src/main/cpp/img_utils/src/FileOutput.cpp b/app/src/main/cpp/img_utils/src/FileOutput.cpp new file mode 100644 index 00000000..0346762b --- /dev/null +++ b/app/src/main/cpp/img_utils/src/FileOutput.cpp @@ -0,0 +1,79 @@ +/* + * Copyright 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include + +namespace android { +namespace img_utils { + +FileOutput::FileOutput(String8 path) : mFp(NULL), mPath(path), mOpen(false) {} + +FileOutput::~FileOutput() { + if (mOpen) { + ALOGW("%s: Destructor called with %s still open.", __FUNCTION__, mPath.string()); + close(); + } +} + +status_t FileOutput::open() { + if (mOpen) { + ALOGW("%s: Open called when file %s already open.", __FUNCTION__, mPath.string()); + return OK; + } + mFp = ::fopen(mPath, "wb"); + if (!mFp) { + ALOGE("%s: Could not open file %s", __FUNCTION__, mPath.string()); + return BAD_VALUE; + } + mOpen = true; + return OK; +} + +status_t FileOutput::write(const uint8_t* buf, size_t offset, size_t count) { + if (!mOpen) { + ALOGE("%s: Could not write file %s, file not open.", __FUNCTION__, mPath.string()); + return BAD_VALUE; + } + + ::fwrite(buf + offset, sizeof(uint8_t), count, mFp); + + int error = ::ferror(mFp); + if (error != 0) { + ALOGE("%s: Error %d occurred while writing file %s.", __FUNCTION__, error, mPath.string()); + return BAD_VALUE; + } + return OK; +} + +status_t FileOutput::close() { + if(!mOpen) { + ALOGW("%s: Close called when file %s already close.", __FUNCTION__, mPath.string()); + return OK; + } + + status_t ret = OK; + if(::fclose(mFp) != 0) { + ALOGE("%s: Failed to close file %s.", __FUNCTION__, mPath.string()); + ret = BAD_VALUE; + } + mOpen = false; + return ret; +} + +} /*namespace img_utils*/ +} /*namespace android*/ diff --git a/app/src/main/cpp/img_utils/src/Input.cpp b/app/src/main/cpp/img_utils/src/Input.cpp new file mode 100644 index 00000000..3782014f --- /dev/null +++ b/app/src/main/cpp/img_utils/src/Input.cpp @@ -0,0 +1,57 @@ +/* + * Copyright 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +namespace android { +namespace img_utils { + +Input::~Input() {} + +status_t Input::open() { return OK; } + +status_t Input::close() { return OK; } + +ssize_t Input::skip(size_t count) { + const size_t SKIP_BUF_SIZE = 1024; + uint8_t skipBuf[SKIP_BUF_SIZE]; + + size_t remaining = count; + while (remaining > 0) { + size_t amt = (SKIP_BUF_SIZE > remaining) ? remaining : SKIP_BUF_SIZE; + ssize_t ret = read(skipBuf, 0, amt); + if (ret < 0) { + if(ret == NOT_ENOUGH_DATA) { + // End of file encountered + if (remaining == count) { + // Read no bytes, return EOF + return NOT_ENOUGH_DATA; + } else { + // Return num bytes read + return count - remaining; + } + } + // Return error code. + return ret; + } + remaining -= ret; + } + return count; +} + +} /*namespace img_utils*/ +} /*namespace android*/ + diff --git a/app/src/main/cpp/img_utils/src/Orderable.cpp b/app/src/main/cpp/img_utils/src/Orderable.cpp new file mode 100644 index 00000000..300f122a --- /dev/null +++ b/app/src/main/cpp/img_utils/src/Orderable.cpp @@ -0,0 +1,39 @@ +/* + * Copyright 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include + +namespace android { +namespace img_utils { + +#define COMPARE(op) \ +bool Orderable::operator op (const Orderable& orderable) const { \ + return getComparableValue() op orderable.getComparableValue(); \ +} + +COMPARE(>) +COMPARE(<) +COMPARE(>=) +COMPARE(<=) +COMPARE(==) +COMPARE(!=) + +Orderable::~Orderable() {} + +} /*namespace img_utils*/ +} /*namespace android*/ diff --git a/app/src/main/cpp/img_utils/src/Output.cpp b/app/src/main/cpp/img_utils/src/Output.cpp new file mode 100644 index 00000000..0e395b95 --- /dev/null +++ b/app/src/main/cpp/img_utils/src/Output.cpp @@ -0,0 +1,28 @@ +/* + * Copyright 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +#include + +namespace android { +namespace img_utils { + +Output::~Output() {} +status_t Output::open() { return OK; } +status_t Output::close() { return OK; } + +} /*namespace img_utils*/ +} /*namespace android*/ diff --git a/app/src/main/cpp/img_utils/src/SortedEntryVector.cpp b/app/src/main/cpp/img_utils/src/SortedEntryVector.cpp new file mode 100644 index 00000000..f0e1fa1e --- /dev/null +++ b/app/src/main/cpp/img_utils/src/SortedEntryVector.cpp @@ -0,0 +1,44 @@ +/* + * Copyright 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include +#include + +namespace android { +namespace img_utils { + +SortedEntryVector::~SortedEntryVector() {} + +ssize_t SortedEntryVector::indexOfTag(uint16_t tag) const { + // TODO: Use binary search here. + for (size_t i = 0; i < size(); ++i) { + if (itemAt(i)->getTag() == tag) { + return i; + } + } + return -1; +} + +int SortedEntryVector::do_compare(const void* lhs, const void* rhs) const { + const sp* lEntry = reinterpret_cast*>(lhs); + const sp* rEntry = reinterpret_cast*>(rhs); + return compare_type(**lEntry, **rEntry); +} + +} /*namespace img_utils*/ +} /*namespace android*/ diff --git a/app/src/main/cpp/img_utils/src/StripSource.cpp b/app/src/main/cpp/img_utils/src/StripSource.cpp new file mode 100644 index 00000000..57b60824 --- /dev/null +++ b/app/src/main/cpp/img_utils/src/StripSource.cpp @@ -0,0 +1,25 @@ +/* + * Copyright 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +namespace android { +namespace img_utils { + +StripSource::~StripSource() {} + +} /*namespace img_utils*/ +} /*namespace android*/ diff --git a/app/src/main/cpp/img_utils/src/TiffEntry.cpp b/app/src/main/cpp/img_utils/src/TiffEntry.cpp new file mode 100644 index 00000000..ba5e8126 --- /dev/null +++ b/app/src/main/cpp/img_utils/src/TiffEntry.cpp @@ -0,0 +1,251 @@ +/* + * Copyright 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include + +#include +#include + +#include + +namespace android { +namespace img_utils { + +TiffEntry::~TiffEntry() {} + +/** + * Specialize for each valid type, including sub-IFDs. + * + * Values with types other than the ones given here should not compile. + */ + +template<> +const sp* TiffEntry::forceValidType >(TagType type, const sp* value) { + if (type == LONG) { + return value; + } + ALOGE("%s: Value of type 'ifd' is not valid for tag with TIFF type %d.", + __FUNCTION__, type); + return NULL; +} + +template<> +const uint8_t* TiffEntry::forceValidType(TagType type, const uint8_t* value) { + if (type == BYTE || type == ASCII || type == UNDEFINED) { + return value; + } + ALOGE("%s: Value of type 'uint8_t' is not valid for tag with TIFF type %d.", + __FUNCTION__, type); + return NULL; +} + +template<> +const int8_t* TiffEntry::forceValidType(TagType type, const int8_t* value) { + if (type == SBYTE || type == ASCII || type == UNDEFINED) { + return value; + } + ALOGE("%s: Value of type 'int8_t' is not valid for tag with TIFF type %d.", + __FUNCTION__, type); + return NULL; +} + +template<> +const uint16_t* TiffEntry::forceValidType(TagType type, const uint16_t* value) { + if (type == SHORT) { + return value; + } + ALOGE("%s: Value of type 'uint16_t' is not valid for tag with TIFF type %d.", + __FUNCTION__, type); + return NULL; +} + +template<> +const int16_t* TiffEntry::forceValidType(TagType type, const int16_t* value) { + if (type == SSHORT) { + return value; + } + ALOGE("%s: Value of type 'int16_t' is not valid for tag with TIFF type %d.", + __FUNCTION__, type); + return NULL; +} + +template<> +const uint32_t* TiffEntry::forceValidType(TagType type, const uint32_t* value) { + if (type == LONG || type == RATIONAL) { + return value; + } + ALOGE("%s: Value of type 'uint32_t' is not valid for tag with TIFF type %d.", + __FUNCTION__, type); + return NULL; +} + +template<> +const int32_t* TiffEntry::forceValidType(TagType type, const int32_t* value) { + if (type == SLONG || type == SRATIONAL) { + return value; + } + ALOGE("%s: Value of type 'int32_t' is not valid for tag with TIFF type %d.", + __FUNCTION__, type); + return NULL; +} + +template<> +const double* TiffEntry::forceValidType(TagType type, const double* value) { + if (type == DOUBLE) { + return value; + } + ALOGE("%s: Value of type 'double' is not valid for tag with TIFF type %d.", + __FUNCTION__, type); + return NULL; +} + +template<> +const float* TiffEntry::forceValidType(TagType type, const float* value) { + if (type == FLOAT) { + return value; + } + ALOGE("%s: Value of type 'float' is not valid for tag with TIFF type %d.", + __FUNCTION__, type); + return NULL; +} + +std::string TiffEntry::toString() const { + std::string output; + uint32_t count = getCount(); + char buf[256] = { 0 }; + snprintf(buf, sizeof(buf), "[id: %x, type: %d, count: %u, value: '", getTag(), getType(), count); + output.append(buf); + + size_t cappedCount = count; + if (count > MAX_PRINT_STRING_LENGTH) { + cappedCount = MAX_PRINT_STRING_LENGTH; + } + + TagType type = getType(); + switch (type) { + case UNDEFINED: + case BYTE: { + const uint8_t* typed_data = getData(); + for (size_t i = 0; i < cappedCount; ++i) { + output.append(std::to_string(typed_data[i])); + output.append(" "); + } + break; + } + case ASCII: { + const char* typed_data = reinterpret_cast(getData()); + size_t len = count; + if (count > MAX_PRINT_STRING_LENGTH) { + len = MAX_PRINT_STRING_LENGTH; + } + output.append(typed_data, len); + break; + } + case SHORT: { + const uint16_t* typed_data = getData(); + for (size_t i = 0; i < cappedCount; ++i) { + output.append(std::to_string(typed_data[i])); + output.append(" "); + } + break; + } + case LONG: { + const uint32_t* typed_data = getData(); + for (size_t i = 0; i < cappedCount; ++i) { + output.append(std::to_string(typed_data[i])); + output.append(" "); + } + break; + } + case RATIONAL: { + const uint32_t* typed_data = getData(); + cappedCount <<= 1; + for (size_t i = 0; i < cappedCount; i+=2) { + output.append(std::to_string(typed_data[i])); + output.append("/"); + output.append(std::to_string(typed_data[i + 1])); + output.append(" "); + } + break; + } + case SBYTE: { + const int8_t* typed_data = getData(); + for (size_t i = 0; i < cappedCount; ++i) { + output.append(std::to_string(typed_data[i])); + output.append(" "); + } + break; + } + case SSHORT: { + const int16_t* typed_data = getData(); + for (size_t i = 0; i < cappedCount; ++i) { + output.append(std::to_string(typed_data[i])); + output.append(" "); + } + break; + } + case SLONG: { + const int32_t* typed_data = getData(); + for (size_t i = 0; i < cappedCount; ++i) { + output.append(std::to_string(typed_data[i])); + output.append(" "); + } + break; + } + case SRATIONAL: { + const int32_t* typed_data = getData(); + cappedCount <<= 1; + for (size_t i = 0; i < cappedCount; i+=2) { + output.append(std::to_string(typed_data[i])); + output.append("/"); + output.append(std::to_string(typed_data[i + 1])); + output.append(" "); + } + break; + } + case FLOAT: { + const float* typed_data = getData(); + for (size_t i = 0; i < cappedCount; ++i) { + output.append(std::to_string(typed_data[i])); + output.append(" "); + } + break; + } + case DOUBLE: { + const double* typed_data = getData(); + for (size_t i = 0; i < cappedCount; ++i) { + output.append(std::to_string(typed_data[i])); + output.append(" "); + } + break; + } + default: { + output.append("unknown type "); + break; + } + } + + if (count > MAX_PRINT_STRING_LENGTH) { + output.append("..."); + } + output.append("']"); + return output; +} + +} /*namespace img_utils*/ +} /*namespace android*/ diff --git a/app/src/main/cpp/img_utils/src/TiffEntryImpl.cpp b/app/src/main/cpp/img_utils/src/TiffEntryImpl.cpp new file mode 100644 index 00000000..416d66be --- /dev/null +++ b/app/src/main/cpp/img_utils/src/TiffEntryImpl.cpp @@ -0,0 +1,25 @@ +/* + * Copyright 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +// #include + +namespace android { +namespace img_utils { + +} /*namespace img_utils*/ +} /*namespace android*/ diff --git a/app/src/main/cpp/img_utils/src/TiffIfd.cpp b/app/src/main/cpp/img_utils/src/TiffIfd.cpp new file mode 100644 index 00000000..ff0a525c --- /dev/null +++ b/app/src/main/cpp/img_utils/src/TiffIfd.cpp @@ -0,0 +1,386 @@ +/* + * Copyright 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "TiffIfd" + +#include +#include +#include +#include + +#include + +namespace android { +namespace img_utils { + +TiffIfd::TiffIfd(uint32_t ifdId) + : mNextIfd(), mIfdId(ifdId), mStripOffsetsInitialized(false) {} + +TiffIfd::~TiffIfd() {} + +status_t TiffIfd::addEntry(const sp& entry) { + size_t size = mEntries.size(); + if (size >= MAX_IFD_ENTRIES) { + ALOGW("%s: Failed to add entry for tag 0x%x to IFD %u, too many entries in IFD!", + __FUNCTION__, entry->getTag(), mIfdId); + return BAD_INDEX; + } + + mEntries[entry->getTag()] = entry; + return OK; +} + +sp TiffIfd::getEntry(uint16_t tag) const { + auto it = mEntries.find(tag); + if (it == mEntries.cend()) { + ALOGW("%s: No entry for tag 0x%x in ifd %u.", __FUNCTION__, tag, mIfdId); + return NULL; + } + return it->second; +} + +void TiffIfd::removeEntry(uint16_t tag) { + std::map >::iterator it = mEntries.find(tag); + if (it != mEntries.end()) { + mEntries.erase(it); + } +} + + +void TiffIfd::setNextIfd(const sp& ifd) { + mNextIfd = ifd; +} + +sp TiffIfd::getNextIfd() const { + return mNextIfd; +} + +uint32_t TiffIfd::checkAndGetOffset(uint32_t offset) const { + size_t size = mEntries.size(); + + if (size > MAX_IFD_ENTRIES) { + ALOGW("%s: Could not calculate IFD offsets, IFD %u contains too many entries.", + __FUNCTION__, mIfdId); + return BAD_OFFSET; + } + + if (size <= 0) { + ALOGW("%s: Could not calculate IFD offsets, IFD %u contains no entries.", __FUNCTION__, + mIfdId); + return BAD_OFFSET; + } + + if (offset == BAD_OFFSET) { + ALOGW("%s: Could not calculate IFD offsets, IFD %u had a bad initial offset.", + __FUNCTION__, mIfdId); + return BAD_OFFSET; + } + + uint32_t ifdSize = calculateIfdSize(size); + WORD_ALIGN(ifdSize); + return offset + ifdSize; +} + +status_t TiffIfd::writeData(uint32_t offset, /*out*/EndianOutput* out) const { + assert((offset % TIFF_WORD_SIZE) == 0); + status_t ret = OK; + + ALOGV("%s: IFD %u written to offset %u", __FUNCTION__, mIfdId, offset ); + uint32_t valueOffset = checkAndGetOffset(offset); + if (valueOffset == 0) { + return BAD_VALUE; + } + + size_t size = mEntries.size(); + + // Writer IFD header (2 bytes, number of entries). + uint16_t header = static_cast(size); + BAIL_ON_FAIL(out->write(&header, 0, 1), ret); + + // Write tag entries + for (auto it = mEntries.cbegin(); it != mEntries.cend(); ++it) { + BAIL_ON_FAIL(it->second->writeTagInfo(valueOffset, out), ret); + valueOffset += it->second->getSize(); + } + + // Writer IFD footer (4 bytes, offset to next IFD). + uint32_t footer = (mNextIfd != NULL) ? offset + getSize() : 0; + BAIL_ON_FAIL(out->write(&footer, 0, 1), ret); + + assert(out->getCurrentOffset() == offset + calculateIfdSize(size)); + + // Write zeroes till word aligned + ZERO_TILL_WORD(out, calculateIfdSize(size), ret); + + // Write values for each tag entry + for (auto it = mEntries.cbegin(); it != mEntries.cend(); ++it) { + size_t last = out->getCurrentOffset(); + // Only write values that are too large to fit in the 12-byte TIFF entry + if (it->second->getSize() > OFFSET_SIZE) { + BAIL_ON_FAIL(it->second->writeData(out->getCurrentOffset(), out), ret); + } + size_t next = out->getCurrentOffset(); + size_t diff = (next - last); + size_t actual = it->second->getSize(); + if (diff != actual) { + ALOGW("Sizes do not match for tag %x. Expected %zu, received %zu", + it->first, actual, diff); + } + } + + assert(out->getCurrentOffset() == offset + getSize()); + + return ret; +} + +size_t TiffIfd::getSize() const { + size_t size = mEntries.size(); + uint32_t total = calculateIfdSize(size); + WORD_ALIGN(total); + for (auto it = mEntries.cbegin(); it != mEntries.cend(); ++it) { + total += it->second->getSize(); + } + return total; +} + +uint32_t TiffIfd::getId() const { + return mIfdId; +} + +uint32_t TiffIfd::getComparableValue() const { + return mIfdId; +} + +status_t TiffIfd::validateAndSetStripTags() { + sp widthEntry = getEntry(TAG_IMAGEWIDTH); + if (widthEntry == NULL) { + ALOGE("%s: IFD %u doesn't have a ImageWidth tag set", __FUNCTION__, mIfdId); + return BAD_VALUE; + } + + sp heightEntry = getEntry(TAG_IMAGELENGTH); + if (heightEntry == NULL) { + ALOGE("%s: IFD %u doesn't have a ImageLength tag set", __FUNCTION__, mIfdId); + return BAD_VALUE; + } + + sp samplesEntry = getEntry(TAG_SAMPLESPERPIXEL); + if (samplesEntry == NULL) { + ALOGE("%s: IFD %u doesn't have a SamplesPerPixel tag set", __FUNCTION__, mIfdId); + return BAD_VALUE; + } + + sp bitsEntry = getEntry(TAG_BITSPERSAMPLE); + if (bitsEntry == NULL) { + ALOGE("%s: IFD %u doesn't have a BitsPerSample tag set", __FUNCTION__, mIfdId); + return BAD_VALUE; + } + + uint32_t width = *(widthEntry->getData()); + uint32_t height = *(heightEntry->getData()); + uint16_t bitsPerSample = *(bitsEntry->getData()); + uint16_t samplesPerPixel = *(samplesEntry->getData()); + + if ((bitsPerSample % 8) != 0) { + ALOGE("%s: BitsPerSample %d in IFD %u is not byte-aligned.", __FUNCTION__, + bitsPerSample, mIfdId); + return BAD_VALUE; + } + + uint32_t bytesPerSample = bitsPerSample / 8; + + // Choose strip size as close to 8kb as possible without splitting rows. + // If the row length is >8kb, each strip will only contain a single row. + const uint32_t rowLengthBytes = bytesPerSample * samplesPerPixel * width; + const uint32_t idealChunkSize = (1 << 13); // 8kb + uint32_t rowsPerChunk = idealChunkSize / rowLengthBytes; + rowsPerChunk = (rowsPerChunk == 0) ? 1 : rowsPerChunk; + const uint32_t actualChunkSize = rowLengthBytes * rowsPerChunk; + + const uint32_t lastChunkRows = height % rowsPerChunk; + const uint32_t lastChunkSize = lastChunkRows * rowLengthBytes; + + if (actualChunkSize > /*max strip size for TIFF/EP*/65536) { + ALOGE("%s: Strip length too long.", __FUNCTION__); + return BAD_VALUE; + } + + size_t numStrips = height / rowsPerChunk; + + // Add another strip for the incomplete chunk. + if (lastChunkRows > 0) { + numStrips += 1; + } + + // Put each row in it's own strip + uint32_t rowsPerStripVal = rowsPerChunk; + sp rowsPerStrip = TiffWriter::uncheckedBuildEntry(TAG_ROWSPERSTRIP, LONG, 1, + UNDEFINED_ENDIAN, &rowsPerStripVal); + + if (rowsPerStrip == NULL) { + ALOGE("%s: Could not build entry for RowsPerStrip tag.", __FUNCTION__); + return BAD_VALUE; + } + + std::vector byteCounts; + byteCounts.reserve(numStrips); + + for (size_t i = 0; i < numStrips; ++i) { + if (lastChunkRows > 0 && i == (numStrips - 1)) { + byteCounts.push_back(lastChunkSize); + } else { + byteCounts.push_back(actualChunkSize); + } + } + + // Set byte counts for each strip + sp stripByteCounts = TiffWriter::uncheckedBuildEntry(TAG_STRIPBYTECOUNTS, LONG, + static_cast(numStrips), UNDEFINED_ENDIAN, &byteCounts[0]); + + if (stripByteCounts == NULL) { + ALOGE("%s: Could not build entry for StripByteCounts tag.", __FUNCTION__); + return BAD_VALUE; + } + + std::vector stripOffsetsVector; + stripOffsetsVector.resize(numStrips); + + // Set uninitialized offsets + sp stripOffsets = TiffWriter::uncheckedBuildEntry(TAG_STRIPOFFSETS, LONG, + static_cast(numStrips), UNDEFINED_ENDIAN, &stripOffsetsVector[0]); + + if (stripOffsets == NULL) { + ALOGE("%s: Could not build entry for StripOffsets tag.", __FUNCTION__); + return BAD_VALUE; + } + + if(addEntry(stripByteCounts) != OK) { + ALOGE("%s: Could not add entry for StripByteCounts to IFD %u", __FUNCTION__, mIfdId); + return BAD_VALUE; + } + + if(addEntry(rowsPerStrip) != OK) { + ALOGE("%s: Could not add entry for StripByteCounts to IFD %u", __FUNCTION__, mIfdId); + return BAD_VALUE; + } + + if(addEntry(stripOffsets) != OK) { + ALOGE("%s: Could not add entry for StripByteCounts to IFD %u", __FUNCTION__, mIfdId); + return BAD_VALUE; + } + + mStripOffsetsInitialized = true; + return OK; +} + +bool TiffIfd::uninitializedOffsets() const { + return mStripOffsetsInitialized; +} + +status_t TiffIfd::setStripOffset(uint32_t offset) { + + // Get old offsets and bytecounts + sp oldOffsets = getEntry(TAG_STRIPOFFSETS); + if (oldOffsets == NULL) { + ALOGE("%s: IFD %u does not contain StripOffsets entry.", __FUNCTION__, mIfdId); + return BAD_VALUE; + } + + sp stripByteCounts = getEntry(TAG_STRIPBYTECOUNTS); + if (stripByteCounts == NULL) { + ALOGE("%s: IFD %u does not contain StripByteCounts entry.", __FUNCTION__, mIfdId); + return BAD_VALUE; + } + + uint32_t offsetsCount = oldOffsets->getCount(); + uint32_t byteCount = stripByteCounts->getCount(); + if (offsetsCount != byteCount) { + ALOGE("%s: StripOffsets count (%u) doesn't match StripByteCounts count (%u) in IFD %u", + __FUNCTION__, offsetsCount, byteCount, mIfdId); + return BAD_VALUE; + } + + const uint32_t* stripByteCountsArray = stripByteCounts->getData(); + + size_t numStrips = offsetsCount; + + std::vector stripOffsets; + stripOffsets.reserve(numStrips); + + // Calculate updated byte offsets + for (size_t i = 0; i < numStrips; ++i) { + stripOffsets.push_back(offset); + offset += stripByteCountsArray[i]; + } + + sp newOffsets = TiffWriter::uncheckedBuildEntry(TAG_STRIPOFFSETS, LONG, + static_cast(numStrips), UNDEFINED_ENDIAN, &stripOffsets[0]); + + if (newOffsets == NULL) { + ALOGE("%s: Coult not build updated offsets entry in IFD %u", __FUNCTION__, mIfdId); + return BAD_VALUE; + } + + if (addEntry(newOffsets) != OK) { + ALOGE("%s: Failed to add updated offsets entry in IFD %u", __FUNCTION__, mIfdId); + return BAD_VALUE; + } + return OK; +} + +uint32_t TiffIfd::getStripSize() const { + sp stripByteCounts = getEntry(TAG_STRIPBYTECOUNTS); + if (stripByteCounts == NULL) { + ALOGE("%s: IFD %u does not contain StripByteCounts entry.", __FUNCTION__, mIfdId); + return BAD_VALUE; + } + + uint32_t count = stripByteCounts->getCount(); + const uint32_t* byteCounts = stripByteCounts->getData(); + + uint32_t total = 0; + for (size_t i = 0; i < static_cast(count); ++i) { + total += byteCounts[i]; + } + return total; +} + +std::string TiffIfd::toString() const { + size_t s = mEntries.size(); + std::string output; + char buf[1024] = { 0 }; + snprintf(buf, sizeof(buf), "[ifd: %x, num_entries: %zu, entries:\n", getId(), s); + output.append(buf); + for(auto it = mEntries.cbegin(); it != mEntries.cend(); ++it) { + output.append("\t"); + output.append(it->second->toString()); + output.append("\n"); + } + output.append(", next_ifd: %x]", ((mNextIfd != NULL) ? mNextIfd->getId() : 0)); + return output; +} + +void TiffIfd::log() const { + size_t s = mEntries.size(); + ALOGI("[ifd: %x, num_entries: %zu, entries:\n", getId(), s); + for(auto it = mEntries.cbegin(); it != mEntries.cend(); ++it) { + ALOGI("\t%s", it->second->toString().c_str()); + } + ALOGI(", next_ifd: %x]", ((mNextIfd != NULL) ? mNextIfd->getId() : 0)); +} + +} /*namespace img_utils*/ +} /*namespace android*/ diff --git a/app/src/main/cpp/img_utils/src/TiffWritable.cpp b/app/src/main/cpp/img_utils/src/TiffWritable.cpp new file mode 100644 index 00000000..f8d7de76 --- /dev/null +++ b/app/src/main/cpp/img_utils/src/TiffWritable.cpp @@ -0,0 +1,31 @@ +/* + * Copyright 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +#include +#include + +#include + +namespace android { +namespace img_utils { + +TiffWritable::TiffWritable() {} + +TiffWritable::~TiffWritable() {} + +} /*namespace img_utils*/ +} /*namespace android*/ diff --git a/app/src/main/cpp/img_utils/src/TiffWriter.cpp b/app/src/main/cpp/img_utils/src/TiffWriter.cpp new file mode 100644 index 00000000..33f8790e --- /dev/null +++ b/app/src/main/cpp/img_utils/src/TiffWriter.cpp @@ -0,0 +1,425 @@ +/* + * Copyright 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "TiffWriter" + +#include +#include +#include + +#include + +namespace android { +namespace img_utils { + +#if 0 +KeyedVector TiffWriter::buildTagMap( + const TagDefinition_t* definitions, size_t length) { + KeyedVector map; + for(size_t i = 0; i < length; ++i) { + map.add(definitions[i].tagId, definitions + i); + } + return map; +} +#endif + +#define COMPARE(op) \ +bool Orderable::operator op (const Orderable& orderable) const { \ + return getComparableValue() op orderable.getComparableValue(); \ +} + +#define ARRAY_SIZE(array) \ + (sizeof(array) / sizeof((array)[0])) + +#if 0 +KeyedVector TiffWriter::sTagMaps[] = { + buildTagMap(TIFF_EP_TAG_DEFINITIONS, ARRAY_SIZE(TIFF_EP_TAG_DEFINITIONS)), + buildTagMap(DNG_TAG_DEFINITIONS, ARRAY_SIZE(DNG_TAG_DEFINITIONS)), + buildTagMap(EXIF_2_3_TAG_DEFINITIONS, ARRAY_SIZE(EXIF_2_3_TAG_DEFINITIONS)), + buildTagMap(TIFF_6_TAG_DEFINITIONS, ARRAY_SIZE(TIFF_6_TAG_DEFINITIONS)) +}; +#endif + +TiffWriter::TiffWriter() : mNumTagMaps(DEFAULT_NUM_TAG_MAPS) +{ + mTagMaps.reserve(DEFAULT_NUM_TAG_MAPS); + // = new KeyedVector[DEFAULT_NUM_TAG_MAPS]; + + std::vector >::iterator it = mTagMaps.insert(mTagMaps.end(), std::map()); + for(size_t i = 0; i < ARRAY_SIZE(TIFF_EP_TAG_DEFINITIONS); ++i) { + (*it)[TIFF_EP_TAG_DEFINITIONS[i].tagId] = TIFF_EP_TAG_DEFINITIONS + i; + } + it = mTagMaps.insert(mTagMaps.end(), std::map()); + for(size_t i = 0; i < ARRAY_SIZE(DNG_TAG_DEFINITIONS); ++i) { + (*it)[DNG_TAG_DEFINITIONS[i].tagId] = DNG_TAG_DEFINITIONS + i; + } + it = mTagMaps.insert(mTagMaps.end(), std::map()); + for(size_t i = 0; i < ARRAY_SIZE(EXIF_2_3_TAG_DEFINITIONS); ++i) { + (*it)[EXIF_2_3_TAG_DEFINITIONS[i].tagId] = EXIF_2_3_TAG_DEFINITIONS + i; + } + it = mTagMaps.insert(mTagMaps.end(), std::map()); + for(size_t i = 0; i < ARRAY_SIZE(TIFF_6_TAG_DEFINITIONS); ++i) { + (*it)[TIFF_6_TAG_DEFINITIONS[i].tagId] = TIFF_6_TAG_DEFINITIONS + i; + } +} + +TiffWriter::TiffWriter(std::map* enabledDefinitions, + size_t length) : mNumTagMaps(length) +{ + mTagMaps.reserve(length); + for (int i = 0; i < length; ++i) + { + auto it = mTagMaps.insert(mTagMaps.end(), std::map()); + for(auto it2 = enabledDefinitions[i].cbegin(); it2 != enabledDefinitions[i].cend(); ++it2) { + (*it)[it2->first] = it2->second; + } + } +} + +TiffWriter::~TiffWriter() {} + +status_t TiffWriter::write(Output* out, StripSource** sources, size_t sourcesCount, + Endianness end) { + status_t ret = OK; + EndianOutput endOut(out, end); + + if (mIfd == NULL) { + ALOGE("%s: Tiff header is empty.", __FUNCTION__); + return BAD_VALUE; + } + + uint32_t totalSize = getTotalSize(); + + std::map offsetVector; + + for (std::map >::iterator it = mNamedIfds.begin(); it != mNamedIfds.end(); ++it) { + if (it->second->uninitializedOffsets()) { + uint32_t stripSize = it->second->getStripSize(); + if (it->second->setStripOffset(totalSize) != OK) { + ALOGE("%s: Could not set strip offsets.", __FUNCTION__); + return BAD_VALUE; + } + totalSize += stripSize; + WORD_ALIGN(totalSize); + offsetVector[it->first] = totalSize; + } + } + + size_t offVecSize = offsetVector.size(); + if (offVecSize != sourcesCount) { + ALOGE("%s: Mismatch between number of IFDs with uninitialized strips (%zu) and" + " sources (%zu).", __FUNCTION__, offVecSize, sourcesCount); + return BAD_VALUE; + } + + BAIL_ON_FAIL(writeFileHeader(endOut), ret); + + uint32_t offset = FILE_HEADER_SIZE; + sp ifd = mIfd; + while(ifd != NULL) { + BAIL_ON_FAIL(ifd->writeData(offset, &endOut), ret); + offset += ifd->getSize(); + ifd = ifd->getNextIfd(); + } + +#ifndef NDEBUG + log(); +#endif + + for (auto it = offsetVector.begin(); it != offsetVector.end(); ++it) { + uint32_t ifdKey = it->first; + uint32_t sizeToWrite = mNamedIfds[ifdKey]->getStripSize(); + bool found = false; + for (size_t j = 0; j < sourcesCount; ++j) { + if (sources[j]->getIfd() == ifdKey) { + int i = std::distance(offsetVector.begin(), it); + if ((ret = sources[i]->writeToStream(endOut, sizeToWrite)) != OK) { + ALOGE("%s: Could not write to stream, received %d.", __FUNCTION__, ret); + return ret; + } + ZERO_TILL_WORD(&endOut, sizeToWrite, ret); + found = true; + break; + } + } + if (!found) { + ALOGE("%s: No stream for byte strips for IFD %u", __FUNCTION__, ifdKey); + return BAD_VALUE; + } + assert(it->second == endOut.getCurrentOffset()); + } + + return ret; +} + +status_t TiffWriter::write(Output* out, Endianness end) { + status_t ret = OK; + EndianOutput endOut(out, end); + + if (mIfd == NULL) { + ALOGE("%s: Tiff header is empty.", __FUNCTION__); + return BAD_VALUE; + } + BAIL_ON_FAIL(writeFileHeader(endOut), ret); + + uint32_t offset = FILE_HEADER_SIZE; + sp ifd = mIfd; + while(ifd != NULL) { + BAIL_ON_FAIL(ifd->writeData(offset, &endOut), ret); + offset += ifd->getSize(); + ifd = ifd->getNextIfd(); + } + return ret; +} + + +const TagDefinition_t* TiffWriter::lookupDefinition(uint16_t tag) const { + const TagDefinition_t* definition = NULL; + for (size_t i = 0; i < mNumTagMaps; ++i) { + auto it = mTagMaps[i].find(tag); + if (it != mTagMaps[i].cend()) { + definition = it->second; + break; + } + } + + if (definition == NULL) { + ALOGE("%s: No definition exists for tag with id %x.", __FUNCTION__, tag); + } + return definition; +} + +sp TiffWriter::getEntry(uint16_t tag, uint32_t ifd) const { + auto it = mNamedIfds.find(ifd); + if (it == mNamedIfds.cend()) { + ALOGE("%s: No IFD %d set for this writer.", __FUNCTION__, ifd); + return NULL; + } + return it->second->getEntry(tag); +} + +void TiffWriter::removeEntry(uint16_t tag, uint32_t ifd) { + auto it = mNamedIfds.find(ifd); + if (it != mNamedIfds.end()) { + it->second->removeEntry(tag); + } +} + +status_t TiffWriter::addEntry(const sp& entry, uint32_t ifd) { + uint16_t tag = entry->getTag(); + + const TagDefinition_t* definition = lookupDefinition(tag); + + if (definition == NULL) { + ALOGE("%s: No definition exists for tag 0x%x.", __FUNCTION__, tag); + return BAD_INDEX; + } + + std::map >::iterator it = mNamedIfds.find(ifd); + + // Add a new IFD if necessary + if (it == mNamedIfds.end()) { + ALOGE("%s: No IFD %u exists.", __FUNCTION__, ifd); + return NAME_NOT_FOUND; + } + + sp selectedIfd = it->second; + return selectedIfd->addEntry(entry); +} + +status_t TiffWriter::addStrip(uint32_t ifd) { + std::map >::iterator it = mNamedIfds.find(ifd); + if (it == mNamedIfds.end()) { + ALOGE("%s: Ifd %u doesn't exist, cannot add strip entries.", __FUNCTION__, ifd); + return BAD_VALUE; + } + sp selected = it->second; + return selected->validateAndSetStripTags(); +} + +status_t TiffWriter::addIfd(uint32_t ifd) { + std::map >::iterator it = mNamedIfds.find(ifd); + if (it != mNamedIfds.end()) { + ALOGE("%s: Ifd with ID 0x%x already exists.", __FUNCTION__, ifd); + return BAD_VALUE; + } + + sp newIfd = new TiffIfd(ifd); + if (mIfd == NULL) { + mIfd = newIfd; + } else { + sp last = findLastIfd(); + last->setNextIfd(newIfd); + } + + mNamedIfds[ifd] = newIfd; + + return OK; +} + +status_t TiffWriter::addSubIfd(uint32_t parentIfd, uint32_t ifd, SubIfdType type) { + + std::map >::iterator it = mNamedIfds.find(ifd); + if (it != mNamedIfds.end()) { + ALOGE("%s: Ifd with ID 0x%x already exists.", __FUNCTION__, ifd); + return BAD_VALUE; + } + + std::map >::iterator parentIt = mNamedIfds.find(parentIfd); + if (parentIt == mNamedIfds.end()) { + ALOGE("%s: Parent IFD with ID 0x%x does not exist.", __FUNCTION__, parentIfd); + return BAD_VALUE; + } + + sp parent = parentIt->second; + sp newIfd = new TiffIfd(ifd); + + uint16_t subIfdTag; + if (type == SUBIFD) { + subIfdTag = TAG_SUBIFDS; + } else if (type == GPSINFO) { + subIfdTag = TAG_GPSINFO; + } else { + ALOGE("%s: Unknown SubIFD type %d.", __FUNCTION__, type); + return BAD_VALUE; + } + + sp subIfds = parent->getEntry(subIfdTag); + if (subIfds == NULL) { + if (buildEntry(subIfdTag, 1, &newIfd, &subIfds) < 0) { + ALOGE("%s: Failed to build SubIfd entry in IFD 0x%x.", __FUNCTION__, parentIfd); + return BAD_VALUE; + } + } else { + if (type == GPSINFO) { + ALOGE("%s: Cannot add GPSInfo SubIFD to IFD %u, one already exists.", __FUNCTION__, + ifd); + return BAD_VALUE; + } + + std::vector > subIfdList; + const sp* oldIfdArray = subIfds->getData >(); + subIfdList.insert(subIfdList.end(), oldIfdArray, oldIfdArray + subIfds->getCount()); +#if 0 + ALOGE("%s: Failed to build SubIfd entry in IFD 0x%x.", __FUNCTION__, parentIfd); + return BAD_VALUE; + } +#endif + + subIfdList.push_back(newIfd); // < 0) { +#if 0 + ALOGE("%s: Failed to build SubIfd entry in IFD 0x%x.", __FUNCTION__, parentIfd); + return BAD_VALUE; + } +#endif + + uint32_t count = subIfdList.size(); + if (buildEntry(subIfdTag, count, &subIfdList[0], &subIfds) < 0) { + ALOGE("%s: Failed to build SubIfd entry in IFD 0x%x.", __FUNCTION__, parentIfd); + return BAD_VALUE; + } + } + + if (parent->addEntry(subIfds) < 0) { + ALOGE("%s: Failed to add SubIfd entry in IFD 0x%x.", __FUNCTION__, parentIfd); + return BAD_VALUE; + } + + mNamedIfds[ifd] = newIfd; + + return OK; +} + +TagType TiffWriter::getDefaultType(uint16_t tag) const { + const TagDefinition_t* definition = lookupDefinition(tag); + if (definition == NULL) { + ALOGE("%s: Could not find definition for tag %x", __FUNCTION__, tag); + return UNKNOWN_TAGTYPE; + } + return definition->defaultType; +} + +uint32_t TiffWriter::getDefaultCount(uint16_t tag) const { + const TagDefinition_t* definition = lookupDefinition(tag); + if (definition == NULL) { + ALOGE("%s: Could not find definition for tag %x", __FUNCTION__, tag); + return 0; + } + return definition->fixedCount; +} + +bool TiffWriter::hasIfd(uint32_t ifd) const { + auto it = mNamedIfds.find(ifd); + return it != mNamedIfds.cend(); +} + +bool TiffWriter::checkIfDefined(uint16_t tag) const { + return lookupDefinition(tag) != NULL; +} + +const char* TiffWriter::getTagName(uint16_t tag) const { + const TagDefinition_t* definition = lookupDefinition(tag); + if (definition == NULL) { + return NULL; + } + return definition->tagName; +} + +sp TiffWriter::findLastIfd() { + sp ifd = mIfd; + while(ifd != NULL) { + sp nextIfd = ifd->getNextIfd(); + if (nextIfd == NULL) { + break; + } + ifd = std::move(nextIfd); + } + return ifd; +} + +status_t TiffWriter::writeFileHeader(EndianOutput& out) { + status_t ret = OK; + uint16_t endMarker = (out.getEndianness() == BIG) ? BIG_ENDIAN_MARKER : LITTLE_ENDIAN_MARKER; + BAIL_ON_FAIL(out.write(&endMarker, 0, 1), ret); + + uint16_t tiffMarker = TIFF_FILE_MARKER; + BAIL_ON_FAIL(out.write(&tiffMarker, 0, 1), ret); + + uint32_t offsetMarker = FILE_HEADER_SIZE; + BAIL_ON_FAIL(out.write(&offsetMarker, 0, 1), ret); + return ret; +} + +uint32_t TiffWriter::getTotalSize() const { + uint32_t totalSize = FILE_HEADER_SIZE; + sp ifd = mIfd; + while(ifd != NULL) { + totalSize += ifd->getSize(); + ifd = ifd->getNextIfd(); + } + return totalSize; +} + +void TiffWriter::log() const { + ALOGI("%s: TiffWriter:", __FUNCTION__); + size_t length = mNamedIfds.size(); + for (auto it = mNamedIfds.begin(); it != mNamedIfds.end(); ++it) { + it->second->log(); + } +} + +} /*namespace img_utils*/ +} /*namespace android*/ diff --git a/app/src/main/cpp/libcutils/android_reboot.cpp b/app/src/main/cpp/libcutils/android_reboot.cpp new file mode 100644 index 00000000..e0def711 --- /dev/null +++ b/app/src/main/cpp/libcutils/android_reboot.cpp @@ -0,0 +1,53 @@ +/* + * Copyright 2011, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include +#include + +#include + +#define TAG "android_reboot" + +int android_reboot(unsigned cmd, int /*flags*/, const char* arg) { + int ret; + const char* restart_cmd = NULL; + char* prop_value; + + switch (cmd) { + case ANDROID_RB_RESTART: // deprecated + case ANDROID_RB_RESTART2: + restart_cmd = "reboot"; + break; + case ANDROID_RB_POWEROFF: + restart_cmd = "shutdown"; + break; + case ANDROID_RB_THERMOFF: + restart_cmd = "shutdown,thermal"; + break; + } + if (!restart_cmd) return -1; + if (arg && arg[0]) { + ret = asprintf(&prop_value, "%s,%s", restart_cmd, arg); + } else { + ret = asprintf(&prop_value, "%s", restart_cmd); + } + if (ret < 0) return -1; + ret = property_set(ANDROID_RB_PROPERTY, prop_value); + free(prop_value); + return ret; +} diff --git a/app/src/main/cpp/libcutils/arch-x86/cache.h b/app/src/main/cpp/libcutils/arch-x86/cache.h new file mode 100644 index 00000000..1c22feaa --- /dev/null +++ b/app/src/main/cpp/libcutils/arch-x86/cache.h @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#if defined(__slm__) +/* Values are optimized for Silvermont */ +#define SHARED_CACHE_SIZE (1024*1024) /* Silvermont L2 Cache */ +#define DATA_CACHE_SIZE (24*1024) /* Silvermont L1 Data Cache */ +#else +/* Values are optimized for Atom */ +#define SHARED_CACHE_SIZE (512*1024) /* Atom L2 Cache */ +#define DATA_CACHE_SIZE (24*1024) /* Atom L1 Data Cache */ +#endif + +#define SHARED_CACHE_SIZE_HALF (SHARED_CACHE_SIZE / 2) +#define DATA_CACHE_SIZE_HALF (DATA_CACHE_SIZE / 2) diff --git a/app/src/main/cpp/libcutils/arch-x86_64/cache.h b/app/src/main/cpp/libcutils/arch-x86_64/cache.h new file mode 100644 index 00000000..f1443093 --- /dev/null +++ b/app/src/main/cpp/libcutils/arch-x86_64/cache.h @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* Values are optimized for Silvermont */ +#define SHARED_CACHE_SIZE (1024*1024) /* Silvermont L2 Cache */ +#define DATA_CACHE_SIZE (24*1024) /* Silvermont L1 Data Cache */ + +#define SHARED_CACHE_SIZE_HALF (SHARED_CACHE_SIZE / 2) +#define DATA_CACHE_SIZE_HALF (DATA_CACHE_SIZE / 2) diff --git a/app/src/main/cpp/libcutils/ashmem-dev.cpp b/app/src/main/cpp/libcutils/ashmem-dev.cpp new file mode 100644 index 00000000..8c232f0c --- /dev/null +++ b/app/src/main/cpp/libcutils/ashmem-dev.cpp @@ -0,0 +1,468 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +/* + * Implementation of the user-space ashmem API for devices, which have our + * ashmem-enabled kernel. See ashmem-sim.c for the "fake" tmp-based version, + * used by the simulator. + */ +#define LOG_TAG "ashmem" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +/* Will be added to UAPI once upstream change is merged */ +#define F_SEAL_FUTURE_WRITE 0x0010 + +/* + * The minimum vendor API level at and after which it is safe to use memfd. + * This is to facilitate deprecation of ashmem. + */ +#define MIN_MEMFD_VENDOR_API_LEVEL 29 +#define MIN_MEMFD_VENDOR_API_LEVEL_CHAR 'Q' + +/* ashmem identity */ +static dev_t __ashmem_rdev; +/* + * If we trigger a signal handler in the middle of locked activity and the + * signal handler calls ashmem, we could get into a deadlock state. + */ +static pthread_mutex_t __ashmem_lock = PTHREAD_MUTEX_INITIALIZER; + +/* + * has_memfd_support() determines if the device can use memfd. memfd support + * has been there for long time, but certain things in it may be missing. We + * check for needed support in it. Also we check if the VNDK version of + * libcutils being used is new enough, if its not, then we cannot use memfd + * since the older copies may be using ashmem so we just use ashmem. Once all + * Android devices that are getting updates are new enough (ex, they were + * originally shipped with Android release > P), then we can just use memfd and + * delete all ashmem code from libcutils (while preserving the interface). + * + * NOTE: + * The sys.use_memfd property is set by default to false in Android + * to temporarily disable memfd, till vendor and apps are ready for it. + * The main issue: either apps or vendor processes can directly make ashmem + * IOCTLs on FDs they receive by assuming they are ashmem, without going + * through libcutils. Such fds could have very well be originally created with + * libcutils hence they could be memfd. Thus the IOCTLs will break. + * + * Set default value of sys.use_memfd property to true once the issue is + * resolved, so that the code can then self-detect if kernel support is present + * on the device. The property can also set to true from adb shell, for + * debugging. + */ + +static bool debug_log = false; /* set to true for verbose logging and other debug */ +static bool pin_deprecation_warn = true; /* Log the pin deprecation warning only once */ + +/* Determine if vendor processes would be ok with memfd in the system: + * + * If VNDK is using older libcutils, don't use memfd. This is so that the + * same shared memory mechanism is used across binder transactions between + * vendor partition processes and system partition processes. + */ +static bool check_vendor_memfd_allowed() { + std::string vndk_version = android::base::GetProperty("ro.vndk.version", ""); + + if (vndk_version == "") { + ALOGE("memfd: ro.vndk.version not defined or invalid (%s), this is mandated since P.\n", + vndk_version.c_str()); + return false; + } + + /* No issues if vendor is targetting current Dessert */ + if (vndk_version == "current") { + return false; + } + + /* Check if VNDK version is a number and act on it */ + char* p; + long int vers = strtol(vndk_version.c_str(), &p, 10); + if (*p == 0) { + if (vers < MIN_MEMFD_VENDOR_API_LEVEL) { + ALOGI("memfd: device VNDK version (%s) is < Q so using ashmem.\n", + vndk_version.c_str()); + return false; + } + + return true; + } + + /* If its not a number, assume string, but check if its a sane string */ + if (tolower(vndk_version[0]) < 'a' || tolower(vndk_version[0]) > 'z') { + ALOGE("memfd: ro.vndk.version not defined or invalid (%s), this is mandated since P.\n", + vndk_version.c_str()); + return false; + } + + if (tolower(vndk_version[0]) < tolower(MIN_MEMFD_VENDOR_API_LEVEL_CHAR)) { + ALOGI("memfd: device is using VNDK version (%s) which is less than Q. Use ashmem only.\n", + vndk_version.c_str()); + return false; + } + + return true; +} + + +/* Determine if memfd can be supported. This is just one-time hardwork + * which will be cached by the caller. + */ +static bool __has_memfd_support() { + if (check_vendor_memfd_allowed() == false) { + return false; + } + + /* Used to turn on/off the detection at runtime, in the future this + * property will be removed once we switch everything over to ashmem. + * Currently it is used only for debugging to switch the system over. + */ + if (!android::base::GetBoolProperty("sys.use_memfd", false)) { + if (debug_log) { + ALOGD("sys.use_memfd=false so memfd disabled\n"); + } + return false; + } + + /* Check if kernel support exists, otherwise fall back to ashmem */ + android::base::unique_fd fd( + syscall(__NR_memfd_create, "test_android_memfd", MFD_ALLOW_SEALING)); + if (fd == -1) { + ALOGE("memfd_create failed: %s, no memfd support.\n", strerror(errno)); + return false; + } + + if (fcntl(fd, F_ADD_SEALS, F_SEAL_FUTURE_WRITE) == -1) { + ALOGE("fcntl(F_ADD_SEALS) failed: %s, no memfd support.\n", strerror(errno)); + return false; + } + + if (debug_log) { + ALOGD("memfd: device has memfd support, using it\n"); + } + return true; +} + +static bool has_memfd_support() { + /* memfd_supported is the initial global per-process state of what is known + * about memfd. + */ + static bool memfd_supported = __has_memfd_support(); + + return memfd_supported; +} + +static std::string get_ashmem_device_path() { + static const std::string boot_id_path = "/proc/sys/kernel/random/boot_id"; + std::string boot_id; + if (!android::base::ReadFileToString(boot_id_path, &boot_id)) { + ALOGE("Failed to read %s: %s.\n", boot_id_path.c_str(), strerror(errno)); + return ""; + }; + boot_id = android::base::Trim(boot_id); + + return "/dev/ashmem" + boot_id; +} + +/* logistics of getting file descriptor for ashmem */ +static int __ashmem_open_locked() +{ + static const std::string ashmem_device_path = get_ashmem_device_path(); + + if (ashmem_device_path.empty()) { + return -1; + } + + int fd = TEMP_FAILURE_RETRY(open(ashmem_device_path.c_str(), O_RDWR | O_CLOEXEC)); + + // fallback for APEX w/ use_vendor on Q, which would have still used /dev/ashmem + if (fd < 0) { + fd = TEMP_FAILURE_RETRY(open("/dev/ashmem", O_RDWR | O_CLOEXEC)); + } + + if (fd < 0) { + return fd; + } + + struct stat st; + int ret = TEMP_FAILURE_RETRY(fstat(fd, &st)); + if (ret < 0) { + int save_errno = errno; + close(fd); + errno = save_errno; + return ret; + } + if (!S_ISCHR(st.st_mode) || !st.st_rdev) { + close(fd); + errno = ENOTTY; + return -1; + } + + __ashmem_rdev = st.st_rdev; + return fd; +} + +static int __ashmem_open() +{ + int fd; + + pthread_mutex_lock(&__ashmem_lock); + fd = __ashmem_open_locked(); + pthread_mutex_unlock(&__ashmem_lock); + + return fd; +} + +/* Make sure file descriptor references ashmem, negative number means false */ +static int __ashmem_is_ashmem(int fd, int fatal) +{ + dev_t rdev; + struct stat st; + + if (fstat(fd, &st) < 0) { + return -1; + } + + rdev = 0; /* Too much complexity to sniff __ashmem_rdev */ + if (S_ISCHR(st.st_mode) && st.st_rdev) { + pthread_mutex_lock(&__ashmem_lock); + rdev = __ashmem_rdev; + if (rdev) { + pthread_mutex_unlock(&__ashmem_lock); + } else { + int fd = __ashmem_open_locked(); + if (fd < 0) { + pthread_mutex_unlock(&__ashmem_lock); + return -1; + } + rdev = __ashmem_rdev; + pthread_mutex_unlock(&__ashmem_lock); + + close(fd); + } + + if (st.st_rdev == rdev) { + return 0; + } + } + + if (fatal) { + if (rdev) { + LOG_ALWAYS_FATAL("illegal fd=%d mode=0%o rdev=%d:%d expected 0%o %d:%d", + fd, st.st_mode, major(st.st_rdev), minor(st.st_rdev), + S_IFCHR | S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IRGRP, + major(rdev), minor(rdev)); + } else { + LOG_ALWAYS_FATAL("illegal fd=%d mode=0%o rdev=%d:%d expected 0%o", + fd, st.st_mode, major(st.st_rdev), minor(st.st_rdev), + S_IFCHR | S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IRGRP); + } + /* NOTREACHED */ + } + + errno = ENOTTY; + return -1; +} + +static int __ashmem_check_failure(int fd, int result) +{ + if (result == -1 && errno == ENOTTY) __ashmem_is_ashmem(fd, 1); + return result; +} + +static bool memfd_is_ashmem(int fd) { + static bool fd_check_error_once = false; + + if (__ashmem_is_ashmem(fd, 0) == 0) { + if (!fd_check_error_once) { + ALOGE("memfd: memfd expected but ashmem fd used - please use libcutils.\n"); + fd_check_error_once = true; + } + + return true; + } + + return false; +} + +int ashmem_valid(int fd) +{ + if (has_memfd_support() && !memfd_is_ashmem(fd)) { + return 1; + } + + return __ashmem_is_ashmem(fd, 0) >= 0; +} + +static int memfd_create_region(const char* name, size_t size) { + android::base::unique_fd fd(syscall(__NR_memfd_create, name, MFD_ALLOW_SEALING)); + + if (fd == -1) { + ALOGE("memfd_create(%s, %zd) failed: %s\n", name, size, strerror(errno)); + return -1; + } + + if (ftruncate(fd, size) == -1) { + ALOGE("ftruncate(%s, %zd) failed for memfd creation: %s\n", name, size, strerror(errno)); + return -1; + } + + if (debug_log) { + ALOGE("memfd_create(%s, %zd) success. fd=%d\n", name, size, fd.get()); + } + return fd.release(); +} + +/* + * ashmem_create_region - creates a new ashmem region and returns the file + * descriptor, or <0 on error + * + * `name' is an optional label to give the region (visible in /proc/pid/maps) + * `size' is the size of the region, in page-aligned bytes + */ +int ashmem_create_region(const char *name, size_t size) +{ + int ret, save_errno; + + if (has_memfd_support()) { + return memfd_create_region(name ? name : "none", size); + } + + int fd = __ashmem_open(); + if (fd < 0) { + return fd; + } + + if (name) { + char buf[ASHMEM_NAME_LEN] = {0}; + + strlcpy(buf, name, sizeof(buf)); + ret = TEMP_FAILURE_RETRY(ioctl(fd, ASHMEM_SET_NAME, buf)); + if (ret < 0) { + goto error; + } + } + + ret = TEMP_FAILURE_RETRY(ioctl(fd, ASHMEM_SET_SIZE, size)); + if (ret < 0) { + goto error; + } + + return fd; + +error: + save_errno = errno; + close(fd); + errno = save_errno; + return ret; +} + +static int memfd_set_prot_region(int fd, int prot) { + /* Only proceed if an fd needs to be write-protected */ + if (prot & PROT_WRITE) { + return 0; + } + + if (fcntl(fd, F_ADD_SEALS, F_SEAL_FUTURE_WRITE) == -1) { + ALOGE("memfd_set_prot_region(%d, %d): F_SEAL_FUTURE_WRITE seal failed: %s\n", fd, prot, + strerror(errno)); + return -1; + } + + return 0; +} + +int ashmem_set_prot_region(int fd, int prot) +{ + if (has_memfd_support() && !memfd_is_ashmem(fd)) { + return memfd_set_prot_region(fd, prot); + } + + return __ashmem_check_failure(fd, TEMP_FAILURE_RETRY(ioctl(fd, ASHMEM_SET_PROT_MASK, prot))); +} + +int ashmem_pin_region(int fd, size_t offset, size_t len) +{ + if (!pin_deprecation_warn || debug_log) { + ALOGE("Pinning is deprecated since Android Q. Please use trim or other methods.\n"); + pin_deprecation_warn = true; + } + + if (has_memfd_support() && !memfd_is_ashmem(fd)) { + return 0; + } + + // TODO: should LP64 reject too-large offset/len? + ashmem_pin pin = { static_cast(offset), static_cast(len) }; + return __ashmem_check_failure(fd, TEMP_FAILURE_RETRY(ioctl(fd, ASHMEM_PIN, &pin))); +} + +int ashmem_unpin_region(int fd, size_t offset, size_t len) +{ + if (!pin_deprecation_warn || debug_log) { + ALOGE("Pinning is deprecated since Android Q. Please use trim or other methods.\n"); + pin_deprecation_warn = true; + } + + if (has_memfd_support() && !memfd_is_ashmem(fd)) { + return 0; + } + + // TODO: should LP64 reject too-large offset/len? + ashmem_pin pin = { static_cast(offset), static_cast(len) }; + return __ashmem_check_failure(fd, TEMP_FAILURE_RETRY(ioctl(fd, ASHMEM_UNPIN, &pin))); +} + +int ashmem_get_size_region(int fd) +{ + if (has_memfd_support() && !memfd_is_ashmem(fd)) { + struct stat sb; + + if (fstat(fd, &sb) == -1) { + ALOGE("ashmem_get_size_region(%d): fstat failed: %s\n", fd, strerror(errno)); + return -1; + } + + if (debug_log) { + ALOGD("ashmem_get_size_region(%d): %d\n", fd, static_cast(sb.st_size)); + } + + return sb.st_size; + } + + return __ashmem_check_failure(fd, TEMP_FAILURE_RETRY(ioctl(fd, ASHMEM_GET_SIZE, NULL))); +} diff --git a/app/src/main/cpp/libcutils/ashmem-host.cpp b/app/src/main/cpp/libcutils/ashmem-host.cpp new file mode 100644 index 00000000..2ba1eb0c --- /dev/null +++ b/app/src/main/cpp/libcutils/ashmem-host.cpp @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +/* + * Implementation of the user-space ashmem API for the simulator, which lacks + * an ashmem-enabled kernel. See ashmem-dev.c for the real ashmem-based version. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +static bool ashmem_validate_stat(int fd, struct stat* buf) { + int result = fstat(fd, buf); + if (result == -1) { + return false; + } + + /* + * Check if this is an "ashmem" region. + * TODO: This is very hacky, and can easily break. + * We need some reliable indicator. + */ + if (!(buf->st_nlink == 0 && S_ISREG(buf->st_mode))) { + errno = ENOTTY; + return false; + } + return true; +} + +int ashmem_valid(int fd) { + struct stat buf; + return ashmem_validate_stat(fd, &buf); +} + +int ashmem_create_region(const char* /*ignored*/, size_t size) { + char pattern[PATH_MAX]; + snprintf(pattern, sizeof(pattern), "/tmp/android-ashmem-%d-XXXXXXXXX", getpid()); + int fd = mkstemp(pattern); + if (fd == -1) return -1; + + unlink(pattern); + + if (TEMP_FAILURE_RETRY(ftruncate(fd, size)) == -1) { + close(fd); + return -1; + } + + return fd; +} + +int ashmem_set_prot_region(int /*fd*/, int /*prot*/) { + return 0; +} + +int ashmem_pin_region(int /*fd*/, size_t /*offset*/, size_t /*len*/) { + return 0 /*ASHMEM_NOT_PURGED*/; +} + +int ashmem_unpin_region(int /*fd*/, size_t /*offset*/, size_t /*len*/) { + return 0 /*ASHMEM_IS_UNPINNED*/; +} + +int ashmem_get_size_region(int fd) +{ + struct stat buf; + if (!ashmem_validate_stat(fd, &buf)) { + return -1; + } + + return buf.st_size; +} diff --git a/app/src/main/cpp/libcutils/canned_fs_config.cpp b/app/src/main/cpp/libcutils/canned_fs_config.cpp new file mode 100644 index 00000000..2772ef0e --- /dev/null +++ b/app/src/main/cpp/libcutils/canned_fs_config.cpp @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +typedef struct { + const char* path; + unsigned uid; + unsigned gid; + unsigned mode; + uint64_t capabilities; +} Path; + +static Path* canned_data = NULL; +static int canned_alloc = 0; +static int canned_used = 0; + +static int path_compare(const void* a, const void* b) { + return strcmp(((Path*)a)->path, ((Path*)b)->path); +} + +int load_canned_fs_config(const char* fn) { + char buf[PATH_MAX + 200]; + FILE* f; + + f = fopen(fn, "r"); + if (f == NULL) { + fprintf(stderr, "failed to open %s: %s\n", fn, strerror(errno)); + return -1; + } + + while (fgets(buf, sizeof(buf), f)) { + Path* p; + char* token; + char* line = buf; + bool rootdir; + + while (canned_used >= canned_alloc) { + canned_alloc = (canned_alloc+1) * 2; + canned_data = (Path*) realloc(canned_data, canned_alloc * sizeof(Path)); + } + p = canned_data + canned_used; + if (line[0] == '/') line++; + rootdir = line[0] == ' '; + p->path = strdup(rootdir ? "" : strtok(line, " ")); + p->uid = atoi(strtok(rootdir ? line : NULL, " ")); + p->gid = atoi(strtok(NULL, " ")); + p->mode = strtol(strtok(NULL, " "), NULL, 8); // mode is in octal + p->capabilities = 0; + + do { + token = strtok(NULL, " "); + if (token && strncmp(token, "capabilities=", 13) == 0) { + p->capabilities = strtoll(token+13, NULL, 0); + break; + } + } while (token); + + canned_used++; + } + + fclose(f); + + qsort(canned_data, canned_used, sizeof(Path), path_compare); + printf("loaded %d fs_config entries\n", canned_used); + + return 0; +} + +static const int kDebugCannedFsConfig = 0; + +void canned_fs_config(const char* path, int dir, const char* target_out_path, + unsigned* uid, unsigned* gid, unsigned* mode, uint64_t* capabilities) { + Path key, *p; + + key.path = path; + if (path[0] == '/') key.path++; // canned paths lack the leading '/' + p = (Path*) bsearch(&key, canned_data, canned_used, sizeof(Path), path_compare); + if (p == NULL) { + fprintf(stderr, "failed to find [%s] in canned fs_config\n", path); + exit(1); + } + *uid = p->uid; + *gid = p->gid; + *mode = p->mode; + *capabilities = p->capabilities; + + if (kDebugCannedFsConfig) { + // for debugging, run the built-in fs_config and compare the results. + + unsigned c_uid, c_gid, c_mode; + uint64_t c_capabilities; + + fs_config(path, dir, target_out_path, &c_uid, &c_gid, &c_mode, &c_capabilities); + + if (c_uid != *uid) printf("%s uid %d %d\n", path, *uid, c_uid); + if (c_gid != *gid) printf("%s gid %d %d\n", path, *gid, c_gid); + if (c_mode != *mode) printf("%s mode 0%o 0%o\n", path, *mode, c_mode); + if (c_capabilities != *capabilities) { + printf("%s capabilities %" PRIx64 " %" PRIx64 "\n", + path, + *capabilities, + c_capabilities); + } + } +} diff --git a/app/src/main/cpp/libcutils/config_utils.cpp b/app/src/main/cpp/libcutils/config_utils.cpp new file mode 100644 index 00000000..a3af01a5 --- /dev/null +++ b/app/src/main/cpp/libcutils/config_utils.cpp @@ -0,0 +1,328 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include +#include +#include +#include +#include + +#include + +cnode* config_node(const char *name, const char *value) +{ + cnode* node = static_cast(calloc(sizeof(cnode), 1)); + if(node) { + node->name = name ? name : ""; + node->value = value ? value : ""; + } + + return node; +} + +cnode* config_find(cnode *root, const char *name) +{ + cnode *node, *match = NULL; + + /* we walk the whole list, as we need to return the last (newest) entry */ + for(node = root->first_child; node; node = node->next) + if(!strcmp(node->name, name)) + match = node; + + return match; +} + +static cnode* _config_create(cnode *root, const char *name) +{ + cnode *node; + + node = config_node(name, NULL); + + if(root->last_child) + root->last_child->next = node; + else + root->first_child = node; + + root->last_child = node; + + return node; +} + +int config_bool(cnode *root, const char *name, int _default) +{ + cnode *node; + + node = config_find(root, name); + if(!node) + return _default; + + switch(node->value[0]) { + case 'y': + case 'Y': + case '1': + return 1; + default: + return 0; + } +} + +const char* config_str(cnode *root, const char *name, const char *_default) +{ + cnode *node; + + node = config_find(root, name); + if(!node) + return _default; + return node->value; +} + +void config_set(cnode *root, const char *name, const char *value) +{ + cnode *node; + + node = config_find(root, name); + if(node) + node->value = value; + else { + node = _config_create(root, name); + node->value = value; + } +} + +#define T_EOF 0 +#define T_TEXT 1 +#define T_DOT 2 +#define T_OBRACE 3 +#define T_CBRACE 4 + +typedef struct +{ + char *data; + char *text; + int len; + char next; +} cstate; + +static int _lex(cstate *cs, int value) +{ + char c; + char *s; + char *data; + + data = cs->data; + + if(cs->next != 0) { + c = cs->next; + cs->next = 0; + goto got_c; + } + +restart: + for(;;) { + c = *data++; + got_c: + if(isspace(c)) + continue; + + switch(c) { + case 0: + return T_EOF; + + case '#': + for(;;) { + switch(*data) { + case 0: + cs->data = data; + return T_EOF; + case '\n': + cs->data = data + 1; + goto restart; + default: + data++; + } + } + break; + + case '.': + cs->data = data; + return T_DOT; + + case '{': + cs->data = data; + return T_OBRACE; + + case '}': + cs->data = data; + return T_CBRACE; + + default: + s = data - 1; + + if(value) { + for(;;) { + if(*data == 0) { + cs->data = data; + break; + } + if(*data == '\n') { + cs->data = data + 1; + *data-- = 0; + break; + } + data++; + } + + /* strip trailing whitespace */ + while(data > s){ + if(!isspace(*data)) break; + *data-- = 0; + } + + goto got_text; + } else { + for(;;) { + if(isspace(*data)) { + *data = 0; + cs->data = data + 1; + goto got_text; + } + switch(*data) { + case 0: + cs->data = data; + goto got_text; + case '.': + case '{': + case '}': + cs->next = *data; + *data = 0; + cs->data = data + 1; + goto got_text; + default: + data++; + } + } + } + } + } + +got_text: + cs->text = s; + return T_TEXT; +} + +#if 0 +char *TOKENNAMES[] = { "EOF", "TEXT", "DOT", "OBRACE", "CBRACE" }; + +static int lex(cstate *cs, int value) +{ + int tok = _lex(cs, value); + printf("TOKEN(%d) %s %s\n", value, TOKENNAMES[tok], + tok == T_TEXT ? cs->text : ""); + return tok; +} +#else +#define lex(cs,v) _lex(cs,v) +#endif + +static int parse_expr(cstate *cs, cnode *node); + +static int parse_block(cstate *cs, cnode *node) +{ + for(;;){ + switch(lex(cs, 0)){ + case T_TEXT: + if(parse_expr(cs, node)) return -1; + continue; + + case T_CBRACE: + return 0; + + default: + return -1; + } + } +} + +static int parse_expr(cstate *cs, cnode *root) +{ + cnode *node; + + /* last token was T_TEXT */ + node = config_find(root, cs->text); + if(!node || *node->value) + node = _config_create(root, cs->text); + + for(;;) { + switch(lex(cs, 1)) { + case T_DOT: + if(lex(cs, 0) != T_TEXT) + return -1; + node = _config_create(node, cs->text); + continue; + + case T_TEXT: + node->value = cs->text; + return 0; + + case T_OBRACE: + return parse_block(cs, node); + + default: + return -1; + } + } +} + +void config_load(cnode *root, char *data) +{ + if(data != 0) { + cstate cs; + cs.data = data; + cs.next = 0; + + for(;;) { + switch(lex(&cs, 0)) { + case T_TEXT: + if(parse_expr(&cs, root)) + return; + break; + default: + return; + } + } + } +} + +void config_load_file(cnode *root, const char *fn) +{ + char* data = static_cast(load_file(fn, nullptr)); + config_load(root, data); + // TODO: deliberate leak :-/ +} + +void config_free(cnode *root) +{ + cnode *cur = root->first_child; + + while (cur) { + cnode *prev = cur; + config_free(cur); + cur = cur->next; + free(prev); + } +} diff --git a/app/src/main/cpp/libcutils/include/cutils/compiler.h b/app/src/main/cpp/libcutils/include/cutils/compiler.h new file mode 100644 index 00000000..70f884a1 --- /dev/null +++ b/app/src/main/cpp/libcutils/include/cutils/compiler.h @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ANDROID_CUTILS_COMPILER_H +#define ANDROID_CUTILS_COMPILER_H + +/* + * helps the compiler's optimizer predicting branches + */ + +#ifdef __cplusplus +# define CC_LIKELY( exp ) (__builtin_expect( !!(exp), true )) +# define CC_UNLIKELY( exp ) (__builtin_expect( !!(exp), false )) +#else +# define CC_LIKELY( exp ) (__builtin_expect( !!(exp), 1 )) +# define CC_UNLIKELY( exp ) (__builtin_expect( !!(exp), 0 )) +#endif + +/** + * exports marked symbols + * + * if used on a C++ class declaration, this macro must be inserted + * after the "class" keyword. For instance: + * + * template + * class ANDROID_API Singleton { } + */ + +#define ANDROID_API __attribute__((visibility("default"))) + +#endif // ANDROID_CUTILS_COMPILER_H diff --git a/app/src/main/cpp/libcutils/include/log/log.h b/app/src/main/cpp/libcutils/include/log/log.h new file mode 100644 index 00000000..6bf1f5af --- /dev/null +++ b/app/src/main/cpp/libcutils/include/log/log.h @@ -0,0 +1,65 @@ +// DO NOT INCLUDE ANYTHING NEW IN THIS FILE. + +// has replaced this file and all changes should go there instead. +// This path remains strictly to include that header as there are thousands of +// references to in the tree. + +// #include +#include +#include + + +#ifdef NDEBUG +#define LOG_NDEBUG 1 +#else +#define LOG_NDEBUG 0 +#endif + +#define LOG_TAG "MPLOG" +#define ALOGV(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG,__VA_ARGS__) +#define ALOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG,__VA_ARGS__) +#define ALOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__) +#define ALOGW(...) __android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__) +#define ALOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG,__VA_ARGS__) + + +#define android_printAssert(cond, tag, ...) \ + __android_log_assert(cond, tag, \ + __android_second(0, ##__VA_ARGS__, NULL) \ + __android_rest(__VA_ARGS__)) + + +#define __FAKE_USE_VA_ARGS(...) ((void)(0)) + +#ifndef LOG_ALWAYS_FATAL_IF +#define LOG_ALWAYS_FATAL_IF(cond, ...) \ + ((__predict_false(cond)) ? (__FAKE_USE_VA_ARGS(__VA_ARGS__), \ + ((void)android_printAssert(#cond, LOG_TAG, ##__VA_ARGS__))) \ + : ((void)0)) +#endif + +#ifndef LOG_ALWAYS_FATAL +#define LOG_ALWAYS_FATAL(...) \ + (((void)android_printAssert(NULL, LOG_TAG, ##__VA_ARGS__))) +#endif + +#if NDEBUG + +#ifndef LOG_FATAL_IF +#define LOG_FATAL_IF(cond, ...) __FAKE_USE_VA_ARGS(__VA_ARGS__) +#endif +#ifndef LOG_FATAL +#define LOG_FATAL(...) __FAKE_USE_VA_ARGS(__VA_ARGS__) +#endif + +#else + +#ifndef LOG_FATAL_IF +#define LOG_FATAL_IF(cond, ...) LOG_ALWAYS_FATAL_IF(cond, ##__VA_ARGS__) +#endif +#ifndef LOG_FATAL +#define LOG_FATAL(...) LOG_ALWAYS_FATAL(__VA_ARGS__) +#endif + +#endif + diff --git a/app/src/main/cpp/libutils/SharedBuffer.cpp b/app/src/main/cpp/libutils/SharedBuffer.cpp new file mode 100644 index 00000000..394492bb --- /dev/null +++ b/app/src/main/cpp/libutils/SharedBuffer.cpp @@ -0,0 +1,144 @@ +/* + * Copyright (C) 2005 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "sharedbuffer" + +#include "SharedBuffer.h" + +#include +#include + +#include + +// --------------------------------------------------------------------------- + +namespace android { + +SharedBuffer* SharedBuffer::alloc(size_t size) +{ + // Don't overflow if the combined size of the buffer / header is larger than + // size_max. +#if 0 + LOG_ALWAYS_FATAL_IF((size >= (SIZE_MAX - sizeof(SharedBuffer))), + "Invalid buffer size %zu", size); +#endif + + SharedBuffer* sb = static_cast(malloc(sizeof(SharedBuffer) + size)); + if (sb) { + // Should be std::atomic_init(&sb->mRefs, 1); + // But that generates a warning with some compilers. + // The following is OK on Android-supported platforms. + sb->mRefs.store(1, std::memory_order_relaxed); + sb->mSize = size; + sb->mClientMetadata = 0; + } + return sb; +} + + +void SharedBuffer::dealloc(const SharedBuffer* released) +{ + free(const_cast(released)); +} + +SharedBuffer* SharedBuffer::edit() const +{ + if (onlyOwner()) { + return const_cast(this); + } + SharedBuffer* sb = alloc(mSize); + if (sb) { + memcpy(sb->data(), data(), size()); + release(); + } + return sb; +} + +SharedBuffer* SharedBuffer::editResize(size_t newSize) const +{ + if (onlyOwner()) { + SharedBuffer* buf = const_cast(this); + if (buf->mSize == newSize) return buf; + // Don't overflow if the combined size of the new buffer / header is larger than + // size_max. +#if 0 + LOG_ALWAYS_FATAL_IF((newSize >= (SIZE_MAX - sizeof(SharedBuffer))), + "Invalid buffer size %zu", newSize); +#endif + + buf = (SharedBuffer*)realloc(buf, sizeof(SharedBuffer) + newSize); + if (buf != nullptr) { + buf->mSize = newSize; + return buf; + } + } + SharedBuffer* sb = alloc(newSize); + if (sb) { + const size_t mySize = mSize; + memcpy(sb->data(), data(), newSize < mySize ? newSize : mySize); + release(); + } + return sb; +} + +SharedBuffer* SharedBuffer::attemptEdit() const +{ + if (onlyOwner()) { + return const_cast(this); + } + return nullptr; +} + +SharedBuffer* SharedBuffer::reset(size_t new_size) const +{ + // cheap-o-reset. + SharedBuffer* sb = alloc(new_size); + if (sb) { + release(); + } + return sb; +} + +void SharedBuffer::acquire() const { + mRefs.fetch_add(1, std::memory_order_relaxed); +} + +int32_t SharedBuffer::release(uint32_t flags) const +{ + const bool useDealloc = ((flags & eKeepStorage) == 0); + if (onlyOwner()) { + // Since we're the only owner, our reference count goes to zero. + mRefs.store(0, std::memory_order_relaxed); + if (useDealloc) { + dealloc(this); + } + // As the only owner, our previous reference count was 1. + return 1; + } + // There's multiple owners, we need to use an atomic decrement. + int32_t prevRefCount = mRefs.fetch_sub(1, std::memory_order_release); + if (prevRefCount == 1) { + // We're the last reference, we need the acquire fence. + atomic_thread_fence(std::memory_order_acquire); + if (useDealloc) { + dealloc(this); + } + } + return prevRefCount; +} + + +}; // namespace android diff --git a/app/src/main/cpp/libutils/SharedBuffer.h b/app/src/main/cpp/libutils/SharedBuffer.h new file mode 100644 index 00000000..476c842f --- /dev/null +++ b/app/src/main/cpp/libutils/SharedBuffer.h @@ -0,0 +1,151 @@ +/* + * Copyright (C) 2005 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * DEPRECATED. DO NOT USE FOR NEW CODE. + */ + +#ifndef ANDROID_SHARED_BUFFER_H +#define ANDROID_SHARED_BUFFER_H + +#include +#include +#include + +// --------------------------------------------------------------------------- + +namespace android { + +class SharedBuffer +{ +public: + + /* flags to use with release() */ + enum { + eKeepStorage = 0x00000001 + }; + + /*! allocate a buffer of size 'size' and acquire() it. + * call release() to free it. + */ + static SharedBuffer* alloc(size_t size); + + /*! free the memory associated with the SharedBuffer. + * Fails if there are any users associated with this SharedBuffer. + * In other words, the buffer must have been release by all its + * users. + */ + static void dealloc(const SharedBuffer* released); + + //! access the data for read + inline const void* data() const; + + //! access the data for read/write + inline void* data(); + + //! get size of the buffer + inline size_t size() const; + + //! get back a SharedBuffer object from its data + static inline SharedBuffer* bufferFromData(void* data); + + //! get back a SharedBuffer object from its data + static inline const SharedBuffer* bufferFromData(const void* data); + + //! get the size of a SharedBuffer object from its data + static inline size_t sizeFromData(const void* data); + + //! edit the buffer (get a writtable, or non-const, version of it) + SharedBuffer* edit() const; + + //! edit the buffer, resizing if needed + SharedBuffer* editResize(size_t size) const; + + //! like edit() but fails if a copy is required + SharedBuffer* attemptEdit() const; + + //! resize and edit the buffer, loose it's content. + SharedBuffer* reset(size_t size) const; + + //! acquire/release a reference on this buffer + void acquire() const; + + /*! release a reference on this buffer, with the option of not + * freeing the memory associated with it if it was the last reference + * returns the previous reference count + */ + int32_t release(uint32_t flags = 0) const; + + //! returns wether or not we're the only owner + inline bool onlyOwner() const; + + +private: + inline SharedBuffer() { } + inline ~SharedBuffer() { } + SharedBuffer(const SharedBuffer&); + SharedBuffer& operator = (const SharedBuffer&); + + // Must be sized to preserve correct alignment. + mutable std::atomic mRefs; + size_t mSize; + uint32_t mReserved; +public: + // mClientMetadata is reserved for client use. It is initialized to 0 + // and the clients can do whatever they want with it. Note that this is + // placed last so that it is adjcent to the buffer allocated. + uint32_t mClientMetadata; +}; + +static_assert(sizeof(SharedBuffer) % 8 == 0 + && (sizeof(size_t) > 4 || sizeof(SharedBuffer) == 16), + "SharedBuffer has unexpected size"); + +// --------------------------------------------------------------------------- + +const void* SharedBuffer::data() const { + return this + 1; +} + +void* SharedBuffer::data() { + return this + 1; +} + +size_t SharedBuffer::size() const { + return mSize; +} + +SharedBuffer* SharedBuffer::bufferFromData(void* data) { + return data ? static_cast(data)-1 : nullptr; +} + +const SharedBuffer* SharedBuffer::bufferFromData(const void* data) { + return data ? static_cast(data)-1 : nullptr; +} + +size_t SharedBuffer::sizeFromData(const void* data) { + return data ? bufferFromData(data)->mSize : 0; +} + +bool SharedBuffer::onlyOwner() const { + return (mRefs.load(std::memory_order_acquire) == 1); +} + +} // namespace android + +// --------------------------------------------------------------------------- + +#endif // ANDROID_VECTOR_H diff --git a/app/src/main/cpp/libutils/StrongPointer.cpp b/app/src/main/cpp/libutils/StrongPointer.cpp new file mode 100644 index 00000000..f8c67919 --- /dev/null +++ b/app/src/main/cpp/libutils/StrongPointer.cpp @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "sp" + +#include + +namespace android { + +void sp_report_race() +{ +#if 0 + LOG_ALWAYS_FATAL("sp<> assignment detected data race"); +#endif +} + +void sp_report_stack_pointer() +{ +#if 0 + LOG_ALWAYS_FATAL("sp<> constructed with stack pointer argument"); +#endif +} + +} diff --git a/app/src/main/cpp/libutils/include/utils/Errors.h b/app/src/main/cpp/libutils/include/utils/Errors.h new file mode 100644 index 00000000..d14d2231 --- /dev/null +++ b/app/src/main/cpp/libutils/include/utils/Errors.h @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include +#include +#include + +namespace android { + +/** + * The type used to return success/failure from frameworks APIs. + * See the anonymous enum below for valid values. + */ +typedef int32_t status_t; + +/* + * Error codes. + * All error codes are negative values. + */ + +// Win32 #defines NO_ERROR as well. It has the same value, so there's no +// real conflict, though it's a bit awkward. +#ifdef _WIN32 +# undef NO_ERROR +#endif + +enum { + OK = 0, // Preferred constant for checking success. + NO_ERROR = OK, // Deprecated synonym for `OK`. Prefer `OK` because it doesn't conflict with Windows. + + UNKNOWN_ERROR = (-2147483647-1), // INT32_MIN value + + NO_MEMORY = -ENOMEM, + INVALID_OPERATION = -ENOSYS, + BAD_VALUE = -EINVAL, + BAD_TYPE = (UNKNOWN_ERROR + 1), + NAME_NOT_FOUND = -ENOENT, + PERMISSION_DENIED = -EPERM, + NO_INIT = -ENODEV, + ALREADY_EXISTS = -EEXIST, + DEAD_OBJECT = -EPIPE, + FAILED_TRANSACTION = (UNKNOWN_ERROR + 2), +#if !defined(_WIN32) + BAD_INDEX = -EOVERFLOW, + NOT_ENOUGH_DATA = -ENODATA, + WOULD_BLOCK = -EWOULDBLOCK, + TIMED_OUT = -ETIMEDOUT, + UNKNOWN_TRANSACTION = -EBADMSG, +#else + BAD_INDEX = -E2BIG, + NOT_ENOUGH_DATA = (UNKNOWN_ERROR + 3), + WOULD_BLOCK = (UNKNOWN_ERROR + 4), + TIMED_OUT = (UNKNOWN_ERROR + 5), + UNKNOWN_TRANSACTION = (UNKNOWN_ERROR + 6), +#endif + FDS_NOT_ALLOWED = (UNKNOWN_ERROR + 7), + UNEXPECTED_NULL = (UNKNOWN_ERROR + 8), +}; + +// Human readable name of error +std::string statusToString(status_t status); + +// Restore define; enumeration is in "android" namespace, so the value defined +// there won't work for Win32 code in a different namespace. +#ifdef _WIN32 +# define NO_ERROR 0L +#endif + +} // namespace android diff --git a/app/src/main/cpp/libutils/include/utils/LightRefBase.h b/app/src/main/cpp/libutils/include/utils/LightRefBase.h new file mode 100644 index 00000000..b04e5c15 --- /dev/null +++ b/app/src/main/cpp/libutils/include/utils/LightRefBase.h @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +/* + * See documentation in RefBase.h + */ + +#include + +#include + +namespace android { + +class ReferenceRenamer; + +template +class LightRefBase +{ +public: + inline LightRefBase() : mCount(0) { } + inline void incStrong(__attribute__((unused)) const void* id) const { + mCount.fetch_add(1, std::memory_order_relaxed); + } + inline void decStrong(__attribute__((unused)) const void* id) const { + if (mCount.fetch_sub(1, std::memory_order_release) == 1) { + std::atomic_thread_fence(std::memory_order_acquire); + delete static_cast(this); + } + } + //! DEBUGGING ONLY: Get current strong ref count. + inline int32_t getStrongCount() const { + return mCount.load(std::memory_order_relaxed); + } + +protected: + inline ~LightRefBase() { } + +private: + friend class ReferenceMover; + inline static void renameRefs(size_t /*n*/, const ReferenceRenamer& /*renamer*/) { } + inline static void renameRefId(T* /*ref*/, const void* /*old_id*/ , const void* /*new_id*/) { } + +private: + mutable std::atomic mCount; +}; + + +// This is a wrapper around LightRefBase that simply enforces a virtual +// destructor to eliminate the template requirement of LightRefBase +class VirtualLightRefBase : public LightRefBase { +public: + virtual ~VirtualLightRefBase() = default; +}; + +} // namespace android diff --git a/app/src/main/cpp/libutils/include/utils/Log.h b/app/src/main/cpp/libutils/include/utils/Log.h new file mode 100644 index 00000000..408d717f --- /dev/null +++ b/app/src/main/cpp/libutils/include/utils/Log.h @@ -0,0 +1,9 @@ +// DO NOT INCLUDE ANYTHING NEW IN THIS FILE. + +// has replaced this file and all changes should go there instead. +// This path remains strictly to include that header as there are thousands of +// references to in the tree. + +// #include +#include +#include \ No newline at end of file diff --git a/app/src/main/cpp/libutils/include/utils/RefBase.h b/app/src/main/cpp/libutils/include/utils/RefBase.h new file mode 100644 index 00000000..89f048db --- /dev/null +++ b/app/src/main/cpp/libutils/include/utils/RefBase.h @@ -0,0 +1,713 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +// SOME COMMENTS ABOUT USAGE: + +// This provides primarily wp<> weak pointer types and RefBase, which work +// together with sp<> from . + +// sp<> (and wp<>) are a type of smart pointer that use a well defined protocol +// to operate. As long as the object they are templated with implements that +// protocol, these smart pointers work. In several places the platform +// instantiates sp<> with non-RefBase objects; the two are not tied to each +// other. + +// RefBase is such an implementation and it supports strong pointers, weak +// pointers and some magic features for the binder. + +// So, when using RefBase objects, you have the ability to use strong and weak +// pointers through sp<> and wp<>. + +// Normally, when the last strong pointer goes away, the object is destroyed, +// i.e. it's destructor is called. HOWEVER, parts of its associated memory is not +// freed until the last weak pointer is released. + +// Weak pointers are essentially "safe" pointers. They are always safe to +// access through promote(). They may return nullptr if the object was +// destroyed because it ran out of strong pointers. This makes them good candidates +// for keys in a cache for instance. + +// Weak pointers remain valid for comparison purposes even after the underlying +// object has been destroyed. Even if object A is destroyed and its memory reused +// for B, A remaining weak pointer to A will not compare equal to one to B. +// This again makes them attractive for use as keys. + +// How is this supposed / intended to be used? + +// Our recommendation is to use strong references (sp<>) when there is an +// ownership relation. e.g. when an object "owns" another one, use a strong +// ref. And of course use strong refs as arguments of functions (it's extremely +// rare that a function will take a wp<>). + +// Typically a newly allocated object will immediately be used to initialize +// a strong pointer, which may then be used to construct or assign to other +// strong and weak pointers. + +// Use weak references when there are no ownership relation. e.g. the keys in a +// cache (you cannot use plain pointers because there is no safe way to acquire +// a strong reference from a vanilla pointer). + +// This implies that two objects should never (or very rarely) have sp<> on +// each other, because they can't both own each other. + + +// Caveats with reference counting + +// Obviously, circular strong references are a big problem; this creates leaks +// and it's hard to debug -- except it's in fact really easy because RefBase has +// tons of debugging code for that. It can basically tell you exactly where the +// leak is. + +// Another problem has to do with destructors with side effects. You must +// assume that the destructor of reference counted objects can be called AT ANY +// TIME. For instance code as simple as this: + +// void setStuff(const sp& stuff) { +// std::lock_guard lock(mMutex); +// mStuff = stuff; +// } + +// is very dangerous. This code WILL deadlock one day or another. + +// What isn't obvious is that ~Stuff() can be called as a result of the +// assignment. And it gets called with the lock held. First of all, the lock is +// protecting mStuff, not ~Stuff(). Secondly, if ~Stuff() uses its own internal +// mutex, now you have mutex ordering issues. Even worse, if ~Stuff() is +// virtual, now you're calling into "user" code (potentially), by that, I mean, +// code you didn't even write. + +// A correct way to write this code is something like: + +// void setStuff(const sp& stuff) { +// std::unique_lock lock(mMutex); +// sp hold = mStuff; +// mStuff = stuff; +// lock.unlock(); +// } + +// More importantly, reference counted objects should do as little work as +// possible in their destructor, or at least be mindful that their destructor +// could be called from very weird and unintended places. + +// Other more specific restrictions for wp<> and sp<>: + +// Do not construct a strong pointer to "this" in an object's constructor. +// The onFirstRef() callback would be made on an incompletely constructed +// object. +// Construction of a weak pointer to "this" in an object's constructor is also +// discouraged. But the implementation was recently changed so that, in the +// absence of extendObjectLifetime() calls, weak pointers no longer impact +// object lifetime, and hence this no longer risks premature deallocation, +// and hence usually works correctly. + +// Such strong or weak pointers can be safely created in the RefBase onFirstRef() +// callback. + +// Use of wp::unsafe_get() for any purpose other than debugging is almost +// always wrong. Unless you somehow know that there is a longer-lived sp<> to +// the same object, it may well return a pointer to a deallocated object that +// has since been reallocated for a different purpose. (And if you know there +// is a longer-lived sp<>, why not use an sp<> directly?) A wp<> should only be +// dereferenced by using promote(). + +// Any object inheriting from RefBase should always be destroyed as the result +// of a reference count decrement, not via any other means. Such objects +// should never be stack allocated, or appear directly as data members in other +// objects. Objects inheriting from RefBase should have their strong reference +// count incremented as soon as possible after construction. Usually this +// will be done via construction of an sp<> to the object, but may instead +// involve other means of calling RefBase::incStrong(). +// Explicitly deleting or otherwise destroying a RefBase object with outstanding +// wp<> or sp<> pointers to it will result in an abort or heap corruption. + +// It is particularly important not to mix sp<> and direct storage management +// since the sp from raw pointer constructor is implicit. Thus if a RefBase- +// -derived object of type T is managed without ever incrementing its strong +// count, and accidentally passed to f(sp), a strong pointer to the object +// will be temporarily constructed and destroyed, prematurely deallocating the +// object, and resulting in heap corruption. None of this would be easily +// visible in the source. + +// Extra Features: + +// RefBase::extendObjectLifetime() can be used to prevent destruction of the +// object while there are still weak references. This is really special purpose +// functionality to support Binder. + +// Wp::promote(), implemented via the attemptIncStrong() member function, is +// used to try to convert a weak pointer back to a strong pointer. It's the +// normal way to try to access the fields of an object referenced only through +// a wp<>. Binder code also sometimes uses attemptIncStrong() directly. + +// RefBase provides a number of additional callbacks for certain reference count +// events, as well as some debugging facilities. + +// Debugging support can be enabled by turning on DEBUG_REFS in RefBase.cpp. +// Otherwise little checking is provided. + +// Thread safety: + +// Like std::shared_ptr, sp<> and wp<> allow concurrent accesses to DIFFERENT +// sp<> and wp<> instances that happen to refer to the same underlying object. +// They do NOT support concurrent access (where at least one access is a write) +// to THE SAME sp<> or wp<>. In effect, their thread-safety properties are +// exactly like those of T*, NOT atomic. + +#ifndef ANDROID_REF_BASE_H +#define ANDROID_REF_BASE_H + +#include +#include +#include // for common_type. + +#include +#include +#include +#include + +// LightRefBase used to be declared in this header, so we have to include it +#include + +#include +#include + +// --------------------------------------------------------------------------- +namespace android { + +// --------------------------------------------------------------------------- + +#define COMPARE_WEAK(_op_) \ +template \ +inline bool operator _op_ (const U* o) const { \ + return m_ptr _op_ o; \ +} \ +/* Needed to handle type inference for nullptr: */ \ +inline bool operator _op_ (const T* o) const { \ + return m_ptr _op_ o; \ +} + +template class comparator, typename T, typename U> +static inline bool _wp_compare_(T* a, U* b) { + return comparator::type>()(a, b); +} + +// Use std::less and friends to avoid undefined behavior when ordering pointers +// to different objects. +#define COMPARE_WEAK_FUNCTIONAL(_op_, _compare_) \ +template \ +inline bool operator _op_ (const U* o) const { \ + return _wp_compare_<_compare_>(m_ptr, o); \ +} + +// --------------------------------------------------------------------------- + +// RefererenceRenamer is pure abstract, there is no virtual method +// implementation to put in a translation unit in order to silence the +// weak vtables warning. +#if defined(__clang__) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wweak-vtables" +#endif + +class ReferenceRenamer { +protected: + // destructor is purposely not virtual so we avoid code overhead from + // subclasses; we have to make it protected to guarantee that it + // cannot be called from this base class (and to make strict compilers + // happy). + ~ReferenceRenamer() { } +public: + virtual void operator()(size_t i) const = 0; +}; + +#if defined(__clang__) +#pragma clang diagnostic pop +#endif + +// --------------------------------------------------------------------------- + +class RefBase +{ +public: + void incStrong(const void* id) const; + void decStrong(const void* id) const; + + void forceIncStrong(const void* id) const; + + //! DEBUGGING ONLY: Get current strong ref count. + int32_t getStrongCount() const; + + class weakref_type + { + public: + RefBase* refBase() const; + + void incWeak(const void* id); + void decWeak(const void* id); + + // acquires a strong reference if there is already one. + bool attemptIncStrong(const void* id); + + // acquires a weak reference if there is already one. + // This is not always safe. see ProcessState.cpp and BpBinder.cpp + // for proper use. + bool attemptIncWeak(const void* id); + + //! DEBUGGING ONLY: Get current weak ref count. + int32_t getWeakCount() const; + + //! DEBUGGING ONLY: Print references held on object. + void printRefs() const; + + //! DEBUGGING ONLY: Enable tracking for this object. + // enable -- enable/disable tracking + // retain -- when tracking is enable, if true, then we save a stack trace + // for each reference and dereference; when retain == false, we + // match up references and dereferences and keep only the + // outstanding ones. + + void trackMe(bool enable, bool retain); + }; + + weakref_type* createWeak(const void* id) const; + + weakref_type* getWeakRefs() const; + + //! DEBUGGING ONLY: Print references held on object. + inline void printRefs() const { getWeakRefs()->printRefs(); } + + //! DEBUGGING ONLY: Enable tracking of object. + inline void trackMe(bool enable, bool retain) + { + getWeakRefs()->trackMe(enable, retain); + } + +protected: + RefBase(); + virtual ~RefBase(); + + //! Flags for extendObjectLifetime() + enum { + OBJECT_LIFETIME_STRONG = 0x0000, + OBJECT_LIFETIME_WEAK = 0x0001, + OBJECT_LIFETIME_MASK = 0x0001 + }; + + void extendObjectLifetime(int32_t mode); + + //! Flags for onIncStrongAttempted() + enum { + FIRST_INC_STRONG = 0x0001 + }; + + // Invoked after creation of initial strong pointer/reference. + virtual void onFirstRef(); + // Invoked when either the last strong reference goes away, or we need to undo + // the effect of an unnecessary onIncStrongAttempted. + virtual void onLastStrongRef(const void* id); + // Only called in OBJECT_LIFETIME_WEAK case. Returns true if OK to promote to + // strong reference. May have side effects if it returns true. + // The first flags argument is always FIRST_INC_STRONG. + // TODO: Remove initial flag argument. + virtual bool onIncStrongAttempted(uint32_t flags, const void* id); + // Invoked in the OBJECT_LIFETIME_WEAK case when the last reference of either + // kind goes away. Unused. + // TODO: Remove. + virtual void onLastWeakRef(const void* id); + +private: + friend class weakref_type; + class weakref_impl; + + RefBase(const RefBase& o); + RefBase& operator=(const RefBase& o); + +private: + friend class ReferenceMover; + + static void renameRefs(size_t n, const ReferenceRenamer& renamer); + + static void renameRefId(weakref_type* ref, + const void* old_id, const void* new_id); + + static void renameRefId(RefBase* ref, + const void* old_id, const void* new_id); + + weakref_impl* const mRefs; +}; + +// --------------------------------------------------------------------------- + +template +class wp +{ +public: + typedef typename RefBase::weakref_type weakref_type; + + inline wp() : m_ptr(nullptr), m_refs(nullptr) { } + + wp(T* other); // NOLINT(implicit) + wp(const wp& other); + explicit wp(const sp& other); + template wp(U* other); // NOLINT(implicit) + template wp(const sp& other); // NOLINT(implicit) + template wp(const wp& other); // NOLINT(implicit) + + ~wp(); + + // Assignment + + wp& operator = (T* other); + wp& operator = (const wp& other); + wp& operator = (const sp& other); + + template wp& operator = (U* other); + template wp& operator = (const wp& other); + template wp& operator = (const sp& other); + + void set_object_and_refs(T* other, weakref_type* refs); + + // promotion to sp + + sp promote() const; + + // Reset + + void clear(); + + // Accessors + + inline weakref_type* get_refs() const { return m_refs; } + + inline T* unsafe_get() const { return m_ptr; } + + // Operators + + COMPARE_WEAK(==) + COMPARE_WEAK(!=) + COMPARE_WEAK_FUNCTIONAL(>, std::greater) + COMPARE_WEAK_FUNCTIONAL(<, std::less) + COMPARE_WEAK_FUNCTIONAL(<=, std::less_equal) + COMPARE_WEAK_FUNCTIONAL(>=, std::greater_equal) + + template + inline bool operator == (const wp& o) const { + return m_refs == o.m_refs; // Implies m_ptr == o.mptr; see invariants below. + } + + template + inline bool operator == (const sp& o) const { + // Just comparing m_ptr fields is often dangerous, since wp<> may refer to an older + // object at the same address. + if (o == nullptr) { + return m_ptr == nullptr; + } else { + return m_refs == o->getWeakRefs(); // Implies m_ptr == o.mptr. + } + } + + template + inline bool operator != (const sp& o) const { + return !(*this == o); + } + + template + inline bool operator > (const wp& o) const { + if (m_ptr == o.m_ptr) { + return _wp_compare_(m_refs, o.m_refs); + } else { + return _wp_compare_(m_ptr, o.m_ptr); + } + } + + template + inline bool operator < (const wp& o) const { + if (m_ptr == o.m_ptr) { + return _wp_compare_(m_refs, o.m_refs); + } else { + return _wp_compare_(m_ptr, o.m_ptr); + } + } + template inline bool operator != (const wp& o) const { return !operator == (o); } + template inline bool operator <= (const wp& o) const { return !operator > (o); } + template inline bool operator >= (const wp& o) const { return !operator < (o); } + +private: + template friend class sp; + template friend class wp; + + T* m_ptr; + weakref_type* m_refs; +}; + +#undef COMPARE_WEAK +#undef COMPARE_WEAK_FUNCTIONAL + +// --------------------------------------------------------------------------- +// No user serviceable parts below here. + +// Implementation invariants: +// Either +// 1) m_ptr and m_refs are both null, or +// 2) m_refs == m_ptr->mRefs, or +// 3) *m_ptr is no longer live, and m_refs points to the weakref_type object that corresponded +// to m_ptr while it was live. *m_refs remains live while a wp<> refers to it. +// +// The m_refs field in a RefBase object is allocated on construction, unique to that RefBase +// object, and never changes. Thus if two wp's have identical m_refs fields, they are either both +// null or point to the same object. If two wp's have identical m_ptr fields, they either both +// point to the same live object and thus have the same m_ref fields, or at least one of the +// objects is no longer live. +// +// Note that the above comparison operations go out of their way to provide an ordering consistent +// with ordinary pointer comparison; otherwise they could ignore m_ptr, and just compare m_refs. + +template +wp::wp(T* other) + : m_ptr(other) +{ + m_refs = other ? m_refs = other->createWeak(this) : nullptr; +} + +template +wp::wp(const wp& other) + : m_ptr(other.m_ptr), m_refs(other.m_refs) +{ + if (m_ptr) m_refs->incWeak(this); +} + +template +wp::wp(const sp& other) + : m_ptr(other.m_ptr) +{ + m_refs = m_ptr ? m_ptr->createWeak(this) : nullptr; +} + +template template +wp::wp(U* other) + : m_ptr(other) +{ + m_refs = other ? other->createWeak(this) : nullptr; +} + +template template +wp::wp(const wp& other) + : m_ptr(other.m_ptr) +{ + if (m_ptr) { + m_refs = other.m_refs; + m_refs->incWeak(this); + } else { + m_refs = nullptr; + } +} + +template template +wp::wp(const sp& other) + : m_ptr(other.m_ptr) +{ + m_refs = m_ptr ? m_ptr->createWeak(this) : nullptr; +} + +template +wp::~wp() +{ + if (m_ptr) m_refs->decWeak(this); +} + +template +wp& wp::operator = (T* other) +{ + weakref_type* newRefs = + other ? other->createWeak(this) : nullptr; + if (m_ptr) m_refs->decWeak(this); + m_ptr = other; + m_refs = newRefs; + return *this; +} + +template +wp& wp::operator = (const wp& other) +{ + weakref_type* otherRefs(other.m_refs); + T* otherPtr(other.m_ptr); + if (otherPtr) otherRefs->incWeak(this); + if (m_ptr) m_refs->decWeak(this); + m_ptr = otherPtr; + m_refs = otherRefs; + return *this; +} + +template +wp& wp::operator = (const sp& other) +{ + weakref_type* newRefs = + other != nullptr ? other->createWeak(this) : nullptr; + T* otherPtr(other.m_ptr); + if (m_ptr) m_refs->decWeak(this); + m_ptr = otherPtr; + m_refs = newRefs; + return *this; +} + +template template +wp& wp::operator = (U* other) +{ + weakref_type* newRefs = + other ? other->createWeak(this) : 0; + if (m_ptr) m_refs->decWeak(this); + m_ptr = other; + m_refs = newRefs; + return *this; +} + +template template +wp& wp::operator = (const wp& other) +{ + weakref_type* otherRefs(other.m_refs); + U* otherPtr(other.m_ptr); + if (otherPtr) otherRefs->incWeak(this); + if (m_ptr) m_refs->decWeak(this); + m_ptr = otherPtr; + m_refs = otherRefs; + return *this; +} + +template template +wp& wp::operator = (const sp& other) +{ + weakref_type* newRefs = + other != nullptr ? other->createWeak(this) : 0; + U* otherPtr(other.m_ptr); + if (m_ptr) m_refs->decWeak(this); + m_ptr = otherPtr; + m_refs = newRefs; + return *this; +} + +template +void wp::set_object_and_refs(T* other, weakref_type* refs) +{ + if (other) refs->incWeak(this); + if (m_ptr) m_refs->decWeak(this); + m_ptr = other; + m_refs = refs; +} + +template +sp wp::promote() const +{ + sp result; + if (m_ptr && m_refs->attemptIncStrong(&result)) { + result.set_pointer(m_ptr); + } + return result; +} + +template +void wp::clear() +{ + if (m_ptr) { + m_refs->decWeak(this); + m_refs = 0; + m_ptr = 0; + } +} + +// --------------------------------------------------------------------------- + +// this class just serves as a namespace so TYPE::moveReferences can stay +// private. +class ReferenceMover { +public: + // it would be nice if we could make sure no extra code is generated + // for sp or wp when TYPE is a descendant of RefBase: + // Using a sp override doesn't work; it's a bit like we wanted + // a template template... + + template static inline + void move_references(sp* dest, sp const* src, size_t n) { + + class Renamer : public ReferenceRenamer { + sp* d_; + sp const* s_; + virtual void operator()(size_t i) const { + // The id are known to be the sp<>'s this pointer + TYPE::renameRefId(d_[i].get(), &s_[i], &d_[i]); + } + public: + Renamer(sp* d, sp const* s) : d_(d), s_(s) { } + virtual ~Renamer() { } + }; + + memmove(dest, src, n*sizeof(sp)); + TYPE::renameRefs(n, Renamer(dest, src)); + } + + + template static inline + void move_references(wp* dest, wp const* src, size_t n) { + + class Renamer : public ReferenceRenamer { + wp* d_; + wp const* s_; + virtual void operator()(size_t i) const { + // The id are known to be the wp<>'s this pointer + TYPE::renameRefId(d_[i].get_refs(), &s_[i], &d_[i]); + } + public: + Renamer(wp* rd, wp const* rs) : d_(rd), s_(rs) { } + virtual ~Renamer() { } + }; + + memmove(dest, src, n*sizeof(wp)); + TYPE::renameRefs(n, Renamer(dest, src)); + } +}; + +// specialization for moving sp<> and wp<> types. +// these are used by the [Sorted|Keyed]Vector<> implementations +// sp<> and wp<> need to be handled specially, because they do not +// have trivial copy operation in the general case (see RefBase.cpp +// when DEBUG ops are enabled), but can be implemented very +// efficiently in most cases. + +template inline +void move_forward_type(sp* d, sp const* s, size_t n) { + ReferenceMover::move_references(d, s, n); +} + +template inline +void move_backward_type(sp* d, sp const* s, size_t n) { + ReferenceMover::move_references(d, s, n); +} + +template inline +void move_forward_type(wp* d, wp const* s, size_t n) { + ReferenceMover::move_references(d, s, n); +} + +template inline +void move_backward_type(wp* d, wp const* s, size_t n) { + ReferenceMover::move_references(d, s, n); +} + +} // namespace android + +// --------------------------------------------------------------------------- + +#endif // ANDROID_REF_BASE_H diff --git a/app/src/main/cpp/libutils/include/utils/StrongPointer.h b/app/src/main/cpp/libutils/include/utils/StrongPointer.h new file mode 100644 index 00000000..6f4fb478 --- /dev/null +++ b/app/src/main/cpp/libutils/include/utils/StrongPointer.h @@ -0,0 +1,317 @@ +/* + * Copyright (C) 2005 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ANDROID_STRONG_POINTER_H +#define ANDROID_STRONG_POINTER_H + +#include +#include // for common_type. + +// --------------------------------------------------------------------------- +namespace android { + +template class wp; + +// --------------------------------------------------------------------------- + +template +class sp { +public: + inline sp() : m_ptr(nullptr) { } + + sp(T* other); // NOLINT(implicit) + sp(const sp& other); + sp(sp&& other) noexcept; + template sp(U* other); // NOLINT(implicit) + template sp(const sp& other); // NOLINT(implicit) + template sp(sp&& other); // NOLINT(implicit) + + ~sp(); + + // Assignment + + sp& operator = (T* other); + sp& operator = (const sp& other); + sp& operator=(sp&& other) noexcept; + + template sp& operator = (const sp& other); + template sp& operator = (sp&& other); + template sp& operator = (U* other); + + //! Special optimization for use by ProcessState (and nobody else). + void force_set(T* other); + + // Reset + + void clear(); + + // Accessors + + inline T& operator* () const { return *m_ptr; } + inline T* operator-> () const { return m_ptr; } + inline T* get() const { return m_ptr; } + inline explicit operator bool () const { return m_ptr != nullptr; } + + // Punt these to the wp<> implementation. + template + inline bool operator == (const wp& o) const { + return o == *this; + } + + template + inline bool operator != (const wp& o) const { + return o != *this; + } + +private: + template friend class sp; + template friend class wp; + void set_pointer(T* ptr); + static inline void check_not_on_stack(const void* ptr); + T* m_ptr; +}; + +#define COMPARE_STRONG(_op_) \ + template \ + static inline bool operator _op_(const sp& t, const sp& u) { \ + return t.get() _op_ u.get(); \ + } \ + template \ + static inline bool operator _op_(const T* t, const sp& u) { \ + return t _op_ u.get(); \ + } \ + template \ + static inline bool operator _op_(const sp& t, const U* u) { \ + return t.get() _op_ u; \ + } \ + template \ + static inline bool operator _op_(const sp& t, std::nullptr_t) { \ + return t.get() _op_ nullptr; \ + } \ + template \ + static inline bool operator _op_(std::nullptr_t, const sp& t) { \ + return nullptr _op_ t.get(); \ + } + +template