camera2: Adding TIFF writing utilities.

Adds a native image utilities library with support for:
- Writing TIFF 6.0 and TIFF EP compliant file headers.
- Additional tags defined in EXIF 2.3 and DNG 1.4 specs.

Change-Id: I7a4fef74bd5254d92baf08a3cf61af5de1b7ca83
diff --git a/media/img_utils/src/Android.mk b/media/img_utils/src/Android.mk
new file mode 100644
index 0000000..80893be
--- /dev/null
+++ b/media/img_utils/src/Android.mk
@@ -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.
+
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := \
+  EndianUtils.cpp \
+  FileInput.cpp \
+  FileOutput.cpp \
+  SortedEntryVector.cpp \
+  Input.cpp \
+  Output.cpp \
+  Orderable.cpp \
+  TiffIfd.cpp \
+  TiffWritable.cpp \
+  TiffWriter.cpp \
+  TiffEntry.cpp \
+  TiffEntryImpl.cpp \
+  ByteArrayOutput.cpp \
+  DngUtils.cpp \
+
+LOCAL_SHARED_LIBRARIES := \
+  libexpat \
+  libutils \
+  libcutils \
+  libcamera_metadata \
+  libcamera_client
+
+LOCAL_C_INCLUDES += \
+  $(LOCAL_PATH)/../include \
+  system/media/camera/include
+
+LOCAL_CFLAGS += \
+  -Wall \
+  -Wextra \
+  -Werror \
+  -fvisibility=hidden
+
+ifneq ($(filter userdebug eng,$(TARGET_BUILD_VARIANT)),)
+    # Enable assert() in eng builds
+    LOCAL_CFLAGS += -UNDEBUG -DLOG_NDEBUG=1
+endif
+
+LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)/../include
+
+LOCAL_MODULE := libimg_utils
+
+include $(BUILD_SHARED_LIBRARY)
diff --git a/media/img_utils/src/ByteArrayOutput.cpp b/media/img_utils/src/ByteArrayOutput.cpp
new file mode 100644
index 0000000..db2d248
--- /dev/null
+++ b/media/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 <img_utils/ByteArrayOutput.h>
+
+#include <utils/Log.h>
+
+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.appendArray(buf + offset, count) < 0) {
+        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.array();
+}
+
+} /*namespace img_utils*/
+} /*namespace android*/
diff --git a/media/img_utils/src/DngUtils.cpp b/media/img_utils/src/DngUtils.cpp
new file mode 100644
index 0000000..788dfc8
--- /dev/null
+++ b/media/img_utils/src/DngUtils.cpp
@@ -0,0 +1,280 @@
+/*
+ * 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 <img_utils/DngUtils.h>
+
+namespace android {
+namespace img_utils {
+
+OpcodeListBuilder::OpcodeListBuilder() : 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) {
+    uint32_t activeAreaWidth = activeAreaRight - activeAreaLeft;
+    uint32_t activeAreaHeight = activeAreaBottom - activeAreaTop;
+    double spacingV = 1.0 / lsmHeight;
+    double spacingH = 1.0 / lsmWidth;
+
+    float redMap[lsmWidth * lsmHeight];
+    float greenEvenMap[lsmWidth * lsmHeight];
+    float greenOddMap[lsmWidth * lsmHeight];
+    float blueMap[lsmWidth * lsmHeight];
+
+    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];
+    }
+
+    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;
+    }
+
+    status_t err = addGainMap(/*top*/redTop,
+                              /*left*/redLeft,
+                              /*bottom*/activeAreaHeight - 1,
+                              /*right*/activeAreaWidth - 1,
+                              /*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 - 1,
+                     /*right*/activeAreaWidth - 1,
+                     /*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 - 1,
+                     /*right*/activeAreaWidth - 1,
+                     /*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 - 1,
+                     /*right*/activeAreaWidth - 1,
+                     /*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::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) {
+
+    uint32_t opcodeId = GAIN_MAP_ID;
+
+    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;
+
+    uint32_t flags = FLAG_OPTIONAL | FLAG_OPTIONAL_FOR_PREVIEW;
+    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;
+}
+
+} /*namespace img_utils*/
+} /*namespace android*/
diff --git a/media/img_utils/src/EndianUtils.cpp b/media/img_utils/src/EndianUtils.cpp
new file mode 100644
index 0000000..8681cbe
--- /dev/null
+++ b/media/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 <img_utils/EndianUtils.h>
+
+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<const uint8_t*>(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<uint32_t>(reinterpret_cast<const uint32_t*>(buf), offset, count);
+}
+
+status_t EndianOutput::write(const double* buf, size_t offset, size_t count) {
+    assert(sizeof(double) == sizeof(uint64_t));
+    return writeHelper<uint64_t>(reinterpret_cast<const uint64_t*>(buf), offset, count);
+}
+
+} /*namespace img_utils*/
+} /*namespace android*/
diff --git a/media/img_utils/src/FileInput.cpp b/media/img_utils/src/FileInput.cpp
new file mode 100644
index 0000000..e43fd53
--- /dev/null
+++ b/media/img_utils/src/FileInput.cpp
@@ -0,0 +1,80 @@
+/*
+ * 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 <img_utils/FileInput.h>
+
+#include <utils/Log.h>
+
+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;
+}
+
+size_t FileInput::read(uint8_t* buf, size_t offset, size_t count, status_t* err) {
+    if (!mOpen) {
+        ALOGE("%s: Could not read file %s, file not open.", __FUNCTION__, mPath.string());
+        if (err != NULL) *err = BAD_VALUE;
+        return 0;
+    }
+
+    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());
+        if (err != NULL) *err = BAD_VALUE;
+    }
+    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 OK;
+}
+
+} /*namespace img_utils*/
+} /*namespace android*/
diff --git a/media/img_utils/src/FileOutput.cpp b/media/img_utils/src/FileOutput.cpp
new file mode 100644
index 0000000..ce763ff
--- /dev/null
+++ b/media/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 <img_utils/FileOutput.h>
+
+#include <utils/Log.h>
+
+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 OK;
+}
+
+} /*namespace img_utils*/
+} /*namespace android*/
diff --git a/media/img_utils/src/Input.cpp b/media/img_utils/src/Input.cpp
new file mode 100644
index 0000000..1e51e10
--- /dev/null
+++ b/media/img_utils/src/Input.cpp
@@ -0,0 +1,29 @@
+/*
+ * 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 <img_utils/Input.h>
+
+namespace android {
+namespace img_utils {
+
+Input::~Input() {}
+status_t Input::open() { return OK; }
+status_t Input::close() { return OK; }
+
+
+} /*namespace img_utils*/
+} /*namespace android*/
+
diff --git a/media/img_utils/src/Orderable.cpp b/media/img_utils/src/Orderable.cpp
new file mode 100644
index 0000000..300f122
--- /dev/null
+++ b/media/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 <img_utils/Orderable.h>
+
+#include <utils/Log.h>
+
+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/media/img_utils/src/Output.cpp b/media/img_utils/src/Output.cpp
new file mode 100644
index 0000000..0e395b9
--- /dev/null
+++ b/media/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 <img_utils/Output.h>
+
+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/media/img_utils/src/SortedEntryVector.cpp b/media/img_utils/src/SortedEntryVector.cpp
new file mode 100644
index 0000000..f0e1fa1
--- /dev/null
+++ b/media/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 <img_utils/SortedEntryVector.h>
+
+#include <utils/TypeHelpers.h>
+#include <utils/Log.h>
+
+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<TiffEntry>* lEntry = reinterpret_cast<const sp<TiffEntry>*>(lhs);
+    const sp<TiffEntry>* rEntry = reinterpret_cast<const sp<TiffEntry>*>(rhs);
+    return compare_type(**lEntry, **rEntry);
+}
+
+} /*namespace img_utils*/
+} /*namespace android*/
diff --git a/media/img_utils/src/TiffEntry.cpp b/media/img_utils/src/TiffEntry.cpp
new file mode 100644
index 0000000..e028827
--- /dev/null
+++ b/media/img_utils/src/TiffEntry.cpp
@@ -0,0 +1,238 @@
+/*
+ * 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 <img_utils/TiffIfd.h>
+#include <img_utils/TiffHelpers.h>
+#include <img_utils/TiffEntry.h>
+
+#include <utils/Errors.h>
+#include <utils/StrongPointer.h>
+#include <utils/Vector.h>
+
+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 Vector<sp<TiffIfd> >* TiffEntry::forceValidType<Vector<sp<TiffIfd> > >(TagType type,
+          const Vector<sp<TiffIfd> >* value) {
+    if (type == LONG) {
+        return value;
+    }
+    ALOGE("%s: Value of type 'ifd vector' is not valid for tag with TIFF type %d.",
+            __FUNCTION__, type);
+    return NULL;
+}
+
+template<>
+const sp<TiffIfd>* TiffEntry::forceValidType<sp<TiffIfd> >(TagType type, const sp<TiffIfd>* 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<uint8_t>(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<int8_t>(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<uint16_t>(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<int16_t>(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<uint32_t>(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<int32_t>(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<double>(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<float>(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;
+}
+
+String8 TiffEntry::toString() const {
+    String8 output;
+    uint32_t count = getCount();
+    output.appendFormat("[id: %x, type: %d, count: %u, value: '", getTag(), getType(), count);
+
+    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<uint8_t>();
+            for (size_t i = 0; i < cappedCount; ++i) {
+                output.appendFormat("%u ", typed_data[i]);
+            }
+            break;
+        }
+        case ASCII: {
+            const char* typed_data = reinterpret_cast<const char*>(getData<uint8_t>());
+            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<uint16_t>();
+            for (size_t i = 0; i < cappedCount; ++i) {
+                output.appendFormat("%u ", typed_data[i]);
+            }
+            break;
+        }
+        case LONG: {
+            const uint32_t* typed_data = getData<uint32_t>();
+            for (size_t i = 0; i < cappedCount; ++i) {
+                output.appendFormat("%u ", typed_data[i]);
+            }
+            break;
+        }
+        case RATIONAL: {
+            const uint32_t* typed_data = getData<uint32_t>();
+            cappedCount <<= 1;
+            for (size_t i = 0; i < cappedCount; i+=2) {
+                output.appendFormat("%u/%u ", typed_data[i], typed_data[i + 1]);
+            }
+            break;
+        }
+        case SBYTE: {
+            const int8_t* typed_data = getData<int8_t>();
+            for (size_t i = 0; i < cappedCount; ++i) {
+                output.appendFormat("%d ", typed_data[i]);
+            }
+            break;
+        }
+        case SSHORT: {
+            const int16_t* typed_data = getData<int16_t>();
+            for (size_t i = 0; i < cappedCount; ++i) {
+                output.appendFormat("%d ", typed_data[i]);
+            }
+            break;
+        }
+        case SLONG: {
+            const int32_t* typed_data = getData<int32_t>();
+            for (size_t i = 0; i < cappedCount; ++i) {
+                output.appendFormat("%d ", typed_data[i]);
+            }
+            break;
+        }
+        case SRATIONAL: {
+            const int32_t* typed_data = getData<int32_t>();
+            cappedCount <<= 1;
+            for (size_t i = 0; i < cappedCount; i+=2) {
+                output.appendFormat("%d/%d ", typed_data[i], typed_data[i + 1]);
+            }
+            break;
+        }
+        case FLOAT:
+        case DOUBLE: {
+            const float* typed_data = getData<float>();
+            for (size_t i = 0; i < cappedCount; ++i) {
+                output.appendFormat("%f ", typed_data[i]);
+            }
+            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/media/img_utils/src/TiffEntryImpl.cpp b/media/img_utils/src/TiffEntryImpl.cpp
new file mode 100644
index 0000000..2052ceb
--- /dev/null
+++ b/media/img_utils/src/TiffEntryImpl.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 <img_utils/TiffEntryImpl.h>
+#include <img_utils/TiffIfd.h>
+
+#include <utils/Vector.h>
+
+namespace android {
+namespace img_utils {
+
+template<>
+uint32_t TiffEntryImpl<TiffIfd>::getSize() const {
+    uint32_t total = 0;
+    for (uint32_t i = 0; i < mCount; ++i) {
+        total += mData[i].getSize();
+    }
+    return total;
+}
+
+template<>
+status_t TiffEntryImpl<TiffIfd>::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);
+    }
+    return ret;
+}
+
+} /*namespace img_utils*/
+} /*namespace android*/
diff --git a/media/img_utils/src/TiffIfd.cpp b/media/img_utils/src/TiffIfd.cpp
new file mode 100644
index 0000000..b75309b
--- /dev/null
+++ b/media/img_utils/src/TiffIfd.cpp
@@ -0,0 +1,182 @@
+/*
+ * 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 <img_utils/TiffIfd.h>
+#include <img_utils/TiffHelpers.h>
+
+#include <utils/Log.h>
+
+namespace android {
+namespace img_utils {
+
+TiffIfd::TiffIfd(uint32_t ifdId)
+        : mNextIfd(), mIfdId(ifdId) {}
+
+TiffIfd::~TiffIfd() {}
+
+status_t TiffIfd::addEntry(const sp<TiffEntry>& entry) {
+    size_t size = mEntries.size();
+    if (size >= MAX_IFD_ENTRIES) {
+        ALOGW("%s: Failed to add entry for tag 0x%x to IFD %d, too many entries in IFD!",
+                __FUNCTION__, entry->getTag(), mIfdId);
+        return BAD_INDEX;
+    }
+
+    if (mEntries.add(entry) < 0) {
+        ALOGW("%s: Failed to add entry for tag 0x%x to ifd %d.", __FUNCTION__, entry->getTag(),
+                mIfdId);
+        return BAD_INDEX;
+    }
+    return OK;
+}
+
+sp<TiffEntry> TiffIfd::getEntry(uint16_t tag) const {
+    ssize_t index = mEntries.indexOfTag(tag);
+    if (index < 0) {
+        ALOGW("%s: No entry for tag 0x%x in ifd %d.", __FUNCTION__, tag, mIfdId);
+        return NULL;
+    }
+    return mEntries[index];
+}
+
+void TiffIfd::setNextIfd(const sp<TiffIfd>& ifd) {
+    mNextIfd = ifd;
+}
+
+sp<TiffIfd> 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 %d contains too many entries.",
+                __FUNCTION__, mIfdId);
+        return BAD_OFFSET;
+    }
+
+    if (size <= 0) {
+        ALOGW("%s: Could not calculate IFD offsets, IFD %d contains no entries.", __FUNCTION__,
+                mIfdId);
+        return BAD_OFFSET;
+    }
+
+    if (offset == BAD_OFFSET) {
+        ALOGW("%s: Could not calculate IFD offsets, IFD %d 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<uint16_t>(size);
+    BAIL_ON_FAIL(out->write(&header, 0, 1), ret);
+
+    // Write tag entries
+    for (size_t i = 0; i < size; ++i) {
+        BAIL_ON_FAIL(mEntries[i]->writeTagInfo(valueOffset, out), ret);
+        valueOffset += mEntries[i]->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 (size_t i = 0; i < size; ++i) {
+        size_t last = out->getCurrentOffset();
+        // Only write values that are too large to fit in the 12-byte TIFF entry
+        if (mEntries[i]->getSize() > OFFSET_SIZE) {
+            BAIL_ON_FAIL(mEntries[i]->writeData(out->getCurrentOffset(), out), ret);
+        }
+        size_t next = out->getCurrentOffset();
+        size_t diff = (next - last);
+        size_t actual = mEntries[i]->getSize();
+        if (diff != actual) {
+            ALOGW("Sizes do not match for tag %x. Expected %d, received %d",
+                    mEntries[i]->getTag(), actual, diff);
+        }
+    }
+
+    assert(out->getCurrentOffset() == offset + getSize());
+
+    return ret;
+}
+
+uint32_t TiffIfd::getSize() const {
+    size_t size = mEntries.size();
+    uint32_t total = calculateIfdSize(size);
+    WORD_ALIGN(total);
+    for (size_t i = 0; i < size; ++i) {
+        total += mEntries[i]->getSize();
+    }
+    return total;
+}
+
+uint32_t TiffIfd::getId() const {
+    return mIfdId;
+}
+
+uint32_t TiffIfd::getComparableValue() const {
+    return mIfdId;
+}
+
+String8 TiffIfd::toString() const {
+    size_t s = mEntries.size();
+    String8 output;
+    output.appendFormat("[ifd: %x, num_entries: %u, entries:\n", getId(), s);
+    for(size_t i = 0; i < mEntries.size(); ++i) {
+        output.append("\t");
+        output.append(mEntries[i]->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: %u, entries:\n", getId(), s);
+    for(size_t i = 0; i < s; ++i) {
+        ALOGI("\t%s", mEntries[i]->toString().string());
+    }
+    ALOGI(", next_ifd: %x]", ((mNextIfd != NULL) ? mNextIfd->getId() : 0));
+}
+
+} /*namespace img_utils*/
+} /*namespace android*/
diff --git a/media/img_utils/src/TiffWritable.cpp b/media/img_utils/src/TiffWritable.cpp
new file mode 100644
index 0000000..f8d7de7
--- /dev/null
+++ b/media/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 <img_utils/TiffWritable.h>
+#include <img_utils/TiffHelpers.h>
+
+#include <assert.h>
+
+namespace android {
+namespace img_utils {
+
+TiffWritable::TiffWritable() {}
+
+TiffWritable::~TiffWritable() {}
+
+} /*namespace img_utils*/
+} /*namespace android*/
diff --git a/media/img_utils/src/TiffWriter.cpp b/media/img_utils/src/TiffWriter.cpp
new file mode 100644
index 0000000..2439033
--- /dev/null
+++ b/media/img_utils/src/TiffWriter.cpp
@@ -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.
+ */
+
+#include <img_utils/TiffIfd.h>
+#include <img_utils/TiffHelpers.h>
+#include <img_utils/TiffWriter.h>
+#include <img_utils/TagDefinitions.h>
+
+#include <assert.h>
+
+namespace android {
+namespace img_utils {
+
+KeyedVector<uint16_t, const TagDefinition_t*> TiffWriter::buildTagMap(
+            const TagDefinition_t* definitions, size_t length) {
+    KeyedVector<uint16_t, const TagDefinition_t*> map;
+    for(size_t i = 0; i < length; ++i) {
+        map.add(definitions[i].tagId, definitions + i);
+    }
+    return map;
+}
+
+#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]))
+
+KeyedVector<uint16_t, const TagDefinition_t*> 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))
+};
+
+TiffWriter::TiffWriter() : mTagMaps(sTagMaps), mNumTagMaps(DEFAULT_NUM_TAG_MAPS) {}
+
+TiffWriter::TiffWriter(KeyedVector<uint16_t, const TagDefinition_t*>* enabledDefinitions,
+        size_t length) : mTagMaps(enabledDefinitions), mNumTagMaps(length) {}
+
+TiffWriter::~TiffWriter() {}
+
+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<TiffIfd> 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) {
+        ssize_t index = mTagMaps[i].indexOfKey(tag);
+        if (index >= 0) {
+            definition = mTagMaps[i][index];
+            break;
+        }
+    }
+
+    if (definition == NULL) {
+        ALOGE("%s: No definition exists for tag with id %x.", __FUNCTION__, tag);
+    }
+    return definition;
+}
+
+sp<TiffEntry> TiffWriter::getEntry(uint16_t tag, uint32_t ifd) const {
+    ssize_t index = mNamedIfds.indexOfKey(ifd);
+    if (index < 0) {
+        ALOGE("%s: No IFD %d set for this writer.", __FUNCTION__, ifd);
+        return NULL;
+    }
+    return mNamedIfds[index]->getEntry(tag);
+}
+
+
+// TODO: Fix this to handle IFD position in chain/sub-IFD tree
+status_t TiffWriter::addEntry(const sp<TiffEntry>& entry) {
+    uint16_t tag = entry->getTag();
+
+    const TagDefinition_t* definition = lookupDefinition(tag);
+
+    if (definition == NULL) {
+        return BAD_INDEX;
+    }
+    uint32_t ifdId = 0;  // TODO: all in IFD0 for now.
+
+    ssize_t index = mNamedIfds.indexOfKey(ifdId);
+
+    // Add a new IFD if necessary
+    if (index < 0) {
+        sp<TiffIfd> ifdEntry = new TiffIfd(ifdId);
+        if (mIfd == NULL) {
+            mIfd = ifdEntry;
+        }
+        index = mNamedIfds.add(ifdId, ifdEntry);
+        assert(index >= 0);
+    }
+
+    sp<TiffIfd> selectedIfd  = mNamedIfds[index];
+    return selectedIfd->addEntry(entry);
+}
+
+status_t TiffWriter::uncheckedAddIfd(const sp<TiffIfd>& ifd) {
+    mNamedIfds.add(ifd->getId(), ifd);
+    sp<TiffIfd> last = findLastIfd();
+    if (last == NULL) {
+        mIfd = ifd;
+    } else {
+        last->setNextIfd(ifd);
+    }
+    last = ifd->getNextIfd();
+    while (last != NULL) {
+        mNamedIfds.add(last->getId(), last);
+        last = last->getNextIfd();
+    }
+    return OK;
+}
+
+status_t TiffWriter::addIfd(uint32_t ifd) {
+    ssize_t index = mNamedIfds.indexOfKey(ifd);
+    if (index >= 0) {
+        ALOGE("%s: Ifd with ID 0x%x already exists.", __FUNCTION__, ifd);
+        return BAD_VALUE;
+    }
+    sp<TiffIfd> newIfd = new TiffIfd(ifd);
+    if (mIfd == NULL) {
+        mIfd = newIfd;
+    } else {
+        sp<TiffIfd> last = findLastIfd();
+        last->setNextIfd(newIfd);
+    }
+    mNamedIfds.add(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::checkIfDefined(uint16_t tag) const {
+    return lookupDefinition(tag) != NULL;
+}
+
+sp<TiffIfd> TiffWriter::findLastIfd() {
+    sp<TiffIfd> ifd = mIfd;
+    while(ifd != NULL) {
+        sp<TiffIfd> nextIfd = ifd->getNextIfd();
+        if (nextIfd == NULL) {
+            break;
+        }
+        ifd = 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<TiffIfd> ifd = mIfd;
+    while(ifd != NULL) {
+        totalSize += ifd->getSize();
+        ifd = ifd->getNextIfd();
+    }
+    return totalSize;
+}
+
+void TiffWriter::log() const {
+    ALOGI("%s: TiffWriter:", __FUNCTION__);
+    sp<TiffIfd> ifd = mIfd;
+    while(ifd != NULL) {
+        ifd->log();
+        ifd = ifd->getNextIfd();
+    }
+}
+
+} /*namespace img_utils*/
+} /*namespace android*/