Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(97)

Side by Side Diff: src/codec/SkRawCodec.cpp

Issue 1520403003: Prototype of RAW decoding in Skia. (Closed) Base URL: https://skia.googlesource.com/skia.git@master
Patch Set: Change the scaling logic for DNG. Address comments. Created 4 years, 11 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« src/codec/SkRawCodec.h ('K') | « src/codec/SkRawCodec.h ('k') | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 /*
2 * Copyright 2015 Google Inc.
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
8 #include "SkCodec.h"
9 #include "SkCodecPriv.h"
10 #include "SkColorPriv.h"
11 #include "SkData.h"
12 #if !defined(GOOGLE3)
13 #include "SkJpegCodec.h"
14 #endif
15 #include "SkRawCodec.h"
16 #include "SkRefCnt.h"
17 #include "SkStream.h"
18 #include "SkStreamPriv.h"
19 #include "SkSwizzler.h"
20 #include "SkTemplates.h"
21 #include "SkTypes.h"
22
23 #include "dng_color_space.h"
24 #include "dng_exceptions.h"
25 #include "dng_host.h"
26 #include "dng_info.h"
27 #include "dng_render.h"
28 #include "dng_stream.h"
29
30 #include "src/piex.h"
31
32 #include <cmath> // for std::round,floor,ceil
33 #include <limits>
34
35 namespace {
36
37 // T must be unsigned type.
38 template <class T>
39 bool safe_add_to_size_t(T arg1, T arg2, size_t* result) {
40 SkASSERT(arg1 >= 0);
41 SkASSERT(arg2 >= 0);
42 if (arg1 >= 0 && arg2 <= std::numeric_limits<T>::max() - arg1) {
43 T sum = arg1 + arg2;
44 if (sum <= std::numeric_limits<size_t>::max()) {
45 *result = static_cast<size_t>(sum);
46 return true;
47 }
48 }
49 return false;
50 }
51
52 } // namespace
53
54 // Note: this class could throw exception if it is used as dng_stream.
55 class SkRawStream : public dng_stream, public ::piex::StreamInterface {
56 public:
57 // Note that this call will take the ownership of stream.
58 explicit SkRawStream(SkStream* stream)
59 : fStream(stream), fWholeStreamRead(false) {}
60
61 ~SkRawStream() override {}
62
63 /*
64 * Creates a SkMemoryStream from the offset with size.
65 * Note: for performance reason, this function is destructive to the SkRawSt ream. One should
66 * abandon current object after the function call.
67 */
68 SkMemoryStream* copyBuffer(size_t offset, size_t size) {
69 SkAutoTUnref<SkData> data(SkData::NewUninitialized(size));
70 if (offset > fStreamBuffer.bytesWritten()) {
71 // If the offset is not buffered, read from fStream directly and ski p the buffering.
72 // This is destructive to currect object.
73 const size_t skipLength = offset - fStreamBuffer.bytesWritten();
74 if (fStream->skip(skipLength) != skipLength) {
75 return nullptr;
76 }
77 if (fStream->read(data->writable_data(), size) != size) {
78 return nullptr;
79 }
80 } else {
81 // If the buffer is already past the offset, increase the buffer to offset + size,
82 // then copy the buffer to output.
83 size_t sum;
84 if (!safe_add_to_size_t(offset, size, &sum) || !this->bufferMoreData (sum)) {
scroggo 2016/01/13 18:24:45 Again, I don't think you need to do any buffering.
yujieqin 2016/01/14 09:33:11 Done.
85 return nullptr;
86 }
87 if (!fStreamBuffer.read(data->writable_data(), offset, size)) {
88 return nullptr;
89 }
90 }
91 return new SkMemoryStream(data);
92 }
93
94 // For PIEX
95 ::piex::Error GetData(const size_t offset, const size_t length,
96 uint8* data) override {
97 if (offset == 0 && length == 0) {
98 return ::piex::Error::kOk;
99 }
100 size_t sum;
101 if (!safe_add_to_size_t(offset, length, &sum) || !this->bufferMoreData(s um)) {
102 return ::piex::Error::kFail;
103 }
104 if (!fStreamBuffer.read(data, offset, length)) {
105 return ::piex::Error::kFail;
106 }
107 return ::piex::Error::kOk;
108 }
109
110 protected:
111 // For dng_stream
112 uint64 DoGetLength() override {
113 if (!this->bufferMoreData(kReadToEnd)) { // read whole stream
114 ThrowReadFile();
115 }
116 return fStreamBuffer.bytesWritten();
117 }
118
119 // For dng_stream
120 void DoRead(void* data, uint32 count, uint64 offset) override {
121 if (count == 0 && offset == 0) {
122 return;
123 }
124 size_t sum;
125 if (!safe_add_to_size_t(static_cast<uint64>(count), offset, &sum) ||
126 !this->bufferMoreData(sum)) {
127 ThrowReadFile();
128 }
129
130 if (!fStreamBuffer.read(data, offset, count)) {
131 ThrowReadFile();
132 }
133 }
134
135 private:
136 // Note: if the newSize == kReadToEnd (0), this function will read to the en d of stream.
137 bool bufferMoreData(size_t newSize) {
138 if (newSize == kReadToEnd) {
139 if (fWholeStreamRead) { // already read-to-end.
140 return true;
141 }
142
143 // TODO: optimize for the special case when the input is SkMemoryStr eam.
144 return SkStreamCopy(&fStreamBuffer, fStream.get());
145 }
146
147 if (newSize <= fStreamBuffer.bytesWritten()) { // already buffered to n ewSize
148 return true;
149 }
150 if (fWholeStreamRead) { // newSize is larger than the whole stream.
151 return false;
152 }
153
154 const size_t sizeToRead = newSize - fStreamBuffer.bytesWritten();
155 SkAutoTMalloc<uint8> tempBuffer(sizeToRead);
156 const size_t bytesRead = fStream->read(tempBuffer.get(), sizeToRead);
157 if (bytesRead != sizeToRead) {
158 return false;
159 }
160 return fStreamBuffer.write(tempBuffer.get(), bytesRead);
161 }
162
163 SkAutoTDelete<SkStream> fStream;
164 bool fWholeStreamRead;
165
166 SkDynamicMemoryWStream fStreamBuffer;
167
168 const size_t kReadToEnd = 0;
169 };
170
171 class SkDngImage {
172 public:
173 static dng_negative* ReadDng(SkRawStream* stream, dng_host* host, dng_info* info) {
scroggo 2016/01/13 18:24:45 I think this does not need to be public?
yujieqin 2016/01/14 09:33:11 Done.
174 try {
175 host->ValidateSizes();
176 info->Parse(*host, *stream);
177 info->PostParse(*host);
178 if (!info->IsValidDNG()) {
179 return nullptr;
180 }
181
182 SkAutoTDelete<dng_negative> negative;
183 negative.reset(host->Make_dng_negative());
184 negative->Parse(*host, *stream, *info);
185 negative->PostParse(*host, *stream, *info);
186 negative->SynchronizeMetadata();
187 return negative.release();
188 } catch (...) {
189 return nullptr;
190 }
191 }
192
193 static SkDngImage* NewFromStream(SkRawStream* stream) {
194 SkAutoTDelete<dng_host> host(new dng_host);
195 SkAutoTDelete<dng_info> info(new dng_info);
196 SkAutoTDelete<dng_negative> negative(SkDngImage::ReadDng(stream, host.ge t(), info.get()));
197 if (!negative) {
198 return nullptr;
199 }
200 return new SkDngImage(stream, host.release(), info.release(), negative.r elease());
201 }
202
203 /*
204 * Renders the DNG image to the size.
205 * The rendered image will be close to the specified size, but there is no g uarantee that any
206 * of the edges will match the requested size. E.g.
207 * 100% size: 4000 x 3000
208 * requested size: 1600 x 1200
209 * returned size could be: 2000 x 1500
210 *
211 */
212 SkCodec::Result render(int width, int height) {
scroggo 2016/01/13 18:24:45 Can this just return a boolean for success or fail
yujieqin 2016/01/14 09:33:11 Changed this method to return pointer.
213 if (!fImage ||
scroggo 2016/01/13 18:24:45 I think saving the image across calls to onGetPixe
yujieqin 2016/01/14 09:33:11 Changed the logic to return dng_image directly and
214 (fImage->Size().h != width || fImage->Size().v != height))
215 {
216 // Reset the DNG image, so that we can rerender it for different siz e.
217 if (fImage) {
218 SkCodecPrintf("Warning: For RAW images, calling render() with a size that does \
219 not match the existing image size forces the decod er to render the \
220 image multiple times.");
221 fImage.reset();
222 fHost.reset(new dng_host);
223 fInfo.reset(new dng_info);
224 fNegative.reset(SkDngImage::ReadDng(fStream, fHost.get(), fInfo. get()));
225 if (!fNegative) {
226 return SkCodec::kInvalidInput;
227 }
228 }
229
230 // DNG SDK preserves the aspect ratio, so it only needs to know the longer dimension.
231 const int preferredSize = SkTMax(width, height);
232 try {
233 fHost->SetPreferredSize(preferredSize);
234 fHost->ValidateSizes();
235
236 fNegative->ReadStage1Image(*fHost, *fStream, *fInfo);
237
238 if (fInfo->fMaskIndex != -1) {
239 fNegative->ReadTransparencyMask(*fHost, *fStream, *fInfo);
240 }
241
242 fNegative->ValidateRawImageDigest(*fHost);
243 if (fNegative->IsDamaged()) {
244 return SkCodec::kInvalidInput;
245 }
246
247 const int32 kMosaicPlane = -1;
248 fNegative->BuildStage2Image(*fHost);
249 fNegative->BuildStage3Image(*fHost, kMosaicPlane);
250
251 dng_render render(*fHost, *fNegative);
252 render.SetFinalSpace(dng_space_sRGB::Get());
253 render.SetFinalPixelType(ttByte);
254
255 dng_point stage3_size = fNegative->Stage3Image()->Size();
256 render.SetMaximumSize(SkTMax(stage3_size.h, stage3_size.v));
257
258 fImage.reset(render.Render());
259
260 // Because the DNG SDK can not gaurantee to render to requested size, we allow a small
scroggo 2016/01/13 18:24:45 nit: 100 chars
yujieqin 2016/01/14 09:33:11 Done.
261 // difference. Only the overlapping region will be copied in onG etPixels().
262 const float maxDiffRatio = 1.02f;
263 const dng_point& imageSize = fImage->Size();
264 if (SkTMax(width, imageSize.h) / SkTMin(width, imageSize.h) > ma xDiffRatio ||
265 SkTMax(height, imageSize.v) / SkTMin(height, imageSize.v) > maxDiffRatio) {
266 return SkCodec::kInvalidScale;
267 }
268 return SkCodec::kSuccess;
269 } catch (...) {
270 fImage.reset();
271 return SkCodec::kInvalidInput;
272 }
273 }
274 return SkCodec::kSuccess;
275 }
276
277 SkCodec::Result writeToBuffer(dng_pixel_buffer* buffer) {
scroggo 2016/01/13 18:24:45 This only returns two values, which are essentiall
yujieqin 2016/01/14 09:33:11 Done.
278 if (!fImage) {
279 return SkCodec::kInvalidInput;
280 }
281
282 try {
283 fImage->Get(*buffer, dng_image::edge_zero);
284 return SkCodec::kSuccess;
285 } catch (...) {
286 return SkCodec::kInvalidInput;
287 }
288 }
289
290 bool hasRenderedResult() const {
291 return fImage != nullptr;
292 }
293
294 SkISize getRenderedSize() const {
295 if (!this->hasRenderedResult()) {
296 return SkISize::Make(0, 0);
297 }
298 SkISize dim;
299 dng_point imageSize = fImage->Size();
300 dim.fWidth = imageSize.h;
301 dim.fHeight = imageSize.v;
302 return dim;
303 }
304
305 SkImageInfo getImageInfo() const {
306 SkASSERT(fNegative);
307 return SkImageInfo::Make(fNegative->DefaultCropSizeH().As_real64(),
308 fNegative->DefaultCropSizeV().As_real64(),
309 kN32_SkColorType, kOpaque_SkAlphaType);
310 }
311
312 private:
313 SkDngImage(SkRawStream* stream, dng_host* host, dng_info* info, dng_negative * negative)
314 : fStream(stream)
315 , fHost(host)
316 , fInfo(info)
317 , fNegative(negative) {}
318
319 SkAutoTDelete<SkRawStream> fStream;
320 SkAutoTDelete<dng_host> fHost;
321 SkAutoTDelete<dng_info> fInfo;
322 SkAutoTDelete<dng_negative> fNegative;
323 SkAutoTDelete<dng_image> fImage;
324 };
325
326 /*
327 * Tries to handle the image with PIEX. If PIEX returns kOk and finds the previe w image, create a
328 * SkJpegCodec. If PIEX returns kFail, then the file is invalid, return nullptr. In other cases,
329 * fallback to create SkRawCodec for DNG images.
330 */
331 SkCodec* SkRawCodec::NewFromStream(SkStream* stream) {
332 SkAutoTDelete<SkRawStream> rawStream(new SkRawStream(stream));
333 ::piex::PreviewImageData imageData;
334 if (::piex::IsRaw(rawStream.get())) {
335 ::piex::Error error = ::piex::GetPreviewImageData(rawStream.get(), &imag eData);
336
337 if (error == ::piex::Error::kOk && imageData.preview_length > 0) {
338 // copyBuffer() is destructive to the rawStream. Abandon the rawStre am after this
339 // function call.
340 SkMemoryStream* memoryStream =
scroggo 2016/01/13 18:24:45 This will leak in GOOGLE3. Instead, I think you wa
yujieqin 2016/01/14 09:33:11 Done.
341 rawStream->copyBuffer(imageData.preview_offset, imageData.pr eview_length);
342 #if !defined(GOOGLE3)
343 return memoryStream ? SkJpegCodec::NewFromStream(memoryStream) : nul lptr;
344 #else
345 return nullptr;
346 #endif
347 } else if (error == ::piex::Error::kFail) {
348 return nullptr;
349 }
350 }
351
352 SkAutoTDelete<SkDngImage> dngImage(SkDngImage::NewFromStream(rawStream.relea se()));
353 if (!dngImage) {
354 return nullptr;
355 }
356
357 return new SkRawCodec(dngImage.release());
358 }
359
360 SkCodec::Result SkRawCodec::onGetPixels(const SkImageInfo& requestedInfo, void* dst,
361 size_t dstRowBytes, const Options& optio ns,
362 SkPMColor ctable[], int* ctableCount,
363 int* rowsDecoded) {
364 int width = requestedInfo.width();
365 int height = requestedInfo.height();
366 SkCodec::Result result = fDngImage->render(width, height);
367 if (result != kSuccess) {
368 return result;
369 }
370
371 // Only copy the top-left of the overlapping region.
scroggo 2016/01/13 18:24:45 In practice, how much will be "cropped" here? If t
yujieqin 2016/01/14 09:33:11 Our experiment shows that the cropping will be les
372 width = SkTMin(width, fDngImage->getRenderedSize().fWidth);
373 height = SkTMin(height, fDngImage->getRenderedSize().fHeight);
374
375 void* dstRow = dst;
376 uint8 srcRow[width * 3];
377 SkAutoTDelete<SkSwizzler> swizzler(SkSwizzler::CreateSwizzler(
378 SkSwizzler::kRGB, nullptr, requestedInfo, options));
379 SkASSERT(swizzler);
380 for (int i = 0; i < height; ++i) {
381 dng_pixel_buffer buffer;
382 buffer.fData = &srcRow[0];
383 buffer.fArea = dng_rect(i, 0, i + 1, width);
scroggo 2016/01/13 18:24:45 Every setup assignment for buffer is the same for
yujieqin 2016/01/14 09:33:11 Done.
384 buffer.fPlane = 0;
385 buffer.fPlanes = 3;
386 buffer.fColStep = buffer.fPlanes;
387 buffer.fPlaneStep = 1;
388 buffer.fPixelType = ttByte;
389 buffer.fPixelSize = sizeof(uint8);
scroggo 2016/01/13 18:24:45 Should this be uint8_t? (Actually, I just realized
yujieqin 2016/01/14 09:33:11 Done.
390 buffer.fRowStep = sizeof(srcRow);
391
392 result = fDngImage->writeToBuffer(&buffer);
scroggo 2016/01/13 18:24:45 if (!fDngImage->readRow(&buffer)) { *rowsDecod
yujieqin 2016/01/14 09:33:11 Done.
393 if (result != kSuccess) {
394 return result;
395 }
396
397 swizzler->swizzle(dstRow, &srcRow[0]);
398 dstRow = SkTAddOffset<void>(dstRow, dstRowBytes);
399 }
400 return kSuccess;
401 }
402
403 SkISize SkRawCodec::onGetScaledDimensions(float desiredScale) const {
404 SkASSERT(desiredScale <= 1.f);
405 SkISize dim = this->getInfo().dimensions();
406 const int longEdge = SkTMax(dim.fWidth, dim.fHeight);
407
408 // Limits the minimun size to be 128 on the long edge.
msarett 2016/01/13 17:15:14 This limitation seems reasonable. Is this a limit
scroggo 2016/01/13 18:24:45 I think that's okay. I believe we've had other cas
yujieqin 2016/01/14 09:33:11 Our experiment shows that the DNG SDK does not sca
scroggo 2016/01/14 15:26:38 Can you add a comment explaining that? Also, is it
409 if (desiredScale < 128.f / static_cast<float>(longEdge)) {
410 desiredScale = 128.f / static_cast<float>(longEdge);
411 }
412
413 const float finalScale = (int)(1.f/ desiredScale);
414
415 dim.fWidth = std::round(dim.fWidth / finalScale);
416 dim.fHeight = std::round(dim.fHeight / finalScale);
417 return dim;
418 }
419
420 bool SkRawCodec::onDimensionsSupported(const SkISize& dim) {
421 const SkImageInfo& info = this->getInfo();
422 return dim.width() >= 1 && dim.width() <= info.width()
msarett 2016/01/13 17:15:14 Everything below this line is dead code?
yujieqin 2016/01/14 09:33:11 Done.
423 && dim.height() >= 1 && dim.height() <= info.height();
424
425 const SkISize fullDim = this->getInfo().dimensions();
426 const float fullLongEdge = SkTMax(fullDim.fWidth, fullDim.fHeight);
427 const float longEdge = SkTMax(dim.fWidth, dim.fHeight);
428
429 SkISize sizeFloor = this->onGetScaledDimensions(1.f / std::floor(fullLongEdg e / longEdge));
430 SkISize sizeCeil = this->onGetScaledDimensions(1.f / std::ceil(fullLongEdge / longEdge));
431 return sizeFloor == dim || sizeCeil == dim;
432 }
433
434 SkRawCodec::~SkRawCodec() {}
435
436 SkRawCodec::SkRawCodec(SkDngImage* dngImage)
437 : INHERITED(dngImage->getImageInfo(), nullptr)
438 , fDngImage(dngImage) {}
OLDNEW
« src/codec/SkRawCodec.h ('K') | « src/codec/SkRawCodec.h ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698