Eino-Ville Talvala | cfa26b5 | 2016-03-07 14:54:52 -0800 | [diff] [blame] | 1 | /* |
| 2 | * Copyright (C) 2016 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 | |
| 17 | //#define LOG_NDEBUG 0 |
| 18 | #define LOG_TAG "DngValidateCamera" |
| 19 | #include <log/log.h> |
| 20 | #include <jni.h> |
| 21 | |
| 22 | #include <string> |
| 23 | #include <sstream> |
| 24 | #include <iostream> |
| 25 | |
| 26 | /** |
| 27 | * Use DNG SDK to validate captured DNG file. |
| 28 | * |
| 29 | * This code is largely based on the dng_validate.cpp implementation included |
| 30 | * with the DNG SDK. The portions of this file that are from the DNG SDK are |
| 31 | * covered by the the DNG SDK license in /external/dng_sdk/LICENSE |
| 32 | */ |
| 33 | |
| 34 | #include "dng_color_space.h" |
| 35 | #include "dng_date_time.h" |
| 36 | #include "dng_exceptions.h" |
| 37 | #include "dng_file_stream.h" |
| 38 | #include "dng_globals.h" |
| 39 | #include "dng_host.h" |
| 40 | #include "dng_ifd.h" |
| 41 | #include "dng_image_writer.h" |
| 42 | #include "dng_info.h" |
| 43 | #include "dng_linearization_info.h" |
| 44 | #include "dng_mosaic_info.h" |
| 45 | #include "dng_negative.h" |
| 46 | #include "dng_preview.h" |
| 47 | #include "dng_render.h" |
| 48 | #include "dng_simple_image.h" |
| 49 | #include "dng_tag_codes.h" |
| 50 | #include "dng_tag_types.h" |
| 51 | #include "dng_tag_values.h" |
| 52 | |
| 53 | // Version of DNG validate referenced for this implementation |
| 54 | #define kDNGValidateVersion "1.4" |
| 55 | |
| 56 | static bool gFourColorBayer = false; |
| 57 | |
| 58 | static int32 gMosaicPlane = -1; |
| 59 | |
| 60 | static uint32 gPreferredSize = 0; |
| 61 | static uint32 gMinimumSize = 0; |
| 62 | static uint32 gMaximumSize = 0; |
| 63 | |
| 64 | static uint32 gProxyDNGSize = 0; |
| 65 | |
| 66 | static const dng_color_space *gFinalSpace = &dng_space_sRGB::Get(); |
| 67 | |
| 68 | static uint32 gFinalPixelType = ttByte; |
| 69 | |
| 70 | static dng_string gDumpStage1; |
| 71 | static dng_string gDumpStage2; |
| 72 | static dng_string gDumpStage3; |
| 73 | static dng_string gDumpTIF; |
| 74 | static dng_string gDumpDNG; |
| 75 | |
| 76 | /** |
| 77 | * Validate DNG file in provided buffer. |
| 78 | * |
| 79 | * Returns dng_error_none (0) on success, otherwise one of the |
| 80 | * dng_error_code enum values is returned. |
| 81 | * |
| 82 | * Warnings and errors found during validation are printed to stderr |
| 83 | */ |
| 84 | static dng_error_code dng_validate(const void* data, uint32_t count) { |
| 85 | |
| 86 | ALOGI("Validating DNG buffer"); |
| 87 | |
| 88 | try { |
| 89 | dng_stream stream(data, count); |
| 90 | |
| 91 | dng_host host; |
| 92 | |
| 93 | host.SetPreferredSize(gPreferredSize); |
| 94 | host.SetMinimumSize(gMinimumSize); |
| 95 | host.SetMaximumSize(gMaximumSize); |
| 96 | |
| 97 | host.ValidateSizes(); |
| 98 | |
| 99 | if (host.MinimumSize()) { |
| 100 | host.SetForPreview(true); |
| 101 | gDumpDNG.Clear(); |
| 102 | } |
| 103 | |
| 104 | if (gDumpDNG.NotEmpty()) { |
| 105 | host.SetSaveDNGVersion(dngVersion_SaveDefault); |
| 106 | host.SetSaveLinearDNG(false); |
| 107 | host.SetKeepOriginalFile(false); |
| 108 | } |
| 109 | |
| 110 | // Read into the negative. |
| 111 | |
| 112 | AutoPtr<dng_negative> negative; |
| 113 | { |
| 114 | dng_info info; |
| 115 | info.Parse(host, stream); |
| 116 | info.PostParse(host); |
| 117 | if (!info.IsValidDNG()) { |
| 118 | return dng_error_bad_format; |
| 119 | } |
| 120 | |
| 121 | negative.Reset(host.Make_dng_negative()); |
| 122 | negative->Parse(host, stream, info); |
| 123 | negative->PostParse(host, stream, info); |
| 124 | |
| 125 | { |
| 126 | dng_timer timer("Raw image read time"); |
| 127 | negative->ReadStage1Image(host, stream, info); |
| 128 | } |
| 129 | |
| 130 | if (info.fMaskIndex != -1) { |
| 131 | dng_timer timer("Transparency mask read time"); |
| 132 | negative->ReadTransparencyMask(host, stream, info); |
| 133 | } |
| 134 | |
| 135 | negative->ValidateRawImageDigest(host); |
| 136 | } |
| 137 | |
| 138 | // Option to write stage 1 image. |
| 139 | |
| 140 | if (gDumpStage1.NotEmpty()) { |
| 141 | dng_file_stream stream2 (gDumpStage1.Get(), true); |
| 142 | const dng_image &stage1 = *negative->Stage1Image(); |
| 143 | dng_image_writer writer; |
| 144 | |
| 145 | writer.WriteTIFF(host, |
| 146 | stream2, |
| 147 | stage1, |
| 148 | stage1.Planes() >= 3 ? piRGB |
| 149 | : piBlackIsZero); |
| 150 | |
| 151 | gDumpStage1.Clear(); |
| 152 | } |
| 153 | |
| 154 | // Metadata. |
| 155 | |
| 156 | negative->SynchronizeMetadata(); |
| 157 | |
| 158 | // Four color Bayer option. |
| 159 | |
| 160 | if (gFourColorBayer) { |
| 161 | negative->SetFourColorBayer(); |
| 162 | } |
| 163 | |
| 164 | // Build stage 2 image. |
| 165 | |
| 166 | { |
| 167 | dng_timer timer("Linearization time"); |
| 168 | negative->BuildStage2Image(host); |
| 169 | } |
| 170 | |
| 171 | if (gDumpStage2.NotEmpty()) { |
| 172 | dng_file_stream stream2(gDumpStage2.Get(), true); |
| 173 | const dng_image &stage2 = *negative->Stage2Image(); |
| 174 | dng_image_writer writer; |
| 175 | |
| 176 | writer.WriteTIFF (host, |
| 177 | stream2, |
| 178 | stage2, |
| 179 | stage2.Planes() >= 3 ? piRGB |
| 180 | : piBlackIsZero); |
| 181 | |
| 182 | gDumpStage2.Clear(); |
| 183 | } |
| 184 | |
| 185 | // Build stage 3 image. |
| 186 | |
| 187 | { |
| 188 | dng_timer timer("Interpolate time"); |
| 189 | negative->BuildStage3Image(host, |
| 190 | gMosaicPlane); |
| 191 | } |
| 192 | |
| 193 | // Convert to proxy, if requested. |
| 194 | |
| 195 | if (gProxyDNGSize) { |
| 196 | dng_timer timer("ConvertToProxy time"); |
| 197 | dng_image_writer writer; |
| 198 | |
| 199 | negative->ConvertToProxy(host, |
| 200 | writer, |
| 201 | gProxyDNGSize); |
| 202 | } |
| 203 | |
| 204 | // Flatten transparency, if required. |
| 205 | |
| 206 | if (negative->NeedFlattenTransparency(host)) { |
| 207 | dng_timer timer("FlattenTransparency time"); |
| 208 | negative->FlattenTransparency(host); |
| 209 | } |
| 210 | |
| 211 | if (gDumpStage3.NotEmpty()) { |
| 212 | dng_file_stream stream2(gDumpStage3.Get(), true); |
| 213 | const dng_image &stage3 = *negative->Stage3Image(); |
| 214 | dng_image_writer writer; |
| 215 | |
| 216 | writer.WriteTIFF (host, |
| 217 | stream2, |
| 218 | stage3, |
| 219 | stage3.Planes () >= 3 ? piRGB |
| 220 | : piBlackIsZero); |
| 221 | |
| 222 | gDumpStage3.Clear(); |
| 223 | } |
| 224 | |
| 225 | // Output DNG file if requested. |
| 226 | |
| 227 | if (gDumpDNG.NotEmpty()) { |
| 228 | // Build the preview list. |
| 229 | dng_preview_list previewList; |
| 230 | dng_date_time_info dateTimeInfo; |
| 231 | CurrentDateTimeAndZone(dateTimeInfo); |
| 232 | |
| 233 | for (uint32 previewIndex = 0; previewIndex < 2; previewIndex++) { |
| 234 | |
| 235 | // Skip preview if writing a compresssed main image to save space |
| 236 | // in this example code. |
| 237 | if (negative->RawJPEGImage() != NULL && previewIndex > 0) { |
| 238 | break; |
| 239 | } |
| 240 | |
| 241 | // Report timing. |
| 242 | dng_timer timer(previewIndex == 0 ? "Build thumbnail time" |
| 243 | : "Build preview time"); |
| 244 | |
| 245 | // Render a preview sized image. |
| 246 | AutoPtr<dng_image> previewImage; |
| 247 | |
| 248 | { |
| 249 | dng_render render (host, *negative); |
| 250 | render.SetFinalSpace (negative->IsMonochrome() ? |
| 251 | dng_space_GrayGamma22::Get() : dng_space_sRGB::Get()); |
| 252 | render.SetFinalPixelType (ttByte); |
| 253 | render.SetMaximumSize (previewIndex == 0 ? 256 : 1024); |
| 254 | |
| 255 | previewImage.Reset (render.Render()); |
| 256 | } |
| 257 | |
| 258 | // Don't write the preview if it is same size as thumbnail. |
| 259 | |
| 260 | if (previewIndex > 0 && |
| 261 | Max_uint32(previewImage->Bounds().W(), |
| 262 | previewImage->Bounds().H()) <= 256) { |
| 263 | break; |
| 264 | } |
| 265 | |
| 266 | // If we have compressed JPEG data, create a compressed thumbnail. Otherwise |
| 267 | // save a uncompressed thumbnail. |
| 268 | bool useCompressedPreview = (negative->RawJPEGImage() != NULL) || |
| 269 | (previewIndex > 0); |
| 270 | |
| 271 | AutoPtr<dng_preview> preview (useCompressedPreview ? |
| 272 | (dng_preview *) new dng_jpeg_preview : |
| 273 | (dng_preview *) new dng_image_preview); |
| 274 | |
| 275 | // Setup up preview info. |
| 276 | |
| 277 | preview->fInfo.fApplicationName.Set("dng_validate"); |
| 278 | preview->fInfo.fApplicationVersion.Set(kDNGValidateVersion); |
| 279 | |
| 280 | preview->fInfo.fSettingsName.Set("Default"); |
| 281 | |
| 282 | preview->fInfo.fColorSpace = previewImage->Planes() == 1 ? |
| 283 | previewColorSpace_GrayGamma22 : |
| 284 | previewColorSpace_sRGB; |
| 285 | |
| 286 | preview->fInfo.fDateTime = dateTimeInfo.Encode_ISO_8601(); |
| 287 | |
| 288 | if (!useCompressedPreview) { |
| 289 | dng_image_preview *imagePreview = static_cast<dng_image_preview *>(preview.Get()); |
| 290 | imagePreview->fImage.Reset(previewImage.Release()); |
| 291 | } else { |
| 292 | dng_jpeg_preview *jpegPreview = static_cast<dng_jpeg_preview *>(preview.Get()); |
| 293 | int32 quality = (previewIndex == 0 ? 8 : 5); |
| 294 | dng_image_writer writer; |
| 295 | writer.EncodeJPEGPreview (host, |
| 296 | *previewImage, |
| 297 | *jpegPreview, |
| 298 | quality); |
| 299 | } |
| 300 | previewList.Append (preview); |
| 301 | } |
| 302 | |
| 303 | // Write DNG file. |
| 304 | |
| 305 | dng_file_stream stream2(gDumpDNG.Get(), true); |
| 306 | |
| 307 | { |
| 308 | dng_timer timer("Write DNG time"); |
| 309 | dng_image_writer writer; |
| 310 | |
| 311 | writer.WriteDNG(host, |
| 312 | stream2, |
| 313 | *negative.Get(), |
| 314 | &previewList, |
| 315 | dngVersion_Current, |
| 316 | false); |
| 317 | } |
| 318 | |
| 319 | gDumpDNG.Clear(); |
| 320 | } |
| 321 | |
| 322 | // Output TIF file if requested. |
| 323 | if (gDumpTIF.NotEmpty()) { |
| 324 | |
| 325 | // Render final image. |
| 326 | |
| 327 | dng_render render(host, *negative); |
| 328 | |
| 329 | render.SetFinalSpace(*gFinalSpace ); |
| 330 | render.SetFinalPixelType(gFinalPixelType); |
| 331 | |
| 332 | if (host.MinimumSize()) { |
| 333 | dng_point stage3Size = negative->Stage3Image()->Size(); |
| 334 | render.SetMaximumSize (Max_uint32(stage3Size.v, |
| 335 | stage3Size.h)); |
| 336 | } |
| 337 | |
| 338 | AutoPtr<dng_image> finalImage; |
| 339 | |
| 340 | { |
| 341 | dng_timer timer("Render time"); |
| 342 | finalImage.Reset(render.Render()); |
| 343 | } |
| 344 | |
| 345 | finalImage->Rotate(negative->Orientation()); |
| 346 | |
| 347 | // Now that Camera Raw supports non-raw formats, we should |
| 348 | // not keep any Camera Raw settings in the XMP around when |
| 349 | // writing rendered files. |
| 350 | #if qDNGUseXMP |
| 351 | if (negative->GetXMP()) { |
| 352 | negative->GetXMP()->RemoveProperties(XMP_NS_CRS); |
| 353 | negative->GetXMP()->RemoveProperties(XMP_NS_CRSS); |
| 354 | } |
| 355 | #endif |
| 356 | |
| 357 | // Write TIF file. |
| 358 | dng_file_stream stream2(gDumpTIF.Get(), true); |
| 359 | |
| 360 | { |
| 361 | dng_timer timer("Write TIFF time"); |
| 362 | dng_image_writer writer; |
| 363 | |
| 364 | writer.WriteTIFF(host, |
| 365 | stream2, |
| 366 | *finalImage.Get(), |
| 367 | finalImage->Planes() >= 3 ? piRGB |
| 368 | : piBlackIsZero, |
| 369 | ccUncompressed, |
| 370 | negative.Get(), |
| 371 | &render.FinalSpace()); |
| 372 | } |
| 373 | gDumpTIF.Clear(); |
| 374 | } |
| 375 | } catch (const dng_exception &except) { |
| 376 | return except.ErrorCode(); |
| 377 | } catch (...) { |
| 378 | return dng_error_unknown; |
| 379 | } |
| 380 | |
| 381 | ALOGI("DNG validation complete"); |
| 382 | |
| 383 | return dng_error_none; |
| 384 | } |
| 385 | |
| 386 | extern "C" jboolean |
| 387 | Java_android_hardware_camera2_cts_DngCreatorTest_validateDngNative( |
| 388 | JNIEnv* env, jclass /*clazz*/, jbyteArray dngBuffer) { |
| 389 | |
| 390 | jbyte* buffer = env->GetByteArrayElements(dngBuffer, NULL); |
| 391 | jsize bufferCount = env->GetArrayLength(dngBuffer); |
| 392 | if (buffer == nullptr) { |
| 393 | ALOGE("Unable to map DNG buffer to native"); |
| 394 | return JNI_FALSE; |
| 395 | } |
| 396 | |
| 397 | // DNG parsing warnings/errors fprintfs are spread throughout the DNG SDK, |
| 398 | // guarded by the qDNGValidate define flag. To avoid modifying the SDK, |
| 399 | // redirect stderr to a pipe to capture output locally. |
| 400 | |
| 401 | int pipeFds[2]; |
| 402 | int err; |
| 403 | |
| 404 | err = pipe(pipeFds); |
| 405 | if (err != 0) { |
| 406 | ALOGE("Error redirecting dng_validate output: %d", errno); |
| 407 | env->ReleaseByteArrayElements(dngBuffer, buffer, 0); |
| 408 | return JNI_FALSE; |
| 409 | } |
| 410 | |
| 411 | int stderrFd = dup(fileno(stderr)); |
| 412 | dup2(pipeFds[1], fileno(stderr)); |
| 413 | close(pipeFds[1]); |
| 414 | |
| 415 | // Actually run the validation |
| 416 | dng_error_code dng_err = dng_validate(buffer, bufferCount); |
| 417 | |
| 418 | env->ReleaseByteArrayElements(dngBuffer, buffer, 0); |
| 419 | |
| 420 | // Restore stderr and read out pipe |
| 421 | dup2(stderrFd, fileno(stderr)); |
| 422 | |
| 423 | std::stringstream errorStream; |
| 424 | const size_t BUF_SIZE = 256; |
| 425 | char readBuf[BUF_SIZE]; |
| 426 | |
| 427 | ssize_t count = 0; |
| 428 | while((count = read(pipeFds[0], readBuf, BUF_SIZE)) > 0) { |
| 429 | errorStream.write(readBuf, count); |
| 430 | } |
| 431 | if (count < 0) { |
| 432 | ALOGE("Error reading from dng_validate output pipe: %d", errno); |
| 433 | return JNI_FALSE; |
| 434 | } |
| 435 | close(pipeFds[1]); |
| 436 | |
| 437 | std::string line; |
| 438 | int lineCount = 0; |
| 439 | ALOGI("Output from DNG validation:"); |
| 440 | // dng_validate doesn't actually propagate all errors/warnings to the |
| 441 | // return error code, so look for an error pattern in output to detect |
| 442 | // problems. Also make sure the output is long enough since some non-error |
| 443 | // content should always be printed. |
| 444 | while(std::getline(errorStream, line, '\n')) { |
| 445 | lineCount++; |
| 446 | if ( (line.size() > 3) && |
| 447 | (line[0] == line[1]) && |
| 448 | (line[1] == line[2]) && |
| 449 | (line[2] == '*') ) { |
| 450 | // Found a warning or error, so need to fail the test |
| 451 | if (dng_err == dng_error_none) { |
| 452 | dng_err = dng_error_bad_format; |
| 453 | } |
| 454 | ALOGE("**|%s", line.c_str()); |
| 455 | } else { |
| 456 | ALOGI(" |%s", line.c_str()); |
| 457 | } |
| 458 | } |
| 459 | // If no output is produced, assume something went wrong |
| 460 | if (lineCount < 3) { |
| 461 | ALOGE("Validation output less than expected!"); |
| 462 | dng_err = dng_error_unknown; |
| 463 | } |
| 464 | if (dng_err != dng_error_none) { |
| 465 | ALOGE("DNG validation failed!"); |
| 466 | } |
| 467 | |
| 468 | return (dng_err == dng_error_none) ? JNI_TRUE : JNI_FALSE; |
| 469 | } |