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 */
dng_validate(const void * data,uint32_t count)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
Java_android_hardware_camera2_cts_DngCreatorTest_validateDngNative(JNIEnv * env,jclass,jbyteArray dngBuffer)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 }
470