| // Copyright 2015 Google Inc. All Rights Reserved. |
| // |
| // 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. |
| |
| #include "cobalt/loader/image/image_decoder.h" |
| |
| #include <algorithm> |
| |
| #include "base/debug/trace_event.h" |
| #include "cobalt/loader/image/dummy_gif_image_decoder.h" |
| #include "cobalt/loader/image/image_decoder_starboard.h" |
| #include "cobalt/loader/image/jpeg_image_decoder.h" |
| #include "cobalt/loader/image/png_image_decoder.h" |
| #include "cobalt/loader/image/stub_image_decoder.h" |
| #include "cobalt/loader/image/webp_image_decoder.h" |
| #include "net/base/mime_util.h" |
| #include "net/http/http_status_code.h" |
| #include "starboard/image.h" |
| |
| namespace cobalt { |
| namespace loader { |
| namespace image { |
| |
| namespace { |
| |
| bool s_use_stub_image_decoder = false; |
| |
| void CacheMessage(std::string* result, const std::string& message) { |
| DCHECK(result); |
| |
| if (!result->empty()) { |
| result->append(" "); |
| } |
| |
| result->append(message); |
| } |
| |
| // Determine the ImageType of an image from its signature. |
| ImageDecoder::ImageType DetermineImageType(const uint8* header) { |
| if (!memcmp(header, "\xFF\xD8\xFF", 3)) { |
| return ImageDecoder::kImageTypeJPEG; |
| } else if (!memcmp(header, "GIF87a", 6) || !memcmp(header, "GIF89a", 6)) { |
| return ImageDecoder::kImageTypeGIF; |
| } else if (!memcmp(header, "\x89\x50\x4E\x47\x0D\x0A\x1A\x0A", 8)) { |
| return ImageDecoder::kImageTypePNG; |
| } else if (!memcmp(header, "RIFF", 4) && !memcmp(header + 8, "WEBPVP", 6)) { |
| return ImageDecoder::kImageTypeWebP; |
| } else { |
| return ImageDecoder::kImageTypeInvalid; |
| } |
| } |
| |
| } // namespace |
| |
| ImageDecoder::ImageDecoder(render_tree::ResourceProvider* resource_provider, |
| const SuccessCallback& success_callback, |
| const ErrorCallback& error_callback) |
| : resource_provider_(resource_provider), |
| success_callback_(success_callback), |
| error_callback_(error_callback), |
| image_type_(kImageTypeInvalid), |
| state_(resource_provider_ ? kWaitingForHeader : kSuspended), |
| is_deletion_pending_(false) { |
| signature_cache_.position = 0; |
| } |
| |
| ImageDecoder::ImageDecoder(render_tree::ResourceProvider* resource_provider, |
| const SuccessCallback& success_callback, |
| const ErrorCallback& error_callback, |
| ImageType image_type) |
| : resource_provider_(resource_provider), |
| success_callback_(success_callback), |
| error_callback_(error_callback), |
| image_type_(image_type), |
| state_(resource_provider_ ? kWaitingForHeader : kSuspended), |
| is_deletion_pending_(false) { |
| signature_cache_.position = 0; |
| } |
| |
| LoadResponseType ImageDecoder::OnResponseStarted( |
| Fetcher* fetcher, const scoped_refptr<net::HttpResponseHeaders>& headers) { |
| UNREFERENCED_PARAMETER(fetcher); |
| TRACE_EVENT0("cobalt::loader::image", "ImageDecoder::OnResponseStarted()"); |
| |
| if (state_ == kSuspended) { |
| DLOG(WARNING) << __FUNCTION__ << "[" << this << "] while suspended."; |
| return kLoadResponseContinue; |
| } |
| |
| if (headers->response_code() == net::HTTP_OK && |
| headers->GetContentLength() == 0) { |
| // The server successfully processed the request and expected some contents, |
| // but it is not returning any content. |
| state_ = kNotApplicable; |
| CacheMessage(&error_message_, "No content returned, but expected some."); |
| } |
| |
| if (headers->response_code() == net::HTTP_NO_CONTENT) { |
| // The server successfully processed the request, but is not returning any |
| // content. |
| state_ = kNotApplicable; |
| CacheMessage(&error_message_, "No content returned."); |
| } |
| |
| bool success = headers->GetMimeType(&mime_type_); |
| if (!success || !net::IsSupportedImageMimeType(mime_type_)) { |
| state_ = kNotApplicable; |
| CacheMessage(&error_message_, "Not an image mime type."); |
| } |
| |
| return kLoadResponseContinue; |
| } |
| |
| void ImageDecoder::DecodeChunk(const char* data, size_t size) { |
| TRACE_EVENT1("cobalt::loader::image_decoder", "ImageDecoder::DecodeChunk", |
| "size", size); |
| // If there's a deletion pending, then just clear out the decoder and return. |
| // There's no point in doing any additional processing that'll get thrown |
| // away without ever being used. |
| if (base::subtle::Acquire_Load(&is_deletion_pending_)) { |
| decoder_.reset(); |
| return; |
| } |
| |
| if (size == 0) { |
| DLOG(WARNING) << "Decoder received 0 bytes."; |
| return; |
| } |
| |
| DecodeChunkInternal(reinterpret_cast<const uint8*>(data), size); |
| } |
| |
| void ImageDecoder::Finish() { |
| TRACE_EVENT0("cobalt::loader::image_decoder", "ImageDecoder::Finish"); |
| // If there's a deletion pending, then just clear out the decoder and return. |
| // There's no point in doing any additional processing that'll get thrown |
| // away without ever being used. |
| if (base::subtle::Acquire_Load(&is_deletion_pending_)) { |
| decoder_.reset(); |
| return; |
| } |
| |
| switch (state_) { |
| case kDecoding: |
| DCHECK(decoder_); |
| if (decoder_->FinishWithSuccess()) { |
| if (!decoder_->has_animation()) { |
| #if SB_HAS(GRAPHICS) |
| SbDecodeTarget target = decoder_->RetrieveSbDecodeTarget(); |
| if (SbDecodeTargetIsValid(target)) { |
| success_callback_.Run(new StaticImage( |
| resource_provider_->CreateImageFromSbDecodeTarget(target))); |
| } else // NOLINT |
| #endif |
| { |
| scoped_ptr<render_tree::ImageData> image_data = |
| decoder_->RetrieveImageData(); |
| success_callback_.Run( |
| image_data ? new StaticImage(resource_provider_->CreateImage( |
| image_data.Pass())) |
| : NULL); |
| } |
| } else { |
| success_callback_.Run(decoder_->animated_image()); |
| } |
| } else { |
| error_callback_.Run(decoder_->GetTypeString() + |
| " failed to decode image."); |
| } |
| break; |
| case kWaitingForHeader: |
| if (signature_cache_.position == 0) { |
| // no image is available. |
| error_callback_.Run(error_message_); |
| } else { |
| error_callback_.Run("No enough image data for header."); |
| } |
| break; |
| case kUnsupportedImageFormat: |
| error_callback_.Run("Unsupported image format."); |
| break; |
| case kSuspended: |
| DLOG(WARNING) << __FUNCTION__ << "[" << this << "] while suspended."; |
| break; |
| case kNotApplicable: |
| // no image is available. |
| error_callback_.Run(error_message_); |
| break; |
| } |
| } |
| |
| bool ImageDecoder::Suspend() { |
| TRACE_EVENT0("cobalt::loader::image", "ImageDecoder::Suspend()"); |
| DCHECK_NE(state_, kSuspended); |
| DCHECK(resource_provider_); |
| |
| if (state_ == kDecoding) { |
| DCHECK(decoder_ || base::subtle::Acquire_Load(&is_deletion_pending_)); |
| decoder_.reset(); |
| } |
| state_ = kSuspended; |
| signature_cache_.position = 0; |
| resource_provider_ = NULL; |
| return true; |
| } |
| |
| void ImageDecoder::Resume(render_tree::ResourceProvider* resource_provider) { |
| TRACE_EVENT0("cobalt::loader::image", "ImageDecoder::Resume()"); |
| DCHECK_EQ(state_, kSuspended); |
| DCHECK(!resource_provider_); |
| DCHECK(resource_provider); |
| |
| state_ = kWaitingForHeader; |
| resource_provider_ = resource_provider; |
| } |
| |
| void ImageDecoder::SetDeletionPending() { |
| base::subtle::Acquire_Store(&is_deletion_pending_, true); |
| } |
| |
| void ImageDecoder::DecodeChunkInternal(const uint8* input_bytes, size_t size) { |
| TRACE_EVENT0("cobalt::loader::image", "ImageDecoder::DecodeChunkInternal()"); |
| switch (state_) { |
| case kWaitingForHeader: { |
| size_t consumed_size = 0; |
| if (InitializeInternalDecoder(input_bytes, size, &consumed_size)) { |
| state_ = kDecoding; |
| DCHECK(decoder_); |
| if (consumed_size == kLengthOfLongestSignature) { |
| // This case means the first chunk is large enough for matching |
| // signature. |
| decoder_->DecodeChunk(input_bytes, size); |
| } else { |
| decoder_->DecodeChunk(signature_cache_.data, |
| kLengthOfLongestSignature); |
| input_bytes += consumed_size; |
| decoder_->DecodeChunk(input_bytes, size - consumed_size); |
| } |
| } |
| } break; |
| case kDecoding: { |
| DCHECK(decoder_); |
| decoder_->DecodeChunk(input_bytes, size); |
| } break; |
| case kNotApplicable: |
| case kUnsupportedImageFormat: |
| case kSuspended: { |
| // Do not attempt to continue processing data. |
| DCHECK(!decoder_); |
| } break; |
| } |
| } |
| |
| namespace { |
| #if SB_HAS(GRAPHICS) |
| const char* GetMimeTypeFromImageType(ImageDecoder::ImageType image_type) { |
| switch (image_type) { |
| case ImageDecoder::kImageTypeJPEG: |
| return "image/jpeg"; |
| case ImageDecoder::kImageTypePNG: |
| return "image/png"; |
| case ImageDecoder::kImageTypeGIF: |
| return "image/gif"; |
| case ImageDecoder::kImageTypeWebP: |
| return "image/webp"; |
| case ImageDecoder::kImageTypeInvalid: |
| return NULL; |
| default: { NOTREACHED(); } |
| } |
| return NULL; |
| } |
| |
| // If |mime_type| is empty, |image_type| will be used to deduce the mime type. |
| scoped_ptr<ImageDataDecoder> MaybeCreateStarboardDecoder( |
| const std::string& mime_type, ImageDecoder::ImageType image_type, |
| render_tree::ResourceProvider* resource_provider) { |
| // clang-format off |
| const SbDecodeTargetFormat kPreferredFormats[] = { |
| kSbDecodeTargetFormat1PlaneRGBA, |
| kSbDecodeTargetFormat1PlaneBGRA, |
| }; |
| // clang-format on |
| |
| const char* mime_type_c_string = NULL; |
| |
| // If we weren't explicitly given a mime type (this might happen if the |
| // resource did not get fetched via HTTP), then deduce it from the image's |
| // header, if we were able to deduce that. |
| if (mime_type.empty()) { |
| mime_type_c_string = GetMimeTypeFromImageType(image_type); |
| } else { |
| mime_type_c_string = mime_type.c_str(); |
| } |
| |
| if (mime_type_c_string) { |
| // Find out if any of our preferred formats are supported for this mime |
| // type. |
| SbDecodeTargetFormat format = kSbDecodeTargetFormatInvalid; |
| for (size_t i = 0; i < SB_ARRAY_SIZE(kPreferredFormats); ++i) { |
| if (SbImageIsDecodeSupported(mime_type_c_string, kPreferredFormats[i])) { |
| format = kPreferredFormats[i]; |
| break; |
| } |
| } |
| |
| if (SbDecodeTargetIsFormatValid(format) && |
| resource_provider->SupportsSbDecodeTarget()) { |
| return make_scoped_ptr<ImageDataDecoder>(new ImageDecoderStarboard( |
| resource_provider, mime_type_c_string, format)); |
| } |
| } |
| return scoped_ptr<ImageDataDecoder>(); |
| } |
| #endif // SB_HAS(GRAPHICS) |
| |
| scoped_ptr<ImageDataDecoder> CreateImageDecoderFromImageType( |
| ImageDecoder::ImageType image_type, |
| render_tree::ResourceProvider* resource_provider) { |
| // Call different types of decoders by matching the image signature. |
| if (s_use_stub_image_decoder) { |
| return make_scoped_ptr<ImageDataDecoder>( |
| new StubImageDecoder(resource_provider)); |
| } else if (image_type == ImageDecoder::kImageTypeJPEG) { |
| return make_scoped_ptr<ImageDataDecoder>( |
| new JPEGImageDecoder(resource_provider)); |
| } else if (image_type == ImageDecoder::kImageTypePNG) { |
| return make_scoped_ptr<ImageDataDecoder>( |
| new PNGImageDecoder(resource_provider)); |
| } else if (image_type == ImageDecoder::kImageTypeWebP) { |
| return make_scoped_ptr<ImageDataDecoder>( |
| new WEBPImageDecoder(resource_provider)); |
| } else if (image_type == ImageDecoder::kImageTypeGIF) { |
| return make_scoped_ptr<ImageDataDecoder>( |
| new DummyGIFImageDecoder(resource_provider)); |
| } else { |
| return scoped_ptr<ImageDataDecoder>(); |
| } |
| } |
| } // namespace |
| |
| bool ImageDecoder::InitializeInternalDecoder(const uint8* input_bytes, |
| size_t size, |
| size_t* consumed_size) { |
| TRACE_EVENT0("cobalt::loader::image", |
| "ImageDecoder::InitializeInternalDecoder()"); |
| const size_t index = signature_cache_.position; |
| size_t fill_size = std::min(kLengthOfLongestSignature - index, size); |
| memcpy(signature_cache_.data + index, input_bytes, fill_size); |
| signature_cache_.position += fill_size; |
| *consumed_size = fill_size; |
| if (signature_cache_.position < kLengthOfLongestSignature) { |
| // Data is not enough for matching signature. |
| return false; |
| } |
| |
| if (image_type_ == kImageTypeInvalid) { |
| image_type_ = DetermineImageType(signature_cache_.data); |
| } |
| |
| #if SB_HAS(GRAPHICS) |
| decoder_ = |
| MaybeCreateStarboardDecoder(mime_type_, image_type_, resource_provider_); |
| #endif // SB_HAS(GRAPHICS) |
| |
| if (!decoder_) { |
| decoder_ = CreateImageDecoderFromImageType(image_type_, resource_provider_); |
| } |
| |
| if (!decoder_) { |
| state_ = kUnsupportedImageFormat; |
| return false; |
| } |
| |
| return true; |
| } |
| |
| // static |
| void ImageDecoder::UseStubImageDecoder() { s_use_stub_image_decoder = true; } |
| |
| } // namespace image |
| } // namespace loader |
| } // namespace cobalt |