/*
 * Copyright (C) 2012 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.
 */

package com.android.gallery3d.exif;

import android.util.Log;

import java.io.UnsupportedEncodingException;
import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

This class stores the EXIF header in IFDs according to the JPEG specification. It is the result produced by ExifReader.
See Also:
/** * This class stores the EXIF header in IFDs according to the JPEG * specification. It is the result produced by {@link ExifReader}. * * @see ExifReader * @see IfdData */
class ExifData { private static final String TAG = "ExifData"; private static final byte[] USER_COMMENT_ASCII = { 0x41, 0x53, 0x43, 0x49, 0x49, 0x00, 0x00, 0x00 }; private static final byte[] USER_COMMENT_JIS = { 0x4A, 0x49, 0x53, 0x00, 0x00, 0x00, 0x00, 0x00 }; private static final byte[] USER_COMMENT_UNICODE = { 0x55, 0x4E, 0x49, 0x43, 0x4F, 0x44, 0x45, 0x00 }; private final IfdData[] mIfdDatas = new IfdData[IfdId.TYPE_IFD_COUNT]; private byte[] mThumbnail; private ArrayList<byte[]> mStripBytes = new ArrayList<byte[]>(); private final ByteOrder mByteOrder; ExifData(ByteOrder order) { mByteOrder = order; }
Gets the compressed thumbnail. Returns null if there is no compressed thumbnail.
See Also:
  • hasCompressedThumbnail()
/** * Gets the compressed thumbnail. Returns null if there is no compressed * thumbnail. * * @see #hasCompressedThumbnail() */
protected byte[] getCompressedThumbnail() { return mThumbnail; }
Sets the compressed thumbnail.
/** * Sets the compressed thumbnail. */
protected void setCompressedThumbnail(byte[] thumbnail) { mThumbnail = thumbnail; }
Returns true it this header contains a compressed thumbnail.
/** * Returns true it this header contains a compressed thumbnail. */
protected boolean hasCompressedThumbnail() { return mThumbnail != null; }
Adds an uncompressed strip.
/** * Adds an uncompressed strip. */
protected void setStripBytes(int index, byte[] strip) { if (index < mStripBytes.size()) { mStripBytes.set(index, strip); } else { for (int i = mStripBytes.size(); i < index; i++) { mStripBytes.add(null); } mStripBytes.add(strip); } }
Gets the strip count.
/** * Gets the strip count. */
protected int getStripCount() { return mStripBytes.size(); }
Gets the strip at the specified index.
@exceptions#IndexOutOfBoundException
/** * Gets the strip at the specified index. * * @exceptions #IndexOutOfBoundException */
protected byte[] getStrip(int index) { return mStripBytes.get(index); }
Returns true if this header contains uncompressed strip.
/** * Returns true if this header contains uncompressed strip. */
protected boolean hasUncompressedStrip() { return mStripBytes.size() != 0; }
Gets the byte order.
/** * Gets the byte order. */
protected ByteOrder getByteOrder() { return mByteOrder; }
Returns the IfdData object corresponding to a given IFD if it exists or null.
/** * Returns the {@link IfdData} object corresponding to a given IFD if it * exists or null. */
protected IfdData getIfdData(int ifdId) { if (ExifTag.isValidIfd(ifdId)) { return mIfdDatas[ifdId]; } return null; }
Adds IFD data. If IFD data of the same type already exists, it will be replaced by the new data.
/** * Adds IFD data. If IFD data of the same type already exists, it will be * replaced by the new data. */
protected void addIfdData(IfdData data) { mIfdDatas[data.getId()] = data; }
Returns the IfdData object corresponding to a given IFD or generates one if none exist.
/** * Returns the {@link IfdData} object corresponding to a given IFD or * generates one if none exist. */
protected IfdData getOrCreateIfdData(int ifdId) { IfdData ifdData = mIfdDatas[ifdId]; if (ifdData == null) { ifdData = new IfdData(ifdId); mIfdDatas[ifdId] = ifdData; } return ifdData; }
Returns the tag with a given TID in the given IFD if the tag exists. Otherwise returns null.
/** * Returns the tag with a given TID in the given IFD if the tag exists. * Otherwise returns null. */
protected ExifTag getTag(short tag, int ifd) { IfdData ifdData = mIfdDatas[ifd]; return (ifdData == null) ? null : ifdData.getTag(tag); }
Adds the given ExifTag to its default IFD and returns an existing ExifTag with the same TID or null if none exist.
/** * Adds the given ExifTag to its default IFD and returns an existing ExifTag * with the same TID or null if none exist. */
protected ExifTag addTag(ExifTag tag) { if (tag != null) { int ifd = tag.getIfd(); return addTag(tag, ifd); } return null; }
Adds the given ExifTag to the given IFD and returns an existing ExifTag with the same TID or null if none exist.
/** * Adds the given ExifTag to the given IFD and returns an existing ExifTag * with the same TID or null if none exist. */
protected ExifTag addTag(ExifTag tag, int ifdId) { if (tag != null && ExifTag.isValidIfd(ifdId)) { IfdData ifdData = getOrCreateIfdData(ifdId); return ifdData.setTag(tag); } return null; } protected void clearThumbnailAndStrips() { mThumbnail = null; mStripBytes.clear(); }
Removes the thumbnail and its related tags. IFD1 will be removed.
/** * Removes the thumbnail and its related tags. IFD1 will be removed. */
protected void removeThumbnailData() { clearThumbnailAndStrips(); mIfdDatas[IfdId.TYPE_IFD_1] = null; }
Removes the tag with a given TID and IFD.
/** * Removes the tag with a given TID and IFD. */
protected void removeTag(short tagId, int ifdId) { IfdData ifdData = mIfdDatas[ifdId]; if (ifdData == null) { return; } ifdData.removeTag(tagId); }
Decodes the user comment tag into string as specified in the EXIF standard. Returns null if decoding failed.
/** * Decodes the user comment tag into string as specified in the EXIF * standard. Returns null if decoding failed. */
protected String getUserComment() { IfdData ifdData = mIfdDatas[IfdId.TYPE_IFD_0]; if (ifdData == null) { return null; } ExifTag tag = ifdData.getTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_USER_COMMENT)); if (tag == null) { return null; } if (tag.getComponentCount() < 8) { return null; } byte[] buf = new byte[tag.getComponentCount()]; tag.getBytes(buf); byte[] code = new byte[8]; System.arraycopy(buf, 0, code, 0, 8); try { if (Arrays.equals(code, USER_COMMENT_ASCII)) { return new String(buf, 8, buf.length - 8, "US-ASCII"); } else if (Arrays.equals(code, USER_COMMENT_JIS)) { return new String(buf, 8, buf.length - 8, "EUC-JP"); } else if (Arrays.equals(code, USER_COMMENT_UNICODE)) { return new String(buf, 8, buf.length - 8, "UTF-16"); } else { return null; } } catch (UnsupportedEncodingException e) { Log.w(TAG, "Failed to decode the user comment"); return null; } }
Returns a list of all ExifTags in the ExifData or null if there are none.
/** * Returns a list of all {@link ExifTag}s in the ExifData or null if there * are none. */
protected List<ExifTag> getAllTags() { ArrayList<ExifTag> ret = new ArrayList<ExifTag>(); for (IfdData d : mIfdDatas) { if (d != null) { ExifTag[] tags = d.getAllTags(); if (tags != null) { for (ExifTag t : tags) { ret.add(t); } } } } if (ret.size() == 0) { return null; } return ret; }
Returns a list of all ExifTags in a given IFD or null if there are none.
/** * Returns a list of all {@link ExifTag}s in a given IFD or null if there * are none. */
protected List<ExifTag> getAllTagsForIfd(int ifd) { IfdData d = mIfdDatas[ifd]; if (d == null) { return null; } ExifTag[] tags = d.getAllTags(); if (tags == null) { return null; } ArrayList<ExifTag> ret = new ArrayList<ExifTag>(tags.length); for (ExifTag t : tags) { ret.add(t); } if (ret.size() == 0) { return null; } return ret; }
Returns a list of all ExifTags with a given TID or null if there are none.
/** * Returns a list of all {@link ExifTag}s with a given TID or null if there * are none. */
protected List<ExifTag> getAllTagsForTagId(short tag) { ArrayList<ExifTag> ret = new ArrayList<ExifTag>(); for (IfdData d : mIfdDatas) { if (d != null) { ExifTag t = d.getTag(tag); if (t != null) { ret.add(t); } } } if (ret.size() == 0) { return null; } return ret; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (obj instanceof ExifData) { ExifData data = (ExifData) obj; if (data.mByteOrder != mByteOrder || data.mStripBytes.size() != mStripBytes.size() || !Arrays.equals(data.mThumbnail, mThumbnail)) { return false; } for (int i = 0; i < mStripBytes.size(); i++) { if (!Arrays.equals(data.mStripBytes.get(i), mStripBytes.get(i))) { return false; } } for (int i = 0; i < IfdId.TYPE_IFD_COUNT; i++) { IfdData ifd1 = data.getIfdData(i); IfdData ifd2 = getIfdData(i); if (ifd1 != ifd2 && ifd1 != null && !ifd1.equals(ifd2)) { return false; } } return true; } return false; } }