/* * Copyright (C) 2016 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. */ //#define LOG_NDEBUG 0 #define LOG_TAG "DngValidateCamera" #include #include #include #include #include /** * Use DNG SDK to validate captured DNG file. * * This code is largely based on the dng_validate.cpp implementation included * with the DNG SDK. The portions of this file that are from the DNG SDK are * covered by the the DNG SDK license in /external/dng_sdk/LICENSE */ #include "dng_color_space.h" #include "dng_date_time.h" #include "dng_exceptions.h" #include "dng_file_stream.h" #include "dng_globals.h" #include "dng_host.h" #include "dng_ifd.h" #include "dng_image_writer.h" #include "dng_info.h" #include "dng_linearization_info.h" #include "dng_mosaic_info.h" #include "dng_negative.h" #include "dng_preview.h" #include "dng_render.h" #include "dng_simple_image.h" #include "dng_tag_codes.h" #include "dng_tag_types.h" #include "dng_tag_values.h" // Version of DNG validate referenced for this implementation #define kDNGValidateVersion "1.4" static bool gFourColorBayer = false; static int32 gMosaicPlane = -1; static uint32 gPreferredSize = 0; static uint32 gMinimumSize = 0; static uint32 gMaximumSize = 0; static uint32 gProxyDNGSize = 0; static const dng_color_space *gFinalSpace = &dng_space_sRGB::Get(); static uint32 gFinalPixelType = ttByte; static dng_string gDumpStage1; static dng_string gDumpStage2; static dng_string gDumpStage3; static dng_string gDumpTIF; static dng_string gDumpDNG; /** * Validate DNG file in provided buffer. * * Returns dng_error_none (0) on success, otherwise one of the * dng_error_code enum values is returned. * * Warnings and errors found during validation are printed to stderr */ static dng_error_code dng_validate(const void* data, uint32_t count) { ALOGI("Validating DNG buffer"); try { dng_stream stream(data, count); dng_host host; host.SetPreferredSize(gPreferredSize); host.SetMinimumSize(gMinimumSize); host.SetMaximumSize(gMaximumSize); host.ValidateSizes(); if (host.MinimumSize()) { host.SetForPreview(true); gDumpDNG.Clear(); } if (gDumpDNG.NotEmpty()) { host.SetSaveDNGVersion(dngVersion_SaveDefault); host.SetSaveLinearDNG(false); host.SetKeepOriginalFile(false); } // Read into the negative. AutoPtr negative; { dng_info info; info.Parse(host, stream); info.PostParse(host); if (!info.IsValidDNG()) { return dng_error_bad_format; } negative.Reset(host.Make_dng_negative()); negative->Parse(host, stream, info); negative->PostParse(host, stream, info); { dng_timer timer("Raw image read time"); negative->ReadStage1Image(host, stream, info); } if (info.fMaskIndex != -1) { dng_timer timer("Transparency mask read time"); negative->ReadTransparencyMask(host, stream, info); } negative->ValidateRawImageDigest(host); } // Option to write stage 1 image. if (gDumpStage1.NotEmpty()) { dng_file_stream stream2 (gDumpStage1.Get(), true); const dng_image &stage1 = *negative->Stage1Image(); dng_image_writer writer; writer.WriteTIFF(host, stream2, stage1, stage1.Planes() >= 3 ? piRGB : piBlackIsZero); gDumpStage1.Clear(); } // Metadata. negative->SynchronizeMetadata(); // Four color Bayer option. if (gFourColorBayer) { negative->SetFourColorBayer(); } // Build stage 2 image. { dng_timer timer("Linearization time"); negative->BuildStage2Image(host); } if (gDumpStage2.NotEmpty()) { dng_file_stream stream2(gDumpStage2.Get(), true); const dng_image &stage2 = *negative->Stage2Image(); dng_image_writer writer; writer.WriteTIFF (host, stream2, stage2, stage2.Planes() >= 3 ? piRGB : piBlackIsZero); gDumpStage2.Clear(); } // Build stage 3 image. { dng_timer timer("Interpolate time"); negative->BuildStage3Image(host, gMosaicPlane); } // Convert to proxy, if requested. if (gProxyDNGSize) { dng_timer timer("ConvertToProxy time"); dng_image_writer writer; negative->ConvertToProxy(host, writer, gProxyDNGSize); } // Flatten transparency, if required. if (negative->NeedFlattenTransparency(host)) { dng_timer timer("FlattenTransparency time"); negative->FlattenTransparency(host); } if (gDumpStage3.NotEmpty()) { dng_file_stream stream2(gDumpStage3.Get(), true); const dng_image &stage3 = *negative->Stage3Image(); dng_image_writer writer; writer.WriteTIFF (host, stream2, stage3, stage3.Planes () >= 3 ? piRGB : piBlackIsZero); gDumpStage3.Clear(); } // Output DNG file if requested. if (gDumpDNG.NotEmpty()) { // Build the preview list. dng_preview_list previewList; dng_date_time_info dateTimeInfo; CurrentDateTimeAndZone(dateTimeInfo); for (uint32 previewIndex = 0; previewIndex < 2; previewIndex++) { // Skip preview if writing a compresssed main image to save space // in this example code. if (negative->RawJPEGImage() != NULL && previewIndex > 0) { break; } // Report timing. dng_timer timer(previewIndex == 0 ? "Build thumbnail time" : "Build preview time"); // Render a preview sized image. AutoPtr previewImage; { dng_render render (host, *negative); render.SetFinalSpace (negative->IsMonochrome() ? dng_space_GrayGamma22::Get() : dng_space_sRGB::Get()); render.SetFinalPixelType (ttByte); render.SetMaximumSize (previewIndex == 0 ? 256 : 1024); previewImage.Reset (render.Render()); } // Don't write the preview if it is same size as thumbnail. if (previewIndex > 0 && Max_uint32(previewImage->Bounds().W(), previewImage->Bounds().H()) <= 256) { break; } // If we have compressed JPEG data, create a compressed thumbnail. Otherwise // save a uncompressed thumbnail. bool useCompressedPreview = (negative->RawJPEGImage() != NULL) || (previewIndex > 0); AutoPtr preview (useCompressedPreview ? (dng_preview *) new dng_jpeg_preview : (dng_preview *) new dng_image_preview); // Setup up preview info. preview->fInfo.fApplicationName.Set("dng_validate"); preview->fInfo.fApplicationVersion.Set(kDNGValidateVersion); preview->fInfo.fSettingsName.Set("Default"); preview->fInfo.fColorSpace = previewImage->Planes() == 1 ? previewColorSpace_GrayGamma22 : previewColorSpace_sRGB; preview->fInfo.fDateTime = dateTimeInfo.Encode_ISO_8601(); if (!useCompressedPreview) { dng_image_preview *imagePreview = static_cast(preview.Get()); imagePreview->fImage.Reset(previewImage.Release()); } else { dng_jpeg_preview *jpegPreview = static_cast(preview.Get()); int32 quality = (previewIndex == 0 ? 8 : 5); dng_image_writer writer; writer.EncodeJPEGPreview (host, *previewImage, *jpegPreview, quality); } previewList.Append (preview); } // Write DNG file. dng_file_stream stream2(gDumpDNG.Get(), true); { dng_timer timer("Write DNG time"); dng_image_writer writer; writer.WriteDNG(host, stream2, *negative.Get(), &previewList, dngVersion_Current, false); } gDumpDNG.Clear(); } // Output TIF file if requested. if (gDumpTIF.NotEmpty()) { // Render final image. dng_render render(host, *negative); render.SetFinalSpace(*gFinalSpace ); render.SetFinalPixelType(gFinalPixelType); if (host.MinimumSize()) { dng_point stage3Size = negative->Stage3Image()->Size(); render.SetMaximumSize (Max_uint32(stage3Size.v, stage3Size.h)); } AutoPtr finalImage; { dng_timer timer("Render time"); finalImage.Reset(render.Render()); } finalImage->Rotate(negative->Orientation()); // Now that Camera Raw supports non-raw formats, we should // not keep any Camera Raw settings in the XMP around when // writing rendered files. #if qDNGUseXMP if (negative->GetXMP()) { negative->GetXMP()->RemoveProperties(XMP_NS_CRS); negative->GetXMP()->RemoveProperties(XMP_NS_CRSS); } #endif // Write TIF file. dng_file_stream stream2(gDumpTIF.Get(), true); { dng_timer timer("Write TIFF time"); dng_image_writer writer; writer.WriteTIFF(host, stream2, *finalImage.Get(), finalImage->Planes() >= 3 ? piRGB : piBlackIsZero, ccUncompressed, negative.Get(), &render.FinalSpace()); } gDumpTIF.Clear(); } } catch (const dng_exception &except) { return except.ErrorCode(); } catch (...) { return dng_error_unknown; } ALOGI("DNG validation complete"); return dng_error_none; } extern "C" jboolean Java_android_hardware_camera2_cts_DngCreatorTest_validateDngNative( JNIEnv* env, jclass /*clazz*/, jbyteArray dngBuffer) { jbyte* buffer = env->GetByteArrayElements(dngBuffer, NULL); jsize bufferCount = env->GetArrayLength(dngBuffer); if (buffer == nullptr) { ALOGE("Unable to map DNG buffer to native"); return JNI_FALSE; } // DNG parsing warnings/errors fprintfs are spread throughout the DNG SDK, // guarded by the qDNGValidate define flag. To avoid modifying the SDK, // redirect stderr to a pipe to capture output locally. int pipeFds[2]; int err; err = pipe(pipeFds); if (err != 0) { ALOGE("Error redirecting dng_validate output: %d", errno); env->ReleaseByteArrayElements(dngBuffer, buffer, 0); return JNI_FALSE; } int stderrFd = dup(fileno(stderr)); dup2(pipeFds[1], fileno(stderr)); close(pipeFds[1]); // Actually run the validation dng_error_code dng_err = dng_validate(buffer, bufferCount); env->ReleaseByteArrayElements(dngBuffer, buffer, 0); // Restore stderr and read out pipe dup2(stderrFd, fileno(stderr)); std::stringstream errorStream; const size_t BUF_SIZE = 256; char readBuf[BUF_SIZE]; ssize_t count = 0; while((count = read(pipeFds[0], readBuf, BUF_SIZE)) > 0) { errorStream.write(readBuf, count); } if (count < 0) { ALOGE("Error reading from dng_validate output pipe: %d", errno); return JNI_FALSE; } close(pipeFds[1]); std::string line; int lineCount = 0; ALOGI("Output from DNG validation:"); // dng_validate doesn't actually propagate all errors/warnings to the // return error code, so look for an error pattern in output to detect // problems. Also make sure the output is long enough since some non-error // content should always be printed. while(std::getline(errorStream, line, '\n')) { lineCount++; if ( (line.size() > 3) && (line[0] == line[1]) && (line[1] == line[2]) && (line[2] == '*') ) { // Found a warning or error, so need to fail the test if (dng_err == dng_error_none) { dng_err = dng_error_bad_format; } ALOGE("**|%s", line.c_str()); } else { ALOGI(" |%s", line.c_str()); } } // If no output is produced, assume something went wrong if (lineCount < 3) { ALOGE("Validation output less than expected!"); dng_err = dng_error_unknown; } if (dng_err != dng_error_none) { ALOGE("DNG validation failed!"); } return (dng_err == dng_error_none) ? JNI_TRUE : JNI_FALSE; }