// Calculates the number of tiles of tile_size that fit into the area in vertical and horizontal // directions.
dng_point num_tiles_in_area(const dng_point &areaSize, const dng_point_real64 &tileSize) { // FIXME: Add a ceil_div() helper in SkCodecPriv.h return dng_point(static_cast<int32>((areaSize.v + tileSize.v - 1) / tileSize.v), static_cast<int32>((areaSize.h + tileSize.h - 1) / tileSize.h));
}
// Calculate the number of tiles to process per task, taking into account the maximum number of // tasks. It prefers to increase horizontally for better locality of reference.
dng_point num_tiles_per_task(constint maxTasks, const dng_point &tilesInArea) {
dng_point tilesInTask = {1, 1}; while (num_tasks_required(tilesInTask, tilesInArea) > maxTasks) { if (tilesInTask.h < tilesInArea.h) {
++tilesInTask.h;
} elseif (tilesInTask.v < tilesInArea.v) {
++tilesInTask.v;
} else {
ThrowProgramError("num_tiles_per_task calculation is wrong.");
}
} return tilesInTask;
}
std::vector<dng_rect> compute_task_areas(constint maxTasks, const dng_rect& area, const dng_point& tileSize) {
std::vector<dng_rect> taskAreas; const dng_point tilesInArea = num_tiles_in_area(area.Size(), tileSize); const dng_point tilesPerTask = num_tiles_per_task(maxTasks, tilesInArea); const dng_point taskAreaSize = {tilesPerTask.v * tileSize.v,
tilesPerTask.h * tileSize.h}; for (int v = 0; v < tilesInArea.v; v += tilesPerTask.v) { for (int h = 0; h < tilesInArea.h; h += tilesPerTask.h) {
dng_rect taskArea;
taskArea.t = area.t + v * tileSize.v;
taskArea.l = area.l + h * tileSize.h;
taskArea.b = Min_int32(taskArea.t + taskAreaSize.v, area.b);
taskArea.r = Min_int32(taskArea.l + taskAreaSize.h, area.r);
// We only re-throw the first exception. if (!exceptions.empty()) {
Throw_dng_error(exceptions.front().ErrorCode(), nullptr, nullptr);
}
}
uint32 PerformAreaTaskThreads() override { #ifdef SK_BUILD_FOR_ANDROID // Only use 1 thread. DNGs with the warp effect require a lot of memory, // and the amount of memory required scales linearly with the number of // threads. The sample used in CTS requires over 500 MB, so even two // threads is significantly expensive. There is no good way to tell // whether the image has the warp effect. return 1; #else return kMaxMPThreads; #endif
}
private: using INHERITED = dng_host;
};
// T must be unsigned type. template <class T> bool safe_add_to_size_t(T arg1, T arg2, size_t* result) {
SkASSERT(arg1 >= 0);
SkASSERT(arg2 >= 0); if (arg1 >= 0 && arg2 <= std::numeric_limits<T>::max() - arg1) {
T sum = arg1 + arg2; if (sum <= std::numeric_limits<size_t>::max()) {
*result = static_cast<size_t>(sum); returntrue;
}
} returnfalse;
}
class SkRawStream { public: virtual ~SkRawStream() {}
/* * Gets the length of the stream. Depending on the type of stream, this may require reading to * the end of the stream.
*/ virtual uint64 getLength() = 0;
/* * Creates an SkMemoryStream from the offset with size. * Note: for performance reason, this function is destructive to the SkRawStream. One should * abandon current object after the function call.
*/ virtual std::unique_ptr<SkMemoryStream> transferBuffer(size_t offset, size_t size) = 0;
};
class SkRawLimitedDynamicMemoryWStream : public SkDynamicMemoryWStream { public:
~SkRawLimitedDynamicMemoryWStream() override {}
private: // Most of valid RAW images will not be larger than 100MB. This limit is helpful to avoid // streaming too large data chunk. We can always adjust the limit here if we need. const size_t kMaxStreamSize = 100 * 1024 * 1024; // 100MB
using INHERITED = SkDynamicMemoryWStream;
};
// Note: the maximum buffer size is 100MB (limited by SkRawLimitedDynamicMemoryWStream). class SkRawBufferedStream : public SkRawStream { public: explicit SkRawBufferedStream(std::unique_ptr<SkStream> stream)
: fStream(std::move(stream))
, fWholeStreamRead(false)
{ // Only use SkRawBufferedStream when the stream is not an asset stream.
SkASSERT(!is_asset_stream(*fStream));
}
private: // Note: if the newSize == kReadToEnd (0), this function will read to the end of stream. bool bufferMoreData(size_t newSize) { if (newSize == kReadToEnd) { if (fWholeStreamRead) { // already read-to-end. returntrue;
}
// TODO: optimize for the special case when the input is SkMemoryStream. return SkStreamCopy(&fStreamBuffer, fStream.get());
}
if (newSize <= fStreamBuffer.bytesWritten()) { // already buffered to newSize returntrue;
} if (fWholeStreamRead) { // newSize is larger than the whole stream. returnfalse;
}
// Try to read at least 8192 bytes to avoid to many small reads. const size_t kMinSizeToRead = 8192; const size_t sizeRequested = newSize - fStreamBuffer.bytesWritten(); const size_t sizeToRead = std::max(kMinSizeToRead, sizeRequested);
AutoSTMalloc<kMinSizeToRead, uint8> tempBuffer(sizeToRead); const size_t bytesRead = fStream->read(tempBuffer.get(), sizeToRead); if (bytesRead < sizeRequested) { returnfalse;
} return fStreamBuffer.write(tempBuffer.get(), bytesRead);
}
// Use a size-limited stream to avoid holding too huge buffer.
SkRawLimitedDynamicMemoryWStream fStreamBuffer;
const size_t kReadToEnd = 0;
};
class SkRawAssetStream : public SkRawStream { public: explicit SkRawAssetStream(std::unique_ptr<SkStream> stream)
: fStream(std::move(stream))
{ // Only use SkRawAssetStream when the stream is an asset stream.
SkASSERT(is_asset_stream(*fStream));
}
size_t sum; if (!safe_add_to_size_t(offset, size, &sum)) { return nullptr;
}
// This will allow read less than the requested "size", because the JPEG codec wants to // handle also a partial JPEG file. const size_t bytesToRead = std::min(sum, fStream->getLength()) - offset; if (bytesToRead == 0) { return nullptr;
}
if (fStream->getMemoryBase()) { // directly copy if getMemoryBase() is available.
sk_sp<SkData> data(SkData::MakeWithCopy( static_cast<const uint8_t*>(fStream->getMemoryBase()) + offset, bytesToRead));
fStream.reset(); return SkMemoryStream::Make(data);
} else {
sk_sp<SkData> data(SkData::MakeUninitialized(bytesToRead)); if (!fStream->seek(offset)) { return nullptr;
} const size_t bytesRead = fStream->read(data->writable_data(), bytesToRead); if (bytesRead < bytesToRead) {
data = SkData::MakeSubset(data.get(), 0, bytesRead);
} return SkMemoryStream::Make(data);
}
} private:
std::unique_ptr<SkStream> fStream;
};
class SkPiexStream : public ::piex::StreamInterface { public: // Will NOT take the ownership of the stream. explicit SkPiexStream(SkRawStream* stream) : fStream(stream) {}
class SkDngImage { public: /* * Initializes the object with the information from Piex in a first attempt. This way it can * save time and storage to obtain the DNG dimensions and color filter array (CFA) pattern * which is essential for the demosaicing of the sensor image. * Note: this will take the ownership of the stream.
*/ static SkDngImage* NewFromStream(SkRawStream* stream) {
std::unique_ptr<SkDngImage> dngImage(new SkDngImage(stream)); #ifdefined(SK_BUILD_FOR_LIBFUZZER) // Libfuzzer easily runs out of memory after here. To avoid that // We just pretend all streams are invalid. Our AFL-fuzzer // should still exercise this code; it's more resistant to OOM. return nullptr; #else if (!dngImage->initFromPiex() && !dngImage->readDng()) { return nullptr;
}
return dngImage.release(); #endif
}
/* * Renders the DNG image to the size. The DNG SDK only allows scaling close to integer factors * down to 80 pixels on the short edge. The rendered image will be close to the specified size, * but there is no guarantee that any of the edges will match the requested size. E.g. * 100% size: 4000 x 3000 * requested size: 1600 x 1200 * returned size could be: 2000 x 1500
*/
dng_image* render(int width, int height) { if (!fHost || !fInfo || !fNegative || !fDngStream) { if (!this->readDng()) { return nullptr;
}
}
// DNG SDK preserves the aspect ratio, so it only needs to know the longer dimension. constint preferredSize = std::max(width, height); try { // render() takes ownership of fHost, fInfo, fNegative and fDngStream when available.
std::unique_ptr<dng_host> host(fHost.release());
std::unique_ptr<dng_info> info(fInfo.release());
std::unique_ptr<dng_negative> negative(fNegative.release());
std::unique_ptr<dng_stream> dngStream(fDngStream.release());
// Quick check if the image contains a valid TIFF header as requested by DNG format. // Does not affect ownership of stream. staticbool IsTiffHeaderValid(SkRawStream* stream) { const size_t kHeaderSize = 4; unsignedchar header[kHeaderSize]; if (!stream->read(header, 0 /* offset */, kHeaderSize)) { returnfalse;
}
// Check if the header is valid (endian info and magic number "42"). bool littleEndian; if (!is_valid_endian_marker(header, &littleEndian)) { returnfalse;
}
// The DNG SDK scales only during demosaicing, so scaling is only possible when // a mosaic info is available.
fIsScalable = cfaPatternSize.v != 0 && cfaPatternSize.h != 0;
fIsXtransImage = fIsScalable ? (cfaPatternSize.v == 6 && cfaPatternSize.h == 6) : false;
return width > 0 && height > 0;
}
bool initFromPiex() { // Does not take the ownership of rawStream.
SkPiexStream piexStream(fStream.get());
::piex::PreviewImageData imageData; if (::piex::IsRaw(&piexStream)
&& ::piex::GetPreviewImageData(&piexStream, &imageData) == ::piex::Error::kOk)
{
dng_point cfaPatternSize(imageData.cfa_pattern_dim[1], imageData.cfa_pattern_dim[0]); return this->init(static_cast<int>(imageData.full_width), static_cast<int>(imageData.full_height), cfaPatternSize);
} returnfalse;
}
bool readDng() { try { // Due to the limit of DNG SDK, we need to reset host and info.
fHost = std::make_unique<SkDngHost>(&fAllocator);
fInfo = std::make_unique<dng_info>();
fDngStream = std::make_unique<SkDngStream>(fStream.get());
fHost->ValidateSizes();
fInfo->Parse(*fHost, *fDngStream);
fInfo->PostParse(*fHost); if (!fInfo->IsValidDNG()) { returnfalse;
}
int fWidth; int fHeight; bool fIsScalable; bool fIsXtransImage;
};
/* * Tries to handle the image with PIEX. If PIEX returns kOk and finds the preview image, create a * SkJpegCodec. If PIEX returns kFail, then the file is invalid, return nullptr. In other cases, * fallback to create SkRawCodec for DNG images.
*/
std::unique_ptr<SkCodec> SkRawCodec::MakeFromStream(std::unique_ptr<SkStream> stream,
Result* result) {
SkASSERT(result); if (!stream) {
*result = SkCodec::kInvalidInput; return nullptr;
}
std::unique_ptr<SkRawStream> rawStream; if (is_asset_stream(*stream)) {
rawStream = std::make_unique<SkRawAssetStream>(std::move(stream));
} else {
rawStream = std::make_unique<SkRawBufferedStream>(std::move(stream));
}
// Does not take the ownership of rawStream.
SkPiexStream piexStream(rawStream.get());
::piex::PreviewImageData imageData; if (::piex::IsRaw(&piexStream)) {
::piex::Error error = ::piex::GetPreviewImageData(&piexStream, &imageData); if (error == ::piex::Error::kFail) {
*result = kInvalidInput; return nullptr;
}
// Theoretically PIEX can return JPEG compressed image or uncompressed RGB image. We only // handle the JPEG compressed preview image here. if (error == ::piex::Error::kOk && imageData.preview.length > 0 &&
imageData.preview.format == ::piex::Image::kJpegCompressed)
{ // transferBuffer() is destructive to the rawStream. Abandon the rawStream after this // function call. // FIXME: one may avoid the copy of memoryStream and use the buffered rawStream. auto memoryStream = rawStream->transferBuffer(imageData.preview.offset,
imageData.preview.length); if (!memoryStream) {
*result = kInvalidInput; return nullptr;
} return SkJpegCodec::MakeFromStream(std::move(memoryStream), result,
std::move(profile));
}
}
if (!SkDngImage::IsTiffHeaderValid(rawStream.get())) {
*result = kUnimplemented; return nullptr;
}
// Takes the ownership of the rawStream.
std::unique_ptr<SkDngImage> dngImage(SkDngImage::NewFromStream(rawStream.release())); if (!dngImage) {
*result = kInvalidInput; return nullptr;
}
// Because the DNG SDK can not guarantee to render to requested size, we allow a small // difference. Only the overlapping region will be converted. constfloat maxDiffRatio = 1.03f; const dng_point& imageSize = image->Size(); if (imageSize.h / (float) width > maxDiffRatio || imageSize.h < width ||
imageSize.v / (float) height > maxDiffRatio || imageSize.v < height) { return SkCodec::kInvalidScale;
}
// Limits the minimum size to be 80 on the short edge. constfloat shortEdge = static_cast<float>(std::min(dim.fWidth, dim.fHeight)); if (desiredScale < 80.f / shortEdge) {
desiredScale = 80.f / shortEdge;
}
// For Xtrans images, the integer-factor scaling does not support the half-size scaling case // (stronger downscalings are fine). In this case, returns the factor "3" scaling instead. if (fDngImage->isXtransImage() && desiredScale > 1.f / 3.f && desiredScale < 1.f) {
desiredScale = 1.f / 3.f;
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.