You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
470 lines
14 KiB
470 lines
14 KiB
/*
|
|
* 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 <log/log.h>
|
|
#include <jni.h>
|
|
|
|
#include <string>
|
|
#include <sstream>
|
|
#include <iostream>
|
|
|
|
/**
|
|
* 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<dng_negative> 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<dng_image> 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<dng_preview> 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<dng_image_preview *>(preview.Get());
|
|
imagePreview->fImage.Reset(previewImage.Release());
|
|
} else {
|
|
dng_jpeg_preview *jpegPreview = static_cast<dng_jpeg_preview *>(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<dng_image> 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;
|
|
}
|