Ruben Brunk | e507721 | 2014-04-28 16:39:12 -0700 | [diff] [blame] | 1 | /* |
| 2 | * Copyright 2014 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 | |
Ruben Brunk | 4510de2 | 2014-05-28 18:42:37 -0700 | [diff] [blame^] | 17 | #define LOG_TAG "TiffIfd" |
| 18 | |
| 19 | #include <img_utils/TagDefinitions.h> |
Ruben Brunk | e507721 | 2014-04-28 16:39:12 -0700 | [diff] [blame] | 20 | #include <img_utils/TiffHelpers.h> |
Ruben Brunk | 4510de2 | 2014-05-28 18:42:37 -0700 | [diff] [blame^] | 21 | #include <img_utils/TiffIfd.h> |
| 22 | #include <img_utils/TiffWriter.h> |
Ruben Brunk | e507721 | 2014-04-28 16:39:12 -0700 | [diff] [blame] | 23 | |
| 24 | #include <utils/Log.h> |
| 25 | |
| 26 | namespace android { |
| 27 | namespace img_utils { |
| 28 | |
| 29 | TiffIfd::TiffIfd(uint32_t ifdId) |
Ruben Brunk | 4510de2 | 2014-05-28 18:42:37 -0700 | [diff] [blame^] | 30 | : mNextIfd(), mIfdId(ifdId), mStripOffsetsInitialized(false) {} |
Ruben Brunk | e507721 | 2014-04-28 16:39:12 -0700 | [diff] [blame] | 31 | |
| 32 | TiffIfd::~TiffIfd() {} |
| 33 | |
| 34 | status_t TiffIfd::addEntry(const sp<TiffEntry>& entry) { |
| 35 | size_t size = mEntries.size(); |
| 36 | if (size >= MAX_IFD_ENTRIES) { |
Ruben Brunk | 272b7f2 | 2014-05-17 01:09:04 -0700 | [diff] [blame] | 37 | ALOGW("%s: Failed to add entry for tag 0x%x to IFD %u, too many entries in IFD!", |
Ruben Brunk | e507721 | 2014-04-28 16:39:12 -0700 | [diff] [blame] | 38 | __FUNCTION__, entry->getTag(), mIfdId); |
| 39 | return BAD_INDEX; |
| 40 | } |
| 41 | |
| 42 | if (mEntries.add(entry) < 0) { |
Ruben Brunk | 272b7f2 | 2014-05-17 01:09:04 -0700 | [diff] [blame] | 43 | ALOGW("%s: Failed to add entry for tag 0x%x to ifd %u.", __FUNCTION__, entry->getTag(), |
Ruben Brunk | e507721 | 2014-04-28 16:39:12 -0700 | [diff] [blame] | 44 | mIfdId); |
| 45 | return BAD_INDEX; |
| 46 | } |
| 47 | return OK; |
| 48 | } |
| 49 | |
| 50 | sp<TiffEntry> TiffIfd::getEntry(uint16_t tag) const { |
| 51 | ssize_t index = mEntries.indexOfTag(tag); |
| 52 | if (index < 0) { |
Ruben Brunk | 272b7f2 | 2014-05-17 01:09:04 -0700 | [diff] [blame] | 53 | ALOGW("%s: No entry for tag 0x%x in ifd %u.", __FUNCTION__, tag, mIfdId); |
Ruben Brunk | e507721 | 2014-04-28 16:39:12 -0700 | [diff] [blame] | 54 | return NULL; |
| 55 | } |
| 56 | return mEntries[index]; |
| 57 | } |
| 58 | |
Ruben Brunk | 4510de2 | 2014-05-28 18:42:37 -0700 | [diff] [blame^] | 59 | void TiffIfd::removeEntry(uint16_t tag) { |
| 60 | ssize_t index = mEntries.indexOfTag(tag); |
| 61 | if (index >= 0) { |
| 62 | mEntries.removeAt(index); |
| 63 | } |
| 64 | } |
| 65 | |
| 66 | |
Ruben Brunk | e507721 | 2014-04-28 16:39:12 -0700 | [diff] [blame] | 67 | void TiffIfd::setNextIfd(const sp<TiffIfd>& ifd) { |
| 68 | mNextIfd = ifd; |
| 69 | } |
| 70 | |
| 71 | sp<TiffIfd> TiffIfd::getNextIfd() const { |
| 72 | return mNextIfd; |
| 73 | } |
| 74 | |
| 75 | uint32_t TiffIfd::checkAndGetOffset(uint32_t offset) const { |
| 76 | size_t size = mEntries.size(); |
| 77 | |
| 78 | if (size > MAX_IFD_ENTRIES) { |
Ruben Brunk | 272b7f2 | 2014-05-17 01:09:04 -0700 | [diff] [blame] | 79 | ALOGW("%s: Could not calculate IFD offsets, IFD %u contains too many entries.", |
Ruben Brunk | e507721 | 2014-04-28 16:39:12 -0700 | [diff] [blame] | 80 | __FUNCTION__, mIfdId); |
| 81 | return BAD_OFFSET; |
| 82 | } |
| 83 | |
| 84 | if (size <= 0) { |
Ruben Brunk | 272b7f2 | 2014-05-17 01:09:04 -0700 | [diff] [blame] | 85 | ALOGW("%s: Could not calculate IFD offsets, IFD %u contains no entries.", __FUNCTION__, |
Ruben Brunk | e507721 | 2014-04-28 16:39:12 -0700 | [diff] [blame] | 86 | mIfdId); |
| 87 | return BAD_OFFSET; |
| 88 | } |
| 89 | |
| 90 | if (offset == BAD_OFFSET) { |
Ruben Brunk | 272b7f2 | 2014-05-17 01:09:04 -0700 | [diff] [blame] | 91 | ALOGW("%s: Could not calculate IFD offsets, IFD %u had a bad initial offset.", |
Ruben Brunk | e507721 | 2014-04-28 16:39:12 -0700 | [diff] [blame] | 92 | __FUNCTION__, mIfdId); |
| 93 | return BAD_OFFSET; |
| 94 | } |
| 95 | |
| 96 | uint32_t ifdSize = calculateIfdSize(size); |
| 97 | WORD_ALIGN(ifdSize); |
| 98 | return offset + ifdSize; |
| 99 | } |
| 100 | |
| 101 | status_t TiffIfd::writeData(uint32_t offset, /*out*/EndianOutput* out) const { |
| 102 | assert((offset % TIFF_WORD_SIZE) == 0); |
| 103 | status_t ret = OK; |
| 104 | |
| 105 | ALOGV("%s: IFD %u written to offset %u", __FUNCTION__, mIfdId, offset ); |
| 106 | uint32_t valueOffset = checkAndGetOffset(offset); |
| 107 | if (valueOffset == 0) { |
| 108 | return BAD_VALUE; |
| 109 | } |
| 110 | |
| 111 | size_t size = mEntries.size(); |
| 112 | |
| 113 | // Writer IFD header (2 bytes, number of entries). |
| 114 | uint16_t header = static_cast<uint16_t>(size); |
| 115 | BAIL_ON_FAIL(out->write(&header, 0, 1), ret); |
| 116 | |
| 117 | // Write tag entries |
| 118 | for (size_t i = 0; i < size; ++i) { |
| 119 | BAIL_ON_FAIL(mEntries[i]->writeTagInfo(valueOffset, out), ret); |
| 120 | valueOffset += mEntries[i]->getSize(); |
| 121 | } |
| 122 | |
| 123 | // Writer IFD footer (4 bytes, offset to next IFD). |
| 124 | uint32_t footer = (mNextIfd != NULL) ? offset + getSize() : 0; |
| 125 | BAIL_ON_FAIL(out->write(&footer, 0, 1), ret); |
| 126 | |
| 127 | assert(out->getCurrentOffset() == offset + calculateIfdSize(size)); |
| 128 | |
| 129 | // Write zeroes till word aligned |
| 130 | ZERO_TILL_WORD(out, calculateIfdSize(size), ret); |
| 131 | |
| 132 | // Write values for each tag entry |
| 133 | for (size_t i = 0; i < size; ++i) { |
| 134 | size_t last = out->getCurrentOffset(); |
| 135 | // Only write values that are too large to fit in the 12-byte TIFF entry |
| 136 | if (mEntries[i]->getSize() > OFFSET_SIZE) { |
| 137 | BAIL_ON_FAIL(mEntries[i]->writeData(out->getCurrentOffset(), out), ret); |
| 138 | } |
| 139 | size_t next = out->getCurrentOffset(); |
| 140 | size_t diff = (next - last); |
| 141 | size_t actual = mEntries[i]->getSize(); |
| 142 | if (diff != actual) { |
Ruben Brunk | 272b7f2 | 2014-05-17 01:09:04 -0700 | [diff] [blame] | 143 | ALOGW("Sizes do not match for tag %x. Expected %zu, received %zu", |
Ruben Brunk | e507721 | 2014-04-28 16:39:12 -0700 | [diff] [blame] | 144 | mEntries[i]->getTag(), actual, diff); |
| 145 | } |
| 146 | } |
| 147 | |
| 148 | assert(out->getCurrentOffset() == offset + getSize()); |
| 149 | |
| 150 | return ret; |
| 151 | } |
| 152 | |
Ruben Brunk | 272b7f2 | 2014-05-17 01:09:04 -0700 | [diff] [blame] | 153 | size_t TiffIfd::getSize() const { |
Ruben Brunk | e507721 | 2014-04-28 16:39:12 -0700 | [diff] [blame] | 154 | size_t size = mEntries.size(); |
| 155 | uint32_t total = calculateIfdSize(size); |
| 156 | WORD_ALIGN(total); |
| 157 | for (size_t i = 0; i < size; ++i) { |
| 158 | total += mEntries[i]->getSize(); |
| 159 | } |
| 160 | return total; |
| 161 | } |
| 162 | |
| 163 | uint32_t TiffIfd::getId() const { |
| 164 | return mIfdId; |
| 165 | } |
| 166 | |
| 167 | uint32_t TiffIfd::getComparableValue() const { |
| 168 | return mIfdId; |
| 169 | } |
| 170 | |
Ruben Brunk | 4510de2 | 2014-05-28 18:42:37 -0700 | [diff] [blame^] | 171 | status_t TiffIfd::validateAndSetStripTags() { |
| 172 | sp<TiffEntry> widthEntry = getEntry(TAG_IMAGEWIDTH); |
| 173 | if (widthEntry == NULL) { |
| 174 | ALOGE("%s: IFD %u doesn't have a ImageWidth tag set", __FUNCTION__, mIfdId); |
| 175 | return BAD_VALUE; |
| 176 | } |
| 177 | |
| 178 | sp<TiffEntry> heightEntry = getEntry(TAG_IMAGELENGTH); |
| 179 | if (heightEntry == NULL) { |
| 180 | ALOGE("%s: IFD %u doesn't have a ImageLength tag set", __FUNCTION__, mIfdId); |
| 181 | return BAD_VALUE; |
| 182 | } |
| 183 | |
| 184 | sp<TiffEntry> samplesEntry = getEntry(TAG_SAMPLESPERPIXEL); |
| 185 | if (samplesEntry == NULL) { |
| 186 | ALOGE("%s: IFD %u doesn't have a SamplesPerPixel tag set", __FUNCTION__, mIfdId); |
| 187 | return BAD_VALUE; |
| 188 | } |
| 189 | |
| 190 | sp<TiffEntry> bitsEntry = getEntry(TAG_BITSPERSAMPLE); |
| 191 | if (bitsEntry == NULL) { |
| 192 | ALOGE("%s: IFD %u doesn't have a BitsPerSample tag set", __FUNCTION__, mIfdId); |
| 193 | return BAD_VALUE; |
| 194 | } |
| 195 | |
| 196 | uint32_t width = *(widthEntry->getData<uint32_t>()); |
| 197 | uint32_t height = *(heightEntry->getData<uint32_t>()); |
| 198 | uint16_t bitsPerSample = *(bitsEntry->getData<uint16_t>()); |
| 199 | uint16_t samplesPerPixel = *(samplesEntry->getData<uint16_t>()); |
| 200 | |
| 201 | if ((bitsPerSample % 8) != 0) { |
| 202 | ALOGE("%s: BitsPerSample %d in IFD %u is not byte-aligned.", __FUNCTION__, |
| 203 | bitsPerSample, mIfdId); |
| 204 | return BAD_VALUE; |
| 205 | } |
| 206 | |
| 207 | uint32_t bytesPerSample = bitsPerSample / 8; |
| 208 | |
| 209 | // Choose strip size as close to 8kb as possible without splitting rows. |
| 210 | // If the row length is >8kb, each strip will only contain a single row. |
| 211 | const uint32_t rowLengthBytes = bytesPerSample * samplesPerPixel * width; |
| 212 | const uint32_t idealChunkSize = (1 << 13); // 8kb |
| 213 | uint32_t rowsPerChunk = idealChunkSize / rowLengthBytes; |
| 214 | rowsPerChunk = (rowsPerChunk == 0) ? 1 : rowsPerChunk; |
| 215 | const uint32_t actualChunkSize = rowLengthBytes * rowsPerChunk; |
| 216 | |
| 217 | const uint32_t lastChunkRows = height % rowsPerChunk; |
| 218 | const uint32_t lastChunkSize = lastChunkRows * rowLengthBytes; |
| 219 | |
| 220 | if (actualChunkSize > /*max strip size for TIFF/EP*/65536) { |
| 221 | ALOGE("%s: Strip length too long.", __FUNCTION__); |
| 222 | return BAD_VALUE; |
| 223 | } |
| 224 | |
| 225 | size_t numStrips = height / rowsPerChunk; |
| 226 | |
| 227 | // Add another strip for the incomplete chunk. |
| 228 | if (lastChunkRows > 0) { |
| 229 | numStrips += 1; |
| 230 | } |
| 231 | |
| 232 | // Put each row in it's own strip |
| 233 | uint32_t rowsPerStripVal = rowsPerChunk; |
| 234 | sp<TiffEntry> rowsPerStrip = TiffWriter::uncheckedBuildEntry(TAG_ROWSPERSTRIP, LONG, 1, |
| 235 | UNDEFINED_ENDIAN, &rowsPerStripVal); |
| 236 | |
| 237 | if (rowsPerStrip == NULL) { |
| 238 | ALOGE("%s: Could not build entry for RowsPerStrip tag.", __FUNCTION__); |
| 239 | return BAD_VALUE; |
| 240 | } |
| 241 | |
| 242 | Vector<uint32_t> byteCounts; |
| 243 | |
| 244 | for (size_t i = 0; i < numStrips; ++i) { |
| 245 | if (lastChunkRows > 0 && i == (numStrips - 1)) { |
| 246 | byteCounts.add(lastChunkSize); |
| 247 | } else { |
| 248 | byteCounts.add(actualChunkSize); |
| 249 | } |
| 250 | } |
| 251 | |
| 252 | // Set byte counts for each strip |
| 253 | sp<TiffEntry> stripByteCounts = TiffWriter::uncheckedBuildEntry(TAG_STRIPBYTECOUNTS, LONG, |
| 254 | static_cast<uint32_t>(numStrips), UNDEFINED_ENDIAN, byteCounts.array()); |
| 255 | |
| 256 | if (stripByteCounts == NULL) { |
| 257 | ALOGE("%s: Could not build entry for StripByteCounts tag.", __FUNCTION__); |
| 258 | return BAD_VALUE; |
| 259 | } |
| 260 | |
| 261 | Vector<uint32_t> stripOffsetsVector; |
| 262 | stripOffsetsVector.resize(numStrips); |
| 263 | |
| 264 | // Set uninitialized offsets |
| 265 | sp<TiffEntry> stripOffsets = TiffWriter::uncheckedBuildEntry(TAG_STRIPOFFSETS, LONG, |
| 266 | static_cast<uint32_t>(numStrips), UNDEFINED_ENDIAN, stripOffsetsVector.array()); |
| 267 | |
| 268 | if (stripOffsets == NULL) { |
| 269 | ALOGE("%s: Could not build entry for StripOffsets tag.", __FUNCTION__); |
| 270 | return BAD_VALUE; |
| 271 | } |
| 272 | |
| 273 | if(addEntry(stripByteCounts) != OK) { |
| 274 | ALOGE("%s: Could not add entry for StripByteCounts to IFD %u", __FUNCTION__, mIfdId); |
| 275 | return BAD_VALUE; |
| 276 | } |
| 277 | |
| 278 | if(addEntry(rowsPerStrip) != OK) { |
| 279 | ALOGE("%s: Could not add entry for StripByteCounts to IFD %u", __FUNCTION__, mIfdId); |
| 280 | return BAD_VALUE; |
| 281 | } |
| 282 | |
| 283 | if(addEntry(stripOffsets) != OK) { |
| 284 | ALOGE("%s: Could not add entry for StripByteCounts to IFD %u", __FUNCTION__, mIfdId); |
| 285 | return BAD_VALUE; |
| 286 | } |
| 287 | |
| 288 | mStripOffsetsInitialized = true; |
| 289 | return OK; |
| 290 | } |
| 291 | |
| 292 | bool TiffIfd::uninitializedOffsets() const { |
| 293 | return mStripOffsetsInitialized; |
| 294 | } |
| 295 | |
| 296 | status_t TiffIfd::setStripOffset(uint32_t offset) { |
| 297 | |
| 298 | // Get old offsets and bytecounts |
| 299 | sp<TiffEntry> oldOffsets = getEntry(TAG_STRIPOFFSETS); |
| 300 | if (oldOffsets == NULL) { |
| 301 | ALOGE("%s: IFD %u does not contain StripOffsets entry.", __FUNCTION__, mIfdId); |
| 302 | return BAD_VALUE; |
| 303 | } |
| 304 | |
| 305 | sp<TiffEntry> stripByteCounts = getEntry(TAG_STRIPBYTECOUNTS); |
| 306 | if (stripByteCounts == NULL) { |
| 307 | ALOGE("%s: IFD %u does not contain StripByteCounts entry.", __FUNCTION__, mIfdId); |
| 308 | return BAD_VALUE; |
| 309 | } |
| 310 | |
| 311 | uint32_t offsetsCount = oldOffsets->getCount(); |
| 312 | uint32_t byteCount = stripByteCounts->getCount(); |
| 313 | if (offsetsCount != byteCount) { |
| 314 | ALOGE("%s: StripOffsets count (%u) doesn't match StripByteCounts count (%u) in IFD %u", |
| 315 | __FUNCTION__, offsetsCount, byteCount, mIfdId); |
| 316 | return BAD_VALUE; |
| 317 | } |
| 318 | |
| 319 | const uint32_t* stripByteCountsArray = stripByteCounts->getData<uint32_t>(); |
| 320 | |
| 321 | size_t numStrips = offsetsCount; |
| 322 | |
| 323 | Vector<uint32_t> stripOffsets; |
| 324 | |
| 325 | // Calculate updated byte offsets |
| 326 | for (size_t i = 0; i < numStrips; ++i) { |
| 327 | stripOffsets.add(offset); |
| 328 | offset += stripByteCountsArray[i]; |
| 329 | } |
| 330 | |
| 331 | sp<TiffEntry> newOffsets = TiffWriter::uncheckedBuildEntry(TAG_STRIPOFFSETS, LONG, |
| 332 | static_cast<uint32_t>(numStrips), UNDEFINED_ENDIAN, stripOffsets.array()); |
| 333 | |
| 334 | if (newOffsets == NULL) { |
| 335 | ALOGE("%s: Coult not build updated offsets entry in IFD %u", __FUNCTION__, mIfdId); |
| 336 | return BAD_VALUE; |
| 337 | } |
| 338 | |
| 339 | if (addEntry(newOffsets) != OK) { |
| 340 | ALOGE("%s: Failed to add updated offsets entry in IFD %u", __FUNCTION__, mIfdId); |
| 341 | return BAD_VALUE; |
| 342 | } |
| 343 | return OK; |
| 344 | } |
| 345 | |
| 346 | uint32_t TiffIfd::getStripSize() const { |
| 347 | sp<TiffEntry> stripByteCounts = getEntry(TAG_STRIPBYTECOUNTS); |
| 348 | if (stripByteCounts == NULL) { |
| 349 | ALOGE("%s: IFD %u does not contain StripByteCounts entry.", __FUNCTION__, mIfdId); |
| 350 | return BAD_VALUE; |
| 351 | } |
| 352 | |
| 353 | uint32_t count = stripByteCounts->getCount(); |
| 354 | const uint32_t* byteCounts = stripByteCounts->getData<uint32_t>(); |
| 355 | |
| 356 | uint32_t total = 0; |
| 357 | for (size_t i = 0; i < static_cast<size_t>(count); ++i) { |
| 358 | total += byteCounts[i]; |
| 359 | } |
| 360 | return total; |
| 361 | } |
| 362 | |
Ruben Brunk | e507721 | 2014-04-28 16:39:12 -0700 | [diff] [blame] | 363 | String8 TiffIfd::toString() const { |
| 364 | size_t s = mEntries.size(); |
| 365 | String8 output; |
Ruben Brunk | 272b7f2 | 2014-05-17 01:09:04 -0700 | [diff] [blame] | 366 | output.appendFormat("[ifd: %x, num_entries: %zu, entries:\n", getId(), s); |
Ruben Brunk | e507721 | 2014-04-28 16:39:12 -0700 | [diff] [blame] | 367 | for(size_t i = 0; i < mEntries.size(); ++i) { |
| 368 | output.append("\t"); |
| 369 | output.append(mEntries[i]->toString()); |
| 370 | output.append("\n"); |
| 371 | } |
| 372 | output.append(", next_ifd: %x]", ((mNextIfd != NULL) ? mNextIfd->getId() : 0)); |
| 373 | return output; |
| 374 | } |
| 375 | |
| 376 | void TiffIfd::log() const { |
| 377 | size_t s = mEntries.size(); |
Ruben Brunk | 272b7f2 | 2014-05-17 01:09:04 -0700 | [diff] [blame] | 378 | ALOGI("[ifd: %x, num_entries: %zu, entries:\n", getId(), s); |
Ruben Brunk | e507721 | 2014-04-28 16:39:12 -0700 | [diff] [blame] | 379 | for(size_t i = 0; i < s; ++i) { |
| 380 | ALOGI("\t%s", mEntries[i]->toString().string()); |
| 381 | } |
| 382 | ALOGI(", next_ifd: %x]", ((mNextIfd != NULL) ? mNextIfd->getId() : 0)); |
| 383 | } |
| 384 | |
| 385 | } /*namespace img_utils*/ |
| 386 | } /*namespace android*/ |