/packages/WallpaperCropper/src/com/android/gallery3d/exif/ExifOutputStream.java

https://github.com/aizuzi/platform_frameworks_base · Java · 518 lines · 404 code · 38 blank · 76 comment · 105 complexity · e2bdfcb74424e5f52bcf316caaa2755e MD5 · raw file

  1. /*
  2. * Copyright (C) 2012 The Android Open Source Project
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. package com.android.gallery3d.exif;
  17. import android.util.Log;
  18. import java.io.BufferedOutputStream;
  19. import java.io.FilterOutputStream;
  20. import java.io.IOException;
  21. import java.io.OutputStream;
  22. import java.nio.ByteBuffer;
  23. import java.nio.ByteOrder;
  24. import java.util.ArrayList;
  25. /**
  26. * This class provides a way to replace the Exif header of a JPEG image.
  27. * <p>
  28. * Below is an example of writing EXIF data into a file
  29. *
  30. * <pre>
  31. * public static void writeExif(byte[] jpeg, ExifData exif, String path) {
  32. * OutputStream os = null;
  33. * try {
  34. * os = new FileOutputStream(path);
  35. * ExifOutputStream eos = new ExifOutputStream(os);
  36. * // Set the exif header
  37. * eos.setExifData(exif);
  38. * // Write the original jpeg out, the header will be add into the file.
  39. * eos.write(jpeg);
  40. * } catch (FileNotFoundException e) {
  41. * e.printStackTrace();
  42. * } catch (IOException e) {
  43. * e.printStackTrace();
  44. * } finally {
  45. * if (os != null) {
  46. * try {
  47. * os.close();
  48. * } catch (IOException e) {
  49. * e.printStackTrace();
  50. * }
  51. * }
  52. * }
  53. * }
  54. * </pre>
  55. */
  56. class ExifOutputStream extends FilterOutputStream {
  57. private static final String TAG = "ExifOutputStream";
  58. private static final boolean DEBUG = false;
  59. private static final int STREAMBUFFER_SIZE = 0x00010000; // 64Kb
  60. private static final int STATE_SOI = 0;
  61. private static final int STATE_FRAME_HEADER = 1;
  62. private static final int STATE_JPEG_DATA = 2;
  63. private static final int EXIF_HEADER = 0x45786966;
  64. private static final short TIFF_HEADER = 0x002A;
  65. private static final short TIFF_BIG_ENDIAN = 0x4d4d;
  66. private static final short TIFF_LITTLE_ENDIAN = 0x4949;
  67. private static final short TAG_SIZE = 12;
  68. private static final short TIFF_HEADER_SIZE = 8;
  69. private static final int MAX_EXIF_SIZE = 65535;
  70. private ExifData mExifData;
  71. private int mState = STATE_SOI;
  72. private int mByteToSkip;
  73. private int mByteToCopy;
  74. private byte[] mSingleByteArray = new byte[1];
  75. private ByteBuffer mBuffer = ByteBuffer.allocate(4);
  76. private final ExifInterface mInterface;
  77. protected ExifOutputStream(OutputStream ou, ExifInterface iRef) {
  78. super(new BufferedOutputStream(ou, STREAMBUFFER_SIZE));
  79. mInterface = iRef;
  80. }
  81. /**
  82. * Sets the ExifData to be written into the JPEG file. Should be called
  83. * before writing image data.
  84. */
  85. protected void setExifData(ExifData exifData) {
  86. mExifData = exifData;
  87. }
  88. /**
  89. * Gets the Exif header to be written into the JPEF file.
  90. */
  91. protected ExifData getExifData() {
  92. return mExifData;
  93. }
  94. private int requestByteToBuffer(int requestByteCount, byte[] buffer
  95. , int offset, int length) {
  96. int byteNeeded = requestByteCount - mBuffer.position();
  97. int byteToRead = length > byteNeeded ? byteNeeded : length;
  98. mBuffer.put(buffer, offset, byteToRead);
  99. return byteToRead;
  100. }
  101. /**
  102. * Writes the image out. The input data should be a valid JPEG format. After
  103. * writing, it's Exif header will be replaced by the given header.
  104. */
  105. @Override
  106. public void write(byte[] buffer, int offset, int length) throws IOException {
  107. while ((mByteToSkip > 0 || mByteToCopy > 0 || mState != STATE_JPEG_DATA)
  108. && length > 0) {
  109. if (mByteToSkip > 0) {
  110. int byteToProcess = length > mByteToSkip ? mByteToSkip : length;
  111. length -= byteToProcess;
  112. mByteToSkip -= byteToProcess;
  113. offset += byteToProcess;
  114. }
  115. if (mByteToCopy > 0) {
  116. int byteToProcess = length > mByteToCopy ? mByteToCopy : length;
  117. out.write(buffer, offset, byteToProcess);
  118. length -= byteToProcess;
  119. mByteToCopy -= byteToProcess;
  120. offset += byteToProcess;
  121. }
  122. if (length == 0) {
  123. return;
  124. }
  125. switch (mState) {
  126. case STATE_SOI:
  127. int byteRead = requestByteToBuffer(2, buffer, offset, length);
  128. offset += byteRead;
  129. length -= byteRead;
  130. if (mBuffer.position() < 2) {
  131. return;
  132. }
  133. mBuffer.rewind();
  134. if (mBuffer.getShort() != JpegHeader.SOI) {
  135. throw new IOException("Not a valid jpeg image, cannot write exif");
  136. }
  137. out.write(mBuffer.array(), 0, 2);
  138. mState = STATE_FRAME_HEADER;
  139. mBuffer.rewind();
  140. writeExifData();
  141. break;
  142. case STATE_FRAME_HEADER:
  143. // We ignore the APP1 segment and copy all other segments
  144. // until SOF tag.
  145. byteRead = requestByteToBuffer(4, buffer, offset, length);
  146. offset += byteRead;
  147. length -= byteRead;
  148. // Check if this image data doesn't contain SOF.
  149. if (mBuffer.position() == 2) {
  150. short tag = mBuffer.getShort();
  151. if (tag == JpegHeader.EOI) {
  152. out.write(mBuffer.array(), 0, 2);
  153. mBuffer.rewind();
  154. }
  155. }
  156. if (mBuffer.position() < 4) {
  157. return;
  158. }
  159. mBuffer.rewind();
  160. short marker = mBuffer.getShort();
  161. if (marker == JpegHeader.APP1) {
  162. mByteToSkip = (mBuffer.getShort() & 0x0000ffff) - 2;
  163. mState = STATE_JPEG_DATA;
  164. } else if (!JpegHeader.isSofMarker(marker)) {
  165. out.write(mBuffer.array(), 0, 4);
  166. mByteToCopy = (mBuffer.getShort() & 0x0000ffff) - 2;
  167. } else {
  168. out.write(mBuffer.array(), 0, 4);
  169. mState = STATE_JPEG_DATA;
  170. }
  171. mBuffer.rewind();
  172. }
  173. }
  174. if (length > 0) {
  175. out.write(buffer, offset, length);
  176. }
  177. }
  178. /**
  179. * Writes the one bytes out. The input data should be a valid JPEG format.
  180. * After writing, it's Exif header will be replaced by the given header.
  181. */
  182. @Override
  183. public void write(int oneByte) throws IOException {
  184. mSingleByteArray[0] = (byte) (0xff & oneByte);
  185. write(mSingleByteArray);
  186. }
  187. /**
  188. * Equivalent to calling write(buffer, 0, buffer.length).
  189. */
  190. @Override
  191. public void write(byte[] buffer) throws IOException {
  192. write(buffer, 0, buffer.length);
  193. }
  194. private void writeExifData() throws IOException {
  195. if (mExifData == null) {
  196. return;
  197. }
  198. if (DEBUG) {
  199. Log.v(TAG, "Writing exif data...");
  200. }
  201. ArrayList<ExifTag> nullTags = stripNullValueTags(mExifData);
  202. createRequiredIfdAndTag();
  203. int exifSize = calculateAllOffset();
  204. if (exifSize + 8 > MAX_EXIF_SIZE) {
  205. throw new IOException("Exif header is too large (>64Kb)");
  206. }
  207. OrderedDataOutputStream dataOutputStream = new OrderedDataOutputStream(out);
  208. dataOutputStream.setByteOrder(ByteOrder.BIG_ENDIAN);
  209. dataOutputStream.writeShort(JpegHeader.APP1);
  210. dataOutputStream.writeShort((short) (exifSize + 8));
  211. dataOutputStream.writeInt(EXIF_HEADER);
  212. dataOutputStream.writeShort((short) 0x0000);
  213. if (mExifData.getByteOrder() == ByteOrder.BIG_ENDIAN) {
  214. dataOutputStream.writeShort(TIFF_BIG_ENDIAN);
  215. } else {
  216. dataOutputStream.writeShort(TIFF_LITTLE_ENDIAN);
  217. }
  218. dataOutputStream.setByteOrder(mExifData.getByteOrder());
  219. dataOutputStream.writeShort(TIFF_HEADER);
  220. dataOutputStream.writeInt(8);
  221. writeAllTags(dataOutputStream);
  222. writeThumbnail(dataOutputStream);
  223. for (ExifTag t : nullTags) {
  224. mExifData.addTag(t);
  225. }
  226. }
  227. private ArrayList<ExifTag> stripNullValueTags(ExifData data) {
  228. ArrayList<ExifTag> nullTags = new ArrayList<ExifTag>();
  229. for(ExifTag t : data.getAllTags()) {
  230. if (t.getValue() == null && !ExifInterface.isOffsetTag(t.getTagId())) {
  231. data.removeTag(t.getTagId(), t.getIfd());
  232. nullTags.add(t);
  233. }
  234. }
  235. return nullTags;
  236. }
  237. private void writeThumbnail(OrderedDataOutputStream dataOutputStream) throws IOException {
  238. if (mExifData.hasCompressedThumbnail()) {
  239. dataOutputStream.write(mExifData.getCompressedThumbnail());
  240. } else if (mExifData.hasUncompressedStrip()) {
  241. for (int i = 0; i < mExifData.getStripCount(); i++) {
  242. dataOutputStream.write(mExifData.getStrip(i));
  243. }
  244. }
  245. }
  246. private void writeAllTags(OrderedDataOutputStream dataOutputStream) throws IOException {
  247. writeIfd(mExifData.getIfdData(IfdId.TYPE_IFD_0), dataOutputStream);
  248. writeIfd(mExifData.getIfdData(IfdId.TYPE_IFD_EXIF), dataOutputStream);
  249. IfdData interoperabilityIfd = mExifData.getIfdData(IfdId.TYPE_IFD_INTEROPERABILITY);
  250. if (interoperabilityIfd != null) {
  251. writeIfd(interoperabilityIfd, dataOutputStream);
  252. }
  253. IfdData gpsIfd = mExifData.getIfdData(IfdId.TYPE_IFD_GPS);
  254. if (gpsIfd != null) {
  255. writeIfd(gpsIfd, dataOutputStream);
  256. }
  257. IfdData ifd1 = mExifData.getIfdData(IfdId.TYPE_IFD_1);
  258. if (ifd1 != null) {
  259. writeIfd(mExifData.getIfdData(IfdId.TYPE_IFD_1), dataOutputStream);
  260. }
  261. }
  262. private void writeIfd(IfdData ifd, OrderedDataOutputStream dataOutputStream)
  263. throws IOException {
  264. ExifTag[] tags = ifd.getAllTags();
  265. dataOutputStream.writeShort((short) tags.length);
  266. for (ExifTag tag : tags) {
  267. dataOutputStream.writeShort(tag.getTagId());
  268. dataOutputStream.writeShort(tag.getDataType());
  269. dataOutputStream.writeInt(tag.getComponentCount());
  270. if (DEBUG) {
  271. Log.v(TAG, "\n" + tag.toString());
  272. }
  273. if (tag.getDataSize() > 4) {
  274. dataOutputStream.writeInt(tag.getOffset());
  275. } else {
  276. ExifOutputStream.writeTagValue(tag, dataOutputStream);
  277. for (int i = 0, n = 4 - tag.getDataSize(); i < n; i++) {
  278. dataOutputStream.write(0);
  279. }
  280. }
  281. }
  282. dataOutputStream.writeInt(ifd.getOffsetToNextIfd());
  283. for (ExifTag tag : tags) {
  284. if (tag.getDataSize() > 4) {
  285. ExifOutputStream.writeTagValue(tag, dataOutputStream);
  286. }
  287. }
  288. }
  289. private int calculateOffsetOfIfd(IfdData ifd, int offset) {
  290. offset += 2 + ifd.getTagCount() * TAG_SIZE + 4;
  291. ExifTag[] tags = ifd.getAllTags();
  292. for (ExifTag tag : tags) {
  293. if (tag.getDataSize() > 4) {
  294. tag.setOffset(offset);
  295. offset += tag.getDataSize();
  296. }
  297. }
  298. return offset;
  299. }
  300. private void createRequiredIfdAndTag() throws IOException {
  301. // IFD0 is required for all file
  302. IfdData ifd0 = mExifData.getIfdData(IfdId.TYPE_IFD_0);
  303. if (ifd0 == null) {
  304. ifd0 = new IfdData(IfdId.TYPE_IFD_0);
  305. mExifData.addIfdData(ifd0);
  306. }
  307. ExifTag exifOffsetTag = mInterface.buildUninitializedTag(ExifInterface.TAG_EXIF_IFD);
  308. if (exifOffsetTag == null) {
  309. throw new IOException("No definition for crucial exif tag: "
  310. + ExifInterface.TAG_EXIF_IFD);
  311. }
  312. ifd0.setTag(exifOffsetTag);
  313. // Exif IFD is required for all files.
  314. IfdData exifIfd = mExifData.getIfdData(IfdId.TYPE_IFD_EXIF);
  315. if (exifIfd == null) {
  316. exifIfd = new IfdData(IfdId.TYPE_IFD_EXIF);
  317. mExifData.addIfdData(exifIfd);
  318. }
  319. // GPS IFD
  320. IfdData gpsIfd = mExifData.getIfdData(IfdId.TYPE_IFD_GPS);
  321. if (gpsIfd != null) {
  322. ExifTag gpsOffsetTag = mInterface.buildUninitializedTag(ExifInterface.TAG_GPS_IFD);
  323. if (gpsOffsetTag == null) {
  324. throw new IOException("No definition for crucial exif tag: "
  325. + ExifInterface.TAG_GPS_IFD);
  326. }
  327. ifd0.setTag(gpsOffsetTag);
  328. }
  329. // Interoperability IFD
  330. IfdData interIfd = mExifData.getIfdData(IfdId.TYPE_IFD_INTEROPERABILITY);
  331. if (interIfd != null) {
  332. ExifTag interOffsetTag = mInterface
  333. .buildUninitializedTag(ExifInterface.TAG_INTEROPERABILITY_IFD);
  334. if (interOffsetTag == null) {
  335. throw new IOException("No definition for crucial exif tag: "
  336. + ExifInterface.TAG_INTEROPERABILITY_IFD);
  337. }
  338. exifIfd.setTag(interOffsetTag);
  339. }
  340. IfdData ifd1 = mExifData.getIfdData(IfdId.TYPE_IFD_1);
  341. // thumbnail
  342. if (mExifData.hasCompressedThumbnail()) {
  343. if (ifd1 == null) {
  344. ifd1 = new IfdData(IfdId.TYPE_IFD_1);
  345. mExifData.addIfdData(ifd1);
  346. }
  347. ExifTag offsetTag = mInterface
  348. .buildUninitializedTag(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT);
  349. if (offsetTag == null) {
  350. throw new IOException("No definition for crucial exif tag: "
  351. + ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT);
  352. }
  353. ifd1.setTag(offsetTag);
  354. ExifTag lengthTag = mInterface
  355. .buildUninitializedTag(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH);
  356. if (lengthTag == null) {
  357. throw new IOException("No definition for crucial exif tag: "
  358. + ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH);
  359. }
  360. lengthTag.setValue(mExifData.getCompressedThumbnail().length);
  361. ifd1.setTag(lengthTag);
  362. // Get rid of tags for uncompressed if they exist.
  363. ifd1.removeTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_STRIP_OFFSETS));
  364. ifd1.removeTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_STRIP_BYTE_COUNTS));
  365. } else if (mExifData.hasUncompressedStrip()) {
  366. if (ifd1 == null) {
  367. ifd1 = new IfdData(IfdId.TYPE_IFD_1);
  368. mExifData.addIfdData(ifd1);
  369. }
  370. int stripCount = mExifData.getStripCount();
  371. ExifTag offsetTag = mInterface.buildUninitializedTag(ExifInterface.TAG_STRIP_OFFSETS);
  372. if (offsetTag == null) {
  373. throw new IOException("No definition for crucial exif tag: "
  374. + ExifInterface.TAG_STRIP_OFFSETS);
  375. }
  376. ExifTag lengthTag = mInterface
  377. .buildUninitializedTag(ExifInterface.TAG_STRIP_BYTE_COUNTS);
  378. if (lengthTag == null) {
  379. throw new IOException("No definition for crucial exif tag: "
  380. + ExifInterface.TAG_STRIP_BYTE_COUNTS);
  381. }
  382. long[] lengths = new long[stripCount];
  383. for (int i = 0; i < mExifData.getStripCount(); i++) {
  384. lengths[i] = mExifData.getStrip(i).length;
  385. }
  386. lengthTag.setValue(lengths);
  387. ifd1.setTag(offsetTag);
  388. ifd1.setTag(lengthTag);
  389. // Get rid of tags for compressed if they exist.
  390. ifd1.removeTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT));
  391. ifd1.removeTag(ExifInterface
  392. .getTrueTagKey(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH));
  393. } else if (ifd1 != null) {
  394. // Get rid of offset and length tags if there is no thumbnail.
  395. ifd1.removeTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_STRIP_OFFSETS));
  396. ifd1.removeTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_STRIP_BYTE_COUNTS));
  397. ifd1.removeTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT));
  398. ifd1.removeTag(ExifInterface
  399. .getTrueTagKey(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH));
  400. }
  401. }
  402. private int calculateAllOffset() {
  403. int offset = TIFF_HEADER_SIZE;
  404. IfdData ifd0 = mExifData.getIfdData(IfdId.TYPE_IFD_0);
  405. offset = calculateOffsetOfIfd(ifd0, offset);
  406. ifd0.getTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_EXIF_IFD)).setValue(offset);
  407. IfdData exifIfd = mExifData.getIfdData(IfdId.TYPE_IFD_EXIF);
  408. offset = calculateOffsetOfIfd(exifIfd, offset);
  409. IfdData interIfd = mExifData.getIfdData(IfdId.TYPE_IFD_INTEROPERABILITY);
  410. if (interIfd != null) {
  411. exifIfd.getTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_INTEROPERABILITY_IFD))
  412. .setValue(offset);
  413. offset = calculateOffsetOfIfd(interIfd, offset);
  414. }
  415. IfdData gpsIfd = mExifData.getIfdData(IfdId.TYPE_IFD_GPS);
  416. if (gpsIfd != null) {
  417. ifd0.getTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_GPS_IFD)).setValue(offset);
  418. offset = calculateOffsetOfIfd(gpsIfd, offset);
  419. }
  420. IfdData ifd1 = mExifData.getIfdData(IfdId.TYPE_IFD_1);
  421. if (ifd1 != null) {
  422. ifd0.setOffsetToNextIfd(offset);
  423. offset = calculateOffsetOfIfd(ifd1, offset);
  424. }
  425. // thumbnail
  426. if (mExifData.hasCompressedThumbnail()) {
  427. ifd1.getTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT))
  428. .setValue(offset);
  429. offset += mExifData.getCompressedThumbnail().length;
  430. } else if (mExifData.hasUncompressedStrip()) {
  431. int stripCount = mExifData.getStripCount();
  432. long[] offsets = new long[stripCount];
  433. for (int i = 0; i < mExifData.getStripCount(); i++) {
  434. offsets[i] = offset;
  435. offset += mExifData.getStrip(i).length;
  436. }
  437. ifd1.getTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_STRIP_OFFSETS)).setValue(
  438. offsets);
  439. }
  440. return offset;
  441. }
  442. static void writeTagValue(ExifTag tag, OrderedDataOutputStream dataOutputStream)
  443. throws IOException {
  444. switch (tag.getDataType()) {
  445. case ExifTag.TYPE_ASCII:
  446. byte buf[] = tag.getStringByte();
  447. if (buf.length == tag.getComponentCount()) {
  448. buf[buf.length - 1] = 0;
  449. dataOutputStream.write(buf);
  450. } else {
  451. dataOutputStream.write(buf);
  452. dataOutputStream.write(0);
  453. }
  454. break;
  455. case ExifTag.TYPE_LONG:
  456. case ExifTag.TYPE_UNSIGNED_LONG:
  457. for (int i = 0, n = tag.getComponentCount(); i < n; i++) {
  458. dataOutputStream.writeInt((int) tag.getValueAt(i));
  459. }
  460. break;
  461. case ExifTag.TYPE_RATIONAL:
  462. case ExifTag.TYPE_UNSIGNED_RATIONAL:
  463. for (int i = 0, n = tag.getComponentCount(); i < n; i++) {
  464. dataOutputStream.writeRational(tag.getRational(i));
  465. }
  466. break;
  467. case ExifTag.TYPE_UNDEFINED:
  468. case ExifTag.TYPE_UNSIGNED_BYTE:
  469. buf = new byte[tag.getComponentCount()];
  470. tag.getBytes(buf);
  471. dataOutputStream.write(buf);
  472. break;
  473. case ExifTag.TYPE_UNSIGNED_SHORT:
  474. for (int i = 0, n = tag.getComponentCount(); i < n; i++) {
  475. dataOutputStream.writeShort((short) tag.getValueAt(i));
  476. }
  477. break;
  478. }
  479. }
  480. }